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) @Throws(IllegalStateException::class)
override suspend fun getFee( override suspend fun getFee(
networkId: String, networkId: String,
amountToSend: BigDecimal, amountToSend: Amount,
currencyToSend: Currency, currencyToSend: Currency,
destinationAddress: String, destinationAddress: String,
increaseBy: Int?, increaseBy: Int?,
@ -174,7 +174,7 @@ class TransactionManagerImpl(
if (walletManager is EthereumOptimisticRollupWalletManager) { if (walletManager is EthereumOptimisticRollupWalletManager) {
return getFeeForOptimismBlockchain( return getFeeForOptimismBlockchain(
walletManager = walletManager, walletManager = walletManager,
amount = createAmount(amountToSend, currencyToSend, blockchain), amount = amountToSend,
destinationAddress = destinationAddress, destinationAddress = destinationAddress,
data = data, data = data,
) )
@ -183,7 +183,6 @@ class TransactionManagerImpl(
walletManager = walletManager, walletManager = walletManager,
blockchain = blockchain, blockchain = blockchain,
amountToSend = amountToSend, amountToSend = amountToSend,
currency = currencyToSend,
destinationAddress = destinationAddress, destinationAddress = destinationAddress,
data = data, data = data,
increaseBy = increaseBy, increaseBy = increaseBy,
@ -192,8 +191,6 @@ class TransactionManagerImpl(
return getFeeForBlockchain( return getFeeForBlockchain(
walletManager = walletManager, walletManager = walletManager,
amountToSend = amountToSend, amountToSend = amountToSend,
currency = currencyToSend,
blockchain = blockchain,
destinationAddress = destinationAddress, destinationAddress = destinationAddress,
) )
} }
@ -209,13 +206,11 @@ class TransactionManagerImpl(
private suspend fun getFeeForBlockchain( private suspend fun getFeeForBlockchain(
walletManager: WalletManager, walletManager: WalletManager,
amountToSend: BigDecimal, amountToSend: Amount,
currency: Currency,
blockchain: Blockchain,
destinationAddress: String, destinationAddress: String,
): ProxyFees { ): ProxyFees {
val fee = (walletManager as? TransactionSender)?.getFee( val fee = (walletManager as? TransactionSender)?.getFee(
amount = createAmount(amountToSend, currency, blockchain), amount = amountToSend,
destination = destinationAddress, destination = destinationAddress,
) ?: error("Cannot cast to TransactionSender") ) ?: error("Cannot cast to TransactionSender")
return when (fee) { return when (fee) {
@ -268,17 +263,14 @@ class TransactionManagerImpl(
private suspend fun getFeeForEthereumBlockchain( private suspend fun getFeeForEthereumBlockchain(
walletManager: EthereumWalletManager, walletManager: EthereumWalletManager,
blockchain: Blockchain, blockchain: Blockchain,
amountToSend: BigDecimal, amountToSend: Amount,
currency: Currency,
destinationAddress: String, destinationAddress: String,
data: String?, data: String?,
increaseBy: Int?, increaseBy: Int?,
): ProxyFees { ): ProxyFees {
val gasLimit = getGasLimit( val gasLimit = getGasLimit(
evmWalletManager = walletManager, evmWalletManager = walletManager,
blockchain = blockchain,
amount = amountToSend, amount = amountToSend,
currency = currency,
destinationAddress = destinationAddress, destinationAddress = destinationAddress,
data = data, data = data,
).increaseBigIntegerByPercents(increaseBy) ).increaseBigIntegerByPercents(increaseBy)
@ -332,23 +324,20 @@ class TransactionManagerImpl(
} }
} }
@Suppress("LongParameterList")
private suspend fun getGasLimit( private suspend fun getGasLimit(
evmWalletManager: EthereumWalletManager, evmWalletManager: EthereumWalletManager,
blockchain: Blockchain, amount: Amount,
amount: BigDecimal,
currency: Currency,
destinationAddress: String, destinationAddress: String,
data: String?, data: String?,
): BigInteger { ): BigInteger {
val result = if (data.isNullOrEmpty()) { val result = if (data.isNullOrEmpty()) {
evmWalletManager.getGasLimit( evmWalletManager.getGasLimit(
amount = createAmount(amount, currency, blockchain), amount = amount,
destination = destinationAddress, destination = destinationAddress,
) )
} else { } else {
evmWalletManager.getGasLimit( evmWalletManager.getGasLimit(
amount = createAmount(amount, currency, blockchain), amount = amount,
destination = destinationAddress, destination = destinationAddress,
data = data, 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 { private fun handleSendResult(result: Result<TransactionSendResult>): SendTxResult {
when (result) { when (result) {
is Result.Success -> { is Result.Success -> {

View file

@ -1,7 +1,6 @@
package com.tangem.datasource.api.express.models.response package com.tangem.datasource.api.express.models.response
import com.squareup.moshi.Json import com.squareup.moshi.Json
import java.math.BigDecimal
data class ExchangeDataResponseWithTxDetails( data class ExchangeDataResponseWithTxDetails(
val dataResponse: ExchangeDataResponse, val dataResponse: ExchangeDataResponse,
@ -50,7 +49,10 @@ data class TxDetails(
val txData: String?, // transaction data if DEX, null if CEX val txData: String?, // transaction data if DEX, null if CEX
@Json(name = "txValue") @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") @Json(name = "externalTxId")
val externalTxId: String?, // null if DEX, provider transaction id if CEX val externalTxId: String?, // null if DEX, provider transaction id if CEX
@ -63,6 +65,9 @@ data class TxDetails(
@Json(name = "txExtraId") @Json(name = "txExtraId")
val txExtraId: String?, val txExtraId: String?,
@Json(name = "gas")
val gas: String?,
) )
enum class TxType { enum class TxType {

View file

@ -568,7 +568,7 @@
<string name="swapping_permission_header">Дать разрешение</string> <string name="swapping_permission_header">Дать разрешение</string>
<string name="swapping_permission_policy_type_footer">Укажите лимит доступа к выбранному токену</string> <string name="swapping_permission_policy_type_footer">Укажите лимит доступа к выбранному токену</string>
<string name="swapping_permission_rows_amount">Количество %s</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_permission_unlimited">Безлимитно</string>
<string name="swapping_success_view_title">В процессе</string> <string name="swapping_success_view_title">В процессе</string>
<string name="swapping_swap_action">Обменять</string> <string name="swapping_swap_action">Обменять</string>

View file

@ -283,7 +283,7 @@
<string name="swapping_permission_buttons_approve">允許</string> <string name="swapping_permission_buttons_approve">允許</string>
<string name="swapping_permission_header">賦予權限</string> <string name="swapping_permission_header">賦予權限</string>
<string name="swapping_permission_rows_amount">數量 %s</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_success_view_title">進行中</string>
<string name="swapping_swap_action">交易</string> <string name="swapping_swap_action">交易</string>
<string name="swapping_token_list_title">選擇代幣</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_header">Give Permission</string>
<string name="swapping_permission_policy_type_footer">Specify the approve limit for the selected token</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_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_permission_unlimited">Unlimited</string>
<string name="swapping_success_view_title">In progress</string> <string name="swapping_success_view_title">In progress</string>
<string name="swapping_swap_action">Swap</string> <string name="swapping_swap_action">Swap</string>

View file

@ -99,7 +99,7 @@ class CryptoCurrencyFactory {
decimals = cryptoCurrency.decimals, decimals = cryptoCurrency.decimals,
id = cryptoCurrency.id.rawCurrencyId, 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) val id = getTokenId(network, sdkToken)
return CryptoCurrency.Token( return CryptoCurrency.Token(
id = id, id = id,

View file

@ -19,7 +19,6 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.math.BigDecimal
internal class DefaultTransactionRepository( internal class DefaultTransactionRepository(
private val walletManagersFacade: WalletManagersFacade, private val walletManagersFacade: WalletManagersFacade,
@ -34,7 +33,6 @@ internal class DefaultTransactionRepository(
destination: String, destination: String,
userWalletId: UserWalletId, userWalletId: UserWalletId,
network: Network, network: Network,
isSwap: Boolean,
txExtras: TransactionExtras?, txExtras: TransactionExtras?,
hash: String?, hash: String?,
): TransactionData? = withContext(coroutineDispatcherProvider.io) { ): TransactionData? = withContext(coroutineDispatcherProvider.io) {
@ -51,7 +49,6 @@ internal class DefaultTransactionRepository(
memo = memo, memo = memo,
destination = destination, destination = destination,
network = network, network = network,
isSwap = isSwap,
txExtras = txExtras, txExtras = txExtras,
hash = hash, hash = hash,
) )
@ -84,7 +81,6 @@ internal class DefaultTransactionRepository(
memo = memo, memo = memo,
destination = destination, destination = destination,
network = network, network = network,
isSwap = isSwap,
txExtras = txExtras, txExtras = txExtras,
hash = hash, hash = hash,
) )
@ -118,23 +114,15 @@ internal class DefaultTransactionRepository(
memo: String?, memo: String?,
destination: String, destination: String,
network: Network, network: Network,
isSwap: Boolean,
txExtras: TransactionExtras?, txExtras: TransactionExtras?,
hash: String?, hash: String?,
): TransactionData { ): 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) { if (txExtras != null && memo != null) {
// throw error for now to avoid programmers errors when use extras // throw error for now to avoid programmers errors when use extras
error("Both txExtras and memo provided, use only one of them") error("Both txExtras and memo provided, use only one of them")
} }
val extras = txExtras ?: getMemoExtras(network.id.value, memo) val extras = txExtras ?: getMemoExtras(network.id.value, memo)
return createTransaction(txAmount, fee, destination).copy( return createTransaction(amount, fee, destination).copy(
hash = hash, hash = hash,
extras = extras, extras = extras,
) )
@ -163,19 +151,4 @@ internal class DefaultTransactionRepository(
else -> null 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, destination: String,
userWalletId: UserWalletId, userWalletId: UserWalletId,
network: Network, network: Network,
isSwap: Boolean,
txExtras: TransactionExtras?, txExtras: TransactionExtras?,
hash: String?, hash: String?,
): TransactionData? ): TransactionData?

View file

@ -24,7 +24,6 @@ class CreateTransactionUseCase(
userWalletId: UserWalletId, userWalletId: UserWalletId,
network: Network, network: Network,
txExtras: TransactionExtras? = null, txExtras: TransactionExtras? = null,
isSwap: Boolean = false,
hash: String? = null, hash: String? = null,
) = Either.catch { ) = Either.catch {
requireNotNull( requireNotNull(
@ -35,7 +34,6 @@ class CreateTransactionUseCase(
destination = destination, destination = destination,
userWalletId = userWalletId, userWalletId = userWalletId,
network = network, network = network,
isSwap = isSwap,
txExtras = txExtras, txExtras = txExtras,
hash = hash, 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.ExpressTransactionModel
import com.tangem.feature.swap.domain.models.domain.SwapDataModel import com.tangem.feature.swap.domain.models.domain.SwapDataModel
import com.tangem.utils.converter.Converter import com.tangem.utils.converter.Converter
import java.math.BigDecimal
internal class ExpressDataConverter : Converter<ExchangeDataResponseWithTxDetails, SwapDataModel> { internal class ExpressDataConverter : Converter<ExchangeDataResponseWithTxDetails, SwapDataModel> {
@ -24,19 +25,30 @@ internal class ExpressDataConverter : Converter<ExchangeDataResponseWithTxDetail
dataResponse: ExchangeDataResponse, dataResponse: ExchangeDataResponse,
): ExpressTransactionModel { ): ExpressTransactionModel {
return if (transactionDto.txType == TxType.SWAP) { 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( ExpressTransactionModel.DEX(
fromAmount = createFromAmountWithOffset(dataResponse.fromAmount, dataResponse.fromDecimals), fromAmount = createFromAmountWithOffset(dataResponse.fromAmount, dataResponse.fromDecimals),
toAmount = createFromAmountWithOffset(dataResponse.toAmount, dataResponse.toDecimals), toAmount = createFromAmountWithOffset(dataResponse.toAmount, dataResponse.toDecimals),
txValue = transactionDto.txValue,
txId = dataResponse.txId, txId = dataResponse.txId,
txTo = transactionDto.txTo, txTo = transactionDto.txTo,
txFrom = requireNotNull(transactionDto.txFrom), txFrom = requireNotNull(transactionDto.txFrom),
txData = requireNotNull(transactionDto.txData), txData = requireNotNull(transactionDto.txData),
txExtraId = transactionDto.txExtraId, txExtraId = transactionDto.txExtraId,
otherNativeFeeWei = otherNativeFeeWei,
gas = transactionDto.gas?.toBigIntegerOrNull() ?: error("gas is empty"),
) )
} else { } else {
ExpressTransactionModel.CEX( ExpressTransactionModel.CEX(
fromAmount = createFromAmountWithOffset(dataResponse.fromAmount, dataResponse.fromDecimals), fromAmount = createFromAmountWithOffset(dataResponse.fromAmount, dataResponse.fromDecimals),
toAmount = createFromAmountWithOffset(dataResponse.toAmount, dataResponse.toDecimals), toAmount = createFromAmountWithOffset(dataResponse.toAmount, dataResponse.toDecimals),
txValue = transactionDto.txValue,
txId = dataResponse.txId, txId = dataResponse.txId,
txTo = transactionDto.txTo, txTo = transactionDto.txTo,
externalTxId = requireNotNull(transactionDto.externalTxId), externalTxId = requireNotNull(transactionDto.externalTxId),

View file

@ -1,28 +1,38 @@
package com.tangem.feature.swap.domain.models.domain package com.tangem.feature.swap.domain.models.domain
import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.SwapAmount
import java.math.BigDecimal
import java.math.BigInteger
sealed class ExpressTransactionModel { sealed class ExpressTransactionModel {
abstract val fromAmount: SwapAmount abstract val fromAmount: SwapAmount
abstract val toAmount: SwapAmount abstract val toAmount: SwapAmount
abstract val txValue: String
abstract val txId: String abstract val txId: String
abstract val txTo: String abstract val txTo: String
abstract val txExtraId: 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( data class DEX(
override val fromAmount: SwapAmount, override val fromAmount: SwapAmount,
override val toAmount: SwapAmount, override val toAmount: SwapAmount,
override val txValue: String,
override val txId: String, override val txId: String,
override val txTo: String, override val txTo: String,
override val txExtraId: String?, override val txExtraId: String?,
val txFrom: String, val txFrom: String,
val txData: String, val txData: String,
val otherNativeFeeWei: BigDecimal?,
val gas: BigInteger,
) : ExpressTransactionModel() ) : ExpressTransactionModel()
data class CEX( data class CEX(
override val fromAmount: SwapAmount, override val fromAmount: SwapAmount,
override val toAmount: SwapAmount, override val toAmount: SwapAmount,
override val txValue: String,
override val txId: String, override val txId: String,
override val txTo: String, override val txTo: String,
override val txExtraId: String?, override val txExtraId: String?,

View file

@ -8,6 +8,10 @@ import java.math.BigDecimal
sealed interface SwapState { 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( data class QuotesLoadedState(
val fromTokenInfo: TokenSwapInfo, val fromTokenInfo: TokenSwapInfo,
val toTokenInfo: TokenSwapInfo, val toTokenInfo: TokenSwapInfo,
@ -22,7 +26,9 @@ sealed interface SwapState {
val permissionState: PermissionDataState = PermissionDataState.Empty, val permissionState: PermissionDataState = PermissionDataState.Empty,
val swapDataModel: SwapDataModel? = null, val swapDataModel: SwapDataModel? = null,
val txFee: TxFeeState, val txFee: TxFeeState,
// val txFeeIncludeOtherNativeFee: TxFeeState,
val warnings: List<Warning> = emptyList(), val warnings: List<Warning> = emptyList(),
val swapProvider: SwapProvider,
) : SwapState ) : SwapState
data class EmptyAmountState(val zeroAmountEquivalent: String) : SwapState data class EmptyAmountState(val zeroAmountEquivalent: String) : SwapState
@ -102,6 +108,9 @@ data class TxFee(
val gasLimit: Int, val gasLimit: Int,
val feeFiatFormatted: String, val feeFiatFormatted: String,
val feeCryptoFormatted: String, val feeCryptoFormatted: String,
val feeIncludeOtherNativeFee: BigDecimal,
val feeFiatFormattedWithNative: String,
val feeCryptoFormattedWithNative: String,
val decimals: Int, val decimals: Int,
val cryptoSymbol: String, val cryptoSymbol: String,
val feeType: FeeType, val feeType: FeeType,

View file

@ -4,8 +4,11 @@ import arrow.core.Either
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.common.* 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.Fee
import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.minimalAmount import com.tangem.blockchainsdk.utils.minimalAmount
import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.hexToBytes
import com.tangem.core.ui.utils.BigDecimalFormatter 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.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.FeePaidCurrency 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.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.QuotesRepository
@ -342,7 +343,7 @@ internal class SwapInteractorImpl @Inject constructor(
) )
} else { } else {
provider to getQuotesState( provider to getQuotesState(
exchangeProviderType = provider.type, provider = provider,
quoteDataModel = quotes, quoteDataModel = quotes,
amount = amount, amount = amount,
fromToken = fromToken, fromToken = fromToken,
@ -366,7 +367,6 @@ internal class SwapInteractorImpl @Inject constructor(
isBalanceWithoutFeeEnough: Boolean, isBalanceWithoutFeeEnough: Boolean,
): Pair<SwapProvider, SwapState> { ): Pair<SwapProvider, SwapState> {
return provider to loadCexQuoteData( return provider to loadCexQuoteData(
exchangeProviderType = ExchangeProviderType.CEX,
networkId = networkId, networkId = networkId,
amount = amount, amount = amount,
fromTokenStatus = fromToken, fromTokenStatus = fromToken,
@ -595,8 +595,8 @@ internal class SwapInteractorImpl @Inject constructor(
swapData = requireNotNull(swapData), swapData = requireNotNull(swapData),
currencyToSendStatus = currencyToSend, currencyToSendStatus = currencyToSend,
currencyToGetStatus = currencyToGet, currencyToGetStatus = currencyToGet,
amountToSwap = amountToSwap,
fee = fee, fee = fee,
amountToSwap = amountToSwap,
userWalletId = requireNotNull(getSelectedWallet()).walletId, userWalletId = requireNotNull(getSelectedWallet()).walletId,
) )
} }
@ -622,8 +622,8 @@ internal class SwapInteractorImpl @Inject constructor(
) )
val fee = when (val txFee = state.txFee) { val fee = when (val txFee = state.txFee) {
TxFeeState.Empty -> BigDecimal.ZERO TxFeeState.Empty -> BigDecimal.ZERO
is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeIncludeOtherNativeFee
is TxFeeState.SingleFeeState -> txFee.fee.feeValue is TxFeeState.SingleFeeState -> txFee.fee.feeIncludeOtherNativeFee
} }
val feeState = getFeeState( val feeState = getFeeState(
fee = fee, fee = fee,
@ -656,8 +656,9 @@ internal class SwapInteractorImpl @Inject constructor(
val derivationPath = currencyToSendStatus.currency.network.derivationPath.value val derivationPath = currencyToSendStatus.currency.network.derivationPath.value
val dexTransaction = swapData.transaction as ExpressTransactionModel.DEX val dexTransaction = swapData.transaction as ExpressTransactionModel.DEX
val dataToSign = dexTransaction.txData val dataToSign = dexTransaction.txData
val amountToSend = createNativeAmountForDex(swapData.transaction.txValue, currencyToSendStatus.currency.network)
val txData = createTransactionUseCase( val txData = createTransactionUseCase(
amount = amount.value.convertToAmount(currencyToSendStatus.currency), amount = amountToSend,
fee = getFeeForTransaction( fee = getFeeForTransaction(
fee = fee, fee = fee,
blockchain = Blockchain.fromId(currencyToSendStatus.currency.network.id.value), blockchain = Blockchain.fromId(currencyToSendStatus.currency.network.id.value),
@ -668,7 +669,6 @@ internal class SwapInteractorImpl @Inject constructor(
network = currencyToSendStatus.currency.network, network = currencyToSendStatus.currency.network,
txExtras = createDexTxExtras(fee.gasLimit, dataToSign), txExtras = createDexTxExtras(fee.gasLimit, dataToSign),
hash = dataToSign, hash = dataToSign,
isSwap = true,
).getOrElse { ).getOrElse {
Timber.e(it) Timber.e(it)
return SwapTransactionState.UnknownError return SwapTransactionState.UnknownError
@ -984,7 +984,6 @@ internal class SwapInteractorImpl @Inject constructor(
*/ */
@Suppress("LongParameterList") @Suppress("LongParameterList")
private suspend fun loadCexQuoteData( private suspend fun loadCexQuoteData(
exchangeProviderType: ExchangeProviderType,
networkId: String, networkId: String,
amount: SwapAmount, amount: SwapAmount,
fromTokenStatus: CryptoCurrencyStatus, fromTokenStatus: CryptoCurrencyStatus,
@ -1035,7 +1034,7 @@ internal class SwapInteractorImpl @Inject constructor(
) )
getQuotesState( getQuotesState(
exchangeProviderType = exchangeProviderType, provider = provider,
quoteDataModel = quotes, quoteDataModel = quotes,
amount = amount, amount = amount,
fromToken = fromTokenStatus, fromToken = fromTokenStatus,
@ -1052,7 +1051,7 @@ internal class SwapInteractorImpl @Inject constructor(
@Suppress("LongMethod") @Suppress("LongMethod")
private suspend fun getQuotesState( private suspend fun getQuotesState(
exchangeProviderType: ExchangeProviderType, provider: SwapProvider,
quoteDataModel: Either<DataError, QuoteModel>, quoteDataModel: Either<DataError, QuoteModel>,
amount: SwapAmount, amount: SwapAmount,
fromToken: CryptoCurrencyStatus, fromToken: CryptoCurrencyStatus,
@ -1074,6 +1073,7 @@ internal class SwapInteractorImpl @Inject constructor(
toTokenAmount = quoteModel.toTokenAmount, toTokenAmount = quoteModel.toTokenAmount,
swapData = null, swapData = null,
txFeeState = txFee, txFeeState = txFee,
provider = provider,
).copy( ).copy(
warnings = manageWarnings( warnings = manageWarnings(
fromTokenStatus = fromToken, fromTokenStatus = fromToken,
@ -1083,7 +1083,7 @@ internal class SwapInteractorImpl @Inject constructor(
), ),
) )
when (exchangeProviderType) { when (provider.type) {
ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> { ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> {
val state = updatePermissionState( val state = updatePermissionState(
networkId = networkId, networkId = networkId,
@ -1147,8 +1147,8 @@ internal class SwapInteractorImpl @Inject constructor(
): IncludeFeeInAmount { ): IncludeFeeInAmount {
val feeValue = when (txFee) { val feeValue = when (txFee) {
TxFeeState.Empty -> BigDecimal.ZERO TxFeeState.Empty -> BigDecimal.ZERO
is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeIncludeOtherNativeFee
is TxFeeState.SingleFeeState -> txFee.fee.feeValue is TxFeeState.SingleFeeState -> txFee.fee.feeIncludeOtherNativeFee
} }
val feePaidCurrency = getFeePaidCurrency( val feePaidCurrency = getFeePaidCurrency(
userWalletId = requireNotNull(getSelectedWallet()).walletId, userWalletId = requireNotNull(getSelectedWallet()).walletId,
@ -1254,29 +1254,35 @@ internal class SwapInteractorImpl @Inject constructor(
providerId = provider.providerId, providerId = provider.providerId,
rateType = RateType.FLOAT, rateType = RateType.FLOAT,
toAddress = toToken.value.networkAddress?.defaultAddress?.value.orEmpty(), toAddress = toToken.value.networkAddress?.defaultAddress?.value.orEmpty(),
refundAddress = fromToken.value.networkAddress?.defaultAddress?.value,
).fold( ).fold(
ifRight = { swapData -> ifRight = { swapData ->
val feeData = transactionManager.getFee( val transaction = swapData.transaction as ExpressTransactionModel.DEX
networkId = networkId, val nativeCoinDecimals = Blockchain.fromNetworkId(networkId)?.decimals()
amountToSend = amount.value, ?: error("Blockchain not found")
currencyToSend = swapCurrencyConverter.convert(fromToken.currency), val otherNativeFee = transaction.otherNativeFeeWei
destinationAddress = swapData.transaction.txTo, ?.movePointLeft(nativeCoinDecimals)
increaseBy = INCREASE_GAS_LIMIT_BY, ?: BigDecimal.ZERO
data = (swapData.transaction as ExpressTransactionModel.DEX).txData, val txFeeState = when (val feeData = getFeeDataForDexSwap(networkId, transaction, fromToken.currency)) {
derivationPath = fromToken.currency.network.derivationPath.value, is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken.currency, otherNativeFee)
) is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken.currency, otherNativeFee)
val txFeeState = when (feeData) {
is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken.currency)
is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken.currency)
} }
val feeByPriority = selectFeeByType(feeType = selectedFee, txFeeState = txFeeState) 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( val feeState = getFeeState(
fee = feeByPriority, fee = feeByPriority,
spendAmount = amount, spendAmount = amount,
networkId = networkId, networkId = networkId,
fromTokenStatus = fromToken, fromTokenStatus = fromToken,
) )
val preparedSwapConfigState = PreparedSwapConfigState(
isAllowedToSpend = true,
isBalanceEnough = isBalanceIncludeFeeEnough,
feeState = feeState,
hasOutgoingTransaction = hasOutgoingTransaction(fromToken),
includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex
)
val swapState = updateBalances( val swapState = updateBalances(
networkId = networkId, networkId = networkId,
fromTokenStatus = fromToken, fromTokenStatus = fromToken,
@ -1285,6 +1291,7 @@ internal class SwapInteractorImpl @Inject constructor(
toTokenAmount = swapData.toTokenAmount, toTokenAmount = swapData.toTokenAmount,
swapData = swapData, swapData = swapData,
txFeeState = txFeeState, txFeeState = txFeeState,
provider = provider,
) )
swapState.copy( swapState.copy(
permissionState = PermissionDataState.Empty, permissionState = PermissionDataState.Empty,
@ -1292,17 +1299,9 @@ internal class SwapInteractorImpl @Inject constructor(
fromTokenStatus = fromToken, fromTokenStatus = fromToken,
amount = amount, amount = amount,
feeState = txFeeState, feeState = txFeeState,
minAdaValue = (feeData as? ProxyFees.SingleFee)?.let { minAdaValue = null, // no ADA in DEX
(it.singleFee as? ProxyFee.CardanoToken)?.minAdaValue
},
),
preparedSwapConfigState = PreparedSwapConfigState(
isAllowedToSpend = true,
isBalanceEnough = isBalanceIncludeFeeEnough,
feeState = feeState,
hasOutgoingTransaction = hasOutgoingTransaction(fromToken),
includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex
), ),
preparedSwapConfigState = preparedSwapConfigState,
) )
}, },
ifLeft = { error -> 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") @Suppress("LongParameterList")
private suspend fun updateBalances( private suspend fun updateBalances(
provider: SwapProvider,
networkId: String, networkId: String,
fromTokenStatus: CryptoCurrencyStatus, fromTokenStatus: CryptoCurrencyStatus,
toTokenStatus: CryptoCurrencyStatus, toTokenStatus: CryptoCurrencyStatus,
@ -1335,7 +1368,6 @@ internal class SwapInteractorImpl @Inject constructor(
val fromToken = fromTokenStatus.currency val fromToken = fromTokenStatus.currency
val toToken = toTokenStatus.currency val toToken = toTokenStatus.currency
val nativeToken = repository.getNativeTokenForNetwork(networkId) val nativeToken = repository.getNativeTokenForNetwork(networkId)
val rates = getQuotes(fromToken.id, toToken.id, nativeToken.id) val rates = getQuotes(fromToken.id, toToken.id, nativeToken.id)
return SwapState.QuotesLoadedState( return SwapState.QuotesLoadedState(
fromTokenInfo = TokenSwapInfo( fromTokenInfo = TokenSwapInfo(
@ -1358,6 +1390,7 @@ internal class SwapInteractorImpl @Inject constructor(
), ),
swapDataModel = swapData, swapDataModel = swapData,
txFee = txFeeState, txFee = txFeeState,
swapProvider = provider,
) )
} }
@ -1367,7 +1400,7 @@ internal class SwapInteractorImpl @Inject constructor(
): TxFeeState { ): TxFeeState {
return txFeeResult?.fold( return txFeeResult?.fold(
ifLeft = { TxFeeState.Empty }, ifLeft = { TxFeeState.Empty },
ifRight = { txFee -> txFee.toTxFeeState(fromToken.currency) }, ifRight = { txFee -> txFee.toTxFeeState(fromToken.currency, null) },
) ?: TxFeeState.Empty ) ?: TxFeeState.Empty
} }
@ -1427,7 +1460,7 @@ internal class SwapInteractorImpl @Inject constructor(
try { try {
transactionManager.getFee( transactionManager.getFee(
networkId = networkId, networkId = networkId,
amountToSend = BigDecimal.ZERO, amountToSend = createNativeAmountForDex("0", fromToken.network),
currencyToSend = swapCurrencyConverter.convert(repository.getNativeTokenForNetwork(networkId)), currencyToSend = swapCurrencyConverter.convert(repository.getNativeTokenForNetwork(networkId)),
destinationAddress = fromToken.getContractAddress(), destinationAddress = fromToken.getContractAddress(),
increaseBy = INCREASE_GAS_LIMIT_BY, 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 normalFeeValue = this.minFee.fee.value // in swap for normal use min fee
val normalFeeGas = this.minFee.gasLimit.toInt() val normalFeeGas = this.minFee.gasLimit.toInt()
val priorityFeeValue = this.normalFee.fee.value // in swap for priority use normal fee val priorityFeeValue = this.normalFee.fee.value // in swap for priority use normal fee
val priorityFeeGas = this.normalFee.gasLimit.toInt() val priorityFeeGas = this.normalFee.gasLimit.toInt()
// region fees to use
val feesFiat = getFormattedFiatFees(fromToken, normalFeeValue, priorityFeeValue) val feesFiat = getFormattedFiatFees(fromToken, normalFeeValue, priorityFeeValue)
val normalFiatFee = requireNotNull(feesFiat.getOrNull(0)) { "feesFiat item 0 couldn't be null" } 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" } val priorityFiatFee = requireNotNull(feesFiat.getOrNull(1)) { "feesFiat item 1 couldn't be null" }
@ -1491,12 +1529,35 @@ internal class SwapInteractorImpl @Inject constructor(
amount = priorityFeeValue, amount = priorityFeeValue,
decimals = normalFee.fee.decimals, 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( return TxFeeState.MultipleFeeState(
normalFee = TxFee( normalFee = TxFee(
feeValue = normalFeeValue, feeValue = normalFeeValue,
gasLimit = normalFeeGas, gasLimit = normalFeeGas,
feeFiatFormatted = normalFiatFee, feeFiatFormatted = normalFiatFee,
feeCryptoFormatted = normalCryptoFee, feeCryptoFormatted = normalCryptoFee,
feeIncludeOtherNativeFee = normalFeeValue + otherNativeFeeValue,
feeFiatFormattedWithNative = normalFiatFeeWithNative,
feeCryptoFormattedWithNative = normalCryptoFeeWithNative,
decimals = minFee.fee.decimals, decimals = minFee.fee.decimals,
cryptoSymbol = minFee.fee.currencySymbol, cryptoSymbol = minFee.fee.currencySymbol,
feeType = FeeType.NORMAL, feeType = FeeType.NORMAL,
@ -1506,6 +1567,9 @@ internal class SwapInteractorImpl @Inject constructor(
gasLimit = priorityFeeGas, gasLimit = priorityFeeGas,
feeFiatFormatted = priorityFiatFee, feeFiatFormatted = priorityFiatFee,
feeCryptoFormatted = priorityCryptoFee, feeCryptoFormatted = priorityCryptoFee,
feeIncludeOtherNativeFee = priorityFeeValue + otherNativeFeeValue,
feeFiatFormattedWithNative = priorityFiatFeeWithNative,
feeCryptoFormattedWithNative = priorityCryptoFeeWithNative,
decimals = normalFee.fee.decimals, decimals = normalFee.fee.decimals,
cryptoSymbol = normalFee.fee.currencySymbol, cryptoSymbol = normalFee.fee.currencySymbol,
feeType = FeeType.PRIORITY, 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 normalFeeValue = this.singleFee.fee.value
val normalFeeGas = this.singleFee.gasLimit.toInt() val normalFeeGas = this.singleFee.gasLimit.toInt()
val feesFiat = getFormattedFiatFees(fromToken, normalFeeValue) val feesFiat = getFormattedFiatFees(fromToken, normalFeeValue)
@ -1522,12 +1590,28 @@ internal class SwapInteractorImpl @Inject constructor(
amount = normalFeeValue, amount = normalFeeValue,
decimals = singleFee.fee.decimals, 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( return TxFeeState.SingleFeeState(
fee = TxFee( fee = TxFee(
feeValue = normalFeeValue, feeValue = normalFeeValue,
gasLimit = normalFeeGas, gasLimit = normalFeeGas,
feeFiatFormatted = normalFiatFee, feeFiatFormatted = normalFiatFee,
feeCryptoFormatted = normalCryptoFee, feeCryptoFormatted = normalCryptoFee,
feeIncludeOtherNativeFee = normalFeeValue + otherNativeFeeValue,
feeFiatFormattedWithNative = normalFiatFeeWithNative,
feeCryptoFormattedWithNative = normalCryptoFeeWithNative,
decimals = singleFee.fee.decimals, decimals = singleFee.fee.decimals,
cryptoSymbol = singleFee.fee.currencySymbol, cryptoSymbol = singleFee.fee.currencySymbol,
feeType = FeeType.NORMAL, 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) { return when (this) {
is TransactionFee.Choosable -> { is TransactionFee.Choosable -> {
val normalFee = this.normal.increaseGasLimitBy(INCREASE_GAS_LIMIT_FOR_SEND) val normalFee = this.normal.increaseGasLimitBy(INCREASE_GAS_LIMIT_FOR_SEND)
@ -1553,12 +1642,31 @@ internal class SwapInteractorImpl @Inject constructor(
amount = feePriority, amount = feePriority,
decimals = priorityFee.amount.decimals, 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( TxFeeState.MultipleFeeState(
normalFee = TxFee( normalFee = TxFee(
feeValue = feeNormal, feeValue = feeNormal,
gasLimit = normalFee.getGasLimit(), gasLimit = normalFee.getGasLimit(),
feeFiatFormatted = normalFiatValue, feeFiatFormatted = normalFiatValue,
feeCryptoFormatted = normalCryptoFee, feeCryptoFormatted = normalCryptoFee,
feeIncludeOtherNativeFee = normalFeeWithOtherNative,
feeFiatFormattedWithNative = normalFiatValueWithNative,
feeCryptoFormattedWithNative = normalCryptoFeeWithNative,
decimals = normalFee.amount.decimals, decimals = normalFee.amount.decimals,
cryptoSymbol = normalFee.amount.currencySymbol, cryptoSymbol = normalFee.amount.currencySymbol,
feeType = FeeType.NORMAL, feeType = FeeType.NORMAL,
@ -1568,6 +1676,9 @@ internal class SwapInteractorImpl @Inject constructor(
gasLimit = priorityFee.getGasLimit(), gasLimit = priorityFee.getGasLimit(),
feeFiatFormatted = priorityFiatValue, feeFiatFormatted = priorityFiatValue,
feeCryptoFormatted = priorityCryptoFee, feeCryptoFormatted = priorityCryptoFee,
feeIncludeOtherNativeFee = priorityFeeWithOtherNative,
feeFiatFormattedWithNative = priorityFiatValueWithNative,
feeCryptoFormattedWithNative = priorityCryptoFeeWithNative,
decimals = priorityFee.amount.decimals, decimals = priorityFee.amount.decimals,
cryptoSymbol = priorityFee.amount.currencySymbol, cryptoSymbol = priorityFee.amount.currencySymbol,
feeType = FeeType.PRIORITY, feeType = FeeType.PRIORITY,
@ -1581,12 +1692,24 @@ internal class SwapInteractorImpl @Inject constructor(
amount = feeNormal, amount = feeNormal,
decimals = this.normal.amount.decimals, 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( TxFeeState.SingleFeeState(
fee = TxFee( fee = TxFee(
feeValue = this.normal.amount.value ?: BigDecimal.ZERO, feeValue = this.normal.amount.value ?: BigDecimal.ZERO,
gasLimit = this.normal.getGasLimit(), gasLimit = this.normal.getGasLimit(),
feeFiatFormatted = normalFiatValue, feeFiatFormatted = normalFiatValue,
feeCryptoFormatted = normalCryptoFee, feeCryptoFormatted = normalCryptoFee,
feeIncludeOtherNativeFee = normalFeeWithOtherNative,
feeFiatFormattedWithNative = normalFiatValueWithNative,
feeCryptoFormattedWithNative = normalCryptoFeeWithNative,
decimals = normal.amount.decimals, decimals = normal.amount.decimals,
cryptoSymbol = normal.amount.currencySymbol, cryptoSymbol = normal.amount.currencySymbol,
feeType = FeeType.NORMAL, 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 * 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) { if (fromToken.currency is CryptoCurrency.Token) {
tokenBalance >= amount.value tokenBalance >= amount.value
} else { } 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() object Empty : SwapPermissionState()
data class ReadyForRequest( data class ReadyForRequest(
val providerName: String,
val currency: String, val currency: String,
val amount: String, val amount: String,
val walletAddress: String, val walletAddress: String,

View file

@ -1,7 +1,6 @@
package com.tangem.feature.swap.models package com.tangem.feature.swap.models
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.core.ui.R import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
@ -74,18 +73,21 @@ data class SwapButton(
sealed interface TransactionCardType { sealed interface TransactionCardType {
val headerResId: Int val header: TextReference
val isError: Boolean
data class Inputtable( data class Inputtable(
val onAmountChanged: ((String) -> Unit), val onAmountChanged: ((String) -> Unit),
val onFocusChanged: ((Boolean) -> 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 ) : TransactionCardType
data class ReadOnly( data class ReadOnly(
val showWarning: Boolean = false, val showWarning: Boolean = false,
val onWarningClick: (() -> Unit)? = null, 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 ) : TransactionCardType
} }
@ -102,7 +104,7 @@ data class LegalState(
sealed interface SwapWarning { sealed interface SwapWarning {
data class PermissionNeeded(val notificationConfig: NotificationConfig) : SwapWarning data class PermissionNeeded(val notificationConfig: NotificationConfig) : SwapWarning
object InsufficientFunds : SwapWarning data object InsufficientFunds : SwapWarning
data class NoAvailableTokensToSwap(val notificationConfig: NotificationConfig) : SwapWarning data class NoAvailableTokensToSwap(val notificationConfig: NotificationConfig) : SwapWarning
data class GenericWarning( data class GenericWarning(
val title: TextReference? = null, val title: TextReference? = null,

View file

@ -51,7 +51,11 @@ internal class StateBuilder(
return SwapStateHolder( return SwapStateHolder(
blockchainId = networkInfo.blockchainId, blockchainId = networkInfo.blockchainId,
sendCardData = SwapCardState.SwapCardData( sendCardData = SwapCardState.SwapCardData(
type = TransactionCardType.Inputtable(actions.onAmountChanged, actions.onAmountSelected), type = TransactionCardType.Inputtable(
onAmountChanged = actions.onAmountChanged,
onFocusChanged = actions.onAmountSelected,
isError = false,
),
amountEquivalent = null, amountEquivalent = null,
amountTextFieldValue = null, amountTextFieldValue = null,
token = null, token = null,
@ -152,9 +156,13 @@ internal class StateBuilder(
val canSelectReceiveToken = mainTokenId != toToken.id.value val canSelectReceiveToken = mainTokenId != toToken.id.value
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
if (uiStateHolder.receiveCardData !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( return uiStateHolder.copy(
sendCardData = SwapCardState.SwapCardData( sendCardData = SwapCardState.SwapCardData(
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable), type = sendInput,
amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue, amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue,
amountEquivalent = null, amountEquivalent = null,
token = uiStateHolder.sendCardData.token, token = uiStateHolder.sendCardData.token,
@ -221,9 +229,19 @@ internal class StateBuilder(
val feeState = createFeeState(quoteModel.txFee, selectedFeeType) val feeState = createFeeState(quoteModel.txFee, selectedFeeType)
val fromCurrencyStatus = quoteModel.fromTokenInfo.cryptoCurrencyStatus val fromCurrencyStatus = quoteModel.fromTokenInfo.cryptoCurrencyStatus
val toCurrencyStatus = quoteModel.toTokenInfo.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( return uiStateHolder.copy(
sendCardData = SwapCardState.SwapCardData( sendCardData = SwapCardState.SwapCardData(
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable), type = sendInput,
amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue, amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue,
amountEquivalent = getFormattedFiatAmount(quoteModel.fromTokenInfo.amountFiat), amountEquivalent = getFormattedFiatAmount(quoteModel.fromTokenInfo.amountFiat),
token = fromCurrencyStatus, token = fromCurrencyStatus,
@ -257,6 +275,7 @@ internal class StateBuilder(
permissionState = convertPermissionState( permissionState = convertPermissionState(
lastPermissionState = uiStateHolder.permissionState, lastPermissionState = uiStateHolder.permissionState,
permissionDataState = quoteModel.permissionState, permissionDataState = quoteModel.permissionState,
providerName = quoteModel.swapProvider.name,
onGivePermissionClick = actions.onGivePermissionClick, onGivePermissionClick = actions.onGivePermissionClick,
onChangeApproveType = actions.onChangeApproveType, onChangeApproveType = actions.onChangeApproveType,
), ),
@ -320,7 +339,7 @@ internal class StateBuilder(
val warnings = mutableListOf<SwapWarning>() val warnings = mutableListOf<SwapWarning>()
maybeAddDomainWarnings(quoteModel, warnings) maybeAddDomainWarnings(quoteModel, warnings)
maybeAddNeedReserveToCreateAccountWarning(quoteModel, warnings) maybeAddNeedReserveToCreateAccountWarning(quoteModel, warnings)
maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken) maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken, quoteModel.swapProvider.name)
maybeAddNetworkFeeCoverageWarning(quoteModel, warnings, selectedFeeType) maybeAddNetworkFeeCoverageWarning(quoteModel, warnings, selectedFeeType)
maybeAddUnableCoverFeeWarning(quoteModel, fromToken, warnings) maybeAddUnableCoverFeeWarning(quoteModel, fromToken, warnings)
maybeAddInsufficientFundsWarning(quoteModel, warnings) maybeAddInsufficientFundsWarning(quoteModel, warnings)
@ -468,6 +487,7 @@ internal class StateBuilder(
quoteModel: SwapState.QuotesLoadedState, quoteModel: SwapState.QuotesLoadedState,
warnings: MutableList<SwapWarning>, warnings: MutableList<SwapWarning>,
fromToken: CryptoCurrency, fromToken: CryptoCurrency,
providerName: String,
) { ) {
if (!quoteModel.preparedSwapConfigState.isAllowedToSpend && if (!quoteModel.preparedSwapConfigState.isAllowedToSpend &&
quoteModel.preparedSwapConfigState.feeState is SwapFeeState.Enough && quoteModel.preparedSwapConfigState.feeState is SwapFeeState.Enough &&
@ -475,7 +495,7 @@ internal class StateBuilder(
) { ) {
warnings.add( warnings.add(
SwapWarning.PermissionNeeded( SwapWarning.PermissionNeeded(
createPermissionNotificationConfig(fromToken.symbol), createPermissionNotificationConfig(fromToken.symbol, providerName),
), ),
) )
} }
@ -493,8 +513,8 @@ internal class StateBuilder(
warnings.add( warnings.add(
SwapWarning.GeneralWarning( SwapWarning.GeneralWarning(
createNetworkFeeCoverageNotificationConfig( createNetworkFeeCoverageNotificationConfig(
fee.feeCryptoFormatted, fee.feeCryptoFormattedWithNative,
fee.feeFiatFormatted, fee.feeFiatFormattedWithNative,
), ),
), ),
) )
@ -547,13 +567,16 @@ internal class StateBuilder(
warnings: MutableList<SwapWarning>, warnings: MutableList<SwapWarning>,
) { ) {
// check isBalanceEnough, but for dex includeFeeInAmount always Excluded // check isBalanceEnough, but for dex includeFeeInAmount always Excluded
if (!quoteModel.preparedSwapConfigState.isBalanceEnough && if (isInsufficientFundsCondition(quoteModel)) {
quoteModel.preparedSwapConfigState.includeFeeInAmount !is IncludeFeeInAmount.Included
) {
warnings.add(SwapWarning.InsufficientFunds) 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 { private fun getSwapButtonEnabled(quoteModel: SwapState.QuotesLoadedState): Boolean {
val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value
if (status is CryptoCurrencyStatus.NoAccount) { if (status is CryptoCurrencyStatus.NoAccount) {
@ -964,9 +987,9 @@ internal class StateBuilder(
return FeeItemState.Content( return FeeItemState.Content(
feeType = feeType, feeType = feeType,
title = resourceReference(R.string.common_network_fee_title), 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, symbolCrypto = fee.cryptoSymbol,
amountFiatFormatted = fee.feeFiatFormatted, amountFiatFormatted = fee.feeFiatFormattedWithNative, // display fee with native as workaround for okx
isClickable = isClickable, isClickable = isClickable,
onClick = actions.onClickFee, onClick = actions.onClickFee,
) )
@ -1017,7 +1040,7 @@ internal class StateBuilder(
showStatusButton = shouldShowStatus, showStatusButton = shouldShowStatus,
providerIcon = providerState.iconUrl, providerIcon = providerState.iconUrl,
rate = providerState.subtitle, rate = providerState.subtitle,
fee = stringReference("${fee.feeCryptoFormatted} (${fee.feeFiatFormatted})"), fee = stringReference("${fee.feeCryptoFormattedWithNative} (${fee.feeFiatFormattedWithNative})"),
fromTokenAmount = stringReference(swapTransactionState.fromAmount.orEmpty()), fromTokenAmount = stringReference(swapTransactionState.fromAmount.orEmpty()),
toTokenAmount = stringReference(swapTransactionState.toAmount.orEmpty()), toTokenAmount = stringReference(swapTransactionState.toAmount.orEmpty()),
fromTokenFiatAmount = stringReference(fromFiatAmount), fromTokenFiatAmount = stringReference(fromFiatAmount),
@ -1146,6 +1169,7 @@ internal class StateBuilder(
private fun convertPermissionState( private fun convertPermissionState(
lastPermissionState: SwapPermissionState, lastPermissionState: SwapPermissionState,
permissionDataState: PermissionDataState, permissionDataState: PermissionDataState,
providerName: String,
onGivePermissionClick: () -> Unit, onGivePermissionClick: () -> Unit,
onChangeApproveType: (ApproveType) -> Unit, onChangeApproveType: (ApproveType) -> Unit,
): SwapPermissionState { ): SwapPermissionState {
@ -1165,6 +1189,7 @@ internal class StateBuilder(
is TxFeeState.SingleFeeState -> fee.fee is TxFeeState.SingleFeeState -> fee.fee
} }
SwapPermissionState.ReadyForRequest( SwapPermissionState.ReadyForRequest(
providerName = providerName,
currency = permissionDataState.currency, currency = permissionDataState.currency,
amount = permissionDataState.amount, amount = permissionDataState.amount,
approveType = approveType, approveType = approveType,
@ -1358,18 +1383,18 @@ internal class StateBuilder(
FeeItemState.Content( FeeItemState.Content(
feeType = this.normalFee.feeType, feeType = this.normalFee.feeType,
title = resourceReference(R.string.common_network_fee_title), title = resourceReference(R.string.common_network_fee_title),
amountCrypto = this.normalFee.feeCryptoFormatted, amountCrypto = this.normalFee.feeCryptoFormattedWithNative,
symbolCrypto = this.normalFee.cryptoSymbol, symbolCrypto = this.normalFee.cryptoSymbol,
amountFiatFormatted = this.normalFee.feeFiatFormatted, amountFiatFormatted = this.normalFee.feeFiatFormattedWithNative,
isClickable = true, isClickable = true,
onClick = {}, onClick = {},
), ),
FeeItemState.Content( FeeItemState.Content(
feeType = this.priorityFee.feeType, feeType = this.priorityFee.feeType,
title = resourceReference(R.string.common_network_fee_title), title = resourceReference(R.string.common_network_fee_title),
amountCrypto = this.priorityFee.feeCryptoFormatted, amountCrypto = this.priorityFee.feeCryptoFormattedWithNative,
symbolCrypto = this.priorityFee.cryptoSymbol, symbolCrypto = this.priorityFee.cryptoSymbol,
amountFiatFormatted = this.priorityFee.feeFiatFormatted, amountFiatFormatted = this.priorityFee.feeFiatFormattedWithNative,
isClickable = true, isClickable = true,
onClick = {}, onClick = {},
), ),
@ -1402,12 +1427,12 @@ internal class StateBuilder(
} }
// region warnings // region warnings
private fun createPermissionNotificationConfig(fromTokenSymbol: String): NotificationConfig { private fun createPermissionNotificationConfig(fromTokenSymbol: String, providerName: String): NotificationConfig {
return NotificationConfig( return NotificationConfig(
title = resourceReference(R.string.express_provider_permission_needed), title = resourceReference(R.string.express_provider_permission_needed),
subtitle = resourceReference( subtitle = resourceReference(
id = R.string.swapping_permission_subheader, id = R.string.swapping_permission_subheader,
formatArgs = wrappedList(fromTokenSymbol), formatArgs = wrappedList(providerName, fromTokenSymbol),
), ),
iconResId = R.drawable.ic_locked_24, iconResId = R.drawable.ic_locked_24,
) )
@ -1604,7 +1629,7 @@ internal class StateBuilder(
id = this.providerId, id = this.providerId,
name = this.name, name = this.name,
iconUrl = this.imageLarge, iconUrl = this.imageLarge,
type = this.type.toString(), type = this.type.providerName,
selectionType = selectionType, selectionType = selectionType,
alertText = alertText, alertText = alertText,
onProviderClick = onProviderClick, onProviderClick = onProviderClick,

View file

@ -68,6 +68,7 @@ private fun SwapPermissionBottomSheetContent(content: GivePermissionBottomSheetC
Text( Text(
text = stringResource( text = stringResource(
id = R.string.swapping_permission_subheader, id = R.string.swapping_permission_subheader,
data.providerName,
data.currency, data.currency,
), ),
color = TangemTheme.colors.text.secondary, color = TangemTheme.colors.text.secondary,
@ -303,6 +304,7 @@ private fun Preview_AgreementBottomSheet() {
private val previewData = GivePermissionBottomSheetConfig( private val previewData = GivePermissionBottomSheetConfig(
data = SwapPermissionState.ReadyForRequest( data = SwapPermissionState.ReadyForRequest(
providerName = "1icnh",
currency = "DAI", currency = "DAI",
amount = "", amount = "",
walletAddress = "", walletAddress = "",

View file

@ -434,7 +434,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U
// region preview // region preview
private val sendCard = SwapCardState.SwapCardData( private val sendCard = SwapCardState.SwapCardData(
type = TransactionCardType.Inputtable({}, {}), type = TransactionCardType.Inputtable({}, {}, false),
amountTextFieldValue = TextFieldValue(), amountTextFieldValue = TextFieldValue(),
amountEquivalent = "1 000 000", amountEquivalent = "1 000 000",
tokenIconUrl = "", tokenIconUrl = "",

View file

@ -37,6 +37,7 @@ import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.tangem.core.ui.R import com.tangem.core.ui.R
import com.tangem.core.ui.components.* 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.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.ImageBackgroundContrastChecker import com.tangem.core.ui.utils.ImageBackgroundContrastChecker
@ -185,10 +186,14 @@ private fun Header(type: TransactionCardType, balance: String, modifier: Modifie
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
val title = type.headerResId val titleColor = if (type.isError) {
TangemTheme.colors.text.warning
} else {
TangemTheme.colors.text.tertiary
}
Text( Text(
text = stringResource(id = title), text = type.header.resolveReference(),
color = TangemTheme.colors.text.tertiary, color = titleColor,
maxLines = 1, maxLines = 1,
style = MaterialTheme.typography.subtitle2, style = MaterialTheme.typography.subtitle2,
modifier = Modifier modifier = Modifier
@ -546,7 +551,7 @@ private fun Preview_TransactionCardWithoutPriceImpact_InDarkTheme() {
@Composable @Composable
private fun TransactionCardPreview() { private fun TransactionCardPreview() {
TransactionCard( TransactionCard(
type = TransactionCardType.Inputtable({}, {}), type = TransactionCardType.Inputtable({}, {}, false),
amountEquivalent = "1 000 000", amountEquivalent = "1 000 000",
tokenIconUrl = "", tokenIconUrl = "",
tokenCurrency = "DAI", tokenCurrency = "DAI",

View file

@ -1,9 +1,11 @@
package com.tangem.lib.crypto package com.tangem.lib.crypto
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.TransactionExtras import com.tangem.blockchain.common.TransactionExtras
import com.tangem.lib.crypto.models.* import com.tangem.lib.crypto.models.*
import com.tangem.lib.crypto.models.transactions.SendTxResult import com.tangem.lib.crypto.models.transactions.SendTxResult
import java.math.BigDecimal import java.math.BigDecimal
import java.math.BigInteger
interface TransactionManager { interface TransactionManager {
@ -46,7 +48,7 @@ interface TransactionManager {
@Throws(IllegalStateException::class) @Throws(IllegalStateException::class)
suspend fun getFee( suspend fun getFee(
networkId: String, networkId: String,
amountToSend: BigDecimal, amountToSend: Amount,
currencyToSend: Currency, currencyToSend: Currency,
destinationAddress: String, destinationAddress: String,
increaseBy: Int?, increaseBy: Int?,
@ -54,6 +56,9 @@ interface TransactionManager {
derivationPath: String?, derivationPath: String?,
): ProxyFees ): ProxyFees
@Throws(IllegalStateException::class)
suspend fun getFeeForGas(networkId: String, gas: BigInteger, derivationPath: String?): ProxyFees
@Throws(IllegalStateException::class) @Throws(IllegalStateException::class)
suspend fun updateWalletManager(networkId: String, derivationPath: String?) suspend fun updateWalletManager(networkId: String, derivationPath: String?)