Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-11 11:11:53 +04:00
parent d4b7aa85e1
commit d84315a40c
4 changed files with 12 additions and 322 deletions

View file

@ -100,18 +100,6 @@ internal object TokensDomainModule {
)
}
@Provides
@Singleton
fun provideGetCurrencyStatusByNetworkUseCase(
currencyStatusOperations: BaseCurrencyStatusOperations,
dispatchers: CoroutineDispatcherProvider,
): GetNetworkCoinStatusUseCase {
return GetNetworkCoinStatusUseCase(
currencyStatusOperations = currencyStatusOperations,
dispatchers = dispatchers,
)
}
@Provides
@Singleton
fun provideGetFeePaidCryptoCurrencyStatusSyncUseCase(

View file

@ -1,74 +0,0 @@
package com.tangem.domain.tokens
import arrow.core.Either
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
class GetNetworkCoinStatusUseCase(
private val currencyStatusOperations: BaseCurrencyStatusOperations,
private val dispatchers: CoroutineDispatcherProvider,
) {
operator fun invoke(
userWalletId: UserWalletId,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
isSingleWalletWithTokens: Boolean,
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
return flow {
emitAll(
flow = getCurrency(
userWalletId = userWalletId,
networkId = networkId,
derivationPath = derivationPath,
isSingleWalletWithTokens = isSingleWalletWithTokens,
),
)
}
.flowOn(dispatchers.io)
}
suspend fun invokeSync(
userWallet: UserWallet,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
): Either<CurrencyStatusError, CryptoCurrencyStatus> {
val userWalletId = userWallet.walletId
val maybeCurrency = if (userWallet.isMultiCurrency) {
currencyStatusOperations.getNetworkCoinSync(userWalletId, networkId, derivationPath)
} else if (userWallet is UserWallet.Cold && userWallet.cardTypesResolver.isSingleWalletWithToken()) {
currencyStatusOperations.getNetworkCoinForSingleWalletWithTokenSync(userWalletId, networkId)
} else {
currencyStatusOperations.getPrimaryCurrencyStatusSync(userWalletId)
}
return maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
}
private suspend fun getCurrency(
userWalletId: UserWalletId,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
isSingleWalletWithTokens: Boolean,
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
val networkFlow = if (isSingleWalletWithTokens) {
currencyStatusOperations.getNetworkCoinForSingleWalletWithTokenFlow(userWalletId, networkId)
} else {
currencyStatusOperations.getNetworkCoinFlow(userWalletId, networkId, derivationPath)
}
return networkFlow.map { maybeCurrency ->
maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
}
}
}

View file

@ -2,15 +2,10 @@ package com.tangem.domain.tokens.operations
import arrow.core.*
import arrow.core.raise.*
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.domain.core.utils.EitherFlow
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.models.staking.StakingID
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.networks.multi.MultiNetworkStatusProducer
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
@ -20,7 +15,6 @@ import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.quotes.single.SingleQuoteStatusProducer
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.model.isStakingSupported
import com.tangem.domain.staking.multi.MultiStakingBalanceProducer
import com.tangem.domain.staking.multi.MultiStakingBalanceSupplier
import com.tangem.domain.staking.single.SingleStakingBalanceProducer
@ -30,7 +24,7 @@ import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations.Error
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.utils.CurrencyStatusProxyCreator
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.firstOrNull
/**
* Base operations for working with currency status
@ -54,112 +48,6 @@ class BaseCurrencyStatusOperations(
private val currencyStatusProxyCreator = CurrencyStatusProxyCreator()
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)
}
suspend fun getCurrencyStatusFlow(
userWalletId: UserWalletId,
currency: CryptoCurrency,
includeQuotes: Boolean = true,
subscribeOnStakingBalance: 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 = getNetworkStatus(userWalletId = userWalletId, network = currency.network)
val isStakingSupported = currency.network.toBlockchain().isStakingSupported
val stakingBalanceFlow = if (isStakingSupported) {
val stakingId = stakingIdFactory.create(
userWalletId = userWalletId,
currencyId = currency.id,
network = currency.network,
)
.getOrNull()
stakingId?.let {
getStakingBalance(userWalletId = userWalletId, stakingId = it)
}
} else {
null
}
return if (subscribeOnStakingBalance && stakingBalanceFlow != null) {
combine(quoteFlow, statusFlow, stakingBalanceFlow) { maybeQuote, maybeNetworkStatus, maybeStakingBalance ->
currencyStatusProxyCreator.createCurrencyStatus(
currency = currency,
maybeQuoteStatus = maybeQuote,
maybeNetworkStatus = maybeNetworkStatus,
maybeStakingBalance = maybeStakingBalance,
)
}
} else {
combine(quoteFlow, statusFlow) { maybeQuote, maybeNetworkStatus ->
currencyStatusProxyCreator.createCurrencyStatus(
currency = currency,
maybeQuoteStatus = maybeQuote,
maybeNetworkStatus = maybeNetworkStatus,
maybeStakingBalance = null,
)
}
}
}
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,
@ -217,32 +105,6 @@ class BaseCurrencyStatusOperations(
}
}
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 = userWalletId,
currency = currency,
includeQuotes = includeQuotes,
subscribeOnStakingBalance = false,
)
}
suspend fun getCurrenciesStatusesSync(userWalletId: UserWalletId): Either<Error, List<CryptoCurrencyStatus>> {
return either {
catch(
@ -279,35 +141,6 @@ class BaseCurrencyStatusOperations(
}
}
suspend fun getPrimaryCurrencyStatusSync(userWalletId: UserWalletId): Either<Error, CryptoCurrencyStatus> = either {
val currency = catch(
block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) },
catch = { raise(Error.DataError(it)) },
)
val quotes = currency.id.rawCurrencyId?.let {
singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(rawCurrencyId = it))
.firstOrNull()
}
?.right()
?: Error.EmptyQuotes.left()
val networkStatus = singleNetworkStatusSupplier(
params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = currency.network),
)
.firstOrNull()
.right()
val stakingBalances = getStakingBalanceSync(userWalletId, currency)
return currencyStatusProxyCreator.createCurrencyStatus(
currency = currency,
maybeQuoteStatus = quotes,
maybeNetworkStatus = networkStatus,
maybeStakingBalance = stakingBalances,
)
}
private suspend fun Raise<Error>.getMultiCurrencyWalletCurrency(
userWalletId: UserWalletId,
currencyId: CryptoCurrency.ID,
@ -323,36 +156,6 @@ class BaseCurrencyStatusOperations(
.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 getStakingBalance(userWalletId: UserWalletId, stakingId: StakingID): EitherFlow<Error, StakingBalance> {
return singleStakingBalanceSupplier(
params = SingleStakingBalanceProducer.Params(
userWalletId = userWalletId,
stakingId = stakingId,
),
)
.map<StakingBalance, Either<Error, StakingBalance>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(Error.EmptyStakingBalances.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,
@ -370,27 +173,6 @@ class BaseCurrencyStatusOperations(
.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 fun getQuotes(id: CryptoCurrency.RawID): Flow<Either<Error, Set<QuoteStatus>>> {
return singleQuoteStatusSupplier(
params = SingleQuoteStatusProducer.Params(rawCurrencyId = id),
)
.map<QuoteStatus, Either<Error, Set<QuoteStatus>>> { setOf(it).right() }
.distinctUntilChanged()
}
private suspend fun getStakingBalancesSync(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
@ -434,13 +216,6 @@ class BaseCurrencyStatusOperations(
ensureNotNull(yieldBalance) { Error.EmptyStakingBalances }
}
private suspend fun Raise<Error>.getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency {
return catch(
block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) },
catch = { raise(Error.DataError(it)) },
)
}
private fun getIds(currencies: List<CryptoCurrency>): Pair<NonEmptySet<Network>, NonEmptySet<CryptoCurrency.ID>> {
val currencyIdToNetworkId = currencies.associate { currency ->
currency.id to currency.network

View file

@ -2,6 +2,8 @@ package com.tangem.features.walletconnect.transaction.model
import androidx.compose.runtime.Stable
import arrow.core.Either
import arrow.core.Option
import arrow.core.none
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.router.stack.pushNew
@ -22,12 +24,12 @@ import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2.Icon.Type
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusOperations.getCoinStatus
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.error.SendTransactionError.UserCancelledError
import com.tangem.domain.transaction.usecase.GetFeeUseCase
@ -79,7 +81,7 @@ internal class WcSendTransactionModel @Inject constructor(
private val useCaseFactory: WcRequestUseCaseFactory,
private val converter: WcSendTransactionUMConverter,
private val getFeeUseCase: GetFeeUseCase,
private val getNetworkCoinUseCase: GetNetworkCoinStatusUseCase,
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val notificationsFactory: WcNotificationsFactory,
private val analytics: AnalyticsEventHandler,
private val urlOpener: UrlOpener,
@ -121,7 +123,7 @@ internal class WcSendTransactionModel @Inject constructor(
-> {
this@WcSendTransactionModel.cryptoCurrencyStatus =
getCryptoCurrencyStatus(userWallet = useCase.wallet, network = useCase.network)
.onLeft { unknownMethodRunnable() }
.onNone { unknownMethodRunnable() }
.getOrNull() ?: return@launch
this@WcSendTransactionModel.useCase = useCase
(useCase as? WcMutableFee)
@ -233,12 +235,11 @@ internal class WcSendTransactionModel @Inject constructor(
private suspend fun getCryptoCurrencyStatus(
userWallet: UserWallet,
network: Network,
): Either<CurrencyStatusError, CryptoCurrencyStatus> {
return getNetworkCoinUseCase.invokeSync(
userWallet = userWallet,
networkId = network.id,
derivationPath = network.derivationPath,
)
): Option<CryptoCurrencyStatus> {
val accountStatusList = singleAccountStatusListSupplier.getSyncOrNull(userWallet.walletId)
?: return none()
return accountStatusList.getCoinStatus(network)
}
private suspend fun buildUiState(