Updated on 2026-08-14
This commit is contained in:
parent
5ad1e47e65
commit
fc9be53caf
9 changed files with 87 additions and 83 deletions
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
||||
)
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ interface SwapInteractor {
|
|||
networkId: String,
|
||||
fromToken: CryptoCurrency,
|
||||
toToken: CryptoCurrency,
|
||||
providers: List<SwapProvider>,
|
||||
amountToSwap: String,
|
||||
selectedFee: FeeType = FeeType.NORMAL,
|
||||
): SwapState
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue