Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-24 01:25:44 +02:00
parent 464f8f8a80
commit 63e52abd27
14 changed files with 141 additions and 133 deletions

View file

@ -42,9 +42,9 @@ interface TangemExpressApi {
@Query("fromNetwork") fromNetwork: String,
@Query("toContractAddress") toContractAddress: String,
@Query("toNetwork") toNetwork: String,
@Query("fromAmount") fromAmount: BigDecimal,
@Query("providerId") providerId: Int,
@Query("rateType") rateType: RateType,
@Query("fromAmount") fromAmount: String,
@Query("providerId") providerId: String,
@Query("rateType") rateType: String,
@Query("toAddress") toAddress: String,
): ApiResponse<ExchangeDataResponse>

View file

@ -4,8 +4,17 @@ import com.squareup.moshi.Json
import java.math.BigDecimal
data class ExchangeDataResponse(
@Json(name = "fromAmount")
val fromAmount: String,
@Json(name = "fromDecimals")
val fromDecimals: Int,
@Json(name = "toAmount")
val toAmount: BigDecimal,
val toAmount: String,
@Json(name = "toDecimals")
val toDecimals: Int,
@Json(name = "txType")
val txType: TxType,

View file

@ -22,7 +22,6 @@ dependencies {
implementation(project(":data:common"))
implementation(project(":libs:auth"))
implementation(project(":libs:crypto"))
implementation(projects.domain.tokens.models)
implementation(deps.material)

View file

@ -49,7 +49,7 @@ internal class SwapRepositoryImpl @Inject constructor(
) : SwapRepository {
private val tokensConverter = TokensConverter()
private val swapConverter = SwapConverter()
private val expressDataConverter = ExpressDataConverter()
private val leastTokenInfoConverter = LeastTokenInfoConverter()
private val swapPairInfoConverter = SwapPairInfoConverter()
private val cryptoCurrencyFactory = CryptoCurrencyFactory()
@ -164,8 +164,8 @@ internal class SwapRepositoryImpl @Inject constructor(
toTokenAmount = createFromAmountWithOffset(response.toAmount, response.toDecimals),
),
)
} catch (ex: OneIncResponseException) {
AggregatedSwapDataModel(null, mapErrors(ex.data.description))
} catch (ex: Exception) {
AggregatedSwapDataModel(null, mapErrors(ex.message))
}
}
}
@ -176,31 +176,33 @@ internal class SwapRepositoryImpl @Inject constructor(
}
}
override suspend fun prepareSwapTransaction(
networkId: String,
fromTokenAddress: String,
toTokenAddress: String,
amount: String,
fromWalletAddress: String,
slippage: Int,
override suspend fun getExchangeData(
fromContractAddress: String,
fromNetwork: String,
toContractAddress: String,
toNetwork: String,
fromAmount: String,
providerId: String,
rateType: RateType,
toAddress: String,
): AggregatedSwapDataModel<SwapDataModel> {
return withContext(coroutineDispatcher.io) {
try {
val swapResponse = oneInchErrorsHandler.handleOneInchResponse(
getOneInchApi(networkId).swap(
fromTokenAddress = fromTokenAddress,
toTokenAddress = toTokenAddress,
amount = amount,
fromAddress = fromWalletAddress,
slippage = slippage,
referrerAddress = configManager.config.swapReferrerAccount?.address,
fee = configManager.config.swapReferrerAccount?.fee,
),
val response = tangemExpressApi.getExchangeData(
fromContractAddress = fromContractAddress,
fromNetwork = fromNetwork,
toContractAddress = toContractAddress,
toNetwork = toNetwork,
fromAmount = fromAmount,
providerId = providerId,
rateType = rateType.name.lowercase(),
toAddress = toAddress
).getOrThrow()
AggregatedSwapDataModel(
dataModel = expressDataConverter.convert(response)
)
AggregatedSwapDataModel(swapConverter.convert(swapResponse))
} catch (ex: OneIncResponseException) {
AggregatedSwapDataModel(null, mapErrors(ex.data.description))
} catch (ex: Exception) {
AggregatedSwapDataModel(null, mapErrors(ex.message))
}
}
}
@ -280,31 +282,6 @@ internal class SwapRepositoryImpl @Inject constructor(
return oneInchApiFactory.getApi(networkId)
}
override suspend fun getExchangeQuote(
fromContractAddress: String,
fromNetwork: String,
toContractAddress: String,
toNetwork: String,
fromAmount: String,
providerId: String,
rateType: RateType,
): ExchangeQuote {
val response = tangemExpressApi.getExchangeQuote(
fromContractAddress,
fromNetwork,
toContractAddress,
toNetwork,
fromAmount,
providerId,
rateType.name.lowercase(),
).getOrThrow()
return ExchangeQuote(
toAmount = response.toAmount,
allowanceContract = response.allowanceContract,
)
}
override fun getNativeTokenForNetwork(networkId: String): CryptoCurrency {
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }

View file

@ -0,0 +1,41 @@
package com.tangem.feature.swap.converters
import com.tangem.datasource.api.express.models.response.ExchangeDataResponse
import com.tangem.datasource.api.express.models.response.TxType
import com.tangem.feature.swap.domain.models.createFromAmountWithOffset
import com.tangem.feature.swap.domain.models.domain.ExpressTransactionModel
import com.tangem.feature.swap.domain.models.domain.SwapDataModel
import com.tangem.utils.converter.Converter
class ExpressDataConverter : Converter<ExchangeDataResponse, SwapDataModel> {
override fun convert(value: ExchangeDataResponse): SwapDataModel {
return SwapDataModel(
toTokenAmount = createFromAmountWithOffset(value.toAmount, value.toDecimals),
transaction = convertTransaction(value),
)
}
private fun convertTransaction(transactionDto: ExchangeDataResponse): ExpressTransactionModel {
return if (transactionDto.txType == TxType.SWAP) {
ExpressTransactionModel.DEX(
fromAmount = createFromAmountWithOffset(transactionDto.fromAmount, transactionDto.fromDecimals),
toAmount = createFromAmountWithOffset(transactionDto.toAmount, transactionDto.toDecimals),
txId = transactionDto.txId,
txTo = transactionDto.txTo,
txFrom = requireNotNull(transactionDto.txFrom),
txData = requireNotNull(transactionDto.txData)
)
} else {
ExpressTransactionModel.CEX(
fromAmount = createFromAmountWithOffset(transactionDto.fromAmount, transactionDto.fromDecimals),
toAmount = createFromAmountWithOffset(transactionDto.toAmount, transactionDto.toDecimals),
txId = transactionDto.txId,
txTo = transactionDto.txTo,
externalTxId = requireNotNull(transactionDto.externalTxId),
externalTxUrl = requireNotNull(transactionDto.externalTxUrl),
)
}
}
}

View file

@ -1,29 +0,0 @@
package com.tangem.feature.swap.converters
import com.tangem.datasource.api.oneinch.models.SwapResponse
import com.tangem.datasource.api.oneinch.models.TransactionDto
import com.tangem.feature.swap.domain.models.createFromAmountWithOffset
import com.tangem.feature.swap.domain.models.domain.SwapDataModel
import com.tangem.feature.swap.domain.models.domain.TransactionModel
import com.tangem.utils.converter.Converter
class SwapConverter : Converter<SwapResponse, SwapDataModel> {
override fun convert(value: SwapResponse): SwapDataModel {
return SwapDataModel(
toTokenAmount = createFromAmountWithOffset(value.toTokenAmount, value.toToken.decimals),
transaction = convertTransaction(value.transaction),
)
}
private fun convertTransaction(transactionDto: TransactionDto): TransactionModel {
return TransactionModel(
fromWalletAddress = transactionDto.fromAddress,
toWalletAddress = transactionDto.toAddress,
data = transactionDto.data,
value = transactionDto.value,
gasPrice = transactionDto.gasPrice,
gas = transactionDto.gas,
)
}
}

View file

@ -286,8 +286,8 @@ internal class SwapInteractorImpl @Inject constructor(
currencyToSend = swapCurrencyConverter.convert(currencyToSend),
feeAmount = fee.feeValue,
gasLimit = fee.gasLimit,
destinationAddress = swapStateData.swapModel.transaction.toWalletAddress,
dataToSign = swapStateData.swapModel.transaction.data,
destinationAddress = swapStateData.swapModel.transaction.txTo,
dataToSign = (swapStateData.swapModel.transaction as ExpressTransactionModel.DEX).txData,
),
isSwap = true,
derivationPath = derivationPath,
@ -518,13 +518,21 @@ internal class SwapInteractorImpl @Inject constructor(
amount: SwapAmount,
selectedFee: FeeType,
): SwapState {
repository.prepareSwapTransaction(
networkId = networkId,
fromTokenAddress = fromTokenAddress,
toTokenAddress = toTokenAddress,
amount = amount.toStringWithRightOffset(),
slippage = DEFAULT_SLIPPAGE,
fromWalletAddress = getWalletAddress(networkId),
repository.getExchangeData(
fromContractAddress = fromToken.currency.getContractAddress(),
fromNetwork = fromToken.currency.network.backendId,
toContractAddress = toToken.currency.getContractAddress(),
toNetwork = toToken.currency.network.backendId,
fromAmount = amount.toStringWithRightOffset(),
providerId = "1", //TODO
rateType = RateType.FLOAT,
toAddress = toToken.value.networkAddress?.defaultAddress ?: ""
// networkId = networkId,
// fromTokenAddress = fromTokenAddress,
// toTokenAddress = toTokenAddress,
// amount = amount.toStringWithRightOffset(),
// slippage = DEFAULT_SLIPPAGE,
// fromWalletAddress = getWalletAddress(networkId),
).let {
val swapData = it.dataModel
if (swapData != null) {
@ -532,9 +540,9 @@ internal class SwapInteractorImpl @Inject constructor(
networkId = networkId,
amountToSend = amount.value,
currencyToSend = swapCurrencyConverter.convert(fromToken.currency),
destinationAddress = swapData.transaction.toWalletAddress,
destinationAddress = swapData.transaction.txTo,
increaseBy = INCREASE_GAS_LIMIT_BY,
data = swapData.transaction.data,
data = (swapData.transaction as ExpressTransactionModel.DEX).txData,
derivationPath = derivationPath,
)
val txFeeState = proxyFeesToFeeState(networkId, feeData)

View file

@ -32,16 +32,6 @@ interface SwapRepository {
*/
suspend fun addressForTrust(networkId: String): String
@Suppress("LongParameterList")
suspend fun prepareSwapTransaction(
networkId: String,
fromTokenAddress: String,
toTokenAddress: String,
amount: String,
fromWalletAddress: String,
slippage: Int,
): AggregatedSwapDataModel<SwapDataModel>
/**
* Returns a tangem fee for swap in percents
* Example: 0.35%
@ -67,7 +57,7 @@ interface SwapRepository {
): String
@Suppress("LongParameterList")
suspend fun getExchangeQuote(
suspend fun getExchangeData(
fromContractAddress: String,
fromNetwork: String,
toContractAddress: String,
@ -75,7 +65,8 @@ interface SwapRepository {
fromAmount: String,
providerId: String,
rateType: RateType,
): ExchangeQuote
toAddress: String,
): AggregatedSwapDataModel<SwapDataModel>
fun getNativeTokenForNetwork(networkId: String): CryptoCurrency
}

View file

@ -1,20 +1,10 @@
package com.tangem.feature.swap.domain.models
sealed class DataError {
object NoError : DataError()
data class UnknownError(val message: String) : DataError()
object InsufficientLiquidity : DataError()
object UnknownError : DataError()
data class Error(val message: String) : DataError()
}
fun mapErrors(error: String?): DataError {
return if (error == null) {
DataError.NoError
} else {
when (error) {
INSUFFICIENT_LIQUIDITY_ERROR -> DataError.InsufficientLiquidity
else -> DataError.UnknownError(error)
}
}
}
private const val INSUFFICIENT_LIQUIDITY_ERROR = "insufficient liquidity"
return error?.let { DataError.Error(it) } ?: DataError.UnknownError
}

View file

@ -11,5 +11,5 @@ import com.tangem.feature.swap.domain.models.DataError
*/
data class AggregatedSwapDataModel<T>(
val dataModel: T?,
val error: DataError = DataError.NoError,
val error: DataError = DataError.UnknownError,
)

View file

@ -0,0 +1,30 @@
package com.tangem.feature.swap.domain.models.domain
import com.tangem.feature.swap.domain.models.SwapAmount
sealed class ExpressTransactionModel {
abstract val fromAmount: SwapAmount
abstract val toAmount: SwapAmount
abstract val txId: String
abstract val txTo: String
data class DEX(
override val fromAmount: SwapAmount,
override val toAmount: SwapAmount,
override val txId: String,
override val txTo: String,
val txFrom: String,
val txData: String,
): ExpressTransactionModel()
data class CEX(
override val fromAmount: SwapAmount,
override val toAmount: SwapAmount,
override val txId: String,
override val txTo: String,
val externalTxId: String,
val externalTxUrl: String,
): ExpressTransactionModel()
}

View file

@ -2,13 +2,7 @@ package com.tangem.feature.swap.domain.models.domain
import com.tangem.feature.swap.domain.models.SwapAmount
/**
* Swap transaction model
*
* @property toTokenAmount amount "target" token
* @property transaction info about transaction
*/
data class SwapDataModel(
val toTokenAmount: SwapAmount,
val transaction: TransactionModel,
val transaction: ExpressTransactionModel,
)

View file

@ -574,7 +574,7 @@ internal class StateBuilder(
// todo use if needed later
// DataError.InsufficientLiquidity -> TODO()
// DataError.NoError -> TODO()
is DataError.UnknownError -> addWarning(uiState, error.message, true, onClick)
is DataError.Error -> addWarning(uiState, error.message, true, onClick)
else -> addWarning(uiState, null, false) {}
}
}

View file

@ -1,12 +1,10 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.jvm)
id("configuration")
}
dependencies {
/** Coroutines */
implementation(deps.kotlin.coroutines)
implementation(projects.domain.tokens.models)
}