Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-03 16:00:38 +04:00
parent 57d3a102ab
commit c501b56063
23 changed files with 296 additions and 256 deletions

View file

@ -26,6 +26,7 @@ dependencies {
api(projects.domain.referral)
api(projects.domain.staking)
api(projects.domain.tokens)
api(projects.domain.walletManager)
api(projects.domain.wallets)
implementation(projects.libs.blockchainSdk)

View file

@ -7,8 +7,6 @@
<ID>MultilineLambdaItParameter:AccountCryptoCurrencyStatusFinder.kt$AccountCryptoCurrencyStatusFinder${ val currency = it.currency val isContractAddressMatch = contractAddress == null || currency.id.contractAddress.equals(contractAddress, ignoreCase = true) currency.network.rawId == networkId.rawId.value &amp;&amp; currency.network.derivationPath.value == derivationPath.value &amp;&amp; isContractAddressMatch }</ID>
<ID>MultilineLambdaItParameter:ApplyTokenListSortingUseCaseV2.kt$ApplyTokenListSortingUseCaseV2${ errors[account.accountId] = it return@map account }</ID>
<ID>MultilineLambdaItParameter:DefaultMultiAccountStatusListProducer.kt$DefaultMultiAccountStatusListProducer${ singleAccountStatusListSupplier( params = SingleAccountStatusListProducer.Params(it.walletId), ) }</ID>
<ID>MultilineLambdaItParameter:ManageCryptoCurrenciesUseCase.kt$ManageCryptoCurrenciesUseCase${ ExpressAsset.ID( networkId = it.network.backendId, contractAddress = (it as? CryptoCurrency.Token)?.contractAddress, ) }</ID>
<ID>MultilineLambdaItParameter:ManageCryptoCurrenciesUseCase.kt$ManageCryptoCurrenciesUseCase${ it.network.backendId == networkId &amp;&amp; !it.isCustom &amp;&amp; it.contractAddress.equals(contractAddress, true) }</ID>
<ID>UnnecessaryAbstractClass:MultiAccountStatusListSupplier.kt$MultiAccountStatusListSupplier$MultiAccountStatusListSupplier</ID>
<ID>UnnecessaryAbstractClass:SingleAccountStatusListSupplier.kt$SingleAccountStatusListSupplier$SingleAccountStatusListSupplier</ID>
<ID>UnnecessaryAbstractClass:SingleAccountStatusSupplier.kt$SingleAccountStatusSupplier$SingleAccountStatusSupplier</ID>

View file

@ -16,6 +16,7 @@ import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.utils.StakingCleaner
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -95,6 +96,7 @@ internal object AccountStatusUseCaseModule {
accountsCRUDRepository: AccountsCRUDRepository,
currenciesRepository: CurrenciesRepository,
derivationsRepository: DerivationsRepository,
walletManagersFacade: WalletManagersFacade,
cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
stakingIdFactory: StakingIdFactory,
networksCleaner: NetworksCleaner,
@ -107,6 +109,7 @@ internal object AccountStatusUseCaseModule {
accountsCRUDRepository = accountsCRUDRepository,
currenciesRepository = currenciesRepository,
derivationsRepository = derivationsRepository,
walletManagersFacade = walletManagersFacade,
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
stakingIdFactory = stakingIdFactory,
networksCleaner = networksCleaner,
@ -120,7 +123,6 @@ internal object AccountStatusUseCaseModule {
@Provides
@Singleton
fun provideCryptoCurrencyBalanceFetcher(
accountsCRUDRepository: AccountsCRUDRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
@ -128,7 +130,6 @@ internal object AccountStatusUseCaseModule {
dispatchers: CoroutineDispatcherProvider,
): CryptoCurrencyBalanceFetcher {
return CryptoCurrencyBalanceFetcher(
accountsCRUDRepository = accountsCRUDRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,

View file

@ -21,8 +21,10 @@ import com.tangem.domain.networks.utils.NetworksCleaner
import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.utils.StakingCleaner
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching
import kotlinx.coroutines.*
import timber.log.Timber
@ -49,6 +51,7 @@ class ManageCryptoCurrenciesUseCase(
private val accountsCRUDRepository: AccountsCRUDRepository,
private val currenciesRepository: CurrenciesRepository,
private val derivationsRepository: DerivationsRepository,
private val walletManagersFacade: WalletManagersFacade,
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
private val stakingIdFactory: StakingIdFactory,
private val networksCleaner: NetworksCleaner,
@ -83,6 +86,11 @@ class ManageCryptoCurrenciesUseCase(
val modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies()
.modify(add = add, remove = remove)
if (!modifiedCurrencyList.hasChanges) {
Timber.d("No changes in currencies, skipping")
return@withContext
}
saveAccount(
account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()),
)
@ -90,17 +98,7 @@ class ManageCryptoCurrenciesUseCase(
derivePublicKeys(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
parallelUpdatingScope.launch {
/*
* If only removal of currencies happened, we need to sync tokens. Otherwise, tokens will be synced
* when balances are refreshed for added currencies.
*/
val isOnlyRemoval = modifiedCurrencyList.added.isEmpty() && modifiedCurrencyList.removed.isNotEmpty()
if (isOnlyRemoval) {
launch { accountsCRUDRepository.syncTokens(userWalletId) }
clearMetadata(userWalletId = userWalletId, currencies = modifiedCurrencyList.removed)
return@launch
}
syncTokens(userWalletId, modifiedCurrencyList)
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total)
@ -121,10 +119,10 @@ class ManageCryptoCurrenciesUseCase(
val foundToken = accountStatus.tokenList.flattenCurrencies()
.mapNotNull { it.currency as? CryptoCurrency.Token }
.firstOrNull {
it.network.backendId == networkId &&
!it.isCustom &&
it.contractAddress.equals(contractAddress, true)
.firstOrNull { token ->
token.network.backendId == networkId &&
!token.isCustom &&
token.contractAddress.equals(contractAddress, true)
}
if (foundToken != null) return@withContext foundToken
@ -137,6 +135,8 @@ class ManageCryptoCurrenciesUseCase(
saveAccount(account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()))
parallelUpdatingScope.launch {
syncTokens(userWalletId, modifiedCurrencyList)
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = listOf(tokenToAdd))
refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total)
}
@ -260,15 +260,40 @@ class ManageCryptoCurrenciesUseCase(
)
}
private suspend fun syncTokens(userWalletId: UserWalletId, modifiedCurrencyList: ModifiedCurrencyList) {
createWalletManagers(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
runSuspendCatching { accountsCRUDRepository.syncTokens(userWalletId) }
.onFailure { Timber.e(it, "Failed to sync tokens for wallet $userWalletId") }
}
/**
* Creates wallet managers for the given [currencies] if they do not already exist.
* The method will generate addresses for new networks to ensure the stability of the "Push notifications" feature.
*
* @param userWalletId The ID of the user's wallet.
* @param currencies The list of cryptocurrencies for which to create wallet managers.
*/
private suspend fun createWalletManagers(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
val networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network)
for (network in networks) {
runSuspendCatching {
walletManagersFacade.getOrCreateWalletManager(userWalletId = userWalletId, network = network)
}
.onFailure { Timber.e(it, "Failed to create wallet manager for network ${network.id}") }
}
}
private suspend fun refreshExpress(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
if (currencies.isEmpty()) return
coroutineScope {
launch {
val assetIds = currencies.mapTo(hashSetOf()) {
val assetIds = currencies.mapTo(hashSetOf()) { currency ->
ExpressAsset.ID(
networkId = it.network.backendId,
contractAddress = (it as? CryptoCurrency.Token)?.contractAddress,
networkId = currency.network.backendId,
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
)
}
@ -325,5 +350,8 @@ class ManageCryptoCurrenciesUseCase(
val added: List<CryptoCurrency>,
val removed: List<CryptoCurrency>,
val total: List<CryptoCurrency>,
)
) {
val hasChanges get() = added.isNotEmpty() || removed.isNotEmpty()
}
}

View file

@ -1,8 +1,6 @@
package com.tangem.domain.account.status.utils
import arrow.core.Either
import arrow.core.raise.either
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
@ -19,7 +17,6 @@ import timber.log.Timber
* Utility class responsible for fetching and refreshing the balances of various crypto currencies
* associated with a user's wallet.
*
* @property accountsCRUDRepository Repository for managing account data.
* @property multiNetworkStatusFetcher Fetcher for updating network statuses.
* @property multiQuoteStatusFetcher Fetcher for updating quote statuses.
* @property multiYieldBalanceFetcher Fetcher for updating yield balances.
@ -29,7 +26,6 @@ import timber.log.Timber
[REDACTED_AUTHOR]
*/
class CryptoCurrencyBalanceFetcher(
private val accountsCRUDRepository: AccountsCRUDRepository,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
@ -80,22 +76,13 @@ class CryptoCurrencyBalanceFetcher(
private suspend fun refreshNetworks(
userWalletId: UserWalletId,
currencies: List<CryptoCurrency>,
): Either<Throwable, Unit> = either {
val either = multiNetworkStatusFetcher(
): Either<Throwable, Unit> {
return multiNetworkStatusFetcher(
params = MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network),
),
)
arrow.core.raise.catch(
block = { accountsCRUDRepository.syncTokens(userWalletId) },
catch = {
Timber.e(it, "Failed to sync tokens for wallet: $userWalletId")
},
)
return either
}
private suspend fun refreshYieldBalances(