From 158fd355def5ad2f26bdecc8e00b16dba006b75a Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 13 Mar 2026 11:01:35 +0400 Subject: [PATCH] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../tap/di/domain/TokensDomainModule.kt | 22 --- .../GeneratedEnvironmentConfigConverter.kt | 2 +- .../utils/CryptoCurrencyBalanceFetcher.kt | 16 ++ .../tokens/FetchCurrencyStatusUseCase.kt | 161 ------------------ .../state/helpers/StakingBalanceUpdater.kt | 10 +- .../feature/swap/domain/SwapInteractorImpl.kt | 12 +- .../DefaultTokenDetailsDeepLinkHandler.kt | 8 +- .../tokendetails/model/TokenDetailsModel.kt | 7 +- 9 files changed, 40 insertions(+), 200 deletions(-) delete mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 827750b5a0..636d7a8aa0 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 827750b5a0ef7202120b0df0ae903d70e18e3d32 +Subproject commit 636d7a8aa0e330e9b95e91d85f23ad15ac6d913f diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 9dd18523ac..f4503ac3d3 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -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( diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/GeneratedEnvironmentConfigConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/GeneratedEnvironmentConfigConverter.kt index fb57e24aee..28644f5c6b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/GeneratedEnvironmentConfigConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/GeneratedEnvironmentConfigConverter.kt @@ -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, ) } diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyBalanceFetcher.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyBalanceFetcher.kt index d9e1e38b0d..367d8411cf 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyBalanceFetcher.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyBalanceFetcher.kt @@ -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) { 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) { + if (currencies.isEmpty()) return + + mutex.withLock { + refreshBalances(userWalletId, currencies) + } + } + private suspend fun refreshBalances(userWalletId: UserWalletId, currencies: List) { coroutineScope { val results = listOf( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt deleted file mode 100644 index a1598a3f5f..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt +++ /dev/null @@ -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 { - 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 { - 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.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.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 { - return singleNetworkStatusFetcher( - params = SingleNetworkStatusFetcher.Params(userWalletId = userWalletId, network = network), - ) - } - - private suspend fun fetchQuote(currencyId: CryptoCurrency.ID): Either { - return multiQuoteStatusFetcher( - params = MultiQuoteStatusFetcher.Params( - currenciesIds = setOfNotNull(currencyId.rawCurrencyId), - appCurrencyId = null, - ), - ) - } - - private suspend fun fetchStakingBalance( - userWalletId: UserWalletId, - cryptoCurrency: CryptoCurrency, - ): Either = 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>.summarizeResult(): Either { - return firstOrNull { it.isLeft() } ?: Unit.right() - } -} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index 2128c11c3d..aea51e584a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -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, ) } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 0489edb872..3b7b8442ab 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -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( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/deeplink/DefaultTokenDetailsDeepLinkHandler.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/deeplink/DefaultTokenDetailsDeepLinkHandler.kt index 81b15eb645..20cf144c6d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/deeplink/DefaultTokenDetailsDeepLinkHandler.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/deeplink/DefaultTokenDetailsDeepLinkHandler.kt @@ -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( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index d897836afb..a8626464c7 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -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()