Updated on 2026-08-14
This commit is contained in:
parent
27e9400b2a
commit
b38ebb3995
16 changed files with 196 additions and 71 deletions
|
|
@ -49,7 +49,12 @@ internal class DefaultDynamicAddressesRepository(
|
|||
}
|
||||
updateTokenDynamicAddressesFlag(userWalletId, network, enabled = true)
|
||||
runSuspendCatching { accountsCRUDRepository.syncTokens(userWalletId) }
|
||||
.onFailure { TangemLogger.e("Failed to sync tokens after DA enable for $userWalletId", it) }
|
||||
.onFailure { throwable ->
|
||||
TangemLogger.e(
|
||||
messageString = "Failed to sync tokens after dynamic addresses enable for $userWalletId",
|
||||
throwable = throwable,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +66,12 @@ internal class DefaultDynamicAddressesRepository(
|
|||
}
|
||||
updateTokenDynamicAddressesFlag(userWalletId, network, enabled = false)
|
||||
runSuspendCatching { accountsCRUDRepository.syncTokens(userWalletId) }
|
||||
.onFailure { TangemLogger.e("Failed to sync tokens after DA disable for $userWalletId", it) }
|
||||
.onFailure { throwable ->
|
||||
TangemLogger.e(
|
||||
messageString = "Failed to sync tokens after dynamic addresses disable for $userWalletId",
|
||||
throwable = throwable,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +150,7 @@ 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)
|
||||
// Check if WalletManager is already in XPUB mode (dynamic addresses was previously enabled on this device)
|
||||
return walletManagersFacade.getDynamicAddressesReceiveAddress(userWalletId, network) != null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.data.dynamicaddresses
|
||||
|
||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
|
||||
import com.tangem.domain.dynamicaddresses.GetDerivedXpubUseCase
|
||||
import com.tangem.domain.dynamicaddresses.model.DynamicAddressesStatus
|
||||
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Provides XPUB strings for networks that need dynamic addresses restore (ENABLED_REQUIRES_SETUP).
|
||||
* Uses only already-derived keys — no card scan triggered.
|
||||
*/
|
||||
@Singleton
|
||||
class DynamicAddressesInitializer @Inject constructor(
|
||||
private val dynamicAddressesRepository: DynamicAddressesRepository,
|
||||
private val dynamicAddressesFeatureToggles: DynamicAddressesFeatureToggles,
|
||||
private val getDerivedXpubUseCase: GetDerivedXpubUseCase,
|
||||
) {
|
||||
|
||||
suspend fun getXpubs(userWalletId: UserWalletId, networks: Set<Network>): Map<Network, String> {
|
||||
if (!dynamicAddressesFeatureToggles.isDynamicAddressesEnabled) return emptyMap()
|
||||
|
||||
val result = mutableMapOf<Network, String>()
|
||||
for (network in networks) {
|
||||
val status = dynamicAddressesRepository.getStatus(userWalletId, network).firstOrNull()
|
||||
if (status != DynamicAddressesStatus.ENABLED_REQUIRES_SETUP) continue
|
||||
|
||||
val xpub = getDerivedXpubUseCase(userWalletId, network)
|
||||
if (xpub != null) {
|
||||
result[network] = xpub
|
||||
} else {
|
||||
TangemLogger.w("Dynamic addresses enabled but XPUB not available for ${network.id}")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ dependencies {
|
|||
|
||||
// region Project - Data
|
||||
implementation(projects.data.common)
|
||||
implementation(projects.data.dynamicAddresses)
|
||||
// endregion
|
||||
|
||||
// region Project - Domain
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ internal class CommonNetworkStatusFetcher @Inject constructor(
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
networkCurrencies: Set<CryptoCurrency>,
|
||||
xpub: String? = null,
|
||||
): Either<Throwable, Unit> {
|
||||
return Either.catchOn(dispatchers.default) {
|
||||
val result = withContext(dispatchers.io) {
|
||||
|
|
@ -52,6 +53,7 @@ internal class CommonNetworkStatusFetcher @Inject constructor(
|
|||
extraTokens = networkCurrencies
|
||||
.filterIsInstance<CryptoCurrency.Token>()
|
||||
.toSet(),
|
||||
xpub = xpub,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,18 @@ package com.tangem.data.networks.multi
|
|||
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.ensure
|
||||
import com.tangem.domain.common.tokens.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.fetcher.CommonNetworkStatusFetcher
|
||||
import com.tangem.data.networks.store.NetworksStatusesStore
|
||||
import com.tangem.data.networks.store.setSourceAsCache
|
||||
import com.tangem.data.networks.store.setSourceAsOnlyCache
|
||||
import com.tangem.data.dynamicaddresses.DynamicAddressesInitializer
|
||||
import com.tangem.domain.common.tokens.CardCryptoCurrencyFactory
|
||||
import com.tangem.domain.core.utils.eitherOn
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
|
@ -27,11 +29,11 @@ import javax.inject.Inject
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
|
||||
private val networksStatusesStore: NetworksStatusesStore,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val commonNetworkStatusFetcher: CommonNetworkStatusFetcher,
|
||||
private val dynamicAddressesInitializer: DynamicAddressesInitializer,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : MultiNetworkStatusFetcher {
|
||||
|
||||
|
|
@ -50,6 +52,14 @@ internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
|
|||
},
|
||||
)
|
||||
|
||||
val xpubByNetwork = catch(
|
||||
block = { dynamicAddressesInitializer.getXpubs(params.userWalletId, params.networks) },
|
||||
catch = { error ->
|
||||
TangemLogger.e("Failed to build XPUBs for restore", error)
|
||||
emptyMap()
|
||||
},
|
||||
)
|
||||
|
||||
val result = coroutineScope {
|
||||
params.networks
|
||||
.map { network ->
|
||||
|
|
@ -58,6 +68,7 @@ internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
|
|||
userWalletId = params.userWalletId,
|
||||
network = network,
|
||||
networkCurrencies = networksCurrencies[network].orEmpty().toSet(),
|
||||
xpub = xpubByNetwork[network],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.data.networks.multi
|
|||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.data.dynamicaddresses.DynamicAddressesInitializer
|
||||
import com.tangem.domain.common.tokens.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.fetcher.CommonNetworkStatusFetcher
|
||||
import com.tangem.data.networks.store.NetworksStatusesStore
|
||||
|
|
@ -27,17 +28,21 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
private val networksStatusesStore: NetworksStatusesStore = mockk(relaxUnitFun = true)
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
|
||||
private val commonNetworkStatusFetcher: CommonNetworkStatusFetcher = mockk()
|
||||
private val dynamicAddressesInitializer: DynamicAddressesInitializer = mockk()
|
||||
|
||||
private val fetcher = DefaultMultiNetworkStatusFetcher(
|
||||
networksStatusesStore = networksStatusesStore,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
commonNetworkStatusFetcher = commonNetworkStatusFetcher,
|
||||
dynamicAddressesInitializer = dynamicAddressesInitializer,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(networksStatusesStore, cardCryptoCurrencyFactory, commonNetworkStatusFetcher)
|
||||
clearMocks(networksStatusesStore, cardCryptoCurrencyFactory, commonNetworkStatusFetcher, dynamicAddressesInitializer)
|
||||
// No dynamic addresses restore by default
|
||||
coEvery { dynamicAddressesInitializer.getXpubs(any(), any()) } returns emptyMap()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -62,6 +67,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
} returns ethereumFetcherResult
|
||||
|
||||
|
|
@ -70,6 +76,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
} returns cardanoFetcherResult
|
||||
|
||||
|
|
@ -87,11 +94,13 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -122,6 +131,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
} returns ethereumFetcherResult
|
||||
|
||||
|
|
@ -130,6 +140,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
} returns cardanoFetcherResult
|
||||
|
||||
|
|
@ -147,11 +158,13 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -182,6 +195,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
} returns ethereumFetcherResult
|
||||
|
||||
|
|
@ -190,6 +204,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
} returns cardanoFetcherResult
|
||||
|
||||
|
|
@ -207,11 +222,13 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
xpub = null,
|
||||
)
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cardano.network,
|
||||
networkCurrencies = setOf(cardano),
|
||||
xpub = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -245,7 +262,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
networksStatusesStore.setSourceAsOnlyCache(params.userWalletId, params.networks)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) { commonNetworkStatusFetcher.fetch(any(), any(), any()) }
|
||||
coVerify(inverse = true) { commonNetworkStatusFetcher.fetch(any(), any(), any(), any()) }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ import java.util.EnumSet
|
|||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LargeClass", "TooManyFunctions")
|
||||
@Suppress("LargeClass", "TooManyFunctions", "LongParameterList")
|
||||
internal class DefaultWalletManagersFacade @Inject constructor(
|
||||
private val walletManagersStore: WalletManagersStore,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
|
|
@ -85,6 +85,7 @@ internal class DefaultWalletManagersFacade @Inject constructor(
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
extraTokens: Set<CryptoCurrency.Token>,
|
||||
xpub: String?,
|
||||
): UpdateWalletManagerResult {
|
||||
val userWallet = getUserWallet(userWalletId)
|
||||
val blockchain = network.toBlockchain()
|
||||
|
|
@ -95,6 +96,7 @@ internal class DefaultWalletManagersFacade @Inject constructor(
|
|||
blockchain = blockchain,
|
||||
derivationPath = derivationPath,
|
||||
extraTokens = extraTokens,
|
||||
xpub = xpub,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -309,6 +311,7 @@ internal class DefaultWalletManagersFacade @Inject constructor(
|
|||
blockchain: Blockchain,
|
||||
derivationPath: String?,
|
||||
extraTokens: Set<CryptoCurrency.Token>,
|
||||
xpub: String? = null,
|
||||
): UpdateWalletManagerResult {
|
||||
if (derivationPath != null && !userWallet.hasDerivation(blockchain, derivationPath)) {
|
||||
TangemLogger.w("Derivation missed for: $blockchain")
|
||||
|
|
@ -326,6 +329,7 @@ internal class DefaultWalletManagersFacade @Inject constructor(
|
|||
}
|
||||
|
||||
val isUpdated = updateWalletManagerTokensIfNeeded(walletManager, extraTokens)
|
||||
if (xpub != null) restoreXpubModeIfNeeded(walletManager, xpub)
|
||||
|
||||
return try {
|
||||
if (userWallet is UserWallet.Cold && demoConfig.isDemoCardId(userWallet.scanResponse.card.cardId)) {
|
||||
|
|
@ -499,6 +503,17 @@ internal class DefaultWalletManagersFacade @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun restoreXpubModeIfNeeded(walletManager: WalletManager, xpub: String) {
|
||||
val dynamicAddressesManager = walletManager as? DynamicAddressesManager ?: return
|
||||
|
||||
try {
|
||||
dynamicAddressesManager.enableDynamicAddresses(xpub)
|
||||
TangemLogger.i("Restored XPUB mode for ${walletManager.wallet.blockchain}")
|
||||
} catch (e: Exception) {
|
||||
TangemLogger.e("Failed to restore XPUB mode: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
// endregion Dynamic Addresses
|
||||
|
||||
@Deprecated("Will be removed in future")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue