Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-22 21:33:23 +04:00
parent a0ef13e0cf
commit 2286db21d0
22 changed files with 733 additions and 1005 deletions

View file

@ -5,6 +5,7 @@ import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.operations.*
import com.tangem.domain.tokens.repository.*
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.features.swap.SwapFeatureToggles
@ -62,17 +63,11 @@ internal object TokensDomainModule {
@Singleton
fun provideGetTokenListUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
tokensFeatureToggles: TokensFeatureToggles,
baseCurrenciesStatusesOperations: BaseCurrenciesStatusesOperations,
): GetTokenListUseCase {
return GetTokenListUseCase(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
tokensFeatureToggles = tokensFeatureToggles,
currenciesStatusesOperations = baseCurrenciesStatusesOperations,
)
}
@ -88,20 +83,12 @@ internal object TokensDomainModule {
@Provides
@Singleton
fun provideGetCurrencyUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
dispatchers: CoroutineDispatcherProvider,
tokensFeatureToggles: TokensFeatureToggles,
): GetCurrencyStatusUpdatesUseCase {
return GetCurrencyStatusUpdatesUseCase(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
currencyStatusOperations = baseCurrencyStatusOperations,
dispatchers = dispatchers,
tokensFeatureToggles = tokensFeatureToggles,
)
}
@ -109,16 +96,12 @@ internal object TokensDomainModule {
@Singleton
fun provideGetAllWalletsCryptoCurrencyStatusesUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
currencyStatusOperations: BaseCurrencyStatusOperations,
dispatchers: CoroutineDispatcherProvider,
): GetAllWalletsCryptoCurrencyStatusesUseCase {
return GetAllWalletsCryptoCurrencyStatusesUseCase(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
currencyStatusOperations = currencyStatusOperations,
dispatchers = dispatchers,
)
}
@ -128,40 +111,30 @@ internal object TokensDomainModule {
fun provideGetCurrencyWarningsUseCase(
walletManagersFacade: WalletManagersFacade,
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
currencyChecksRepository: CurrencyChecksRepository,
stakingRepository: StakingRepository,
dispatchers: CoroutineDispatcherProvider,
tokensFeatureToggles: TokensFeatureToggles,
baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
): GetCurrencyWarningsUseCase {
return GetCurrencyWarningsUseCase(
walletManagersFacade = walletManagersFacade,
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
currencyChecksRepository = currencyChecksRepository,
stakingRepository = stakingRepository,
dispatchers = dispatchers,
tokensFeatureToggles = tokensFeatureToggles,
currencyStatusOperations = baseCurrencyStatusOperations,
)
}
@Provides
@Singleton
fun provideGetPrimaryCurrencyUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
currencyStatusOperations: BaseCurrencyStatusOperations,
dispatchers: CoroutineDispatcherProvider,
): GetPrimaryCurrencyStatusUpdatesUseCase {
return GetPrimaryCurrencyStatusUpdatesUseCase(
currenciesRepository,
quotesRepository,
networksRepository,
stakingRepository,
dispatchers,
currencyStatusOperations = currencyStatusOperations,
dispatchers = dispatchers,
)
}
@ -190,19 +163,9 @@ internal object TokensDomainModule {
@Provides
@Singleton
fun providesGetCryptoCurrencyStatusSyncUseCase(
currenciesRepository: CurrenciesRepository,
dispatcherProvider: CoroutineDispatcherProvider,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
currencyStatusOperations: BaseCurrencyStatusOperations,
): GetCryptoCurrencyStatusSyncUseCase {
return GetCryptoCurrencyStatusSyncUseCase(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
dispatchers = dispatcherProvider,
)
return GetCryptoCurrencyStatusSyncUseCase(currencyStatusOperations)
}
@Provides
@ -240,40 +203,32 @@ internal object TokensDomainModule {
rampStateManager: RampStateManager,
walletManagersFacade: WalletManagersFacade,
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
promoRepository: PromoRepository,
swapFeatureToggles: SwapFeatureToggles,
dispatchers: CoroutineDispatcherProvider,
currencyStatusOperations: BaseCurrencyStatusOperations,
): GetCryptoCurrencyActionsUseCase {
return GetCryptoCurrencyActionsUseCase(
rampManager = rampStateManager,
walletManagersFacade = walletManagersFacade,
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
promoRepository = promoRepository,
swapFeatureToggles = swapFeatureToggles,
dispatchers = dispatchers,
currencyStatusOperations = currencyStatusOperations,
)
}
@Provides
@Singleton
fun provideGetCurrencyStatusByNetworkUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
currencyStatusOperations: BaseCurrencyStatusOperations,
dispatchers: CoroutineDispatcherProvider,
): GetNetworkCoinStatusUseCase {
return GetNetworkCoinStatusUseCase(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
currencyStatusOperations = currencyStatusOperations,
dispatchers = dispatchers,
)
}
@ -282,17 +237,11 @@ internal object TokensDomainModule {
@Singleton
fun provideGetFeePaidCryptoCurrencyStatusSyncUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
dispatchers: CoroutineDispatcherProvider,
currencyStatusOperations: BaseCurrencyStatusOperations,
): GetFeePaidCryptoCurrencyStatusSyncUseCase {
return GetFeePaidCryptoCurrencyStatusSyncUseCase(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
dispatchers = dispatchers,
currencyStatusOperations = currencyStatusOperations,
)
}
@ -363,19 +312,9 @@ internal object TokensDomainModule {
@Provides
@Singleton
fun provideGetWalletTotalBalanceUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
tokensFeatureToggles: TokensFeatureToggles,
baseCurrenciesStatusesOperations: BaseCurrenciesStatusesOperations,
): GetWalletTotalBalanceUseCase {
return GetWalletTotalBalanceUseCase(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
tokensFeatureToggles = tokensFeatureToggles,
)
return GetWalletTotalBalanceUseCase(baseCurrenciesStatusesOperations)
}
@Provides
@ -398,4 +337,56 @@ internal object TokensDomainModule {
): GetCurrencyCheckUseCase {
return GetCurrencyCheckUseCase(currencyChecksRepository, dispatchers)
}
@Provides
@Singleton
fun provideBaseCurrenciesStatusesOperations(
tokensFeatureToggles: TokensFeatureToggles,
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
): BaseCurrenciesStatusesOperations {
return if (tokensFeatureToggles.isBalancesCachingEnabled) {
CachedCurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
)
} else {
LceCurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
)
}
}
@Provides
@Singleton
fun provideBaseCurrencyStatusOperations(
tokensFeatureToggles: TokensFeatureToggles,
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
): BaseCurrencyStatusOperations {
return if (tokensFeatureToggles.isBalancesCachingEnabled) {
CachedCurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
)
} else {
CurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
)
}
}
}

View file

@ -1,15 +1,13 @@
package com.tangem.domain.tokens
import arrow.core.Either
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -19,19 +17,14 @@ import kotlinx.coroutines.flow.*
* Get crypto currency statuses by raw ID for all wallets
*
* @property currenciesRepository currencies repository
* @property quotesRepository quotes repository
* @property networksRepository networks repository
* @property stakingRepository staking repository
* @property dispatchers dispatchers
*
[REDACTED_AUTHOR]
*/
class GetAllWalletsCryptoCurrencyStatusesUseCase(
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
private val dispatchers: CoroutineDispatcherProvider,
private val currencyStatusOperations: BaseCurrencyStatusOperations,
) {
/**
@ -47,16 +40,8 @@ class GetAllWalletsCryptoCurrencyStatusesUseCase(
return currenciesRepository.getAllWalletsCryptoCurrencies(currencyRawId, needFilterByAvailable)
.flatMapLatest { userWalletsWithCurrencies: Map<UserWallet, List<CryptoCurrency>> ->
val walletStatusFlows = userWalletsWithCurrencies.map { (userWallet, cryptoCurrencies) ->
val operations = CurrenciesStatusesOperations(
userWalletId = userWallet.walletId,
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
)
val currencyStatusFlows = cryptoCurrencies.map { cryptoCurrency ->
operations.getCurrencyStatusFlow(cryptoCurrency)
currencyStatusOperations.getCurrencyStatusFlow(userWallet.walletId, cryptoCurrency)
.map { it.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError) }
}

View file

@ -7,10 +7,8 @@ import com.tangem.domain.promo.models.StoryContentIds
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet
@ -30,36 +28,29 @@ class GetCryptoCurrencyActionsUseCase(
private val rampManager: RampStateManager,
private val walletManagersFacade: WalletManagersFacade,
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
private val promoRepository: PromoRepository,
private val dispatchers: CoroutineDispatcherProvider,
private val swapFeatureToggles: SwapFeatureToggles,
private val currencyStatusOperations: BaseCurrencyStatusOperations,
) {
suspend operator fun invoke(
userWallet: UserWallet,
cryptoCurrencyStatus: CryptoCurrencyStatus,
): Flow<TokenActionsState> {
val operations = CurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
userWalletId = userWallet.walletId,
)
val networkId = cryptoCurrencyStatus.currency.network.id
val requirements = withTimeoutOrNull(REQUEST_EXCHANGE_DATA_TIMEOUT) {
walletManagersFacade.getAssetRequirements(userWallet.walletId, cryptoCurrencyStatus.currency)
}
return flow {
val networkFlow = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
operations.getNetworkCoinForSingleWalletWithTokenFlow(networkId)
currencyStatusOperations.getNetworkCoinForSingleWalletWithTokenFlow(userWallet.walletId, networkId)
} else if (!userWallet.isMultiCurrency) {
operations.getPrimaryCurrencyStatusFlow(includeQuotes = false)
currencyStatusOperations.getPrimaryCurrencyStatusFlow(userWallet.walletId, includeQuotes = false)
} else {
operations.getNetworkCoinFlow(
currencyStatusOperations.getNetworkCoinFlow(
userWallet.walletId,
networkId = networkId,
derivationPath = cryptoCurrencyStatus.currency.network.derivationPath,
includeQuotes = false,

View file

@ -1,24 +1,15 @@
package com.tangem.domain.tokens
import arrow.core.Either
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
class GetCryptoCurrencyStatusSyncUseCase(
internal val currenciesRepository: CurrenciesRepository,
internal val quotesRepository: QuotesRepository,
internal val networksRepository: NetworksRepository,
internal val stakingRepository: StakingRepository,
internal val dispatchers: CoroutineDispatcherProvider,
private val currencyStatusOperations: BaseCurrencyStatusOperations,
) {
// multi-currency
@ -27,29 +18,13 @@ class GetCryptoCurrencyStatusSyncUseCase(
cryptoCurrencyId: CryptoCurrency.ID,
isSingleWalletWithTokens: Boolean = false,
): Either<CurrencyStatusError, CryptoCurrencyStatus> {
val operations = CurrenciesStatusesOperations(
userWalletId = userWalletId,
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
)
return operations.getCurrencyStatusSync(cryptoCurrencyId, isSingleWalletWithTokens)
return currencyStatusOperations.getCurrencyStatusSync(userWalletId, cryptoCurrencyId, isSingleWalletWithTokens)
.mapLeft { error -> error.mapToCurrencyError() }
}
// single-currency
suspend operator fun invoke(userWalletId: UserWalletId): Either<CurrencyStatusError, CryptoCurrencyStatus> {
val operations = CurrenciesStatusesOperations(
userWalletId = userWalletId,
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
)
return operations.getPrimaryCurrencyStatusSync()
return currencyStatusOperations.getPrimaryCurrencyStatusSync(userWalletId)
.mapLeft { error -> error.mapToCurrencyError() }
}
}

View file

@ -1,35 +1,18 @@
package com.tangem.domain.tokens
import arrow.core.Either
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
class GetCryptoCurrencyStatusesSyncUseCase(
internal val currenciesRepository: CurrenciesRepository,
internal val quotesRepository: QuotesRepository,
internal val networksRepository: NetworksRepository,
internal val stakingRepository: StakingRepository,
internal val dispatchers: CoroutineDispatcherProvider,
private val currencyStatusOperations: BaseCurrencyStatusOperations,
) {
suspend operator fun invoke(userWalletId: UserWalletId): Either<TokenListError, List<CryptoCurrencyStatus>> {
val operations = CurrenciesStatusesOperations(
userWalletId = userWalletId,
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
)
return operations.getCurrenciesStatusesSync()
return currencyStatusOperations.getCurrenciesStatusesSync(userWalletId)
.mapLeft { error -> error.mapToTokenListError() }
}
}

View file

@ -1,16 +1,12 @@
package com.tangem.domain.tokens
import arrow.core.Either
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.operations.CurrenciesStatusesCachedOperations
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
@ -18,17 +14,10 @@ import kotlinx.coroutines.flow.*
/**
* Use case for fetching the status of a cryptocurrency associated with a user wallet.
*
* @property currenciesRepository Repository for managing and fetching cryptocurrencies.
* @property quotesRepository Repository for managing and fetching cryptocurrency quotes.
* @property networksRepository Repository for managing and fetching information related to blockchain networks.
*/
class GetCurrencyStatusUpdatesUseCase(
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
private val currencyStatusOperations: BaseCurrencyStatusOperations,
private val dispatchers: CoroutineDispatcherProvider,
private val tokensFeatureToggles: TokensFeatureToggles,
) {
/**
@ -60,28 +49,12 @@ class GetCurrencyStatusUpdatesUseCase(
currencyId: CryptoCurrency.ID,
isSingleWalletWithTokens: Boolean,
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
val currencyFlow = if (tokensFeatureToggles.isBalancesCachingEnabled) {
CurrenciesStatusesCachedOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
).getCurrencyStatusFlow(userWalletId, currencyId)
} else {
val operations = CurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
userWalletId = userWalletId,
)
val currencyFlow = currencyStatusOperations.getCurrencyStatusFlow(
userWalletId = userWalletId,
currencyId = currencyId,
isSingleWalletWithTokens = isSingleWalletWithTokens,
)
if (isSingleWalletWithTokens) {
operations.getCurrencyStatusSingleWalletWithTokensFlow(currencyId)
} else {
operations.getCurrencyStatusFlow(currencyId)
}
}
return currencyFlow.map { maybeCurrency ->
maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
}

View file

@ -1,17 +1,14 @@
package com.tangem.domain.tokens
import com.tangem.domain.models.StatusSource
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.tokens.model.warnings.HederaWarnings
import com.tangem.domain.tokens.model.warnings.KaspaWarnings
import com.tangem.domain.tokens.operations.CurrenciesStatusesCachedOperations
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
@ -24,12 +21,10 @@ import java.math.BigDecimal
class GetCurrencyWarningsUseCase(
private val walletManagersFacade: WalletManagersFacade,
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
private val dispatchers: CoroutineDispatcherProvider,
private val currencyChecksRepository: CurrencyChecksRepository,
private val tokensFeatureToggles: TokensFeatureToggles,
private val currencyStatusOperations: BaseCurrencyStatusOperations,
) {
suspend operator fun invoke(
@ -75,43 +70,16 @@ class GetCurrencyWarningsUseCase(
derivationPath: Network.DerivationPath,
isSingleWalletWithTokens: Boolean,
): Flow<List<CryptoCurrencyWarning>> {
val cachedOperations by lazy {
CurrenciesStatusesCachedOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
)
}
val currencyFlow = currencyStatusOperations.getCurrencyStatusFlow(
userWalletId = userWalletId,
currencyId = currencyId,
isSingleWalletWithTokens = isSingleWalletWithTokens,
)
val lceOperations by lazy {
CurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
userWalletId = userWalletId,
)
}
val currencyFlow = if (tokensFeatureToggles.isBalancesCachingEnabled) {
cachedOperations.getCurrencyStatusFlow(userWalletId, currencyId)
val networkFlow = if (isSingleWalletWithTokens) {
currencyStatusOperations.getNetworkCoinForSingleWalletWithTokenFlow(userWalletId, networkId)
} else {
if (isSingleWalletWithTokens) {
lceOperations.getCurrencyStatusSingleWalletWithTokensFlow(currencyId)
} else {
lceOperations.getCurrencyStatusFlow(currencyId)
}
}
val networkFlow = if (tokensFeatureToggles.isBalancesCachingEnabled) {
cachedOperations.getNetworkCoinFlow(userWalletId, networkId, derivationPath)
} else {
if (isSingleWalletWithTokens) {
lceOperations.getNetworkCoinForSingleWalletWithTokenFlow(networkId)
} else {
lceOperations.getNetworkCoinFlow(networkId, derivationPath)
}
currencyStatusOperations.getNetworkCoinFlow(userWalletId, networkId, derivationPath)
}
return combine(

View file

@ -2,23 +2,16 @@ package com.tangem.domain.tokens
import arrow.core.Either
import arrow.core.raise.either
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.FeePaidCurrency
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
class GetFeePaidCryptoCurrencyStatusSyncUseCase(
internal val currenciesRepository: CurrenciesRepository,
internal val quotesRepository: QuotesRepository,
internal val networksRepository: NetworksRepository,
internal val stakingRepository: StakingRepository,
internal val dispatchers: CoroutineDispatcherProvider,
private val currencyStatusOperations: BaseCurrencyStatusOperations,
) {
suspend operator fun invoke(
@ -28,24 +21,20 @@ class GetFeePaidCryptoCurrencyStatusSyncUseCase(
val cryptoCurrency = cryptoCurrencyStatus.currency
val network = cryptoCurrency.network
val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, network)
val operations = CurrenciesStatusesOperations(
userWalletId = userWalletId,
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
)
return either {
when (feePaidCurrency) {
FeePaidCurrency.Coin ->
operations
.getNetworkCoinSync(network.id, network.derivationPath)
is FeePaidCurrency.Coin -> {
currencyStatusOperations.getNetworkCoinSync(userWalletId, network.id, network.derivationPath)
.getOrNull()
FeePaidCurrency.SameCurrency,
}
is FeePaidCurrency.Token -> {
currencyStatusOperations.getCurrencyStatusSync(userWalletId, feePaidCurrency.tokenId)
.getOrNull()
}
is FeePaidCurrency.SameCurrency,
is FeePaidCurrency.FeeResource,
-> cryptoCurrencyStatus
is FeePaidCurrency.Token -> operations.getCurrencyStatusSync(feePaidCurrency.tokenId).getOrNull()
}
}
}

View file

@ -1,24 +1,18 @@
package com.tangem.domain.tokens
import arrow.core.Either
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
class GetNetworkCoinStatusUseCase(
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
private val currencyStatusOperations: BaseCurrencyStatusOperations,
private val dispatchers: CoroutineDispatcherProvider,
) {
@ -47,17 +41,10 @@ class GetNetworkCoinStatusUseCase(
derivationPath: Network.DerivationPath,
isSingleWalletWithTokens: Boolean,
): Either<CurrencyStatusError, CryptoCurrencyStatus> {
val operations = CurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
userWalletId = userWalletId,
)
val maybeCurrency = if (isSingleWalletWithTokens) {
operations.getNetworkCoinForSingleWalletWithTokenSync(networkId)
currencyStatusOperations.getNetworkCoinForSingleWalletWithTokenSync(userWalletId, networkId)
} else {
operations.getNetworkCoinSync(networkId, derivationPath)
currencyStatusOperations.getNetworkCoinSync(userWalletId, networkId, derivationPath)
}
return maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
}
@ -68,17 +55,10 @@ class GetNetworkCoinStatusUseCase(
derivationPath: Network.DerivationPath,
isSingleWalletWithTokens: Boolean,
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
val operations = CurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
userWalletId = userWalletId,
)
val networkFlow = if (isSingleWalletWithTokens) {
operations.getNetworkCoinForSingleWalletWithTokenFlow(networkId)
currencyStatusOperations.getNetworkCoinForSingleWalletWithTokenFlow(userWalletId, networkId)
} else {
operations.getNetworkCoinFlow(networkId, derivationPath)
currencyStatusOperations.getNetworkCoinFlow(userWalletId, networkId, derivationPath)
}
return networkFlow.map { maybeCurrency ->
maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)

View file

@ -1,14 +1,11 @@
package com.tangem.domain.tokens
import arrow.core.Either
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
@ -16,17 +13,11 @@ import kotlinx.coroutines.flow.*
/**
* Use case for fetching the status of the primary cryptocurrency associated with a user wallet.
*
* @property currenciesRepository Repository for managing and fetching cryptocurrencies.
* @property quotesRepository Repository for managing and fetching cryptocurrency quotes.
* @property networksRepository Repository for managing and fetching information related to blockchain networks.
* @property dispatchers Provides coroutine dispatchers.
*/
class GetPrimaryCurrencyStatusUpdatesUseCase(
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
private val dispatchers: CoroutineDispatcherProvider,
private val currencyStatusOperations: BaseCurrencyStatusOperations,
) {
/**
@ -44,15 +35,7 @@ class GetPrimaryCurrencyStatusUpdatesUseCase(
private suspend fun getPrimaryCurrency(
userWalletId: UserWalletId,
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
val operations = CurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
userWalletId = userWalletId,
)
return operations.getPrimaryCurrencyStatusFlow().map { maybeCurrency ->
return currencyStatusOperations.getPrimaryCurrencyStatusFlow(userWalletId).map { maybeCurrency ->
maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
}
}

View file

@ -4,17 +4,13 @@ import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.utils.lceError
import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.core.utils.toLce
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.operations.CurrenciesStatusesCachedOperations
import com.tangem.domain.tokens.operations.CurrenciesStatusesLceOperations
import com.tangem.domain.tokens.operations.BaseCurrenciesStatusesOperations
import com.tangem.domain.tokens.operations.TokenListOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emitAll
@ -23,45 +19,27 @@ import kotlinx.coroutines.flow.transformLatest
class GetTokenListUseCase(
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
private val tokensFeatureToggles: TokensFeatureToggles,
private val currenciesStatusesOperations: BaseCurrenciesStatusesOperations,
) {
@OptIn(ExperimentalCoroutinesApi::class)
fun launch(userWalletId: UserWalletId): LceFlow<TokenListError, TokenList> {
val statusesFlow = if (tokensFeatureToggles.isBalancesCachingEnabled) {
CurrenciesStatusesCachedOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
).getCurrenciesStatuses(userWalletId)
} else {
CurrenciesStatusesLceOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
).getCurrenciesStatuses(userWalletId)
}
return statusesFlow.transformLatest { maybeCurrencies ->
maybeCurrencies.fold(
ifLoading = { maybeContent ->
if (maybeContent != null) {
emitAll(createTokenListLce(userWalletId, maybeContent, isCurrenciesLoading = true))
} else {
emit(lceLoading())
}
},
ifContent = { content ->
emitAll(createTokenListLce(userWalletId, content, isCurrenciesLoading = false))
},
ifError = { error -> emit(error.lceError()) },
)
}
return currenciesStatusesOperations.getCurrenciesStatuses(userWalletId)
.transformLatest { maybeCurrencies ->
maybeCurrencies.fold(
ifLoading = { maybeContent ->
if (maybeContent != null) {
emitAll(createTokenListLce(userWalletId, maybeContent, isCurrenciesLoading = true))
} else {
emit(lceLoading())
}
},
ifContent = { content ->
emitAll(createTokenListLce(userWalletId, content, isCurrenciesLoading = false))
},
ifError = { error -> emit(error.lceError()) },
)
}
}
private fun createTokenListLce(

View file

@ -7,16 +7,11 @@ import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.lce.lce
import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TotalFiatBalance
import com.tangem.domain.tokens.operations.CurrenciesStatusesCachedOperations
import com.tangem.domain.tokens.operations.CurrenciesStatusesLceOperations
import com.tangem.domain.tokens.operations.BaseCurrenciesStatusesOperations
import com.tangem.domain.tokens.operations.TokenListFiatBalanceOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.combine
@ -25,11 +20,7 @@ import kotlinx.coroutines.flow.transformLatest
import timber.log.Timber
class GetWalletTotalBalanceUseCase(
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
private val tokensFeatureToggles: TokensFeatureToggles,
private val currenciesStatusesOperations: BaseCurrenciesStatusesOperations,
) {
operator fun invoke(
@ -65,13 +56,12 @@ class GetWalletTotalBalanceUseCase(
@OptIn(ExperimentalCoroutinesApi::class)
operator fun invoke(userWalletId: UserWalletId): LceFlow<TokenListError, TotalFiatBalance> {
val currenciesStatuses = getStatuses(userWalletId)
return currenciesStatusesOperations.getCurrenciesStatuses(userWalletId)
.transformLatest { maybeStatuses ->
val balance = createBalance(maybeStatuses)
return currenciesStatuses.transformLatest { maybeStatuses ->
val balance = createBalance(maybeStatuses)
emit(balance)
}
emit(balance)
}
}
private fun createBalance(
@ -86,22 +76,4 @@ class GetWalletTotalBalanceUseCase(
operations.calculateFiatBalance()
}
private fun getStatuses(userWalletId: UserWalletId): LceFlow<TokenListError, List<CryptoCurrencyStatus>> {
return if (tokensFeatureToggles.isBalancesCachingEnabled) {
CurrenciesStatusesCachedOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
).getCurrenciesStatuses(userWalletId)
} else {
CurrenciesStatusesLceOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
).getCurrenciesStatuses(userWalletId)
}
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.domain.tokens.operations
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWalletId
/**
* Base operations for working with currencies statuses
*
[REDACTED_AUTHOR]
*/
interface BaseCurrenciesStatusesOperations {
/** Get [LceFlow] of currencies statuses by [userWalletId] */
fun getCurrenciesStatuses(userWalletId: UserWalletId): LceFlow<TokenListError, List<CryptoCurrencyStatus>>
}

View file

@ -0,0 +1,366 @@
package com.tangem.domain.tokens.operations
import arrow.core.*
import arrow.core.raise.Raise
import arrow.core.raise.catch
import arrow.core.raise.either
import arrow.core.raise.recover
import com.tangem.domain.core.utils.EitherFlow
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations.Error
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.utils.CurrencyStatusProxyCreator
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.*
/**
* Base operations for working with currency status
*
* @property currenciesRepository repository for currencies
* @property quotesRepository repository for quotes
* @property networksRepository repository for networks
* @property stakingRepository repository for staking
*
[REDACTED_AUTHOR]
*/
abstract class BaseCurrencyStatusOperations(
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
) {
protected val currencyStatusProxyCreator = CurrencyStatusProxyCreator(stakingRepository)
protected abstract fun getQuotes(id: CryptoCurrency.RawID): Flow<Either<Error, Set<Quote>>>
protected abstract fun getNetworksStatuses(
userWalletId: UserWalletId,
network: Network,
): EitherFlow<Error, Set<NetworkStatus>>
suspend fun getCurrencyStatusFlow(
userWalletId: UserWalletId,
currencyId: CryptoCurrency.ID,
isSingleWalletWithTokens: Boolean,
): Flow<Either<Error, CryptoCurrencyStatus>> {
val currency = recover(
block = {
if (isSingleWalletWithTokens) {
getSingleCurrencyWalletWithCardTokensCurrency(userWalletId, currencyId)
} else {
getMultiCurrencyWalletCurrency(userWalletId, currencyId)
}
},
recover = { return flowOf(it.left()) },
)
return getCurrencyStatusFlow(userWalletId = userWalletId, currency = currency)
}
fun getCurrencyStatusFlow(
userWalletId: UserWalletId,
currency: CryptoCurrency,
includeQuotes: Boolean = true,
): Flow<Either<Error, CryptoCurrencyStatus>> {
val rawCurrencyId = currency.id.rawCurrencyId
val quoteFlow = if (includeQuotes && rawCurrencyId != null) {
getQuotes(rawCurrencyId)
.map { maybeQuotes ->
maybeQuotes.flatMap { quotes ->
quotes.singleOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }?.right()
?: Error.EmptyQuotes.left()
}
}
} else {
// don't use emptyFlow()
flow { emit(Error.EmptyQuotes.left()) }
}
val statusFlow = getNetworksStatuses(userWalletId = userWalletId, network = currency.network)
.map { maybeStatuses ->
maybeStatuses.flatMap { statuses ->
statuses.singleOrNull { it.network == currency.network }?.right()
?: Error.EmptyNetworksStatuses.left()
}
}
val yieldBalanceFlow = getYieldBalance(userWalletId = userWalletId, cryptoCurrency = currency)
return combine(quoteFlow, statusFlow, yieldBalanceFlow) { maybeQuote, maybeNetworkStatus, maybeYieldBalance ->
currencyStatusProxyCreator.createCurrencyStatus(
currency = currency,
maybeQuote = maybeQuote,
maybeNetworkStatus = maybeNetworkStatus,
maybeYieldBalance = maybeYieldBalance,
)
}
}
suspend fun getNetworkCoinFlow(
userWalletId: UserWalletId,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
includeQuotes: Boolean = true,
): Flow<Either<Error, CryptoCurrencyStatus>> {
val currency = recover(
block = { getNetworkCoin(userWalletId, networkId, derivationPath) },
recover = { return flowOf(it.left()) },
)
return getCurrencyStatusFlow(userWalletId, currency, includeQuotes)
}
suspend fun getNetworkCoinForSingleWalletWithTokenFlow(
userWalletId: UserWalletId,
networkId: Network.ID,
): Flow<Either<Error, CryptoCurrencyStatus>> {
val currency = recover(
block = { getNetworkCoinForSingleWalletWithToken(userWalletId, networkId) },
recover = { return flowOf(it.left()) },
)
return getCurrencyStatusFlow(userWalletId, currency)
}
suspend fun getNetworkCoinSync(
userWalletId: UserWalletId,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
): Either<Error, CryptoCurrencyStatus> {
val currency = recover(
block = { getNetworkCoin(userWalletId, networkId, derivationPath) },
recover = { return it.left() },
)
return getCurrencyStatusSync(userWalletId, currency.id)
}
suspend fun getCurrencyStatusSync(
userWalletId: UserWalletId,
cryptoCurrencyId: CryptoCurrency.ID,
isSingleWalletWithTokens: Boolean = false,
): Either<Error, CryptoCurrencyStatus> {
return either {
catch(
block = {
val currency = if (isSingleWalletWithTokens) {
currenciesRepository.getSingleCurrencyWalletWithCardCurrency(userWalletId, cryptoCurrencyId)
} else {
currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, cryptoCurrencyId)
}
val quote = cryptoCurrencyId.rawCurrencyId?.let { quotesRepository.getQuoteSync(it) }?.right()
?: Error.EmptyQuotes.left()
val networkStatuses =
networksRepository.getNetworkStatusesSync(
userWalletId = userWalletId,
networks = setOf(currency.network),
refresh = false,
).firstOrNull { it.network == currency.network }.right()
val yieldBalances = getYieldBalanceSync(userWalletId, currency)
return currencyStatusProxyCreator.createCurrencyStatus(
currency = currency,
maybeQuote = quote,
maybeNetworkStatus = networkStatuses,
maybeYieldBalance = yieldBalances,
)
},
catch = { raise(Error.DataError(it)) },
)
}
}
suspend fun getNetworkCoinForSingleWalletWithTokenSync(
userWalletId: UserWalletId,
networkId: Network.ID,
): Either<Error, CryptoCurrencyStatus> = either {
val currency = getNetworkCoinForSingleWalletWithToken(userWalletId, networkId)
return getCurrencyStatusSync(userWalletId, currency.id)
}
suspend fun getPrimaryCurrencyStatusFlow(
userWalletId: UserWalletId,
includeQuotes: Boolean = true,
): Flow<Either<Error, CryptoCurrencyStatus>> {
val currency = recover(
block = { getPrimaryCurrency(userWalletId) },
recover = { return flowOf(it.left()) },
)
return getCurrencyStatusFlow(userWalletId, currency, includeQuotes)
}
suspend fun getCurrenciesStatusesSync(userWalletId: UserWalletId): Either<Error, List<CryptoCurrencyStatus>> {
return either {
catch(
block = {
val nonEmptyCurrencies =
currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId).toNonEmptyListOrNull()
?: return emptyList<CryptoCurrencyStatus>().right()
val (networks, currenciesIds) = getIds(nonEmptyCurrencies)
val rawIds = currenciesIds.mapNotNull { it.rawCurrencyId }.toSet()
val quotes = quotesRepository.getQuotesSync(rawIds, false).right()
val networkStatuses =
networksRepository.getNetworkStatusesSync(userWalletId, networks, false).right()
val yieldBalances = getYieldBalancesSync(userWalletId, nonEmptyCurrencies)
return currencyStatusProxyCreator.createCurrenciesStatuses(
currencies = nonEmptyCurrencies,
maybeQuotes = quotes,
maybeNetworkStatuses = networkStatuses,
maybeYieldBalances = yieldBalances,
)
},
catch = { raise(Error.DataError(it)) },
)
}
}
suspend fun getPrimaryCurrencyStatusSync(userWalletId: UserWalletId): Either<Error, CryptoCurrencyStatus> = either {
val currency = catch(
block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) },
catch = { raise(Error.DataError(it)) },
)
val quotes = catch(
block = {
currency.id.rawCurrencyId?.let { quotesRepository.getQuoteSync(it) }
?.right() ?: Error.EmptyQuotes.left()
},
catch = { Error.DataError(it).left() },
)
val networkStatus = catch(
block = {
networksRepository.getNetworkStatusesSync(userWalletId, setOf(currency.network))
.firstOrNull { it.network == currency.network }
.right()
},
catch = { Error.DataError(it).left() },
)
val yieldBalances = getYieldBalanceSync(userWalletId, currency)
return currencyStatusProxyCreator.createCurrencyStatus(
currency = currency,
maybeQuote = quotes,
maybeNetworkStatus = networkStatus,
maybeYieldBalance = yieldBalances,
)
}
private suspend fun Raise<Error>.getMultiCurrencyWalletCurrency(
userWalletId: UserWalletId,
currencyId: CryptoCurrency.ID,
): CryptoCurrency {
return Either.catch {
currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId = userWalletId, id = currencyId)
}
.mapLeft(Error::DataError)
.bind()
}
private suspend fun Raise<Error>.getSingleCurrencyWalletWithCardTokensCurrency(
userWalletId: UserWalletId,
currencyId: CryptoCurrency.ID,
): CryptoCurrency {
return Either.catch { currenciesRepository.getSingleCurrencyWalletWithCardCurrency(userWalletId, currencyId) }
.mapLeft { Error.DataError(it) }
.bind()
}
private fun getYieldBalance(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): EitherFlow<Error, YieldBalance> {
return stakingRepository.getSingleYieldBalanceFlow(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
).map<YieldBalance, Either<Error, YieldBalance>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(Error.EmptyYieldBalances.left()) }
}
private suspend fun Raise<Error>.getNetworkCoin(
userWalletId: UserWalletId,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
): CryptoCurrency {
return Either.catch { currenciesRepository.getNetworkCoin(userWalletId, networkId, derivationPath) }
.mapLeft { Error.DataError(it) }
.bind()
}
private suspend fun Raise<Error>.getNetworkCoinForSingleWalletWithToken(
userWalletId: UserWalletId,
networkId: Network.ID,
): CryptoCurrency {
return Either.catch {
currenciesRepository.getSingleCurrencyWalletWithCardCurrencies(userWalletId)
.find { it.network.id == networkId && it is CryptoCurrency.Coin }
?: raise(Error.DataError(IllegalStateException("Coin with network $networkId not found for this card")))
}
.mapLeft { Error.DataError(it) }
.bind()
}
private suspend fun getYieldBalancesSync(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): Either<Error.EmptyYieldBalances, YieldBalanceList> {
return catch(
block = {
stakingRepository.getMultiYieldBalanceSync(
userWalletId,
cryptoCurrencies,
).right()
},
catch = {
Error.EmptyYieldBalances.left()
},
)
}
private suspend fun getYieldBalanceSync(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): Either<Error.EmptyYieldBalances, YieldBalance> {
return catch(
block = {
stakingRepository.getSingleYieldBalanceSync(
userWalletId,
cryptoCurrency,
).right()
},
catch = {
Error.EmptyYieldBalances.left()
},
)
}
private suspend fun Raise<Error>.getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency {
return catch(
block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) },
catch = { raise(Error.DataError(it)) },
)
}
protected fun getIds(currencies: List<CryptoCurrency>): Pair<NonEmptySet<Network>, NonEmptySet<CryptoCurrency.ID>> {
val currencyIdToNetworkId = currencies.associate { currency ->
currency.id to currency.network
}
val currenciesIds = currencyIdToNetworkId.keys.toNonEmptySetOrNull()
val networks = currencyIdToNetworkId.values.toNonEmptySetOrNull()
requireNotNull(currenciesIds) { "Currencies IDs cannot be empty" }
requireNotNull(networks) { "Networks IDs cannot be empty" }
return networks to currenciesIds
}
}

