Updated on 2026-08-14
This commit is contained in:
parent
b73c5486d3
commit
e80ade9558
13 changed files with 470 additions and 10 deletions
|
|
@ -13,6 +13,7 @@ internal class DefaultSwapErrorResolver(
|
|||
is ApiResponseError.HttpException -> {
|
||||
expressErrorConverter.convert(throwable.errorBody.orEmpty())
|
||||
}
|
||||
is ExpressError -> throwable
|
||||
else -> ExpressError.UnknownError
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue