From eb0b9ca69c41099a8c535b78fdd545d59c0c8145 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 20 Dec 2023 11:54:55 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../swap/domain/models/ui/SwapState.kt | 1 + .../feature/swap/domain/SwapInteractorImpl.kt | 51 ++++++++++++------- .../tangem/feature/swap/ui/StateBuilder.kt | 15 ++++-- .../feature/swap/viewmodels/SwapViewModel.kt | 1 + 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt index 97c7c85fb3..6711df346c 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt @@ -37,6 +37,7 @@ sealed interface SwapState { data class SwapError( val fromTokenInfo: TokenSwapInfo, val error: DataError, + val includeFeeInAmount: IncludeFeeInAmount, ) : SwapState } 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 2fd8561359..038f23b435 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 @@ -37,6 +37,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.firstOrNull import timber.log.Timber import java.math.BigDecimal +import java.math.BigInteger import java.math.RoundingMode import javax.inject.Inject @@ -263,7 +264,6 @@ internal class SwapInteractorImpl @Inject constructor( provider = provider, amount = amount, isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough, - selectedFee = selectedFee, ) } } @@ -340,7 +340,6 @@ internal class SwapInteractorImpl @Inject constructor( provider: SwapProvider, amount: SwapAmount, isBalanceWithoutFeeEnough: Boolean, - selectedFee: FeeType, ): Pair { return provider to loadCexQuoteData( exchangeProviderType = ExchangeProviderType.CEX, @@ -351,7 +350,6 @@ internal class SwapInteractorImpl @Inject constructor( isAllowedToSpend = true, isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough, provider = provider, - selectedFee = selectedFee, ) } @@ -411,7 +409,6 @@ internal class SwapInteractorImpl @Inject constructor( txFee = state.txFee, amount = amount, fromToken = fromToken.currency, - selectedFee = selectedFee, ) return state.copy( permissionState = PermissionDataState.Empty, @@ -580,10 +577,11 @@ internal class SwapInteractorImpl @Inject constructor( ) return if (fee.gasLimit != 0) { + val feeAmountWithDecimals = feeAmountValue.movePointRight(fee.decimals) Fee.Ethereum( amount = feeAmount, gasLimit = fee.gasLimit.toBigInteger(), - gasPrice = (feeAmountValue / fee.gasLimit.toBigDecimal()).toBigInteger(), + gasPrice = (feeAmountWithDecimals / fee.gasLimit.toBigDecimal()).toBigInteger(), ) } else { Fee.Common(feeAmount) @@ -709,7 +707,6 @@ internal class SwapInteractorImpl @Inject constructor( provider: SwapProvider, isAllowedToSpend: Boolean, isBalanceWithoutFeeEnough: Boolean, - selectedFee: FeeType, ): SwapState { val fromToken = fromTokenStatus.currency val toToken = toTokenStatus.currency @@ -725,7 +722,6 @@ internal class SwapInteractorImpl @Inject constructor( txFee = txFee, amount = amount, fromToken = fromToken, - selectedFee = selectedFee, ) val amountToRequest = if (includeFeeInAmount is IncludeFeeInAmount.Included) { includeFeeInAmount.amountSubtractFee @@ -824,7 +820,7 @@ internal class SwapInteractorImpl @Inject constructor( ?: BigDecimal.ZERO, cryptoCurrencyStatus = fromToken, ) - return SwapState.SwapError(fromTokenSwapInfo, error) + return SwapState.SwapError(fromTokenSwapInfo, error, includeFeeInAmount) }, ) } @@ -834,7 +830,6 @@ internal class SwapInteractorImpl @Inject constructor( txFee: TxFeeState, amount: SwapAmount, fromToken: CryptoCurrency, - selectedFee: FeeType, ): IncludeFeeInAmount { if (fromToken is CryptoCurrency.Token) { return IncludeFeeInAmount.Excluded @@ -851,11 +846,7 @@ internal class SwapInteractorImpl @Inject constructor( } val feeValue = when (txFee) { TxFeeState.Empty -> BigDecimal.ZERO - is TxFeeState.MultipleFeeState -> if (selectedFee == FeeType.NORMAL) { - txFee.normalFee.feeValue - } else { - txFee.priorityFee.feeValue - } + is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue is TxFeeState.SingleFeeState -> txFee.fee.feeValue } @@ -965,6 +956,7 @@ internal class SwapInteractorImpl @Inject constructor( SwapState.SwapError( fromTokenSwapInfo, error, + IncludeFeeInAmount.Excluded, ) }, ) @@ -1191,8 +1183,10 @@ internal class SwapInteractorImpl @Inject constructor( val decimals = transactionManager.getNativeTokenDecimals(networkId) return when (this) { is TransactionFee.Choosable -> { - val feeNormal = this.normal.amount.value ?: BigDecimal.ZERO - val feePriority = this.priority.amount.value ?: BigDecimal.ZERO + val normalFee = this.normal.increaseGasLimitBy(INCREASE_GAS_LIMIT_FOR_SEND) + val priorityFee = this.priority.increaseGasLimitBy(INCREASE_GAS_LIMIT_FOR_SEND) + val feeNormal = normalFee.amount.value ?: BigDecimal.ZERO + val feePriority = priorityFee.amount.value ?: BigDecimal.ZERO val normalFiatValue = getFormattedFiatFees(networkId, feeNormal)[0] val priorityFiatValue = getFormattedFiatFees(networkId, feePriority)[0] @@ -1207,7 +1201,7 @@ internal class SwapInteractorImpl @Inject constructor( TxFeeState.MultipleFeeState( normalFee = TxFee( feeValue = feeNormal, - gasLimit = this.normal.getGasLimit(), + gasLimit = normalFee.getGasLimit(), feeFiatFormatted = normalFiatValue, feeCryptoFormatted = normalCryptoFee, decimals = decimals, @@ -1216,7 +1210,7 @@ internal class SwapInteractorImpl @Inject constructor( ), priorityFee = TxFee( feeValue = feePriority, - gasLimit = this.priority.getGasLimit(), + gasLimit = priorityFee.getGasLimit(), feeFiatFormatted = priorityFiatValue, feeCryptoFormatted = priorityCryptoFee, decimals = decimals, @@ -1247,6 +1241,26 @@ internal class SwapInteractorImpl @Inject constructor( } } + /** + * Workaround to increase gas limit cause we calculate fee for random address + */ + private fun Fee.increaseGasLimitBy(percentage: Int): Fee { + if (this !is Fee.Ethereum) return this + val gasLimit = this.gasLimit + val increasedGasPrice = this.amount.value?.movePointRight(this.amount.decimals) + ?.divide(gasLimit.toBigDecimal(), RoundingMode.HALF_UP) + val increaseGasLimit = gasLimit + .multiply(percentage.toBigInteger()) + .divide(BigInteger("100")) + val increasedAmount = this.amount.copy( + value = increaseGasLimit.toBigDecimal().multiply(increasedGasPrice).movePointLeft(this.amount.decimals), + ) + return this.copy( + amount = increasedAmount, + gasLimit = increaseGasLimit, + ) + } + private fun hasOutgoingTransaction(cryptoCurrencyStatuses: CryptoCurrencyStatus): Boolean { return cryptoCurrencyStatuses.value.pendingTransactions.any { it.isOutgoing } } @@ -1377,6 +1391,7 @@ internal class SwapInteractorImpl @Inject constructor( companion object { @Suppress("UnusedPrivateMember") private const val INCREASE_GAS_LIMIT_BY = 112 // 12% + private const val INCREASE_GAS_LIMIT_FOR_SEND = 105 // 5% private const val INFINITY_SYMBOL = "∞" private val ONE_INCH_SUPPORTED_NETWORKS = listOf( 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 c74bd436f7..4e93debef0 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 @@ -394,12 +394,21 @@ internal class StateBuilder( swapProvider: SwapProvider, fromToken: TokenSwapInfo, toToken: CryptoCurrencyStatus?, + includeFeeInAmount: IncludeFeeInAmount, dataError: DataError, isReverseSwapPossible: Boolean, ): SwapStateHolder { if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder - val warning = getWarningForError(dataError, fromToken.cryptoCurrencyStatus.currency) + val warnings = mutableListOf() + warnings.add(getWarningForError(dataError, fromToken.cryptoCurrencyStatus.currency)) + if (includeFeeInAmount is IncludeFeeInAmount.Included) { + warnings.add( + SwapWarning.GeneralWarning( + createNetworkFeeCoverageNotificationConfig(), + ), + ) + } val providerState = getProviderStateForError( swapProvider = swapProvider, fromToken = fromToken.cryptoCurrencyStatus.currency, @@ -437,7 +446,7 @@ internal class StateBuilder( amountEquivalent = getFormattedFiatAmount(fromToken.amountFiat), ), receiveCardData = receiveCardData, - warnings = listOf(warning), + warnings = warnings, permissionState = SwapPermissionState.Empty, fee = FeeItemState.Empty, swapButton = SwapButton( @@ -1221,7 +1230,7 @@ internal class StateBuilder( } private fun SwapAmount.getFormattedCryptoAmount(token: CryptoCurrency): String { - return "${this.formatToUIRepresentation()} ${token.network.currencySymbol}" + return "${this.formatToUIRepresentation()} ${token.symbol}" } private fun BigDecimal.calculateRate(to: BigDecimal, decimals: Int): BigDecimal { 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 fe3d608266..1d9fbafcbc 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 @@ -344,6 +344,7 @@ internal class SwapViewModel @Inject constructor( fromToken = state.fromTokenInfo, toToken = dataState.toCryptoCurrency, dataError = state.error, + includeFeeInAmount = state.includeFeeInAmount, isReverseSwapPossible = isReverseSwapPossible(), ) sendErrorAnalyticsEvent(state.error, provider)