Updated on 2026-08-14
This commit is contained in:
parent
b73c5486d3
commit
e80ade9558
13 changed files with 470 additions and 10 deletions
|
|
@ -3,10 +3,7 @@ package com.tangem.tap.di.domain
|
||||||
import com.tangem.domain.swap.SwapErrorResolver
|
import com.tangem.domain.swap.SwapErrorResolver
|
||||||
import com.tangem.domain.swap.SwapRepositoryV2
|
import com.tangem.domain.swap.SwapRepositoryV2
|
||||||
import com.tangem.domain.swap.SwapTransactionRepository
|
import com.tangem.domain.swap.SwapTransactionRepository
|
||||||
import com.tangem.domain.swap.usecase.GetSwapPairsUseCase
|
import com.tangem.domain.swap.usecase.*
|
||||||
import com.tangem.domain.swap.usecase.GetSwapQuoteUseCase
|
|
||||||
import com.tangem.domain.swap.usecase.GetSwapSupportedPairsUseCase
|
|
||||||
import com.tangem.domain.swap.usecase.SelectInitialPairUseCase
|
|
||||||
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
|
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
@ -73,4 +70,30 @@ internal object SwapDomainModule {
|
||||||
swapErrorResolver = swapErrorResolver,
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ internal class DefaultSwapErrorResolver(
|
||||||
is ApiResponseError.HttpException -> {
|
is ApiResponseError.HttpException -> {
|
||||||
expressErrorConverter.convert(throwable.errorBody.orEmpty())
|
expressErrorConverter.convert(throwable.errorBody.orEmpty())
|
||||||
}
|
}
|
||||||
|
is ExpressError -> throwable
|
||||||
else -> ExpressError.UnknownError
|
else -> ExpressError.UnknownError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,28 @@
|
||||||
package com.tangem.data.swap
|
package com.tangem.data.swap
|
||||||
|
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
import com.tangem.data.common.api.safeApiCall
|
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.data.swap.converter.TokenInfoConverter
|
||||||
import com.tangem.datasource.api.common.response.getOrThrow
|
import com.tangem.datasource.api.common.response.getOrThrow
|
||||||
import com.tangem.datasource.api.express.TangemExpressApi
|
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.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.exchangeservice.swap.ExpressUtils
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.domain.express.ExpressRepository
|
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.ExpressProvider
|
||||||
|
import com.tangem.domain.express.models.ExpressProviderType
|
||||||
import com.tangem.domain.express.models.ExpressRateType
|
import com.tangem.domain.express.models.ExpressRateType
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.swap.SwapRepositoryV2
|
import com.tangem.domain.swap.SwapRepositoryV2
|
||||||
import com.tangem.domain.swap.models.SwapPairModel
|
import com.tangem.domain.swap.models.*
|
||||||
import com.tangem.domain.swap.models.SwapQuoteModel
|
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||||
import com.tangem.domain.wallets.models.UserWallet
|
import com.tangem.domain.wallets.models.UserWallet
|
||||||
|
|
@ -24,19 +33,26 @@ import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.io.IOException
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList", "LargeClass")
|
||||||
internal class DefaultSwapRepositoryV2 @Inject constructor(
|
internal class DefaultSwapRepositoryV2 @Inject constructor(
|
||||||
private val tangemExpressApi: TangemExpressApi,
|
private val tangemExpressApi: TangemExpressApi,
|
||||||
private val expressRepository: ExpressRepository,
|
private val expressRepository: ExpressRepository,
|
||||||
private val coroutineDispatcher: CoroutineDispatcherProvider,
|
private val coroutineDispatcher: CoroutineDispatcherProvider,
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
private val currencyStatusOperations: BaseCurrencyStatusOperations,
|
private val currencyStatusOperations: BaseCurrencyStatusOperations,
|
||||||
|
private val dataSignatureVerifier: DataSignatureVerifier,
|
||||||
|
@NetworkMoshi moshi: Moshi,
|
||||||
) : SwapRepositoryV2 {
|
) : SwapRepositoryV2 {
|
||||||
|
|
||||||
|
private val swapDataConverter = SwapDataConverter()
|
||||||
private val tokenInfoConverter = TokenInfoConverter()
|
private val tokenInfoConverter = TokenInfoConverter()
|
||||||
|
private val exchangeStatusConverter = SwapStatusConverter()
|
||||||
|
private val txDetailsMoshiAdapter = moshi.adapter(TxDetails::class.java)
|
||||||
|
|
||||||
override suspend fun getPairs(
|
override suspend fun getPairs(
|
||||||
userWallet: UserWallet,
|
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(
|
private suspend fun CoroutineScope.getPairsInternal(
|
||||||
userWallet: UserWallet,
|
userWallet: UserWallet,
|
||||||
initialCurrency: CryptoCurrency,
|
initialCurrency: CryptoCurrency,
|
||||||
|
|
@ -200,7 +325,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
private suspend fun CoroutineScope.createPairModelOnly(
|
private suspend fun createPairModelOnly(
|
||||||
currencyFrom: CryptoCurrency?,
|
currencyFrom: CryptoCurrency?,
|
||||||
currencyTo: CryptoCurrency?,
|
currencyTo: CryptoCurrency?,
|
||||||
userWalletId: UserWalletId,
|
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 {
|
private fun CryptoCurrency.getContractAddress(): String {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is CryptoCurrency.Token -> this.contractAddress
|
is CryptoCurrency.Token -> this.contractAddress
|
||||||
|
|
|
||||||
|
|
@ -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<ExchangeDataResponseWithTxDetails, SwapDataModel> {
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import com.tangem.data.swap.DefaultSwapRepositoryV2
|
||||||
import com.tangem.data.swap.DefaultSwapTransactionRepository
|
import com.tangem.data.swap.DefaultSwapTransactionRepository
|
||||||
import com.tangem.datasource.api.express.TangemExpressApi
|
import com.tangem.datasource.api.express.TangemExpressApi
|
||||||
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
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.di.NetworkMoshi
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.domain.express.ExpressRepository
|
import com.tangem.domain.express.ExpressRepository
|
||||||
|
|
@ -43,6 +44,8 @@ internal object SwapDataModule {
|
||||||
coroutineDispatcher: CoroutineDispatcherProvider,
|
coroutineDispatcher: CoroutineDispatcherProvider,
|
||||||
appPreferencesStore: AppPreferencesStore,
|
appPreferencesStore: AppPreferencesStore,
|
||||||
currencyStatusOperations: BaseCurrencyStatusOperations,
|
currencyStatusOperations: BaseCurrencyStatusOperations,
|
||||||
|
dataSignatureVerifier: DataSignatureVerifier,
|
||||||
|
@NetworkMoshi moshi: Moshi,
|
||||||
): SwapRepositoryV2 {
|
): SwapRepositoryV2 {
|
||||||
return DefaultSwapRepositoryV2(
|
return DefaultSwapRepositoryV2(
|
||||||
tangemExpressApi = tangemExpressApi,
|
tangemExpressApi = tangemExpressApi,
|
||||||
|
|
@ -50,6 +53,8 @@ internal object SwapDataModule {
|
||||||
coroutineDispatcher = coroutineDispatcher,
|
coroutineDispatcher = coroutineDispatcher,
|
||||||
appPreferencesStore = appPreferencesStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
currencyStatusOperations = currencyStatusOperations,
|
currencyStatusOperations = currencyStatusOperations,
|
||||||
|
dataSignatureVerifier = dataSignatureVerifier,
|
||||||
|
moshi = moshi,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package com.tangem.domain.express.models
|
||||||
|
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
sealed class ExpressError {
|
sealed class ExpressError : Throwable() {
|
||||||
|
|
||||||
abstract val code: Int
|
abstract val code: Int
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,16 @@ enum class ExpressProviderType(val typeName: String) {
|
||||||
|
|
||||||
@Json(name = "onramp")
|
@Json(name = "onramp")
|
||||||
ONRAMP(typeName = "ONRAMP"),
|
ONRAMP(typeName = "ONRAMP"),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun ExpressProviderType.shouldStoreSwapTransaction() = when (this) {
|
||||||
|
CEX,
|
||||||
|
DEX_BRIDGE,
|
||||||
|
-> true
|
||||||
|
DEX,
|
||||||
|
ONRAMP,
|
||||||
|
-> false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -23,9 +23,11 @@ dependencies {
|
||||||
|
|
||||||
/** Util */
|
/** Util */
|
||||||
implementation(projects.core.utils)
|
implementation(projects.core.utils)
|
||||||
|
implementation(projects.core.datasource)
|
||||||
|
|
||||||
/** Other */
|
/** Other */
|
||||||
implementation(deps.arrow.core)
|
implementation(deps.arrow.core)
|
||||||
implementation(deps.kotlin.coroutines)
|
implementation(deps.kotlin.coroutines)
|
||||||
|
implementation(deps.jodatime)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
@ -3,8 +3,10 @@ package com.tangem.domain.swap
|
||||||
import com.tangem.domain.express.models.ExpressProvider
|
import com.tangem.domain.express.models.ExpressProvider
|
||||||
import com.tangem.domain.express.models.ExpressRateType
|
import com.tangem.domain.express.models.ExpressRateType
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
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.SwapPairModel
|
||||||
import com.tangem.domain.swap.models.SwapQuoteModel
|
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.tokens.model.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.wallets.models.UserWallet
|
import com.tangem.domain.wallets.models.UserWallet
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
@ -12,6 +14,7 @@ import java.math.BigDecimal
|
||||||
/**
|
/**
|
||||||
* Swap repository
|
* Swap repository
|
||||||
*/
|
*/
|
||||||
|
@Suppress("LongParameterList")
|
||||||
interface SwapRepositoryV2 {
|
interface SwapRepositoryV2 {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -44,7 +47,6 @@ interface SwapRepositoryV2 {
|
||||||
* @param provider selected express provider
|
* @param provider selected express provider
|
||||||
* @param rateType rate type
|
* @param rateType rate type
|
||||||
*/
|
*/
|
||||||
@Suppress("LongParameterList")
|
|
||||||
suspend fun getSwapQuote(
|
suspend fun getSwapQuote(
|
||||||
userWallet: UserWallet,
|
userWallet: UserWallet,
|
||||||
fromCryptoCurrency: CryptoCurrency,
|
fromCryptoCurrency: CryptoCurrency,
|
||||||
|
|
@ -53,4 +55,52 @@ interface SwapRepositoryV2 {
|
||||||
provider: ExpressProvider,
|
provider: ExpressProvider,
|
||||||
rateType: ExpressRateType,
|
rateType: ExpressRateType,
|
||||||
): SwapQuoteModel
|
): 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
|
||||||
}
|
}
|
||||||
|
|
@ -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<ExpressError, SwapDataModel> = Either.catch {
|
||||||
|
swapRepositoryV2.getSwapData(
|
||||||
|
userWallet = userWallet,
|
||||||
|
fromCryptoCurrencyStatus = fromCryptoCurrencyStatus,
|
||||||
|
fromAmount = fromAmount,
|
||||||
|
toCryptoCurrencyStatus = toCryptoCurrencyStatus,
|
||||||
|
toAddress = toAddress,
|
||||||
|
expressProvider = expressProvider,
|
||||||
|
rateType = rateType,
|
||||||
|
)
|
||||||
|
}.mapLeft { throwable ->
|
||||||
|
swapErrorResolver.resolve(throwable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue