diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index f5eb1f355e..646c003c7f 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -4,8 +4,10 @@ import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.exchange.RampStateManager +import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase +import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository @@ -45,6 +47,8 @@ internal object ActivityModule { fun provideDefaultRampManager( appStateHolder: AppStateHolder, swapServiceLoader: SwapServiceLoader, + currenciesRepository: CurrenciesRepository, + getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, dispatchers: CoroutineDispatcherProvider, ): RampStateManager { return DefaultRampManager( @@ -52,6 +56,8 @@ internal object ActivityModule { buyService = Provider { requireNotNull(appStateHolder.buyService) }, sellService = Provider { requireNotNull(appStateHolder.sellService) }, swapServiceLoader = swapServiceLoader, + currenciesRepository = currenciesRepository, + getNetworkCoinStatusUseCase = getNetworkCoinStatusUseCase, dispatchers = dispatchers, ) } diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index b512d32ba7..0186fe0f32 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -4,23 +4,37 @@ import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRA import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrency +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.UserWalletId import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.isNullOrZero import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext +@Suppress("LongParameterList") internal class DefaultRampManager( private val exchangeService: ExchangeService?, private val buyService: Provider, private val sellService: Provider, private val swapServiceLoader: SwapServiceLoader, + private val currenciesRepository: CurrenciesRepository, + private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, private val dispatchers: CoroutineDispatcherProvider, ) : RampStateManager { private val cryptoCurrencyConverter = CryptoCurrencyConverter() + override fun isSellSupportedByService(cryptoCurrency: CryptoCurrency): Boolean { + return exchangeService?.availableForSell( + currency = cryptoCurrencyConverter.convertBack(cryptoCurrency), + ) ?: false + } + override fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean { return exchangeService?.availableForBuy( scanResponse = scanResponse, @@ -28,10 +42,14 @@ internal class DefaultRampManager( ) ?: false } - override fun availableForSell(cryptoCurrency: CryptoCurrency): Boolean { - return exchangeService?.availableForSell( - currency = cryptoCurrencyConverter.convertBack(cryptoCurrency), - ) ?: false + override suspend fun availableForSell(userWalletId: UserWalletId, status: CryptoCurrencyStatus): Boolean { + val sellSupportedByService = isSellSupportedByService(cryptoCurrency = status.currency) + + if (!sellSupportedByService) return false + + val reason = getSendUnavailabilityReason(userWalletId, status) + + return reason == ScenarioUnavailabilityReason.None } override suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean { @@ -75,4 +93,34 @@ internal class DefaultRampManager( asset?.exchangeAvailable ?: false } } + + private suspend fun getSendUnavailabilityReason( + userWalletId: UserWalletId, + cryptoCurrencyStatus: CryptoCurrencyStatus, + ): ScenarioUnavailabilityReason { + val coinStatus = getNetworkCoinStatusUseCase.invokeSync( + userWalletId = userWalletId, + networkId = cryptoCurrencyStatus.currency.network.id, + derivationPath = cryptoCurrencyStatus.currency.network.derivationPath, + isSingleWalletWithTokens = false, + ).getOrNull() + + return when { + cryptoCurrencyStatus.value.amount.isNullOrZero() -> { + ScenarioUnavailabilityReason.EmptyBalance(ScenarioUnavailabilityReason.WithdrawalScenario.SEND) + } + currenciesRepository.isSendBlockedByPendingTransactions( + cryptoCurrencyStatus = cryptoCurrencyStatus, + coinStatus = coinStatus, + ) -> { + ScenarioUnavailabilityReason.PendingTransaction( + withdrawalScenario = ScenarioUnavailabilityReason.WithdrawalScenario.SEND, + networkName = coinStatus?.currency?.network?.name.orEmpty(), + ) + } + else -> { + ScenarioUnavailabilityReason.None + } + } + } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt index 512be7ca64..9aacb2358c 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt @@ -3,6 +3,7 @@ package com.tangem.domain.exchange import com.tangem.domain.core.lce.Lce import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow @@ -11,9 +12,18 @@ import kotlinx.coroutines.flow.Flow */ interface RampStateManager { + /** Check if sell service is supported for given [CryptoCurrency] */ + fun isSellSupportedByService(cryptoCurrency: CryptoCurrency): Boolean + fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean - fun availableForSell(cryptoCurrency: CryptoCurrency): Boolean + /** + * Check if [CryptoCurrency] is available for sell + * + * @param userWalletId id of multi-currency wallet + * @param status crypto currency status + */ + suspend fun availableForSell(userWalletId: UserWalletId, status: CryptoCurrencyStatus): Boolean suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index 2e91b110df..6d668d1e97 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -189,7 +189,7 @@ class GetCryptoCurrencyActionsUseCase( } // sell - val sellSupportedByService = rampManager.availableForSell(cryptoCurrency) + val sellSupportedByService = rampManager.isSellSupportedByService(cryptoCurrency) val sendAvailable = sendUnavailabilityReason is ScenarioUnavailabilityReason.None when { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt index e144bfbffe..6947d107cd 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt @@ -11,10 +11,10 @@ import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.usecase.GetWalletsUseCase -import com.tangem.features.onramp.tokenlist.entity.OnrampOperation import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.selecttoken.ui.OnrampSelectToken import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent +import com.tangem.features.onramp.tokenlist.entity.OnrampOperation import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -34,7 +34,7 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor( params = OnrampTokenListComponent.Params( filterOperation = when (params) { is OnrampOperationComponent.Params.Buy -> OnrampOperation.BUY - is OnrampOperationComponent.Params.Sell -> OnrampOperation.SWAP + is OnrampOperationComponent.Params.Sell -> OnrampOperation.SELL }, hasSearchBar = true, userWalletId = params.userWalletId, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt index ee219d1fc5..0788cb7346 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt @@ -124,7 +124,6 @@ internal class UpdateTokenItemsTransformer( -> null } }, - onItemClick = onItemClick, ) } 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 97d7afd5be..c86d5ec70f 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 @@ -15,8 +15,8 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.tokenlist.entity.OnrampOperation import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent +import com.tangem.features.onramp.tokenlist.entity.OnrampOperation import com.tangem.features.onramp.tokenlist.entity.TokenListUM import com.tangem.features.onramp.tokenlist.entity.TokenListUMController import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer @@ -124,20 +124,35 @@ internal class OnrampTokenListModel @Inject constructor( private suspend fun List.filterByAvailability(): Map> { return groupBy { status -> - val isAvailable = when (params.filterOperation) { - OnrampOperation.BUY -> { - rampStateManager.availableForBuy(scanResponse = scanResponse, cryptoCurrency = status.currency) - } - OnrampOperation.SELL -> rampStateManager.availableForSell(cryptoCurrency = status.currency) - OnrampOperation.SWAP -> rampStateManager.availableForSwap( + val isAvailable = checkAvailabilityByOperation(status = status) + val isNotMissedDerivation = status.value !is CryptoCurrencyStatus.MissedDerivation + + 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 + } + + isAvailable && isNotMissedDerivation && isNotUnreachable + } + } + + private suspend fun checkAvailabilityByOperation(status: CryptoCurrencyStatus): Boolean { + return when (params.filterOperation) { + OnrampOperation.BUY -> { + rampStateManager.availableForBuy(scanResponse = scanResponse, cryptoCurrency = status.currency) + } + OnrampOperation.SELL -> { + rampStateManager.availableForSell(userWalletId = params.userWalletId, status = status) + } + OnrampOperation.SWAP -> { + val isAvailable = rampStateManager.availableForSwap( userWalletId = params.userWalletId, cryptoCurrency = status.currency, ) - } - isAvailable && - status.value !is CryptoCurrencyStatus.MissedDerivation && - status.value !is CryptoCurrencyStatus.Unreachable + isAvailable && status.value !is CryptoCurrencyStatus.NoQuote + } } } } \ No newline at end of file