Updated on 2026-08-14
This commit is contained in:
parent
27e9400b2a
commit
b38ebb3995
16 changed files with 196 additions and 71 deletions
|
|
@ -11,7 +11,7 @@ class DisableDynamicAddressesUseCase(
|
|||
|
||||
/**
|
||||
* Returns true when consolidation is required before disabling (non-base balances exist),
|
||||
* or false when DA was disabled immediately.
|
||||
* or false when dynamic addresses was disabled immediately.
|
||||
*/
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either<Throwable, Boolean> =
|
||||
Either.catch {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package com.tangem.domain.dynamicaddresses
|
||||
|
||||
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).
|
||||
*
|
||||
* Per ASMPT-005: DA is NOT used for Legacy (m/44' for BTC/LTC) or Taproot (m/86') addresses.
|
||||
* Dynamic addresses is NOT used for Legacy (m/44' for BTC/LTC) or Taproot (m/86') addresses.
|
||||
* Only the default derivation style per blockchain is supported.
|
||||
*/
|
||||
object DynamicAddressesSupportedBlockchains {
|
||||
|
|
@ -26,22 +27,22 @@ object DynamicAddressesSupportedBlockchains {
|
|||
Blockchain.RavencoinTestnet,
|
||||
)
|
||||
|
||||
private val supportedNetworkIds = supported.map { it.id }.toSet()
|
||||
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.id, BIP84_PURPOSE)
|
||||
put(Blockchain.BitcoinTestnet.id, BIP84_PURPOSE)
|
||||
put(Blockchain.Litecoin.id, BIP84_PURPOSE)
|
||||
put(Blockchain.BitcoinCash.id, BIP44_PURPOSE)
|
||||
put(Blockchain.BitcoinCashTestnet.id, BIP44_PURPOSE)
|
||||
put(Blockchain.Dogecoin.id, BIP44_PURPOSE)
|
||||
put(Blockchain.Dash.id, BIP44_PURPOSE)
|
||||
put(Blockchain.Ravencoin.id, BIP44_PURPOSE)
|
||||
put(Blockchain.RavencoinTestnet.id, BIP44_PURPOSE)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.domain.dynamicaddresses
|
||||
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.calculateRipemd160
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.crypto.NetworkType
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
|
||||
/**
|
||||
* Returns the XPUB string if account-level keys are already derived (no card scan needed),
|
||||
* or null if keys are not available.
|
||||
*/
|
||||
class GetDerivedXpubUseCase(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val derivationsRepository: DerivationsRepository,
|
||||
) {
|
||||
|
||||
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
|
||||
if (hdKey.path.nodes.size <= ACCOUNT_PATH_DROP_COUNT) return null
|
||||
|
||||
val seedKey = ByteArrayKey(walletManager.wallet.publicKey.seedKey)
|
||||
val existingKeys = derivationsRepository.getExistingDerivedKeys(userWalletId, seedKey)
|
||||
|
||||
val accountPath = DerivationPath(hdKey.path.nodes.dropLast(ACCOUNT_PATH_DROP_COUNT))
|
||||
val parentPath = DerivationPath(accountPath.nodes.dropLast(1))
|
||||
|
||||
val childExtKey = existingKeys[accountPath] ?: return null
|
||||
val parentExtKey = existingKeys[parentPath] ?: return null
|
||||
|
||||
val parentFingerprint = parentExtKey.publicKey
|
||||
.calculateSha256().calculateRipemd160()
|
||||
.take(PARENT_FINGERPRINT_SIZE).toByteArray()
|
||||
|
||||
val net = if (blockchain.isTestnet()) NetworkType.Testnet else NetworkType.Mainnet
|
||||
return ExtendedPublicKey(
|
||||
publicKey = childExtKey.publicKey,
|
||||
chainCode = childExtKey.chainCode,
|
||||
depth = accountPath.nodes.size,
|
||||
parentFingerprint = parentFingerprint,
|
||||
childNumber = accountPath.nodes.last().index,
|
||||
).serialize(net)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val ACCOUNT_PATH_DROP_COUNT = 2
|
||||
const val PARENT_FINGERPRINT_SIZE = 4
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package com.tangem.domain.dynamicaddresses
|
||||
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
|
||||
/**
|
||||
* Checks if the account-level XPUB key is already derived (no card scan needed).
|
||||
*/
|
||||
class IsXpubDerivedUseCase(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val derivationsRepository: DerivationsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Boolean {
|
||||
val blockchain = network.toBlockchain()
|
||||
if (!DynamicAddressesSupportedBlockchains.isSupported(blockchain)) return false
|
||||
if (!blockchain.isBip44DerivationStyleXPUB()) return false
|
||||
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) ?: return false
|
||||
val hdKey = walletManager.wallet.publicKey.derivationType?.hdKey ?: return false
|
||||
if (hdKey.path.nodes.size <= ACCOUNT_PATH_DROP_COUNT) return false
|
||||
val accountPath = DerivationPath(hdKey.path.nodes.dropLast(ACCOUNT_PATH_DROP_COUNT))
|
||||
|
||||
val seedKey = ByteArrayKey(walletManager.wallet.publicKey.seedKey)
|
||||
val existingKeys = derivationsRepository.getExistingDerivedKeys(userWalletId, seedKey)
|
||||
return existingKeys[accountPath] != null
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val ACCOUNT_PATH_DROP_COUNT = 2
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,6 @@ interface DynamicAddressesRepository {
|
|||
|
||||
suspend fun hasNonBaseBalances(userWalletId: UserWalletId, network: Network): Boolean
|
||||
|
||||
/** Returns true if there are custom tokens with change/index ≠ 0 that conflict with DA */
|
||||
/** Returns true if there are custom tokens with change/index ≠ 0 that conflict with dynamic addresses */
|
||||
suspend fun hasConflictingCustomTokens(userWalletId: UserWalletId, network: Network): Boolean
|
||||
}
|
||||
|
|
@ -38,12 +38,14 @@ interface WalletManagersFacade {
|
|||
* @param userWalletId The ID of the user's wallet.
|
||||
* @param network The network.
|
||||
* @param extraTokens Additional tokens.
|
||||
* @param xpub XPUB string to restore dynamic addresses mode if not yet active.
|
||||
* @return The result of updating the wallet manager.
|
||||
*/
|
||||
suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
extraTokens: Set<CryptoCurrency.Token>,
|
||||
xpub: String? = null,
|
||||
): UpdateWalletManagerResult
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue