diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index b5d8b00f73..43010d791d 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -161,6 +161,7 @@ internal class SwapRepositoryImpl @Inject constructor( AggregatedSwapDataModel( dataModel = QuoteModel( toTokenAmount = createFromAmountWithOffset(response.toAmount, response.toDecimals), + allowanceContract = response.allowanceContract, ), ) } catch (ex: Exception) { @@ -218,6 +219,7 @@ internal class SwapRepositoryImpl @Inject constructor( derivationPath: String?, tokenDecimalCount: Int, tokenAddress: String, + spenderAddress: String, ): BigDecimal { val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } val walletManager = walletManagersFacade.getOrCreateWalletManager( @@ -225,7 +227,6 @@ internal class SwapRepositoryImpl @Inject constructor( blockchain = blockchain, derivationPath = derivationPath, ) - val spenderAddress = addressForTrust(networkId) val result = (walletManager as? Approver)?.getAllowance( spenderAddress, @@ -248,6 +249,7 @@ internal class SwapRepositoryImpl @Inject constructor( derivationPath: String?, currency: CryptoCurrency, amount: BigDecimal?, + spenderAddress: String, ): String { val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } @@ -256,7 +258,6 @@ internal class SwapRepositoryImpl @Inject constructor( blockchain = blockchain, derivationPath = derivationPath, ) - val spenderAddress = addressForTrust(networkId) return (walletManager as? Approver)?.getApproveData( spenderAddress, diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt deleted file mode 100644 index 4352341538..0000000000 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.feature.swap.converters - -import com.tangem.datasource.api.oneinch.models.QuoteResponse -import com.tangem.feature.swap.domain.models.createFromAmountWithOffset -import com.tangem.feature.swap.domain.models.domain.QuoteModel -import com.tangem.utils.converter.Converter - -class QuotesConverter : Converter { - - override fun convert(value: QuoteResponse): QuoteModel { - return QuoteModel( - toTokenAmount = createFromAmountWithOffset(value.toTokenAmount, value.toToken.decimals), - ) - } -} \ No newline at end of file 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 90d0ca8442..7b8944fe0a 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 @@ -197,6 +197,7 @@ internal class SwapInteractorImpl @Inject constructor( networkId = networkId, derivationPath = derivationPath, fromToken = permissionOptions.fromToken, + spenderAddress = permissionOptions.spenderAddress, ) } else { permissionOptions.approveData.approveData @@ -289,8 +290,22 @@ internal class SwapInteractorImpl @Inject constructor( amount: SwapAmount, isBalanceWithoutFeeEnough: Boolean, ): Pair { + val quotes = repository.findBestQuote( + fromContractAddress = fromToken.currency.getContractAddress(), + fromNetwork = fromToken.currency.network.backendId, + toContractAddress = toToken.currency.getContractAddress(), + toNetwork = toToken.currency.network.backendId, + fromAmount = amount.toStringWithRightOffset(), + fromDecimals = amount.decimals, + providerId = provider.providerId, + rateType = RateType.FLOAT, + ) + val fromTokenAddress = getTokenAddress(fromToken.currency) - val isAllowedToSpend = isAllowedToSpend(networkId, fromToken.currency, amount) + val isAllowedToSpend = quotes.dataModel?.allowanceContract?.let { + isAllowedToSpend(networkId, fromToken.currency, amount, it) + } ?: false + if (isAllowedToSpend && allowPermissionsHandler.isAddressAllowanceInProgress(fromTokenAddress)) { allowPermissionsHandler.removeAddressFromProgress(fromTokenAddress) transactionManager.updateWalletManager(networkId, derivationPath) @@ -305,15 +320,16 @@ internal class SwapInteractorImpl @Inject constructor( selectedFee = selectedFee, ) } else { - provider to loadQuoteData( + provider to getQuotesState( exchangeProviderType = ExchangeProviderType.DEX, - networkId = networkId, + quoteDataModel = quotes, amount = amount, - fromTokenStatus = fromToken, - toTokenStatus = toToken, + fromToken = fromToken, + toToken = toToken, + networkId = networkId, isAllowedToSpend = isAllowedToSpend, isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough, - provider = provider, + providerType = provider.type, selectedFee = selectedFee, ) } @@ -501,16 +517,22 @@ internal class SwapInteractorImpl @Inject constructor( } } - private suspend fun isAllowedToSpend(networkId: String, fromToken: CryptoCurrency, amount: SwapAmount): Boolean { + private suspend fun isAllowedToSpend( + networkId: String, + fromToken: CryptoCurrency, + amount: SwapAmount, + spenderAddress: String, + ): Boolean { if (fromToken is CryptoCurrency.Coin) return true return getSelectedWalletSyncUseCase().fold( ifRight = { userWallet -> val allowance = repository.getAllowance( - userWallet.walletId, - networkId, - derivationPath, - getTokenDecimals(fromToken), - getTokenAddress(fromToken), + userWalletId = userWallet.walletId, + networkId = networkId, + derivationPath = derivationPath, + tokenDecimalCount = getTokenDecimals(fromToken), + tokenAddress = getTokenAddress(fromToken), + spenderAddress = spenderAddress, ) allowance >= amount.value }, @@ -620,6 +642,8 @@ internal class SwapInteractorImpl @Inject constructor( fromToken = fromToken.currency, swapAmount = amount, quotesLoadedState = swapState, + isAllowedToSpend = isAllowedToSpend, + spenderAddress = requireNotNull(quoteModel.allowanceContract) { "Allowance contract is null" }, ) state.copy( preparedSwapConfigState = state.preparedSwapConfigState.copy( @@ -791,19 +815,26 @@ internal class SwapInteractorImpl @Inject constructor( }, ifRight = { txFee -> txFee.toTxFeeState(networkId) - } + }, ) ?: TxFeeState.Empty } return TxFeeState.Empty } - @Suppress("LongParameterList") + @Suppress("LongParameterList", "LongMethod") private suspend fun updatePermissionState( networkId: String, fromToken: CryptoCurrency, swapAmount: SwapAmount, quotesLoadedState: SwapState.QuotesLoadedState, + spenderAddress: String, + isAllowedToSpend: Boolean, ): SwapState.QuotesLoadedState { + if (isAllowedToSpend) { + return quotesLoadedState.copy( + permissionState = PermissionDataState.Empty, + ) + } // if token balance ZERO not show permission state to avoid user to spend money for fee val isTokenZeroBalance = getTokenBalance(networkId, fromToken).value.signum() == 0 if (isTokenZeroBalance) { @@ -822,20 +853,28 @@ internal class SwapInteractorImpl @Inject constructor( derivationPath = derivationPath, fromToken = fromToken, swapAmount = swapAmount, + spenderAddress = spenderAddress, ) - val feeData = transactionManager.getFee( - networkId = networkId, - amountToSend = BigDecimal.ZERO, - currencyToSend = swapCurrencyConverter.convert(repository.getNativeTokenForNetwork(networkId)), - destinationAddress = getTokenAddress(fromToken), - increaseBy = INCREASE_GAS_LIMIT_BY, - data = transactionData, - derivationPath = derivationPath, - ) - val feeState = when (feeData) { - is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(networkId) - is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(networkId) + val feeData = try { + transactionManager.getFee( + networkId = networkId, + amountToSend = BigDecimal.ZERO, + currencyToSend = swapCurrencyConverter.convert(repository.getNativeTokenForNetwork(networkId)), + destinationAddress = fromToken.getContractAddress(), + increaseBy = INCREASE_GAS_LIMIT_BY, + data = transactionData, + derivationPath = derivationPath, + ) + } catch (e: Exception) { + Timber.e(e, "Failed to get fee") + null } + val feeState = feeData?.let { + when (feeData) { + is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(networkId) + is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(networkId) + } + } ?: TxFeeState.Empty val fee = when (feeState) { TxFeeState.Empty -> BigDecimal.ZERO is TxFeeState.MultipleFeeState -> feeState.normalFee.feeValue @@ -857,6 +896,7 @@ internal class SwapInteractorImpl @Inject constructor( fee = feeState, approveData = transactionData, fromTokenAmount = swapAmount, + spenderAddress = spenderAddress, ), ), preparedSwapConfigState = quotesLoadedState.preparedSwapConfigState.copy( @@ -1089,6 +1129,7 @@ internal class SwapInteractorImpl @Inject constructor( derivationPath: String?, fromToken: CryptoCurrency, swapAmount: SwapAmount? = null, + spenderAddress: String, ): String { return getSelectedWalletSyncUseCase().fold( ifRight = { userWallet -> @@ -1098,6 +1139,7 @@ internal class SwapInteractorImpl @Inject constructor( derivationPath = derivationPath, currency = fromToken, amount = swapAmount?.value, + spenderAddress = spenderAddress, ) }, ifLeft = { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index dd2d72e7df..13650fe4fe 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -39,6 +39,7 @@ interface SwapRepository { */ fun getTangemFee(): Double + @Suppress("LongParameterList") @Throws(IllegalStateException::class) suspend fun getAllowance( userWalletId: UserWalletId, @@ -46,8 +47,10 @@ interface SwapRepository { derivationPath: String?, tokenDecimalCount: Int, tokenAddress: String, + spenderAddress: String, ): BigDecimal + @Suppress("LongParameterList") @Throws(IllegalStateException::class) suspend fun getApproveData( userWalletId: UserWalletId, @@ -55,6 +58,7 @@ interface SwapRepository { derivationPath: String?, currency: CryptoCurrency, amount: BigDecimal?, + spenderAddress: String, ): String @Suppress("LongParameterList") diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PermissionOptions.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PermissionOptions.kt index 88d57a301f..bd1d290190 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PermissionOptions.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PermissionOptions.kt @@ -17,6 +17,7 @@ data class PermissionOptions( val approveData: RequestApproveStateData, val forTokenContractAddress: String, val fromToken: CryptoCurrency, + val spenderAddress: String, val approveType: SwapApproveType, val txFee: TxFee, ) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/QuoteModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/QuoteModel.kt index 5e5d782666..df74da7af3 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/QuoteModel.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/QuoteModel.kt @@ -9,4 +9,5 @@ import com.tangem.feature.swap.domain.models.SwapAmount */ data class QuoteModel( val toTokenAmount: SwapAmount, + val allowanceContract: String?, ) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt index 78ccdccca5..0c98986dc1 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt @@ -61,6 +61,7 @@ data class RequestApproveStateData( val fee: TxFeeState, val approveData: String, val fromTokenAmount: SwapAmount, + val spenderAddress: String, ) // data class SwapStateData( diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index a685079175..2335991b5e 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -36,7 +36,6 @@ data class SwapStateHolder( val onSuccess: (() -> Unit)? = null, val onMaxAmountSelected: (() -> Unit)? = null, val onShowPermissionBottomSheet: () -> Unit = {}, - val onCancelPermissionBottomSheet: () -> Unit = {}, ) sealed class SwapCardState { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt index 76efb65203..76844c8cd2 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt @@ -13,7 +13,6 @@ data class UiActions( val onBackClicked: () -> Unit, val onMaxAmountSelected: () -> Unit, val openPermissionBottomSheet: () -> Unit, - val hidePermissionBottomSheet: () -> Unit, val onChangeApproveType: (ApproveType) -> Unit, // region new actions val onClickFee: () -> Unit, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/GivePermissionBottomSheetConfig.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/GivePermissionBottomSheetConfig.kt index 57628ded51..9a8bd7a519 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/GivePermissionBottomSheetConfig.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/GivePermissionBottomSheetConfig.kt @@ -3,7 +3,7 @@ package com.tangem.feature.swap.models.states import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.feature.swap.models.SwapPermissionState -class GivePermissionBottomSheetConfig( +data class GivePermissionBottomSheetConfig( val data: SwapPermissionState.ReadyForRequest, val onCancel: () -> Unit, ) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index b6a1ec961e..6a730aef6e 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -81,7 +81,6 @@ internal class StateBuilder( onMaxAmountSelected = actions.onMaxAmountSelected, updateInProgress = true, onShowPermissionBottomSheet = actions.openPermissionBottomSheet, - onCancelPermissionBottomSheet = actions.hidePermissionBottomSheet, providerState = ProviderState.Empty(), ) } @@ -381,10 +380,13 @@ internal class StateBuilder( } fun updateApproveType(uiState: SwapStateHolder, approveType: ApproveType): SwapStateHolder { - return if (uiState.permissionState is SwapPermissionState.ReadyForRequest) { + val config = uiState.bottomSheetConfig?.content as? GivePermissionBottomSheetConfig + return if (config != null) { uiState.copy( - permissionState = uiState.permissionState.copy( - approveType = approveType, + bottomSheetConfig = uiState.bottomSheetConfig.copy( + content = config.copy( + data = config.data.copy(approveType = approveType), + ), ), ) } else { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt index 191d36f5de..7b5bd446fa 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt @@ -7,6 +7,7 @@ import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData import com.tangem.feature.swap.domain.models.ui.SwapState import com.tangem.feature.swap.domain.models.ui.TokensDataStateExpress import com.tangem.feature.swap.domain.models.ui.TxFee +import com.tangem.feature.swap.models.ApproveType data class SwapProcessDataState( // Initial network id @@ -16,6 +17,7 @@ data class SwapProcessDataState( // Amount from input val amount: String? = null, val approveDataModel: RequestApproveStateData? = null, + val approveType: ApproveType? = null, val swapDataModel: SwapDataModel? = null, val selectedFee: TxFee? = null, // todo val tokensDataState: TokensDataStateExpress? = null, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index cfd88bd3c6..901ef4daa7 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -22,10 +22,7 @@ import com.tangem.feature.swap.domain.models.domain.SwapDataModel import com.tangem.feature.swap.domain.models.domain.SwapProvider import com.tangem.feature.swap.domain.models.formatToUIRepresentation import com.tangem.feature.swap.domain.models.ui.* -import com.tangem.feature.swap.models.SwapPermissionState -import com.tangem.feature.swap.models.SwapStateHolder -import com.tangem.feature.swap.models.UiActions -import com.tangem.feature.swap.models.toDomainApproveType +import com.tangem.feature.swap.models.* import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.feature.swap.router.SwapNavScreen import com.tangem.feature.swap.router.SwapRouter @@ -320,6 +317,7 @@ internal class SwapViewModel @Inject constructor( dataState = if (permissionState is PermissionDataState.PermissionReadyForRequest) { dataState.copy( approveDataModel = permissionState.requestApproveData, + approveType = dataState.approveType ?: ApproveType.UNLIMITED, ) } else { dataState.copy( @@ -407,12 +405,15 @@ internal class SwapViewModel @Inject constructor( fromToken = requireNotNull(dataState.fromCryptoCurrency?.currency) { "dataState.fromCurrency might not be null" }, - approveType = requireNotNull(uiState.permissionState as? SwapPermissionState.ReadyForRequest) { - "uiState.permissionState should be SwapPermissionState.ReadyForRequest" - }.approveType.toDomainApproveType(), + approveType = requireNotNull(dataState.approveType) { + "uiState.permissionState should not be null" + }.toDomainApproveType(), txFee = requireNotNull(dataState.selectedFee) { "dataState.selectedFee shouldn't be null" }, + spenderAddress = requireNotNull(dataState.approveDataModel?.spenderAddress) { + "dataState.approveDataModel.spenderAddress shouldn't be null" + }, ), ) } @@ -604,15 +605,16 @@ internal class SwapViewModel @Inject constructor( openPermissionBottomSheet = { singleTaskScheduler.cancelTask() analyticsEventHandler.send(SwapEvents.ButtonGivePermissionClicked) - uiState = stateBuilder.showPermissionBottomSheet(uiState) { stateBuilder.dismissBottomSheet(uiState) } - }, - hidePermissionBottomSheet = { - startLoadingQuotesFromLastState() - analyticsEventHandler.send(SwapEvents.ButtonPermissionCancelClicked) + uiState = stateBuilder.showPermissionBottomSheet(uiState) { + startLoadingQuotesFromLastState() + analyticsEventHandler.send(SwapEvents.ButtonPermissionCancelClicked) + stateBuilder.dismissBottomSheet(uiState) + } }, onAmountSelected = { onAmountSelected(it) }, onChangeApproveType = { approveType -> uiState = stateBuilder.updateApproveType(uiState, approveType) + dataState = dataState.copy(approveType = approveType) }, onClickFee = { val selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL