Updated on 2026-08-14
This commit is contained in:
parent
464f8f8a80
commit
63e52abd27
14 changed files with 141 additions and 133 deletions
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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()
|
||||
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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) {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue