Updated on 2026-08-14
This commit is contained in:
parent
16ebea436f
commit
f153804a41
7 changed files with 300 additions and 63 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<DerivationNode>? {
|
||||
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<DerivationNode>): Boolean {
|
||||
if (nodes.size < BIP44_NODE_COUNT) return false
|
||||
|
||||
val change = nodes[CHANGE_NODE_INDEX].getIndex(includeHardened = false)
|
||||
|
|
|
|||
|
|
@ -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<String, Long> = 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]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue