Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-18 12:02:54 +03:00
parent 0bcfed032f
commit 7d087423e9
4 changed files with 39 additions and 44 deletions

View file

@ -2,7 +2,6 @@ package com.tangem.feature.swap.domain
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.IncludeFeeInAmount
@ -15,8 +14,6 @@ interface SwapInteractor {
suspend fun getTokensDataState(currency: CryptoCurrency): TokensDataStateExpress
fun initDerivationPathAndNetwork(derivationPath: String?, network: Network)
/**
* Gives permission to swap, this starts scan card process
*
@ -30,7 +27,6 @@ interface SwapInteractor {
* Find best quote for given tokens to swap
* under the hood calls different methods to receive data, depends on permission for given token
*
* @param networkId network for tokens
* @param fromToken [Currency] from which want to swap
* @param toToken [Currency] that receive after swap
* @param amountToSwap amount you want to swap
@ -39,7 +35,6 @@ interface SwapInteractor {
*/
@Throws(IllegalStateException::class)
suspend fun findBestQuote(
networkId: String,
fromToken: CryptoCurrencyStatus,
toToken: CryptoCurrencyStatus,
providers: List<SwapProvider>,
@ -62,7 +57,6 @@ interface SwapInteractor {
@Throws(IllegalStateException::class)
suspend fun onSwap(
swapProvider: SwapProvider,
networkId: String,
swapData: SwapDataModel?,
currencyToSend: CryptoCurrencyStatus,
currencyToGet: CryptoCurrencyStatus,
@ -76,7 +70,6 @@ interface SwapInteractor {
selectedFee: FeeType,
fromToken: CryptoCurrencyStatus,
amountToSwap: String,
networkId: String,
): SwapState.QuotesLoadedState
/**

View file

@ -62,7 +62,6 @@ internal class SwapInteractorImpl @Inject constructor(
private val swapCurrencyConverter = SwapCurrencyConverter()
private val amountFormatter = AmountFormatter()
private var derivationPath: String? = null
private var network: Network? = null
override suspend fun getTokensDataState(currency: CryptoCurrency): TokensDataStateExpress {
@ -182,14 +181,9 @@ internal class SwapInteractorImpl @Inject constructor(
return repository.getPairs(initialCurrency, currenciesList)
}
@Deprecated("used in old swap mechanism")
override fun initDerivationPathAndNetwork(derivationPath: String?, network: Network) {
this.derivationPath = derivationPath
this.network = network
}
@Deprecated("used in old swap mechanism")
override suspend fun givePermissionToSwap(networkId: String, permissionOptions: PermissionOptions): TxState {
val derivationPath = permissionOptions.fromToken.network.derivationPath.value
val dataToSign = if (permissionOptions.approveType == SwapApproveType.UNLIMITED) {
getApproveData(
networkId = networkId,
@ -233,7 +227,6 @@ internal class SwapInteractorImpl @Inject constructor(
@Deprecated("used in old swap mechanism")
override suspend fun findBestQuote(
networkId: String,
fromToken: CryptoCurrencyStatus,
toToken: CryptoCurrencyStatus,
providers: List<SwapProvider>,
@ -249,7 +242,7 @@ internal class SwapInteractorImpl @Inject constructor(
}
val amount = SwapAmount(amountDecimal, getTokenDecimals(fromToken.currency))
val isBalanceWithoutFeeEnough = isBalanceEnough(fromToken, amount, null)
val networkId = fromToken.currency.network.backendId
when (provider.type) {
ExchangeProviderType.DEX -> {
manageDex(
@ -310,7 +303,10 @@ internal class SwapInteractorImpl @Inject constructor(
if (isAllowedToSpend && allowPermissionsHandler.isAddressAllowanceInProgress(fromTokenAddress)) {
allowPermissionsHandler.removeAddressFromProgress(fromTokenAddress)
transactionManager.updateWalletManager(networkId, derivationPath)
transactionManager.updateWalletManager(
networkId,
fromToken.currency.network.derivationPath.value,
)
}
return if (isAllowedToSpend && isBalanceWithoutFeeEnough) {
provider to loadDexSwapData(
@ -361,7 +357,6 @@ internal class SwapInteractorImpl @Inject constructor(
override suspend fun onSwap(
swapProvider: SwapProvider,
networkId: String,
swapData: SwapDataModel?,
currencyToSend: CryptoCurrencyStatus,
currencyToGet: CryptoCurrencyStatus,
@ -389,7 +384,7 @@ internal class SwapInteractorImpl @Inject constructor(
}
ExchangeProviderType.DEX -> {
onSwapDex(
networkId = networkId,
networkId = currencyToSend.currency.network.backendId,
swapData = requireNotNull(swapData),
currencyToSend = currencyToSend.currency,
currencyToGet = currencyToGet.currency,
@ -405,7 +400,6 @@ internal class SwapInteractorImpl @Inject constructor(
selectedFee: FeeType,
fromToken: CryptoCurrencyStatus,
amountToSwap: String,
networkId: String,
): SwapState.QuotesLoadedState {
val amountDecimal = toBigDecimalOrNull(amountToSwap)
if (amountDecimal == null || amountDecimal.signum() == 0) {
@ -413,7 +407,7 @@ internal class SwapInteractorImpl @Inject constructor(
}
val amount = SwapAmount(amountDecimal, getTokenDecimals(fromToken.currency))
val includeFeeInAmount = getIncludeFeeInAmount(
networkId = networkId,
networkId = fromToken.currency.network.backendId,
txFee = state.txFee,
amount = amount,
fromToken = fromToken.currency,
@ -422,6 +416,7 @@ internal class SwapInteractorImpl @Inject constructor(
return state.copy(
permissionState = PermissionDataState.Empty,
preparedSwapConfigState = state.preparedSwapConfigState.copy(
isFeeEnough = includeFeeInAmount !is IncludeFeeInAmount.BalanceNotEnough,
isBalanceEnough = includeFeeInAmount !is IncludeFeeInAmount.BalanceNotEnough,
includeFeeInAmount = includeFeeInAmount,
),
@ -438,6 +433,7 @@ internal class SwapInteractorImpl @Inject constructor(
): TxState {
val amountDecimal = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format" }
val amount = SwapAmount(amountDecimal, getTokenDecimals(currencyToSend))
val derivationPath = currencyToSend.network.derivationPath.value
val result = transactionManager.sendTransaction(
txData = SwapTxData(
networkId = networkId,
@ -481,6 +477,7 @@ internal class SwapInteractorImpl @Inject constructor(
}
}
@Suppress("LongMethod")
private suspend fun onSwapCex(
currencyToSend: CryptoCurrencyStatus,
currencyToGet: CryptoCurrencyStatus,
@ -519,6 +516,7 @@ internal class SwapInteractorImpl @Inject constructor(
val externalUrl = (exchangeData.transaction as? ExpressTransactionModel.CEX)?.externalTxUrl
val derivationPath = currencyToSend.currency.network.derivationPath.value
return result.fold(
ifLeft = {
when (it) {
@ -659,7 +657,7 @@ internal class SwapInteractorImpl @Inject constructor(
val allowance = repository.getAllowance(
userWalletId = userWallet.walletId,
networkId = networkId,
derivationPath = derivationPath,
derivationPath = fromToken.network.derivationPath.value,
tokenDecimalCount = getTokenDecimals(fromToken),
tokenAddress = getTokenAddress(fromToken),
spenderAddress = spenderAddress,
@ -832,7 +830,10 @@ internal class SwapInteractorImpl @Inject constructor(
}
val tokenForFeeBalance =
userWalletManager.getNativeTokenBalance(networkId, derivationPath) ?: ProxyAmount.empty()
userWalletManager.getNativeTokenBalance(
networkId,
fromToken.network.derivationPath.value,
) ?: ProxyAmount.empty()
if (amount.value > tokenForFeeBalance.value) {
return IncludeFeeInAmount.BalanceNotEnough
@ -907,7 +908,7 @@ internal class SwapInteractorImpl @Inject constructor(
destinationAddress = swapData.transaction.txTo,
increaseBy = INCREASE_GAS_LIMIT_BY,
data = (swapData.transaction as ExpressTransactionModel.DEX).txData,
derivationPath = derivationPath,
derivationPath = fromToken.currency.network.derivationPath.value,
)
val txFeeState = when (feeData) {
is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(networkId)
@ -1048,6 +1049,7 @@ internal class SwapInteractorImpl @Inject constructor(
permissionState = PermissionDataState.PermissionLoading,
)
}
val derivationPath = fromToken.network.derivationPath.value
// setting up amount for approve with given amount for swap [SwapApproveType.Limited]
val transactionData = getApproveData(
networkId = networkId,
@ -1091,7 +1093,7 @@ internal class SwapInteractorImpl @Inject constructor(
permissionState = PermissionDataState.PermissionReadyForRequest(
currency = fromToken.symbol,
amount = INFINITY_SYMBOL,
walletAddress = getWalletAddress(networkId),
walletAddress = getWalletAddress(networkId, derivationPath),
spenderAddress = getTokenAddress(fromToken),
requestApproveData = RequestApproveStateData(
fee = feeState,
@ -1262,7 +1264,7 @@ internal class SwapInteractorImpl @Inject constructor(
}
}
private suspend fun getWalletAddress(networkId: String): String {
private suspend fun getWalletAddress(networkId: String, derivationPath: String?): String {
return userWalletManager.getWalletAddress(networkId, derivationPath)
}
@ -1286,7 +1288,10 @@ internal class SwapInteractorImpl @Inject constructor(
if (fee == null) {
return false
}
val nativeTokenBalance = userWalletManager.getNativeTokenBalance(networkId, derivationPath)
val nativeTokenBalance = userWalletManager.getNativeTokenBalance(
networkId,
fromToken.network.derivationPath.value,
)
val percentsToFeeIncrease = BigDecimal.ONE
return when (fromToken) {
is CryptoCurrency.Coin -> {

View file

@ -11,7 +11,6 @@ import com.tangem.feature.swap.models.ApproveType
data class SwapProcessDataState(
// Initial network id
val networkId: String,
val fromCryptoCurrency: CryptoCurrencyStatus? = null,
val toCryptoCurrency: CryptoCurrencyStatus? = null,
// Amount from input

View file

@ -79,7 +79,7 @@ internal class SwapViewModel @Inject constructor(
private val amountDebouncer = Debouncer()
private val singleTaskScheduler = SingleTaskScheduler<Map<SwapProvider, SwapState>>()
private var dataState by mutableStateOf(SwapProcessDataState(networkId = initialCryptoCurrency.network.backendId))
private var dataState by mutableStateOf(SwapProcessDataState())
var uiState: SwapStateHolder by mutableStateOf(
stateBuilder.createInitialLoadingState(
@ -103,11 +103,6 @@ internal class SwapViewModel @Inject constructor(
requireNotNull(getCryptoCurrencyStatusUseCase(it.walletId, initialCryptoCurrency.id).getOrNull()) {
"Failed to get initial crypto currency status"
}
swapInteractor.initDerivationPathAndNetwork(
derivationPath = initialCryptoCurrency.network.derivationPath.value,
network = initialCryptoCurrency.network,
)
initTokens()
}
}
@ -278,7 +273,6 @@ internal class SwapViewModel @Inject constructor(
approveDataModel = null,
)
swapInteractor.findBestQuote(
networkId = dataState.networkId,
fromToken = fromToken,
toToken = toToken,
providers = toProvidersList,
@ -438,13 +432,13 @@ internal class SwapViewModel @Inject constructor(
Timber.e("Last loaded quotes state is null")
return
}
val fromCurrency = requireNotNull(dataState.fromCryptoCurrency)
viewModelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) {
swapInteractor.onSwap(
swapProvider = provider,
networkId = dataState.networkId,
swapData = dataState.swapDataModel,
currencyToSend = requireNotNull(dataState.fromCryptoCurrency),
currencyToSend = fromCurrency,
currencyToGet = requireNotNull(dataState.toCryptoCurrency),
amountToSwap = requireNotNull(dataState.amount),
includeFeeInAmount = lastLoadedQuotesState.preparedSwapConfigState.includeFeeInAmount,
@ -454,7 +448,7 @@ internal class SwapViewModel @Inject constructor(
when (it) {
is TxState.TxSent -> {
val url = blockchainInteractor.getExplorerTransactionLink(
networkId = dataState.networkId,
networkId = fromCurrency.currency.network.backendId,
txAddress = it.txAddress,
)
uiState = stateBuilder.createSuccessState(
@ -528,17 +522,18 @@ internal class SwapViewModel @Inject constructor(
is TxFeeState.SingleFeeState -> fee.fee
null -> error("Fee should not be null")
}
val fromToken = requireNotNull(dataState.fromCryptoCurrency?.currency) {
"dataState.fromCurrency might not be null"
}
swapInteractor.givePermissionToSwap(
networkId = dataState.networkId,
networkId = fromToken.network.backendId,
permissionOptions = PermissionOptions(
approveData = requireNotNull(dataState.approveDataModel) {
"dataState.approveDataModel might not be null"
},
forTokenContractAddress = (dataState.fromCryptoCurrency?.currency as? CryptoCurrency.Token)
?.contractAddress ?: "",
fromToken = requireNotNull(dataState.fromCryptoCurrency?.currency) {
"dataState.fromCurrency might not be null"
},
fromToken = fromToken,
approveType = requireNotNull(dataState.approveType) {
"uiState.permissionState should not be null"
}.toDomainApproveType(),
@ -805,7 +800,6 @@ internal class SwapViewModel @Inject constructor(
selectedFee = it.feeType,
fromToken = fromToken,
amountToSwap = amountToSwap,
networkId = dataState.networkId,
)
setupLoadedState(selectedProvider, updatedState, fromToken)
}
@ -839,7 +833,11 @@ internal class SwapViewModel @Inject constructor(
},
onBuyClick = {
swapInteractor.getSelectedWallet()?.let {
swapRouter.openTokenDetails(it.walletId, swapInteractor.getNativeToken(dataState.networkId))
val fromToken = dataState.fromCryptoCurrency ?: return@let
swapRouter.openTokenDetails(
it.walletId,
swapInteractor.getNativeToken(fromToken.currency.network.backendId),
)
}
},
onRetryClick = {