From f153804a41d8c2ad231d4162316c6d3e67dcef20 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 May 2026 21:46:57 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../di/domain/DynamicAddressesDomainModule.kt | 10 + .../DynamicAddressesDerivationChecker.kt | 13 +- .../DynamicAddressesSupportedBlockchains.kt | 29 +-- .../IsDynamicAddressesAvailableUseCase.kt | 47 ++++ .../IsDynamicAddressesAvailableUseCaseTest.kt | 222 ++++++++++++++++++ .../model/DynamicAddressesDelegate.kt | 4 +- .../tokendetails/model/TokenDetailsModel.kt | 38 +-- 7 files changed, 300 insertions(+), 63 deletions(-) create mode 100644 domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/IsDynamicAddressesAvailableUseCase.kt create mode 100644 domain/dynamic-addresses/src/test/kotlin/com/tangem/domain/dynamicaddresses/IsDynamicAddressesAvailableUseCaseTest.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/DynamicAddressesDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/DynamicAddressesDomainModule.kt index 6fc115d8f3..e1323551b4 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/DynamicAddressesDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/DynamicAddressesDomainModule.kt @@ -1,10 +1,12 @@ package com.tangem.tap.di.domain import com.tangem.domain.dynamicaddresses.CreateConsolidationTransactionUseCase +import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles import com.tangem.domain.dynamicaddresses.EnableDynamicAddressesUseCase import com.tangem.domain.dynamicaddresses.GetDynamicAddressesStatusUseCase import com.tangem.domain.dynamicaddresses.GetDynamicReceiveAddressUseCase import com.tangem.domain.dynamicaddresses.GetDerivedXpubUseCase +import com.tangem.domain.dynamicaddresses.IsDynamicAddressesAvailableUseCase import com.tangem.domain.dynamicaddresses.IsDynamicAddressesConsolidationRequiredUseCase import com.tangem.domain.dynamicaddresses.IsXpubSupportedUseCase import com.tangem.domain.dynamicaddresses.repository.ConsolidationRepository @@ -67,6 +69,14 @@ internal object DynamicAddressesDomainModule { return IsXpubSupportedUseCase(walletManagersFacade) } + @Provides + @Singleton + fun provideIsDynamicAddressesAvailableUseCase( + featureToggles: DynamicAddressesFeatureToggles, + ): IsDynamicAddressesAvailableUseCase { + return IsDynamicAddressesAvailableUseCase(featureToggles) + } + @Provides @Singleton fun provideGetDerivedXpubUseCase( diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesDerivationChecker.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesDerivationChecker.kt index f2e2bf3486..d3da4b8db2 100644 --- a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesDerivationChecker.kt +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesDerivationChecker.kt @@ -1,5 +1,6 @@ package com.tangem.domain.dynamicaddresses +import com.tangem.crypto.hdWallet.DerivationNode import com.tangem.crypto.hdWallet.DerivationPath /** @@ -11,16 +12,24 @@ import com.tangem.crypto.hdWallet.DerivationPath */ object DynamicAddressesDerivationChecker { - private const val BIP44_NODE_COUNT = 5 + const val BIP44_NODE_COUNT = 5 private const val ACCOUNT_NODE_COUNT = 3 private const val CHANGE_NODE_INDEX = 3 private const val ADDRESS_INDEX_NODE_INDEX = 4 + fun parseNodes(path: String): List? { + return runCatching { DerivationPath(path).nodes }.getOrNull() + } + /** * @return `true` if [path] has zero change (node 3) and zero address_index (node 4). */ fun isBaseDerivation(path: String): Boolean { - val nodes = runCatching { DerivationPath(path).nodes }.getOrNull() ?: return false + val nodes = parseNodes(path) ?: return false + return isBaseDerivation(nodes) + } + + fun isBaseDerivation(nodes: List): Boolean { if (nodes.size < BIP44_NODE_COUNT) return false val change = nodes[CHANGE_NODE_INDEX].getIndex(includeHardened = false) diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesSupportedBlockchains.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesSupportedBlockchains.kt index 9ba156ddaa..dfdd1fcfc8 100644 --- a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesSupportedBlockchains.kt +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesSupportedBlockchains.kt @@ -4,17 +4,11 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.toNetworkId /** - * List of blockchains that support Dynamic Addresses (XPUB-based multi-address mode). - * Must match [Blockchain.isBip44DerivationStyleXPUB] from blockchain-sdk minus Kaspa (deferred). - * - * Dynamic addresses are NOT used for Legacy (m/44' for BTC/LTC) or Taproot (m/86') addresses. - * Only the default derivation style per blockchain is supported. + * Whitelist of blockchains eligible for Dynamic Addresses (XPUB-based multi-address mode). + * Mirrors [Blockchain.isBip44DerivationStyleXPUB] from blockchain-sdk minus Kaspa (deferred). */ object DynamicAddressesSupportedBlockchains { - private const val BIP44_PURPOSE = 44L - private const val BIP84_PURPOSE = 84L - private val supported = setOf( Blockchain.Bitcoin, Blockchain.BitcoinTestnet, @@ -29,26 +23,7 @@ object DynamicAddressesSupportedBlockchains { private val supportedNetworkIds = supported.map { it.toNetworkId() }.toSet() - /** - * Allowed BIP purpose nodes per network ID. - * BTC/LTC use BIP-84 (SegWit), others use BIP-44 (Legacy P2PKH). - */ - private val allowedPurposeByNetworkId: Map = buildMap { - put(Blockchain.Bitcoin.toNetworkId(), BIP84_PURPOSE) - put(Blockchain.BitcoinTestnet.toNetworkId(), BIP84_PURPOSE) - put(Blockchain.Litecoin.toNetworkId(), BIP84_PURPOSE) - put(Blockchain.BitcoinCash.toNetworkId(), BIP44_PURPOSE) - put(Blockchain.BitcoinCashTestnet.toNetworkId(), BIP44_PURPOSE) - put(Blockchain.Dogecoin.toNetworkId(), BIP44_PURPOSE) - put(Blockchain.Dash.toNetworkId(), BIP44_PURPOSE) - put(Blockchain.Ravencoin.toNetworkId(), BIP44_PURPOSE) - put(Blockchain.RavencoinTestnet.toNetworkId(), BIP44_PURPOSE) - } - fun isSupported(blockchain: Blockchain): Boolean = blockchain in supported fun isSupportedByNetworkId(networkId: String): Boolean = networkId in supportedNetworkIds - - /** Returns the allowed BIP purpose node for the given network, or null if not supported */ - fun getAllowedPurpose(networkId: String): Long? = allowedPurposeByNetworkId[networkId] } \ No newline at end of file diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/IsDynamicAddressesAvailableUseCase.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/IsDynamicAddressesAvailableUseCase.kt new file mode 100644 index 0000000000..e6e97b552b --- /dev/null +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/IsDynamicAddressesAvailableUseCase.kt @@ -0,0 +1,47 @@ +package com.tangem.domain.dynamicaddresses + +import com.tangem.blockchainsdk.utils.toBlockchain +import com.tangem.domain.dynamicaddresses.DynamicAddressesDerivationChecker.BIP44_NODE_COUNT +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.wallets.derivations.derivationStyleProvider + +/** + * Whether the Dynamic Addresses menu entry should be shown for a given (wallet, currency) pair. + * Policy check only — hardware XPUB capability is verified by [IsXpubSupportedUseCase]. + */ +class IsDynamicAddressesAvailableUseCase( + private val featureToggles: DynamicAddressesFeatureToggles, +) { + + operator fun invoke(userWallet: UserWallet, cryptoCurrency: CryptoCurrency): Boolean { + if (!featureToggles.isDynamicAddressesEnabled) return false + if (cryptoCurrency !is CryptoCurrency.Coin) return false + + val network = cryptoCurrency.network + if (!DynamicAddressesSupportedBlockchains.isSupportedByNetworkId(network.rawId)) return false + + return isWalletDefaultDerivation(userWallet, network) + } + + private fun isWalletDefaultDerivation(userWallet: UserWallet, network: Network): Boolean { + val actualPath = network.derivationPath.value ?: return false + val actualNodes = DynamicAddressesDerivationChecker.parseNodes(actualPath) ?: return false + if (!DynamicAddressesDerivationChecker.isBaseDerivation(actualNodes)) return false + + val style = userWallet.derivationStyleProvider.getDerivationStyle() ?: return false + val expectedPath = network.toBlockchain().derivationPath(style)?.rawPath ?: return false + val expectedNodes = DynamicAddressesDerivationChecker.parseNodes(expectedPath) ?: return false + if (expectedNodes.size < BIP44_NODE_COUNT) return false + + // Match purpose + coin_type; account is allowed to differ for secondary accounts. + return actualNodes[PURPOSE_NODE_INDEX] == expectedNodes[PURPOSE_NODE_INDEX] && + actualNodes[COIN_TYPE_NODE_INDEX] == expectedNodes[COIN_TYPE_NODE_INDEX] + } + + private companion object { + const val PURPOSE_NODE_INDEX = 0 + const val COIN_TYPE_NODE_INDEX = 1 + } +} \ No newline at end of file diff --git a/domain/dynamic-addresses/src/test/kotlin/com/tangem/domain/dynamicaddresses/IsDynamicAddressesAvailableUseCaseTest.kt b/domain/dynamic-addresses/src/test/kotlin/com/tangem/domain/dynamicaddresses/IsDynamicAddressesAvailableUseCaseTest.kt new file mode 100644 index 0000000000..c48c34c2b0 --- /dev/null +++ b/domain/dynamic-addresses/src/test/kotlin/com/tangem/domain/dynamicaddresses/IsDynamicAddressesAvailableUseCaseTest.kt @@ -0,0 +1,222 @@ +package com.tangem.domain.dynamicaddresses + +import com.google.common.truth.Truth.assertThat +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.derivation.DerivationStyle +import com.tangem.blockchainsdk.utils.toNetworkId +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.wallets.derivations.DerivationStyleProvider +import com.tangem.domain.wallets.derivations.derivationStyleProvider +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class IsDynamicAddressesAvailableUseCaseTest { + + private val featureToggles: DynamicAddressesFeatureToggles = mockk() + private val useCase = IsDynamicAddressesAvailableUseCase(featureToggles) + + @BeforeAll + fun setup() { + mockkStatic("com.tangem.domain.wallets.derivations.DerivationStyleProviderExtKt") + every { featureToggles.isDynamicAddressesEnabled } returns true + } + + @AfterAll + fun teardown() { + unmockkAll() + } + + // region Gating + + @Test + fun `feature toggle off returns false`() { + every { featureToggles.isDynamicAddressesEnabled } returns false + + val coin = coin(Blockchain.Bitcoin, "m/84'/0'/0'/0/0") + val result = useCase(walletWithStyle(DerivationStyle.V3), coin) + + assertThat(result).isFalse() + every { featureToggles.isDynamicAddressesEnabled } returns true // restore + } + + @Test + fun `token currency returns false`() { + val token = token(Blockchain.Ethereum, "m/44'/60'/0'/0/0") + val result = useCase(walletWithStyle(DerivationStyle.V3), token) + assertThat(result).isFalse() + } + + @Test + fun `unsupported network returns false`() { + val coin = coin(Blockchain.Ethereum, "m/44'/60'/0'/0/0") + val result = useCase(walletWithStyle(DerivationStyle.V3), coin) + assertThat(result).isFalse() + } + + @Test + fun `non-HD wallet returns false`() { + val coin = coin(Blockchain.Bitcoin, "m/84'/0'/0'/0/0") + val result = useCase(walletWithStyle(style = null), coin) + assertThat(result).isFalse() + } + + // endregion + + // region BTC: V2 (Wallet 1) ↔ V3 (Wallet 2 / Hot) + + @Test + fun `V3 wallet accepts BIP-84 BTC`() { + val coin = coin(Blockchain.Bitcoin, "m/84'/0'/0'/0/0") + val result = useCase(walletWithStyle(DerivationStyle.V3), coin) + assertThat(result).isTrue() + } + + @Test + fun `V3 wallet rejects BIP-44 BTC`() { + val coin = coin(Blockchain.Bitcoin, "m/44'/0'/0'/0/0") + val result = useCase(walletWithStyle(DerivationStyle.V3), coin) + assertThat(result).isFalse() + } + + @Test + fun `V2 wallet accepts BIP-44 BTC`() { + val coin = coin(Blockchain.Bitcoin, "m/44'/0'/0'/0/0") + val result = useCase(walletWithStyle(DerivationStyle.V2), coin) + assertThat(result).isTrue() + } + + @Test + fun `V2 wallet rejects BIP-84 BTC`() { + val coin = coin(Blockchain.Bitcoin, "m/84'/0'/0'/0/0") + val result = useCase(walletWithStyle(DerivationStyle.V2), coin) + assertThat(result).isFalse() + } + + // endregion + + // region LTC: same dual-style behavior + + @Test + fun `V3 wallet accepts BIP-84 LTC`() { + val coin = coin(Blockchain.Litecoin, "m/84'/2'/0'/0/0") + val result = useCase(walletWithStyle(DerivationStyle.V3), coin) + assertThat(result).isTrue() + } + + @Test + fun `V2 wallet accepts BIP-44 LTC`() { + val coin = coin(Blockchain.Litecoin, "m/44'/2'/0'/0/0") + val result = useCase(walletWithStyle(DerivationStyle.V2), coin) + assertThat(result).isTrue() + } + + @Test + fun `coin_type mismatch is rejected`() { + // BTC coin_type is 0; using LTC's coin_type 2 must fail + val coin = coin(Blockchain.Bitcoin, "m/84'/2'/0'/0/0") + val result = useCase(walletWithStyle(DerivationStyle.V3), coin) + assertThat(result).isFalse() + } + + // endregion + + // region Other supported chains (V2 and V3 share BIP-44) + + @Test + fun `V3 wallet accepts BIP-44 Dogecoin`() { + val coin = coin(Blockchain.Dogecoin, "m/44'/3'/0'/0/0") + val result = useCase(walletWithStyle(DerivationStyle.V3), coin) + assertThat(result).isTrue() + } + + @Test + fun `V2 wallet accepts BIP-44 Dash`() { + val coin = coin(Blockchain.Dash, "m/44'/5'/0'/0/0") + val result = useCase(walletWithStyle(DerivationStyle.V2), coin) + assertThat(result).isTrue() + } + + // endregion + + // region Account & non-base path + + @Test + fun `secondary account is accepted`() { + val coin = coin(Blockchain.Bitcoin, "m/84'/0'/3'/0/0") + val result = useCase(walletWithStyle(DerivationStyle.V3), coin) + assertThat(result).isTrue() + } + + @Test + fun `non-zero change is rejected`() { + val coin = coin(Blockchain.Bitcoin, "m/84'/0'/0'/1/0") + val result = useCase(walletWithStyle(DerivationStyle.V3), coin) + assertThat(result).isFalse() + } + + @Test + fun `non-zero address index is rejected`() { + val coin = coin(Blockchain.Bitcoin, "m/84'/0'/0'/0/5") + val result = useCase(walletWithStyle(DerivationStyle.V3), coin) + assertThat(result).isFalse() + } + + @Test + fun `path with fewer than 5 nodes is rejected`() { + val coin = coin(Blockchain.Bitcoin, "m/84'/0'/0'") + val result = useCase(walletWithStyle(DerivationStyle.V3), coin) + assertThat(result).isFalse() + } + + // endregion + + // region helpers + + private fun walletWithStyle(style: DerivationStyle?): UserWallet { + val wallet: UserWallet = mockk() + val provider = object : DerivationStyleProvider { + override fun getDerivationStyle(): DerivationStyle? = style + } + every { wallet.derivationStyleProvider } returns provider + return wallet + } + + private fun network(blockchain: Blockchain, derivationPathValue: String): Network { + val derivationPath = Network.DerivationPath.Card(derivationPathValue) + return Network( + id = Network.ID(value = blockchain.toNetworkId(), derivationPath = derivationPath), + name = blockchain.fullName, + currencySymbol = blockchain.currency, + derivationPath = derivationPath, + isTestnet = blockchain.isTestnet(), + standardType = Network.StandardType.Unspecified(blockchain.fullName), + hasFiatFeeRate = true, + canHandleTokens = false, + transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, + ) + } + + private fun coin(blockchain: Blockchain, derivationPathValue: String): CryptoCurrency.Coin { + val coin: CryptoCurrency.Coin = mockk() + every { coin.network } returns network(blockchain, derivationPathValue) + return coin + } + + private fun token(blockchain: Blockchain, derivationPathValue: String): CryptoCurrency.Token { + val token: CryptoCurrency.Token = mockk() + every { token.network } returns network(blockchain, derivationPathValue) + return token + } + + // endregion +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/DynamicAddressesDelegate.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/DynamicAddressesDelegate.kt index 6fdffa5d34..b1eae9eee6 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/DynamicAddressesDelegate.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/DynamicAddressesDelegate.kt @@ -182,7 +182,9 @@ internal class DynamicAddressesDelegate @AssistedInject constructor( coroutineScope.launch(dispatchers.main) { isConsolidationRequiredUseCase(userWalletId, network).fold( ifLeft = { error -> - TangemLogger.e("Failed to check disable: ${error.message}") + TangemLogger.e( + "Error in consolidation required check: ${error.message}", + ) _bottomSheetConfig.value = DynamicAddressesBottomSheetConfig.ServiceUnavailable( onDismissClick = dismissBottomSheet, ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index 5dad745d67..816b82fa5b 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -8,10 +8,7 @@ import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.arkivanov.decompose.router.slot.dismiss import com.tangem.blockchain.common.address.AddressType -import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.domain.dynamicaddresses.DynamicAddressesDerivationChecker -import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles -import com.tangem.domain.dynamicaddresses.DynamicAddressesSupportedBlockchains +import com.tangem.domain.dynamicaddresses.IsDynamicAddressesAvailableUseCase import com.tangem.domain.dynamicaddresses.IsXpubSupportedUseCase import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository import com.tangem.common.routing.AppRoute @@ -50,12 +47,12 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.demo.IsDemoCardUseCase +import com.tangem.domain.dynamicaddresses.DynamicAddressesSupportedBlockchains import com.tangem.domain.models.StatusSource import com.tangem.domain.models.TokenReceiveNotification import com.tangem.domain.models.account.Account import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId @@ -171,9 +168,9 @@ internal class TokenDetailsModel @Inject constructor( private val yieldSupplyGetRewardsBalanceUseCase: YieldSupplyGetRewardsBalanceUseCase, private val signCloreMessageUseCase: SignCloreMessageUseCase, private val isXpubSupportedUseCase: IsXpubSupportedUseCase, - private val dynamicAddressesFeatureToggles: DynamicAddressesFeatureToggles, private val dynamicAddressesRepository: DynamicAddressesRepository, private val dynamicAddressesDelegateFactory: DynamicAddressesDelegate.Factory, + private val isDynamicAddressesAvailableUseCase: IsDynamicAddressesAvailableUseCase, private val dialogFactory: TokenDetailsDialogFactory, private val userWalletsListRepository: UserWalletsListRepository, private val singleAccountListSupplier: SingleAccountListSupplier, @@ -452,7 +449,8 @@ internal class TokenDetailsModel @Inject constructor( ).getOrElse { false } val isSupported = isXPUBSupported() - val isDynamicAddressesAvailable = isSupported && isDynamicAddressesAvailable() + val isDynamicAddressesAvailable = isSupported && + isDynamicAddressesAvailableUseCase(userWallet, cryptoCurrency) uiState.value = stateFactory.getStateWithUpdatedMenu( userWallet = userWallet, @@ -463,28 +461,6 @@ internal class TokenDetailsModel @Inject constructor( } } - private fun isDynamicAddressesAvailable(): Boolean { - if (!dynamicAddressesFeatureToggles.isDynamicAddressesEnabled) return false - if (cryptoCurrency !is CryptoCurrency.Coin) return false - - val networkId = cryptoCurrency.network.rawId - if (!DynamicAddressesSupportedBlockchains.isSupportedByNetworkId(networkId)) return false - - return isDefaultBaseDerivation(cryptoCurrency.network.derivationPath, networkId) - } - - private fun isDefaultBaseDerivation(derivationPath: Network.DerivationPath, networkId: String): Boolean { - val pathValue = derivationPath.value ?: return false - val nodes = runCatching { DerivationPath(pathValue).nodes }.getOrNull() ?: return false - if (nodes.size < BASE_DERIVATION_NODE_COUNT) return false - - val purposeNode = nodes.first() - val allowedPurpose = DynamicAddressesSupportedBlockchains.getAllowedPurpose(networkId) ?: return false - if (purposeNode.getIndex(includeHardened = false) != allowedPurpose) return false - - return DynamicAddressesDerivationChecker.isBaseDerivation(pathValue) - } - private suspend fun isXPUBSupported(): Boolean { return isXpubSupportedUseCase(userWalletId = userWalletId, network = cryptoCurrency.network) } @@ -1383,8 +1359,4 @@ internal class TokenDetailsModel @Inject constructor( val deviceIconUM: DeviceIconUM, val account: Account.CryptoPortfolio?, ) - - private companion object { - const val BASE_DERIVATION_NODE_COUNT = 5 - } } \ No newline at end of file