From 02e44033c92dfac7326b263118edaf90e0de45f1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Nov 2025 17:25:27 +0400 Subject: [PATCH] Updated on 2026-08-14 --- data/common/detekt-baseline-debug.xml | 14 - .../data/common/api/ApiResponseRaise.kt | 6 +- .../common/cache/etag/DefaultETagsStore.kt | 12 +- .../common/currency/CryptoCurrencyFactory.kt | 7 +- .../DefaultCardCryptoCurrencyFactory.kt | 6 +- .../UserTokensResponseAddressesEnricher.kt | 2 +- .../currency/UserTokensResponseFactory.kt | 8 +- .../data/common/currency/UserTokensSaver.kt | 6 +- .../data/common/network/NetworkFactory.kt | 4 + data/visa/detekt-baseline-debug.xml | 1 - detekt_baseline_report.txt | 19 +- .../utils/CryptoCurrencyBalanceFetcher.kt | 4 +- .../hot-wallet/impl/detekt-baseline-debug.xml | 5 - .../converter/AvailableToAddDataConverter.kt | 11 +- .../details/impl/detekt-baseline-debug.xml | 2 - libs/crypto/build.gradle.kts | 3 +- .../derivation/AccountNodeRecognizer.kt | 21 +- .../derivation/AccountNodeRecognizerTest.kt | 451 ++++++++---------- 18 files changed, 266 insertions(+), 316 deletions(-) delete mode 100644 data/common/detekt-baseline-debug.xml diff --git a/data/common/detekt-baseline-debug.xml b/data/common/detekt-baseline-debug.xml deleted file mode 100644 index c9e3828304..0000000000 --- a/data/common/detekt-baseline-debug.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - MultilineLambdaItParameter:ApiResponseRaise.kt${ Timber.e(it, "Unable to perform safe API call") onError(it) } - MultilineLambdaItParameter:DefaultCardCryptoCurrencyFactory.kt$DefaultCardCryptoCurrencyFactory${ cryptoCurrencyFactory.createCoin( blockchain = it, extraDerivationPath = null, userWallet = userWallet, ) } - 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, ) } } - MultilineLambdaItParameter:UserTokensSaver.kt$UserTokensSaver${ if (accountsFeatureToggles.isFeatureEnabled) { it.enrichByAccountId(userWalletId = userWalletId) } else { it } } - NamedArguments:CryptoCurrencyFactory.kt$CryptoCurrencyFactory$createCoin(blockchain, extraDerivationPath, userWallet, accountIndex) - NoNameShadowing:DefaultETagsStore.kt$DefaultETagsStore$key - UnnecessaryLet:DefaultCardCryptoCurrencyFactory.kt$DefaultCardCryptoCurrencyFactory$let(::listOf) - UseOrEmpty:UserTokensResponseAddressesEnricher.kt$UserTokensResponseAddressesEnricher$withTimeoutOrNull( FETCH_TIMEOUT_SECONDS.seconds, { multiNetworkStatusSupplier.invoke(MultiNetworkStatusProducer.Params(userWalletId)).first() }, ) ?: emptySet() - - diff --git a/data/common/src/main/kotlin/com/tangem/data/common/api/ApiResponseRaise.kt b/data/common/src/main/kotlin/com/tangem/data/common/api/ApiResponseRaise.kt index bda26c7b94..4cdf5d1178 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/api/ApiResponseRaise.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/api/ApiResponseRaise.kt @@ -66,8 +66,8 @@ suspend inline fun 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) }, ) \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/DefaultETagsStore.kt b/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/DefaultETagsStore.kt index 90921f262e..3fe201e5de 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/DefaultETagsStore.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/DefaultETagsStore.kt @@ -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 { diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/CryptoCurrencyFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/CryptoCurrencyFactory.kt index 2541ac4d46..14bb34f708 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/CryptoCurrencyFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/CryptoCurrencyFactory.kt @@ -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 diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt index f10df788e9..626f998bfb 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt @@ -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, ) diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricher.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricher.kt index 2b155a84fc..aff3f5c5fa 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricher.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseAddressesEnricher.kt @@ -27,7 +27,7 @@ class UserTokensResponseAddressesEnricher @Inject constructor( withTimeoutOrNull( FETCH_TIMEOUT_SECONDS.seconds, { multiNetworkStatusSupplier.invoke(MultiNetworkStatusProducer.Params(userWalletId)).first() }, - ) ?: emptySet() + ).orEmpty() } else { emptySet() } diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseFactory.kt index 77f5282614..85bf2f102f 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseFactory.kt @@ -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, ) } } \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt index bcac2b6715..990995357d 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt @@ -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 } } } diff --git a/data/common/src/main/kotlin/com/tangem/data/common/network/NetworkFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/network/NetworkFactory.kt index 1e7906cfbb..d3291a9f0c 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/network/NetworkFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/network/NetworkFactory.kt @@ -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( diff --git a/data/visa/detekt-baseline-debug.xml b/data/visa/detekt-baseline-debug.xml index 4c936a4a08..bf83464b1c 100644 --- a/data/visa/detekt-baseline-debug.xml +++ b/data/visa/detekt-baseline-debug.xml @@ -14,7 +14,6 @@ NullCheckOnMutableProperty:VisaLibLoader.kt$VisaLibLoader$if (config != null) return@withLock requireNotNull(config) NullCheckOnMutableProperty:VisaLibLoader.kt$VisaLibLoader$if (provider != null) return@withLock requireNotNull(provider) NullableToStringCall:DefaultOnboardingRepository.kt$DefaultOnboardingRepository$${error.message} - NullableToStringCall:DefaultTangemPayCardDetailsRepository.kt$DefaultTangemPayCardDetailsRepository$${response.result?.status} NullableToStringCall:TangemPayRequestPerformer.kt$TangemPayRequestPerformer$${error.message} RedundantSuspendModifier:DefaultVisaRepository.kt$DefaultVisaRepository$suspend SuspendFunSwallowedCancellation:DefaultVisaRepository.kt$DefaultVisaRepository$runCatching diff --git a/detekt_baseline_report.txt b/detekt_baseline_report.txt index b8cef02ecb..8bd49ad41c 100644 --- a/detekt_baseline_report.txt +++ b/detekt_baseline_report.txt @@ -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 diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyBalanceFetcher.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyBalanceFetcher.kt index adde054406..f492131ba6 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyBalanceFetcher.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyBalanceFetcher.kt @@ -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 } } } diff --git a/features/hot-wallet/impl/detekt-baseline-debug.xml b/features/hot-wallet/impl/detekt-baseline-debug.xml index 49d05f161c..75e6e8dacb 100644 --- a/features/hot-wallet/impl/detekt-baseline-debug.xml +++ b/features/hot-wallet/impl/detekt-baseline-debug.xml @@ -3,8 +3,6 @@ BooleanPropertyNaming:AccessCodeModel.kt$AccessCodeModel$var buttonClicked = false - BooleanPropertyNaming:AccessCodeUM.kt$AccessCodeUM$val buttonEnabled: Boolean - BooleanPropertyNaming:AccessCodeUM.kt$AccessCodeUM$val buttonInProgress: Boolean BooleanPropertyNaming:AddExistingWalletImportUM.kt$AddExistingWalletImportUM$val importWalletEnabled: Boolean BooleanPropertyNaming:AddExistingWalletImportUM.kt$AddExistingWalletImportUM$val importWalletProgress: Boolean BooleanPropertyNaming:AddExistingWalletImportUM.kt$AddExistingWalletImportUM$val readyToImport: Boolean @@ -24,8 +22,6 @@ BooleanPropertyNaming:WalletBackupUM.kt$WalletBackupUM$val backedUp: Boolean BooleanPropertyNaming:WalletHardwareBackupUM.kt$WalletHardwareBackupUM$val showPurchaseBlock: Boolean = false MaxChainedCallsOnSameLine:UpgradeWalletModel.kt$UpgradeWalletModel$it.card.wallets.map { it.curve }.toSet().isNotEmpty() - MultilineLambdaItParameter:AccessCodeModel.kt$AccessCodeModel${ Timber.e(it) uiState.update { it.copy(buttonInProgress = false) } } - 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 }, ) } MultilineLambdaItParameter:AddExistingWalletImportModel.kt$AddExistingWalletImportModel${ Timber.e(it) setImportProgress(false) } 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)), ) } } } 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) } } } } @@ -56,7 +52,6 @@ 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) } MultilineLambdaItParameter:ViewPhraseContent.kt${ EnumeratedTwoColumnGridItem( index = it + 1, mnemonic = "word${it + 1}", ) } MultilineLambdaItParameter:ViewPhraseModel.kt$ViewPhraseModel${ it.copy( words = words.mapIndexed { index, s -> EnumeratedTwoColumnGridItem(index + 1, s) }.toImmutableList(), ) } - NoNameShadowing:AccessCodeModel.kt$AccessCodeModel${ it.copy(buttonInProgress = false) } NoNameShadowing:CreateMobileWalletModel.kt$CreateMobileWalletModel${ it.copy(createButtonLoading = false) } NoNameShadowing:ManualBackupCheckModel.kt$ManualBackupCheckModel${ it.copy(completeButtonProgress = false) } PropertyUsedBeforeDeclaration:AddExistingWalletImportModel.kt$AddExistingWalletImportModel$uiState diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/converter/AvailableToAddDataConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/converter/AvailableToAddDataConverter.kt index 5bda3aeb4f..4bbeec4cee 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/converter/AvailableToAddDataConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/converter/AvailableToAddDataConverter.kt @@ -28,10 +28,12 @@ internal class AvailableToAddDataConverter @Inject constructor( availableNetworks: Set, 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, ): 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 = 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( diff --git a/features/tangempay/details/impl/detekt-baseline-debug.xml b/features/tangempay/details/impl/detekt-baseline-debug.xml index 6c37bced10..8e67fe2685 100644 --- a/features/tangempay/details/impl/detekt-baseline-debug.xml +++ b/features/tangempay/details/impl/detekt-baseline-debug.xml @@ -9,12 +9,10 @@ BooleanPropertyNaming:TangemPayChangePinUM.kt$TangemPayChangePinUM$val submitButtonLoading: Boolean BooleanPropertyNaming:TangemPayDetailsScreen.kt$var showDropdownMenu by rememberSaveable { mutableStateOf(false) } BooleanPropertyNaming:TangemPayDetailsUM.kt$TangemPayDetailsUM$val addFundsEnabled: Boolean - BooleanPropertyNaming:TangemPayFreezeUnfreezeStateTransformer.kt$TangemPayFreezeUnfreezeStateTransformer$private val frozen: Boolean BooleanPropertyNaming:TangemPayTxHistoryListManager.kt$TangemPayTxHistoryListManager$val clearUiBatches = state.status is PaginationStatus.InitialLoading && batchListState.status is PaginationStatus.Paginating CanBeNonNullable:TangemPayTxHistoryDetailsModel.kt$TangemPayTxHistoryDetailsModel$txHash: String? MaxChainedCallsOnSameLine:TangemPayTxHistoryUiManager.kt$TangemPayTxHistoryUiManager$prevBatch?.data?.lastOrNull()?.date?.millis?.toDateFormatWithTodayYesterday() MultilineLambdaItParameter:TangemPayAddFundsContent.kt${ key(it.title) { TangemPayTopUpItem(state = it) } } - MultilineLambdaItParameter:TangemPayCardDetailsBlockModel.kt$TangemPayCardDetailsBlockModel${ uiState.update( transformer = DetailsRevealedStateTransformer( details = it, onClickHide = ::hideCardDetails, ), ) } MultilineLambdaItParameter:TangemPayDetailsScreen.kt${ TangemDropdownItem( item = it.dropdownItem, dismissParent = { showDropdownMenu = false }, ) } MultilineLambdaItParameter:TangemPayTxHistoryUiManager.kt$TangemPayTxHistoryUiManager${ it.status !is PaginationStatus.None && it.status !is PaginationStatus.InitialLoading && it.status !is PaginationStatus.InitialLoadingError } 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 } } diff --git a/libs/crypto/build.gradle.kts b/libs/crypto/build.gradle.kts index 2adf84b11c..4b9238c713 100644 --- a/libs/crypto/build.gradle.kts +++ b/libs/crypto/build.gradle.kts @@ -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 } \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/derivation/AccountNodeRecognizer.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/derivation/AccountNodeRecognizer.kt index 37c9b75e4d..d89a55b6bc 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/derivation/AccountNodeRecognizer.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/derivation/AccountNodeRecognizer.kt @@ -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 } } \ No newline at end of file diff --git a/libs/crypto/src/test/kotlin/com/tangem/lib/crypto/derivation/AccountNodeRecognizerTest.kt b/libs/crypto/src/test/kotlin/com/tangem/lib/crypto/derivation/AccountNodeRecognizerTest.kt index 89b8cfd373..4d004e0616 100644 --- a/libs/crypto/src/test/kotlin/com/tangem/lib/crypto/derivation/AccountNodeRecognizerTest.kt +++ b/libs/crypto/src/test/kotlin/com/tangem/lib/crypto/derivation/AccountNodeRecognizerTest.kt @@ -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 { + 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 = 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() + } + } } \ No newline at end of file