Updated on 2026-08-14
This commit is contained in:
parent
e3b857b79a
commit
02e44033c9
18 changed files with 266 additions and 316 deletions
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>MultilineLambdaItParameter:ApiResponseRaise.kt${ Timber.e(it, "Unable to perform safe API call") onError(it) }</ID>
|
||||
<ID>MultilineLambdaItParameter:DefaultCardCryptoCurrencyFactory.kt$DefaultCardCryptoCurrencyFactory${ cryptoCurrencyFactory.createCoin( blockchain = it, extraDerivationPath = null, userWallet = userWallet, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:UserTokensResponseFactory.kt$UserTokensResponseFactory${ getDefaultWalletBlockchains(userWallet = it, demoConfig = DemoConfig) .map { blockchain -> val derivationPath = networkFactory.createDerivationPath( blockchain = blockchain, extraDerivationPath = null, cardDerivationStyleProvider = userWallet.derivationStyleProvider, ).value UserTokensResponse.Token( id = blockchain.toCoinId(), accountId = accountId?.value, networkId = blockchain.toNetworkId(), derivationPath = derivationPath, name = blockchain.getCoinName(), symbol = blockchain.currency, decimals = blockchain.decimals(), contractAddress = null, ) } }</ID>
|
||||
<ID>MultilineLambdaItParameter:UserTokensSaver.kt$UserTokensSaver${ if (accountsFeatureToggles.isFeatureEnabled) { it.enrichByAccountId(userWalletId = userWalletId) } else { it } }</ID>
|
||||
<ID>NamedArguments:CryptoCurrencyFactory.kt$CryptoCurrencyFactory$createCoin(blockchain, extraDerivationPath, userWallet, accountIndex)</ID>
|
||||
<ID>NoNameShadowing:DefaultETagsStore.kt$DefaultETagsStore$key</ID>
|
||||
<ID>UnnecessaryLet:DefaultCardCryptoCurrencyFactory.kt$DefaultCardCryptoCurrencyFactory$let(::listOf)</ID>
|
||||
<ID>UseOrEmpty:UserTokensResponseAddressesEnricher.kt$UserTokensResponseAddressesEnricher$withTimeoutOrNull( FETCH_TIMEOUT_SECONDS.seconds, { multiNetworkStatusSupplier.invoke(MultiNetworkStatusProducer.Params(userWalletId)).first() }, ) ?: emptySet()</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -66,8 +66,8 @@ suspend inline fun <T> safeApiCall(
|
|||
crossinline onError: suspend (ApiResponseError) -> T,
|
||||
): T = recover(
|
||||
block = { call(ApiResponseRaise(raise = this)) },
|
||||
recover = {
|
||||
Timber.e(it, "Unable to perform safe API call")
|
||||
onError(it)
|
||||
recover = { error ->
|
||||
Timber.e(error, "Unable to perform safe API call")
|
||||
onError(error)
|
||||
},
|
||||
)
|
||||
|
|
@ -18,9 +18,9 @@ internal class DefaultETagsStore(
|
|||
) : ETagsStore {
|
||||
|
||||
override suspend fun getSyncOrNull(userWalletId: UserWalletId, key: ETagsStore.Key): String? {
|
||||
val key = getAccountsETagKey(userWalletId = userWalletId, key = key)
|
||||
val storeKey = getAccountsETagKey(userWalletId = userWalletId, key = key)
|
||||
|
||||
return appPreferencesStore.getSyncOrNull(key = key)
|
||||
return appPreferencesStore.getSyncOrNull(key = storeKey)
|
||||
}
|
||||
|
||||
override suspend fun store(userWalletId: UserWalletId, key: ETagsStore.Key, value: String) {
|
||||
|
|
@ -29,14 +29,14 @@ internal class DefaultETagsStore(
|
|||
return
|
||||
}
|
||||
|
||||
val key = getAccountsETagKey(userWalletId = userWalletId, key = key)
|
||||
val storeKey = getAccountsETagKey(userWalletId = userWalletId, key = key)
|
||||
|
||||
appPreferencesStore.store(key = key, value = value)
|
||||
appPreferencesStore.store(key = storeKey, value = value)
|
||||
}
|
||||
|
||||
override suspend fun clear(userWalletId: UserWalletId, key: ETagsStore.Key) {
|
||||
val key = getAccountsETagKey(userWalletId = userWalletId, key = key)
|
||||
appPreferencesStore.editData { it.remove(key) }
|
||||
val storeKey = getAccountsETagKey(userWalletId = userWalletId, key = key)
|
||||
appPreferencesStore.editData { it.remove(storeKey) }
|
||||
}
|
||||
|
||||
private fun getAccountsETagKey(userWalletId: UserWalletId, key: ETagsStore.Key): Preferences.Key<String> {
|
||||
|
|
|
|||
|
|
@ -86,7 +86,12 @@ class CryptoCurrencyFactory(
|
|||
val blockchain: Blockchain? = Chain.entries.find { it.id == chainId }?.blockchain
|
||||
|
||||
return if (blockchain != null) {
|
||||
createCoin(blockchain, extraDerivationPath, userWallet, accountIndex)
|
||||
createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = extraDerivationPath,
|
||||
userWallet = userWallet,
|
||||
accountIndex = accountIndex,
|
||||
)
|
||||
} else {
|
||||
Timber.e("Unable to get blockchain from chainId == $chainId")
|
||||
null
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ internal class DefaultCardCryptoCurrencyFactory(
|
|||
}
|
||||
|
||||
// single-currency wallet
|
||||
return createPrimaryCurrencyForSingleCurrencyCard(userWallet).let(::listOf)
|
||||
return listOf(createPrimaryCurrencyForSingleCurrencyCard(userWallet))
|
||||
}
|
||||
|
||||
override suspend fun createCurrenciesForMultiCurrencyCard(
|
||||
|
|
@ -106,9 +106,9 @@ internal class DefaultCardCryptoCurrencyFactory(
|
|||
|
||||
val blockchains = getDefaultWalletBlockchains(userWallet, demoConfig)
|
||||
|
||||
return blockchains.mapNotNull {
|
||||
return blockchains.mapNotNull { blockchain ->
|
||||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = it,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class UserTokensResponseAddressesEnricher @Inject constructor(
|
|||
withTimeoutOrNull(
|
||||
FETCH_TIMEOUT_SECONDS.seconds,
|
||||
{ multiNetworkStatusSupplier.invoke(MultiNetworkStatusProducer.Params(userWalletId)).first() },
|
||||
) ?: emptySet()
|
||||
).orEmpty()
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ class UserTokensResponseFactory @Inject constructor() {
|
|||
networkFactory: NetworkFactory,
|
||||
accountId: AccountId?,
|
||||
): UserTokensResponse {
|
||||
val tokens = userWallet?.let {
|
||||
getDefaultWalletBlockchains(userWallet = it, demoConfig = DemoConfig)
|
||||
val tokens = if (userWallet != null) {
|
||||
getDefaultWalletBlockchains(userWallet = userWallet, demoConfig = DemoConfig)
|
||||
.map { blockchain ->
|
||||
val derivationPath = networkFactory.createDerivationPath(
|
||||
blockchain = blockchain,
|
||||
|
|
@ -75,12 +75,14 @@ class UserTokensResponseFactory @Inject constructor() {
|
|||
contractAddress = null,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
return UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.NONE,
|
||||
sort = UserTokensResponse.SortType.MANUAL,
|
||||
tokens = tokens.orEmpty(),
|
||||
tokens = tokens,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -91,11 +91,11 @@ class UserTokensSaver(
|
|||
|
||||
return this
|
||||
.enrichByAddress(userWalletId = userWalletId)
|
||||
.let {
|
||||
.let { response ->
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
it.enrichByAccountId(userWalletId = userWalletId)
|
||||
response.enrichByAccountId(userWalletId = userWalletId)
|
||||
} else {
|
||||
it
|
||||
response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ class NetworkFactory @Inject constructor(
|
|||
blockchain = blockchain,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
),
|
||||
accountIndex = accountIndex,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -118,6 +119,7 @@ class NetworkFactory @Inject constructor(
|
|||
accountIndex = accountIndex,
|
||||
),
|
||||
canHandleTokens = canHandleTokens,
|
||||
accountIndex = accountIndex,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -125,8 +127,10 @@ class NetworkFactory @Inject constructor(
|
|||
blockchain: Blockchain,
|
||||
derivationPath: Network.DerivationPath,
|
||||
canHandleTokens: Boolean,
|
||||
accountIndex: DerivationIndex? = null,
|
||||
): Network? {
|
||||
if (!blockchain.isBlockchainSupported()) return null
|
||||
if (blockchain == Blockchain.Chia && accountIndex != DerivationIndex.Main) return null
|
||||
|
||||
return runCatching {
|
||||
Network(
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
<ID>NullCheckOnMutableProperty:VisaLibLoader.kt$VisaLibLoader$if (config != null) return@withLock requireNotNull(config)</ID>
|
||||
<ID>NullCheckOnMutableProperty:VisaLibLoader.kt$VisaLibLoader$if (provider != null) return@withLock requireNotNull(provider)</ID>
|
||||
<ID>NullableToStringCall:DefaultOnboardingRepository.kt$DefaultOnboardingRepository$${error.message}</ID>
|
||||
<ID>NullableToStringCall:DefaultTangemPayCardDetailsRepository.kt$DefaultTangemPayCardDetailsRepository$${response.result?.status}</ID>
|
||||
<ID>NullableToStringCall:TangemPayRequestPerformer.kt$TangemPayRequestPerformer$${error.message}</ID>
|
||||
<ID>RedundantSuspendModifier:DefaultVisaRepository.kt$DefaultVisaRepository$suspend</ID>
|
||||
<ID>SuspendFunSwallowedCancellation:DefaultVisaRepository.kt$DefaultVisaRepository$runCatching</ID>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
==========================================
|
||||
Detekt Baseline Updater & Issue Counter
|
||||
==========================================
|
||||
Date: 2025-11-19 15:21:36
|
||||
Date: 2025-11-20 16:34:32
|
||||
|
||||
Updating detekt baseline for debug variant...
|
||||
|
||||
|
|
@ -13,13 +13,13 @@ Counting issues in baseline files...
|
|||
==========================================
|
||||
|
||||
Summary:
|
||||
Total Issues: 1778
|
||||
Modules with Issues: 91
|
||||
Total Issues: 1744
|
||||
Modules with Issues: 90
|
||||
Average Issues per Module: 19
|
||||
|
||||
Progress:
|
||||
Fixed: 24 out of 1802 (1%)
|
||||
Remaining: 1778
|
||||
Fixed: 58 out of 1802 (3%)
|
||||
Remaining: 1744
|
||||
|
||||
==========================================
|
||||
All Modules with Issues (sorted by count)
|
||||
|
|
@ -33,7 +33,7 @@ features/onboarding-v2/impl 137
|
|||
features/onramp/impl 89
|
||||
features/send-v2/impl 80
|
||||
features/swap/impl 73
|
||||
features/hot-wallet/impl 66
|
||||
features/hot-wallet/impl 61
|
||||
features/staking/impl 56
|
||||
data/wallet-connect 55
|
||||
features/swap-v2/impl 53
|
||||
|
|
@ -49,9 +49,8 @@ domain/tokens 28
|
|||
features/swap/domain 27
|
||||
core/ui 27
|
||||
common/ui 26
|
||||
features/tangempay/details/impl 24
|
||||
data/visa 24
|
||||
features/wallet-settings/impl 21
|
||||
data/visa 23
|
||||
features/tangempay/details/impl 22
|
||||
data/nft 20
|
||||
features/swap/data 15
|
||||
data/swap 13
|
||||
|
|
@ -71,7 +70,6 @@ data/networks 9
|
|||
features/welcome/impl 8
|
||||
features/home/impl 8
|
||||
domain/transaction 8
|
||||
data/common 8
|
||||
data/account 8
|
||||
libs/tangem-sdk-api 7
|
||||
data/txhistory 7
|
||||
|
|
@ -88,6 +86,7 @@ features/account/impl 4
|
|||
features/account/api 4
|
||||
data/promo 4
|
||||
core/config-toggles 4
|
||||
features/wallet-settings/impl 3
|
||||
features/txhistory/impl 3
|
||||
features/tangempay/onboarding/impl 3
|
||||
features/create-wallet-start/impl 3
|
||||
|
|
|
|||
|
|
@ -68,13 +68,11 @@ class CryptoCurrencyBalanceFetcher(
|
|||
source to error
|
||||
}
|
||||
|
||||
check(errors.isEmpty()) {
|
||||
if (errors.isNotEmpty()) {
|
||||
val message = "Failed to fetch next sources for $userWalletId:\n" +
|
||||
errors.joinToString(separator = "\n") { "${it.first.name} – ${it.second}" }
|
||||
|
||||
Timber.e(message)
|
||||
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>BooleanPropertyNaming:AccessCodeModel.kt$AccessCodeModel$var buttonClicked = false</ID>
|
||||
<ID>BooleanPropertyNaming:AccessCodeUM.kt$AccessCodeUM$val buttonEnabled: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:AccessCodeUM.kt$AccessCodeUM$val buttonInProgress: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:AddExistingWalletImportUM.kt$AddExistingWalletImportUM$val importWalletEnabled: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:AddExistingWalletImportUM.kt$AddExistingWalletImportUM$val importWalletProgress: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:AddExistingWalletImportUM.kt$AddExistingWalletImportUM$val readyToImport: Boolean</ID>
|
||||
|
|
@ -24,8 +22,6 @@
|
|||
<ID>BooleanPropertyNaming:WalletBackupUM.kt$WalletBackupUM$val backedUp: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:WalletHardwareBackupUM.kt$WalletHardwareBackupUM$val showPurchaseBlock: Boolean = false</ID>
|
||||
<ID>MaxChainedCallsOnSameLine:UpgradeWalletModel.kt$UpgradeWalletModel$it.card.wallets.map { it.curve }.toSet().isNotEmpty()</ID>
|
||||
<ID>MultilineLambdaItParameter:AccessCodeModel.kt$AccessCodeModel${ Timber.e(it) uiState.update { it.copy(buttonInProgress = false) } }</ID>
|
||||
<ID>MultilineLambdaItParameter:AccessCodeModel.kt$AccessCodeModel${ it.copy( accessCode = value, buttonEnabled = if (params.accessCodeToConfirm != null) { value == params.accessCodeToConfirm } else { value.length == uiState.value.accessCodeLength }, accessCodeColor = when { params.accessCodeToConfirm == null -> PinTextColor.Primary value.length != uiState.value.accessCodeLength -> PinTextColor.Primary value == params.accessCodeToConfirm -> PinTextColor.Primary else -> PinTextColor.WrongCode }, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:AddExistingWalletImportModel.kt$AddExistingWalletImportModel${ Timber.e(it) setImportProgress(false) }</ID>
|
||||
<ID>MultilineLambdaItParameter:AddExistingWalletImportModel.kt$AddExistingWalletImportModel${ setImportProgress(false) when (it) { is SaveWalletError.DataError -> Timber.e(it.toString(), "Unable to save user wallet") is SaveWalletError.WalletAlreadySaved -> { uiMessageSender.send( SnackbarMessage(resourceReference(R.string.hw_import_seed_phrase_already_imported)), ) } } }</ID>
|
||||
<ID>MultilineLambdaItParameter:CreateHardwareWalletModel.kt$CreateHardwareWalletModel${ delay(HIDE_PROGRESS_DELAY) setLoading(false) when (it) { is SaveWalletError.DataError -> Timber.e(it.toString(), "Unable to save user wallet") is SaveWalletError.WalletAlreadySaved -> { userWalletsListRepository.unlock( userWalletId = userWallet.walletId, unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse), ).onRight { router.replaceAll(AppRoute.Wallet) } } } }</ID>
|
||||
|
|
@ -56,7 +52,6 @@
|
|||
<ID>MultilineLambdaItParameter:UpgradeWalletModel.kt$UpgradeWalletModel${ // Check if user attempted to upgrade before but something went wrong and a full reset is required val userWallet = coldUserWalletBuilderFactory.create(it).build() val sameWalletButNotFinishedBackup by lazy { userWallet?.walletId == params.userWalletId && BackupValidator.isValidFull(it.card).not() } val otherWalletAndAlreadyCreated by lazy { userWallet?.walletId != params.userWalletId && it.card.wallets.map { it.curve }.toSet().isNotEmpty() } if (userWallet != null && (sameWalletButNotFinishedBackup || otherWalletAndAlreadyCreated)) { startResetCardsFlow.emit(userWallet) return@doOnSuccess } delay(DELAY_SDK_DIALOG_CLOSE) tangemSdkManager.changeDisplayedCardIdNumbersCount(it) navigateToUpgradeFlow(it) }</ID>
|
||||
<ID>MultilineLambdaItParameter:ViewPhraseContent.kt${ EnumeratedTwoColumnGridItem( index = it + 1, mnemonic = "word${it + 1}", ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:ViewPhraseModel.kt$ViewPhraseModel${ it.copy( words = words.mapIndexed { index, s -> EnumeratedTwoColumnGridItem(index + 1, s) }.toImmutableList(), ) }</ID>
|
||||
<ID>NoNameShadowing:AccessCodeModel.kt$AccessCodeModel${ it.copy(buttonInProgress = false) }</ID>
|
||||
<ID>NoNameShadowing:CreateMobileWalletModel.kt$CreateMobileWalletModel${ it.copy(createButtonLoading = false) }</ID>
|
||||
<ID>NoNameShadowing:ManualBackupCheckModel.kt$ManualBackupCheckModel${ it.copy(completeButtonProgress = false) }</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:AddExistingWalletImportModel.kt$AddExistingWalletImportModel$uiState</ID>
|
||||
|
|
|
|||
|
|
@ -28,10 +28,12 @@ internal class AvailableToAddDataConverter @Inject constructor(
|
|||
availableNetworks: Set<TokenMarketInfo.Network>,
|
||||
marketParams: TokenMarketParams,
|
||||
): AvailableToAddData {
|
||||
suspend fun AccountStatus.getAvailableToAddAccount(wallet: UserWallet): AvailableToAddAccount {
|
||||
suspend fun AccountStatus.getAvailableToAddAccount(wallet: UserWallet): AvailableToAddAccount? {
|
||||
val currencies = availableNetworks
|
||||
.mapNotNull { createCryptoCurrency(wallet, it, marketParams, this.account) }
|
||||
|
||||
if (currencies.isEmpty()) return null
|
||||
|
||||
val addedNetworks = getAccountCurrencyStatusUseCase.invokeSync(wallet.walletId, currencies)
|
||||
.fold(
|
||||
ifEmpty = { emptySet() },
|
||||
|
|
@ -52,12 +54,15 @@ internal class AvailableToAddDataConverter @Inject constructor(
|
|||
suspend fun getAvailableToAddWallet(
|
||||
entry: Map.Entry<UserWalletId, PortfolioFetcher.PortfolioBalance>,
|
||||
): AvailableToAddWallet {
|
||||
val (walletId, balance) = entry
|
||||
val (_, balance) = entry
|
||||
val wallet = balance.userWallet
|
||||
val filteredNetworks = wallet.filteredAvailableNetworks(availableNetworks)
|
||||
val accounts = balance.accountsBalance.accountStatuses
|
||||
val availableToAddAccounts: Map<AccountId, AvailableToAddAccount> = accounts
|
||||
.map { it.account.accountId to it.getAvailableToAddAccount(wallet) }
|
||||
.mapNotNull { accountStatus ->
|
||||
val availableToAddAccount = accountStatus.getAvailableToAddAccount(wallet) ?: return@mapNotNull null
|
||||
accountStatus.account.accountId to availableToAddAccount
|
||||
}
|
||||
.filter { (_, account) -> account.availableToAddNetworks.isNotEmpty() }
|
||||
.toMap()
|
||||
return AvailableToAddWallet(
|
||||
|
|
|
|||
|
|
@ -9,12 +9,10 @@
|
|||
<ID>BooleanPropertyNaming:TangemPayChangePinUM.kt$TangemPayChangePinUM$val submitButtonLoading: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:TangemPayDetailsScreen.kt$var showDropdownMenu by rememberSaveable { mutableStateOf(false) }</ID>
|
||||
<ID>BooleanPropertyNaming:TangemPayDetailsUM.kt$TangemPayDetailsUM$val addFundsEnabled: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:TangemPayFreezeUnfreezeStateTransformer.kt$TangemPayFreezeUnfreezeStateTransformer$private val frozen: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:TangemPayTxHistoryListManager.kt$TangemPayTxHistoryListManager$val clearUiBatches = state.status is PaginationStatus.InitialLoading && batchListState.status is PaginationStatus.Paginating</ID>
|
||||
<ID>CanBeNonNullable:TangemPayTxHistoryDetailsModel.kt$TangemPayTxHistoryDetailsModel$txHash: String?</ID>
|
||||
<ID>MaxChainedCallsOnSameLine:TangemPayTxHistoryUiManager.kt$TangemPayTxHistoryUiManager$prevBatch?.data?.lastOrNull()?.date?.millis?.toDateFormatWithTodayYesterday()</ID>
|
||||
<ID>MultilineLambdaItParameter:TangemPayAddFundsContent.kt${ key(it.title) { TangemPayTopUpItem(state = it) } }</ID>
|
||||
<ID>MultilineLambdaItParameter:TangemPayCardDetailsBlockModel.kt$TangemPayCardDetailsBlockModel${ uiState.update( transformer = DetailsRevealedStateTransformer( details = it, onClickHide = ::hideCardDetails, ), ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:TangemPayDetailsScreen.kt${ TangemDropdownItem( item = it.dropdownItem, dismissParent = { showDropdownMenu = false }, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:TangemPayTxHistoryUiManager.kt$TangemPayTxHistoryUiManager${ it.status !is PaginationStatus.None && it.status !is PaginationStatus.InitialLoading && it.status !is PaginationStatus.InitialLoadingError }</ID>
|
||||
<ID>NullCheckOnMutableProperty:GoogleWalletUtil.kt$GoogleWalletUtil$if (walletIntent != null) { walletIntent } else { try { context.packageManager.getLaunchIntentForPackage(WALLET_PACKAGE_NAME) ?.apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } .also { walletIntent = it } } catch (exception: Exception) { Timber.tag(TAG).e(exception) null } }</ID>
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ dependencies {
|
|||
// endregion
|
||||
|
||||
// region Test libraries
|
||||
testImplementation(deps.test.junit5)
|
||||
testImplementation(projects.test.core)
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(deps.test.truth)
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -25,17 +25,15 @@ class AccountNodeRecognizer(private val blockchain: Blockchain) {
|
|||
val nodesCount = derivationPath.nodes.size
|
||||
|
||||
val index = when {
|
||||
blockchain == Blockchain.Tezos -> {
|
||||
UTXO_BLOCKCHAIN_NODE_INDEX.takeIf { nodesCount == 4 }
|
||||
}
|
||||
blockchain == Blockchain.Quai || blockchain.isUTXO -> {
|
||||
UTXO_BLOCKCHAIN_NODE_INDEX.takeIf { nodesCount == 5 }
|
||||
}
|
||||
!blockchain.isUTXO -> {
|
||||
(nodesCount - 1).takeIf { nodesCount == 3 || nodesCount == 5 }
|
||||
}
|
||||
else -> null
|
||||
// region Ethereum-like blockchains
|
||||
blockchain == Blockchain.Tezos -> ACCOUNT_NODE_INDEX
|
||||
blockchain == Blockchain.Quai -> ACCOUNT_NODE_INDEX
|
||||
blockchain.isEvm() -> ADDRESS_INDEX_NODE_INDEX
|
||||
// endregion
|
||||
blockchain.isUTXO -> ACCOUNT_NODE_INDEX
|
||||
else -> ACCOUNT_NODE_INDEX
|
||||
}
|
||||
.takeIf { nodesCount >= it }
|
||||
|
||||
if (index == null) {
|
||||
Timber.e("Cannot determine account node index for ${blockchain.fullName}: ${derivationPath.rawPath}")
|
||||
|
|
@ -251,6 +249,7 @@ class AccountNodeRecognizer(private val blockchain: Blockchain) {
|
|||
}
|
||||
|
||||
private companion object {
|
||||
const val UTXO_BLOCKCHAIN_NODE_INDEX = 2
|
||||
const val ACCOUNT_NODE_INDEX = 2
|
||||
const val ADDRESS_INDEX_NODE_INDEX = 4
|
||||
}
|
||||
}
|
||||
|
|
@ -3,175 +3,214 @@ package com.tangem.lib.crypto.derivation
|
|||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.test.core.ProvideTestModels
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class AccountNodeRecognizerTest {
|
||||
|
||||
private val utxoBlockchain = Blockchain.Bitcoin
|
||||
private val ethLikeBlockchain = Blockchain.Ethereum
|
||||
|
||||
@Nested
|
||||
inner class RecognizeAsDerivationPath {
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Recognize {
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and UTXO blockchain`() {
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun recognize(model: TestModel) {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/1'/0/0")
|
||||
val recognizer = AccountNodeRecognizer(blockchain = model.blockchain)
|
||||
val derivationPath = DerivationPath(rawPath = model.derivationPath)
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
val expected = 1
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
Truth.assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/1'/0")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
private fun provideTestModels(): List<TestModel> {
|
||||
return provideEthLikeTestModels() +
|
||||
provideUTXOTestModels() +
|
||||
provideOtherBlockchainTests()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and non-UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/1'/2/3")
|
||||
private fun provideEthLikeTestModels() = listOf(
|
||||
// region Tezos blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tezos,
|
||||
derivationPath = "m/44'/1729'/1'/0/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tezos,
|
||||
derivationPath = "m/44'/1729'/1'/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tezos,
|
||||
derivationPath = "m/44'/1729'/1'",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tezos,
|
||||
derivationPath = "m/44'/1729'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
// region Quai blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Quai,
|
||||
derivationPath = "m/44'/994'/1'/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Quai,
|
||||
derivationPath = "m/44'/994'/1'",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Quai,
|
||||
derivationPath = "m/44'/994'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
|
||||
// Assert
|
||||
val expected = 3
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
// region Ethereum-like blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
derivationPath = "m/44'/60'/0'/0/1",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
derivationPath = "m/44'/60'/0'/0",
|
||||
expected = null,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
derivationPath = "m/44'/60'/0'",
|
||||
expected = null,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
derivationPath = "m/44'/60'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and non-UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/1'/2")
|
||||
private fun provideUTXOTestModels() = listOf(
|
||||
// region Bitcoin blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Bitcoin,
|
||||
derivationPath = "m/44'/0'/1'/0/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Bitcoin,
|
||||
derivationPath = "m/44'/0'/1'/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Bitcoin,
|
||||
derivationPath = "m/44'/0'/1'",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Bitcoin,
|
||||
derivationPath = "m/44'/0'",
|
||||
expected = null,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Bitcoin,
|
||||
derivationPath = "m/44'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
private fun provideOtherBlockchainTests(): List<TestModel> = listOf(
|
||||
// region Solana blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Solana,
|
||||
derivationPath = "m/44'/501'/1'",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Solana,
|
||||
derivationPath = "m/44'/501'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
// region Cardano blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Cardano,
|
||||
derivationPath = "m/1852'/1815'/1'/0/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Cardano,
|
||||
derivationPath = "m/1852'/1815'/1'/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Cardano,
|
||||
derivationPath = "m/1852'/1815'/1'",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Cardano,
|
||||
derivationPath = "m/1852'/1815'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 3 nodes and non-UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/1'")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
val expected = 1
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and Tezos blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Tezos)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/0/5'")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
val expected = 0
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and Tezos blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Tezos)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/0'/5/1")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and Quai blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Quai)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/0/5'")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and Quai blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Quai)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'/0'/5/1")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
val expected = 0
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns null if derivation path is shorter than expected`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPath = DerivationPath(rawPath = "m/44'/0'")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
// region Tron blockchain
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tron,
|
||||
derivationPath = "m/44'/195'/1'/0/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tron,
|
||||
derivationPath = "m/44'/195'/1'/0",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tron,
|
||||
derivationPath = "m/44'/195'/1'",
|
||||
expected = 1,
|
||||
),
|
||||
TestModel(
|
||||
blockchain = Blockchain.Tron,
|
||||
derivationPath = "m/44'/195'",
|
||||
expected = null,
|
||||
),
|
||||
// endregion
|
||||
)
|
||||
}
|
||||
|
||||
data class TestModel(
|
||||
val blockchain: Blockchain,
|
||||
val derivationPath: String,
|
||||
val expected: Long?,
|
||||
)
|
||||
|
||||
@Nested
|
||||
inner class RecognizeAsString {
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and UTXO blockchain`() {
|
||||
fun `GIVEN empty derivation path THEN returns null`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPathValue = "m/44'/0'/1'/0/0"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
val expected = 1
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPathValue = "m/44'/0'/1'/0"
|
||||
val derivationPathValue = "invalid/path"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
|
@ -181,115 +220,7 @@ internal class AccountNodeRecognizerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and non-UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPathValue = "m/44'/0'/1'/2/3"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
val expected = 3
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and non-UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPathValue = "m/44'/0'/1'/2"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 3 nodes and non-UTXO blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPathValue = "m/44'/0'/1'"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
val expected = 1
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and Tezos blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Tezos)
|
||||
val derivationPathValue = "m/44'/0'/0/5'"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
val expected = 0
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and Tezos blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Tezos)
|
||||
val derivationPathValue = "m/44'/0'/0'/5/1"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 4 nodes and Quai blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Quai)
|
||||
val derivationPathValue = "m/44'/0'/0/5'"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns account node value for a derivation path with 5 nodes and Quai blockchain`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(Blockchain.Quai)
|
||||
val derivationPathValue = "m/44'/0'/0'/5/1"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
val expected = 0
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns null if derivation path is shorter than expected`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(ethLikeBlockchain)
|
||||
val derivationPathValue = "m/44'/0'"
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns null if derivation path string is invalid`() {
|
||||
fun `GIVEN invalid derivation path THEN returns null`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPathValue = "invalid/path"
|
||||
|
|
@ -301,4 +232,34 @@ internal class AccountNodeRecognizerTest {
|
|||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class RecognizeAsNetworkDerivationPath {
|
||||
|
||||
@Test
|
||||
fun `GIVEN empty derivation path THEN returns null`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPath = Network.DerivationPath.None
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPath)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN invalid derivation path THEN returns null`() {
|
||||
// Arrange
|
||||
val recognizer = AccountNodeRecognizer(utxoBlockchain)
|
||||
val derivationPathValue = Network.DerivationPath.Card("invalid/path")
|
||||
|
||||
// Act
|
||||
val actual = recognizer.recognize(derivationPathValue)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue