Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-21 13:47:40 +02:00
parent 5ad1e47e65
commit fc9be53caf
9 changed files with 87 additions and 83 deletions

View file

@ -1,53 +0,0 @@
package com.tangem.datasource.api.express
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.express.models.request.AssetsRequestBody
import com.tangem.datasource.api.express.models.request.PairsRequestBody
import com.tangem.datasource.api.express.models.response.*
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
import java.math.BigDecimal
/**
* Interface of Tangem Express API (new swap mechanism)
*/
@Suppress("LongParameterList")
interface ExpressApi {
@POST("assets")
suspend fun getAssets(@Body body: AssetsRequestBody): ApiResponse<List<Asset>>
@POST("pairs")
suspend fun getPairs(@Body body: PairsRequestBody): ApiResponse<List<SwapPair>>
@GET("providers")
suspend fun getProviders(): ApiResponse<List<ExchangeProvider>>
@GET("exchange-quote")
suspend fun getExchangeQuote(
@Query("fromContractAddress") fromContractAddress: String,
@Query("fromNetwork") fromNetwork: String,
@Query("toContractAddress") toContractAddress: String,
@Query("toNetwork") toNetwork: String,
@Query("fromAmount") fromAmount: BigDecimal,
@Query("providerId") providerId: Int,
@Query("rateType") rateType: RateType,
): ApiResponse<ExchangeQuoteResponse>
@GET("exchange-data")
suspend fun getExchangeData(
@Query("fromContractAddress") fromContractAddress: String,
@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("toAddress") toAddress: String,
): ApiResponse<ExchangeDataResponse>
@GET("exchange-result")
suspend fun getExchangeResults(@Query("txId") txId: String): ApiResponse<ExchangeResultsResponse>
}

View file

@ -31,9 +31,9 @@ interface TangemExpressApi {
@Query("fromNetwork") fromNetwork: String,
@Query("toContractAddress") toContractAddress: String,
@Query("toNetwork") toNetwork: String,
@Query("fromAmount") fromAmount: BigDecimal,
@Query("fromAmount") fromAmount: String,
@Query("providerId") providerId: Int,
@Query("rateType") rateType: RateType,
@Query("rateType") rateType: String,
): ApiResponse<ExchangeQuoteResponse>
@GET("exchange-data")

View file

@ -4,9 +4,23 @@ import com.squareup.moshi.Json
import java.math.BigDecimal
data class ExchangeQuoteResponse(
@Json(name = "fromAmount")
val fromAmount: BigDecimal,
@Json(name = "fromDecimals")
val fromDecimals: Int,
@Json(name = "toAmount")
val toAmount: BigDecimal,
@Json(name = "toDecimals")
val toDecimals: Int,
@Json(name = "allowanceContract")
val allowanceContract: String?,
@Json(name = "minAmount")
val minAmount: BigDecimal,
)

View file

@ -20,13 +20,16 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.swap.converters.*
import com.tangem.feature.swap.domain.SwapRepository
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.data.AggregatedSwapDataModel
import com.tangem.feature.swap.domain.models.domain.*
import com.tangem.feature.swap.domain.models.mapErrors
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import retrofit2.http.Query
import java.math.BigDecimal
import java.util.Locale
import javax.inject.Inject
import com.tangem.datasource.api.express.models.request.LeastTokenInfo as NetworkLeastTokenInfo
@ -127,21 +130,33 @@ internal class SwapRepositoryImpl @Inject constructor(
}
override suspend fun findBestQuote(
networkId: String,
fromTokenAddress: String,
toTokenAddress: String,
amount: String,
fromContractAddress: String,
fromNetwork: String,
toContractAddress: String,
toNetwork: String,
fromAmount: String,
providerId: Int,
rateType: RateType,
): AggregatedSwapDataModel<QuoteModel> {
return withContext(coroutineDispatcher.io) {
try {
val response = oneInchErrorsHandler.handleOneInchResponse(
getOneInchApi(networkId).quote(
fromTokenAddress = fromTokenAddress,
toTokenAddress = toTokenAddress,
amount = amount,
),
val response = tangemExpressApi.getExchangeQuote(
fromContractAddress = fromContractAddress,
fromNetwork = fromNetwork,
toContractAddress = toContractAddress,
toNetwork = toNetwork,
fromAmount = fromAmount,
providerId = providerId,
rateType = rateType.name.lowercase()
).getOrThrow()
AggregatedSwapDataModel(
dataModel = QuoteModel(
SwapAmount(
value = response.toAmount,
decimals = response.toDecimals
)
)
)
AggregatedSwapDataModel(dataModel = quotesConverter.convert(response))
} catch (ex: OneIncResponseException) {
AggregatedSwapDataModel(null, mapErrors(ex.data.description))
}
@ -263,7 +278,7 @@ internal class SwapRepositoryImpl @Inject constructor(
fromNetwork: String,
toContractAddress: String,
toNetwork: String,
fromAmount: BigDecimal,
fromAmount: String,
providerId: Int,
rateType: RateType
): ExchangeQuote {
@ -274,7 +289,7 @@ internal class SwapRepositoryImpl @Inject constructor(
toNetwork,
fromAmount,
providerId,
rateTypeConverter.convertBack(rateType)
rateType.name.lowercase()
).getOrThrow()
return ExchangeQuote(

View file

@ -66,6 +66,7 @@ interface SwapInteractor {
networkId: String,
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
providers: List<SwapProvider>,
amountToSwap: String,
selectedFee: FeeType = FeeType.NORMAL,
): SwapState

View file

@ -274,6 +274,7 @@ internal class SwapInteractorImpl @Inject constructor(
networkId: String,
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
providers: List<SwapProvider>,
amountToSwap: String,
selectedFee: FeeType,
): SwapState {
@ -311,6 +312,7 @@ internal class SwapInteractorImpl @Inject constructor(
toToken = toToken,
isAllowedToSpend = isAllowedToSpend,
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
providers = providers
)
}
}
@ -518,14 +520,23 @@ internal class SwapInteractorImpl @Inject constructor(
amount: SwapAmount,
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
providers: List<SwapProvider>,
isAllowedToSpend: Boolean,
isBalanceWithoutFeeEnough: Boolean,
): SwapState {
repository.findBestQuote(
networkId = networkId,
fromTokenAddress = fromTokenAddress,
toTokenAddress = toTokenAddress,
amount = amount.toStringWithRightOffset(),
fromContractAddress = fromToken.getContractAddress(),
fromNetwork = fromToken.network.backendId,
toContractAddress = toToken.getContractAddress(),
toNetwork = toToken.network.backendId,
fromAmount = amount.toStringWithRightOffset(),
providerId = providers[0].providerId,
rateType = RateType.FLOAT
// networkId = networkId,
// fromTokenAddress = fromTokenAddress,
// toTokenAddress = toTokenAddress,
// amount = amount.toStringWithRightOffset(),
).let { quotes ->
val quoteDataModel = quotes.dataModel
if (quoteDataModel != null) {
@ -668,16 +679,16 @@ internal class SwapInteractorImpl @Inject constructor(
tokenWalletBalance = toTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") }
?: ZERO_BALANCE,
tokenFiatBalance = toTokenAmount.value.toFiatString(
rateValue = rates[toToken.id.value]?.toBigDecimal() ?: BigDecimal.ZERO,
rateValue = rates[toToken.network.backendId]?.toBigDecimal() ?: BigDecimal.ZERO,
fiatCurrencyName = appCurrency.symbol,
formatWithSpaces = true,
),
),
priceImpact = calculatePriceImpact(
fromTokenAmount = fromTokenAmount.value,
fromRate = rates[fromToken.id.value] ?: 0.0,
fromRate = rates[fromToken.network.backendId] ?: 0.0,
toTokenAmount = toTokenAmount.value,
toRate = rates[toToken.id.value] ?: 0.0,
toRate = rates[toToken.network.backendId] ?: 0.0,
),
networkCurrency = userWalletManager.getNetworkCurrency(networkId),
swapDataModel = swapStateData,
@ -819,7 +830,7 @@ internal class SwapInteractorImpl @Inject constructor(
private fun getTokenAddress(currency: CryptoCurrency): String {
return when (currency) {
is CryptoCurrency.Coin -> {
DEFAULT_BLOCKCHAIN_INCH_ADDRESS
"0"
}
is CryptoCurrency.Token -> {
currency.contractAddress

View file

@ -15,10 +15,13 @@ interface SwapRepository {
suspend fun getExchangeableTokens(networkId: String): List<Currency>
suspend fun findBestQuote(
networkId: String,
fromTokenAddress: String,
toTokenAddress: String,
amount: String,
fromContractAddress: String,
fromNetwork: String,
toContractAddress: String,
toNetwork: String,
fromAmount: String,
providerId: Int,
rateType: RateType,
): AggregatedSwapDataModel<QuoteModel>
/**
@ -67,7 +70,7 @@ interface SwapRepository {
fromNetwork: String,
toContractAddress: String,
toNetwork: String,
fromAmount: BigDecimal,
fromAmount: String,
providerId: Int,
rateType: RateType,
) : ExchangeQuote

View file

@ -9,6 +9,7 @@ import java.math.BigDecimal
sealed interface SwapState {
data class QuotesLoadedState(
//add map < Provider ID, Swap state data>
val fromTokenInfo: TokenSwapInfo,
val toTokenInfo: TokenSwapInfo,
val priceImpact: Float,

View file

@ -13,6 +13,8 @@ import com.tangem.feature.swap.analytics.SwapEvents
import com.tangem.feature.swap.domain.BlockchainInteractor
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.feature.swap.domain.models.domain.PermissionOptions
import com.tangem.feature.swap.domain.models.domain.RateType
import com.tangem.feature.swap.domain.models.domain.SwapProvider
import com.tangem.feature.swap.domain.models.formatToUIRepresentation
import com.tangem.feature.swap.domain.models.ui.*
import com.tangem.feature.swap.models.SwapPermissionState
@ -138,10 +140,12 @@ internal class SwapViewModel @Inject constructor(
)
// updateTokensState(dataState = state.foundTokensState)
val toToken = state.toGroup.available.first()
startLoadingQuotes(
fromToken = initialCryptoCurrency,
toToken = state.toGroup.available.first().currencyStatus.currency,
toToken = toToken.currencyStatus.currency,
amount = lastAmount.value,
toProvidersList = toToken.providers
)
}.onFailure {
Timber.tag(loggingTag).e(it)
@ -179,7 +183,12 @@ internal class SwapViewModel @Inject constructor(
)
}
private fun startLoadingQuotes(fromToken: CryptoCurrency, toToken: CryptoCurrency, amount: String) {
private fun startLoadingQuotes(
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
amount: String,
toProvidersList: List<SwapProvider> = listOf(SwapProvider(1, listOf(RateType.FLOAT))),
) {
singleTaskScheduler.cancelTask()
uiState = stateBuilder.createQuotesLoadingState(uiState, fromToken, toToken, initialCryptoCurrency.id.value)
singleTaskScheduler.scheduleTask(
@ -188,6 +197,7 @@ internal class SwapViewModel @Inject constructor(
fromToken = fromToken,
toToken = toToken,
amount = amount,
toProvidersList = toProvidersList
),
)
}
@ -205,6 +215,7 @@ internal class SwapViewModel @Inject constructor(
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
amount: String,
toProvidersList: List<SwapProvider>,
): PeriodicTask<SwapState> {
return PeriodicTask(
UPDATE_DELAY,
@ -220,6 +231,7 @@ internal class SwapViewModel @Inject constructor(
networkId = dataState.networkId,
fromToken = fromToken,
toToken = toToken,
providers = toProvidersList,
amountToSwap = amount,
selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
)