Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-13 11:01:35 +04:00
parent 4f3ff79ac9
commit 158fd355de
9 changed files with 40 additions and 200 deletions

@ -1 +1 @@
Subproject commit 827750b5a0ef7202120b0df0ae903d70e18e3d32
Subproject commit 636d7a8aa0e330e9b95e91d85f23ad15ac6d913f

View file

@ -11,7 +11,6 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.staking.single.SingleStakingBalanceFetcher
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
@ -29,7 +28,6 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
@Suppress("TooManyFunctions", "LargeClass")
internal object TokensDomainModule {
@Provides
@ -46,26 +44,6 @@ internal object TokensDomainModule {
return DefaultTokensFeatureToggles(featureTogglesManager = featureTogglesManager)
}
@Provides
@Singleton
fun provideFetchCurrencyStatusUseCase(
currenciesRepository: CurrenciesRepository,
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleStakingBalanceFetcher: SingleStakingBalanceFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
stakingIdFactory: StakingIdFactory,
): FetchCurrencyStatusUseCase {
return FetchCurrencyStatusUseCase(
currenciesRepository = currenciesRepository,
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleStakingBalanceFetcher = singleStakingBalanceFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
stakingIdFactory = stakingIdFactory,
)
}
@Provides
@Singleton
fun provideGetCryptoCurrencyActionsUseCase(

View file

@ -51,7 +51,7 @@ internal object GeneratedEnvironmentConfigConverter {
bffStaticTokenDev = GeneratedEnvironmentConfig.bffStaticTokenDev,
gaslessTxApiKeyDev = GeneratedEnvironmentConfig.gaslessTxApiKeyDev,
gaslessTxApiKey = GeneratedEnvironmentConfig.gaslessTxApiKey,
customerIoCdpApiKey = GeneratedEnvironmentConfig.CustomerIO.appApiKey,
customerIoCdpApiKey = GeneratedEnvironmentConfig.CustomerIO.androidApiKey,
surveySparrowToken = GeneratedEnvironmentConfig.SurveySparrow.apiKey,
)
}

View file

@ -35,6 +35,10 @@ class CryptoCurrencyBalanceFetcher(
private val mutex = Mutex()
operator fun invoke(userWalletId: UserWalletId, currency: CryptoCurrency) {
invoke(userWalletId = userWalletId, currencies = listOf(currency))
}
operator fun invoke(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
if (currencies.isEmpty()) return
@ -45,6 +49,18 @@ class CryptoCurrencyBalanceFetcher(
}
}
suspend fun invokeAndAwait(userWalletId: UserWalletId, currency: CryptoCurrency) {
invokeAndAwait(userWalletId = userWalletId, currencies = listOf(currency))
}
suspend fun invokeAndAwait(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
if (currencies.isEmpty()) return
mutex.withLock {
refreshBalances(userWalletId, currencies)
}
}
private suspend fun refreshBalances(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
coroutineScope {
val results = listOf(

View file

@ -1,161 +0,0 @@
package com.tangem.domain.tokens
import arrow.core.Either
import arrow.core.getOrElse
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.models.wallet.UserWalletId
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.single.SingleStakingBalanceFetcher
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.repository.CurrenciesRepository
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
/**
* Use case responsible for fetching currency status information, including network status
* and quotes for a given cryptocurrency. It provides methods to fetch currency status either
* by providing a specific currency ID or fetching the status of the primary currency.
*
* @param currenciesRepository The repository for retrieving currency-related data.
*/
// TODO: Add tests
@Suppress("LongParameterList")
class FetchCurrencyStatusUseCase(
private val currenciesRepository: CurrenciesRepository,
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val singleStakingBalanceFetcher: SingleStakingBalanceFetcher,
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
private val stakingIdFactory: StakingIdFactory,
) {
/**
* Fetches the status of a specific cryptocurrency for a given user wallet.
*
* @param userWalletId The ID of the user's wallet.
* @param id The ID of the cryptocurrency.
* @return An [Either] representing success (Right) or an error (Left) in fetching the status.
*/
suspend operator fun invoke(userWalletId: UserWalletId, id: CryptoCurrency.ID): Either<CurrencyStatusError, Unit> {
return either {
val currency = getCurrency(userWalletId, id)
return@either coroutineScope {
val fetchStatus = async {
fetchNetworkStatus(userWalletId = userWalletId, network = currency.network)
}
val fetchQuote = async { fetchQuote(currencyId = currency.id) }
val fetchStakingBalance = async {
fetchStakingBalance(userWalletId = userWalletId, cryptoCurrency = currency)
}
awaitAll(fetchStatus, fetchQuote, fetchStakingBalance).summarizeResult()
}
}
}
/**
* Fetches the status of the primary cryptocurrency for a given user wallet.
*
* @param userWalletId The ID of the user's wallet.
* @param refresh Indicates whether to force a refresh of the status data.
* @return An [Either] representing success (Right) or an error (Left) in fetching the status.
*/
suspend operator fun invoke(
userWalletId: UserWalletId,
refresh: Boolean = false,
): Either<CurrencyStatusError, Unit> {
return either {
val currency = getPrimaryCurrency(userWalletId, refresh)
return@either coroutineScope {
val fetchStatus = async {
fetchNetworkStatus(userWalletId = userWalletId, network = currency.network)
}
val fetchQuote = async { fetchQuote(currencyId = currency.id) }
awaitAll(fetchStatus, fetchQuote).summarizeResult()
}
}
}
private suspend fun Raise<CurrencyStatusError>.getCurrency(
userWalletId: UserWalletId,
id: CryptoCurrency.ID,
): CryptoCurrency {
return catch(
block = {
multiWalletCryptoCurrenciesSupplier.getSyncOrNull(
params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId),
)
?.firstOrNull { it.id == id }
?: error("Unable to find currency with ID: $id")
},
) {
raise(CurrencyStatusError.DataError(it))
}
}
private suspend fun Raise<CurrencyStatusError>.getPrimaryCurrency(
userWalletId: UserWalletId,
refresh: Boolean = false,
): CryptoCurrency {
return catch({ currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId, refresh) }) {
raise(CurrencyStatusError.DataError(it))
}
}
private suspend fun fetchNetworkStatus(userWalletId: UserWalletId, network: Network): Either<Throwable, Unit> {
return singleNetworkStatusFetcher(
params = SingleNetworkStatusFetcher.Params(userWalletId = userWalletId, network = network),
)
}
private suspend fun fetchQuote(currencyId: CryptoCurrency.ID): Either<Throwable, Unit> {
return multiQuoteStatusFetcher(
params = MultiQuoteStatusFetcher.Params(
currenciesIds = setOfNotNull(currencyId.rawCurrencyId),
appCurrencyId = null,
),
)
}
private suspend fun fetchStakingBalance(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): Either<Throwable, Unit> = either {
val stakingId = stakingIdFactory.create(
userWalletId = userWalletId,
currencyId = cryptoCurrency.id,
network = cryptoCurrency.network,
)
.getOrElse {
when (it) {
is StakingIdFactory.Error.UnableToGetAddress -> raise(IllegalStateException("$it"))
StakingIdFactory.Error.UnsupportedCurrency -> Unit.right()
}
return@either
}
singleStakingBalanceFetcher(
params = SingleStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingId = stakingId),
)
.bind()
}
private fun List<Either<Throwable, Unit>>.summarizeResult(): Either<Throwable, Unit> {
return firstOrNull { it.isLeft() } ?: Unit.right()
}
}

View file

@ -1,16 +1,16 @@
package com.tangem.features.staking.impl.presentation.state.helpers
import com.tangem.utils.coroutines.AppCoroutineScope
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.staking.FetchActionsUseCase
import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase
import com.tangem.domain.staking.model.StakingIntegration
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter
import com.tangem.utils.coroutines.AppCoroutineScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -22,7 +22,7 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
private val fetchActionsUseCase: FetchActionsUseCase,
private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
private val fetchStakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase,
private val coroutineScope: AppCoroutineScope,
@Assisted private val userWallet: UserWallet,
@ -83,9 +83,9 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
private suspend fun fetchCurrencyStatus(delayMillis: Long = 0L) {
delay(delayMillis)
fetchCurrencyStatusUseCase(
cryptoCurrencyBalanceFetcher.invokeAndAwait(
userWalletId = userWallet.walletId,
id = cryptoCurrencyStatus.currency.id,
currency = cryptoCurrencyStatus.currency,
)
}

View file

@ -21,6 +21,8 @@ import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.extenstions.unwrap
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
@ -38,8 +40,10 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.TangemPayWithdrawExchangeState
import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.GetAssetRequirementsUseCase
import com.tangem.domain.tokens.GetCurrencyCheckUseCase
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.model.FeePaidCurrency
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
import com.tangem.domain.tokens.repository.CurrenciesRepository
@ -75,7 +79,7 @@ import java.math.RoundingMode
internal class SwapInteractorImpl @AssistedInject constructor(
private val repository: SwapRepository,
private val allowPermissionsHandler: AllowPermissionsHandler,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
private val sendTransactionUseCase: SendTransactionUseCase,
private val createTransactionUseCase: CreateTransactionUseCase,
private val createTransferTransactionUseCase: CreateTransferTransactionUseCase,
@ -438,7 +442,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
if (isAllowedToSpend && allowPermissionsHandler.isAddressAllowanceInProgress(fromTokenAddress)) {
allowPermissionsHandler.removeAddressFromProgress(fromTokenAddress)
fetchCurrencyStatusUseCase(userWalletId = userWalletId, id = fromToken.currency.id)
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currency = fromToken.currency)
}
return if (isAllowedToSpend && isBalanceWithoutFeeEnough) {
provider to loadDexSwapData(

View file

@ -18,7 +18,7 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.notifications.models.NotificationType
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
@ -40,7 +40,7 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
@Assisted private val isFromOnNewIntent: Boolean,
private val appRouter: AppRouter,
private val selectWalletUseCase: SelectWalletUseCase,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
private val tokenDetailsDeepLinkActionTrigger: TokenDetailsDeepLinkActionTrigger,
private val walletDeepLinkActionTrigger: WalletDeepLinkActionTrigger,
private val analyticsEventHandler: AnalyticsEventHandler,
@ -122,9 +122,9 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
userWallet is UserWallet.Cold &&
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
when {
isMultiCurrency -> fetchCurrencyStatusUseCase.invoke(
isMultiCurrency -> cryptoCurrencyBalanceFetcher(
userWalletId = userWallet.walletId,
id = cryptoCurrency.id,
currency = cryptoCurrency,
)
!isMultiCurrency -> walletBalanceFetcher(
params = WalletBalanceFetcher.Params(

View file

@ -35,6 +35,7 @@ import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
@ -110,7 +111,7 @@ import javax.inject.Inject
internal class TokenDetailsModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
private val getExploreUrlUseCase: GetExploreUrlUseCase,
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
private val isCryptoCurrencyCoinCouldHideUseCase: IsCryptoCurrencyCoinCouldHideUseCase,
@ -838,7 +839,9 @@ internal class TokenDetailsModel @Inject constructor(
modelScope.launch(dispatchers.main) {
listOf(
async { fetchCurrencyStatusUseCase(userWalletId = userWalletId, id = cryptoCurrency.id) },
async {
cryptoCurrencyBalanceFetcher.invokeAndAwait(userWalletId = userWalletId, currency = cryptoCurrency)
},
async {
updateTxHistory()
subscribeOnExpressTransactionsUpdates()