Updated on 2026-08-14
This commit is contained in:
parent
7e35a4de75
commit
c0471dbf9f
20 changed files with 315 additions and 133 deletions
|
|
@ -161,7 +161,7 @@ class TransactionManagerImpl(
|
|||
@Throws(IllegalStateException::class)
|
||||
override suspend fun getFee(
|
||||
networkId: String,
|
||||
amountToSend: BigDecimal,
|
||||
amountToSend: Amount,
|
||||
currencyToSend: Currency,
|
||||
destinationAddress: String,
|
||||
increaseBy: Int?,
|
||||
|
|
@ -174,7 +174,7 @@ class TransactionManagerImpl(
|
|||
if (walletManager is EthereumOptimisticRollupWalletManager) {
|
||||
return getFeeForOptimismBlockchain(
|
||||
walletManager = walletManager,
|
||||
amount = createAmount(amountToSend, currencyToSend, blockchain),
|
||||
amount = amountToSend,
|
||||
destinationAddress = destinationAddress,
|
||||
data = data,
|
||||
)
|
||||
|
|
@ -183,7 +183,6 @@ class TransactionManagerImpl(
|
|||
walletManager = walletManager,
|
||||
blockchain = blockchain,
|
||||
amountToSend = amountToSend,
|
||||
currency = currencyToSend,
|
||||
destinationAddress = destinationAddress,
|
||||
data = data,
|
||||
increaseBy = increaseBy,
|
||||
|
|
@ -192,8 +191,6 @@ class TransactionManagerImpl(
|
|||
return getFeeForBlockchain(
|
||||
walletManager = walletManager,
|
||||
amountToSend = amountToSend,
|
||||
currency = currencyToSend,
|
||||
blockchain = blockchain,
|
||||
destinationAddress = destinationAddress,
|
||||
)
|
||||
}
|
||||
|
|
@ -209,13 +206,11 @@ class TransactionManagerImpl(
|
|||
|
||||
private suspend fun getFeeForBlockchain(
|
||||
walletManager: WalletManager,
|
||||
amountToSend: BigDecimal,
|
||||
currency: Currency,
|
||||
blockchain: Blockchain,
|
||||
amountToSend: Amount,
|
||||
destinationAddress: String,
|
||||
): ProxyFees {
|
||||
val fee = (walletManager as? TransactionSender)?.getFee(
|
||||
amount = createAmount(amountToSend, currency, blockchain),
|
||||
amount = amountToSend,
|
||||
destination = destinationAddress,
|
||||
) ?: error("Cannot cast to TransactionSender")
|
||||
return when (fee) {
|
||||
|
|
@ -268,17 +263,14 @@ class TransactionManagerImpl(
|
|||
private suspend fun getFeeForEthereumBlockchain(
|
||||
walletManager: EthereumWalletManager,
|
||||
blockchain: Blockchain,
|
||||
amountToSend: BigDecimal,
|
||||
currency: Currency,
|
||||
amountToSend: Amount,
|
||||
destinationAddress: String,
|
||||
data: String?,
|
||||
increaseBy: Int?,
|
||||
): ProxyFees {
|
||||
val gasLimit = getGasLimit(
|
||||
evmWalletManager = walletManager,
|
||||
blockchain = blockchain,
|
||||
amount = amountToSend,
|
||||
currency = currency,
|
||||
destinationAddress = destinationAddress,
|
||||
data = data,
|
||||
).increaseBigIntegerByPercents(increaseBy)
|
||||
|
|
@ -332,23 +324,20 @@ class TransactionManagerImpl(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private suspend fun getGasLimit(
|
||||
evmWalletManager: EthereumWalletManager,
|
||||
blockchain: Blockchain,
|
||||
amount: BigDecimal,
|
||||
currency: Currency,
|
||||
amount: Amount,
|
||||
destinationAddress: String,
|
||||
data: String?,
|
||||
): BigInteger {
|
||||
val result = if (data.isNullOrEmpty()) {
|
||||
evmWalletManager.getGasLimit(
|
||||
amount = createAmount(amount, currency, blockchain),
|
||||
amount = amount,
|
||||
destination = destinationAddress,
|
||||
)
|
||||
} else {
|
||||
evmWalletManager.getGasLimit(
|
||||
amount = createAmount(amount, currency, blockchain),
|
||||
amount = amount,
|
||||
destination = destinationAddress,
|
||||
data = data,
|
||||
)
|
||||
|
|
@ -363,6 +352,18 @@ class TransactionManagerImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getFeeForGas(networkId: String, gas: BigInteger, derivationPath: String?): ProxyFees {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
val walletManager = getActualWalletManager(blockchain, derivationPath)
|
||||
val gasPriceResult = (walletManager as? EthereumWalletManager)?.getGasPrice()
|
||||
?: error("not supported for $blockchain")
|
||||
val gasPrice = when (gasPriceResult) {
|
||||
is Result.Failure -> error("fail to receive gasPrice")
|
||||
is Result.Success -> gasPriceResult.data
|
||||
}
|
||||
return createMultipleProxyFees(gasPrice, gas, blockchain)
|
||||
}
|
||||
|
||||
private fun handleSendResult(result: Result<TransactionSendResult>): SendTxResult {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.datasource.api.express.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class ExchangeDataResponseWithTxDetails(
|
||||
val dataResponse: ExchangeDataResponse,
|
||||
|
|
@ -50,7 +49,10 @@ data class TxDetails(
|
|||
val txData: String?, // transaction data if DEX, null if CEX
|
||||
|
||||
@Json(name = "txValue")
|
||||
val txValue: BigDecimal, // amount (same as fromAmount)
|
||||
val txValue: String, // amount (same as fromAmount for Coin, but for bridge equal to otherNativeFee)
|
||||
|
||||
@Json(name = "otherNativeFee")
|
||||
val otherNativeFee: String?,
|
||||
|
||||
@Json(name = "externalTxId")
|
||||
val externalTxId: String?, // null if DEX, provider transaction id if CEX
|
||||
|
|
@ -63,6 +65,9 @@ data class TxDetails(
|
|||
|
||||
@Json(name = "txExtraId")
|
||||
val txExtraId: String?,
|
||||
|
||||
@Json(name = "gas")
|
||||
val gas: String?,
|
||||
)
|
||||
|
||||
enum class TxType {
|
||||
|
|
|
|||
|
|
@ -568,7 +568,7 @@
|
|||
<string name="swapping_permission_header">Дать разрешение</string>
|
||||
<string name="swapping_permission_policy_type_footer">Укажите лимит доступа к выбранному токену</string>
|
||||
<string name="swapping_permission_rows_amount">Количество %s</string>
|
||||
<string name="swapping_permission_subheader">Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s</string>
|
||||
<string name="swapping_permission_subheader">Чтобы продолжить, вам нужно разрешить смарт-контракту %1$s использовать ваш %2$s</string>
|
||||
<string name="swapping_permission_unlimited">Безлимитно</string>
|
||||
<string name="swapping_success_view_title">В процессе</string>
|
||||
<string name="swapping_swap_action">Обменять</string>
|
||||
|
|
|
|||
|
|
@ -283,7 +283,7 @@
|
|||
<string name="swapping_permission_buttons_approve">允許</string>
|
||||
<string name="swapping_permission_header">賦予權限</string>
|
||||
<string name="swapping_permission_rows_amount">數量 %s</string>
|
||||
<string name="swapping_permission_subheader">要繼續,您需要允許 1inch 智能合約使用您的 %s</string>
|
||||
<string name="swapping_permission_subheader">要繼續,您需要允許 %1$s 智能合約使用您的 %2$s</string>
|
||||
<string name="swapping_success_view_title">進行中</string>
|
||||
<string name="swapping_swap_action">交易</string>
|
||||
<string name="swapping_token_list_title">選擇代幣</string>
|
||||
|
|
|
|||
|
|
@ -561,7 +561,7 @@
|
|||
<string name="swapping_permission_header">Give Permission</string>
|
||||
<string name="swapping_permission_policy_type_footer">Specify the approve limit for the selected token</string>
|
||||
<string name="swapping_permission_rows_amount">Amount %s</string>
|
||||
<string name="swapping_permission_subheader">To continue, grant 1inch smart contracts permission to use your %s</string>
|
||||
<string name="swapping_permission_subheader">To continue, grant %1$s smart contracts permission to use your %2$s</string>
|
||||
<string name="swapping_permission_unlimited">Unlimited</string>
|
||||
<string name="swapping_success_view_title">In progress</string>
|
||||
<string name="swapping_swap_action">Swap</string>
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ class CryptoCurrencyFactory {
|
|||
decimals = cryptoCurrency.decimals,
|
||||
id = cryptoCurrency.id.rawCurrencyId,
|
||||
)
|
||||
val blockchain = Blockchain.fromNetworkId(cryptoCurrency.network.id.value) ?: Blockchain.Unknown
|
||||
val blockchain = Blockchain.fromNetworkId(cryptoCurrency.network.backendId) ?: Blockchain.Unknown
|
||||
val id = getTokenId(network, sdkToken)
|
||||
return CryptoCurrency.Token(
|
||||
id = id,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class DefaultTransactionRepository(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
|
|
@ -34,7 +33,6 @@ internal class DefaultTransactionRepository(
|
|||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
isSwap: Boolean,
|
||||
txExtras: TransactionExtras?,
|
||||
hash: String?,
|
||||
): TransactionData? = withContext(coroutineDispatcherProvider.io) {
|
||||
|
|
@ -51,7 +49,6 @@ internal class DefaultTransactionRepository(
|
|||
memo = memo,
|
||||
destination = destination,
|
||||
network = network,
|
||||
isSwap = isSwap,
|
||||
txExtras = txExtras,
|
||||
hash = hash,
|
||||
)
|
||||
|
|
@ -84,7 +81,6 @@ internal class DefaultTransactionRepository(
|
|||
memo = memo,
|
||||
destination = destination,
|
||||
network = network,
|
||||
isSwap = isSwap,
|
||||
txExtras = txExtras,
|
||||
hash = hash,
|
||||
)
|
||||
|
|
@ -118,23 +114,15 @@ internal class DefaultTransactionRepository(
|
|||
memo: String?,
|
||||
destination: String,
|
||||
network: Network,
|
||||
isSwap: Boolean,
|
||||
txExtras: TransactionExtras?,
|
||||
hash: String?,
|
||||
): TransactionData {
|
||||
// TODO: refactor workaround to use general mechanism in bsdk for build tx for DEX
|
||||
val txAmount = if (isSwap) {
|
||||
createAmountForSwap(amount)
|
||||
} else {
|
||||
amount
|
||||
}
|
||||
|
||||
if (txExtras != null && memo != null) {
|
||||
// throw error for now to avoid programmers errors when use extras
|
||||
error("Both txExtras and memo provided, use only one of them")
|
||||
}
|
||||
val extras = txExtras ?: getMemoExtras(network.id.value, memo)
|
||||
return createTransaction(txAmount, fee, destination).copy(
|
||||
return createTransaction(amount, fee, destination).copy(
|
||||
hash = hash,
|
||||
extras = extras,
|
||||
)
|
||||
|
|
@ -163,19 +151,4 @@ internal class DefaultTransactionRepository(
|
|||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAmountForSwap(amount: Amount): Amount {
|
||||
return when (amount.type) {
|
||||
is AmountType.Coin -> amount
|
||||
else -> {
|
||||
// 1. when creates swap amount for NonNativeToken, amount should be ZERO
|
||||
// 2. Amount has .Coin type, as workaround to use destinationAddress in bsdk, not contractAddress
|
||||
Amount(
|
||||
currencySymbol = amount.currencySymbol,
|
||||
value = BigDecimal.ZERO,
|
||||
decimals = amount.decimals,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ interface TransactionRepository {
|
|||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
isSwap: Boolean,
|
||||
txExtras: TransactionExtras?,
|
||||
hash: String?,
|
||||
): TransactionData?
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ class CreateTransactionUseCase(
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
txExtras: TransactionExtras? = null,
|
||||
isSwap: Boolean = false,
|
||||
hash: String? = null,
|
||||
) = Either.catch {
|
||||
requireNotNull(
|
||||
|
|
@ -35,7 +34,6 @@ class CreateTransactionUseCase(
|
|||
destination = destination,
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
isSwap = isSwap,
|
||||
txExtras = txExtras,
|
||||
hash = hash,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ 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
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class ExpressDataConverter : Converter<ExchangeDataResponseWithTxDetails, SwapDataModel> {
|
||||
|
||||
|
|
@ -24,19 +25,30 @@ internal class ExpressDataConverter : Converter<ExchangeDataResponseWithTxDetail
|
|||
dataResponse: ExchangeDataResponse,
|
||||
): ExpressTransactionModel {
|
||||
return if (transactionDto.txType == TxType.SWAP) {
|
||||
val otherNativeFeeWei = transactionDto.otherNativeFee?.let {
|
||||
if (it == "0") {
|
||||
BigDecimal.ZERO
|
||||
} else {
|
||||
requireNotNull(it.toBigDecimalOrNull()) { "wrong amount format, use only digits" }
|
||||
}
|
||||
}
|
||||
ExpressTransactionModel.DEX(
|
||||
fromAmount = createFromAmountWithOffset(dataResponse.fromAmount, dataResponse.fromDecimals),
|
||||
toAmount = createFromAmountWithOffset(dataResponse.toAmount, dataResponse.toDecimals),
|
||||
txValue = transactionDto.txValue,
|
||||
txId = dataResponse.txId,
|
||||
txTo = transactionDto.txTo,
|
||||
txFrom = requireNotNull(transactionDto.txFrom),
|
||||
txData = requireNotNull(transactionDto.txData),
|
||||
txExtraId = transactionDto.txExtraId,
|
||||
otherNativeFeeWei = otherNativeFeeWei,
|
||||
gas = transactionDto.gas?.toBigIntegerOrNull() ?: error("gas is empty"),
|
||||
)
|
||||
} else {
|
||||
ExpressTransactionModel.CEX(
|
||||
fromAmount = createFromAmountWithOffset(dataResponse.fromAmount, dataResponse.fromDecimals),
|
||||
toAmount = createFromAmountWithOffset(dataResponse.toAmount, dataResponse.toDecimals),
|
||||
txValue = transactionDto.txValue,
|
||||
txId = dataResponse.txId,
|
||||
txTo = transactionDto.txTo,
|
||||
externalTxId = requireNotNull(transactionDto.externalTxId),
|
||||
|
|
|
|||
|
|
@ -1,28 +1,38 @@
|
|||
package com.tangem.feature.swap.domain.models.domain
|
||||
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
sealed class ExpressTransactionModel {
|
||||
|
||||
abstract val fromAmount: SwapAmount
|
||||
abstract val toAmount: SwapAmount
|
||||
abstract val txValue: String
|
||||
abstract val txId: String
|
||||
abstract val txTo: String
|
||||
abstract val txExtraId: String?
|
||||
|
||||
/**
|
||||
* @param txValue amount for tx, should use native coin decimals, this value will send as native amount in tx
|
||||
*/
|
||||
data class DEX(
|
||||
override val fromAmount: SwapAmount,
|
||||
override val toAmount: SwapAmount,
|
||||
override val txValue: String,
|
||||
override val txId: String,
|
||||
override val txTo: String,
|
||||
override val txExtraId: String?,
|
||||
val txFrom: String,
|
||||
val txData: String,
|
||||
val otherNativeFeeWei: BigDecimal?,
|
||||
val gas: BigInteger,
|
||||
) : ExpressTransactionModel()
|
||||
|
||||
data class CEX(
|
||||
override val fromAmount: SwapAmount,
|
||||
override val toAmount: SwapAmount,
|
||||
override val txValue: String,
|
||||
override val txId: String,
|
||||
override val txTo: String,
|
||||
override val txExtraId: String?,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ import java.math.BigDecimal
|
|||
|
||||
sealed interface SwapState {
|
||||
|
||||
/**
|
||||
* @param txFee fee state uses for calculation and build transaction
|
||||
* @param txFeeIncludeOtherNativeFee fee state uses for display and included otherNativeFee (specific for bridge)
|
||||
*/
|
||||
data class QuotesLoadedState(
|
||||
val fromTokenInfo: TokenSwapInfo,
|
||||
val toTokenInfo: TokenSwapInfo,
|
||||
|
|
@ -22,7 +26,9 @@ sealed interface SwapState {
|
|||
val permissionState: PermissionDataState = PermissionDataState.Empty,
|
||||
val swapDataModel: SwapDataModel? = null,
|
||||
val txFee: TxFeeState,
|
||||
// val txFeeIncludeOtherNativeFee: TxFeeState,
|
||||
val warnings: List<Warning> = emptyList(),
|
||||
val swapProvider: SwapProvider,
|
||||
) : SwapState
|
||||
|
||||
data class EmptyAmountState(val zeroAmountEquivalent: String) : SwapState
|
||||
|
|
@ -102,6 +108,9 @@ data class TxFee(
|
|||
val gasLimit: Int,
|
||||
val feeFiatFormatted: String,
|
||||
val feeCryptoFormatted: String,
|
||||
val feeIncludeOtherNativeFee: BigDecimal,
|
||||
val feeFiatFormattedWithNative: String,
|
||||
val feeCryptoFormattedWithNative: String,
|
||||
val decimals: Int,
|
||||
val cryptoSymbol: String,
|
||||
val feeType: FeeType,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@ import arrow.core.Either
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.minimalAmount
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
|
|
@ -15,10 +18,8 @@ import com.tangem.domain.appcurrency.extenstions.unwrap
|
|||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.model.FeePaidCurrency
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
|
|
@ -342,7 +343,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
)
|
||||
} else {
|
||||
provider to getQuotesState(
|
||||
exchangeProviderType = provider.type,
|
||||
provider = provider,
|
||||
quoteDataModel = quotes,
|
||||
amount = amount,
|
||||
fromToken = fromToken,
|
||||
|
|
@ -366,7 +367,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
isBalanceWithoutFeeEnough: Boolean,
|
||||
): Pair<SwapProvider, SwapState> {
|
||||
return provider to loadCexQuoteData(
|
||||
exchangeProviderType = ExchangeProviderType.CEX,
|
||||
networkId = networkId,
|
||||
amount = amount,
|
||||
fromTokenStatus = fromToken,
|
||||
|
|
@ -595,8 +595,8 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
swapData = requireNotNull(swapData),
|
||||
currencyToSendStatus = currencyToSend,
|
||||
currencyToGetStatus = currencyToGet,
|
||||
amountToSwap = amountToSwap,
|
||||
fee = fee,
|
||||
amountToSwap = amountToSwap,
|
||||
userWalletId = requireNotNull(getSelectedWallet()).walletId,
|
||||
)
|
||||
}
|
||||
|
|
@ -622,8 +622,8 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
)
|
||||
val fee = when (val txFee = state.txFee) {
|
||||
TxFeeState.Empty -> BigDecimal.ZERO
|
||||
is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue
|
||||
is TxFeeState.SingleFeeState -> txFee.fee.feeValue
|
||||
is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeIncludeOtherNativeFee
|
||||
is TxFeeState.SingleFeeState -> txFee.fee.feeIncludeOtherNativeFee
|
||||
}
|
||||
val feeState = getFeeState(
|
||||
fee = fee,
|
||||
|
|
@ -656,8 +656,9 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
val derivationPath = currencyToSendStatus.currency.network.derivationPath.value
|
||||
val dexTransaction = swapData.transaction as ExpressTransactionModel.DEX
|
||||
val dataToSign = dexTransaction.txData
|
||||
val amountToSend = createNativeAmountForDex(swapData.transaction.txValue, currencyToSendStatus.currency.network)
|
||||
val txData = createTransactionUseCase(
|
||||
amount = amount.value.convertToAmount(currencyToSendStatus.currency),
|
||||
amount = amountToSend,
|
||||
fee = getFeeForTransaction(
|
||||
fee = fee,
|
||||
blockchain = Blockchain.fromId(currencyToSendStatus.currency.network.id.value),
|
||||
|
|
@ -668,7 +669,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
network = currencyToSendStatus.currency.network,
|
||||
txExtras = createDexTxExtras(fee.gasLimit, dataToSign),
|
||||
hash = dataToSign,
|
||||
isSwap = true,
|
||||
).getOrElse {
|
||||
Timber.e(it)
|
||||
return SwapTransactionState.UnknownError
|
||||
|
|
@ -984,7 +984,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
*/
|
||||
@Suppress("LongParameterList")
|
||||
private suspend fun loadCexQuoteData(
|
||||
exchangeProviderType: ExchangeProviderType,
|
||||
networkId: String,
|
||||
amount: SwapAmount,
|
||||
fromTokenStatus: CryptoCurrencyStatus,
|
||||
|
|
@ -1035,7 +1034,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
)
|
||||
|
||||
getQuotesState(
|
||||
exchangeProviderType = exchangeProviderType,
|
||||
provider = provider,
|
||||
quoteDataModel = quotes,
|
||||
amount = amount,
|
||||
fromToken = fromTokenStatus,
|
||||
|
|
@ -1052,7 +1051,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
|
||||
@Suppress("LongMethod")
|
||||
private suspend fun getQuotesState(
|
||||
exchangeProviderType: ExchangeProviderType,
|
||||
provider: SwapProvider,
|
||||
quoteDataModel: Either<DataError, QuoteModel>,
|
||||
amount: SwapAmount,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
|
|
@ -1074,6 +1073,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
toTokenAmount = quoteModel.toTokenAmount,
|
||||
swapData = null,
|
||||
txFeeState = txFee,
|
||||
provider = provider,
|
||||
).copy(
|
||||
warnings = manageWarnings(
|
||||
fromTokenStatus = fromToken,
|
||||
|
|
@ -1083,7 +1083,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
),
|
||||
)
|
||||
|
||||
when (exchangeProviderType) {
|
||||
when (provider.type) {
|
||||
ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> {
|
||||
val state = updatePermissionState(
|
||||
networkId = networkId,
|
||||
|
|
@ -1147,8 +1147,8 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
): IncludeFeeInAmount {
|
||||
val feeValue = when (txFee) {
|
||||
TxFeeState.Empty -> BigDecimal.ZERO
|
||||
is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue
|
||||
is TxFeeState.SingleFeeState -> txFee.fee.feeValue
|
||||
is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeIncludeOtherNativeFee
|
||||
is TxFeeState.SingleFeeState -> txFee.fee.feeIncludeOtherNativeFee
|
||||
}
|
||||
val feePaidCurrency = getFeePaidCurrency(
|
||||
userWalletId = requireNotNull(getSelectedWallet()).walletId,
|
||||
|
|
@ -1254,29 +1254,35 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
providerId = provider.providerId,
|
||||
rateType = RateType.FLOAT,
|
||||
toAddress = toToken.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
refundAddress = fromToken.value.networkAddress?.defaultAddress?.value,
|
||||
).fold(
|
||||
ifRight = { swapData ->
|
||||
val feeData = transactionManager.getFee(
|
||||
networkId = networkId,
|
||||
amountToSend = amount.value,
|
||||
currencyToSend = swapCurrencyConverter.convert(fromToken.currency),
|
||||
destinationAddress = swapData.transaction.txTo,
|
||||
increaseBy = INCREASE_GAS_LIMIT_BY,
|
||||
data = (swapData.transaction as ExpressTransactionModel.DEX).txData,
|
||||
derivationPath = fromToken.currency.network.derivationPath.value,
|
||||
)
|
||||
val txFeeState = when (feeData) {
|
||||
is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken.currency)
|
||||
is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken.currency)
|
||||
val transaction = swapData.transaction as ExpressTransactionModel.DEX
|
||||
val nativeCoinDecimals = Blockchain.fromNetworkId(networkId)?.decimals()
|
||||
?: error("Blockchain not found")
|
||||
val otherNativeFee = transaction.otherNativeFeeWei
|
||||
?.movePointLeft(nativeCoinDecimals)
|
||||
?: BigDecimal.ZERO
|
||||
val txFeeState = when (val feeData = getFeeDataForDexSwap(networkId, transaction, fromToken.currency)) {
|
||||
is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken.currency, otherNativeFee)
|
||||
is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken.currency, otherNativeFee)
|
||||
}
|
||||
val feeByPriority = selectFeeByType(feeType = selectedFee, txFeeState = txFeeState)
|
||||
val isBalanceIncludeFeeEnough = isBalanceEnough(fromToken, amount, feeByPriority)
|
||||
val feeToCheckFunds = feeByPriority + (otherNativeFee ?: BigDecimal.ZERO)
|
||||
val isBalanceIncludeFeeEnough = isBalanceEnough(fromToken, amount, feeToCheckFunds)
|
||||
val feeState = getFeeState(
|
||||
fee = feeByPriority,
|
||||
spendAmount = amount,
|
||||
networkId = networkId,
|
||||
fromTokenStatus = fromToken,
|
||||
)
|
||||
val preparedSwapConfigState = PreparedSwapConfigState(
|
||||
isAllowedToSpend = true,
|
||||
isBalanceEnough = isBalanceIncludeFeeEnough,
|
||||
feeState = feeState,
|
||||
hasOutgoingTransaction = hasOutgoingTransaction(fromToken),
|
||||
includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex
|
||||
)
|
||||
val swapState = updateBalances(
|
||||
networkId = networkId,
|
||||
fromTokenStatus = fromToken,
|
||||
|
|
@ -1285,6 +1291,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
toTokenAmount = swapData.toTokenAmount,
|
||||
swapData = swapData,
|
||||
txFeeState = txFeeState,
|
||||
provider = provider,
|
||||
)
|
||||
swapState.copy(
|
||||
permissionState = PermissionDataState.Empty,
|
||||
|
|
@ -1292,17 +1299,9 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
fromTokenStatus = fromToken,
|
||||
amount = amount,
|
||||
feeState = txFeeState,
|
||||
minAdaValue = (feeData as? ProxyFees.SingleFee)?.let {
|
||||
(it.singleFee as? ProxyFee.CardanoToken)?.minAdaValue
|
||||
},
|
||||
),
|
||||
preparedSwapConfigState = PreparedSwapConfigState(
|
||||
isAllowedToSpend = true,
|
||||
isBalanceEnough = isBalanceIncludeFeeEnough,
|
||||
feeState = feeState,
|
||||
hasOutgoingTransaction = hasOutgoingTransaction(fromToken),
|
||||
includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex
|
||||
minAdaValue = null, // no ADA in DEX
|
||||
),
|
||||
preparedSwapConfigState = preparedSwapConfigState,
|
||||
)
|
||||
},
|
||||
ifLeft = { error ->
|
||||
|
|
@ -1322,8 +1321,42 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun getFeeDataForDexSwap(
|
||||
networkId: String,
|
||||
transaction: ExpressTransactionModel.DEX,
|
||||
fromToken: CryptoCurrency,
|
||||
): ProxyFees {
|
||||
return try {
|
||||
val nativeBalance = userWalletManager.getNativeTokenBalance(
|
||||
networkId = networkId,
|
||||
derivationPath = fromToken.network.derivationPath.value,
|
||||
) ?: ProxyAmount.empty()
|
||||
val amountToSend = createNativeAmountForDex(transaction.txValue, fromToken.network)
|
||||
// transaction.txValue is always native coin
|
||||
if (nativeBalance.value < amountToSend.value) {
|
||||
error("It's impossible to calculate fee for nativeBalance.value < amountToSend.value")
|
||||
}
|
||||
transactionManager.getFee(
|
||||
networkId = networkId,
|
||||
amountToSend = amountToSend,
|
||||
currencyToSend = swapCurrencyConverter.convert(fromToken),
|
||||
destinationAddress = transaction.txTo,
|
||||
increaseBy = INCREASE_GAS_LIMIT_BY,
|
||||
data = transaction.txData,
|
||||
derivationPath = fromToken.network.derivationPath.value,
|
||||
)
|
||||
} catch (e: IllegalStateException) {
|
||||
transactionManager.getFeeForGas(
|
||||
networkId = networkId,
|
||||
gas = transaction.gas,
|
||||
derivationPath = fromToken.network.derivationPath.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private suspend fun updateBalances(
|
||||
provider: SwapProvider,
|
||||
networkId: String,
|
||||
fromTokenStatus: CryptoCurrencyStatus,
|
||||
toTokenStatus: CryptoCurrencyStatus,
|
||||
|
|
@ -1335,7 +1368,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
val fromToken = fromTokenStatus.currency
|
||||
val toToken = toTokenStatus.currency
|
||||
val nativeToken = repository.getNativeTokenForNetwork(networkId)
|
||||
|
||||
val rates = getQuotes(fromToken.id, toToken.id, nativeToken.id)
|
||||
return SwapState.QuotesLoadedState(
|
||||
fromTokenInfo = TokenSwapInfo(
|
||||
|
|
@ -1358,6 +1390,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
),
|
||||
swapDataModel = swapData,
|
||||
txFee = txFeeState,
|
||||
swapProvider = provider,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1367,7 +1400,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
): TxFeeState {
|
||||
return txFeeResult?.fold(
|
||||
ifLeft = { TxFeeState.Empty },
|
||||
ifRight = { txFee -> txFee.toTxFeeState(fromToken.currency) },
|
||||
ifRight = { txFee -> txFee.toTxFeeState(fromToken.currency, null) },
|
||||
) ?: TxFeeState.Empty
|
||||
}
|
||||
|
||||
|
|
@ -1427,7 +1460,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
try {
|
||||
transactionManager.getFee(
|
||||
networkId = networkId,
|
||||
amountToSend = BigDecimal.ZERO,
|
||||
amountToSend = createNativeAmountForDex("0", fromToken.network),
|
||||
currencyToSend = swapCurrencyConverter.convert(repository.getNativeTokenForNetwork(networkId)),
|
||||
destinationAddress = fromToken.getContractAddress(),
|
||||
increaseBy = INCREASE_GAS_LIMIT_BY,
|
||||
|
|
@ -1475,11 +1508,16 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun ProxyFees.MultipleFees.proxyFeesToFeeState(fromToken: CryptoCurrency): TxFeeState {
|
||||
private suspend fun ProxyFees.MultipleFees.proxyFeesToFeeState(
|
||||
fromToken: CryptoCurrency,
|
||||
otherNativeFee: BigDecimal? = null,
|
||||
): TxFeeState {
|
||||
val otherNativeFeeValue = otherNativeFee ?: BigDecimal.ZERO
|
||||
val normalFeeValue = this.minFee.fee.value // in swap for normal use min fee
|
||||
val normalFeeGas = this.minFee.gasLimit.toInt()
|
||||
val priorityFeeValue = this.normalFee.fee.value // in swap for priority use normal fee
|
||||
val priorityFeeGas = this.normalFee.gasLimit.toInt()
|
||||
// region fees to use
|
||||
val feesFiat = getFormattedFiatFees(fromToken, normalFeeValue, priorityFeeValue)
|
||||
val normalFiatFee = requireNotNull(feesFiat.getOrNull(0)) { "feesFiat item 0 couldn't be null" }
|
||||
val priorityFiatFee = requireNotNull(feesFiat.getOrNull(1)) { "feesFiat item 1 couldn't be null" }
|
||||
|
|
@ -1491,12 +1529,35 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
amount = priorityFeeValue,
|
||||
decimals = normalFee.fee.decimals,
|
||||
)
|
||||
//
|
||||
// region fees include otherNativeFee
|
||||
val feesFiatWithNative = getFormattedFiatFees(
|
||||
fromToken = fromToken,
|
||||
normalFeeValue + otherNativeFeeValue,
|
||||
priorityFeeValue + otherNativeFeeValue,
|
||||
)
|
||||
val normalFiatFeeWithNative =
|
||||
requireNotNull(feesFiatWithNative.getOrNull(0)) { "feesFiat item 0 couldn't be null" }
|
||||
val priorityFiatFeeWithNative =
|
||||
requireNotNull(feesFiatWithNative.getOrNull(1)) { "feesFiat item 1 couldn't be null" }
|
||||
val normalCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = normalFeeValue + otherNativeFeeValue,
|
||||
decimals = minFee.fee.decimals,
|
||||
)
|
||||
val priorityCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = priorityFeeValue + otherNativeFeeValue,
|
||||
decimals = normalFee.fee.decimals,
|
||||
)
|
||||
//
|
||||
return TxFeeState.MultipleFeeState(
|
||||
normalFee = TxFee(
|
||||
feeValue = normalFeeValue,
|
||||
gasLimit = normalFeeGas,
|
||||
feeFiatFormatted = normalFiatFee,
|
||||
feeCryptoFormatted = normalCryptoFee,
|
||||
feeIncludeOtherNativeFee = normalFeeValue + otherNativeFeeValue,
|
||||
feeFiatFormattedWithNative = normalFiatFeeWithNative,
|
||||
feeCryptoFormattedWithNative = normalCryptoFeeWithNative,
|
||||
decimals = minFee.fee.decimals,
|
||||
cryptoSymbol = minFee.fee.currencySymbol,
|
||||
feeType = FeeType.NORMAL,
|
||||
|
|
@ -1506,6 +1567,9 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
gasLimit = priorityFeeGas,
|
||||
feeFiatFormatted = priorityFiatFee,
|
||||
feeCryptoFormatted = priorityCryptoFee,
|
||||
feeIncludeOtherNativeFee = priorityFeeValue + otherNativeFeeValue,
|
||||
feeFiatFormattedWithNative = priorityFiatFeeWithNative,
|
||||
feeCryptoFormattedWithNative = priorityCryptoFeeWithNative,
|
||||
decimals = normalFee.fee.decimals,
|
||||
cryptoSymbol = normalFee.fee.currencySymbol,
|
||||
feeType = FeeType.PRIORITY,
|
||||
|
|
@ -1513,7 +1577,11 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun ProxyFees.SingleFee.proxyFeesToFeeState(fromToken: CryptoCurrency): TxFeeState {
|
||||
private suspend fun ProxyFees.SingleFee.proxyFeesToFeeState(
|
||||
fromToken: CryptoCurrency,
|
||||
otherNativeFee: BigDecimal? = null,
|
||||
): TxFeeState {
|
||||
val otherNativeFeeValue = otherNativeFee ?: BigDecimal.ZERO
|
||||
val normalFeeValue = this.singleFee.fee.value
|
||||
val normalFeeGas = this.singleFee.gasLimit.toInt()
|
||||
val feesFiat = getFormattedFiatFees(fromToken, normalFeeValue)
|
||||
|
|
@ -1522,12 +1590,28 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
amount = normalFeeValue,
|
||||
decimals = singleFee.fee.decimals,
|
||||
)
|
||||
// region fees include otherNativeFee
|
||||
val feesFiatWithNative = getFormattedFiatFees(
|
||||
fromToken = fromToken,
|
||||
normalFeeValue + otherNativeFeeValue,
|
||||
normalFeeValue + otherNativeFeeValue,
|
||||
)
|
||||
val normalFiatFeeWithNative =
|
||||
requireNotNull(feesFiatWithNative.getOrNull(0)) { "feesFiat item 0 couldn't be null" }
|
||||
val normalCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = normalFeeValue + otherNativeFeeValue,
|
||||
decimals = singleFee.fee.decimals,
|
||||
)
|
||||
//
|
||||
return TxFeeState.SingleFeeState(
|
||||
fee = TxFee(
|
||||
feeValue = normalFeeValue,
|
||||
gasLimit = normalFeeGas,
|
||||
feeFiatFormatted = normalFiatFee,
|
||||
feeCryptoFormatted = normalCryptoFee,
|
||||
feeIncludeOtherNativeFee = normalFeeValue + otherNativeFeeValue,
|
||||
feeFiatFormattedWithNative = normalFiatFeeWithNative,
|
||||
feeCryptoFormattedWithNative = normalCryptoFeeWithNative,
|
||||
decimals = singleFee.fee.decimals,
|
||||
cryptoSymbol = singleFee.fee.currencySymbol,
|
||||
feeType = FeeType.NORMAL,
|
||||
|
|
@ -1535,7 +1619,12 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun TransactionFee.toTxFeeState(fromToken: CryptoCurrency): TxFeeState {
|
||||
@Suppress("LongMethod")
|
||||
private suspend fun TransactionFee.toTxFeeState(
|
||||
fromToken: CryptoCurrency,
|
||||
otherNativeFee: BigDecimal?,
|
||||
): TxFeeState {
|
||||
val otherNativeFeeValue = otherNativeFee ?: BigDecimal.ZERO
|
||||
return when (this) {
|
||||
is TransactionFee.Choosable -> {
|
||||
val normalFee = this.normal.increaseGasLimitBy(INCREASE_GAS_LIMIT_FOR_SEND)
|
||||
|
|
@ -1553,12 +1642,31 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
amount = feePriority,
|
||||
decimals = priorityFee.amount.decimals,
|
||||
)
|
||||
|
||||
// region otherNativeFee
|
||||
val normalFeeWithOtherNative = feeNormal + otherNativeFeeValue
|
||||
val priorityFeeWithOtherNative = feeNormal + otherNativeFeeValue
|
||||
val normalFiatValueWithNative = getFormattedFiatFees(fromToken, normalFeeWithOtherNative)[0]
|
||||
val priorityFiatValueWithNative = getFormattedFiatFees(fromToken, priorityFeeWithOtherNative)[0]
|
||||
|
||||
val normalCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = normalFeeWithOtherNative,
|
||||
decimals = normalFee.amount.decimals,
|
||||
)
|
||||
val priorityCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = priorityFeeWithOtherNative,
|
||||
decimals = priorityFee.amount.decimals,
|
||||
)
|
||||
//
|
||||
TxFeeState.MultipleFeeState(
|
||||
normalFee = TxFee(
|
||||
feeValue = feeNormal,
|
||||
gasLimit = normalFee.getGasLimit(),
|
||||
feeFiatFormatted = normalFiatValue,
|
||||
feeCryptoFormatted = normalCryptoFee,
|
||||
feeIncludeOtherNativeFee = normalFeeWithOtherNative,
|
||||
feeFiatFormattedWithNative = normalFiatValueWithNative,
|
||||
feeCryptoFormattedWithNative = normalCryptoFeeWithNative,
|
||||
decimals = normalFee.amount.decimals,
|
||||
cryptoSymbol = normalFee.amount.currencySymbol,
|
||||
feeType = FeeType.NORMAL,
|
||||
|
|
@ -1568,6 +1676,9 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
gasLimit = priorityFee.getGasLimit(),
|
||||
feeFiatFormatted = priorityFiatValue,
|
||||
feeCryptoFormatted = priorityCryptoFee,
|
||||
feeIncludeOtherNativeFee = priorityFeeWithOtherNative,
|
||||
feeFiatFormattedWithNative = priorityFiatValueWithNative,
|
||||
feeCryptoFormattedWithNative = priorityCryptoFeeWithNative,
|
||||
decimals = priorityFee.amount.decimals,
|
||||
cryptoSymbol = priorityFee.amount.currencySymbol,
|
||||
feeType = FeeType.PRIORITY,
|
||||
|
|
@ -1581,12 +1692,24 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
amount = feeNormal,
|
||||
decimals = this.normal.amount.decimals,
|
||||
)
|
||||
// region otherNativeFee
|
||||
val normalFeeWithOtherNative = feeNormal + otherNativeFeeValue
|
||||
val normalFiatValueWithNative = getFormattedFiatFees(fromToken, normalFeeWithOtherNative)[0]
|
||||
|
||||
val normalCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = normalFeeWithOtherNative,
|
||||
decimals = this.normal.amount.decimals,
|
||||
)
|
||||
//
|
||||
TxFeeState.SingleFeeState(
|
||||
fee = TxFee(
|
||||
feeValue = this.normal.amount.value ?: BigDecimal.ZERO,
|
||||
gasLimit = this.normal.getGasLimit(),
|
||||
feeFiatFormatted = normalFiatValue,
|
||||
feeCryptoFormatted = normalCryptoFee,
|
||||
feeIncludeOtherNativeFee = normalFeeWithOtherNative,
|
||||
feeFiatFormattedWithNative = normalFiatValueWithNative,
|
||||
feeCryptoFormattedWithNative = normalCryptoFeeWithNative,
|
||||
decimals = normal.amount.decimals,
|
||||
cryptoSymbol = normal.amount.currencySymbol,
|
||||
feeType = FeeType.NORMAL,
|
||||
|
|
@ -1596,6 +1719,18 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createNativeAmountForDex(txValueAmount: String, network: Network): Amount {
|
||||
val nativeDecimals = Blockchain.fromNetworkId(network.backendId)?.decimals()
|
||||
?: error("Blockchain not found")
|
||||
val decimalValue = txValueAmount.toBigDecimalOrNull()?.movePointLeft(nativeDecimals)
|
||||
?: error("txValue parse error")
|
||||
return Amount(
|
||||
currencySymbol = network.currencySymbol,
|
||||
value = decimalValue,
|
||||
decimals = nativeDecimals,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Workaround to increase gas limit cause we calculate fee for random address
|
||||
*/
|
||||
|
|
@ -1656,7 +1791,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
if (fromToken.currency is CryptoCurrency.Token) {
|
||||
tokenBalance >= amount.value
|
||||
} else {
|
||||
tokenBalance > amount.value.plus(fee ?: BigDecimal.ZERO)
|
||||
tokenBalance >= amount.value.plus(fee ?: BigDecimal.ZERO)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ sealed class SwapPermissionState {
|
|||
object Empty : SwapPermissionState()
|
||||
|
||||
data class ReadyForRequest(
|
||||
val providerName: String,
|
||||
val currency: String,
|
||||
val amount: String,
|
||||
val walletAddress: String,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.feature.swap.models
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
|
|
@ -74,18 +73,21 @@ data class SwapButton(
|
|||
|
||||
sealed interface TransactionCardType {
|
||||
|
||||
val headerResId: Int
|
||||
val header: TextReference
|
||||
val isError: Boolean
|
||||
|
||||
data class Inputtable(
|
||||
val onAmountChanged: ((String) -> Unit),
|
||||
val onFocusChanged: ((Boolean) -> Unit),
|
||||
@StringRes override val headerResId: Int = R.string.swapping_from_title,
|
||||
override val isError: Boolean,
|
||||
override val header: TextReference = TextReference.Res(R.string.swapping_from_title),
|
||||
) : TransactionCardType
|
||||
|
||||
data class ReadOnly(
|
||||
val showWarning: Boolean = false,
|
||||
val onWarningClick: (() -> Unit)? = null,
|
||||
@StringRes override val headerResId: Int = R.string.swapping_to_title,
|
||||
override val isError: Boolean = false,
|
||||
override val header: TextReference = TextReference.Res(R.string.swapping_to_title),
|
||||
) : TransactionCardType
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +104,7 @@ data class LegalState(
|
|||
|
||||
sealed interface SwapWarning {
|
||||
data class PermissionNeeded(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
object InsufficientFunds : SwapWarning
|
||||
data object InsufficientFunds : SwapWarning
|
||||
data class NoAvailableTokensToSwap(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class GenericWarning(
|
||||
val title: TextReference? = null,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,11 @@ internal class StateBuilder(
|
|||
return SwapStateHolder(
|
||||
blockchainId = networkInfo.blockchainId,
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = TransactionCardType.Inputtable(actions.onAmountChanged, actions.onAmountSelected),
|
||||
type = TransactionCardType.Inputtable(
|
||||
onAmountChanged = actions.onAmountChanged,
|
||||
onFocusChanged = actions.onAmountSelected,
|
||||
isError = false,
|
||||
),
|
||||
amountEquivalent = null,
|
||||
amountTextFieldValue = null,
|
||||
token = null,
|
||||
|
|
@ -152,9 +156,13 @@ internal class StateBuilder(
|
|||
val canSelectReceiveToken = mainTokenId != toToken.id.value
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
val sendInput = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable).copy(
|
||||
isError = false,
|
||||
header = TextReference.Res(R.string.swapping_from_title),
|
||||
)
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable),
|
||||
type = sendInput,
|
||||
amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue,
|
||||
amountEquivalent = null,
|
||||
token = uiStateHolder.sendCardData.token,
|
||||
|
|
@ -221,9 +229,19 @@ internal class StateBuilder(
|
|||
val feeState = createFeeState(quoteModel.txFee, selectedFeeType)
|
||||
val fromCurrencyStatus = quoteModel.fromTokenInfo.cryptoCurrencyStatus
|
||||
val toCurrencyStatus = quoteModel.toTokenInfo.cryptoCurrencyStatus
|
||||
val isInsufficientFunds = isInsufficientFundsCondition(quoteModel)
|
||||
val insufficientFundsHeader = if (isInsufficientFunds) {
|
||||
TextReference.Res(R.string.swapping_insufficient_funds)
|
||||
} else {
|
||||
TextReference.Res(R.string.swapping_from_title)
|
||||
}
|
||||
val sendInput = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable).copy(
|
||||
isError = isInsufficientFunds,
|
||||
header = insufficientFundsHeader,
|
||||
)
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable),
|
||||
type = sendInput,
|
||||
amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue,
|
||||
amountEquivalent = getFormattedFiatAmount(quoteModel.fromTokenInfo.amountFiat),
|
||||
token = fromCurrencyStatus,
|
||||
|
|
@ -257,6 +275,7 @@ internal class StateBuilder(
|
|||
permissionState = convertPermissionState(
|
||||
lastPermissionState = uiStateHolder.permissionState,
|
||||
permissionDataState = quoteModel.permissionState,
|
||||
providerName = quoteModel.swapProvider.name,
|
||||
onGivePermissionClick = actions.onGivePermissionClick,
|
||||
onChangeApproveType = actions.onChangeApproveType,
|
||||
),
|
||||
|
|
@ -320,7 +339,7 @@ internal class StateBuilder(
|
|||
val warnings = mutableListOf<SwapWarning>()
|
||||
maybeAddDomainWarnings(quoteModel, warnings)
|
||||
maybeAddNeedReserveToCreateAccountWarning(quoteModel, warnings)
|
||||
maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken)
|
||||
maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken, quoteModel.swapProvider.name)
|
||||
maybeAddNetworkFeeCoverageWarning(quoteModel, warnings, selectedFeeType)
|
||||
maybeAddUnableCoverFeeWarning(quoteModel, fromToken, warnings)
|
||||
maybeAddInsufficientFundsWarning(quoteModel, warnings)
|
||||
|
|
@ -468,6 +487,7 @@ internal class StateBuilder(
|
|||
quoteModel: SwapState.QuotesLoadedState,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
fromToken: CryptoCurrency,
|
||||
providerName: String,
|
||||
) {
|
||||
if (!quoteModel.preparedSwapConfigState.isAllowedToSpend &&
|
||||
quoteModel.preparedSwapConfigState.feeState is SwapFeeState.Enough &&
|
||||
|
|
@ -475,7 +495,7 @@ internal class StateBuilder(
|
|||
) {
|
||||
warnings.add(
|
||||
SwapWarning.PermissionNeeded(
|
||||
createPermissionNotificationConfig(fromToken.symbol),
|
||||
createPermissionNotificationConfig(fromToken.symbol, providerName),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -493,8 +513,8 @@ internal class StateBuilder(
|
|||
warnings.add(
|
||||
SwapWarning.GeneralWarning(
|
||||
createNetworkFeeCoverageNotificationConfig(
|
||||
fee.feeCryptoFormatted,
|
||||
fee.feeFiatFormatted,
|
||||
fee.feeCryptoFormattedWithNative,
|
||||
fee.feeFiatFormattedWithNative,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -547,13 +567,16 @@ internal class StateBuilder(
|
|||
warnings: MutableList<SwapWarning>,
|
||||
) {
|
||||
// check isBalanceEnough, but for dex includeFeeInAmount always Excluded
|
||||
if (!quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
||||
quoteModel.preparedSwapConfigState.includeFeeInAmount !is IncludeFeeInAmount.Included
|
||||
) {
|
||||
if (isInsufficientFundsCondition(quoteModel)) {
|
||||
warnings.add(SwapWarning.InsufficientFunds)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isInsufficientFundsCondition(quoteModel: SwapState.QuotesLoadedState): Boolean {
|
||||
return !quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
||||
quoteModel.preparedSwapConfigState.includeFeeInAmount !is IncludeFeeInAmount.Included
|
||||
}
|
||||
|
||||
private fun getSwapButtonEnabled(quoteModel: SwapState.QuotesLoadedState): Boolean {
|
||||
val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value
|
||||
if (status is CryptoCurrencyStatus.NoAccount) {
|
||||
|
|
@ -964,9 +987,9 @@ internal class StateBuilder(
|
|||
return FeeItemState.Content(
|
||||
feeType = feeType,
|
||||
title = resourceReference(R.string.common_network_fee_title),
|
||||
amountCrypto = fee.feeCryptoFormatted,
|
||||
amountCrypto = fee.feeCryptoFormattedWithNative, // display fee with native as workaround for okx
|
||||
symbolCrypto = fee.cryptoSymbol,
|
||||
amountFiatFormatted = fee.feeFiatFormatted,
|
||||
amountFiatFormatted = fee.feeFiatFormattedWithNative, // display fee with native as workaround for okx
|
||||
isClickable = isClickable,
|
||||
onClick = actions.onClickFee,
|
||||
)
|
||||
|
|
@ -1017,7 +1040,7 @@ internal class StateBuilder(
|
|||
showStatusButton = shouldShowStatus,
|
||||
providerIcon = providerState.iconUrl,
|
||||
rate = providerState.subtitle,
|
||||
fee = stringReference("${fee.feeCryptoFormatted} (${fee.feeFiatFormatted})"),
|
||||
fee = stringReference("${fee.feeCryptoFormattedWithNative} (${fee.feeFiatFormattedWithNative})"),
|
||||
fromTokenAmount = stringReference(swapTransactionState.fromAmount.orEmpty()),
|
||||
toTokenAmount = stringReference(swapTransactionState.toAmount.orEmpty()),
|
||||
fromTokenFiatAmount = stringReference(fromFiatAmount),
|
||||
|
|
@ -1146,6 +1169,7 @@ internal class StateBuilder(
|
|||
private fun convertPermissionState(
|
||||
lastPermissionState: SwapPermissionState,
|
||||
permissionDataState: PermissionDataState,
|
||||
providerName: String,
|
||||
onGivePermissionClick: () -> Unit,
|
||||
onChangeApproveType: (ApproveType) -> Unit,
|
||||
): SwapPermissionState {
|
||||
|
|
@ -1165,6 +1189,7 @@ internal class StateBuilder(
|
|||
is TxFeeState.SingleFeeState -> fee.fee
|
||||
}
|
||||
SwapPermissionState.ReadyForRequest(
|
||||
providerName = providerName,
|
||||
currency = permissionDataState.currency,
|
||||
amount = permissionDataState.amount,
|
||||
approveType = approveType,
|
||||
|
|
@ -1358,18 +1383,18 @@ internal class StateBuilder(
|
|||
FeeItemState.Content(
|
||||
feeType = this.normalFee.feeType,
|
||||
title = resourceReference(R.string.common_network_fee_title),
|
||||
amountCrypto = this.normalFee.feeCryptoFormatted,
|
||||
amountCrypto = this.normalFee.feeCryptoFormattedWithNative,
|
||||
symbolCrypto = this.normalFee.cryptoSymbol,
|
||||
amountFiatFormatted = this.normalFee.feeFiatFormatted,
|
||||
amountFiatFormatted = this.normalFee.feeFiatFormattedWithNative,
|
||||
isClickable = true,
|
||||
onClick = {},
|
||||
),
|
||||
FeeItemState.Content(
|
||||
feeType = this.priorityFee.feeType,
|
||||
title = resourceReference(R.string.common_network_fee_title),
|
||||
amountCrypto = this.priorityFee.feeCryptoFormatted,
|
||||
amountCrypto = this.priorityFee.feeCryptoFormattedWithNative,
|
||||
symbolCrypto = this.priorityFee.cryptoSymbol,
|
||||
amountFiatFormatted = this.priorityFee.feeFiatFormatted,
|
||||
amountFiatFormatted = this.priorityFee.feeFiatFormattedWithNative,
|
||||
isClickable = true,
|
||||
onClick = {},
|
||||
),
|
||||
|
|
@ -1402,12 +1427,12 @@ internal class StateBuilder(
|
|||
}
|
||||
|
||||
// region warnings
|
||||
private fun createPermissionNotificationConfig(fromTokenSymbol: String): NotificationConfig {
|
||||
private fun createPermissionNotificationConfig(fromTokenSymbol: String, providerName: String): NotificationConfig {
|
||||
return NotificationConfig(
|
||||
title = resourceReference(R.string.express_provider_permission_needed),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.swapping_permission_subheader,
|
||||
formatArgs = wrappedList(fromTokenSymbol),
|
||||
formatArgs = wrappedList(providerName, fromTokenSymbol),
|
||||
),
|
||||
iconResId = R.drawable.ic_locked_24,
|
||||
)
|
||||
|
|
@ -1604,7 +1629,7 @@ internal class StateBuilder(
|
|||
id = this.providerId,
|
||||
name = this.name,
|
||||
iconUrl = this.imageLarge,
|
||||
type = this.type.toString(),
|
||||
type = this.type.providerName,
|
||||
selectionType = selectionType,
|
||||
alertText = alertText,
|
||||
onProviderClick = onProviderClick,
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ private fun SwapPermissionBottomSheetContent(content: GivePermissionBottomSheetC
|
|||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.swapping_permission_subheader,
|
||||
data.providerName,
|
||||
data.currency,
|
||||
),
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
|
|
@ -303,6 +304,7 @@ private fun Preview_AgreementBottomSheet() {
|
|||
|
||||
private val previewData = GivePermissionBottomSheetConfig(
|
||||
data = SwapPermissionState.ReadyForRequest(
|
||||
providerName = "1icnh",
|
||||
currency = "DAI",
|
||||
amount = "∞",
|
||||
walletAddress = "",
|
||||
|
|
|
|||
|
|
@ -434,7 +434,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U
|
|||
// region preview
|
||||
|
||||
private val sendCard = SwapCardState.SwapCardData(
|
||||
type = TransactionCardType.Inputtable({}, {}),
|
||||
type = TransactionCardType.Inputtable({}, {}, false),
|
||||
amountTextFieldValue = TextFieldValue(),
|
||||
amountEquivalent = "1 000 000",
|
||||
tokenIconUrl = "",
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import coil.compose.SubcomposeAsyncImage
|
|||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.ImageBackgroundContrastChecker
|
||||
|
|
@ -185,10 +186,14 @@ private fun Header(type: TransactionCardType, balance: String, modifier: Modifie
|
|||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val title = type.headerResId
|
||||
val titleColor = if (type.isError) {
|
||||
TangemTheme.colors.text.warning
|
||||
} else {
|
||||
TangemTheme.colors.text.tertiary
|
||||
}
|
||||
Text(
|
||||
text = stringResource(id = title),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
text = type.header.resolveReference(),
|
||||
color = titleColor,
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.subtitle2,
|
||||
modifier = Modifier
|
||||
|
|
@ -546,7 +551,7 @@ private fun Preview_TransactionCardWithoutPriceImpact_InDarkTheme() {
|
|||
@Composable
|
||||
private fun TransactionCardPreview() {
|
||||
TransactionCard(
|
||||
type = TransactionCardType.Inputtable({}, {}),
|
||||
type = TransactionCardType.Inputtable({}, {}, false),
|
||||
amountEquivalent = "1 000 000",
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "DAI",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.lib.crypto
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.TransactionExtras
|
||||
import com.tangem.lib.crypto.models.*
|
||||
import com.tangem.lib.crypto.models.transactions.SendTxResult
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
interface TransactionManager {
|
||||
|
||||
|
|
@ -46,7 +48,7 @@ interface TransactionManager {
|
|||
@Throws(IllegalStateException::class)
|
||||
suspend fun getFee(
|
||||
networkId: String,
|
||||
amountToSend: BigDecimal,
|
||||
amountToSend: Amount,
|
||||
currencyToSend: Currency,
|
||||
destinationAddress: String,
|
||||
increaseBy: Int?,
|
||||
|
|
@ -54,6 +56,9 @@ interface TransactionManager {
|
|||
derivationPath: String?,
|
||||
): ProxyFees
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun getFeeForGas(networkId: String, gas: BigInteger, derivationPath: String?): ProxyFees
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun updateWalletManager(networkId: String, derivationPath: String?)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue