From 361bd8168bc9f7f12fd67acb2056014778fac52f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 Apr 2026 18:33:00 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../DynamicAddressesInitializer.kt | 3 + .../derivations/MissedDerivationsFinder.kt | 6 +- .../MissedDerivationsFinderTest.kt | 67 +++++++++++++++++++ .../dynamicaddresses/GetDerivedXpubUseCase.kt | 1 - 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/data/dynamic-addresses/src/main/java/com/tangem/data/dynamicaddresses/DynamicAddressesInitializer.kt b/data/dynamic-addresses/src/main/java/com/tangem/data/dynamicaddresses/DynamicAddressesInitializer.kt index 6eae2bf3b5..ec96dcdcea 100644 --- a/data/dynamic-addresses/src/main/java/com/tangem/data/dynamicaddresses/DynamicAddressesInitializer.kt +++ b/data/dynamic-addresses/src/main/java/com/tangem/data/dynamicaddresses/DynamicAddressesInitializer.kt @@ -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() for (network in networks) { + if (!DynamicAddressesSupportedBlockchains.isSupportedByNetworkId(network.rawId)) continue + val status = dynamicAddressesRepository.getStatus(userWalletId, network).firstOrNull() if (status != DynamicAddressesStatus.ENABLED_REQUIRES_SETUP) continue diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/derivations/MissedDerivationsFinder.kt b/data/wallets/src/main/java/com/tangem/data/wallets/derivations/MissedDerivationsFinder.kt index 9c4c0af642..1b0b9f93b2 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/derivations/MissedDerivationsFinder.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/derivations/MissedDerivationsFinder.kt @@ -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 { 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() diff --git a/data/wallets/src/test/java/com/tangem/data/wallets/derivations/MissedDerivationsFinderTest.kt b/data/wallets/src/test/java/com/tangem/data/wallets/derivations/MissedDerivationsFinderTest.kt index b0285167fc..dea2d7df49 100644 --- a/data/wallets/src/test/java/com/tangem/data/wallets/derivations/MissedDerivationsFinderTest.kt +++ b/data/wallets/src/test/java/com/tangem/data/wallets/derivations/MissedDerivationsFinderTest.kt @@ -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( diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetDerivedXpubUseCase.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetDerivedXpubUseCase.kt index 4c3b3cd2bf..7d5b34d04c 100644 --- a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetDerivedXpubUseCase.kt +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetDerivedXpubUseCase.kt @@ -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