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 49da15b6c1..bc8fe9a242 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -22,6 +22,7 @@ import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.ProxyAmount +import com.tangem.lib.crypto.models.ProxyFee import com.tangem.lib.crypto.models.ProxyNetworkInfo import com.tangem.lib.crypto.models.transactions.SendTxResult import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -31,7 +32,11 @@ import com.tangem.tap.domain.TangemSigner import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.tangemSdk import java.math.BigDecimal +import java.math.BigInteger +import java.math.MathContext +import java.math.RoundingMode +@Suppress("LargeClass") class TransactionManagerImpl( private val appStateHolder: AppStateHolder, private val analytics: AnalyticsHandler, @@ -40,7 +45,7 @@ class TransactionManagerImpl( override suspend fun sendApproveTransaction( networkId: String, feeAmount: BigDecimal, - estimatedGas: Int, + gasLimit: Int, destinationAddress: String, dataToSign: String, ): SendTxResult { @@ -53,7 +58,7 @@ class TransactionManagerImpl( amount = amount, blockchain = blockchain, feeAmount = feeAmount, - estimatedGas = estimatedGas, + gasLimit = gasLimit, destinationAddress = destinationAddress, dataToSign = dataToSign, ) @@ -63,7 +68,7 @@ class TransactionManagerImpl( networkId: String, amountToSend: BigDecimal, feeAmount: BigDecimal, - estimatedGas: Int, + gasLimit: Int, destinationAddress: String, dataToSign: String, isSwap: Boolean, @@ -82,7 +87,7 @@ class TransactionManagerImpl( amount = amount, blockchain = blockchain, feeAmount = feeAmount, - estimatedGas = estimatedGas, + gasLimit = gasLimit, destinationAddress = destinationAddress, dataToSign = dataToSign, ) @@ -94,7 +99,7 @@ class TransactionManagerImpl( amount: Amount, blockchain: Blockchain, feeAmount: BigDecimal, - estimatedGas: Int, + gasLimit: Int, destinationAddress: String, dataToSign: String, ): SendTxResult { @@ -102,12 +107,12 @@ class TransactionManagerImpl( amount = amount, fee = Amount(value = feeAmount, blockchain = blockchain), destination = destinationAddress, - ).copy(hash = dataToSign, extras = createExtras(walletManager, estimatedGas, dataToSign)) + ).copy(hash = dataToSign, extras = createExtras(walletManager, gasLimit, dataToSign)) val signer = transactionSigner(walletManager) val sendResult = try { - (walletManager as TransactionSender).send(txData, signer) + (walletManager as? TransactionSender)?.send(txData, signer) ?: error("Cannot cast to TransactionSender") } catch (ex: Exception) { FirebaseCrashlytics.getInstance().recordException(ex) return SendTxResult.UnknownError(ex) @@ -141,19 +146,54 @@ class TransactionManagerImpl( amountToSend: BigDecimal, currencyToSend: Currency, destinationAddress: String, - ): ProxyAmount { + data: String?, + ): ProxyFee { val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } val walletManager = getActualWalletManager(blockchain) - val fee = (walletManager as TransactionSender).getFee( - amount = createAmount(amountToSend, currencyToSend, blockchain), - destination = destinationAddress, - ) - when (fee) { - is Result.Success -> { - return convertToProxyAmount(fee.data.firstOrNull() ?: error("no fee found")) + if (walletManager is EthereumWalletManager) { + val gasLimit = getGasLimit( + evmWalletManager = walletManager, + blockchain = blockchain, + amount = amountToSend, + currency = currencyToSend, + destinationAddress = destinationAddress, + data = data, + ) + return when (val gasPrice = walletManager.getGasPrice()) { + is Result.Success -> { + val fee = gasLimit.multiply(gasPrice.data).toBigDecimal( + scale = blockchain.decimals(), + mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN), + ) + ProxyFee( + gasLimit = gasLimit, + fee = ProxyAmount( + currencySymbol = blockchain.currency, + value = fee, + decimals = blockchain.decimals(), + ), + ) + } + is Result.Failure -> { + error(gasPrice.error.message ?: gasPrice.error.customMessage) + } } - is Result.Failure -> { - error(fee.error.message ?: fee.error.customMessage) + } else { + val fee = (walletManager as? TransactionSender)?.getFee( + amount = createAmount(amountToSend, currencyToSend, blockchain), + destination = destinationAddress, + ) ?: error("Cannot cast to TransactionSender") + when (fee) { + is Result.Success -> { + // for not EVM blockchains set gasLimit ZERO for now + return ProxyFee( + gasLimit = BigInteger.ZERO, + fee = convertToProxyAmount(fee.data.firstOrNull() ?: error("no fee found")), + ) + } + is Result.Failure -> { + error(fee.error.message ?: fee.error.customMessage) + } } } } @@ -167,6 +207,37 @@ class TransactionManagerImpl( ) } + @Suppress("LongParameterList") + private suspend fun getGasLimit( + evmWalletManager: EthereumWalletManager, + blockchain: Blockchain, + amount: BigDecimal, + currency: Currency, + destinationAddress: String, + data: String?, + ): BigInteger { + val result = if (data.isNullOrEmpty()) { + evmWalletManager.getGasLimit( + amount = createAmount(amount, currency, blockchain), + destination = destinationAddress, + ) + } else { + evmWalletManager.getGasLimit( + amount = createAmount(amount, currency, blockchain), + destination = destinationAddress, + data = data, + ) + } + when (result) { + is Result.Success -> { + return result.data + } + is Result.Failure -> { + error(result.error.message ?: result.error.customMessage) + } + } + } + private fun handleSendResult(result: SimpleResult): SendTxResult { when (result) { is SimpleResult.Success -> { @@ -239,14 +310,14 @@ class TransactionManagerImpl( private fun createExtras( walletManager: WalletManager, - estimatedGas: Int, + gasLimit: Int, transactionHash: String, ): TransactionExtras? { return when (walletManager) { is EthereumWalletManager -> { return EthereumTransactionExtras( data = transactionHash.removePrefix(HEX_PREFIX).hexToBytes(), - gasLimit = estimatedGas.toBigInteger(), + gasLimit = gasLimit.toBigInteger(), ) } else -> { diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index adb41a5c61..b02e115cd7 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -61,10 +61,10 @@ object Versions { // endregion Other libraries // region Tangem - const val tangemBlockchainSdk = "develop-173" + const val tangemBlockchainSdk = "develop-174" // const val tangemBlockchainSdk = "0.0.1" // Keep it! - used for local builds - const val tangemCardSdk = "develop-198" + const val tangemCardSdk = "develop-199" // const val tangemCardSdk = "0.0.1" // Keep it! - used for local builds // endregion Tangem diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index 4a5da9708f..1ea5c8378c 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -1,11 +1,11 @@ package com.tangem.feature.swap.domain import com.tangem.feature.swap.domain.models.SwapAmount -import com.tangem.feature.swap.domain.models.domain.ApproveModel import com.tangem.feature.swap.domain.models.domain.Currency -import com.tangem.feature.swap.domain.models.domain.SwapDataModel import com.tangem.feature.swap.domain.models.ui.FoundTokensState +import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData import com.tangem.feature.swap.domain.models.ui.SwapState +import com.tangem.feature.swap.domain.models.ui.SwapStateData import com.tangem.feature.swap.domain.models.ui.TokensDataState import com.tangem.feature.swap.domain.models.ui.TxState @@ -42,15 +42,13 @@ interface SwapInteractor { * Gives permission to swap, this starts scan card process * * @param networkId network in which selected token - * @param estimatedGas estimated gas for transaction - * @param transactionData tx data to give approve, it loaded from 1inch in findBestQuote if needed + * @param approveData tx data to give approve, it loaded from 1inch in findBestQuote if needed * @param forTokenContractAddress token contract address for which needs permission */ @Throws(IllegalStateException::class) suspend fun givePermissionToSwap( networkId: String, - estimatedGas: Int, - transactionData: ApproveModel, + approveData: RequestApproveStateData, forTokenContractAddress: String, ): TxState @@ -76,7 +74,7 @@ interface SwapInteractor { * Starts swap transaction, perform sign transaction * * @param networkId network for tokens - * @param swapData tx data to swap, contains data to sign + * @param swapStateData tx data to swap, contains data to sign * @param currencyToSend [Currency] * @param currencyToGet [Currency] * @param amountToSwap amount to swap @@ -85,7 +83,7 @@ interface SwapInteractor { @Throws(IllegalStateException::class) suspend fun onSwap( networkId: String, - swapData: SwapDataModel, + swapStateData: SwapStateData, currencyToSend: Currency, currencyToGet: Currency, amountToSwap: String, 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 3b78f398f7..4127ecc179 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,10 +4,8 @@ import com.tangem.feature.swap.domain.cache.SwapDataCache import com.tangem.feature.swap.domain.converters.CryptoCurrencyConverter import com.tangem.feature.swap.domain.models.DataError import com.tangem.feature.swap.domain.models.SwapAmount -import com.tangem.feature.swap.domain.models.domain.ApproveModel import com.tangem.feature.swap.domain.models.domain.Currency import com.tangem.feature.swap.domain.models.domain.PreparedSwapConfigState -import com.tangem.feature.swap.domain.models.domain.SwapDataModel import com.tangem.feature.swap.domain.models.toStringWithRightOffset import com.tangem.feature.swap.domain.models.ui.AmountFormatter import com.tangem.feature.swap.domain.models.ui.FoundTokensState @@ -15,6 +13,7 @@ import com.tangem.feature.swap.domain.models.ui.PermissionDataState import com.tangem.feature.swap.domain.models.ui.PreselectTokens import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData import com.tangem.feature.swap.domain.models.ui.SwapState +import com.tangem.feature.swap.domain.models.ui.SwapStateData import com.tangem.feature.swap.domain.models.ui.TokenBalanceData import com.tangem.feature.swap.domain.models.ui.TokenSwapInfo import com.tangem.feature.swap.domain.models.ui.TokenWithBalance @@ -110,19 +109,15 @@ internal class SwapInteractorImpl @Inject constructor( override suspend fun givePermissionToSwap( networkId: String, - estimatedGas: Int, - transactionData: ApproveModel, + approveData: RequestApproveStateData, forTokenContractAddress: String, ): TxState { - val increasedEstimatedGas = increaseByPercents(TWENTY_FIVE_PERCENTS, estimatedGas) - val gasPrice = transactionData.gasPrice.toBigDecimalOrNull() ?: error("cannot parse gasPrice") - val fee = transactionManager.calculateFee(networkId, gasPrice.toPlainString(), increasedEstimatedGas) val result = transactionManager.sendApproveTransaction( networkId = networkId, - feeAmount = fee, - estimatedGas = estimatedGas, - destinationAddress = transactionData.toAddress, - dataToSign = transactionData.data, + feeAmount = approveData.fee, + gasLimit = approveData.gasLimit, + destinationAddress = approveData.approveModel.toAddress, + dataToSign = approveData.approveModel.data, ) return when (result) { is SendTxResult.Success -> { @@ -156,32 +151,9 @@ internal class SwapInteractorImpl @Inject constructor( allowPermissionsHandler.removeAddressFromProgress(fromTokenAddress) transactionManager.updateWalletManager(networkId) } - // load initial quotes data, it works despite balance - val quotesData = loadQuoteData( - networkId = networkId, - fromTokenAddress = fromTokenAddress, - toTokenAddress = toTokenAddress, - amount = amount, - fromToken = fromToken, - toToken = toToken, - ) - // get fee from loaded quotes data, if error, use blockchain fee for 0 amount tx - val fee = getInchFee(quotesData) - val isFeeEnough = checkFeeIsEnough( - fee = fee, - spendAmount = amount, - networkId = networkId, - fromToken = fromToken, - ) - val isBalanceEnough = isBalanceEnough(fromToken, amount, fee) - val preparedSwapConfigState = PreparedSwapConfigState( - isAllowedToSpend = isAllowedToSpend, - isBalanceEnough = isBalanceEnough, - isFeeEnough = isFeeEnough, - ) - return if (isAllowedToSpend && isBalanceEnough && isFeeEnough) { - // if enough balance, fee and spend was allowed, request swap data - val swapData = loadSwapData( + val isBalanceWithoutFeeEnough = isBalanceEnough(fromToken, amount, null) + return if (isAllowedToSpend && isBalanceWithoutFeeEnough) { + loadSwapData( networkId = networkId, fromTokenAddress = fromTokenAddress, toTokenAddress = toTokenAddress, @@ -189,42 +161,36 @@ internal class SwapInteractorImpl @Inject constructor( toToken = toToken, amount = amount, ) - if (swapData is SwapState.QuotesLoadedState) { - swapData.copy(preparedSwapConfigState = preparedSwapConfigState) - } else { - swapData - } } else { - if (quotesData is SwapState.QuotesLoadedState) { - quotesData.copy(preparedSwapConfigState = preparedSwapConfigState) - } else { - quotesData - } + loadQuoteData( + networkId = networkId, + fromTokenAddress = fromTokenAddress, + toTokenAddress = toTokenAddress, + amount = amount, + fromToken = fromToken, + toToken = toToken, + isAllowedToSpend = isAllowedToSpend, + isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough, + ) } } override suspend fun onSwap( networkId: String, - swapData: SwapDataModel, + swapStateData: SwapStateData, currencyToSend: Currency, currencyToGet: Currency, amountToSwap: String, ): TxState { val amount = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format, use only digits" } - val estimatedGas = swapData.transaction.gas.toIntOrNull() ?: DEFAULT_GAS - val fee = transactionManager.calculateFee( - networkId = networkId, - gasPrice = swapData.transaction.gasPrice, - estimatedGas = estimatedGas, - ) val result = transactionManager.sendTransaction( networkId = networkId, amountToSend = amount, currencyToSend = cryptoCurrencyConverter.convert(currencyToSend), - feeAmount = fee, - estimatedGas = estimatedGas, - destinationAddress = swapData.transaction.toWalletAddress, - dataToSign = swapData.transaction.data, + feeAmount = swapStateData.fee, + gasLimit = swapStateData.gasLimit, + destinationAddress = swapStateData.swapModel.transaction.toWalletAddress, + dataToSign = swapStateData.swapModel.transaction.data, isSwap = true, ) return when (result) { @@ -232,8 +198,14 @@ internal class SwapInteractorImpl @Inject constructor( userWalletManager.addToken(cryptoCurrencyConverter.convert(currencyToGet)) userWalletManager.refreshWallet() TxState.TxSent( - fromAmount = amountFormatter.formatSwapAmountToUI(swapData.fromTokenAmount, currencyToSend.symbol), - toAmount = amountFormatter.formatSwapAmountToUI(swapData.toTokenAmount, currencyToGet.symbol), + fromAmount = amountFormatter.formatSwapAmountToUI( + swapStateData.swapModel.fromTokenAmount, + currencyToSend.symbol, + ), + toAmount = amountFormatter.formatSwapAmountToUI( + swapStateData.swapModel.toTokenAmount, + currencyToGet.symbol, + ), txAddress = userWalletManager.getLastTransactionHash(networkId) ?: "", ) } @@ -265,12 +237,6 @@ internal class SwapInteractorImpl @Inject constructor( } } - private fun getInchFee(quotesData: SwapState): BigDecimal? { - return if (quotesData is SwapState.QuotesLoadedState) { - quotesData.feeRaw - } else null - } - private fun selectToToken( initialToken: Currency, tokensInWallet: List, @@ -353,6 +319,8 @@ internal class SwapInteractorImpl @Inject constructor( amount: SwapAmount, fromToken: Currency, toToken: Currency, + isAllowedToSpend: Boolean, + isBalanceWithoutFeeEnough: Boolean, ): SwapState { repository.findBestQuote( networkId = networkId, @@ -362,35 +330,25 @@ internal class SwapInteractorImpl @Inject constructor( ).let { quotes -> val quoteDataModel = quotes.dataModel if (quoteDataModel != null) { - val transactionData = repository.dataToApprove(networkId, getTokenAddress(fromToken)) - val fee = transactionManager.calculateFee( - networkId = networkId, - estimatedGas = quoteDataModel.estimatedGas, - gasPrice = transactionData.gasPrice, - ) - val feeFiat = getFormattedFiatFee(networkId, fromToken.id, toToken.id, fee) - val formattedFee = amountFormatter.formatBigDecimalAmountToUI( - amount = fee, - decimals = transactionManager.getNativeTokenDecimals(networkId), - currency = userWalletManager.getNetworkCurrency(networkId), - ) + feeFiat val swapState = updateBalances( networkId = networkId, fromToken = fromToken, toToken = toToken, fromTokenAmount = quoteDataModel.fromTokenAmount, toTokenAmount = quoteDataModel.toTokenAmount, - formattedFee = formattedFee, - feeRaw = fee, - swapDataModel = null, + swapStateData = null, + formattedFee = null, ) return updatePermissionState( networkId = networkId, fromToken = fromToken, quotesLoadedState = swapState, - estimatedGas = quoteDataModel.estimatedGas, - transactionData = transactionData, - formattedFee = formattedFee, + ).copy( + preparedSwapConfigState = PreparedSwapConfigState( + isAllowedToSpend = isAllowedToSpend, + isBalanceEnough = isBalanceWithoutFeeEnough, + isFeeEnough = true, + ), ) } else { return SwapState.SwapError(quotes.error) @@ -400,13 +358,11 @@ internal class SwapInteractorImpl @Inject constructor( private suspend fun getFormattedFiatFee( networkId: String, - fromTokenId: String, - toTokenId: String, fee: BigDecimal, ): String { val appCurrency = userWalletManager.getUserAppCurrency() val nativeToken = userWalletManager.getNativeTokenForNetwork(networkId) - val rates = repository.getRates(appCurrency.code, listOf(fromTokenId, toTokenId, nativeToken.id)) + val rates = repository.getRates(appCurrency.code, listOf(nativeToken.id)) return rates[nativeToken.id]?.toBigDecimal()?.let { rate -> " (${fee.toFiatString(rate, appCurrency.symbol, true)})" }.orEmpty() @@ -434,17 +390,26 @@ internal class SwapInteractorImpl @Inject constructor( ).let { val swapData = it.dataModel if (swapData != null) { - val fee = transactionManager.calculateFee( + val feeData = transactionManager.getFee( networkId = networkId, - estimatedGas = swapData.transaction.gas.toIntOrNull() ?: DEFAULT_GAS, - gasPrice = swapData.transaction.gasPrice, + amountToSend = amount.value, + currencyToSend = cryptoCurrencyConverter.convert(fromToken), + destinationAddress = swapData.transaction.toWalletAddress, + data = swapData.transaction.data, ) - val feeFiat = getFormattedFiatFee(networkId, fromToken.id, toToken.id, fee) + val feeFiat = getFormattedFiatFee(networkId, feeData.fee.value) val formattedFee = amountFormatter.formatBigDecimalAmountToUI( - amount = fee, + amount = feeData.fee.value, decimals = transactionManager.getNativeTokenDecimals(networkId), currency = userWalletManager.getNetworkCurrency(networkId), ) + feeFiat + val isBalanceIncludeFeeEnough = isBalanceEnough(fromToken, amount, feeData.fee.value) + val isFeeEnough = checkFeeIsEnough( + fee = feeData.fee.value, + spendAmount = amount, + networkId = networkId, + fromToken = fromToken, + ) val swapState = updateBalances( networkId = networkId, fromToken = fromToken, @@ -452,11 +417,19 @@ internal class SwapInteractorImpl @Inject constructor( fromTokenAmount = swapData.fromTokenAmount, toTokenAmount = swapData.toTokenAmount, formattedFee = formattedFee, - swapDataModel = swapData, - feeRaw = fee, + swapStateData = SwapStateData( + gasLimit = feeData.gasLimit.toInt(), + fee = feeData.fee.value, + swapModel = swapData, + ), ) return swapState.copy( permissionState = PermissionDataState.Empty, + preparedSwapConfigState = PreparedSwapConfigState( + isAllowedToSpend = true, + isBalanceEnough = isBalanceIncludeFeeEnough, + isFeeEnough = isFeeEnough, + ), ) } else { return SwapState.SwapError(it.error) @@ -471,9 +444,8 @@ internal class SwapInteractorImpl @Inject constructor( toToken: Currency, fromTokenAmount: SwapAmount, toTokenAmount: SwapAmount, - formattedFee: String, - feeRaw: BigDecimal, - swapDataModel: SwapDataModel?, + formattedFee: String?, + swapStateData: SwapStateData?, ): SwapState.QuotesLoadedState { val appCurrency = userWalletManager.getUserAppCurrency() val nativeToken = userWalletManager.getNativeTokenForNetwork(networkId) @@ -511,21 +483,18 @@ internal class SwapInteractorImpl @Inject constructor( toRate = rates[toToken.id] ?: 0.0, ), networkCurrency = userWalletManager.getNetworkCurrency(networkId), - swapDataModel = swapDataModel, + swapDataModel = swapStateData, tangemFee = getTangemFee(), - feeRaw = feeRaw, ) } @Suppress("LongParameterList") - private fun updatePermissionState( + private suspend fun updatePermissionState( networkId: String, fromToken: Currency, quotesLoadedState: SwapState.QuotesLoadedState, - estimatedGas: Int, - transactionData: ApproveModel, - formattedFee: String, ): SwapState.QuotesLoadedState { + // if token balance ZERO not show permission state to avoid user to spend money for fee val isTokenZeroBalance = getTokenBalance(fromToken).value.compareTo(BigDecimal.ZERO) == 0 if (isTokenZeroBalance) { return quotesLoadedState.copy( @@ -537,6 +506,20 @@ internal class SwapInteractorImpl @Inject constructor( permissionState = PermissionDataState.PermissionLoading, ) } + val transactionData = repository.dataToApprove(networkId, getTokenAddress(fromToken)) + val feeData = transactionManager.getFee( + networkId = networkId, + amountToSend = BigDecimal.ZERO, + currencyToSend = userWalletManager.getNativeTokenForNetwork(networkId), + destinationAddress = transactionData.toAddress, + data = transactionData.data, + ) + val feeFiat = getFormattedFiatFee(networkId, feeData.fee.value) + val formattedFee = amountFormatter.formatBigDecimalAmountToUI( + amount = feeData.fee.value, + decimals = transactionManager.getNativeTokenDecimals(networkId), + currency = userWalletManager.getNetworkCurrency(networkId), + ) + feeFiat return quotesLoadedState.copy( permissionState = PermissionDataState.PermissionReadyForRequest( currency = fromToken.symbol, @@ -545,7 +528,8 @@ internal class SwapInteractorImpl @Inject constructor( spenderAddress = transactionData.toAddress, fee = formattedFee, requestApproveData = RequestApproveStateData( - estimatedGas = estimatedGas, + fee = feeData.fee.value, + gasLimit = feeData.gasLimit.toInt(), approveModel = transactionData, ), ), @@ -613,11 +597,6 @@ internal class SwapInteractorImpl @Inject constructor( } } - @Suppress("MagicNumber") - private fun increaseByPercents(percents: Int, value: Int): Int { - return value * (percents / 100 + 1) - } - private fun toBigDecimalOrNull(amountToSwap: String): BigDecimal? { return amountToSwap.replace(",", ".").toBigDecimalOrNull() } @@ -636,9 +615,7 @@ internal class SwapInteractorImpl @Inject constructor( companion object { private const val DEFAULT_SLIPPAGE = 2 private const val ZERO_BALANCE = "0" - private const val DEFAULT_GAS = 300000 private const val DEFAULT_BLOCKCHAIN_INCH_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - private const val TWENTY_FIVE_PERCENTS = 25 private const val INCREASE_FEE_TO_CHECK_ENOUGH_PERCENT = 1.4 private const val USDT_SYMBOL = "USDT" private const val USDC_SYMBOL = "USDC" diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt index deace8a843..af6b0745f3 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt @@ -12,7 +12,7 @@ sealed interface SwapState { data class QuotesLoadedState( val fromTokenInfo: TokenSwapInfo, val toTokenInfo: TokenSwapInfo, - val fee: String, + val fee: String?, val priceImpact: Float, val networkCurrency: String, val preparedSwapConfigState: PreparedSwapConfigState = PreparedSwapConfigState( @@ -21,9 +21,8 @@ sealed interface SwapState { isFeeEnough = false, ), val permissionState: PermissionDataState = PermissionDataState.Empty, - val swapDataModel: SwapDataModel? = null, + val swapDataModel: SwapStateData? = null, val tangemFee: Double, - val feeRaw: BigDecimal, ) : SwapState data class EmptyAmountState( @@ -61,6 +60,13 @@ data class TokenSwapInfo( ) data class RequestApproveStateData( - val estimatedGas: Int, + val fee: BigDecimal, + val gasLimit: Int, val approveModel: ApproveModel, +) + +data class SwapStateData( + val fee: BigDecimal, + val gasLimit: Int, + val swapModel: SwapDataModel, ) \ No newline at end of file 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 31d4ce3cb4..c5924121b8 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 @@ -131,9 +131,9 @@ internal class StateBuilder(val actions: UiActions) { warnings.add(SwapWarning.HighPriceImpact((quoteModel.priceImpact * HUNDRED_PERCENTS).toInt())) } val feeState = if (quoteModel.preparedSwapConfigState.isFeeEnough) { - FeeState.Loaded(tangemFee = quoteModel.tangemFee, fee = quoteModel.fee) + FeeState.Loaded(tangemFee = quoteModel.tangemFee, fee = quoteModel.fee ?: "") } else { - FeeState.NotEnoughFundsWarning(tangemFee = quoteModel.tangemFee, fee = quoteModel.fee) + FeeState.NotEnoughFundsWarning(tangemFee = quoteModel.tangemFee, fee = quoteModel.fee ?: "") } return uiStateHolder.copy( sendCardData = SwapCardData( 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 0d9a788af5..78df67253d 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 @@ -255,12 +255,14 @@ private fun FeeItem(feeState: FeeState, currency: String) { val disclaimer = stringResource(id = R.string.swapping_tangem_fee_disclaimer, "${feeState.tangemFee}%") when (feeState) { is FeeState.Loaded -> { - SmallInfoCardWithDisclaimer( - startText = titleString, - endText = feeState.fee, - disclaimer = disclaimer, - isLoading = false, - ) + if (feeState.fee.isNotEmpty()) { + SmallInfoCardWithDisclaimer( + startText = titleString, + endText = feeState.fee, + disclaimer = disclaimer, + isLoading = false, + ) + } } FeeState.Loading -> { SmallInfoCardWithDisclaimer( @@ -271,16 +273,18 @@ private fun FeeItem(feeState: FeeState, currency: String) { ) } is FeeState.NotEnoughFundsWarning -> { - SmallInfoCardWithWarning( - startText = titleString, - endText = feeState.fee, - disclaimer = disclaimer, - warningText = stringResource( - id = R.string.swapping_not_enough_funds_for_fee, - currency, - currency, - ), - ) + if (feeState.fee.isNotEmpty()) { + SmallInfoCardWithWarning( + startText = titleString, + endText = feeState.fee, + disclaimer = disclaimer, + warningText = stringResource( + id = R.string.swapping_not_enough_funds_for_fee, + currency, + currency, + ), + ) + } } is FeeState.Empty -> { SmallInfoCard(startText = titleString, endText = "") diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt index 4fe4e4bfb8..fdc185ac48 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt @@ -1,15 +1,14 @@ package com.tangem.feature.swap.viewmodels -import com.tangem.feature.swap.domain.models.domain.ApproveModel import com.tangem.feature.swap.domain.models.domain.Currency -import com.tangem.feature.swap.domain.models.domain.SwapDataModel +import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData +import com.tangem.feature.swap.domain.models.ui.SwapStateData data class SwapProcessDataState( val networkId: String, val fromCurrency: Currency? = null, val toCurrency: Currency? = null, val amount: String? = null, - val estimatedGas: Int? = null, - val approveModel: ApproveModel? = null, - val swapModel: SwapDataModel? = null, + val approveDataModel: RequestApproveStateData? = null, + val swapDataModel: SwapStateData? = null, ) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 8851cc69d2..47a659a127 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -12,11 +12,11 @@ import com.tangem.feature.swap.analytics.SwapEvents import com.tangem.feature.swap.domain.BlockchainInteractor import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.feature.swap.domain.models.domain.Currency -import com.tangem.feature.swap.domain.models.domain.SwapDataModel import com.tangem.feature.swap.domain.models.formatToUIRepresentation import com.tangem.feature.swap.domain.models.ui.FoundTokensState import com.tangem.feature.swap.domain.models.ui.PermissionDataState import com.tangem.feature.swap.domain.models.ui.SwapState +import com.tangem.feature.swap.domain.models.ui.SwapStateData import com.tangem.feature.swap.domain.models.ui.TxState import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.models.UiActions @@ -169,9 +169,8 @@ internal class SwapViewModel @Inject constructor( runCatching(dispatchers.io) { dataState = dataState.copy( amount = amount, - swapModel = null, - estimatedGas = null, - approveModel = null, + swapDataModel = null, + approveDataModel = null, ) swapInteractor.findBestQuote( networkId = dataState.networkId, @@ -210,15 +209,14 @@ internal class SwapViewModel @Inject constructor( ) } - private fun fillDataState(permissionState: PermissionDataState, swapDataModel: SwapDataModel?) { + private fun fillDataState(permissionState: PermissionDataState, swapDataModel: SwapStateData?) { dataState = if (permissionState is PermissionDataState.PermissionReadyForRequest) { dataState.copy( - estimatedGas = permissionState.requestApproveData.estimatedGas, - approveModel = permissionState.requestApproveData.approveModel, + approveDataModel = permissionState.requestApproveData, ) } else { dataState.copy( - swapModel = swapDataModel, + swapDataModel = swapDataModel, ) } } @@ -230,7 +228,7 @@ internal class SwapViewModel @Inject constructor( runCatching(dispatchers.io) { swapInteractor.onSwap( networkId = dataState.networkId, - swapData = requireNotNull(dataState.swapModel), + swapStateData = requireNotNull(dataState.swapDataModel), currencyToSend = requireNotNull(dataState.fromCurrency), currencyToGet = requireNotNull(dataState.toCurrency), amountToSwap = requireNotNull(dataState.amount), @@ -276,8 +274,7 @@ internal class SwapViewModel @Inject constructor( runCatching(dispatchers.io) { swapInteractor.givePermissionToSwap( networkId = dataState.networkId, - estimatedGas = dataState.estimatedGas!!, - transactionData = dataState.approveModel!!, + approveData = dataState.approveDataModel!!, forTokenContractAddress = (dataState.fromCurrency as? Currency.NonNativeToken)?.contractAddress ?: "", ) 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 3c40b3a4c0..f167603b88 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,7 +1,7 @@ package com.tangem.lib.crypto import com.tangem.lib.crypto.models.Currency -import com.tangem.lib.crypto.models.ProxyAmount +import com.tangem.lib.crypto.models.ProxyFee import com.tangem.lib.crypto.models.ProxyNetworkInfo import com.tangem.lib.crypto.models.transactions.SendTxResult import java.math.BigDecimal @@ -12,7 +12,7 @@ interface TransactionManager { suspend fun sendApproveTransaction( networkId: String, feeAmount: BigDecimal, - estimatedGas: Int, + gasLimit: Int, destinationAddress: String, dataToSign: String, ): SendTxResult @@ -23,7 +23,7 @@ interface TransactionManager { networkId: String, amountToSend: BigDecimal, feeAmount: BigDecimal, - estimatedGas: Int, + gasLimit: Int, destinationAddress: String, dataToSign: String, isSwap: Boolean, @@ -36,7 +36,8 @@ interface TransactionManager { amountToSend: BigDecimal, currencyToSend: Currency, destinationAddress: String, - ): ProxyAmount + data: String?, + ): ProxyFee @Throws(IllegalStateException::class) fun getNativeTokenDecimals(networkId: String): Int diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFee.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFee.kt new file mode 100644 index 0000000000..56b92d5688 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFee.kt @@ -0,0 +1,8 @@ +package com.tangem.lib.crypto.models + +import java.math.BigInteger + +data class ProxyFee( + val gasLimit: BigInteger, + val fee: ProxyAmount, +) \ No newline at end of file