Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-23 18:33:00 +03:00
parent 72989eab5a
commit 361bd8168b
4 changed files with 74 additions and 3 deletions

View file

@ -1,6 +1,7 @@
package com.tangem.data.dynamicaddresses
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
import com.tangem.domain.dynamicaddresses.DynamicAddressesSupportedBlockchains
import com.tangem.domain.dynamicaddresses.GetDerivedXpubUseCase
import com.tangem.domain.dynamicaddresses.model.DynamicAddressesStatus
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
@ -27,6 +28,8 @@ class DynamicAddressesInitializer @Inject constructor(
val result = mutableMapOf<Network, String>()
for (network in networks) {
if (!DynamicAddressesSupportedBlockchains.isSupportedByNetworkId(network.rawId)) continue
val status = dynamicAddressesRepository.getStatus(userWalletId, network).firstOrNull()
if (status != DynamicAddressesStatus.ENABLED_REQUIRES_SETUP) continue

View file

@ -6,6 +6,7 @@ import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.toMapKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.dynamicaddresses.DynamicAddressesSupportedBlockchains
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.scan.KeyWalletPublicKey
@ -140,11 +141,12 @@ class MissedDerivationsFinder private constructor(
* - Account-level (e.g. m/84'/0'/0') the XPUB itself
* - Parent (e.g. m/84'/0') needed for parent fingerprint in XPUB serialization
*
* Only applicable for BIP44-style XPUB blockchains (BTC, BCH, LTC, DOGE, DASH, RVN).
* Only applicable for blockchains listed in [DynamicAddressesSupportedBlockchains]
* (BTC/LTC via BIP-84 SegWit, BCH/DOGE/DASH/RVN via BIP-44, plus their testnets).
*/
private fun Blockchain.getXpubDerivationPaths(derivationPath: DerivationPath): List<DerivationPath> {
if (!isDynamicAddressesEnabled) return emptyList()
if (!isBip44DerivationStyleXPUB()) return emptyList()
if (!DynamicAddressesSupportedBlockchains.isSupported(this)) return emptyList()
val nodes = derivationPath.nodes
if (nodes.size < XPUB_MIN_NODES) return emptyList()

View file

@ -132,6 +132,73 @@ internal class MissedDerivationsFinderTest {
Truth.assertThat(actual).isEmpty()
}
@Test
fun `XPUB derivations added for supported blockchain when dynamic addresses enabled`() {
val userWallet = MockUserWalletFactory.create(createWallet2ScanResponse())
val finder = MissedDerivationsFinder(userWallet = userWallet, isDynamicAddressesEnabled = true)
val currencies = listOf(MockCryptoCurrencyFactory(userWallet).createCoin(Blockchain.Bitcoin))
val actual = finder.find(currencies)
Truth.assertThat(actual).containsExactly(
ByteArrayKey(EllipticCurve.Secp256k1.name.toByteArray()),
listOf(
DerivationPath("m/84'/0'/0'/0/0"), // Bitcoin BIP-84 default
DerivationPath("m/84'/0'/0'"), // XPUB account-level path
DerivationPath("m/84'/0'"), // Parent (for XPUB fingerprint)
DerivationPath("m/44'/60'/0'/0/0"), // Ethereum added by enrichBlockchains
),
)
}
@Test
fun `XPUB derivations NOT added for supported blockchain when dynamic addresses disabled`() {
val userWallet = MockUserWalletFactory.create(createWallet2ScanResponse())
val finder = MissedDerivationsFinder(userWallet = userWallet, isDynamicAddressesEnabled = false)
val currencies = listOf(MockCryptoCurrencyFactory(userWallet).createCoin(Blockchain.Bitcoin))
val actual = finder.find(currencies)
Truth.assertThat(actual).containsExactly(
ByteArrayKey(EllipticCurve.Secp256k1.name.toByteArray()),
listOf(
DerivationPath("m/84'/0'/0'/0/0"),
DerivationPath("m/44'/60'/0'/0/0"),
),
)
}
@Test
fun `XPUB derivations NOT added for unsupported blockchain when dynamic addresses enabled`() {
val userWallet = MockUserWalletFactory.create(createWallet2ScanResponse())
val finder = MissedDerivationsFinder(userWallet = userWallet, isDynamicAddressesEnabled = true)
val currencies = listOf(MockCryptoCurrencyFactory(userWallet).createCoin(Blockchain.Ethereum))
val actual = finder.find(currencies)
Truth.assertThat(actual).containsExactly(
ByteArrayKey(EllipticCurve.Secp256k1.name.toByteArray()),
listOf(DerivationPath("m/44'/60'/0'/0/0")),
)
}
/**
* Wallet2 config yields DerivationStyle.V3 (BIP-84 SegWit for BTC/LTC) the style the
* Dynamic Addresses feature actually targets. [MockScanResponseFactory] hardcodes
* `isHDWalletAllowed = false` for Wallet2, so we patch it to `true` to mirror production
* scans that reach [MissedDerivationsFinder].
*/
private fun createWallet2ScanResponse() = MockScanResponseFactory.create(
cardConfig = Wallet2CardConfig,
derivedKeys = emptyMap(),
).let {
it.copy(
card = it.card.copy(
settings = it.card.settings.copy(isHDWalletAllowed = true, isBackupAllowed = true),
),
)
}
@Test
fun `derivations ONLY for never derived currencies`() {
val scanResponse = MockScanResponseFactory.create(

View file

@ -24,7 +24,6 @@ class GetDerivedXpubUseCase(
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): String? {
val blockchain = network.toBlockchain()
if (!DynamicAddressesSupportedBlockchains.isSupported(blockchain)) return null
if (!blockchain.isBip44DerivationStyleXPUB()) return null
val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) ?: return null
val hdKey = walletManager.wallet.publicKey.derivationType?.hdKey ?: return null