Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-12 23:28:30 +05:00
parent 592f4cf8e7
commit 5e8c334e3d
9 changed files with 116 additions and 18 deletions

View file

@ -1219,6 +1219,122 @@ internal class SwapInteractorImpl @Inject constructor(
}
}
/**
* [REDACTED_TASK_KEY] Phase 3 unified fee API. Delegates to [DexSwapFeeCalculator] /
* [CexSwapFeeCalculator] and wraps the result in a [SwapFee].
*
* Behavior parity with the legacy `loadFeeForSwapTransaction` overloads is intentional
* the legacy methods stay in place through Phase 4. See `SwapInteractor.loadSwapFee` for
* the full contract.
*/
@Suppress("LongParameterList", "ReturnCount")
override suspend fun loadSwapFee(
provider: SwapProvider,
fromStatus: SwapCurrencyStatus,
toStatus: SwapCurrencyStatus,
amount: SwapAmount,
swapData: SwapDataModel?,
selectedFeeToken: CryptoCurrencyStatus?,
): Either<GetFeeError, SwapFee> = either {
if (amount.value.signum() == 0) {
raise(GetFeeError.UnknownError)
}
return when (provider.type) {
ExchangeProviderType.DEX,
ExchangeProviderType.DEX_BRIDGE,
-> loadDexSwapFee(
fromStatus = fromStatus,
swapData = swapData,
selectedFeeToken = selectedFeeToken,
)
ExchangeProviderType.CEX -> loadCexSwapFee(
fromStatus = fromStatus,
amount = amount,
selectedFeeToken = selectedFeeToken,
)
}
}
/**
* [REDACTED_TASK_KEY] DEX branch of [loadSwapFee]. Pulls the cached `ExpressTransactionModel.DEX`
* out of [swapData] and hands it to [DexSwapFeeCalculator]. Maps [ExpressDataError]
* `Left(GetFeeError.UnknownError)` to keep the unified surface a single error type, matching
* what the legacy `loadFeeForSwapTransaction` overload 2 does for DEX failures (line 1027 of
* the original code).
*/
private suspend fun loadDexSwapFee(
fromStatus: SwapCurrencyStatus,
swapData: SwapDataModel?,
selectedFeeToken: CryptoCurrencyStatus?,
): Either<GetFeeError, SwapFee> {
val transaction = swapData?.transaction as? ExpressTransactionModel.DEX
?: return GetFeeError.UnknownError.left()
return dexSwapFeeCalculator.calculate(
fromSwapCurrencyStatus = fromStatus,
transaction = transaction,
selectedToken = selectedFeeToken,
).fold(
ifLeft = { GetFeeError.UnknownError.left() },
ifRight = { dexFeeResult ->
val feeToken = selectedFeeToken
?: resolveNativeFeeTokenStatus(fromStatus)
?: return@fold GetFeeError.UnknownError.left()
SwapFeeFactory.from(
transactionFeeResult = dexFeeResult.transactionFee,
selectedFeeToken = feeToken,
otherNativeFee = dexFeeResult.otherNativeFee,
feeBucket = FeeBucket.MARKET,
).right()
},
)
}
/**
* [REDACTED_TASK_KEY] CEX branch of [loadSwapFee]. Native-fallback behaviour is preserved: when
* [selectedFeeToken] is null the gasless use case (invoked inside [CexSwapFeeCalculator])
* decides native vs token. The resulting `SwapFee.selectedFeeToken` is the explicit choice
* if provided, otherwise the native coin status of the from-token's network.
*/
private suspend fun loadCexSwapFee(
fromStatus: SwapCurrencyStatus,
amount: SwapAmount,
selectedFeeToken: CryptoCurrencyStatus?,
): Either<GetFeeError, SwapFee> {
return cexSwapFeeCalculator.calculate(
userWallet = fromStatus.userWallet,
fromSwapCurrencyStatus = fromStatus,
amount = amount.value,
selectedFeeToken = selectedFeeToken,
).fold(
ifLeft = { it.left() },
ifRight = { cexFeeResult ->
val feeToken = selectedFeeToken
?: resolveNativeFeeTokenStatus(fromStatus)
?: return@fold GetFeeError.UnknownError.left()
SwapFeeFactory.from(
transactionFeeResult = cexFeeResult.transactionFee,
selectedFeeToken = feeToken,
otherNativeFee = BigDecimal.ZERO,
feeBucket = FeeBucket.MARKET,
).right()
},
)
}
/**
* [REDACTED_TASK_KEY] resolves the native-coin [CryptoCurrencyStatus] for the from-token's network.
* Used as the default `selectedFeeToken` of [SwapFee] when the caller did not provide an
* explicit choice. Mirrors how `SwapModel.updateFeePaidCryptoCurrencyFor` populates
* `dataState.feePaidCryptoCurrency`.
*/
private suspend fun resolveNativeFeeTokenStatus(fromStatus: SwapCurrencyStatus): CryptoCurrencyStatus? {
return getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = fromStatus.userWalletId,
cryptoCurrencyStatus = fromStatus.status,
).getOrNull()
}
private suspend fun storeLastCryptoCurrencyId(swapCurrencyStatus: SwapCurrencyStatus) {
swapTransactionRepository.storeLastSwappedCryptoCurrencyId(
userWalletId = swapCurrencyStatus.userWalletId,

View file

@ -1,7 +1,5 @@
package com.tangem.feature.swap.domain.fee
import com.tangem.feature.swap.domain.TransactionFeeResult
/**
* Result of calculating the CEX swap transaction fee.
*

View file

@ -10,7 +10,6 @@ import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.usecase.EstimateFeeUseCase
import com.tangem.domain.transaction.usecase.gasless.EstimateFeeForGaslessTxUseCase
import com.tangem.domain.transaction.usecase.gasless.EstimateFeeForTokenUseCase
import com.tangem.feature.swap.domain.TransactionFeeResult
import java.math.BigDecimal
/**

View file

@ -1,6 +1,5 @@
package com.tangem.feature.swap.domain.fee
import com.tangem.feature.swap.domain.TransactionFeeResult
import java.math.BigDecimal
import java.math.BigInteger

View file

@ -19,7 +19,6 @@ import com.tangem.domain.transaction.usecase.GetEthSpecificFeeUseCase
import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.transaction.usecase.gasless.GetFeeForTokenUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.feature.swap.domain.TransactionFeeResult
import com.tangem.feature.swap.domain.models.ExpressDataError
import com.tangem.feature.swap.domain.models.domain.ExpressTransactionModel
import com.tangem.lib.crypto.BlockchainUtils.SOLANA_TRANSACTION_SIZE_THRESHOLD_BYTES

View file

@ -16,7 +16,6 @@ import com.tangem.domain.transaction.models.TransactionFeeExtended
import com.tangem.domain.transaction.usecase.EstimateFeeUseCase
import com.tangem.domain.transaction.usecase.gasless.EstimateFeeForGaslessTxUseCase
import com.tangem.domain.transaction.usecase.gasless.EstimateFeeForTokenUseCase
import com.tangem.feature.swap.domain.TransactionFeeResult
import com.tangem.feature.swap.domain.buildSwapCurrencyStatus
import io.mockk.*
import kotlinx.coroutines.test.runTest

View file

@ -21,7 +21,6 @@ import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.feature.swap.domain.fee.PatchEthGasLimitForSwap
import com.tangem.domain.transaction.usecase.gasless.GetFeeForTokenUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.feature.swap.domain.TransactionFeeResult
import com.tangem.feature.swap.domain.buildSwapCurrencyStatus
import com.tangem.feature.swap.domain.models.ExpressDataError
import com.tangem.feature.swap.domain.models.SwapAmount

View file

@ -103,7 +103,6 @@ internal class SwapNotificationsFactoryFeeWarningsTest {
quoteModel = quoteModel,
feeCryptoCurrencyStatus = feeStatus,
selectedFeeType = FeeType.NORMAL,
providerName = "TestProvider",
hideFee = false,
)
@ -129,7 +128,6 @@ internal class SwapNotificationsFactoryFeeWarningsTest {
quoteModel = quoteModel,
feeCryptoCurrencyStatus = feeStatus,
selectedFeeType = FeeType.NORMAL,
providerName = "TestProvider",
hideFee = false,
)
@ -155,7 +153,6 @@ internal class SwapNotificationsFactoryFeeWarningsTest {
quoteModel = quoteModel,
feeCryptoCurrencyStatus = feeStatus,
selectedFeeType = FeeType.NORMAL,
providerName = "TestProvider",
hideFee = false,
)
@ -181,7 +178,6 @@ internal class SwapNotificationsFactoryFeeWarningsTest {
quoteModel = quoteModel,
feeCryptoCurrencyStatus = feeStatus,
selectedFeeType = FeeType.NORMAL,
providerName = "TestProvider",
hideFee = true,
)
@ -207,7 +203,6 @@ internal class SwapNotificationsFactoryFeeWarningsTest {
quoteModel = quoteModel,
feeCryptoCurrencyStatus = feeStatus,
selectedFeeType = FeeType.NORMAL,
providerName = "TestProvider",
hideFee = false,
)
@ -237,7 +232,6 @@ internal class SwapNotificationsFactoryFeeWarningsTest {
quoteModel = quoteModel,
feeCryptoCurrencyStatus = feeStatus,
selectedFeeType = FeeType.NORMAL,
providerName = "TestProvider",
hideFee = false,
)
@ -265,7 +259,6 @@ internal class SwapNotificationsFactoryFeeWarningsTest {
quoteModel = quoteModel,
feeCryptoCurrencyStatus = feeStatus,
selectedFeeType = FeeType.NORMAL,
providerName = "TestProvider",
hideFee = false,
)
@ -296,7 +289,6 @@ internal class SwapNotificationsFactoryFeeWarningsTest {
quoteModel = quoteModel,
feeCryptoCurrencyStatus = feeStatus,
selectedFeeType = FeeType.NORMAL,
providerName = "TestProvider",
hideFee = false,
)
@ -322,7 +314,6 @@ internal class SwapNotificationsFactoryFeeWarningsTest {
quoteModel = quoteModel,
feeCryptoCurrencyStatus = feeStatus,
selectedFeeType = FeeType.NORMAL,
providerName = "TestProvider",
hideFee = false,
)
@ -351,7 +342,6 @@ internal class SwapNotificationsFactoryFeeWarningsTest {
quoteModel = quoteModel,
feeCryptoCurrencyStatus = feeStatus,
selectedFeeType = FeeType.NORMAL,
providerName = "TestProvider",
hideFee = false,
)
@ -378,7 +368,6 @@ internal class SwapNotificationsFactoryFeeWarningsTest {
quoteModel = quoteModel,
feeCryptoCurrencyStatus = feeStatus,
selectedFeeType = FeeType.NORMAL,
providerName = "TestProvider",
hideFee = false,
)