Updated on 2026-08-14
This commit is contained in:
parent
52a607c1f5
commit
9944036c4d
29 changed files with 874 additions and 60 deletions
|
|
@ -30,6 +30,7 @@ dependencies {
|
|||
|
||||
// region Project - Libs
|
||||
implementation(tangemDeps.blockchain) { exclude(module = "joda-time") }
|
||||
implementation(tangemDeps.card.core)
|
||||
// endregion
|
||||
|
||||
// region DI
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.data.dynamicaddresses
|
||||
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||
import com.tangem.data.common.account.WalletAccountsSaver
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
|
|
@ -10,6 +11,7 @@ import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
|
|||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
|
|
@ -31,17 +33,20 @@ internal class DefaultDynamicAddressesRepository(
|
|||
.map { response ->
|
||||
val token = response.findToken(network)
|
||||
when {
|
||||
token?.dynamicAddressesEnabled == true -> DynamicAddressesStatus.ENABLED
|
||||
else -> DynamicAddressesStatus.DISABLED
|
||||
token?.dynamicAddressesEnabled != true -> DynamicAddressesStatus.DISABLED
|
||||
!isXpubAvailable(userWalletId, network) -> DynamicAddressesStatus.ENABLED_REQUIRES_SETUP
|
||||
else -> DynamicAddressesStatus.ENABLED
|
||||
}
|
||||
// TODO handle ENABLED_REQUIRES_SETUP when XPUB is not derived locally
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
override suspend fun enable(userWalletId: UserWalletId, network: Network, xpub: String) {
|
||||
withContext(dispatchers.io) {
|
||||
walletManagersFacade.enableXpubMode(userWalletId, network, xpub)
|
||||
val result = walletManagersFacade.enableXpubMode(userWalletId, network, xpub)
|
||||
if (result is SimpleResult.Failure) {
|
||||
error("Failed to enable xpub mode for $userWalletId / ${network.id}: ${result.error}")
|
||||
}
|
||||
updateTokenDynamicAddressesFlag(userWalletId, network, enabled = true)
|
||||
runSuspendCatching { accountsCRUDRepository.syncTokens(userWalletId) }
|
||||
.onFailure { TangemLogger.e("Failed to sync tokens after DA enable for $userWalletId", it) }
|
||||
|
|
@ -50,7 +55,10 @@ internal class DefaultDynamicAddressesRepository(
|
|||
|
||||
override suspend fun disable(userWalletId: UserWalletId, network: Network) {
|
||||
withContext(dispatchers.io) {
|
||||
walletManagersFacade.disableXpubMode(userWalletId, network)
|
||||
val result = walletManagersFacade.disableXpubMode(userWalletId, network)
|
||||
if (result is SimpleResult.Failure) {
|
||||
error("Failed to disable xpub mode for $userWalletId / ${network.id}: ${result.error}")
|
||||
}
|
||||
updateTokenDynamicAddressesFlag(userWalletId, network, enabled = false)
|
||||
runSuspendCatching { accountsCRUDRepository.syncTokens(userWalletId) }
|
||||
.onFailure { TangemLogger.e("Failed to sync tokens after DA disable for $userWalletId", it) }
|
||||
|
|
@ -70,6 +78,45 @@ internal class DefaultDynamicAddressesRepository(
|
|||
return walletManagersFacade.hasDynamicAddressesNonBaseBalances(userWalletId, network)
|
||||
}
|
||||
|
||||
override suspend fun hasConflictingCustomTokens(userWalletId: UserWalletId, network: Network): Boolean {
|
||||
return withContext(dispatchers.io) {
|
||||
val response = walletAccountsFetcher.getSaved(userWalletId) ?: return@withContext false
|
||||
val baseDerivationPath = network.derivationPath.value ?: return@withContext false
|
||||
|
||||
response.accounts
|
||||
.flatMap { it.tokens.orEmpty() }
|
||||
.any { token ->
|
||||
val tokenDerivationPath = token.derivationPath ?: return@any false
|
||||
token.networkId == network.backendId &&
|
||||
tokenDerivationPath != baseDerivationPath &&
|
||||
hasNonZeroChangeOrIndex(tokenDerivationPath, baseDerivationPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the token's derivation path has the same first 3 nodes (purpose/coin/account)
|
||||
* as the base path but different change/index nodes (not both 0).
|
||||
*/
|
||||
private fun hasNonZeroChangeOrIndex(tokenPath: String, basePath: String): Boolean {
|
||||
val tokenNodes = runCatching { DerivationPath(tokenPath).nodes }.getOrNull() ?: return false
|
||||
val baseNodes = runCatching { DerivationPath(basePath).nodes }.getOrNull() ?: return false
|
||||
|
||||
if (tokenNodes.size < DERIVATION_NODE_COUNT || baseNodes.size < DERIVATION_NODE_COUNT) return false
|
||||
|
||||
// First 3 nodes must match (purpose/coin/account) by value, ignoring hardening
|
||||
val isSameAccount = (0 until ACCOUNT_NODE_COUNT).all { i ->
|
||||
tokenNodes[i].getIndex(includeHardened = false) == baseNodes[i].getIndex(includeHardened = false)
|
||||
}
|
||||
if (!isSameAccount) return false
|
||||
|
||||
// Check if change or index ≠ 0
|
||||
val change = tokenNodes[CHANGE_NODE_INDEX].getIndex(includeHardened = false)
|
||||
val index = tokenNodes[INDEX_NODE_INDEX].getIndex(includeHardened = false)
|
||||
|
||||
return change != 0L || index != 0L
|
||||
}
|
||||
|
||||
private suspend fun updateTokenDynamicAddressesFlag(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
|
|
@ -92,6 +139,11 @@ internal class DefaultDynamicAddressesRepository(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun isXpubAvailable(userWalletId: UserWalletId, network: Network): Boolean {
|
||||
// Check if WalletManager is already in XPUB mode (DA was previously enabled on this device)
|
||||
return walletManagersFacade.getDynamicAddressesReceiveAddress(userWalletId, network) != null
|
||||
}
|
||||
|
||||
private fun GetWalletAccountsResponse.findToken(network: Network): UserTokensResponse.Token? {
|
||||
return accounts
|
||||
.flatMap { it.tokens.orEmpty() }
|
||||
|
|
@ -103,4 +155,11 @@ internal class DefaultDynamicAddressesRepository(
|
|||
derivationPath == network.derivationPath.value &&
|
||||
contractAddress == null
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val DERIVATION_NODE_COUNT = 5
|
||||
const val ACCOUNT_NODE_COUNT = 3
|
||||
const val CHANGE_NODE_INDEX = 3
|
||||
const val INDEX_NODE_INDEX = 4
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue