Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-26 13:22:19 +04:00
parent 73832d3e8e
commit 867d9fd735
9 changed files with 66 additions and 79 deletions

View file

@ -18,6 +18,7 @@ import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.utils.Provider
@ -55,7 +56,7 @@ internal class DefaultRampManager(
}
override suspend fun availableForSell(
userWalletId: UserWalletId,
userWallet: UserWallet,
status: CryptoCurrencyStatus,
): Either<ScenarioUnavailabilityReason, Unit> {
return either {
@ -68,7 +69,7 @@ internal class DefaultRampManager(
catch = { raise(ScenarioUnavailabilityReason.NotSupportedBySellService(status.currency.name)) },
)
val reason = getSendUnavailabilityReason(userWalletId, status)
val reason = getSendUnavailabilityReason(userWallet = userWallet, cryptoCurrencyStatus = status)
ensure(condition = reason is ScenarioUnavailabilityReason.None) {
when (reason) {
@ -205,14 +206,13 @@ internal class DefaultRampManager(
}
private suspend fun getSendUnavailabilityReason(
userWalletId: UserWalletId,
userWallet: UserWallet,
cryptoCurrencyStatus: CryptoCurrencyStatus,
): ScenarioUnavailabilityReason {
val coinStatus = getNetworkCoinStatusUseCase.invokeSync(
userWalletId = userWalletId,
userWallet = userWallet,
networkId = cryptoCurrencyStatus.currency.network.id,
derivationPath = cryptoCurrencyStatus.currency.network.derivationPath,
isSingleWalletWithTokens = false,
).getOrNull()
return when {

View file

@ -14,6 +14,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
/**
* Common implementation of network status fetcher
@ -24,6 +25,7 @@ import javax.inject.Inject
*
[REDACTED_AUTHOR]
*/
@Singleton
internal class CommonNetworkStatusFetcher @Inject constructor(
private val walletManagersFacade: WalletManagersFacade,
private val networksStatusesStore: NetworksStatusesStore,

View file

@ -6,6 +6,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
@ -23,11 +24,11 @@ interface RampStateManager {
/**
* Check if [CryptoCurrency] is available for sell
*
* @param userWalletId id of multi-currency wallet
* @param status crypto currency status
* @param userWallet user wallet
* @param status crypto currency status
*/
suspend fun availableForSell(
userWalletId: UserWalletId,
userWallet: UserWallet,
status: CryptoCurrencyStatus,
): Either<ScenarioUnavailabilityReason, Unit>

View file

@ -4,6 +4,7 @@ import arrow.core.Either
import arrow.core.raise.Raise
import arrow.core.raise.catch
import arrow.core.raise.either
import arrow.core.right
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
@ -51,17 +52,18 @@ class FetchCurrencyStatusUseCase(
return either {
val currency = getCurrency(userWalletId, id)
coroutineScope {
return@either coroutineScope {
val fetchStatus = async {
fetchNetworkStatus(userWalletId, currency.network)
}
val fetchQuote = async {
fetchQuote(currency.id)
fetchNetworkStatus(userWalletId = userWalletId, network = currency.network)
}
val fetchQuote = async { fetchQuote(currencyId = currency.id) }
val fetchStakingBalance = async {
fetchStakingBalance(userWalletId, currency, refresh)
fetchStakingBalance(userWalletId = userWalletId, cryptoCurrency = currency, refresh = refresh)
}
awaitAll(fetchStatus, fetchQuote, fetchStakingBalance)
awaitAll(fetchStatus, fetchQuote, fetchStakingBalance).summarizeResult()
}
}
}
@ -80,14 +82,14 @@ class FetchCurrencyStatusUseCase(
return either {
val currency = getPrimaryCurrency(userWalletId, refresh)
coroutineScope {
return@either coroutineScope {
val fetchStatus = async {
fetchNetworkStatus(userWalletId, currency.network)
fetchNetworkStatus(userWalletId = userWalletId, network = currency.network)
}
val fetchQuote = async {
fetchQuote(currency.id)
}
awaitAll(fetchStatus, fetchQuote)
val fetchQuote = async { fetchQuote(currencyId = currency.id) }
awaitAll(fetchStatus, fetchQuote).summarizeResult()
}
}
}
@ -97,9 +99,7 @@ class FetchCurrencyStatusUseCase(
id: CryptoCurrency.ID,
): CryptoCurrency {
return catch(
block = {
currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, id)
},
block = { currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId = userWalletId, id = id) },
) {
raise(CurrencyStatusError.DataError(it))
}
@ -114,31 +114,27 @@ class FetchCurrencyStatusUseCase(
}
}
private suspend fun Raise<CurrencyStatusError>.fetchNetworkStatus(userWalletId: UserWalletId, network: Network) {
singleNetworkStatusFetcher(
private suspend fun fetchNetworkStatus(userWalletId: UserWalletId, network: Network): Either<Throwable, Unit> {
return singleNetworkStatusFetcher(
params = SingleNetworkStatusFetcher.Params(userWalletId = userWalletId, network = network),
)
.mapLeft { CurrencyStatusError.DataError(it) }
.bind()
}
private suspend fun Raise<CurrencyStatusError>.fetchQuote(currencyId: CryptoCurrency.ID) {
multiQuoteFetcher(
private suspend fun fetchQuote(currencyId: CryptoCurrency.ID): Either<Throwable, Unit> {
return multiQuoteFetcher(
params = MultiQuoteFetcher.Params(
currenciesIds = setOfNotNull(currencyId.rawCurrencyId),
appCurrencyId = null,
),
)
.mapLeft { CurrencyStatusError.DataError(it) }
.bind()
}
private suspend fun Raise<CurrencyStatusError>.fetchStakingBalance(
private suspend fun fetchStakingBalance(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
refresh: Boolean,
) {
if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) {
): Either<Throwable, Unit> {
return if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) {
singleYieldBalanceFetcher(
params = SingleYieldBalanceFetcher.Params(
userWalletId = userWalletId,
@ -147,11 +143,11 @@ class FetchCurrencyStatusUseCase(
),
)
} else {
catch(
block = { stakingRepository.fetchSingleYieldBalance(userWalletId, cryptoCurrency, refresh) },
) {
raise(CurrencyStatusError.DataError(it))
}
Either.catch { stakingRepository.fetchSingleYieldBalance(userWalletId, cryptoCurrency, refresh) }
}
}
private fun List<Either<Throwable, Unit>>.summarizeResult(): Either<Throwable, Unit> {
return firstOrNull { it.isLeft() } ?: Unit.right()
}
}

View file

@ -194,10 +194,7 @@ class GetCryptoCurrencyActionsUseCase(
}
// region sell
rampManager.availableForSell(
userWalletId = userWallet.walletId,
status = cryptoCurrencyStatus,
)
rampManager.availableForSell(userWallet = userWallet, status = cryptoCurrencyStatus)
.onRight {
activeList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.None))
}

View file

@ -1,13 +1,16 @@
package com.tangem.domain.tokens
import arrow.core.Either
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.network.Network
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.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
@ -36,16 +39,21 @@ class GetNetworkCoinStatusUseCase(
}
suspend fun invokeSync(
userWalletId: UserWalletId,
userWallet: UserWallet,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
isSingleWalletWithTokens: Boolean,
): Either<CurrencyStatusError, CryptoCurrencyStatus> {
val maybeCurrency = if (isSingleWalletWithTokens) {
val userWalletId = userWallet.walletId
val cardTypesResolver = userWallet.requireColdWallet().cardTypesResolver // TODO [REDACTED_TASK_KEY]
val maybeCurrency = if (userWallet.isMultiCurrency) {
currencyStatusOperations.getNetworkCoinSync(userWalletId, networkId, derivationPath)
} else if (cardTypesResolver.isSingleWalletWithToken()) {
currencyStatusOperations.getNetworkCoinForSingleWalletWithTokenSync(userWalletId, networkId)
} else {
currencyStatusOperations.getNetworkCoinSync(userWalletId, networkId, derivationPath)
currencyStatusOperations.getPrimaryCurrencyStatusSync(userWalletId)
}
return maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
}

View file

@ -54,11 +54,6 @@ abstract class BaseCurrencyStatusOperations(
protected abstract fun getQuotes(id: CryptoCurrency.RawID): Flow<Either<Error, Set<Quote>>>
protected abstract fun getNetworksStatuses(
userWalletId: UserWalletId,
network: Network,
): EitherFlow<Error, Set<NetworkStatus>>
protected abstract suspend fun fetchComponents(
userWalletId: UserWalletId,
networks: Set<Network>,
@ -106,13 +101,7 @@ abstract class BaseCurrencyStatusOperations(
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 statusFlow = getNetworkStatus(userWalletId = userWalletId, network = currency.network)
val yieldBalanceFlow = getYieldBalance(userWalletId = userWalletId, cryptoCurrency = currency)
@ -349,6 +338,15 @@ abstract class BaseCurrencyStatusOperations(
.onEmpty { emit(Error.EmptyYieldBalances.left()) }
}
private fun getNetworkStatus(userWalletId: UserWalletId, network: Network): EitherFlow<Error, NetworkStatus> {
return singleNetworkStatusSupplier(
params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network),
)
.map<NetworkStatus, Either<Error, NetworkStatus>>(NetworkStatus::right)
.distinctUntilChanged()
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
}
private suspend fun Raise<Error>.getNetworkCoin(
userWalletId: UserWalletId,
networkId: Network.ID,

View file

@ -303,18 +303,6 @@ class CachedCurrenciesStatusesOperations(
.distinctUntilChanged()
}
override fun getNetworksStatuses(
userWalletId: UserWalletId,
network: Network,
): EitherFlow<Error, Set<NetworkStatus>> {
return singleNetworkStatusSupplier(
params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network),
)
.map<NetworkStatus, Either<Error, Set<NetworkStatus>>> { setOf(it).right() }
.distinctUntilChanged()
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
}
private fun getYieldBalances(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,

View file

@ -59,9 +59,9 @@ internal class OnrampTokenListModel @Inject constructor(
val state: StateFlow<TokenListUM> = tokenListUMController.state
private val params: OnrampTokenListComponent.Params = paramsContainer.require()
private val scanResponse by lazy {
private val userWallet by lazy {
getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
.requireColdWallet().scanResponse // TODO [REDACTED_TASK_KEY]
.requireColdWallet() // TODO [REDACTED_TASK_KEY]
}
init {
@ -229,16 +229,13 @@ internal class OnrampTokenListModel @Inject constructor(
return when (params.filterOperation) {
OnrampOperation.BUY -> {
rampStateManager.availableForBuy(
scanResponse = scanResponse,
scanResponse = userWallet.scanResponse,
userWalletId = params.userWalletId,
cryptoCurrency = status.currency,
).isAvailable()
}
OnrampOperation.SELL -> {
rampStateManager.availableForSell(
userWalletId = params.userWalletId,
status = status,
).isRight()
rampStateManager.availableForSell(userWallet = userWallet, status = status).isRight()
}
OnrampOperation.SWAP -> {
val isAvailable = rampStateManager.availableForSwap(