From e80ade95584df7322b706e1072ff4f4fd33950ce Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 14 Jul 2025 20:45:28 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/tap/di/domain/SwapDomainModule.kt | 31 +++- .../data/swap/DefaultSwapErrorResolver.kt | 1 + .../data/swap/DefaultSwapRepositoryV2.kt | 142 +++++++++++++++++- .../data/swap/converter/SwapDataConverter.kt | 67 +++++++++ .../com/tangem/data/swap/di/SwapDataModule.kt | 5 + .../domain/express/models/ExpressError.kt | 2 +- .../express/models/ExpressProviderType.kt | 12 ++ domain/swap/build.gradle.kts | 2 + .../domain/swap/models/SwapDataModel.kt | 47 ++++++ .../domain/swap/models/SwapRefundData.kt | 12 ++ .../tangem/domain/swap/SwapRepositoryV2.kt | 52 ++++++- .../domain/swap/usecase/GetSwapDataUseCase.kt | 40 +++++ .../usecase/SwapTransactionSentUseCase.kt | 67 +++++++++ 13 files changed, 470 insertions(+), 10 deletions(-) create mode 100644 data/swap/src/main/java/com/tangem/data/swap/converter/SwapDataConverter.kt create mode 100644 domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapDataModel.kt create mode 100644 domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapRefundData.kt create mode 100644 domain/swap/src/main/java/com/tangem/domain/swap/usecase/GetSwapDataUseCase.kt create mode 100644 domain/swap/src/main/java/com/tangem/domain/swap/usecase/SwapTransactionSentUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt index f0ba15b856..8cf51ccfe1 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt @@ -3,10 +3,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.swap.SwapErrorResolver import com.tangem.domain.swap.SwapRepositoryV2 import com.tangem.domain.swap.SwapTransactionRepository -import com.tangem.domain.swap.usecase.GetSwapPairsUseCase -import com.tangem.domain.swap.usecase.GetSwapQuoteUseCase -import com.tangem.domain.swap.usecase.GetSwapSupportedPairsUseCase -import com.tangem.domain.swap.usecase.SelectInitialPairUseCase +import com.tangem.domain.swap.usecase.* import com.tangem.feature.swap.domain.GetAvailablePairsUseCase import dagger.Module import dagger.Provides @@ -73,4 +70,30 @@ internal object SwapDomainModule { swapErrorResolver = swapErrorResolver, ) } + + @Provides + @Singleton + fun provideGetSwapDataUseCase( + swapRepositoryV2: SwapRepositoryV2, + swapErrorResolver: SwapErrorResolver, + ): GetSwapDataUseCase { + return GetSwapDataUseCase( + swapRepositoryV2 = swapRepositoryV2, + swapErrorResolver = swapErrorResolver, + ) + } + + @Provides + @Singleton + fun provideSwapTransactionSentUseCase( + swapRepositoryV2: SwapRepositoryV2, + swapTransactionRepository: SwapTransactionRepository, + swapErrorResolver: SwapErrorResolver, + ): SwapTransactionSentUseCase { + return SwapTransactionSentUseCase( + swapRepositoryV2 = swapRepositoryV2, + swapTransactionRepository = swapTransactionRepository, + swapErrorResolver = swapErrorResolver, + ) + } } \ No newline at end of file diff --git a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapErrorResolver.kt b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapErrorResolver.kt index 8827a8bc7c..b81d5b6231 100644 --- a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapErrorResolver.kt +++ b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapErrorResolver.kt @@ -13,6 +13,7 @@ internal class DefaultSwapErrorResolver( is ApiResponseError.HttpException -> { expressErrorConverter.convert(throwable.errorBody.orEmpty()) } + is ExpressError -> throwable else -> ExpressError.UnknownError } } diff --git a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt index 9adca6af89..7b2442b6ac 100644 --- a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt +++ b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt @@ -1,19 +1,28 @@ package com.tangem.data.swap +import com.squareup.moshi.Moshi import com.tangem.data.common.api.safeApiCall +import com.tangem.data.swap.converter.SwapDataConverter +import com.tangem.data.swap.converter.SwapStatusConverter import com.tangem.data.swap.converter.TokenInfoConverter import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.express.TangemExpressApi +import com.tangem.datasource.api.express.models.request.ExchangeSentRequestBody import com.tangem.datasource.api.express.models.request.PairsRequestBody +import com.tangem.datasource.api.express.models.response.ExchangeDataResponseWithTxDetails +import com.tangem.datasource.api.express.models.response.TxDetails +import com.tangem.datasource.crypto.DataSignatureVerifier +import com.tangem.datasource.di.NetworkMoshi 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.ExpressProvider +import com.tangem.domain.express.models.ExpressProviderType import com.tangem.domain.express.models.ExpressRateType import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.swap.SwapRepositoryV2 -import com.tangem.domain.swap.models.SwapPairModel -import com.tangem.domain.swap.models.SwapQuoteModel +import com.tangem.domain.swap.models.* import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations import com.tangem.domain.wallets.models.UserWallet @@ -24,19 +33,26 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.withContext import timber.log.Timber +import java.io.IOException import java.math.BigDecimal +import java.util.UUID import javax.inject.Inject -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") internal class DefaultSwapRepositoryV2 @Inject constructor( private val tangemExpressApi: TangemExpressApi, private val expressRepository: ExpressRepository, private val coroutineDispatcher: CoroutineDispatcherProvider, private val appPreferencesStore: AppPreferencesStore, private val currencyStatusOperations: BaseCurrencyStatusOperations, + private val dataSignatureVerifier: DataSignatureVerifier, + @NetworkMoshi moshi: Moshi, ) : SwapRepositoryV2 { + private val swapDataConverter = SwapDataConverter() private val tokenInfoConverter = TokenInfoConverter() + private val exchangeStatusConverter = SwapStatusConverter() + private val txDetailsMoshiAdapter = moshi.adapter(TxDetails::class.java) override suspend fun getPairs( userWallet: UserWallet, @@ -153,6 +169,115 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( ) } + override suspend fun getSwapData( + userWallet: UserWallet, + fromCryptoCurrencyStatus: CryptoCurrencyStatus, + toCryptoCurrencyStatus: CryptoCurrencyStatus, + fromAmount: String, + toAddress: String?, + expressProvider: ExpressProvider, + rateType: ExpressRateType, + ): SwapDataModel = withContext(coroutineDispatcher.io) { + val requestId = UUID.randomUUID().toString() + val fromCryptoCurrency = fromCryptoCurrencyStatus.currency + val toCryptoCurrency = toCryptoCurrencyStatus.currency + + val refundData = when (expressProvider.type) { + ExpressProviderType.CEX, + ExpressProviderType.DEX_BRIDGE, + ExpressProviderType.DEX, + -> SwapRefundData( + refundAddress = fromCryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value, + refundExtraId = null, // currently always null + ) + else -> null + } + + val response = tangemExpressApi.getExchangeData( + fromContractAddress = fromCryptoCurrency.getContractAddress(), + toContractAddress = toCryptoCurrency.getContractAddress(), + fromNetwork = fromCryptoCurrency.network.backendId, + toNetwork = toCryptoCurrency.network.backendId, + fromAddress = fromCryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value.orEmpty(), + toAddress = toAddress ?: toCryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value.orEmpty(), + fromDecimals = fromCryptoCurrency.decimals, + toDecimals = toCryptoCurrency.decimals, + fromAmount = fromAmount, + providerId = expressProvider.name, + rateType = rateType.name.lowercase(), + requestId = requestId, + refundAddress = refundData?.refundAddress, + refundExtraId = refundData?.refundExtraId, + userWalletId = userWallet.walletId.stringValue, + refCode = ExpressUtils.getRefCode( + userWallet = userWallet, + appPreferencesStore = appPreferencesStore, + ), + ).getOrThrow() + + if (dataSignatureVerifier.verifySignature(response.signature, response.txDetailsJson)) { + val txDetails = parseTxDetails(response.txDetailsJson) + ?: throw ExpressError.UnknownError + if (txDetails.requestId != requestId) { + throw ExpressError.InvalidRequestIdError() + } + if (!toAddress.equals(txDetails.payoutAddress, ignoreCase = true)) { + throw ExpressError.InvalidPayoutAddressError() + } + swapDataConverter.convert( + ExchangeDataResponseWithTxDetails( + dataResponse = response, + txDetails = txDetails, + ), + ) + } else { + throw ExpressError.InvalidSignatureError() + } + } + + override suspend fun swapTransactionSent( + userWallet: UserWallet, + fromCryptoCurrencyStatus: CryptoCurrencyStatus, + toAddress: String, + txId: String, + txHash: String, + txExtraId: String?, + ) { + withContext(coroutineDispatcher.io) { + tangemExpressApi.exchangeSent( + userWalletId = userWallet.walletId.stringValue, + refCode = ExpressUtils.getRefCode( + userWallet = userWallet, + appPreferencesStore = appPreferencesStore, + ), + body = ExchangeSentRequestBody( + txId = txId, + fromNetwork = fromCryptoCurrencyStatus.currency.network.backendId, + fromAddress = fromCryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value.orEmpty(), + payinAddress = toAddress, + payinExtraId = txExtraId, + txHash = txHash, + ), + ).getOrThrow() + } + } + + override suspend fun getExchangeStatus(userWallet: UserWallet, txId: String): SwapStatusModel = + withContext(coroutineDispatcher.io) { + exchangeStatusConverter.convert( + tangemExpressApi + .getExchangeStatus( + userWalletId = userWallet.walletId.stringValue, + refCode = ExpressUtils.getRefCode( + userWallet = userWallet, + appPreferencesStore = appPreferencesStore, + ), + txId = txId, + ) + .getOrThrow(), + ) + } + private suspend fun CoroutineScope.getPairsInternal( userWallet: UserWallet, initialCurrency: CryptoCurrency, @@ -200,7 +325,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( }, ) - private suspend fun CoroutineScope.createPairModelOnly( + private suspend fun createPairModelOnly( currencyFrom: CryptoCurrency?, currencyTo: CryptoCurrency?, userWalletId: UserWalletId, @@ -229,6 +354,15 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( } } + private fun parseTxDetails(txDetailsJson: String): TxDetails? { + return try { + txDetailsMoshiAdapter.fromJson(txDetailsJson) + } catch (e: IOException) { + Timber.e(e, "error parsing txDetailsJson") + null + } + } + private fun CryptoCurrency.getContractAddress(): String { return when (this) { is CryptoCurrency.Token -> this.contractAddress diff --git a/data/swap/src/main/java/com/tangem/data/swap/converter/SwapDataConverter.kt b/data/swap/src/main/java/com/tangem/data/swap/converter/SwapDataConverter.kt new file mode 100644 index 0000000000..34f26aed27 --- /dev/null +++ b/data/swap/src/main/java/com/tangem/data/swap/converter/SwapDataConverter.kt @@ -0,0 +1,67 @@ +package com.tangem.data.swap.converter + +import com.tangem.datasource.api.express.models.response.ExchangeDataResponse +import com.tangem.datasource.api.express.models.response.ExchangeDataResponseWithTxDetails +import com.tangem.datasource.api.express.models.response.TxDetails +import com.tangem.datasource.api.express.models.response.TxType +import com.tangem.domain.swap.models.SwapDataModel +import com.tangem.domain.swap.models.SwapDataTransactionModel +import com.tangem.utils.converter.Converter +import java.math.BigDecimal + +internal class SwapDataConverter : Converter { + + override fun convert(value: ExchangeDataResponseWithTxDetails): SwapDataModel { + val data = value.dataResponse + return SwapDataModel( + toTokenAmount = requireNotNull(data.toAmount.toBigDecimalOrNull()?.movePointLeft(data.toDecimals)), + transaction = convertTransaction(value.txDetails, data), + ) + } + + private fun convertTransaction( + transactionDto: TxDetails, + dataResponse: ExchangeDataResponse, + ): SwapDataTransactionModel { + val fromAmount = requireNotNull( + dataResponse.fromAmount.toBigDecimalOrNull()?.movePointLeft(dataResponse.fromDecimals), + ) + val toAmount = requireNotNull( + dataResponse.toAmount.toBigDecimalOrNull()?.movePointLeft(dataResponse.toDecimals), + ) + + return if (transactionDto.txType == TxType.SWAP) { + val otherNativeFeeWei = transactionDto.otherNativeFee?.let { + if (it == "0") { + BigDecimal.ZERO + } else { + requireNotNull(it.toBigDecimalOrNull()) { "wrong amount format, use only digits" } + } + } + SwapDataTransactionModel.DEX( + fromAmount = fromAmount, + toAmount = toAmount, + txValue = transactionDto.txValue, + txId = dataResponse.txId, + txTo = transactionDto.txTo, + txFrom = requireNotNull(transactionDto.txFrom), + txData = requireNotNull(transactionDto.txData), + txExtraId = transactionDto.txExtraId, + otherNativeFeeWei = otherNativeFeeWei, + gas = transactionDto.gas?.toBigIntegerOrNull() ?: error("gas is empty"), + ) + } else { + SwapDataTransactionModel.CEX( + fromAmount = fromAmount, + toAmount = toAmount, + txValue = transactionDto.txValue, + txId = dataResponse.txId, + txTo = transactionDto.txTo, + externalTxId = requireNotNull(transactionDto.externalTxId), + externalTxUrl = requireNotNull(transactionDto.externalTxUrl), + txExtraIdName = transactionDto.txExtraIdName, + txExtraId = transactionDto.txExtraId, + ) + } + } +} \ No newline at end of file diff --git a/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt b/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt index 486080c4e4..635a89c736 100644 --- a/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt +++ b/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt @@ -8,6 +8,7 @@ import com.tangem.data.swap.DefaultSwapRepositoryV2 import com.tangem.data.swap.DefaultSwapTransactionRepository import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.express.models.response.ExpressErrorResponse +import com.tangem.datasource.crypto.DataSignatureVerifier import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.express.ExpressRepository @@ -43,6 +44,8 @@ internal object SwapDataModule { coroutineDispatcher: CoroutineDispatcherProvider, appPreferencesStore: AppPreferencesStore, currencyStatusOperations: BaseCurrencyStatusOperations, + dataSignatureVerifier: DataSignatureVerifier, + @NetworkMoshi moshi: Moshi, ): SwapRepositoryV2 { return DefaultSwapRepositoryV2( tangemExpressApi = tangemExpressApi, @@ -50,6 +53,8 @@ internal object SwapDataModule { coroutineDispatcher = coroutineDispatcher, appPreferencesStore = appPreferencesStore, currencyStatusOperations = currencyStatusOperations, + dataSignatureVerifier = dataSignatureVerifier, + moshi = moshi, ) } diff --git a/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressError.kt b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressError.kt index 7d153e9bf1..6d3effb0c0 100644 --- a/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressError.kt +++ b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressError.kt @@ -2,7 +2,7 @@ package com.tangem.domain.express.models import java.math.BigDecimal -sealed class ExpressError { +sealed class ExpressError : Throwable() { abstract val code: Int diff --git a/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressProviderType.kt b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressProviderType.kt index 3c77da80fb..343c0bda7b 100644 --- a/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressProviderType.kt +++ b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressProviderType.kt @@ -21,4 +21,16 @@ enum class ExpressProviderType(val typeName: String) { @Json(name = "onramp") ONRAMP(typeName = "ONRAMP"), + ; + + companion object { + fun ExpressProviderType.shouldStoreSwapTransaction() = when (this) { + CEX, + DEX_BRIDGE, + -> true + DEX, + ONRAMP, + -> false + } + } } \ No newline at end of file diff --git a/domain/swap/build.gradle.kts b/domain/swap/build.gradle.kts index c0231df3f5..2cfa89ac20 100644 --- a/domain/swap/build.gradle.kts +++ b/domain/swap/build.gradle.kts @@ -23,9 +23,11 @@ dependencies { /** Util */ implementation(projects.core.utils) + implementation(projects.core.datasource) /** Other */ implementation(deps.arrow.core) implementation(deps.kotlin.coroutines) + implementation(deps.jodatime) } \ No newline at end of file diff --git a/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapDataModel.kt b/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapDataModel.kt new file mode 100644 index 0000000000..098e6cd413 --- /dev/null +++ b/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapDataModel.kt @@ -0,0 +1,47 @@ +package com.tangem.domain.swap.models + +import java.math.BigDecimal +import java.math.BigInteger + +data class SwapDataModel( + val toTokenAmount: BigDecimal, + val transaction: SwapDataTransactionModel, +) + +sealed class SwapDataTransactionModel { + + abstract val fromAmount: BigDecimal + abstract val toAmount: BigDecimal + abstract val txValue: String + abstract val txId: String + abstract val txTo: String + abstract val txExtraId: String? + + /** + * @param txValue amount for tx, should use native coin decimals, this value will send as native amount in tx + */ + data class DEX( + override val fromAmount: BigDecimal, + override val toAmount: BigDecimal, + override val txValue: String, + override val txId: String, + override val txTo: String, + override val txExtraId: String?, + val txFrom: String, + val txData: String, + val otherNativeFeeWei: BigDecimal?, + val gas: BigInteger, + ) : SwapDataTransactionModel() + + data class CEX( + override val fromAmount: BigDecimal, + override val toAmount: BigDecimal, + override val txValue: String, + override val txId: String, + override val txTo: String, + override val txExtraId: String?, + val externalTxId: String, + val externalTxUrl: String, + val txExtraIdName: String?, + ) : SwapDataTransactionModel() +} \ No newline at end of file diff --git a/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapRefundData.kt b/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapRefundData.kt new file mode 100644 index 0000000000..7d9c82d65d --- /dev/null +++ b/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapRefundData.kt @@ -0,0 +1,12 @@ +package com.tangem.domain.swap.models + +/** + * Refund data + * + * @param refundAddress address refund send to + * @param refundExtraId refund token id + */ +data class SwapRefundData( + val refundAddress: String? = null, + val refundExtraId: String? = null, +) \ No newline at end of file diff --git a/domain/swap/src/main/java/com/tangem/domain/swap/SwapRepositoryV2.kt b/domain/swap/src/main/java/com/tangem/domain/swap/SwapRepositoryV2.kt index 7bb2c091ba..a5124d565c 100644 --- a/domain/swap/src/main/java/com/tangem/domain/swap/SwapRepositoryV2.kt +++ b/domain/swap/src/main/java/com/tangem/domain/swap/SwapRepositoryV2.kt @@ -3,8 +3,10 @@ package com.tangem.domain.swap import com.tangem.domain.express.models.ExpressProvider import com.tangem.domain.express.models.ExpressRateType import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.swap.models.SwapDataModel import com.tangem.domain.swap.models.SwapPairModel import com.tangem.domain.swap.models.SwapQuoteModel +import com.tangem.domain.swap.models.SwapStatusModel import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet import java.math.BigDecimal @@ -12,6 +14,7 @@ import java.math.BigDecimal /** * Swap repository */ +@Suppress("LongParameterList") interface SwapRepositoryV2 { /** @@ -44,7 +47,6 @@ interface SwapRepositoryV2 { * @param provider selected express provider * @param rateType rate type */ - @Suppress("LongParameterList") suspend fun getSwapQuote( userWallet: UserWallet, fromCryptoCurrency: CryptoCurrency, @@ -53,4 +55,52 @@ interface SwapRepositoryV2 { provider: ExpressProvider, rateType: ExpressRateType, ): SwapQuoteModel + + /** + * Returns swap data [SwapDataModel] ready to sign and send on selected quote + * + * @param userWallet selected user wallet + * @param fromCryptoCurrencyStatus currency status being swapped from + * @param toCryptoCurrencyStatus currency status being swapped to + * @param fromAmount swap amount + * @param toAddress destination address (optional, if null send to self) + * @param expressProvider selected swap provider + * @param rateType selected provider rate type + */ + suspend fun getSwapData( + userWallet: UserWallet, + fromCryptoCurrencyStatus: CryptoCurrencyStatus, + toCryptoCurrencyStatus: CryptoCurrencyStatus, + fromAmount: String, + toAddress: String?, + expressProvider: ExpressProvider, + rateType: ExpressRateType, + ): SwapDataModel + + /** + * Send ExpressApi info that swap transaction occurred + * + * @param userWallet selected user wallet + * @param fromCryptoCurrencyStatus currency status being swapped from + * @param toAddress swap destination address + * @param txId transaction id in ExpressApi + * @param txHash transaction hash in blockchain + * @param txExtraId extra transaction id in ExpressApi + */ + suspend fun swapTransactionSent( + userWallet: UserWallet, + fromCryptoCurrencyStatus: CryptoCurrencyStatus, + toAddress: String, + txId: String, + txHash: String, + txExtraId: String?, + ) + + /** + * Returns status [SwapStatusModel] on active swap + * + * @param userWallet selected user wallet + * @param txId transaction id in ExpressApi + */ + suspend fun getExchangeStatus(userWallet: UserWallet, txId: String): SwapStatusModel } \ No newline at end of file diff --git a/domain/swap/src/main/java/com/tangem/domain/swap/usecase/GetSwapDataUseCase.kt b/domain/swap/src/main/java/com/tangem/domain/swap/usecase/GetSwapDataUseCase.kt new file mode 100644 index 0000000000..d1f98ede4c --- /dev/null +++ b/domain/swap/src/main/java/com/tangem/domain/swap/usecase/GetSwapDataUseCase.kt @@ -0,0 +1,40 @@ +package com.tangem.domain.swap.usecase + +import arrow.core.Either +import com.tangem.domain.express.models.ExpressError +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.express.models.ExpressRateType +import com.tangem.domain.swap.SwapErrorResolver +import com.tangem.domain.swap.SwapRepositoryV2 +import com.tangem.domain.swap.models.SwapDataModel +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.models.UserWallet + +@Suppress("LongParameterList") +class GetSwapDataUseCase( + private val swapRepositoryV2: SwapRepositoryV2, + private val swapErrorResolver: SwapErrorResolver, +) { + + suspend operator fun invoke( + userWallet: UserWallet, + fromCryptoCurrencyStatus: CryptoCurrencyStatus, + fromAmount: String, + toCryptoCurrencyStatus: CryptoCurrencyStatus, + toAddress: String?, + expressProvider: ExpressProvider, + rateType: ExpressRateType, + ): Either = Either.catch { + swapRepositoryV2.getSwapData( + userWallet = userWallet, + fromCryptoCurrencyStatus = fromCryptoCurrencyStatus, + fromAmount = fromAmount, + toCryptoCurrencyStatus = toCryptoCurrencyStatus, + toAddress = toAddress, + expressProvider = expressProvider, + rateType = rateType, + ) + }.mapLeft { throwable -> + swapErrorResolver.resolve(throwable) + } +} \ No newline at end of file diff --git a/domain/swap/src/main/java/com/tangem/domain/swap/usecase/SwapTransactionSentUseCase.kt b/domain/swap/src/main/java/com/tangem/domain/swap/usecase/SwapTransactionSentUseCase.kt new file mode 100644 index 0000000000..59ca0bb5d0 --- /dev/null +++ b/domain/swap/src/main/java/com/tangem/domain/swap/usecase/SwapTransactionSentUseCase.kt @@ -0,0 +1,67 @@ +package com.tangem.domain.swap.usecase + +import arrow.core.Either +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.express.models.ExpressProviderType.Companion.shouldStoreSwapTransaction +import com.tangem.domain.swap.SwapErrorResolver +import com.tangem.domain.swap.SwapRepositoryV2 +import com.tangem.domain.swap.SwapTransactionRepository +import com.tangem.domain.swap.models.SwapDataTransactionModel +import com.tangem.domain.swap.models.SwapStatus +import com.tangem.domain.swap.models.SwapStatusModel +import com.tangem.domain.swap.models.SwapTransactionModel +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.models.UserWallet + +@Suppress("LongParameterList") +class SwapTransactionSentUseCase( + private val swapRepositoryV2: SwapRepositoryV2, + private val swapTransactionRepository: SwapTransactionRepository, + private val swapErrorResolver: SwapErrorResolver, +) { + + suspend operator fun invoke( + userWallet: UserWallet, + fromCryptoCurrencyStatus: CryptoCurrencyStatus, + toCryptoCurrencyStatus: CryptoCurrencyStatus, + swapDataTransactionModel: SwapDataTransactionModel, + provider: ExpressProvider, + txHash: String, + ) = Either.catch { + swapRepositoryV2.swapTransactionSent( + userWallet = userWallet, + fromCryptoCurrencyStatus = fromCryptoCurrencyStatus, + toAddress = swapDataTransactionModel.txTo, + txId = swapDataTransactionModel.txId, + txHash = txHash, + txExtraId = swapDataTransactionModel.txExtraId, + ) + if (provider.type.shouldStoreSwapTransaction()) { + val timestamp = System.currentTimeMillis() + swapTransactionRepository.storeTransaction( + userWalletId = userWallet.walletId, + fromCryptoCurrency = fromCryptoCurrencyStatus.currency, + toCryptoCurrency = toCryptoCurrencyStatus.currency, + transaction = SwapTransactionModel( + txId = swapDataTransactionModel.txId, + provider = provider, + timestamp = timestamp, + fromCryptoAmount = swapDataTransactionModel.fromAmount, + toCryptoAmount = swapDataTransactionModel.toAmount, + status = SwapStatusModel( + providerId = provider.providerId, + status = SwapStatus.New, + txId = swapDataTransactionModel.txId, + txExternalUrl = (swapDataTransactionModel as? SwapDataTransactionModel.CEX)?.externalTxUrl, + txExternalId = (swapDataTransactionModel as? SwapDataTransactionModel.CEX)?.externalTxId, + averageDuration = null, + ), + ), + ) + } + swapTransactionRepository.storeLastSwappedCryptoCurrencyId( + userWalletId = userWallet.walletId, + cryptoCurrencyId = toCryptoCurrencyStatus.currency.id, + ) + }.mapLeft(swapErrorResolver::resolve) +} \ No newline at end of file