Updated on 2026-08-14
This commit is contained in:
parent
72989eab5a
commit
361bd8168b
4 changed files with 74 additions and 3 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.data.dynamicaddresses
|
package com.tangem.data.dynamicaddresses
|
||||||
|
|
||||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
|
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
|
||||||
|
import com.tangem.domain.dynamicaddresses.DynamicAddressesSupportedBlockchains
|
||||||
import com.tangem.domain.dynamicaddresses.GetDerivedXpubUseCase
|
import com.tangem.domain.dynamicaddresses.GetDerivedXpubUseCase
|
||||||
import com.tangem.domain.dynamicaddresses.model.DynamicAddressesStatus
|
import com.tangem.domain.dynamicaddresses.model.DynamicAddressesStatus
|
||||||
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
|
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
|
||||||
|
|
@ -27,6 +28,8 @@ class DynamicAddressesInitializer @Inject constructor(
|
||||||
|
|
||||||
val result = mutableMapOf<Network, String>()
|
val result = mutableMapOf<Network, String>()
|
||||||
for (network in networks) {
|
for (network in networks) {
|
||||||
|
if (!DynamicAddressesSupportedBlockchains.isSupportedByNetworkId(network.rawId)) continue
|
||||||
|
|
||||||
val status = dynamicAddressesRepository.getStatus(userWalletId, network).firstOrNull()
|
val status = dynamicAddressesRepository.getStatus(userWalletId, network).firstOrNull()
|
||||||
if (status != DynamicAddressesStatus.ENABLED_REQUIRES_SETUP) continue
|
if (status != DynamicAddressesStatus.ENABLED_REQUIRES_SETUP) continue
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import com.tangem.blockchainsdk.utils.toBlockchain
|
||||||
import com.tangem.common.extensions.ByteArrayKey
|
import com.tangem.common.extensions.ByteArrayKey
|
||||||
import com.tangem.common.extensions.toMapKey
|
import com.tangem.common.extensions.toMapKey
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
|
import com.tangem.domain.dynamicaddresses.DynamicAddressesSupportedBlockchains
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.network.Network
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
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
|
* - Account-level (e.g. m/84'/0'/0') — the XPUB itself
|
||||||
* - Parent (e.g. m/84'/0') — needed for parent fingerprint in XPUB serialization
|
* - 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> {
|
private fun Blockchain.getXpubDerivationPaths(derivationPath: DerivationPath): List<DerivationPath> {
|
||||||
if (!isDynamicAddressesEnabled) return emptyList()
|
if (!isDynamicAddressesEnabled) return emptyList()
|
||||||
if (!isBip44DerivationStyleXPUB()) return emptyList()
|
if (!DynamicAddressesSupportedBlockchains.isSupported(this)) return emptyList()
|
||||||
|
|
||||||
val nodes = derivationPath.nodes
|
val nodes = derivationPath.nodes
|
||||||
if (nodes.size < XPUB_MIN_NODES) return emptyList()
|
if (nodes.size < XPUB_MIN_NODES) return emptyList()
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,73 @@ internal class MissedDerivationsFinderTest {
|
||||||
Truth.assertThat(actual).isEmpty()
|
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
|
@Test
|
||||||
fun `derivations ONLY for never derived currencies`() {
|
fun `derivations ONLY for never derived currencies`() {
|
||||||
val scanResponse = MockScanResponseFactory.create(
|
val scanResponse = MockScanResponseFactory.create(
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ class GetDerivedXpubUseCase(
|
||||||
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): String? {
|
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): String? {
|
||||||
val blockchain = network.toBlockchain()
|
val blockchain = network.toBlockchain()
|
||||||
if (!DynamicAddressesSupportedBlockchains.isSupported(blockchain)) return null
|
if (!DynamicAddressesSupportedBlockchains.isSupported(blockchain)) return null
|
||||||
if (!blockchain.isBip44DerivationStyleXPUB()) return null
|
|
||||||
|
|
||||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) ?: return null
|
val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) ?: return null
|
||||||
val hdKey = walletManager.wallet.publicKey.derivationType?.hdKey ?: return null
|
val hdKey = walletManager.wallet.publicKey.derivationType?.hdKey ?: return null
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue