Updated on 2026-08-14
This commit is contained in:
parent
182aa24095
commit
94a9b132a2
23 changed files with 458 additions and 60 deletions
|
|
@ -31,6 +31,7 @@ dependencies {
|
|||
/** Domain */
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.dynamicAddresses)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.crypto.hdWallet.DerivationPath
|
|||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.data.wallets.derivations.Derivations
|
||||
import com.tangem.data.wallets.derivations.MissedDerivationsFinder
|
||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
|
@ -30,6 +31,7 @@ internal class DefaultColdMapDerivationsRepository @Inject constructor(
|
|||
private val tangemSdkManager: TangemSdkManager,
|
||||
private val networkFactory: NetworkFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val dynamicAddressesFeatureToggles: DynamicAddressesFeatureToggles,
|
||||
) : ColdMapDerivationsRepository {
|
||||
|
||||
override suspend fun derivePublicKeys(
|
||||
|
|
@ -64,7 +66,7 @@ internal class DefaultColdMapDerivationsRepository @Inject constructor(
|
|||
return@withContext userWallet
|
||||
}
|
||||
|
||||
val derivations = MissedDerivationsFinder(userWallet)
|
||||
val derivations = MissedDerivationsFinder(userWallet, dynamicAddressesFeatureToggles.isDynamicAddressesEnabled)
|
||||
.findByNetworks(networks)
|
||||
.ifEmpty {
|
||||
TangemLogger.d("Nothing to derive")
|
||||
|
|
@ -103,7 +105,7 @@ internal class DefaultColdMapDerivationsRepository @Inject constructor(
|
|||
networksWithDerivationPath: Map<BackendId, String?>,
|
||||
): Boolean = withContext(dispatchers.io) {
|
||||
val derivations =
|
||||
MissedDerivationsFinder(userWallet)
|
||||
MissedDerivationsFinder(userWallet, dynamicAddressesFeatureToggles.isDynamicAddressesEnabled)
|
||||
.findByNetworks(
|
||||
networksWithDerivationPath.mapNotNull { (backendId, extraDerivationPath) ->
|
||||
networkFactory.create(
|
||||
|
|
|
|||
|
|
@ -30,17 +30,20 @@ data class BlockchainToDerive(
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class MissedDerivationsFinder private constructor(private val source: DerivationsSource) {
|
||||
class MissedDerivationsFinder private constructor(
|
||||
private val source: DerivationsSource,
|
||||
private val isDynamicAddressesEnabled: Boolean,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Secondary constructor for backward compatibility with UserWallet
|
||||
*/
|
||||
constructor(userWallet: UserWallet) : this(DerivationsSource.FromUserWallet(userWallet))
|
||||
constructor(userWallet: UserWallet, isDynamicAddressesEnabled: Boolean) : this(
|
||||
source = DerivationsSource.FromUserWallet(userWallet),
|
||||
isDynamicAddressesEnabled = isDynamicAddressesEnabled,
|
||||
)
|
||||
|
||||
/**
|
||||
* Secondary constructor for ScanResponse
|
||||
*/
|
||||
constructor(scanResponse: ScanResponse) : this(DerivationsSource.FromScanResponse(scanResponse))
|
||||
constructor(scanResponse: ScanResponse, isDynamicAddressesEnabled: Boolean) : this(
|
||||
source = DerivationsSource.FromScanResponse(scanResponse),
|
||||
isDynamicAddressesEnabled = isDynamicAddressesEnabled,
|
||||
)
|
||||
|
||||
/** Find missed derivations for given currencies [currencies] */
|
||||
fun find(currencies: List<CryptoCurrency>): Derivations {
|
||||
|
|
@ -115,6 +118,9 @@ class MissedDerivationsFinder private constructor(private val source: Derivation
|
|||
|
||||
// Extended Cardano derivation path if needed
|
||||
add(getCardanoExtendedDerivationPath(derivationPath))
|
||||
|
||||
// Account-level and parent paths for XPUB generation (Dynamic Addresses)
|
||||
addAll(getXpubDerivationPaths(derivationPath))
|
||||
}
|
||||
.filterNotNull()
|
||||
.distinct()
|
||||
|
|
@ -129,6 +135,26 @@ class MissedDerivationsFinder private constructor(private val source: Derivation
|
|||
return CardanoUtils.extendedDerivationPath(derivationPath = customDerivationPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns account-level and parent derivation paths needed for XPUB generation.
|
||||
* - 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).
|
||||
*/
|
||||
private fun Blockchain.getXpubDerivationPaths(derivationPath: DerivationPath): List<DerivationPath> {
|
||||
if (!isDynamicAddressesEnabled) return emptyList()
|
||||
if (!isBip44DerivationStyleXPUB()) return emptyList()
|
||||
|
||||
val nodes = derivationPath.nodes
|
||||
if (nodes.size < XPUB_MIN_NODES) return emptyList()
|
||||
|
||||
val accountPath = DerivationPath(nodes.take(XPUB_ACCOUNT_NODE_COUNT))
|
||||
val parentPath = DerivationPath(nodes.take(XPUB_PARENT_NODE_COUNT))
|
||||
|
||||
return listOf(accountPath, parentPath)
|
||||
}
|
||||
|
||||
private fun List<DerivationPath>.filterAlreadyDerivedKeys(publicKey: KeyWalletPublicKey): List<DerivationPath> {
|
||||
val alreadyDerivedPaths = source.getDerivedKeys(publicKey).keys.toList()
|
||||
return filterNot(alreadyDerivedPaths::contains)
|
||||
|
|
@ -161,4 +187,10 @@ class MissedDerivationsFinder private constructor(private val source: Derivation
|
|||
}
|
||||
|
||||
// endregion
|
||||
|
||||
private companion object {
|
||||
const val XPUB_MIN_NODES = 3
|
||||
const val XPUB_ACCOUNT_NODE_COUNT = 3
|
||||
const val XPUB_PARENT_NODE_COUNT = 2
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.crypto.hdWallet.DerivationPath
|
|||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.data.wallets.derivations.MissedDerivationsFinder
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
|
||||
import com.tangem.domain.common.wallets.getSyncStrict
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
@ -28,6 +29,7 @@ internal class DefaultHotMapDerivationsRepository @Inject constructor(
|
|||
private val networkFactory: NetworkFactory,
|
||||
private val hotWalletAccessor: HotWalletAccessor,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val dynamicAddressesFeatureToggles: DynamicAddressesFeatureToggles,
|
||||
) : HotMapDerivationsRepository {
|
||||
|
||||
override suspend fun derivePublicKeys(
|
||||
|
|
@ -59,7 +61,7 @@ internal class DefaultHotMapDerivationsRepository @Inject constructor(
|
|||
userWallet: UserWallet.Hot,
|
||||
networks: List<Network>,
|
||||
): UserWallet.Hot = withContext(dispatchers.default) {
|
||||
val derivations = MissedDerivationsFinder(userWallet)
|
||||
val derivations = MissedDerivationsFinder(userWallet, dynamicAddressesFeatureToggles.isDynamicAddressesEnabled)
|
||||
.findByNetworks(networks)
|
||||
.ifEmpty {
|
||||
TangemLogger.d("Nothing to derive")
|
||||
|
|
@ -103,7 +105,7 @@ internal class DefaultHotMapDerivationsRepository @Inject constructor(
|
|||
userWallet: UserWallet.Hot,
|
||||
networksWithDerivationPath: Map<BackendId, String?>,
|
||||
): Boolean = withContext(dispatchers.default) {
|
||||
val derivations = MissedDerivationsFinder(userWallet)
|
||||
val derivations = MissedDerivationsFinder(userWallet, dynamicAddressesFeatureToggles.isDynamicAddressesEnabled)
|
||||
.findByNetworks(
|
||||
networksWithDerivationPath.mapNotNull { (backendId, extraDerivationPath) ->
|
||||
networkFactory.create(
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ internal class MissedDerivationsFinderTest {
|
|||
fun `empty derivations for empty currencies`() {
|
||||
val scanResponse = MockScanResponseFactory.create(cardConfig = GenericCardConfig(2), derivedKeys = emptyMap())
|
||||
val userWallet = MockUserWalletFactory.create(scanResponse)
|
||||
val finder = MissedDerivationsFinder(userWallet)
|
||||
val finder = MissedDerivationsFinder(userWallet = userWallet, isDynamicAddressesEnabled = false)
|
||||
|
||||
val actual = finder.find(emptyList())
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ internal class MissedDerivationsFinderTest {
|
|||
// Bls is not supported
|
||||
val scanResponse = MockScanResponseFactory.create(cardConfig = GenericCardConfig(2), derivedKeys = emptyMap())
|
||||
val userWallet = MockUserWalletFactory.create(scanResponse)
|
||||
val finder = MissedDerivationsFinder(userWallet)
|
||||
val finder = MissedDerivationsFinder(userWallet = userWallet, isDynamicAddressesEnabled = false)
|
||||
|
||||
val currencies = MockCryptoCurrencyFactory(userWallet).chia.let(::listOf)
|
||||
val actual = finder.find(currencies)
|
||||
|
|
@ -61,7 +61,7 @@ internal class MissedDerivationsFinderTest {
|
|||
)
|
||||
}
|
||||
val userWallet = MockUserWalletFactory.create(scanResponse)
|
||||
val finder = MissedDerivationsFinder(userWallet)
|
||||
val finder = MissedDerivationsFinder(userWallet = userWallet, isDynamicAddressesEnabled = false)
|
||||
|
||||
val currencies = MockCryptoCurrencyFactory(userWallet).chiaAndEthereum
|
||||
val actual = finder.find(currencies)
|
||||
|
|
@ -76,7 +76,7 @@ internal class MissedDerivationsFinderTest {
|
|||
fun `derivations for custom token`() {
|
||||
val scanResponse = MockScanResponseFactory.create(cardConfig = MultiWalletCardConfig, derivedKeys = emptyMap())
|
||||
val userWallet = MockUserWalletFactory.create(scanResponse)
|
||||
val finder = MissedDerivationsFinder(userWallet)
|
||||
val finder = MissedDerivationsFinder(userWallet = userWallet, isDynamicAddressesEnabled = false)
|
||||
|
||||
val currencies = MockCryptoCurrencyFactory(userWallet).ethereumTokenWithBinanceDerivation
|
||||
val actual = finder.find(currencies)
|
||||
|
|
@ -94,7 +94,7 @@ internal class MissedDerivationsFinderTest {
|
|||
fun `derivations for cardano`() {
|
||||
val scanResponse = MockScanResponseFactory.create(cardConfig = MultiWalletCardConfig, derivedKeys = emptyMap())
|
||||
val userWallet = MockUserWalletFactory.create(scanResponse)
|
||||
val finder = MissedDerivationsFinder(userWallet)
|
||||
val finder = MissedDerivationsFinder(userWallet = userWallet, isDynamicAddressesEnabled = false)
|
||||
|
||||
val currencies = MockCryptoCurrencyFactory(userWallet).cardano.let(::listOf)
|
||||
val actual = finder.find(currencies)
|
||||
|
|
@ -124,7 +124,7 @@ internal class MissedDerivationsFinderTest {
|
|||
derivedKeys = DerivedKeysMocks.ethereumDerivedKeys,
|
||||
)
|
||||
val userWallet = MockUserWalletFactory.create(scanResponse)
|
||||
val finder = MissedDerivationsFinder(userWallet)
|
||||
val finder = MissedDerivationsFinder(userWallet = userWallet, isDynamicAddressesEnabled = false)
|
||||
|
||||
val currencies = MockCryptoCurrencyFactory(userWallet).ethereum.let(::listOf)
|
||||
val actual = finder.find(currencies)
|
||||
|
|
@ -139,7 +139,7 @@ internal class MissedDerivationsFinderTest {
|
|||
derivedKeys = DerivedKeysMocks.ethereumDerivedKeys,
|
||||
)
|
||||
val userWallet = MockUserWalletFactory.create(scanResponse)
|
||||
val finder = MissedDerivationsFinder(userWallet)
|
||||
val finder = MissedDerivationsFinder(userWallet = userWallet, isDynamicAddressesEnabled = false)
|
||||
|
||||
val currencies = MockCryptoCurrencyFactory(userWallet).ethereumAndStellar
|
||||
val actual = finder.find(currencies)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue