Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-15 18:34:01 +04:00
parent 41d216b4a8
commit 7503c2817c
30 changed files with 86 additions and 1172 deletions

View file

@ -7,7 +7,6 @@ import com.tangem.domain.managetokens.repository.ManageTokensRepository
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.tokens.repository.CurrenciesRepository
@ -72,7 +71,6 @@ internal object ManageTokensDomainModule {
walletManagersFacade: WalletManagersFacade,
currenciesRepository: CurrenciesRepository,
derivationsRepository: DerivationsRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
@ -84,7 +82,6 @@ internal object ManageTokensDomainModule {
walletManagersFacade = walletManagersFacade,
currenciesRepository = currenciesRepository,
derivationsRepository = derivationsRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,

View file

@ -10,8 +10,6 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import dagger.Module
@ -62,21 +60,17 @@ object MarketsDomainModule {
derivationsRepository: DerivationsRepository,
marketsTokenRepository: MarketsTokenRepository,
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): SaveMarketTokensUseCase {
return SaveMarketTokensUseCase(
derivationsRepository = derivationsRepository,
marketsTokenRepository = marketsTokenRepository,
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
)
}

View file

@ -94,12 +94,10 @@ internal object StakingDomainModule {
@Provides
@Singleton
fun provideFetchStakingYieldBalanceUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
): FetchStakingYieldBalanceUseCase {
return FetchStakingYieldBalanceUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver,
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
)

View file

@ -41,7 +41,6 @@ internal object TokensDomainModule {
@Singleton
fun provideAddCryptoCurrenciesUseCase(
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
@ -50,7 +49,6 @@ internal object TokensDomainModule {
): AddCryptoCurrenciesUseCase {
return AddCryptoCurrenciesUseCase(
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
@ -63,19 +61,15 @@ internal object TokensDomainModule {
@Singleton
fun provideFetchTokenListUseCase(
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): FetchTokenListUseCase {
return FetchTokenListUseCase(
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@ -173,7 +167,6 @@ internal object TokensDomainModule {
@Singleton
fun provideFetchCurrencyStatusUseCase(
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
@ -182,7 +175,6 @@ internal object TokensDomainModule {
): FetchCurrencyStatusUseCase {
return FetchCurrencyStatusUseCase(
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
@ -195,19 +187,15 @@ internal object TokensDomainModule {
@Singleton
fun provideFetchCardTokenListUseCase(
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): FetchCardTokenListUseCase {
return FetchCardTokenListUseCase(
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
)
}

View file

@ -7,9 +7,6 @@ internal class DefaultTokensFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : TokensFeatureToggles {
override val isStakingLoadingRefactoringEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "STAKING_LOADING_REFACTORING_ENABLED")
override val isWalletBalanceFetcherEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "WALLET_BALANCE_FETCHER_ENABLED")
}

View file

@ -23,10 +23,6 @@
"name": "WALLET_CONNECT_REDESIGN_ENABLED",
"version": "undefined"
},
{
"name": "STAKING_LOADING_REFACTORING_ENABLED",
"version": "5.25.0"
},
{
"name": "PUSH_NOTIFICATIONS_ENABLED",
"version": "undefined"

View file

@ -8,8 +8,10 @@ import com.squareup.moshi.Moshi
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
import com.tangem.datasource.local.datastore.RuntimeDataStore
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.datasource.local.token.*
import com.tangem.datasource.local.token.DefaultStakingActionsStore
import com.tangem.datasource.local.token.DefaultStakingYieldsStore
import com.tangem.datasource.local.token.StakingActionsStore
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.datasource.utils.MoshiDataStoreSerializer
import com.tangem.datasource.utils.listTypes
import com.tangem.datasource.utils.mapWithStringKeyTypes
@ -66,17 +68,6 @@ internal object StakingStoreModule {
)
}
@Provides
@Singleton
fun provideStakingBalanceStore(
persistenceStore: DataStore<Map<String, Set<YieldBalanceWrapperDTO>>>,
): StakingBalanceStore {
return DefaultStakingBalanceStore(
persistenceStore = persistenceStore,
runtimeStore = RuntimeSharedStore(),
)
}
@Provides
@Singleton
fun provideStakingActionsStore(): StakingActionsStore {

View file

@ -1,269 +0,0 @@
package com.tangem.datasource.local.token
import androidx.datastore.core.DataStore
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.datasource.local.token.StakingBalanceStore.StakingID
import com.tangem.datasource.local.token.converter.YieldBalanceConverter
import com.tangem.domain.models.StatusSource
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
internal typealias YieldBalanceWrappersDTO = Map<String, Set<YieldBalanceWrapperDTO>>
internal typealias YieldBalanceListByWalletId = Map<UserWalletId, Set<YieldBalance>>
/**
* Default implementation of [StakingBalanceStore]
*
* @property persistenceStore persistence store
* @property runtimeStore runtime store
*/
internal class DefaultStakingBalanceStore(
private val persistenceStore: DataStore<YieldBalanceWrappersDTO>,
private val runtimeStore: RuntimeSharedStore<YieldBalanceListByWalletId>,
) : StakingBalanceStore {
override fun get(userWalletId: UserWalletId, stakingIds: List<StakingID>): Flow<Set<YieldBalance>> = channelFlow {
val cachedBalances = persistenceStore.data
.map {
val wrappers = it[userWalletId.stringValue].orEmpty()
.filter { wrapper ->
stakingIds.any { id ->
id.address == wrapper.addresses.address && id.integrationId == wrapper.integrationId
}
}
YieldBalanceConverter(isCached = true).convertSet(input = wrappers)
}
.firstOrNull()
.orEmpty()
if (cachedBalances.isNotEmpty()) {
send(cachedBalances)
}
runtimeStore.get()
.map {
it[userWalletId].orEmpty().filter { balance ->
stakingIds.any { id ->
id.address == balance.address && id.integrationId == balance.integrationId
}
}
.toSet()
}
.onEach {
val mergedBalances = mergeYieldBalances(
stakingIds = stakingIds,
cachedBalances = cachedBalances,
runtimeBalances = it,
)
send(mergedBalances)
}
.launchIn(scope = this)
}
override fun get(userWalletId: UserWalletId, stakingID: StakingID): Flow<YieldBalance?> {
return get(userWalletId = userWalletId, stakingIds = listOf(stakingID)).map { balances ->
balances.getBalance(stakingID = stakingID)
}
}
override suspend fun getSyncOrNull(userWalletId: UserWalletId): Set<YieldBalance>? {
val runtimeBalances = runtimeStore.getSyncOrNull()?.getValue(userWalletId).orEmpty()
val cachedBalances = persistenceStore.data.firstOrNull()?.get(userWalletId.stringValue).orEmpty()
if (runtimeBalances.isEmpty() && cachedBalances.isEmpty()) return null
return cachedBalances.mapTo(hashSetOf()) {
val cached = YieldBalanceConverter(source = StatusSource.ONLY_CACHE).convert(value = it)
val runtime = runtimeBalances.getBalance(address = cached.address, integrationId = cached.integrationId)
if (runtime == null || runtime is YieldBalance.Error) cached else runtime
}
}
override suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingIds: List<StakingID>): Set<YieldBalance>? {
val runtime = runtimeStore.getSyncOrNull()?.getValue(userWalletId)
val cached = persistenceStore.data.firstOrNull()?.get(userWalletId.stringValue)
if (runtime.isNullOrEmpty() && cached.isNullOrEmpty()) return null
return mergeYieldBalances(
cachedBalances = YieldBalanceConverter(source = StatusSource.ONLY_CACHE)
.convertSet(input = cached.orEmpty()),
runtimeBalances = runtime.orEmpty(),
stakingIds = stakingIds,
)
}
override suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingID: StakingID): YieldBalance? {
val balances = getSyncOrNull(userWalletId = userWalletId, stakingIds = listOf(stakingID)) ?: return null
return balances.getBalance(stakingID = stakingID)
}
override suspend fun store(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>) {
coroutineScope {
launch {
val newBalances = YieldBalanceConverter(isCached = false).convertSet(input = items)
runtimeStore.update(default = emptyMap()) { saved ->
saved.toMutableMap().apply {
this[userWalletId] = saved[userWalletId]
?.addOrReplace(newBalances) { old, new ->
old.integrationId == new.integrationId && old.address == new.address
}
?: newBalances
}
}
}
launch { storeInPersistenceStore(userWalletId = userWalletId, items = items) }
}
}
override suspend fun refresh(userWalletId: UserWalletId, stakingIds: List<StakingID>) {
updateRuntimeStore(userWalletId = userWalletId) { saved ->
saved.mapTo(hashSetOf()) {
val yieldBalance = it.takeIf { balance ->
stakingIds.any { id -> balance.integrationId == id.integrationId && balance.address == id.address }
}
yieldBalance?.copySealed(source = StatusSource.CACHE) ?: it
}
}
}
override suspend fun store(userWalletId: UserWalletId, stakingID: StakingID, item: YieldBalanceWrapperDTO) {
coroutineScope {
launch {
storeInRuntimeStore(
userWalletId = userWalletId,
integrationId = stakingID.integrationId,
address = stakingID.address,
item = item,
)
storeInPersistenceStore(
userWalletId = userWalletId,
integrationId = stakingID.integrationId,
address = stakingID.address,
item = item,
)
}
}
}
override suspend fun storeSingleYieldBalance(userWalletId: UserWalletId, item: YieldBalance) {
runtimeStore.update(default = emptyMap()) { saved ->
saved.toMutableMap().apply {
this[userWalletId] = saved[userWalletId]
?.addOrReplace(item) { it.integrationId == item.integrationId && it.address == item.address }
?: setOf(item)
}
}
}
private suspend fun storeInRuntimeStore(
userWalletId: UserWalletId,
integrationId: String,
address: String,
item: YieldBalanceWrapperDTO,
) {
val newBalance = YieldBalanceConverter(isCached = false).convert(value = item)
runtimeStore.update(default = emptyMap()) { saved ->
saved.toMutableMap().apply {
this[userWalletId] = saved[userWalletId]
?.addOrReplace(newBalance) { it.integrationId == integrationId && it.address == address }
?: setOf(newBalance)
}
}
}
private suspend fun updateRuntimeStore(
userWalletId: UserWalletId,
function: (Set<YieldBalance>) -> Set<YieldBalance>,
) {
runtimeStore.update(default = emptyMap()) { saved ->
saved.toMutableMap().apply {
this[userWalletId] = function(this[userWalletId].orEmpty())
}
}
}
private suspend fun storeInPersistenceStore(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>) {
persistenceStore.updateData { current ->
current.toMutableMap().apply {
this[userWalletId.stringValue] = current[userWalletId.stringValue]
?.addOrReplace(items = items) { old, new ->
old.integrationId == new.integrationId && old.addresses.address == new.addresses.address
}
?: items
}
}
}
private suspend fun storeInPersistenceStore(
userWalletId: UserWalletId,
integrationId: String,
address: String,
item: YieldBalanceWrapperDTO,
) {
persistenceStore.updateData { current ->
current.toMutableMap().apply {
this[userWalletId.stringValue] = current[userWalletId.stringValue]
?.addOrReplace(item) { it.integrationId == integrationId && it.addresses.address == address }
?: setOf(item)
}
}
}
private fun mergeYieldBalances(
cachedBalances: Set<YieldBalance>,
runtimeBalances: Set<YieldBalance>,
stakingIds: List<StakingID>,
): Set<YieldBalance> {
return stakingIds.mapTo(hashSetOf()) { id ->
val runtime = runtimeBalances.getBalance(stakingID = id)
if (runtime == null || runtime is YieldBalance.Error) {
getCachedBalanceIfPossible(cachedBalances = cachedBalances, stakingID = id)
} else {
runtime
}
}
}
private fun getCachedBalanceIfPossible(cachedBalances: Set<YieldBalance>, stakingID: StakingID): YieldBalance {
val cached = cachedBalances.getBalance(stakingID)
?: return YieldBalance.Error(integrationId = stakingID.address, address = stakingID.integrationId)
val updatedCached = when (cached) {
is YieldBalance.Data -> cached.copy(source = StatusSource.ONLY_CACHE)
is YieldBalance.Empty -> cached.copy(source = StatusSource.ONLY_CACHE)
is YieldBalance.Error,
is YieldBalance.Unsupported,
-> null
}
return updatedCached ?: YieldBalance.Error(integrationId = stakingID.address, address = stakingID.integrationId)
}
private fun Set<YieldBalance>.getBalance(stakingID: StakingID): YieldBalance? {
return getBalance(address = stakingID.address, integrationId = stakingID.integrationId)
}
private fun Set<YieldBalance>.getBalance(address: String?, integrationId: String?): YieldBalance? {
return firstOrNull { yieldBalance ->
val isCorrectAddress = address != null && address == yieldBalance.address
val isCorrectIntegration = integrationId != null && yieldBalance.integrationId == integrationId
isCorrectIntegration && isCorrectAddress
}
}
}

View file

@ -1,40 +0,0 @@
package com.tangem.datasource.local.token
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
/** Staking balance store */
interface StakingBalanceStore {
/** Get flow of [YieldBalanceList] by [userWalletId] and [stakingIds] */
fun get(userWalletId: UserWalletId, stakingIds: List<StakingID>): Flow<Set<YieldBalance>>
/** Get flow of [YieldBalance] by [userWalletId] and [stakingID] */
fun get(userWalletId: UserWalletId, stakingID: StakingID): Flow<YieldBalance?>
/** Get all [YieldBalance] synchronously or null by [userWalletId] */
suspend fun getSyncOrNull(userWalletId: UserWalletId): Set<YieldBalance>?
/** Get [YieldBalanceList] synchronously or null by [userWalletId] and [stakingIds] */
suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingIds: List<StakingID>): Set<YieldBalance>?
/** Get [YieldBalance] synchronously or null by [userWalletId] and [stakingID] */
suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingID: StakingID): YieldBalance?
/** Store [items] by [userWalletId] */
suspend fun store(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>)
/** Store [item] by [userWalletId] and [stakingID] */
suspend fun store(userWalletId: UserWalletId, stakingID: StakingID, item: YieldBalanceWrapperDTO)
/** Store [item] by [userWalletId] */
suspend fun storeSingleYieldBalance(userWalletId: UserWalletId, item: YieldBalance)
/** Refresh balances of [stakingIds] by [userWalletId] */
suspend fun refresh(userWalletId: UserWalletId, stakingIds: List<StakingID>)
data class StakingID(val integrationId: String, val address: String)
}

View file

@ -2,7 +2,6 @@ package com.tangem.data.staking
import android.util.Base64
import arrow.core.getOrElse
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.squareup.moshi.Moshi
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
@ -15,8 +14,6 @@ import com.tangem.blockchainsdk.utils.toCoinId
import com.tangem.blockchainsdk.utils.toMigratedCoinId
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toCompressedPublicKey
import com.tangem.data.common.api.safeApiCall
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.staking.converters.YieldBalanceListConverter
import com.tangem.data.staking.converters.YieldConverter
import com.tangem.data.staking.converters.action.ActionStatusConverter
@ -27,16 +24,13 @@ import com.tangem.data.staking.converters.transaction.StakingTransactionStatusCo
import com.tangem.data.staking.converters.transaction.StakingTransactionTypeConverter
import com.tangem.data.staking.store.YieldsBalancesStore
import com.tangem.data.staking.utils.StakingIdFactory
import com.tangem.data.staking.utils.StakingIdFactory.Companion.integrationIdMap
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.stakekit.models.request.*
import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO
import com.tangem.datasource.api.stakekit.models.response.model.transaction.tron.TronStakeKitTransaction
import com.tangem.datasource.local.token.StakingBalanceStore
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.datasource.local.token.converter.StakingNetworkTypeConverter
import com.tangem.datasource.local.token.converter.TokenConverter
@ -68,9 +62,7 @@ import com.tangem.lib.crypto.BlockchainUtils.isSolana
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.orZero
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import timber.log.Timber
import java.math.BigDecimal
import kotlin.time.Duration.Companion.seconds
@ -79,9 +71,7 @@ import kotlin.time.Duration.Companion.seconds
internal class DefaultStakingRepository(
private val stakeKitApi: StakeKitApi,
private val stakingYieldsStore: StakingYieldsStore,
private val stakingBalanceStore: StakingBalanceStore,
private val stakingBalanceStoreV2: YieldsBalancesStore,
private val cacheRegistry: CacheRegistry,
private val dispatchers: CoroutineDispatcherProvider,
private val walletManagersFacade: WalletManagersFacade,
private val getUserWalletUseCase: GetUserWalletUseCase,
@ -107,12 +97,8 @@ internal class DefaultStakingRepository(
private val networkTypeAdapter by lazy { moshi.adapter(NetworkTypeDTO::class.java) }
private val stakingActionStatusAdapter by lazy { moshi.adapter(StakingActionStatusDTO::class.java) }
override fun getIntegrationKey(cryptoCurrencyId: CryptoCurrency.ID): String = with(cryptoCurrencyId) {
rawNetworkId.plus(rawCurrencyId)
}
override fun getSupportedIntegrationId(cryptoCurrencyId: CryptoCurrency.ID): String? {
return integrationIdMap.getOrDefault(getIntegrationKey(cryptoCurrencyId), null)
return stakingIdFactory.createIntegrationId(currencyId = cryptoCurrencyId)
}
override suspend fun fetchEnabledYields() {
@ -398,107 +384,6 @@ internal class DefaultStakingRepository(
}
}
override suspend fun fetchSingleYieldBalance(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
refresh: Boolean,
) = withContext(dispatchers.io) {
cacheRegistry.invokeOnExpire(
key = getYieldBalancesKey(userWalletId),
skipCache = refresh,
block = {
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network)
if (integrationId == null || address.isNullOrBlank()) {
cacheRegistry.invalidate(getYieldBalancesKey(userWalletId))
Timber.w(
"IntegrationId or address is null fetching ${cryptoCurrency.name} staking balance",
)
return@invokeOnExpire
}
val requestBody = getBalanceRequestData(address, integrationId)
safeApiCall(
call = {
val result = stakeKitApi.getSingleYieldBalance(
integrationId = requestBody.integrationId,
body = requestBody,
).bind()
stakingBalanceStore.store(
userWalletId = userWalletId,
stakingID = StakingBalanceStore.StakingID(
integrationId = requestBody.integrationId,
address = address,
),
item = YieldBalanceWrapperDTO(
balances = result,
integrationId = requestBody.integrationId,
addresses = requestBody.addresses,
),
)
},
onError = {
stakingBalanceStore.storeSingleYieldBalance(
userWalletId = userWalletId,
item = YieldBalance.Error(integrationId = requestBody.integrationId, address = address),
)
},
)
},
)
}
override fun getSingleYieldBalanceFlow(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): Flow<YieldBalance> = channelFlow {
launch(dispatchers.io) {
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
?: error("Could not get integrationId")
stakingBalanceStore.get(
userWalletId = userWalletId,
stakingID = StakingBalanceStore.StakingID(integrationId = integrationId, address = address),
)
.distinctUntilChanged()
.collectLatest {
if (it != null) {
send(it)
} else {
FirebaseCrashlytics.getInstance()
.log("No yield balance available for currency ${cryptoCurrency.id.value}")
send(YieldBalance.Error(integrationId, address))
}
}
}
withContext(dispatchers.io) {
fetchSingleYieldBalance(userWalletId = userWalletId, cryptoCurrency = cryptoCurrency)
}
}.cancellable()
override suspend fun getSingleYieldBalanceSyncLegacy(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): YieldBalance = withContext(dispatchers.io) {
fetchSingleYieldBalance(userWalletId, cryptoCurrency)
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
?: error("Could not get integrationId")
stakingBalanceStore.getSyncOrNull(
userWalletId = userWalletId,
stakingID = StakingBalanceStore.StakingID(integrationId = integrationId, address = address),
)
?: YieldBalance.Error(integrationId, address)
}
override suspend fun getSingleYieldBalanceSync(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
@ -513,129 +398,6 @@ internal class DefaultStakingRepository(
?: YieldBalance.Error(integrationId = stakingId.integrationId, address = stakingId.address)
}
@Suppress("LongMethod")
override suspend fun fetchMultiYieldBalance(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
refresh: Boolean,
) = withContext(dispatchers.io) {
if (refresh) {
stakingBalanceStore.refresh(
userWalletId = userWalletId,
stakingIds = cryptoCurrencies.mapStakingId(userWalletId),
)
}
val yieldDTOs = withTimeoutOrNull(YIELDS_WATITING_TIMEOUT) {
runCatching { stakingYieldsStore.get().firstOrNull() }.getOrNull()
}
if (yieldDTOs == null) {
Timber.i("No enabled yields for $userWalletId")
stakingBalanceStore.store(userWalletId, emptySet())
return@withContext
}
cacheRegistry.invokeOnExpire(
key = getYieldBalancesKey(userWalletId),
skipCache = refresh,
block = {
val yields = YieldConverter.convertListIgnoreErrors(
input = yieldDTOs,
onError = { Timber.e("Error converting one of the items in enabled yields: $it") },
)
val availableCurrencies = cryptoCurrencies
.mapNotNull { currency ->
val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network)
val integrationId = integrationIdMap[getIntegrationKey(currency.id)]
if (integrationId != null && yields.any { it.id == integrationId }) {
addresses to integrationId
} else {
null
}
}
.flatMap { (addresses, integrationId) ->
addresses.map { address -> address to integrationId }
}
.map { getBalanceRequestData(it.first.value, it.second) }
.ifEmpty {
stakingBalanceStore.store(userWalletId, emptySet())
cacheRegistry.invalidate(getYieldBalancesKey(userWalletId))
return@invokeOnExpire
}
val yieldBalances = safeApiCall(
call = {
stakeKitApi
.getMultipleYieldBalances(availableCurrencies)
.bind()
},
onError = {
Timber.e(it, "Unable to fetch yield balances")
cacheRegistry.invalidate(getYieldBalancesKey(userWalletId))
emptySet()
},
)
stakingBalanceStore.store(userWalletId, yieldBalances)
},
)
}
private suspend fun List<CryptoCurrency>.mapStakingId(
userWalletId: UserWalletId,
): List<StakingBalanceStore.StakingID> {
return this
.mapNotNull { currency ->
val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network)
val integrationId = integrationIdMap[getIntegrationKey(currency.id)]
if (integrationId != null) {
addresses to integrationId
} else {
null
}
}
.flatMap { (addresses, integrationId) ->
addresses.map { address ->
StakingBalanceStore.StakingID(
integrationId = integrationId,
address = address.value,
)
}
}
}
override fun getMultiYieldBalanceUpdates(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): Flow<YieldBalanceList> {
return flow {
stakingBalanceStore.get(
userWalletId = userWalletId,
stakingIds = cryptoCurrencies.mapStakingId(userWalletId),
)
.map(YieldBalanceListConverter::convert)
.collect { emit(it) }
}
}
override suspend fun getMultiYieldBalanceSyncLegacy(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): YieldBalanceList = withContext(dispatchers.io) {
fetchMultiYieldBalance(userWalletId, cryptoCurrencies)
stakingBalanceStore.getSyncOrNull(userWalletId, cryptoCurrencies.mapStakingId(userWalletId))
?.let(YieldBalanceListConverter::convert)
?: YieldBalanceList.Error
}
override suspend fun getMultiYieldBalanceSync(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
@ -724,7 +486,9 @@ internal class DefaultStakingRepository(
}
override fun getStakingApproval(cryptoCurrency: CryptoCurrency): StakingApproval {
return when (getIntegrationKey(cryptoCurrency.id)) {
val integrationId = stakingIdFactory.createIntegrationId(currencyId = cryptoCurrency.id)
return when (integrationId) {
Blockchain.Ethereum.id + Blockchain.Polygon.toCoinId(),
Blockchain.Ethereum.id + Blockchain.Polygon.toMigratedCoinId(),
-> StakingApproval.Needed(ETHEREUM_POLYGON_APPROVE_SPENDER)
@ -773,22 +537,6 @@ internal class DefaultStakingRepository(
}
}
private fun getBalanceRequestData(address: String, integrationId: String): YieldBalanceRequestBody {
return YieldBalanceRequestBody(
addresses = Address(
address = address,
additionalAddresses = null, // todo fill additional addresses metadata if needed
explorerUrl = "", // todo fill exporer url [REDACTED_JIRA]
),
args = YieldBalanceRequestBody.YieldBalanceRequestArgs(
validatorAddresses = listOf(), // todo add validators [REDACTED_JIRA]
),
integrationId = integrationId,
)
}
private fun getYieldBalancesKey(userWalletId: UserWalletId) = "yield_balance_${userWalletId.stringValue}"
private fun getTronResource(network: Network): TronResource? {
val blockchain = Blockchain.fromNetworkId(network.backendId)

View file

@ -3,7 +3,6 @@ package com.tangem.data.staking.di
import com.squareup.moshi.Moshi
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.staking.DefaultStakingActionRepository
import com.tangem.data.staking.DefaultStakingErrorResolver
import com.tangem.data.staking.DefaultStakingRepository
@ -17,7 +16,6 @@ import com.tangem.datasource.api.stakekit.models.response.model.error.StakeKitEr
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.token.StakingActionsStore
import com.tangem.datasource.local.token.StakingBalanceStore
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.repositories.StakingActionRepository
import com.tangem.domain.staking.repositories.StakingErrorResolver
@ -42,9 +40,7 @@ internal object StakingDataModule {
fun provideStakingRepository(
stakeKitApi: StakeKitApi,
stakingYieldsStore: StakingYieldsStore,
stakingBalanceStore: StakingBalanceStore,
yieldsBalancesStore: YieldsBalancesStore,
cacheRegistry: CacheRegistry,
dispatchers: CoroutineDispatcherProvider,
walletManagersFacade: WalletManagersFacade,
getUserWalletUseCase: GetUserWalletUseCase,
@ -55,9 +51,7 @@ internal object StakingDataModule {
return DefaultStakingRepository(
stakeKitApi = stakeKitApi,
stakingYieldsStore = stakingYieldsStore,
stakingBalanceStore = stakingBalanceStore,
stakingBalanceStoreV2 = yieldsBalancesStore,
cacheRegistry = cacheRegistry,
dispatchers = dispatchers,
walletManagersFacade = walletManagersFacade,
getUserWalletUseCase = getUserWalletUseCase,

View file

@ -55,7 +55,7 @@ internal class StakingIdFactory @Inject constructor(
private const val CARDANO_INTEGRATION_ID = "cardano-ada-native-staking"
// uncomment items as implementation is ready
val integrationIdMap = mapOf(
private val integrationIdMap = mapOf(
Blockchain.TON.toDefaultKey() to TON_INTEGRATION_ID,
Blockchain.Solana.toDefaultKey() to SOLANA_INTEGRATION_ID,
Blockchain.Cosmos.toDefaultKey() to COSMOS_INTEGRATION_ID,

View file

@ -10,7 +10,6 @@ import com.tangem.domain.models.network.Network
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.TokensFeatureToggles
@ -24,7 +23,6 @@ class SaveManagedTokensUseCase(
private val walletManagersFacade: WalletManagersFacade,
private val currenciesRepository: CurrenciesRepository,
private val derivationsRepository: DerivationsRepository,
private val stakingRepository: StakingRepository,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
@ -119,20 +117,12 @@ class SaveManagedTokensUseCase(
userWalletId: UserWalletId,
existingCurrencies: List<CryptoCurrency>,
) {
if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) {
multiYieldBalanceFetcher(
params = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyIdWithNetworkMap = existingCurrencies.associateTo(hashMapOf()) { it.id to it.network },
),
)
} else {
stakingRepository.fetchMultiYieldBalance(
multiYieldBalanceFetcher(
params = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
cryptoCurrencies = existingCurrencies,
refresh = true,
)
}
currencyIdWithNetworkMap = existingCurrencies.associateTo(hashMapOf()) { it.id to it.network },
),
)
}
private suspend fun refreshUpdatedQuotes(addedCurrencies: List<CryptoCurrency>) {

View file

@ -8,8 +8,6 @@ import com.tangem.domain.models.network.Network
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId
@ -27,11 +25,9 @@ class SaveMarketTokensUseCase(
private val derivationsRepository: DerivationsRepository,
private val marketsTokenRepository: MarketsTokenRepository,
private val currenciesRepository: CurrenciesRepository,
private val stakingRepository: StakingRepository,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
private val tokensFeatureToggles: TokensFeatureToggles,
) {
suspend operator fun invoke(
@ -92,20 +88,12 @@ class SaveMarketTokensUseCase(
userWalletId: UserWalletId,
existingCurrencies: List<CryptoCurrency>,
) {
if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) {
multiYieldBalanceFetcher(
params = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyIdWithNetworkMap = existingCurrencies.associateTo(hashMapOf()) { it.id to it.network },
),
)
} else {
stakingRepository.fetchMultiYieldBalance(
multiYieldBalanceFetcher(
params = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
cryptoCurrencies = existingCurrencies,
refresh = true,
)
}
currencyIdWithNetworkMap = existingCurrencies.associateTo(hashMapOf()) { it.id to it.network },
),
)
}
private suspend fun refreshUpdatedQuotes(addedCurrencies: List<CryptoCurrency>) {

View file

@ -6,12 +6,10 @@ import arrow.core.raise.either
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
import com.tangem.domain.wallets.models.UserWalletId
class FetchStakingYieldBalanceUseCase(
private val stakingRepository: StakingRepository,
private val stakingErrorResolver: StakingErrorResolver,
private val singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
) {
@ -19,27 +17,17 @@ class FetchStakingYieldBalanceUseCase(
suspend operator fun invoke(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
isRefactoringEnabled: Boolean,
refresh: Boolean = false,
): Either<StakingError, Unit> {
return either {
catch(
block = {
if (isRefactoringEnabled) {
singleYieldBalanceFetcher(
params = SingleYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyId = cryptoCurrency.id,
network = cryptoCurrency.network,
),
)
} else {
stakingRepository.fetchSingleYieldBalance(
singleYieldBalanceFetcher(
params = SingleYieldBalanceFetcher.Params(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
refresh = refresh,
)
}
currencyId = cryptoCurrency.id,
network = cryptoCurrency.network,
),
)
},
catch = { stakingErrorResolver.resolve(it) },
)

View file

@ -25,8 +25,6 @@ import java.math.BigDecimal
@Suppress("TooManyFunctions")
interface StakingRepository {
fun getIntegrationKey(cryptoCurrencyId: CryptoCurrency.ID): String
fun getSupportedIntegrationId(cryptoCurrencyId: CryptoCurrency.ID): String?
suspend fun fetchEnabledYields()
@ -51,37 +49,8 @@ interface StakingRepository {
stakingActionStatus: StakingActionStatus,
): List<StakingAction>
suspend fun fetchSingleYieldBalance(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
refresh: Boolean = false,
)
fun getSingleYieldBalanceFlow(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Flow<YieldBalance>
suspend fun getSingleYieldBalanceSyncLegacy(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): YieldBalance
suspend fun getSingleYieldBalanceSync(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): YieldBalance
suspend fun fetchMultiYieldBalance(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
refresh: Boolean = false,
)
fun getMultiYieldBalanceUpdates(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): Flow<YieldBalanceList>
suspend fun getMultiYieldBalanceSyncLegacy(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): YieldBalanceList
suspend fun getMultiYieldBalanceSync(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,

View file

@ -8,7 +8,6 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId
@ -26,7 +25,6 @@ import kotlinx.coroutines.coroutineScope
@Suppress("LongParameterList")
class AddCryptoCurrenciesUseCase(
private val currenciesRepository: CurrenciesRepository,
private val stakingRepository: StakingRepository,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
@ -156,21 +154,13 @@ class AddCryptoCurrenciesUseCase(
}
private suspend fun refreshUpdatedYieldBalances(userWalletId: UserWalletId, addedCurrency: CryptoCurrency) {
if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) {
singleYieldBalanceFetcher(
params = SingleYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyId = addedCurrency.id,
network = addedCurrency.network,
),
)
} else {
stakingRepository.fetchSingleYieldBalance(
singleYieldBalanceFetcher(
params = SingleYieldBalanceFetcher.Params(
userWalletId = userWalletId,
cryptoCurrency = addedCurrency,
refresh = true,
)
}
currencyId = addedCurrency.id,
network = addedCurrency.network,
),
)
}
private suspend fun refreshUpdatedQuotes(currencyToAdd: CryptoCurrency) {

View file

@ -9,7 +9,6 @@ import com.tangem.domain.models.network.Network
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId
@ -17,14 +16,11 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
@Suppress("LongParameterList")
class FetchCardTokenListUseCase(
private val currenciesRepository: CurrenciesRepository,
private val stakingRepository: StakingRepository,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
private val tokensFeatureToggles: TokensFeatureToggles,
) {
suspend operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = false): Either<TokenListError, Unit> {
@ -47,7 +43,6 @@ class FetchCardTokenListUseCase(
fetchYieldBalances(
userWalletId = userWalletId,
currencies = currencies,
refresh = refresh,
)
}
awaitAll(fetchStatuses, fetchQuotes, yieldBalances)
@ -87,23 +82,12 @@ class FetchCardTokenListUseCase(
)
}
private suspend fun fetchYieldBalances(
userWalletId: UserWalletId,
currencies: List<CryptoCurrency>,
refresh: Boolean,
) {
if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) {
multiYieldBalanceFetcher(
params = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyIdWithNetworkMap = currencies.associateTo(hashMapOf()) { it.id to it.network },
),
)
} else {
catch(
block = { stakingRepository.fetchMultiYieldBalance(userWalletId, currencies, refresh) },
catch = { /* Ignore error */ },
)
}
private suspend fun fetchYieldBalances(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
multiYieldBalanceFetcher(
params = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyIdWithNetworkMap = currencies.associateTo(hashMapOf()) { it.id to it.network },
),
)
}
}

View file

@ -9,7 +9,6 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.repository.CurrenciesRepository
@ -29,7 +28,6 @@ import kotlinx.coroutines.coroutineScope
@Suppress("LongParameterList")
class FetchCurrencyStatusUseCase(
private val currenciesRepository: CurrenciesRepository,
private val stakingRepository: StakingRepository,
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
@ -42,14 +40,9 @@ class FetchCurrencyStatusUseCase(
*
* @param userWalletId The ID of the user's wallet.
* @param id The ID of the cryptocurrency.
* @param refresh Indicates whether to force a refresh of the status data.
* @return An [Either] representing success (Right) or an error (Left) in fetching the status.
*/
suspend operator fun invoke(
userWalletId: UserWalletId,
id: CryptoCurrency.ID,
refresh: Boolean = false,
): Either<CurrencyStatusError, Unit> {
suspend operator fun invoke(userWalletId: UserWalletId, id: CryptoCurrency.ID): Either<CurrencyStatusError, Unit> {
return either {
val currency = getCurrency(userWalletId, id)
@ -61,7 +54,7 @@ class FetchCurrencyStatusUseCase(
val fetchQuote = async { fetchQuote(currencyId = currency.id) }
val fetchStakingBalance = async {
fetchStakingBalance(userWalletId = userWalletId, cryptoCurrency = currency, refresh = refresh)
fetchStakingBalance(userWalletId = userWalletId, cryptoCurrency = currency)
}
awaitAll(fetchStatus, fetchQuote, fetchStakingBalance).summarizeResult()
@ -143,19 +136,14 @@ class FetchCurrencyStatusUseCase(
private suspend fun fetchStakingBalance(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
refresh: Boolean,
): Either<Throwable, Unit> {
return if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) {
singleYieldBalanceFetcher(
params = SingleYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyId = cryptoCurrency.id,
network = cryptoCurrency.network,
),
)
} else {
Either.catch { stakingRepository.fetchSingleYieldBalance(userWalletId, cryptoCurrency, refresh) }
}
return singleYieldBalanceFetcher(
params = SingleYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyId = cryptoCurrency.id,
network = cryptoCurrency.network,
),
)
}
private fun List<Either<Throwable, Unit>>.summarizeResult(): Either<Throwable, Unit> {

View file

@ -11,7 +11,6 @@ import com.tangem.domain.models.network.Network
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId
@ -24,17 +23,12 @@ import kotlinx.coroutines.coroutineScope
* network statuses, and quotes for tokens associated with a user's wallet.
*
* @param currenciesRepository The repository for retrieving currency-related data.
* @param stakingRepository The repository for retrieving staking-related data.
*/
// TODO: Add tests
@Suppress("LongParameterList")
class FetchTokenListUseCase(
private val currenciesRepository: CurrenciesRepository,
private val stakingRepository: StakingRepository,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
private val tokensFeatureToggles: TokensFeatureToggles,
) {
/**
@ -42,22 +36,17 @@ class FetchTokenListUseCase(
* network statuses, and quotes for associated tokens.
*
* @param userWalletId The ID of the user's wallet.
* @param mode The refresh mode to control the fetching process.
* @return An [Either] representing success (Right) or an error (Left) in fetching the token list.
*/
suspend operator fun invoke(
userWalletId: UserWalletId,
mode: RefreshMode = RefreshMode.NONE,
): Either<TokenListError, Unit> = either {
val currencies = fetchCurrencies(userWalletId, refresh = mode.refreshCurrencies)
suspend operator fun invoke(userWalletId: UserWalletId): Either<TokenListError, Unit> = either {
val currencies = fetchCurrencies(userWalletId)
invoke(userWalletId = userWalletId, currencies = currencies, mode = mode)
invoke(userWalletId = userWalletId, currencies = currencies)
}
suspend operator fun invoke(
userWalletId: UserWalletId,
currencies: List<CryptoCurrency>,
mode: RefreshMode = RefreshMode.NONE,
): Either<TokenListError, Unit> = either {
coroutineScope {
val fetchStatuses = async {
@ -73,23 +62,16 @@ class FetchTokenListUseCase(
}
val yieldBalances = async {
fetchYieldBalances(
userWalletId = userWalletId,
currencies = currencies,
refresh = mode.refreshYieldBalances,
)
fetchYieldBalances(userWalletId = userWalletId, currencies = currencies)
}
awaitAll(fetchStatuses, fetchQuotes, yieldBalances)
}
}
private suspend fun Raise<TokenListError>.fetchCurrencies(
userWalletId: UserWalletId,
refresh: Boolean,
): List<CryptoCurrency> {
private suspend fun Raise<TokenListError>.fetchCurrencies(userWalletId: UserWalletId): List<CryptoCurrency> {
val currencies = catch(
block = { currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId, refresh) },
block = { currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId, true) },
) {
raise(TokenListError.DataError(it))
}
@ -121,48 +103,12 @@ class FetchTokenListUseCase(
.bind()
}
private suspend fun fetchYieldBalances(
userWalletId: UserWalletId,
currencies: List<CryptoCurrency>,
refresh: Boolean,
) {
if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) {
multiYieldBalanceFetcher(
params = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyIdWithNetworkMap = currencies.associateTo(hashMapOf()) { it.id to it.network },
),
)
} else {
catch(
block = { stakingRepository.fetchMultiYieldBalance(userWalletId, currencies, refresh) },
catch = { /* Ignore error */ },
)
}
}
/**
* Represents the refresh modes available for fetching token list information.
*/
enum class RefreshMode(
internal val refreshCurrencies: Boolean,
internal val refreshQuotes: Boolean,
internal val refreshYieldBalances: Boolean,
) {
NONE(
refreshCurrencies = false,
refreshQuotes = false,
refreshYieldBalances = false,
),
FULL(
refreshCurrencies = true,
refreshQuotes = true,
refreshYieldBalances = true,
),
SKIP_CURRENCIES(
refreshCurrencies = false,
refreshQuotes = true,
refreshYieldBalances = true,
),
private suspend fun fetchYieldBalances(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
multiYieldBalanceFetcher(
params = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyIdWithNetworkMap = currencies.associateTo(hashMapOf()) { it.id to it.network },
),
)
}
}

View file

@ -7,7 +7,5 @@ package com.tangem.domain.tokens
*/
interface TokensFeatureToggles {
val isStakingLoadingRefactoringEnabled: Boolean
val isWalletBalanceFetcherEnabled: Boolean
}

View file

@ -234,8 +234,7 @@ abstract class BaseCurrencyStatusOperations(
userWalletId = userWalletId,
currency = currency,
includeQuotes = includeQuotes,
// If toggle is off, then subscribe on yield balance. If toggle is on, then don't
subscribeOnYieldBalance = !tokensFeatureToggles.isStakingLoadingRefactoringEnabled,
subscribeOnYieldBalance = false,
)
}
@ -340,17 +339,13 @@ abstract class BaseCurrencyStatusOperations(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): EitherFlow<Error, YieldBalance> {
return if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) {
singleYieldBalanceSupplier(
params = SingleYieldBalanceProducer.Params(
userWalletId = userWalletId,
currencyId = cryptoCurrency.id,
network = cryptoCurrency.network,
),
)
} else {
stakingRepository.getSingleYieldBalanceFlow(userWalletId = userWalletId, cryptoCurrency = cryptoCurrency)
}
return singleYieldBalanceSupplier(
params = SingleYieldBalanceProducer.Params(
userWalletId = userWalletId,
currencyId = cryptoCurrency.id,
network = cryptoCurrency.network,
),
)
.map<YieldBalance, Either<Error, YieldBalance>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(Error.EmptyYieldBalances.left()) }
@ -404,18 +399,10 @@ abstract class BaseCurrencyStatusOperations(
): Either<Error.EmptyYieldBalances, YieldBalanceList> {
return catch(
block = {
if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) {
stakingRepository.getMultiYieldBalanceSync(
userWalletId = userWalletId,
cryptoCurrencies = cryptoCurrencies,
)
} else {
stakingRepository.getMultiYieldBalanceSyncLegacy(
userWalletId = userWalletId,
cryptoCurrencies = cryptoCurrencies,
)
}
.right()
stakingRepository.getMultiYieldBalanceSync(
userWalletId = userWalletId,
cryptoCurrencies = cryptoCurrencies,
).right()
},
catch = {
Error.EmptyYieldBalances.left()
@ -428,14 +415,7 @@ abstract class BaseCurrencyStatusOperations(
cryptoCurrency: CryptoCurrency,
): Either<Error.EmptyYieldBalances, YieldBalance> {
return catch(
block = {
if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) {
stakingRepository.getSingleYieldBalanceSync(userWalletId, cryptoCurrency)
} else {
stakingRepository.getSingleYieldBalanceSyncLegacy(userWalletId, cryptoCurrency)
}
.right()
},
block = { stakingRepository.getSingleYieldBalanceSync(userWalletId, cryptoCurrency).right() },
catch = {
Error.EmptyYieldBalances.left()
},

View file

@ -162,7 +162,7 @@ class CachedCurrenciesStatusesOperations(
combine(
flow = getQuotes(currenciesIds),
flow2 = getNetworkStatusesUpdates(userWalletId, networks),
flow3 = getYieldBalances(userWalletId, currencies),
flow3 = getYieldsBalancesUpdates(userWalletId, currencies),
flow4 = fetchingState.map {
val state = it[userWalletId] ?: return@map false
@ -213,16 +213,12 @@ class CachedCurrenciesStatusesOperations(
)
},
async {
if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) {
multiYieldBalanceFetcher(
params = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyIdWithNetworkMap = currencies.associateTo(hashMapOf()) { it.id to it.network },
),
)
} else {
stakingRepository.fetchMultiYieldBalance(userWalletId, currencies)
}
multiYieldBalanceFetcher(
params = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyIdWithNetworkMap = currencies.associateTo(hashMapOf()) { it.id to it.network },
),
)
},
)
}
@ -308,25 +304,6 @@ class CachedCurrenciesStatusesOperations(
.distinctUntilChanged()
}
private fun getYieldBalances(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): EitherFlow<TokenListError, YieldBalanceList> {
return if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) {
getYieldsBalancesUpdates(userWalletId, cryptoCurrencies)
} else {
stakingRepository.getMultiYieldBalanceUpdates(userWalletId, cryptoCurrencies)
.map<YieldBalanceList, Either<TokenListError, YieldBalanceList>> { it.right() }
.retryWhen { cause, _ ->
emit(TokenListError.DataError(cause).left())
// adding delay before retry to avoid spam when flow restarted
delay(RETRY_DELAY)
true
}
.distinctUntilChanged()
}
}
// temporary code because token list is built using networks list
@OptIn(FlowPreview::class)
private fun getNetworkStatusesUpdates(
@ -433,7 +410,7 @@ class CachedCurrenciesStatusesOperations(
}
private fun isFetchingStarted(userWalletId: UserWalletId): Boolean {
return fetchingState.value[userWalletId]?.let { it.isStarted() || it.isFinished() } ?: false
return fetchingState.value[userWalletId]?.let { it.isStarted() || it.isFinished() } == true
}
private fun setFetchStarted(userWalletId: UserWalletId) {
@ -460,7 +437,6 @@ class CachedCurrenciesStatusesOperations(
}
companion object {
internal const val RETRY_DELAY = 2000L
private val fetchingState = MutableStateFlow(value = emptyMap<UserWalletId, FetchingState>())
}

View file

@ -1,268 +0,0 @@
package com.tangem.domain.tokens.repository
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.staking.model.StakingApproval
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.stakekit.*
import com.tangem.domain.staking.model.stakekit.action.StakingAction
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
import com.tangem.domain.staking.model.stakekit.transaction.*
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.flowOf
import org.joda.time.DateTime
import java.math.BigDecimal
class MockStakingRepository : StakingRepository {
override fun getSupportedIntegrationId(cryptoCurrencyId: CryptoCurrency.ID): String? = null
override fun getIntegrationKey(cryptoCurrencyId: CryptoCurrency.ID): String = ""
override suspend fun fetchEnabledYields() {
/* no-op */
}
override suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo =
StakingEntryInfo(
apr = 1.toBigDecimal(),
tokenSymbol = "SOL",
rewardSchedule = Yield.Metadata.RewardSchedule.DAY,
)
override suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield = yield
override suspend fun getYield(yieldId: String) = yield
override fun getStakingAvailability(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): Flow<StakingAvailability> = flowOf(StakingAvailability.Unavailable)
override suspend fun getStakingAvailabilitySync(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): StakingAvailability = StakingAvailability.Unavailable
override suspend fun getActions(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
networkType: NetworkType,
stakingActionStatus: StakingActionStatus,
): List<StakingAction> {
return emptyList()
}
override suspend fun fetchSingleYieldBalance(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
refresh: Boolean,
) {
/* no-op */
}
override fun getSingleYieldBalanceFlow(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): Flow<YieldBalance> = channelFlow {
send(YieldBalance.Error(integrationId = null, address = null))
}
override suspend fun getSingleYieldBalanceSyncLegacy(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): YieldBalance = YieldBalance.Error(integrationId = null, address = null)
override suspend fun getSingleYieldBalanceSync(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): YieldBalance = YieldBalance.Error(integrationId = null, address = null)
override suspend fun fetchMultiYieldBalance(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
refresh: Boolean,
) {
/* no-op */
}
override fun getMultiYieldBalanceUpdates(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): Flow<YieldBalanceList> {
return flowOf(
YieldBalanceList.Data(
balances = listOf(YieldBalance.Error(integrationId = null, address = null)),
),
)
}
override suspend fun getMultiYieldBalanceSyncLegacy(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): YieldBalanceList = YieldBalanceList.Data(
balances = listOf(YieldBalance.Error(integrationId = null, address = null)),
)
override suspend fun getMultiYieldBalanceSync(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): YieldBalanceList = YieldBalanceList.Data(
balances = listOf(YieldBalance.Error(integrationId = null, address = null)),
)
override suspend fun createAction(
userWalletId: UserWalletId,
network: Network,
params: ActionParams,
): StakingAction {
return StakingAction(
id = "quis",
integrationId = "persequeris",
status = StakingActionStatus.PROCESSING,
type = StakingActionType.CLAIM_REWARDS,
currentStepIndex = 8701,
amount = BigDecimal.ZERO,
validatorAddress = null,
validatorAddresses = listOf(),
transactions = listOf(),
createdAt = DateTime.now(),
)
}
override suspend fun estimateGas(
userWalletId: UserWalletId,
network: Network,
params: ActionParams,
): StakingGasEstimate {
return StakingGasEstimate(
amount = BigDecimal(0.0001),
token = Token(
name = "Solana",
network = NetworkType.SOLANA,
symbol = "SOL",
decimals = 18,
address = null,
coinGeckoId = "solana",
logoURI = null,
isPoints = null,
),
gasLimit = null,
)
}
override suspend fun constructTransaction(
networkId: String,
fee: Fee,
amount: Amount,
transactionId: String,
): Pair<StakingTransaction, TransactionData.Compiled> = StakingTransaction(
id = "id",
network = NetworkType.SOLANA,
status = StakingTransactionStatus.SIGNED,
type = StakingTransactionType.FREEZE_ENERGY,
hash = null,
signedTransaction = null,
unsignedTransaction = null,
stepIndex = 9368,
error = null,
gasEstimate = null,
stakeId = null,
explorerUrl = null,
ledgerHwAppId = null,
isMessage = false,
) to TransactionData.Compiled(
value = TransactionData.Compiled.Data.RawString(""),
status = TransactionStatus.Unconfirmed,
)
override fun getStakingApproval(cryptoCurrency: CryptoCurrency): StakingApproval = StakingApproval.Empty
override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean = false
override fun getActionRequirementAmount(integrationId: String, stakingActionType: StakingActionType): BigDecimal? =
null
private companion object {
val yield = Yield(
id = "1",
token = Token(
name = "Solana",
network = NetworkType.SOLANA,
symbol = "SOL",
decimals = 18,
address = null,
coinGeckoId = "solana",
logoURI = null,
isPoints = null,
),
tokens = listOf(),
args = Yield.Args(
enter = Yield.Args.Enter(
addresses = Yield.Args.Enter.Addresses(
address = AddressArgument(
required = false,
network = null,
minimum = null,
maximum = null,
),
additionalAddresses = mapOf(),
),
args = mapOf(),
),
exit = null,
),
status = Yield.Status(enter = false, exit = null),
apy = 1.toBigDecimal(),
rewardRate = 2.3,
rewardType = Yield.RewardType.APR,
metadata = Yield.Metadata(
name = "Yield",
logoUri = "",
description = "",
documentation = null,
gasFeeToken = Token(
name = "Solana",
network = NetworkType.SOLANA,
symbol = "SOL",
decimals = 18,
address = null,
coinGeckoId = null,
logoURI = null,
isPoints = null,
),
token = Token(
name = "Solana",
network = NetworkType.SOLANA,
symbol = "SOL",
decimals = 18,
address = null,
coinGeckoId = null,
logoURI = null,
isPoints = null,
),
tokens = listOf(),
type = "auto",
rewardSchedule = Yield.Metadata.RewardSchedule.DAY,
cooldownPeriod = Yield.Metadata.Period(days = 1),
warmupPeriod = Yield.Metadata.Period(days = 1),
rewardClaiming = Yield.Metadata.RewardClaiming.AUTO,
defaultValidator = null,
minimumStake = null,
supportsMultipleValidators = false,
revshare = Yield.Metadata.Enabled(enabled = false),
fee = Yield.Metadata.Enabled(enabled = false),
),
validators = listOf(),
isAvailable = false,
)
}
}

View file

@ -65,6 +65,7 @@ dependencies {
implementation(projects.domain.transaction)
implementation(projects.domain.transaction.models)
implementation(projects.domain.txhistory)
implementation(projects.domain.txhistory.models)
implementation(projects.domain.feedback)
implementation(projects.domain.feedback.models)
implementation(projects.domain.notifications.models)

View file

@ -43,7 +43,6 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
fetchStakingYieldBalanceUseCase(
userWalletId = userWallet.walletId,
cryptoCurrency = cryptoCurrencyStatus.currency,
isRefactoringEnabled = true,
)
},
// we should update tx history and network for new balances
@ -82,7 +81,6 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
fetchCurrencyStatusUseCase(
userWalletId = userWallet.walletId,
id = cryptoCurrencyStatus.currency.id,
refresh = true,
)
}

View file

@ -324,14 +324,14 @@ internal class SwapInteractorImpl @AssistedInject constructor(
ifRight = { quotes ->
quotes.allowanceContract?.let {
isAllowedToSpend(networkId, fromToken.currency, amount, it)
} ?: true
} != false
},
ifLeft = { false },
)
if (isAllowedToSpend && allowPermissionsHandler.isAddressAllowanceInProgress(fromTokenAddress)) {
allowPermissionsHandler.removeAddressFromProgress(fromTokenAddress)
fetchCurrencyStatusUseCase(userWalletId, fromToken.currency.id, true)
fetchCurrencyStatusUseCase(userWalletId = userWalletId, id = fromToken.currency.id)
}
return if (isAllowedToSpend && isBalanceWithoutFeeEnough) {
provider to loadDexSwapData(

View file

@ -125,7 +125,6 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
isMultiCurrency -> fetchCurrencyStatusUseCase.invoke(
userWalletId = userWallet.walletId,
id = cryptoCurrency.id,
refresh = true,
)
!isMultiCurrency && tokensFeatureToggles.isWalletBalanceFetcherEnabled ->
walletBalanceFetcher(params = WalletBalanceFetcher.Params(userWalletId = userWallet.walletId))

View file

@ -777,13 +777,7 @@ internal class TokenDetailsModel @Inject constructor(
modelScope.launch(dispatchers.main) {
listOf(
async {
fetchCurrencyStatusUseCase(
userWalletId = userWalletId,
id = cryptoCurrency.id,
refresh = true,
)
},
async { fetchCurrencyStatusUseCase(userWalletId = userWalletId, id = cryptoCurrency.id) },
async {
updateTxHistory(
refresh = true,

View file

@ -10,7 +10,6 @@ import com.tangem.domain.settings.NeverToShowWalletsScrollPreview
import com.tangem.domain.tokens.FetchCardTokenListUseCase
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.FetchTokenListUseCase
import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
@ -140,7 +139,7 @@ internal class WalletClickIntents @Inject constructor(
val maybeFetchResult = if (isSingleWalletWithToken) {
fetchCardTokenListUseCase(userWalletId = userWallet.walletId, refresh = true)
} else {
fetchTokenListUseCase(userWalletId = userWallet.walletId, mode = RefreshMode.FULL)
fetchTokenListUseCase(userWalletId = userWallet.walletId)
}
maybeFetchResult.onLeft {