Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-20 11:54:55 +03:00
parent ef2cb2ce43
commit eb0b9ca69c
4 changed files with 47 additions and 21 deletions

View file

@ -37,6 +37,7 @@ sealed interface SwapState {
data class SwapError(
val fromTokenInfo: TokenSwapInfo,
val error: DataError,
val includeFeeInAmount: IncludeFeeInAmount,
) : SwapState
}

View file

@ -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<SwapProvider, SwapState> {
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(

View file

@ -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<SwapWarning>()
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 {

View file

@ -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)