Updated on 2026-08-14
This commit is contained in:
parent
fa478206b5
commit
a0cc4794f8
2 changed files with 79 additions and 5 deletions
|
|
@ -1581,6 +1581,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
||||||
feeValue = nativeFee,
|
feeValue = nativeFee,
|
||||||
selectedFeeToken = fee.selectedFeeToken,
|
selectedFeeToken = fee.selectedFeeToken,
|
||||||
provider = state.swapProvider,
|
provider = state.swapProvider,
|
||||||
|
txType = state.txType,
|
||||||
)
|
)
|
||||||
val currencyCheck = manageWarnings(
|
val currencyCheck = manageWarnings(
|
||||||
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
|
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
|
||||||
|
|
@ -1621,6 +1622,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
||||||
* from-currencies but "fee > native balance" for Token from-currencies is resolved here
|
* from-currencies but "fee > native balance" for Token from-currencies is resolved here
|
||||||
* by consulting `isBalanceEnough` (amount-alone check) directly.
|
* by consulting `isBalanceEnough` (amount-alone check) directly.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("LongParameterList")
|
||||||
private suspend fun computeBalanceStatus(
|
private suspend fun computeBalanceStatus(
|
||||||
fromSwapCurrencyStatus: SwapCurrencyStatus,
|
fromSwapCurrencyStatus: SwapCurrencyStatus,
|
||||||
amount: SwapAmount,
|
amount: SwapAmount,
|
||||||
|
|
@ -1628,9 +1630,10 @@ internal class SwapInteractorImpl @Inject constructor(
|
||||||
feeValue: BigDecimal,
|
feeValue: BigDecimal,
|
||||||
selectedFeeToken: CryptoCurrencyStatus?,
|
selectedFeeToken: CryptoCurrencyStatus?,
|
||||||
provider: SwapProvider,
|
provider: SwapProvider,
|
||||||
|
txType: ExpressTxType?,
|
||||||
): SwapBalanceStatus {
|
): SwapBalanceStatus {
|
||||||
when (provider.type) {
|
when (resolveQuoteFlow(provider, txType)) {
|
||||||
ExchangeProviderType.CEX -> {
|
ResolvedFlow.CexLike -> {
|
||||||
val includeStatus = getIncludeFeeInAmountInternal(
|
val includeStatus = getIncludeFeeInAmountInternal(
|
||||||
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
|
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
|
||||||
amount = amount,
|
amount = amount,
|
||||||
|
|
@ -1642,9 +1645,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
||||||
return SwapBalanceStatus.FeeAdjustedAmount(adjustedAmount = includeStatus.amountSubtractFee)
|
return SwapBalanceStatus.FeeAdjustedAmount(adjustedAmount = includeStatus.amountSubtractFee)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExchangeProviderType.DEX,
|
ResolvedFlow.DexLike -> Unit
|
||||||
ExchangeProviderType.DEX_BRIDGE,
|
|
||||||
-> Unit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val isAmountAlone = isBalanceEnough(fromSwapCurrencyStatus, amount, fee = feeValue)
|
val isAmountAlone = isBalanceEnough(fromSwapCurrencyStatus, amount, fee = feeValue)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||||
import com.tangem.feature.swap.domain.fee.TransactionFeeResult
|
import com.tangem.feature.swap.domain.fee.TransactionFeeResult
|
||||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||||
import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType
|
import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType
|
||||||
|
import com.tangem.feature.swap.domain.models.domain.ExpressTxType
|
||||||
import com.tangem.feature.swap.domain.models.domain.PreparedSwapConfigState
|
import com.tangem.feature.swap.domain.models.domain.PreparedSwapConfigState
|
||||||
import com.tangem.feature.swap.domain.models.domain.SwapBalanceStatus
|
import com.tangem.feature.swap.domain.models.domain.SwapBalanceStatus
|
||||||
import com.tangem.feature.swap.domain.models.ui.*
|
import com.tangem.feature.swap.domain.models.ui.*
|
||||||
|
|
@ -214,6 +215,76 @@ internal class SwapInteractorImplApplySwapFeeMatrixTest : SwapInteractorImplTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Section A2: DEX provider re-routed to the CEX-like flow via txType=SEND ([REDACTED_TASK_KEY])
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class `DEX provider with SEND txType follows CEX semantics` {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [REDACTED_TASK_KEY]: a DEX-typed provider (e.g. Moonpay trade) whose quote returned txType=SEND
|
||||||
|
* executes as a plain transfer built by the app, so the fee must be folded into the amount
|
||||||
|
* exactly like for a CEX provider.
|
||||||
|
*
|
||||||
|
* GIVEN ExchangeProviderType.DEX, txType = SEND
|
||||||
|
* fromToken is Coin, amount = full native balance (max amount), fee = 0.01
|
||||||
|
* WHEN applySwapFee runs
|
||||||
|
* THEN balanceStatus == FeeAdjustedAmount with adjustedAmount = balance - fee
|
||||||
|
* (NOT InsufficientAmount — the pre-fix behavior that showed "Insufficient funds")
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun `applySwapFee DEX with SEND txType — max amount returns FeeAdjustedAmount like CEX`() = runTest {
|
||||||
|
coEvery { currenciesRepository.getFeePaidCurrency(any(), any()) } returns FeePaidCurrency.Coin
|
||||||
|
coEvery {
|
||||||
|
walletManagersFacade.getNativeTokenBalance(any(), any(), any())
|
||||||
|
} returns BigDecimal("1.0")
|
||||||
|
|
||||||
|
val state = buildQuotesLoadedState(
|
||||||
|
providerType = ExchangeProviderType.DEX,
|
||||||
|
fromAmount = SwapAmount(BigDecimal("1.0"), 18),
|
||||||
|
isCoin = true,
|
||||||
|
fromBalance = BigDecimal("1.0"),
|
||||||
|
txType = ExpressTxType.SEND,
|
||||||
|
)
|
||||||
|
val fee = buildSwapFeeWithCoinToken(feeValue = BigDecimal("0.01"))
|
||||||
|
|
||||||
|
val result = sut.applySwapFee(state, fee, lastReducedBalanceBy)
|
||||||
|
|
||||||
|
val balanceStatus = result.preparedSwapConfigState.balanceStatus
|
||||||
|
assertThat(balanceStatus).isInstanceOf(SwapBalanceStatus.FeeAdjustedAmount::class.java)
|
||||||
|
assertThat((balanceStatus as SwapBalanceStatus.FeeAdjustedAmount).adjustedAmount.value)
|
||||||
|
.isEqualTo(BigDecimal("0.99"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Twin guard: the same max-amount scenario with txType = SWAP keeps the DEX invariant —
|
||||||
|
* the fee is never deducted from the amount, and the amount alone exceeding
|
||||||
|
* balance-with-fee yields InsufficientAmount.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun `applySwapFee DEX with SWAP txType — max amount keeps DEX semantics without fee deduction`() = runTest {
|
||||||
|
coEvery { currenciesRepository.getFeePaidCurrency(any(), any()) } returns FeePaidCurrency.Coin
|
||||||
|
coEvery {
|
||||||
|
walletManagersFacade.getNativeTokenBalance(any(), any(), any())
|
||||||
|
} returns BigDecimal("1.0")
|
||||||
|
|
||||||
|
val state = buildQuotesLoadedState(
|
||||||
|
providerType = ExchangeProviderType.DEX,
|
||||||
|
fromAmount = SwapAmount(BigDecimal("1.0"), 18),
|
||||||
|
isCoin = true,
|
||||||
|
fromBalance = BigDecimal("1.0"),
|
||||||
|
txType = ExpressTxType.SWAP,
|
||||||
|
)
|
||||||
|
val fee = buildSwapFeeWithCoinToken(feeValue = BigDecimal("0.01"))
|
||||||
|
|
||||||
|
val result = sut.applySwapFee(state, fee, lastReducedBalanceBy)
|
||||||
|
|
||||||
|
assertThat(result.preparedSwapConfigState.balanceStatus)
|
||||||
|
.isInstanceOf(SwapBalanceStatus.InsufficientAmount::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Section B: FeePaidCurrency.Token (gasless-token) paths
|
// Section B: FeePaidCurrency.Token (gasless-token) paths
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
@ -749,6 +820,7 @@ internal class SwapInteractorImplApplySwapFeeMatrixTest : SwapInteractorImplTest
|
||||||
fromAmount: SwapAmount,
|
fromAmount: SwapAmount,
|
||||||
isCoin: Boolean,
|
isCoin: Boolean,
|
||||||
fromBalance: BigDecimal,
|
fromBalance: BigDecimal,
|
||||||
|
txType: ExpressTxType? = null,
|
||||||
): SwapState.QuotesLoadedState {
|
): SwapState.QuotesLoadedState {
|
||||||
val from = buildSwapCurrencyStatus(
|
val from = buildSwapCurrencyStatus(
|
||||||
networkRawId = ethNetwork,
|
networkRawId = ethNetwork,
|
||||||
|
|
@ -778,6 +850,7 @@ internal class SwapInteractorImplApplySwapFeeMatrixTest : SwapInteractorImplTest
|
||||||
validationResult = null,
|
validationResult = null,
|
||||||
minAdaValue = null,
|
minAdaValue = null,
|
||||||
swapProvider = buildSwapProvider(providerType),
|
swapProvider = buildSwapProvider(providerType),
|
||||||
|
txType = txType,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue