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 eddbbc0fd4..819dab82ba 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 @@ -484,4 +484,10 @@ internal object TokensDomainModule { dispatchers = dispatchers, ) } + + @Provides + @Singleton + fun provideGetAssetRequirementsUseCase(walletManagersFacade: WalletManagersFacade): GetAssetRequirementsUseCase { + return GetAssetRequirementsUseCase(walletManagersFacade) + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetAssetRequirementsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetAssetRequirementsUseCase.kt new file mode 100644 index 0000000000..06c27b8c64 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetAssetRequirementsUseCase.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.transaction.models.AssetRequirementsCondition +import com.tangem.domain.walletmanager.WalletManagersFacade + +class GetAssetRequirementsUseCase( + private val walletManagersFacade: WalletManagersFacade, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + currency: CryptoCurrency, + ): Either { + return Either.Companion.catch { + walletManagersFacade.getAssetRequirements(userWalletId, currency) + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/BaseActionsFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/BaseActionsFactory.kt index 9f6481cf9f..f8285e582d 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/BaseActionsFactory.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/BaseActionsFactory.kt @@ -56,15 +56,24 @@ internal open class BaseActionsFactory( * * @param userWallet the user's cold wallet * @param currency the cryptocurrency to check + * @param requirementsDeferred a deferred object containing the asset requirements condition */ protected suspend fun getOnrampUnavailabilityReason( userWallet: UserWallet, currency: CryptoCurrency, + requirementsDeferred: Deferred?, ): ScenarioUnavailabilityReason { - return rampStateManager.availableForBuy( + val onrampUnavailabilityReason = rampStateManager.availableForBuy( userWallet = userWallet, cryptoCurrency = currency, ) + val shouldCheckAssetRequirements = + onrampUnavailabilityReason == ScenarioUnavailabilityReason.None && requirementsDeferred != null + return if (shouldCheckAssetRequirements) { + getReceiveScenario(requirementsDeferred.await()) + } else { + onrampUnavailabilityReason + } } /** diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/CommonActionsFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/CommonActionsFactory.kt index bd735df30c..47e33ba88b 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/CommonActionsFactory.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/CommonActionsFactory.kt @@ -52,7 +52,11 @@ internal class CommonActionsFactory( } val onrampUnavailabilityReasonDeferred = async { - getOnrampUnavailabilityReason(userWallet = userWallet, currency = cryptoCurrencyStatus.currency) + getOnrampUnavailabilityReason( + userWallet = userWallet, + currency = cryptoCurrencyStatus.currency, + requirementsDeferred = requirementsDeferred, + ) } val sendUnavailabilityReasonDeferred = async { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/OutdatedDataActionsFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/OutdatedDataActionsFactory.kt index f38522a4f7..288ef4398d 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/OutdatedDataActionsFactory.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/OutdatedDataActionsFactory.kt @@ -2,12 +2,12 @@ package com.tangem.domain.tokens.actions import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.TokenActionsState.ActionState import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.models.wallet.UserWallet import kotlinx.coroutines.Deferred import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope @@ -50,7 +50,11 @@ internal class OutdatedDataActionsFactory( } val onrampUnavailabilityReasonDeferred = async { - getOnrampUnavailabilityReason(userWallet = userWallet, currency = cryptoCurrencyStatus.currency) + getOnrampUnavailabilityReason( + userWallet = userWallet, + currency = cryptoCurrencyStatus.currency, + requirementsDeferred = requirementsDeferred, + ) } val sendUnavailabilityReasonDeferred = if (sources.networkSource == StatusSource.ACTUAL) { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/UnreachableActionsFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/UnreachableActionsFactory.kt index ded293f444..cd59aafb14 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/UnreachableActionsFactory.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/UnreachableActionsFactory.kt @@ -1,11 +1,11 @@ package com.tangem.domain.tokens.actions import com.tangem.domain.exchange.RampStateManager +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.TokenActionsState.ActionState import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.models.wallet.UserWallet import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope @@ -36,7 +36,11 @@ internal class UnreachableActionsFactory( } val onrampUnavailabilityReasonDeferred = async { - getOnrampUnavailabilityReason(userWallet = userWallet, currency = cryptoCurrencyStatus.currency) + getOnrampUnavailabilityReason( + userWallet = userWallet, + currency = cryptoCurrencyStatus.currency, + requirementsDeferred = requirementsDeferred, + ) } // endregion diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index b32858e319..efcd01bc52 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -45,6 +45,7 @@ dependencies { implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) implementation(projects.domain.settings) + implementation(projects.domain.transaction.models) /** DI */ implementation(deps.hilt.android) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index c9e0a59aee..8a3bd36ae7 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -16,11 +16,13 @@ import com.tangem.domain.models.TotalFiatBalance import com.tangem.domain.models.wallet.requireColdWallet import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.models.UserCountry +import com.tangem.domain.tokens.GetAssetRequirementsUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent @@ -54,6 +56,7 @@ internal class OnrampTokenListModel @Inject constructor( private val getWalletsUseCase: GetWalletsUseCase, private val rampStateManager: RampStateManager, private val getUserCountryUseCase: GetUserCountryUseCase, + private val getAssetRequirementsUseCase: GetAssetRequirementsUseCase, ) : Model() { val state: StateFlow = tokenListUMController.state @@ -206,17 +209,28 @@ internal class OnrampTokenListModel @Inject constructor( return coroutineScope { map { status -> async { - val isAvailable = checkAvailabilityByOperation(status = status) + val isOperationAvailable = checkAvailabilityByOperation(status = status) val isNotMissedDerivation = status.value !is CryptoCurrencyStatus.MissedDerivation val isNotLoading = status.value !is CryptoCurrencyStatus.Loading + val requirements = getAssetRequirementsUseCase( + userWalletId = userWallet.walletId, + currency = status.currency, + ).getOrNull() - val isNotUnreachable = when (params.filterOperation) { - OnrampOperation.BUY -> true // unreachable state is available for Buy operation - OnrampOperation.SELL -> status.value !is CryptoCurrencyStatus.Unreachable - OnrampOperation.SWAP -> status.value !is CryptoCurrencyStatus.Unreachable + val isNotTrustlineRequired = requirements !is AssetRequirementsCondition.RequiredTrustline + val isNotUnreachable = status.value !is CryptoCurrencyStatus.Unreachable + + val isAvailable = when (params.filterOperation) { + OnrampOperation.BUY -> { + isNotTrustlineRequired + } // unreachable state is available for Buy operation + OnrampOperation.SELL -> isNotUnreachable + OnrampOperation.SWAP -> { + isNotUnreachable && isNotTrustlineRequired + } } - status to (isAvailable && isNotMissedDerivation && isNotLoading && isNotUnreachable) + status to (isOperationAvailable && isNotMissedDerivation && isNotLoading && isAvailable) } } .awaitAll() diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index 9c2a64eebe..5b0d422759 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { implementation(projects.domain.models) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) + implementation(projects.domain.transaction.models) /** Data */ implementation(projects.data.common) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt index 69f6fe5d7e..a23bc00f4b 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt @@ -26,10 +26,11 @@ import com.tangem.datasource.crypto.DataSignatureVerifier import com.tangem.datasource.exchangeservice.swap.ExpressUtils import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.transaction.models.AssetRequirementsCondition +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.feature.swap.converters.* import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.models.ExpressDataError @@ -134,7 +135,12 @@ internal class DefaultSwapRepository( contractAddress = initialCurrency.contractAddress, network = initialCurrency.network, ) - val currenciesList = currencyList.map { leastTokenInfoConverter.convert(it) } + val currenciesList = currencyList + .filter { + val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, it) + requirements !is AssetRequirementsCondition.RequiredTrustline + } + .map { leastTokenInfoConverter.convert(it) } val pairsDeferred = async { getPairsInternal( 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 f0fbc9fbcc..712d322bd4 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 @@ -20,6 +20,8 @@ import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.quote.QuoteStatus +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.quotes.QuotesRepository import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.tokens.* @@ -29,10 +31,9 @@ import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.transaction.error.GetFeeError +import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.domain.transaction.usecase.* import com.tangem.domain.utils.convertToSdkAmount -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.models.ExpressDataError @@ -79,6 +80,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( private val getEthSpecificFeeUseCase: GetEthSpecificFeeUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase, + private val getAssetRequirementsUseCase: GetAssetRequirementsUseCase, private val amountFormatter: AmountFormatter, @Assisted private val userWalletId: UserWalletId, ) : SwapInteractor { @@ -139,7 +141,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( ) } - private fun getToCurrenciesGroup( + private suspend fun getToCurrenciesGroup( currency: CryptoCurrency, leastPairs: List, cryptoCurrenciesList: List, @@ -171,15 +173,18 @@ internal class SwapInteractorImpl @AssistedInject constructor( ) } - private fun findProvidersForPair( + private suspend fun findProvidersForPair( cryptoCurrencyStatuses: CryptoCurrencyStatus, swapPairsLeastList: List, tokenInfoForAvailable: (SwapPairLeast) -> LeastTokenInfo, ): List? { + val requirements = getAssetRequirementsUseCase.invoke(userWalletId, cryptoCurrencyStatuses.currency).getOrNull() + return swapPairsLeastList.firstNotNullOfOrNull { val listTokenInfo = tokenInfoForAvailable(it) if (cryptoCurrencyStatuses.currency.network.backendId == listTokenInfo.network && - cryptoCurrencyStatuses.currency.getContractAddress() == listTokenInfo.contractAddress + cryptoCurrencyStatuses.currency.getContractAddress() == listTokenInfo.contractAddress && + requirements !is AssetRequirementsCondition.RequiredTrustline ) { it.providers } else {