Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-01 18:00:10 +03:00
parent d83a31a9b0
commit 042df9828f
16 changed files with 47 additions and 1 deletions

View file

@ -66,6 +66,7 @@ interface TangemExpressApi {
@Query("requestId") requestId: String,
@Query("refundAddress") refundAddress: String?, // for cex only
@Query("refundExtraId") refundExtraId: String?, // for cex only
@Query("partnerOperationType") partnerOperationType: String?, // swap/ swap-and-send
): ApiResponse<ExchangeDataResponse>
@GET("exchange-status")

View file

@ -18,6 +18,7 @@ import com.tangem.datasource.exchangeservice.swap.ExpressUtils
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.express.ExpressRepository
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.express.models.ExpressOperationType
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.express.models.ExpressProviderType
import com.tangem.domain.express.models.ExpressRateType
@ -208,6 +209,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
toAddress: String,
expressProvider: ExpressProvider,
rateType: ExpressRateType,
expressOperationType: ExpressOperationType,
): SwapDataModel = withContext(coroutineDispatcher.io) {
val requestId = UUID.randomUUID().toString()
val fromCryptoCurrency = fromCryptoCurrencyStatus.currency
@ -239,6 +241,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
refundAddress = refundData?.refundAddress,
refundExtraId = refundData?.refundExtraId,
userWalletId = userWallet.walletId.stringValue,
partnerOperationType = expressOperationType.value,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,

View file

@ -0,0 +1,6 @@
package com.tangem.domain.express.models
enum class ExpressOperationType(val value: String) {
SWAP("swap"),
SEND_WITH_SWAP("swap-and-send"),
}

View file

@ -1,5 +1,6 @@
package com.tangem.domain.swap
import com.tangem.domain.express.models.ExpressOperationType
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.express.models.ExpressProviderType
import com.tangem.domain.express.models.ExpressRateType
@ -74,6 +75,7 @@ interface SwapRepositoryV2 {
* @param toAddress destination address
* @param expressProvider selected swap provider
* @param rateType selected provider rate type
* @param expressOperationType operation type swap or send with swap
*/
suspend fun getSwapData(
userWallet: UserWallet,
@ -83,6 +85,7 @@ interface SwapRepositoryV2 {
toAddress: String,
expressProvider: ExpressProvider,
rateType: ExpressRateType,
expressOperationType: ExpressOperationType,
): SwapDataModel
/**

View file

@ -2,6 +2,7 @@ package com.tangem.domain.swap.usecase
import arrow.core.Either
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.express.models.ExpressOperationType
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.express.models.ExpressRateType
import com.tangem.domain.models.currency.CryptoCurrency
@ -25,6 +26,7 @@ class GetSwapDataUseCase(
toAddress: String,
expressProvider: ExpressProvider,
rateType: ExpressRateType,
expressOperationType: ExpressOperationType,
): Either<ExpressError, SwapDataModel> = Either.catch {
swapRepositoryV2.getSwapData(
userWallet = userWallet,
@ -34,6 +36,7 @@ class GetSwapDataUseCase(
toAddress = toAddress,
expressProvider = expressProvider,
rateType = rateType,
expressOperationType = expressOperationType,
)
}.mapLeft { throwable ->
swapErrorResolver.resolve(throwable)

View file

@ -17,6 +17,7 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.express.models.ExpressOperationType
import com.tangem.domain.express.models.ExpressProviderType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
@ -293,6 +294,7 @@ internal class SendWithSwapConfirmModel @Inject constructor(
}
router.replaceAll(SendWithSwapRoute.Success)
},
expressOperationType = ExpressOperationType.SEND_WITH_SWAP,
)
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model
import arrow.core.getOrElse
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.express.models.ExpressOperationType
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.express.models.ExpressProviderType
import com.tangem.domain.models.currency.CryptoCurrencyStatus
@ -40,6 +41,7 @@ internal class SwapTransactionSender @AssistedInject constructor(
onSendSuccess: (String, Long, SwapDataModel) -> Unit,
onExpressError: (ExpressError) -> Unit,
onSendError: (SendTransactionError?) -> Unit,
expressOperationType: ExpressOperationType,
) {
val provider = confirmData.quote?.provider ?: return
@ -50,6 +52,7 @@ internal class SwapTransactionSender @AssistedInject constructor(
onExpressError = onExpressError,
onSendSuccess = onSendSuccess,
onSendError = onSendError,
expressOperationType = expressOperationType,
)
ExpressProviderType.DEX,
ExpressProviderType.DEX_BRIDGE,
@ -67,6 +70,7 @@ internal class SwapTransactionSender @AssistedInject constructor(
onSendSuccess: (String, Long, SwapDataModel) -> Unit,
onExpressError: (ExpressError) -> Unit,
onSendError: (SendTransactionError?) -> Unit,
expressOperationType: ExpressOperationType,
) {
val fromStatus = confirmData.fromCryptoCurrencyStatus ?: return
val toStatus = confirmData.toCryptoCurrencyStatus ?: return
@ -92,6 +96,7 @@ internal class SwapTransactionSender @AssistedInject constructor(
toAddress = destination,
expressProvider = provider,
rateType = rateType,
expressOperationType,
).getOrElse { onExpressError(it); return }
createAndSendCexTransaction(

View file

@ -42,6 +42,7 @@ dependencies {
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
implementation(projects.domain.transaction.models)
implementation(projects.domain.express.models)
/** Data */
implementation(projects.data.common)

View file

@ -27,6 +27,7 @@ import com.tangem.datasource.exchangeservice.swap.ExpressUtils
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.express.models.ExpressOperationType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
@ -283,6 +284,7 @@ internal class DefaultSwapRepository(
providerId: String,
rateType: RateType,
toAddress: String,
expressOperationType: ExpressOperationType,
refundAddress: String?, // for cex only
refundExtraId: String?, // for cex only
): Either<ExpressDataError, SwapDataModel> {
@ -304,6 +306,7 @@ internal class DefaultSwapRepository(
requestId = requestId,
refundAddress = refundAddress,
refundExtraId = refundExtraId,
partnerOperationType = expressOperationType.value,
userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,

View file

@ -12,6 +12,7 @@ dependencies {
implementation(projects.features.swap.domain.models)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
implementation(projects.domain.express.models)
implementation(deps.arrow.core)

View file

@ -1,6 +1,7 @@
package com.tangem.feature.swap.domain.api
import arrow.core.Either
import com.tangem.domain.express.models.ExpressOperationType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
@ -64,6 +65,7 @@ interface SwapRepository {
providerId: String,
rateType: RateType,
toAddress: String,
expressOperationType: ExpressOperationType,
refundAddress: String? = null, // for cex only
refundExtraId: String? = null, // for cex only
): Either<ExpressDataError, SwapDataModel>

View file

@ -35,6 +35,7 @@ dependencies {
implementation(projects.domain.txhistory.models)
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
implementation(projects.domain.express.models)
implementation(projects.features.swap.domain.api)
implementation(projects.features.swap.domain.models)

View file

@ -1,5 +1,6 @@
package com.tangem.feature.swap.domain
import com.tangem.domain.express.models.ExpressOperationType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWalletId
@ -65,6 +66,7 @@ interface SwapInteractor {
amountToSwap: String,
includeFeeInAmount: IncludeFeeInAmount,
fee: TxFee,
expressOperationType: ExpressOperationType,
): SwapTransactionState
// suspend fun updateQuotesStateWithSelectedFee(

View file

@ -18,6 +18,7 @@ import com.tangem.domain.appcurrency.extenstions.unwrap
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.express.models.ExpressOperationType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
@ -288,6 +289,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
selectedFee = selectedFee,
amount = amount,
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
expressOperationType = ExpressOperationType.SWAP,
)
}
ExchangeProviderType.CEX -> {
@ -314,6 +316,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
selectedFee: FeeType,
amount: SwapAmount,
isBalanceWithoutFeeEnough: Boolean,
expressOperationType: ExpressOperationType,
): Pair<SwapProvider, SwapState> {
val maybeQuotes = repository.findBestQuote(
userWallet = userWallet,
@ -350,6 +353,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
toToken = toToken,
amount = amount,
selectedFee = selectedFee,
expressOperationType = expressOperationType,
)
} else {
provider to getQuotesState(
@ -496,6 +500,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
amountToSwap: String,
includeFeeInAmount: IncludeFeeInAmount,
fee: TxFee,
expressOperationType: ExpressOperationType,
): SwapTransactionState {
Timber.i(
"""
@ -530,6 +535,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
amount = amountToSwapWithFee,
txFee = fee,
swapProvider = swapProvider,
expressOperationType = expressOperationType,
)
}
ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> {
@ -677,6 +683,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
amount: SwapAmount,
txFee: TxFee,
swapProvider: SwapProvider,
expressOperationType: ExpressOperationType,
): SwapTransactionState {
val exchangeData = repository.getExchangeData(
userWallet = userWallet,
@ -690,9 +697,10 @@ internal class SwapInteractorImpl @AssistedInject constructor(
toDecimals = currencyToGet.currency.decimals,
providerId = swapProvider.providerId,
rateType = RateType.FLOAT,
expressOperationType = expressOperationType,
toAddress = currencyToGet.value.networkAddress?.defaultAddress?.value.orEmpty(),
refundAddress = currencyToSend.value.networkAddress?.defaultAddress?.value,
refundExtraId = null, // currently always null
refundExtraId = null, // currently always null,
).getOrElse { return SwapTransactionState.Error.ExpressError(it) }
val exchangeDataCex =
@ -1162,6 +1170,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
toToken: CryptoCurrencyStatus,
amount: SwapAmount,
selectedFee: FeeType,
expressOperationType: ExpressOperationType,
): SwapState {
return repository.getExchangeData(
userWallet = userWallet,
@ -1177,6 +1186,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
rateType = RateType.FLOAT,
toAddress = toToken.value.networkAddress?.defaultAddress?.value.orEmpty(),
refundAddress = fromToken.value.networkAddress?.defaultAddress?.value,
expressOperationType = expressOperationType,
).fold(
ifRight = { swapData ->
val transaction = swapData.transaction as ExpressTransactionModel.DEX

View file

@ -43,6 +43,7 @@ dependencies {
implementation(projects.domain.promo.models)
implementation(projects.domain.txhistory)
implementation(projects.domain.txhistory.models)
implementation(projects.domain.express.models)
/** Feature modules */
implementation(projects.features.swap.domain)

View file

@ -23,6 +23,7 @@ import com.tangem.core.ui.utils.InputNumberFormatter
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.express.models.ExpressOperationType
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
@ -613,6 +614,7 @@ internal class SwapModel @Inject constructor(
amountToSwap = requireNotNull(dataState.amount),
includeFeeInAmount = lastLoadedQuotesState.preparedSwapConfigState.includeFeeInAmount,
fee = fee,
expressOperationType = ExpressOperationType.SWAP,
)
}.onSuccess {
when (it) {