Updated on 2026-08-14

This commit is contained in:
Tangem 2024-07-04 14:51:41 +01:00
parent 7e35a4de75
commit c0471dbf9f
20 changed files with 315 additions and 133 deletions

View file

@ -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 -> {

View file

@ -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 {

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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,

View file

@ -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,
)
}
}
}
}

View file

@ -16,7 +16,6 @@ interface TransactionRepository {
destination: String,
userWalletId: UserWalletId,
network: Network,
isSwap: Boolean,
txExtras: TransactionExtras?,
hash: String?,
): TransactionData?

View file

@ -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,
),

View file

@ -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),

View file

@ -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?,

View file

@ -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,

View file

@ -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)
}
}
}

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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 = "",

View file

@ -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 = "",

View file

@ -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",

View file

@ -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?)