View file

@ -16,108 +16,28 @@ import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations.Error
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.utils.extractAddress
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
@Suppress("LargeClass")
internal class CurrenciesStatusesCachedOperations(
class CachedCurrenciesStatusesOperations(
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
) {
) : BaseCurrenciesStatusesOperations,
BaseCurrencyStatusOperations(currenciesRepository, quotesRepository, networksRepository, stakingRepository) {
fun getCurrenciesStatuses(userWalletId: UserWalletId): LceFlow<TokenListError, List<CryptoCurrencyStatus>> {
override fun getCurrenciesStatuses(
userWalletId: UserWalletId,
): LceFlow<TokenListError, List<CryptoCurrencyStatus>> {
return transformToCurrenciesStatuses(
userWalletId = userWalletId,
currenciesFlow = getCurrencies(userWalletId),
)
}
suspend fun getCurrencyStatusFlow(
userWalletId: UserWalletId,
currencyId: CryptoCurrency.ID,
includeQuotes: Boolean = true,
): Flow<Either<Error, CryptoCurrencyStatus>> {
val currency = recover(
block = { getMultiCurrencyWalletCurrency(userWalletId = userWalletId, currencyId = currencyId) },
recover = { return flowOf(it.left()) },
)
return getCurrencyStatusFlow(userWalletId = userWalletId, currency = currency, includeQuotes = includeQuotes)
}
suspend fun getNetworkCoinFlow(
userWalletId: UserWalletId,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
includeQuotes: Boolean = true,
): Flow<Either<Error, CryptoCurrencyStatus>> {
val operations = CurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
userWalletId = userWalletId,
)
val currency = recover(
block = {
with(operations) { getNetworkCoin(networkId, derivationPath) }
},
recover = { return flowOf(it.left()) },
)
return getCurrencyStatusFlow(userWalletId, currency, includeQuotes)
}
private fun getCurrencyStatusFlow(
userWalletId: UserWalletId,
currency: CryptoCurrency,
includeQuotes: Boolean = true,
): Flow<Either<Error, CryptoCurrencyStatus>> {
val rawCurrencyId = currency.id.rawCurrencyId
val quoteFlow = if (includeQuotes && rawCurrencyId != null) {
getQuotes(rawCurrencyId)
.map { maybeQuotes ->
maybeQuotes.flatMap { quotes ->
quotes.singleOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }?.right()
?: Error.EmptyQuotes.left()
}
}
} else {
// don't use emptyFlow()
flow { emit(Error.EmptyQuotes.left()) }
}
val statusFlow = getNetworksStatuses(userWalletId = userWalletId, network = currency.network)
.map { maybeStatuses ->
maybeStatuses.flatMap { statuses ->
statuses.singleOrNull { it.network == currency.network }?.right()
?: Error.EmptyNetworksStatuses.left()
}
}
val yieldBalanceFlow = getYieldBalance(userWalletId = userWalletId, cryptoCurrency = currency)
return combine(quoteFlow, statusFlow, yieldBalanceFlow) { maybeQuote, maybeNetworkStatus, maybeYieldBalance ->
createCurrencyStatus(currency, maybeQuote, maybeNetworkStatus, maybeYieldBalance)
}
}
private suspend fun Raise<Error>.getMultiCurrencyWalletCurrency(
userWalletId: UserWalletId,
currencyId: CryptoCurrency.ID,
): CryptoCurrency {
return Either.catch {
currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId = userWalletId, id = currencyId)
}
.mapLeft { Error.DataError(it) }
.bind()
}
private fun transformToCurrenciesStatuses(
userWalletId: UserWalletId,
currenciesFlow: EitherFlow<TokenListError, List<CryptoCurrency>>,
@ -213,7 +133,7 @@ internal class CurrenciesStatusesCachedOperations(
val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network }
val yieldBalance = findYieldBalanceOrNull(yieldBalances, currency, networkStatus)
val currencyStatus = createCurrencyStatus(
val currencyStatus = currencyStatusProxyCreator.createCurrencyStatus(
currency = currency,
quote = quote,
networkStatus = networkStatus,
@ -225,30 +145,6 @@ internal class CurrenciesStatusesCachedOperations(
}
}
private fun createCurrencyStatus(
currency: CryptoCurrency,
maybeQuote: Either<Error, Quote?>,
maybeNetworkStatus: Either<Error, NetworkStatus?>,
maybeYieldBalance: Either<Error, YieldBalance>?,
): Either<Error, CryptoCurrencyStatus> = either {
var quoteRetrievingFailed = false
val networkStatus = maybeNetworkStatus.bind()
val quote = recover({ maybeQuote.bind() }) {
quoteRetrievingFailed = true
null
}
val yieldBalance = maybeYieldBalance?.getOrNull()
createCurrencyStatus(
currency = currency,
quote = quote,
networkStatus = networkStatus,
ignoreQuote = quoteRetrievingFailed,
yieldBalance = yieldBalance,
)
}
private fun findYieldBalanceOrNull(
yieldBalances: YieldBalanceList?,
currency: CryptoCurrency,
@ -266,24 +162,6 @@ internal class CurrenciesStatusesCachedOperations(
)
}
private fun createCurrencyStatus(
currency: CryptoCurrency,
quote: Quote?,
networkStatus: NetworkStatus?,
yieldBalance: YieldBalance?,
ignoreQuote: Boolean,
): CryptoCurrencyStatus {
val currencyStatusOperations = CurrencyStatusOperations(
currency = currency,
quote = quote,
networkStatus = networkStatus,
yieldBalance = yieldBalance,
ignoreQuote = ignoreQuote,
)
return currencyStatusOperations.createTokenStatus()
}
private fun getCurrencies(userWalletId: UserWalletId): EitherFlow<TokenListError, List<CryptoCurrency>> {
return currenciesRepository.getWalletCurrenciesUpdates(userWalletId)
.map<List<CryptoCurrency>, Either<TokenListError, List<CryptoCurrency>>> { it.right() }
@ -303,7 +181,7 @@ internal class CurrenciesStatusesCachedOperations(
.distinctUntilChanged()
}
private fun getQuotes(id: CryptoCurrency.RawID): Flow<Either<Error, Set<Quote>>> {
override fun getQuotes(id: CryptoCurrency.RawID): Flow<Either<Error, Set<Quote>>> {
return quotesRepository.getQuotesUpdates(setOf(id))
.map<Set<Quote>, Either<Error, Set<Quote>>> { it.right() }
.retryWhen { cause, _ ->
@ -315,6 +193,21 @@ internal class CurrenciesStatusesCachedOperations(
.distinctUntilChanged()
}
override fun getNetworksStatuses(
userWalletId: UserWalletId,
network: Network,
): EitherFlow<Error, Set<NetworkStatus>> {
return networksRepository.getNetworkStatusesUpdates(userWalletId, setOf(network))
.map<Set<NetworkStatus>, Either<Error, Set<NetworkStatus>>> { it.right() }
.retryWhen { cause, _ ->
emit(Error.DataError(cause).left())
// adding delay before retry to avoid spam when flow restarted
delay(RETRY_DELAY)
true
}
.distinctUntilChanged()
}
private fun getNetworksStatuses(
userWalletId: UserWalletId,
networks: NonEmptySet<Network>,
@ -330,21 +223,6 @@ internal class CurrenciesStatusesCachedOperations(
.distinctUntilChanged()
}
private fun getNetworksStatuses(
userWalletId: UserWalletId,
network: Network,
): EitherFlow<Error, Set<NetworkStatus>> {
return networksRepository.getNetworkStatusesUpdates(userWalletId, setOf(network))
.map<Set<NetworkStatus>, Either<Error, Set<NetworkStatus>>> { it.right() }
.retryWhen { cause, _ ->
emit(Error.DataError(cause).left())
// adding delay before retry to avoid spam when flow restarted
delay(RETRY_DELAY)
true
}
.distinctUntilChanged()
}
private fun getYieldBalances(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
@ -360,40 +238,6 @@ internal class CurrenciesStatusesCachedOperations(
.distinctUntilChanged()
}
private fun getYieldBalance(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): EitherFlow<Error, YieldBalance> {
return stakingRepository.getSingleYieldBalanceFlow(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
).map<YieldBalance, Either<Error, YieldBalance>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(Error.EmptyYieldBalances.left()) }
}
private fun getIds(currencies: List<CryptoCurrency>): Pair<NonEmptySet<Network>, NonEmptySet<CryptoCurrency.ID>> {
val currencyIdToNetworkId = currencies.associate { currency ->
currency.id to currency.network
}
val currenciesIds = currencyIdToNetworkId.keys.toNonEmptySetOrNull()
val networks = currencyIdToNetworkId.values.toNonEmptySetOrNull()
requireNotNull(currenciesIds) { "Currencies IDs cannot be empty" }
requireNotNull(networks) { "Networks IDs cannot be empty" }
return networks to currenciesIds
}
private fun extractAddress(networkStatus: NetworkStatus?): String? {
return when (val value = networkStatus?.value) {
is NetworkStatus.NoAccount -> value.address.defaultAddress.value
is NetworkStatus.Unreachable -> value.address?.defaultAddress?.value
is NetworkStatus.Verified -> value.address.defaultAddress.value
else -> null
}
}
companion object {
private const val RETRY_DELAY = 2000L
}

View file

@ -1,355 +1,29 @@
package com.tangem.domain.tokens.operations
import arrow.core.*
import arrow.core.raise.*
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.domain.core.utils.EitherFlow
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.*
// FIXME: Refactor - [REDACTED_JIRA]
@Suppress("LargeClass")
internal class CurrenciesStatusesOperations(
private val currenciesRepository: CurrenciesRepository,
class CurrenciesStatusesOperations(
currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
private val userWalletId: UserWalletId,
) {
stakingRepository: StakingRepository,
) : BaseCurrencyStatusOperations(currenciesRepository, quotesRepository, networksRepository, stakingRepository) {
suspend fun getCurrenciesStatusesSync(): Either<Error, List<CryptoCurrencyStatus>> {
return either {
catch(
block = {
val nonEmptyCurrencies =
currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId).toNonEmptyListOrNull()
?: return emptyList<CryptoCurrencyStatus>().right()
val (networks, currenciesIds) = getIds(nonEmptyCurrencies)
val rawIds = currenciesIds.mapNotNull { it.rawCurrencyId }.toSet()
val quotes = quotesRepository.getQuotesSync(rawIds, false).right()
val networkStatuses =
networksRepository.getNetworkStatusesSync(userWalletId, networks, false).right()
val yieldBalances = getYieldBalancesSync(nonEmptyCurrencies)
return createCurrenciesStatuses(
nonEmptyCurrencies,
quotes,
networkStatuses,
yieldBalances,
)
},
catch = { raise(Error.DataError(it)) },
)
}
}
suspend fun getCurrencyStatusSync(
cryptoCurrencyId: CryptoCurrency.ID,
isSingleWalletWithTokens: Boolean = false,
): Either<Error, CryptoCurrencyStatus> {
return either {
catch(
block = {
val currency = if (isSingleWalletWithTokens) {
currenciesRepository.getSingleCurrencyWalletWithCardCurrency(userWalletId, cryptoCurrencyId)
} else {
currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, cryptoCurrencyId)
}
val quote = cryptoCurrencyId.rawCurrencyId?.let { quotesRepository.getQuoteSync(it) }?.right()
?: Error.EmptyQuotes.left()
val networkStatuses =
networksRepository.getNetworkStatusesSync(
userWalletId,
setOf(currency.network),
false,
).firstOrNull {
it.network == currency.network
}.right()
val yieldBalances = getYieldBalanceSync(currency)
return createCurrencyStatus(currency, quote, networkStatuses, yieldBalances)
},
catch = { raise(Error.DataError(it)) },
)
}
}
suspend fun getNetworkCoinSync(
networkId: Network.ID,
derivationPath: Network.DerivationPath,
): Either<Error, CryptoCurrencyStatus> {
val currency = recover(
block = { getNetworkCoin(networkId, derivationPath) },
recover = { return it.left() },
)
return getCurrencyStatusSync(currency.id)
}
suspend fun getNetworkCoinForSingleWalletWithTokenSync(
networkId: Network.ID,
): Either<Error, CryptoCurrencyStatus> = either {
val currency = getNetworkCoinForSingleWalletWithToken(networkId)
return getCurrencyStatusSync(currency.id)
}
suspend fun getPrimaryCurrencyStatusSync(): Either<Error, CryptoCurrencyStatus> = either {
val currency = catch(
block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) },
catch = { raise(Error.DataError(it)) },
)
val quotes = catch(
block = {
currency.id.rawCurrencyId?.let { quotesRepository.getQuoteSync(it) }
?.right() ?: Error.EmptyQuotes.left()
},
catch = { Error.DataError(it).left() },
)
val networkStatus = catch(
block = {
networksRepository.getNetworkStatusesSync(userWalletId, setOf(currency.network))
.firstOrNull { it.network == currency.network }
.right()
},
catch = { Error.DataError(it).left() },
)
val yieldBalances = getYieldBalanceSync(currency)
return createCurrencyStatus(currency, quotes, networkStatus, yieldBalances)
}
suspend fun getCurrencyStatusFlow(currencyId: CryptoCurrency.ID): Flow<Either<Error, CryptoCurrencyStatus>> {
val currency = recover(
block = { getMultiCurrencyWalletCurrency(currencyId) },
recover = { return flowOf(it.left()) },
)
return getCurrencyStatusFlow(currency)
}
suspend fun getCurrencyStatusSingleWalletWithTokensFlow(
currencyId: CryptoCurrency.ID,
): Flow<Either<Error, CryptoCurrencyStatus>> {
val currency = recover(
block = { getSingleCurrencyWalletWithCardTokensCurrency(currencyId) },
recover = { return flowOf(it.left()) },
)
return getCurrencyStatusFlow(currency)
}
suspend fun getNetworkCoinFlow(
networkId: Network.ID,
derivationPath: Network.DerivationPath,
includeQuotes: Boolean = true,
): Flow<Either<Error, CryptoCurrencyStatus>> {
val currency = recover(
block = { getNetworkCoin(networkId, derivationPath) },
recover = { return flowOf(it.left()) },
)
return getCurrencyStatusFlow(currency, includeQuotes)
}
suspend fun getNetworkCoinForSingleWalletWithTokenFlow(
networkId: Network.ID,
): Flow<Either<Error, CryptoCurrencyStatus>> {
val currency = recover(
block = { getNetworkCoinForSingleWalletWithToken(networkId) },
recover = { return flowOf(it.left()) },
)
return getCurrencyStatusFlow(currency)
}
suspend fun getPrimaryCurrencyStatusFlow(includeQuotes: Boolean = true): Flow<Either<Error, CryptoCurrencyStatus>> {
val currency = recover(
block = { getPrimaryCurrency() },
recover = { return flowOf(it.left()) },
)
return getCurrencyStatusFlow(currency, includeQuotes)
}
fun getCurrencyStatusFlow(
currency: CryptoCurrency,
includeQuotes: Boolean = true,
): Flow<Either<Error, CryptoCurrencyStatus>> {
val (networks, currenciesIds) = getIds(nonEmptyListOf(currency))
val quoteFlow = if (includeQuotes) {
getQuotes(currenciesIds)
.map { maybeQuotes ->
maybeQuotes.flatMap { quotes ->
quotes.singleOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }?.right()
?: Error.EmptyQuotes.left()
}
}
} else {
// don't use emptyFlow()
flow { emit(Error.EmptyQuotes.left()) }
}
val statusFlow = getNetworksStatuses(networks)
.map { maybeStatuses ->
maybeStatuses.flatMap { statuses ->
statuses.singleOrNull { it.network == currency.network }?.right()
?: Error.EmptyNetworksStatuses.left()
}
}
val yieldBalanceFlow = getYieldBalance(currency)
return combine(quoteFlow, statusFlow, yieldBalanceFlow) { maybeQuote, maybeNetworkStatus, maybeYieldBalance ->
createCurrencyStatus(currency, maybeQuote, maybeNetworkStatus, maybeYieldBalance)
}
}
private fun createCurrenciesStatuses(
currencies: NonEmptyList<CryptoCurrency>,
maybeQuotes: Either<Error, Set<Quote>>?,
maybeNetworkStatuses: Either<Error, Set<NetworkStatus>>?,
maybeYieldBalances: Either<Error, YieldBalanceList>?,
): Either<Error, List<CryptoCurrencyStatus>> = either {
var quotesRetrievingFailed = false
val networksStatuses = maybeNetworkStatuses?.bind()?.toNonEmptySetOrNull()
val quotes: Set<Quote>? = maybeQuotes?.fold(
ifLeft = {
quotesRetrievingFailed = true
null
},
ifRight = {
it.ifEmpty {
quotesRetrievingFailed = true
null
}
},
)
val yieldBalances = maybeYieldBalances?.getOrNull()
currencies.map { currency ->
val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network }
val address = extractAddress(networkStatus)
val supportedIntegration = stakingRepository.getSupportedIntegrationId(currency.id)
val yieldBalance = if (supportedIntegration.isNullOrEmpty().not()) {
(yieldBalances as? YieldBalanceList.Data)?.getBalance(
address = address,
integrationId = supportedIntegration,
)
} else {
null
}
createCurrencyStatus(
currency = currency,
quote = quote,
networkStatus = networkStatus,
ignoreQuote = quotesRetrievingFailed,
yieldBalance = yieldBalance,
)
}
}
private fun createCurrencyStatus(
currency: CryptoCurrency,
maybeQuote: Either<Error, Quote?>,
maybeNetworkStatus: Either<Error, NetworkStatus?>,
maybeYieldBalance: Either<Error, YieldBalance>?,
): Either<Error, CryptoCurrencyStatus> = either {
var quoteRetrievingFailed = false
val networkStatus = maybeNetworkStatus.bind()
val quote = recover({ maybeQuote.bind() }) {
quoteRetrievingFailed = true
null
}
val yieldBalance = maybeYieldBalance?.getOrNull()
createCurrencyStatus(
currency = currency,
quote = quote,
networkStatus = networkStatus,
ignoreQuote = quoteRetrievingFailed,
yieldBalance = yieldBalance,
)
}
private fun createCurrencyStatus(
currency: CryptoCurrency,
quote: Quote?,
networkStatus: NetworkStatus?,
ignoreQuote: Boolean,
yieldBalance: YieldBalance?,
): CryptoCurrencyStatus {
val currencyStatusOperations = CurrencyStatusOperations(
currency = currency,
quote = quote,
networkStatus = networkStatus,
ignoreQuote = ignoreQuote,
yieldBalance = yieldBalance,
)
return currencyStatusOperations.createTokenStatus()
}
private suspend fun Raise<Error>.getMultiCurrencyWalletCurrency(currencyId: CryptoCurrency.ID): CryptoCurrency {
return Either.catch {
currenciesRepository.getMultiCurrencyWalletCurrency(
userWalletId = userWalletId,
id = currencyId,
)
}
.mapLeft { Error.DataError(it) }
.bind()
}
private suspend fun Raise<Error>.getSingleCurrencyWalletWithCardTokensCurrency(
currencyId: CryptoCurrency.ID,
): CryptoCurrency {
return Either.catch { currenciesRepository.getSingleCurrencyWalletWithCardCurrency(userWalletId, currencyId) }
.mapLeft { Error.DataError(it) }
.bind()
}
suspend fun Raise<Error>.getNetworkCoin(
networkId: Network.ID,
derivationPath: Network.DerivationPath,
): CryptoCurrency {
return Either.catch { currenciesRepository.getNetworkCoin(userWalletId, networkId, derivationPath) }
.mapLeft { Error.DataError(it) }
.bind()
}
private suspend fun Raise<Error>.getNetworkCoinForSingleWalletWithToken(networkId: Network.ID): CryptoCurrency {
return Either.catch {
currenciesRepository.getSingleCurrencyWalletWithCardCurrencies(userWalletId)
.find { it.network.id == networkId && it is CryptoCurrency.Coin }
?: raise(Error.DataError(IllegalStateException("Coin with network $networkId not found for this card")))
}
.mapLeft { Error.DataError(it) }
.bind()
}
private suspend fun Raise<Error>.getPrimaryCurrency(): CryptoCurrency {
return catch(
block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) },
catch = { raise(Error.DataError(it)) },
)
}
private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Either<Error, Set<Quote>>> {
val rawIds = tokensIds.mapNotNull { it.rawCurrencyId }.toSet()
return quotesRepository.getQuotesUpdatesLegacy(rawIds)
override fun getQuotes(id: CryptoCurrency.RawID): Flow<Either<Error, Set<Quote>>> {
return quotesRepository.getQuotesUpdatesLegacy(setOf(id))
.map<Set<Quote>, Either<Error, Set<Quote>>> { quotes ->
if (quotes.isEmpty()) Error.EmptyQuotes.left() else quotes.right()
}
@ -359,78 +33,16 @@ internal class CurrenciesStatusesOperations(
}
}
private fun getNetworksStatuses(networks: NonEmptySet<Network>): Flow<Either<Error, Set<NetworkStatus>>> {
return networksRepository.getNetworkStatusesUpdatesLegacy(userWalletId, networks)
override fun getNetworksStatuses(
userWalletId: UserWalletId,
network: Network,
): EitherFlow<Error, Set<NetworkStatus>> {
return networksRepository.getNetworkStatusesUpdatesLegacy(userWalletId, setOf(network))
.map<Set<NetworkStatus>, Either<Error, Set<NetworkStatus>>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
}
private suspend fun getYieldBalancesSync(
cryptoCurrencies: List<CryptoCurrency>,
): Either<Error.EmptyYieldBalances, YieldBalanceList> {
return catch(
block = {
stakingRepository.getMultiYieldBalanceSync(
userWalletId,
cryptoCurrencies,
).right()
},
catch = {
Error.EmptyYieldBalances.left()
},
)
}
private suspend fun getYieldBalanceSync(
cryptoCurrency: CryptoCurrency,
): Either<Error.EmptyYieldBalances, YieldBalance> {
return catch(
block = {
stakingRepository.getSingleYieldBalanceSync(
userWalletId,
cryptoCurrency,
).right()
},
catch = {
Error.EmptyYieldBalances.left()
},
)
}
private fun getYieldBalance(cryptoCurrency: CryptoCurrency): EitherFlow<Error, YieldBalance> {
return stakingRepository.getSingleYieldBalanceFlow(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
).map<YieldBalance, Either<Error, YieldBalance>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(Error.EmptyYieldBalances.left()) }
}
private fun getIds(
currencies: NonEmptyList<CryptoCurrency>,
): Pair<NonEmptySet<Network>, NonEmptySet<CryptoCurrency.ID>> {
val currencyIdToNetworkId = currencies.associate { currency ->
currency.id to currency.network
}
val currenciesIds = currencyIdToNetworkId.keys.toNonEmptySetOrNull()
val networks = currencyIdToNetworkId.values.toNonEmptySetOrNull()
requireNotNull(currenciesIds) { "Currencies IDs cannot be empty" }
requireNotNull(networks) { "Networks IDs cannot be empty" }
return networks to currenciesIds
}
private fun extractAddress(networkStatus: NetworkStatus?): String? {
return when (val value = networkStatus?.value) {
is NetworkStatus.NoAccount -> value.address.defaultAddress.value
is NetworkStatus.Unreachable -> value.address?.defaultAddress?.value
is NetworkStatus.Verified -> value.address.defaultAddress.value
else -> null
}
}
sealed class Error {
data object EmptyCurrencies : Error()

View file

@ -20,14 +20,16 @@ import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
internal class CurrenciesStatusesLceOperations(
class LceCurrenciesStatusesOperations(
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val stakingRepository: StakingRepository,
) {
) : BaseCurrenciesStatusesOperations {
fun getCurrenciesStatuses(userWalletId: UserWalletId): LceFlow<TokenListError, List<CryptoCurrencyStatus>> {
override fun getCurrenciesStatuses(
userWalletId: UserWalletId,
): LceFlow<TokenListError, List<CryptoCurrencyStatus>> {
return transformToCurrenciesStatuses(
userWalletId = userWalletId,
currenciesFlow = getWalletCurrencies(userWalletId),
@ -217,7 +219,7 @@ internal class CurrenciesStatusesLceOperations(
}
}
companion object {
private const val RETRY_QUOTES_DELAY = 2000L
private companion object {
const val RETRY_QUOTES_DELAY = 2000L
}
}

View file

@ -20,11 +20,11 @@ internal class TokenListOperations(
fun getTokenListFlow(): Flow<Either<Error, TokenList>> {
return combine(
getIsGrouped(),
getIsSortedByBalance(),
flow = getIsGrouped(),
flow2 = getIsSortedByBalance(),
) { isGrouped, isSortedByBalance ->
either {
createTokenList(isGrouped.bind(), isSortedByBalance.bind())
createTokenList(isGrouped = isGrouped.bind(), isSortedByBalance = isSortedByBalance.bind())
}
}
}

View file

@ -0,0 +1,117 @@
package com.tangem.domain.tokens.utils
import arrow.core.Either
import arrow.core.NonEmptyList
import arrow.core.raise.either
import arrow.core.toNonEmptySetOrNull
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations.Error
import com.tangem.domain.tokens.operations.CurrencyStatusOperations
/**
* Proxy creator of [CryptoCurrencyStatus]. Used [CurrencyStatusOperations] to create statuses.
*
* @property stakingRepository staking repository
*
[REDACTED_AUTHOR]
*/
class CurrencyStatusProxyCreator(
private val stakingRepository: StakingRepository,
) {
fun createCurrencyStatus(
currency: CryptoCurrency,
maybeQuote: Either<Error, Quote?>,
maybeNetworkStatus: Either<Error, NetworkStatus?>,
maybeYieldBalance: Either<Error, YieldBalance>?,
): Either<Error, CryptoCurrencyStatus> = either {
var quoteRetrievingFailed = false
val networkStatus = maybeNetworkStatus.bind()
val quote = arrow.core.raise.recover({ maybeQuote.bind() }) {
quoteRetrievingFailed = true
null
}
val yieldBalance = maybeYieldBalance?.getOrNull()
createCurrencyStatus(
currency = currency,
quote = quote,
networkStatus = networkStatus,
ignoreQuote = quoteRetrievingFailed,
yieldBalance = yieldBalance,
)
}
fun createCurrenciesStatuses(
currencies: NonEmptyList<CryptoCurrency>,
maybeQuotes: Either<Error, Set<Quote>>?,
maybeNetworkStatuses: Either<Error, Set<NetworkStatus>>?,
maybeYieldBalances: Either<Error, YieldBalanceList>?,
): Either<Error, List<CryptoCurrencyStatus>> = either {
var quotesRetrievingFailed = false
val networksStatuses = maybeNetworkStatuses?.bind()?.toNonEmptySetOrNull()
val quotes: Set<Quote>? = maybeQuotes?.fold(
ifLeft = {
quotesRetrievingFailed = true
null
},
ifRight = {
it.ifEmpty {
quotesRetrievingFailed = true
null
}
},
)
val yieldBalances = maybeYieldBalances?.getOrNull()
currencies.map { currency ->
val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network }
val address = extractAddress(networkStatus)
val supportedIntegration = stakingRepository.getSupportedIntegrationId(currency.id)
val yieldBalance = if (supportedIntegration.isNullOrEmpty().not()) {
(yieldBalances as? YieldBalanceList.Data)?.getBalance(
address = address,
integrationId = supportedIntegration,
)
} else {
null
}
createCurrencyStatus(
currency = currency,
quote = quote,
networkStatus = networkStatus,
ignoreQuote = quotesRetrievingFailed,
yieldBalance = yieldBalance,
)
}
}
fun createCurrencyStatus(
currency: CryptoCurrency,
quote: Quote?,
networkStatus: NetworkStatus?,
ignoreQuote: Boolean,
yieldBalance: YieldBalance?,
): CryptoCurrencyStatus {
val currencyStatusOperations = CurrencyStatusOperations(
currency = currency,
quote = quote,
networkStatus = networkStatus,
ignoreQuote = ignoreQuote,
yieldBalance = yieldBalance,
)
return currencyStatusOperations.createTokenStatus()
}
}

View file

@ -0,0 +1,13 @@
package com.tangem.domain.tokens.utils
import com.tangem.domain.tokens.model.NetworkStatus
/** Extract address from [networkStatus] */
internal fun extractAddress(networkStatus: NetworkStatus?): String? {
return when (val value = networkStatus?.value) {
is NetworkStatus.NoAccount -> value.address.defaultAddress.value
is NetworkStatus.Unreachable -> value.address?.defaultAddress?.value
is NetworkStatus.Verified -> value.address.defaultAddress.value
else -> null
}
}

View file

@ -10,6 +10,7 @@ import com.tangem.domain.tokens.mock.MockQuotes
import com.tangem.domain.tokens.mock.MockTokens
import com.tangem.domain.tokens.mock.MockTokensStates
import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.MockCurrenciesRepository
import com.tangem.domain.tokens.repository.MockNetworksRepository
import com.tangem.domain.tokens.repository.MockQuotesRepository
@ -162,17 +163,19 @@ internal class GetPrimaryCurrencyStatusUpdatesUseCaseTest {
quotes: Flow<Either<DataError, Set<Quote>>> = flowOf(MockQuotes.quotes.right()),
statuses: Flow<Either<DataError, Set<NetworkStatus>>> = flowOf(MockNetworks.verifiedNetworksStatuses.right()),
) = GetPrimaryCurrencyStatusUpdatesUseCase(
dispatchers = dispatchers,
currenciesRepository = MockCurrenciesRepository(
sortTokensResult = Unit.right(),
removeCurrencyResult = removeCurrencyResult,
token = token,
tokens = flowOf(),
isGrouped = flowOf(),
isSortedByBalance = flowOf(),
currencyStatusOperations = CurrenciesStatusesOperations(
currenciesRepository = MockCurrenciesRepository(
sortTokensResult = Unit.right(),
removeCurrencyResult = removeCurrencyResult,
token = token,
tokens = flowOf(),
isGrouped = flowOf(),
isSortedByBalance = flowOf(),
),
quotesRepository = MockQuotesRepository(quotes),
networksRepository = MockNetworksRepository(statuses),
stakingRepository = MockStakingRepository(),
),
quotesRepository = MockQuotesRepository(quotes),
networksRepository = MockNetworksRepository(statuses),
stakingRepository = MockStakingRepository(),
dispatchers = dispatchers,
)
}

View file

@ -1,13 +1,9 @@
package com.tangem.feature.swap.domain.di
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.feature.swap.domain.*
import com.tangem.lib.crypto.TransactionManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -40,19 +36,9 @@ internal class SwapDomainModule {
@Provides
@Singleton
fun providesGetCryptoCurrencyStatusUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
dispatchers: CoroutineDispatcherProvider,
currencyStatusOperations: BaseCurrencyStatusOperations,
): GetCryptoCurrencyStatusesSyncUseCase {
return GetCryptoCurrencyStatusesSyncUseCase(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
stakingRepository = stakingRepository,
dispatchers = dispatchers,
)
return GetCryptoCurrencyStatusesSyncUseCase(currencyStatusOperations)
}
@Provides