From c0471dbf9fc04833a0b45a6cdafebb7c0b370fd5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Jul 2024 14:51:41 +0100 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/proxy/TransactionManagerImpl.kt | 39 +-- .../models/response/ExchangeDataResponse.kt | 9 +- core/res/src/main/res/values-ru/strings.xml | 2 +- .../src/main/res/values-zh-rTW/strings.xml | 2 +- core/res/src/main/res/values/strings.xml | 2 +- .../tokens/utils/CryptoCurrencyFactory.kt | 2 +- .../DefaultTransactionRepository.kt | 29 +-- .../transaction/TransactionRepository.kt | 1 - .../usecase/CreateTransactionUseCase.kt | 2 - .../swap/converters/ExpressDataConverter.kt | 12 + .../models/domain/ExpressTransactionModel.kt | 10 + .../swap/domain/models/ui/SwapState.kt | 9 + .../feature/swap/domain/SwapInteractorImpl.kt | 227 ++++++++++++++---- .../swap/models/SwapPermissionStateHolder.kt | 1 + .../feature/swap/models/SwapStateHolder.kt | 12 +- .../tangem/feature/swap/ui/StateBuilder.kt | 65 +++-- .../swap/ui/SwapPermissionBottomSheet.kt | 2 + .../feature/swap/ui/SwapScreenContent.kt | 2 +- .../tangem/feature/swap/ui/TransactionCard.kt | 13 +- .../tangem/lib/crypto/TransactionManager.kt | 7 +- 20 files changed, 315 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index a18e07daef..8177d10df8 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -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): SendTxResult { when (result) { is Result.Success -> { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeDataResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeDataResponse.kt index 7496a0a8e2..5e1eb6d5d3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeDataResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeDataResponse.kt @@ -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 { diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index e70467d63f..3ee8a28d64 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -568,7 +568,7 @@ Дать разрешение Укажите лимит доступа к выбранному токену Количество %s - Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s + Чтобы продолжить, вам нужно разрешить смарт-контракту %1$s использовать ваш %2$s Безлимитно В процессе Обменять diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index e0b8c69381..2a44adee3c 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -283,7 +283,7 @@ 允許 賦予權限 數量 %s - 要繼續,您需要允許 1inch 智能合約使用您的 %s + 要繼續,您需要允許 %1$s 智能合約使用您的 %2$s 進行中 交易 選擇代幣 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 40b0b45e01..ffdb3de5f8 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -561,7 +561,7 @@ Give Permission Specify the approve limit for the selected token Amount %s - To continue, grant 1inch smart contracts permission to use your %s + To continue, grant %1$s smart contracts permission to use your %2$s Unlimited In progress Swap diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CryptoCurrencyFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CryptoCurrencyFactory.kt index 55b2b0b161..b1e215ed19 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CryptoCurrencyFactory.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CryptoCurrencyFactory.kt @@ -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, diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index d36cb4f74b..3f3d8aaa68 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -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, - ) - } - } - } } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt index 52b707e8d1..181ce66767 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt @@ -16,7 +16,6 @@ interface TransactionRepository { destination: String, userWalletId: UserWalletId, network: Network, - isSwap: Boolean, txExtras: TransactionExtras?, hash: String?, ): TransactionData? diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt index 056dfc0f0c..ab5172eaf8 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt @@ -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, ), diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExpressDataConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExpressDataConverter.kt index 1fb2d9a8d2..34c663840b 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExpressDataConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExpressDataConverter.kt @@ -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 { @@ -24,19 +25,30 @@ internal class ExpressDataConverter : Converter = 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, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index df53e5e0e7..e5111d0545 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -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 { 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, 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) } } } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt index 4192106336..7e31eb9e60 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapPermissionStateHolder.kt @@ -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, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index cd22de2a3b..6e429c9c13 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -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, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 1925e60ee7..05a1fa9451 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -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() 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, 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, ) { // 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, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt index 8ac175588a..1915863d98 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapPermissionBottomSheet.kt @@ -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 = "", diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 7be20ace87..fc0b98feb3 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -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 = "", diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index 7ae3edc6a2..9baf09e90a 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -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", diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt index 6b69d96fb8..a3a9846b43 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt @@ -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?)