From 0fb88d97c96cfee11b6b60b50d25611ed1d3198b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Jun 2026 21:57:56 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../feature/swap/domain/SwapInteractor.kt | 1 + .../feature/swap/domain/SwapInteractorImpl.kt | 4 + .../swap/domain/fee/CexSwapFeeCalculator.kt | 67 ++++++++++------- .../SwapInteractorImplLoadSwapFeeTest.kt | 73 ++++++++++++------- .../domain/fee/CexSwapFeeCalculatorTest.kt | 16 ++-- .../tangem/feature/swap/model/SwapModel.kt | 4 +- 6 files changed, 103 insertions(+), 62 deletions(-) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index 7f71623656..01cd972d61 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -129,5 +129,6 @@ interface SwapInteractor { amount: SwapAmount, swapData: SwapDataModel?, selectedFeeToken: CryptoCurrencyStatus?, + isGasless: Boolean, ): Either } \ 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 f902272fa7..93eaea8fda 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 @@ -966,6 +966,7 @@ internal class SwapInteractorImpl @Inject constructor( amount: SwapAmount, swapData: SwapDataModel?, selectedFeeToken: CryptoCurrencyStatus?, + isGasless: Boolean, ): Either = either { if (amount.value.signum() == 0) { raise(GetFeeError.UnknownError) @@ -982,6 +983,7 @@ internal class SwapInteractorImpl @Inject constructor( fromStatus = fromStatus, amount = amount, selectedFeeToken = selectedFeeToken, + isGasless = isGasless, ) } } @@ -1031,12 +1033,14 @@ internal class SwapInteractorImpl @Inject constructor( fromStatus: SwapCurrencyStatus, amount: SwapAmount, selectedFeeToken: CryptoCurrencyStatus?, + isGasless: Boolean, ): Either { return cexSwapFeeCalculator.calculate( userWallet = fromStatus.userWallet, fromSwapCurrencyStatus = fromStatus, amount = amount.value, selectedFeeToken = selectedFeeToken, + isGasless = isGasless, ).fold( ifLeft = { it.left() }, ifRight = { cexFeeResult -> diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/fee/CexSwapFeeCalculator.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/fee/CexSwapFeeCalculator.kt index a352fd73a7..3bf712d490 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/fee/CexSwapFeeCalculator.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/fee/CexSwapFeeCalculator.kt @@ -45,40 +45,51 @@ class CexSwapFeeCalculator( fromSwapCurrencyStatus: SwapCurrencyStatus, amount: BigDecimal, selectedFeeToken: CryptoCurrencyStatus?, + isGasless: Boolean, ): Either = either { if (amount.signum() == 0) { raise(GetFeeError.UnknownError) } - val transactionFeeResult: TransactionFeeResult = when { - selectedFeeToken == null -> { - // Gasless path — overload 1 in SwapInteractorImpl. No gas-limit bump. - val feeExtended = estimateFeeForGaslessTxUseCase( - amount = amount, - userWallet = userWallet, - sendingTokenCurrencyStatus = fromSwapCurrencyStatus.status, - ).bind() - TransactionFeeResult.LoadedExtended(feeExtended) - } - selectedFeeToken.currency is CryptoCurrency.Token -> { - // Explicit gasless-token path — overload 1 in SwapInteractorImpl. No gas-limit bump. - val feeExtended = estimateFeeForTokenUseCase( - userWallet = userWallet, - feeTokenCurrencyStatus = selectedFeeToken, - sendingTokenCurrencyStatus = fromSwapCurrencyStatus.status, - amount = amount, - ).bind() - TransactionFeeResult.LoadedExtended(feeExtended) - } - else -> { - // Explicit native fee path — overload 2 in SwapInteractorImpl. Apply 5% bump. - val fee = estimateFeeUseCase( - amount = amount, - userWallet = userWallet, - cryptoCurrencyStatus = fromSwapCurrencyStatus.status, - ).bind() - TransactionFeeResult.Loaded(patchEthGasLimitForSwap(fee)) + val transactionFeeResult: TransactionFeeResult = if (isGasless) { + when { + selectedFeeToken == null -> { + // Gasless path — overload 1 in SwapInteractorImpl. No gas-limit bump. + val feeExtended = estimateFeeForGaslessTxUseCase( + amount = amount, + userWallet = userWallet, + sendingTokenCurrencyStatus = fromSwapCurrencyStatus.status, + ).bind() + TransactionFeeResult.LoadedExtended(feeExtended) + } + selectedFeeToken.currency is CryptoCurrency.Token -> { + // Explicit gasless-token path — overload 1 in SwapInteractorImpl. No gas-limit bump. + val feeExtended = estimateFeeForTokenUseCase( + userWallet = userWallet, + feeTokenCurrencyStatus = selectedFeeToken, + sendingTokenCurrencyStatus = fromSwapCurrencyStatus.status, + amount = amount, + ).bind() + TransactionFeeResult.LoadedExtended(feeExtended) + } + else -> { + // Explicit native fee path — overload 2 in SwapInteractorImpl. Apply 5% bump. + val fee = estimateFeeUseCase( + amount = amount, + userWallet = userWallet, + cryptoCurrencyStatus = fromSwapCurrencyStatus.status, + ).bind() + TransactionFeeResult.Loaded(patchEthGasLimitForSwap(fee)) + } } + } else { + // Explicit native fee path — overload 2 in SwapInteractorImpl. Apply 5% bump. + val fee = estimateFeeUseCase( + amount = amount, + userWallet = userWallet, + cryptoCurrencyStatus = fromSwapCurrencyStatus.status, + ).bind() + TransactionFeeResult.Loaded(patchEthGasLimitForSwap(fee)) } CexFeeResult(transactionFee = transactionFeeResult) diff --git a/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplLoadSwapFeeTest.kt b/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplLoadSwapFeeTest.kt index cf265e96d2..e705f7c63e 100644 --- a/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplLoadSwapFeeTest.kt +++ b/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplLoadSwapFeeTest.kt @@ -95,6 +95,7 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() amount = SwapAmount(BigDecimal.ONE, 18), swapData = swapData, selectedFeeToken = null, + isGasless = false, ) assertThat(result.isRight()).isTrue() @@ -138,6 +139,7 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() amount = SwapAmount(BigDecimal.ONE, 9), swapData = swapData, selectedFeeToken = null, + isGasless = false, ) assertThat(result.isRight()).isTrue() @@ -173,6 +175,7 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() amount = SwapAmount(BigDecimal.ONE, 18), swapData = swapData, selectedFeeToken = null, + isGasless = false, ) assertThat(result.isRight()).isTrue() @@ -193,6 +196,7 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() amount = SwapAmount(BigDecimal.ONE, 18), swapData = null, selectedFeeToken = null, + isGasless = false, ) assertThat(result.isLeft()).isTrue() @@ -214,7 +218,9 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() amount = SwapAmount(BigDecimal.ONE, 18), swapData = null, selectedFeeToken = null, - ) + isGasless = false, + + ) assertThat(result.isLeft()).isTrue() result.onLeft { error -> @@ -240,8 +246,9 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() toStatus = toStatus, amount = SwapAmount(BigDecimal.ONE, 18), swapData = swapData, - selectedFeeToken = null, - ) + selectedFeeToken = null, isGasless = false, + + ) assertThat(result.isLeft()).isTrue() result.onLeft { error -> @@ -265,7 +272,7 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() ) } coEvery { - cexSwapFeeCalculator.calculate(any(), any(), any(), any()) + cexSwapFeeCalculator.calculate(any(), any(), any(), any(), any()) } returns CexFeeResult( transactionFee = TransactionFeeResult.LoadedExtended(extendedFee), ).right() @@ -277,6 +284,7 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() amount = SwapAmount(BigDecimal.ONE, 18), swapData = null, selectedFeeToken = null, + isGasless = true, ) assertThat(result.isRight()).isTrue() @@ -291,7 +299,9 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() fromSwapCurrencyStatus = fromStatus, amount = BigDecimal.ONE, selectedFeeToken = null, - ) + isGasless = true, + + ) } } @@ -307,7 +317,7 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() val toStatus = buildSwapCurrencyStatus(networkRawId = ethNetwork, isCoin = true) val extendedFee = mockk(relaxed = true) coEvery { - cexSwapFeeCalculator.calculate(any(), any(), any(), any()) + cexSwapFeeCalculator.calculate(any(), any(), any(), any(), any()) } returns CexFeeResult( transactionFee = TransactionFeeResult.LoadedExtended(extendedFee), ).right() @@ -318,8 +328,9 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() toStatus = toStatus, amount = SwapAmount(BigDecimal.ONE, 18), swapData = null, - selectedFeeToken = null, - ) + selectedFeeToken = null, isGasless = true, + + ) assertThat(result.isRight()).isTrue() result.onRight { swapFee -> @@ -337,7 +348,7 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() } val extendedFee = mockk(relaxed = true) coEvery { - cexSwapFeeCalculator.calculate(any(), any(), any(), any()) + cexSwapFeeCalculator.calculate(any(), any(), any(), any(), any()) } returns CexFeeResult( transactionFee = TransactionFeeResult.LoadedExtended(extendedFee), ).right() @@ -348,8 +359,9 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() toStatus = toStatus, amount = SwapAmount(BigDecimal.ONE, 18), swapData = null, - selectedFeeToken = explicitTokenStatus, - ) + selectedFeeToken = explicitTokenStatus, isGasless = true, + + ) assertThat(result.isRight()).isTrue() result.onRight { swapFee -> @@ -360,8 +372,9 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() userWallet = fromStatus.userWallet, fromSwapCurrencyStatus = fromStatus, amount = BigDecimal.ONE, - selectedFeeToken = explicitTokenStatus, - ) + selectedFeeToken = explicitTokenStatus, isGasless = true, + + ) } } @@ -374,7 +387,7 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() } val rawFee = TransactionFee.Single(normal = mockk(relaxed = true)) coEvery { - cexSwapFeeCalculator.calculate(any(), any(), any(), any()) + cexSwapFeeCalculator.calculate(any(), any(), any(), any(), any()) } returns CexFeeResult( transactionFee = TransactionFeeResult.Loaded(rawFee), ).right() @@ -385,8 +398,9 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() toStatus = toStatus, amount = SwapAmount(BigDecimal.ONE, 18), swapData = null, - selectedFeeToken = explicitNativeStatus, - ) + selectedFeeToken = explicitNativeStatus, isGasless = true, + + ) assertThat(result.isRight()).isTrue() result.onRight { swapFee -> @@ -400,7 +414,7 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() val fromStatus = buildSwapCurrencyStatus(networkRawId = ethNetwork, isCoin = true) val toStatus = buildSwapCurrencyStatus(networkRawId = ethNetwork, isCoin = true) coEvery { - cexSwapFeeCalculator.calculate(any(), any(), any(), any()) + cexSwapFeeCalculator.calculate(any(), any(), any(), any(), any()) } returns GetFeeError.UnknownError.left() val result = sut.loadSwapFee( @@ -409,8 +423,9 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() toStatus = toStatus, amount = SwapAmount(BigDecimal.ONE, 18), swapData = null, - selectedFeeToken = null, - ) + selectedFeeToken = null, isGasless = true, + + ) assertThat(result.isLeft()).isTrue() result.onLeft { error -> @@ -433,14 +448,15 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() toStatus = toStatus, amount = SwapAmount(BigDecimal.ZERO, 18), swapData = null, - selectedFeeToken = null, - ) + selectedFeeToken = null, isGasless = true, + + ) assertThat(result.isLeft()).isTrue() result.onLeft { error -> assertThat(error).isInstanceOf(GetFeeError.UnknownError::class.java) } - coVerify(exactly = 0) { cexSwapFeeCalculator.calculate(any(), any(), any(), any()) } + coVerify(exactly = 0) { cexSwapFeeCalculator.calculate(any(), any(), any(), any(), any()) } } @Test @@ -459,7 +475,9 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() amount = SwapAmount(BigDecimal.ZERO, 18), swapData = swapData, selectedFeeToken = null, - ) + isGasless = false, + + ) assertThat(result.isLeft()).isTrue() result.onLeft { error -> @@ -501,7 +519,9 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() amount = SwapAmount(BigDecimal.ONE, 18), swapData = swapData, selectedFeeToken = explicitTokenStatus, - ) + isGasless = false, + + ) assertThat(result.isRight()).isTrue() result.onRight { swapFee -> @@ -571,8 +591,9 @@ internal class SwapInteractorImplLoadSwapFeeTest : SwapInteractorImplTestBase() toStatus = toStatus, amount = SwapAmount(BigDecimal.ONE, 18), swapData = swapData, - selectedFeeToken = null, - ) + selectedFeeToken = null, isGasless = false, + + ) // When resolveNativeFeeTokenStatus returns null → Left(UnknownError) assertThat(result.isLeft()).isTrue() diff --git a/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/fee/CexSwapFeeCalculatorTest.kt b/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/fee/CexSwapFeeCalculatorTest.kt index 6d4e06580a..846dc194ee 100644 --- a/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/fee/CexSwapFeeCalculatorTest.kt +++ b/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/fee/CexSwapFeeCalculatorTest.kt @@ -69,6 +69,7 @@ internal class CexSwapFeeCalculatorTest { fromSwapCurrencyStatus = fromStatus, amount = BigDecimal.ZERO, selectedFeeToken = null, + isGasless = true, ) assertThat(result.isLeft()).isTrue() @@ -97,7 +98,7 @@ internal class CexSwapFeeCalculatorTest { userWallet = fromStatus.userWallet, fromSwapCurrencyStatus = fromStatus, amount = BigDecimal("1.5"), - selectedFeeToken = null, + selectedFeeToken = null, isGasless = true, ) assertThat(result.isRight()).isTrue() @@ -130,7 +131,7 @@ internal class CexSwapFeeCalculatorTest { userWallet = fromStatus.userWallet, fromSwapCurrencyStatus = fromStatus, amount = BigDecimal("1.0"), - selectedFeeToken = null, + selectedFeeToken = null, isGasless = true, ) assertThat(result.isLeft()).isTrue() @@ -160,7 +161,7 @@ internal class CexSwapFeeCalculatorTest { userWallet = fromStatus.userWallet, fromSwapCurrencyStatus = fromStatus, amount = BigDecimal("2.0"), - selectedFeeToken = tokenStatus, + selectedFeeToken = tokenStatus, isGasless = true, ) assertThat(result.isRight()).isTrue() @@ -207,7 +208,7 @@ internal class CexSwapFeeCalculatorTest { userWallet = fromStatus.userWallet, fromSwapCurrencyStatus = fromStatus, amount = BigDecimal("3.0"), - selectedFeeToken = coinStatus, + selectedFeeToken = coinStatus, isGasless = true, ) assertThat(result.isRight()).isTrue() @@ -251,7 +252,7 @@ internal class CexSwapFeeCalculatorTest { userWallet = fromStatus.userWallet, fromSwapCurrencyStatus = fromStatus, amount = BigDecimal("1.0"), - selectedFeeToken = coinStatus, + selectedFeeToken = coinStatus, isGasless = true, ) result.onRight { cexResult -> @@ -276,7 +277,7 @@ internal class CexSwapFeeCalculatorTest { userWallet = fromStatus.userWallet, fromSwapCurrencyStatus = fromStatus, amount = BigDecimal("1.0"), - selectedFeeToken = coinStatus, + selectedFeeToken = coinStatus, isGasless = true, ) assertThat(result.isLeft()).isTrue() @@ -321,7 +322,7 @@ internal class CexSwapFeeCalculatorTest { userWallet = fromStatus.userWallet, fromSwapCurrencyStatus = fromStatus, amount = BigDecimal("1.0"), - selectedFeeToken = coinStatus, + selectedFeeToken = coinStatus, isGasless = true, ) result.onRight { cexResult -> @@ -355,6 +356,7 @@ internal class CexSwapFeeCalculatorTest { fromSwapCurrencyStatus = fromStatus, amount = BigDecimal("1.0"), selectedFeeToken = null, + isGasless = true, ) coVerify(exactly = 1) { diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index 975da73307..bbd9023571 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -2176,7 +2176,8 @@ internal class SwapModel @Inject constructor( toStatus = toSwapCurrencyStatus, amount = swapAmount, swapData = swapDataForCall, - selectedFeeToken = dataState.feePaidCryptoCurrency, + selectedFeeToken = null, + isGasless = false, ).map { swapFee -> when (val res = swapFee.transactionFeeResult) { is TransactionFeeResult.LoadedExtended -> res.fee.transactionFee @@ -2230,6 +2231,7 @@ internal class SwapModel @Inject constructor( amount = swapAmount, swapData = swapDataForCall, selectedFeeToken = selectedToken, + isGasless = true, ).map { swapFee -> // The fee selector block consumes TransactionFeeExtended; build one when // `transactionFeeResult` is LoadedExtended, else wrap the native fee in a