Updated on 2026-08-14
This commit is contained in:
parent
6936f04093
commit
c7151d95a7
20 changed files with 335 additions and 1096 deletions
|
|
@ -24,7 +24,7 @@ internal class RuntimeUserWalletsStore(
|
|||
return userWalletsListManager.userWalletsSync.firstOrNull { it.walletId == key }
|
||||
}
|
||||
|
||||
override suspend fun getSyncStrict(key: UserWalletId): UserWallet {
|
||||
override fun getSyncStrict(key: UserWalletId): UserWallet {
|
||||
return requireNotNull(getSyncOrNull(key)) { "Unable to find user wallet with provided ID: $key" }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,12 @@ internal object TransactionDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetEthSpecificFeeUseCase(walletManagersFacade: WalletManagersFacade): GetEthSpecificFeeUseCase {
|
||||
return GetEthSpecificFeeUseCase(walletManagersFacade = walletManagersFacade)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTransferGetFeeUseCase(walletManagersFacade: WalletManagersFacade): GetTransferFeeUseCase {
|
||||
|
|
|
|||
|
|
@ -1,367 +0,0 @@
|
|||
package com.tangem.tap.proxy
|
||||
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.blockchains.optimism.EthereumOptimisticRollupWalletManager
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.smartcontract.SmartContractCallData
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.externallinkprovider.TxExploreState
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.lib.crypto.models.*
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.math.MathContext
|
||||
import java.math.RoundingMode
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class TransactionManagerImpl(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
) : TransactionManager {
|
||||
|
||||
override fun getExplorerTransactionLink(networkId: String, txAddress: String): String {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: error("blockchain not found")
|
||||
return when (val txUrlState = blockchain.getExploreTxUrl(txAddress)) {
|
||||
TxExploreState.Unsupported -> ""
|
||||
is TxExploreState.Url -> txUrlState.url
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateWalletManager(networkId: String, derivationPath: String?) {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
getActualWalletManager(blockchain, derivationPath).update()
|
||||
}
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
override suspend fun getFee(
|
||||
networkId: String,
|
||||
amountToSend: Amount,
|
||||
currencyToSend: Currency,
|
||||
destinationAddress: String,
|
||||
increaseBy: Int?,
|
||||
callData: SmartContractCallData?,
|
||||
derivationPath: String?,
|
||||
): ProxyFees {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
val walletManager = getActualWalletManager(blockchain, derivationPath)
|
||||
if (walletManager is EthereumWalletManager) {
|
||||
if (walletManager is EthereumOptimisticRollupWalletManager) {
|
||||
return getFeeForOptimismBlockchain(
|
||||
walletManager = walletManager,
|
||||
amount = amountToSend,
|
||||
destinationAddress = destinationAddress,
|
||||
callData = callData,
|
||||
)
|
||||
}
|
||||
return getFeeForEthereumBlockchain(
|
||||
walletManager = walletManager,
|
||||
blockchain = blockchain,
|
||||
amountToSend = amountToSend,
|
||||
destinationAddress = destinationAddress,
|
||||
callData = callData,
|
||||
increaseBy = increaseBy,
|
||||
)
|
||||
} else {
|
||||
return getFeeForBlockchain(
|
||||
walletManager = walletManager,
|
||||
amountToSend = amountToSend,
|
||||
destinationAddress = destinationAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBlockchainInfo(networkId: String): ProxyNetworkInfo {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
return ProxyNetworkInfo(
|
||||
name = blockchain.fullName,
|
||||
blockchainId = blockchain.id,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getFeeForBlockchain(
|
||||
walletManager: WalletManager,
|
||||
amountToSend: Amount,
|
||||
destinationAddress: String,
|
||||
): ProxyFees {
|
||||
val fee = (walletManager as? TransactionSender)?.getFee(
|
||||
amount = amountToSend,
|
||||
destination = destinationAddress,
|
||||
) ?: error("Cannot cast to TransactionSender")
|
||||
return when (fee) {
|
||||
is Result.Success -> {
|
||||
// for not EVM blockchains set gasLimit ZERO for now
|
||||
when (fee.data) {
|
||||
is TransactionFee.Single -> {
|
||||
val singleFee = when (val normalFee = (fee.data as TransactionFee.Single).normal) {
|
||||
is Fee.CardanoToken -> {
|
||||
ProxyFee.CardanoToken(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = normalFee.amount),
|
||||
minAdaValue = normalFee.minAdaValue,
|
||||
)
|
||||
}
|
||||
is Fee.Filecoin -> {
|
||||
ProxyFee.Filecoin(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = normalFee.amount),
|
||||
gasPremium = normalFee.gasPremium,
|
||||
)
|
||||
}
|
||||
is Fee.Sui -> {
|
||||
ProxyFee.Sui(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = normalFee.amount),
|
||||
gasPrice = normalFee.gasPrice,
|
||||
gasBudget = normalFee.gasBudget,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
ProxyFee.Common(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = normalFee.amount),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ProxyFees.SingleFee(singleFee = singleFee)
|
||||
}
|
||||
is TransactionFee.Choosable -> {
|
||||
val choosableFee = fee.data as TransactionFee.Choosable
|
||||
ProxyFees.MultipleFees(
|
||||
minFee = ProxyFee.Common(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = choosableFee.minimum.amount),
|
||||
),
|
||||
normalFee = ProxyFee.Common(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = choosableFee.normal.amount),
|
||||
),
|
||||
priorityFee = ProxyFee.Common(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = choosableFee.priority.amount),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
error(fee.error.message ?: fee.error.customMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private suspend fun getFeeForEthereumBlockchain(
|
||||
walletManager: EthereumWalletManager,
|
||||
blockchain: Blockchain,
|
||||
amountToSend: Amount,
|
||||
destinationAddress: String,
|
||||
callData: SmartContractCallData?,
|
||||
increaseBy: Int?,
|
||||
): ProxyFees {
|
||||
val gasLimit = getGasLimit(
|
||||
evmWalletManager = walletManager,
|
||||
amount = amountToSend,
|
||||
destinationAddress = destinationAddress,
|
||||
callData = callData,
|
||||
).increaseBigIntegerByPercents(increaseBy)
|
||||
return when (val gasPrice = walletManager.getGasPrice()) {
|
||||
is Result.Success -> {
|
||||
createMultipleProxyFees(gasPrice = gasPrice.data, gasLimit = gasLimit, blockchain = blockchain)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
error(gasPrice.error.message ?: gasPrice.error.customMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getFeeForOptimismBlockchain(
|
||||
walletManager: EthereumOptimisticRollupWalletManager,
|
||||
amount: Amount,
|
||||
destinationAddress: String,
|
||||
callData: SmartContractCallData?,
|
||||
): ProxyFees {
|
||||
val fee = if (callData == null) {
|
||||
walletManager.getFee(amount, destinationAddress)
|
||||
} else {
|
||||
walletManager.getFee(amount, destinationAddress, callData)
|
||||
}
|
||||
return when (fee) {
|
||||
is Result.Success -> {
|
||||
val choosableFee = fee.data as? TransactionFee.Choosable ?: error("Incorrect fee type")
|
||||
|
||||
val minProxyFee = ProxyFee.Common(
|
||||
gasLimit = (choosableFee.minimum as Fee.Ethereum).gasLimit,
|
||||
fee = convertToProxyAmount(amount = choosableFee.minimum.amount),
|
||||
)
|
||||
val normalProxyFee = ProxyFee.Common(
|
||||
gasLimit = (choosableFee.normal as Fee.Ethereum).gasLimit,
|
||||
fee = convertToProxyAmount(amount = choosableFee.normal.amount),
|
||||
)
|
||||
val priorityProxyFee = ProxyFee.Common(
|
||||
gasLimit = (choosableFee.priority as Fee.Ethereum).gasLimit,
|
||||
fee = convertToProxyAmount(amount = choosableFee.priority.amount),
|
||||
)
|
||||
|
||||
ProxyFees.MultipleFees(
|
||||
minFee = minProxyFee,
|
||||
normalFee = normalProxyFee,
|
||||
priorityFee = priorityProxyFee,
|
||||
)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
error(fee.error.message ?: fee.error.customMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getGasLimit(
|
||||
evmWalletManager: EthereumWalletManager,
|
||||
amount: Amount,
|
||||
destinationAddress: String,
|
||||
callData: SmartContractCallData?,
|
||||
): BigInteger {
|
||||
val result = if (callData == null) {
|
||||
evmWalletManager.getGasLimit(
|
||||
amount = amount,
|
||||
destination = destinationAddress,
|
||||
)
|
||||
} else {
|
||||
evmWalletManager.getGasLimit(
|
||||
amount = amount,
|
||||
destination = destinationAddress,
|
||||
callData = callData,
|
||||
)
|
||||
}
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
return result.data
|
||||
}
|
||||
is Result.Failure -> {
|
||||
error(result.error.message ?: result.error.customMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 suspend fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager {
|
||||
val selectedUserWallet = requireNotNull(
|
||||
userWalletsListManager.selectedUserWalletSync,
|
||||
) { "userWallet or userWalletsListManager is null" }
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
selectedUserWallet.walletId,
|
||||
blockchain,
|
||||
derivationPath,
|
||||
)
|
||||
|
||||
return requireNotNull(walletManager) { "no wallet manager found" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create proxy fees
|
||||
*
|
||||
* @param gasPrice min fee gasPrice
|
||||
* @param gasLimit
|
||||
* @param blockchain
|
||||
*/
|
||||
private fun createMultipleProxyFees(gasPrice: BigInteger, gasLimit: BigInteger, blockchain: Blockchain): ProxyFees {
|
||||
val patchedGasLimit = gasLimit.toBigDecimal().increaseForMantleIfNeeded(blockchain).toBigInteger()
|
||||
val gasPriceNormal = gasPrice
|
||||
.increaseBigIntegerByPercents(MULTIPLIER_GAS_PRICE_FOR_NORMAL_FEE)
|
||||
val gasPricePriority = gasPrice.increaseBigIntegerByPercents(MULTIPLIER_GAS_PRICE_FOR_PRIORITY_FEE)
|
||||
val feeMin = patchedGasLimit.multiply(gasPrice).toBigDecimal(
|
||||
scale = blockchain.decimals(),
|
||||
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
|
||||
).increaseForMantleIfNeeded(blockchain)
|
||||
val feeNormal = patchedGasLimit.multiply(gasPriceNormal).toBigDecimal(
|
||||
scale = blockchain.decimals(),
|
||||
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
|
||||
).increaseForMantleIfNeeded(blockchain)
|
||||
val feePriority = patchedGasLimit.multiply(gasPricePriority).toBigDecimal(
|
||||
scale = blockchain.decimals(),
|
||||
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
|
||||
).increaseForMantleIfNeeded(blockchain)
|
||||
val minFee = ProxyFee.Common(
|
||||
gasLimit = patchedGasLimit,
|
||||
fee = ProxyAmount(
|
||||
currencySymbol = blockchain.currency,
|
||||
value = feeMin,
|
||||
decimals = blockchain.decimals(),
|
||||
),
|
||||
)
|
||||
val normalFee = ProxyFee.Common(
|
||||
gasLimit = patchedGasLimit,
|
||||
fee = ProxyAmount(
|
||||
currencySymbol = blockchain.currency,
|
||||
value = feeNormal,
|
||||
decimals = blockchain.decimals(),
|
||||
),
|
||||
)
|
||||
val priorityFee = ProxyFee.Common(
|
||||
gasLimit = patchedGasLimit,
|
||||
fee = ProxyAmount(
|
||||
currencySymbol = blockchain.currency,
|
||||
value = feePriority,
|
||||
decimals = blockchain.decimals(),
|
||||
),
|
||||
)
|
||||
return ProxyFees.MultipleFees(
|
||||
minFee = minFee,
|
||||
normalFee = normalFee,
|
||||
priorityFee = priorityFee,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertToProxyAmount(amount: Amount): ProxyAmount {
|
||||
return ProxyAmount(
|
||||
currencySymbol = amount.currencySymbol,
|
||||
value = amount.value ?: BigDecimal.ZERO,
|
||||
decimals = amount.decimals,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase big integer by percents
|
||||
*
|
||||
* @param percents in format 150 -> 50%
|
||||
* @return increased value
|
||||
*/
|
||||
private fun BigInteger.increaseBigIntegerByPercents(percents: Int?): BigInteger {
|
||||
return if (percents != null && percents != 0) {
|
||||
this.multiply(percents.toBigInteger()).divide(BigInteger("100"))
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Workaround for Mantle. Remove after [REDACTED_JIRA]
|
||||
private fun BigDecimal.increaseForMantleIfNeeded(blockchain: Blockchain): BigDecimal {
|
||||
return if (blockchain == Blockchain.Mantle) {
|
||||
this.multiply(MANTLE_FEE_ESTIMATE_MULTIPLIER)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MULTIPLIER_GAS_PRICE_FOR_NORMAL_FEE = 150 // 50%
|
||||
private const val MULTIPLIER_GAS_PRICE_FOR_PRIORITY_FEE = 200 // 50%
|
||||
private val MANTLE_FEE_ESTIMATE_MULTIPLIER = BigDecimal("1.8")
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,8 @@ package com.tangem.tap.proxy.di
|
|||
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.TransactionManagerImpl
|
||||
import com.tangem.tap.proxy.UserWalletManagerImpl
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -37,16 +35,4 @@ internal object ProxyModule {
|
|||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTransactionManager(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
): TransactionManager {
|
||||
return TransactionManagerImpl(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ interface UserWalletsStore {
|
|||
|
||||
fun getSyncOrNull(key: UserWalletId): UserWallet?
|
||||
|
||||
suspend fun getSyncStrict(key: UserWalletId): UserWallet
|
||||
fun getSyncStrict(key: UserWalletId): UserWallet
|
||||
|
||||
suspend fun getAllSyncOrNull(): List<UserWallet>?
|
||||
|
||||
|
|
|
|||
|
|
@ -277,10 +277,7 @@ class DefaultWalletManagersFacade(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getUserWallet(userWalletId: UserWalletId) =
|
||||
requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"Unable to find a user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
private fun getUserWallet(userWalletId: UserWalletId) = userWalletsStore.getSyncStrict(userWalletId)
|
||||
|
||||
private suspend fun getAndUpdateWalletManager(
|
||||
userWallet: UserWallet,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.blockchain.common.smartcontract.CompiledSmartContractCallData
|
||||
import com.tangem.blockchain.common.smartcontract.SmartContractCallData
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
|
|
@ -12,14 +14,38 @@ class CreateTransactionDataExtrasUseCase(
|
|||
) {
|
||||
|
||||
operator fun invoke(data: String, network: Network, gasLimit: BigInteger? = null, nonce: BigInteger? = null) =
|
||||
Either.catch {
|
||||
requireNotNull(
|
||||
either {
|
||||
catch(
|
||||
{
|
||||
transactionRepository.createTransactionDataExtras(
|
||||
callData = CompiledSmartContractCallData(data.hexToBytes()),
|
||||
network = network,
|
||||
nonce = nonce,
|
||||
gasLimit = gasLimit,
|
||||
),
|
||||
) { "Failed to create transaction" }
|
||||
)
|
||||
},
|
||||
) {
|
||||
raise(it)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun invoke(
|
||||
callData: SmartContractCallData,
|
||||
network: Network,
|
||||
gasLimit: BigInteger? = null,
|
||||
nonce: BigInteger? = null,
|
||||
) = either {
|
||||
catch(
|
||||
{
|
||||
transactionRepository.createTransactionDataExtras(
|
||||
callData = callData,
|
||||
network = network,
|
||||
nonce = nonce,
|
||||
gasLimit = gasLimit,
|
||||
)
|
||||
},
|
||||
) {
|
||||
raise(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import java.math.BigInteger
|
||||
import java.math.MathContext
|
||||
import java.math.RoundingMode
|
||||
|
||||
/**
|
||||
* Use case to get transaction fee for ETH when we have gas from other services
|
||||
*
|
||||
*/
|
||||
class GetEthSpecificFeeUseCase(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
gasLimit: BigInteger,
|
||||
gasPrice: BigInteger? = null,
|
||||
) = either {
|
||||
catch(
|
||||
block = {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWallet.walletId,
|
||||
network = cryptoCurrency.network,
|
||||
)
|
||||
val gasPriceResult = gasPrice
|
||||
?: (walletManager as? EthereumWalletManager)?.getGasPriceValue()
|
||||
?: error("not supported for ${cryptoCurrency.network}")
|
||||
|
||||
val blockchain = Blockchain.fromNetworkId(networkId = cryptoCurrency.network.backendId)
|
||||
?: error("unknown networkId ${cryptoCurrency.network.backendId}")
|
||||
|
||||
val minimalFee = getEthLegacyFee(
|
||||
gasPrice = gasPriceResult,
|
||||
gasLimit = gasLimit,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
blockchain = blockchain,
|
||||
)
|
||||
|
||||
val normalGasPrice = gasPriceResult.increaseBigIntegerByPercents(MULTIPLIER_GAS_PRICE_FOR_NORMAL_FEE)
|
||||
val normalFee = getEthLegacyFee(
|
||||
gasPrice = normalGasPrice,
|
||||
gasLimit = gasLimit,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
blockchain = blockchain,
|
||||
)
|
||||
|
||||
val priorityGasPrice = gasPriceResult.increaseBigIntegerByPercents(
|
||||
MULTIPLIER_GAS_PRICE_FOR_PRIORITY_FEE,
|
||||
)
|
||||
val priorityFee = getEthLegacyFee(
|
||||
gasPrice = priorityGasPrice,
|
||||
gasLimit = gasLimit,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
blockchain = blockchain,
|
||||
)
|
||||
|
||||
TransactionFee.Choosable(
|
||||
minimum = minimalFee,
|
||||
normal = normalFee,
|
||||
priority = priorityFee,
|
||||
)
|
||||
},
|
||||
catch = {
|
||||
raise(GetFeeError.DataError(it))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun getEthLegacyFee(
|
||||
gasPrice: BigInteger,
|
||||
decimals: Int,
|
||||
gasLimit: BigInteger,
|
||||
blockchain: Blockchain,
|
||||
): Fee.Ethereum.Legacy {
|
||||
val amount = Amount(
|
||||
value = gasLimit.multiply(gasPrice).toBigDecimal(
|
||||
scale = decimals,
|
||||
mathContext = MathContext(decimals, RoundingMode.HALF_EVEN),
|
||||
),
|
||||
blockchain = blockchain,
|
||||
)
|
||||
return Fee.Ethereum.Legacy(
|
||||
amount = amount,
|
||||
gasLimit = gasLimit,
|
||||
gasPrice = gasPrice,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun EthereumWalletManager.getGasPriceValue(): BigInteger {
|
||||
return when (val gasPrice = this.getGasPrice()) {
|
||||
is Result.Failure -> throw gasPrice.error
|
||||
is Result.Success -> gasPrice.data
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase big integer by percents
|
||||
*
|
||||
* @param percents in format 150 -> 50%
|
||||
* @return increased value
|
||||
*/
|
||||
private fun BigInteger.increaseBigIntegerByPercents(percents: Int?): BigInteger {
|
||||
return if (percents != null && percents != 0) {
|
||||
this.multiply(percents.toBigInteger()).divide(BigInteger("100"))
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MULTIPLIER_GAS_PRICE_FOR_NORMAL_FEE = 150 // 50%
|
||||
const val MULTIPLIER_GAS_PRICE_FOR_PRIORITY_FEE = 200 // 50%
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ dependencies {
|
|||
implementation(projects.core.datasource)
|
||||
|
||||
/** Other Libraries **/
|
||||
implementation(tangemDeps.blockchain)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.moshi.kotlin)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.swap.domain.models.ui
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
|
|
@ -86,11 +87,6 @@ data class RequestApproveStateData(
|
|||
val spenderAddress: String,
|
||||
)
|
||||
|
||||
// data class SwapStateData(
|
||||
// val fee: TxFeeState,
|
||||
// val swapModel: SwapDataModel,
|
||||
// )
|
||||
|
||||
sealed class TxFeeState {
|
||||
data class MultipleFeeState(
|
||||
val normalFee: TxFee,
|
||||
|
|
@ -114,34 +110,15 @@ sealed class TxFeeState {
|
|||
|
||||
data class TxFee(
|
||||
val feeValue: BigDecimal,
|
||||
val gasLimit: Int,
|
||||
val feeFiatFormatted: String,
|
||||
val feeCryptoFormatted: String,
|
||||
val feeIncludeOtherNativeFee: BigDecimal,
|
||||
val feeFiatFormattedWithNative: String,
|
||||
val feeCryptoFormattedWithNative: String,
|
||||
val decimals: Int,
|
||||
val cryptoSymbol: String,
|
||||
val feeType: FeeType,
|
||||
val params: Params?,
|
||||
) {
|
||||
|
||||
sealed class Params {
|
||||
|
||||
data class Filecoin(
|
||||
val gasPremium: Long,
|
||||
) : Params()
|
||||
|
||||
data class Sui(
|
||||
val gasBudget: Long,
|
||||
val gasPrice: Long,
|
||||
) : Params()
|
||||
|
||||
data class Hedera(
|
||||
val additionalHBARFee: BigDecimal,
|
||||
) : Params()
|
||||
}
|
||||
}
|
||||
val fee: Fee,
|
||||
)
|
||||
|
||||
enum class FeeType {
|
||||
NORMAL, PRIORITY
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
|
||||
|
||||
interface BlockchainInteractor {
|
||||
|
||||
/**
|
||||
* In app blockchain id, actual in blockchain sdk, not the same as networkId
|
||||
*
|
||||
* workaround till not use backend only and not integrated server vs sdk
|
||||
*/
|
||||
fun getBlockchainInfo(networkId: String): NetworkInfo
|
||||
|
||||
fun getExplorerTransactionLink(networkId: String, txHash: String): String
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultBlockchainInteractor @Inject constructor(
|
||||
private val transactionManager: TransactionManager,
|
||||
) : BlockchainInteractor {
|
||||
|
||||
override fun getBlockchainInfo(networkId: String): NetworkInfo {
|
||||
return transactionManager.getBlockchainInfo(networkId).let {
|
||||
NetworkInfo(
|
||||
name = it.name,
|
||||
blockchainId = it.blockchainId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getExplorerTransactionLink(networkId: String, txHash: String): String {
|
||||
return transactionManager.getExplorerTransactionLink(networkId, txHash)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,16 +3,13 @@ package com.tangem.feature.swap.domain
|
|||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Blockchain.*
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionExtras
|
||||
import com.tangem.blockchain.common.smartcontract.CompiledSmartContractCallData
|
||||
import com.tangem.blockchain.common.smartcontract.SmartContractCallDataProviderFactory
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
|
|
@ -20,6 +17,7 @@ import com.tangem.domain.appcurrency.extenstions.unwrap
|
|||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.quotes.QuotesRepositoryV2
|
||||
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
|
||||
import com.tangem.domain.tokens.GetCurrencyCheckUseCase
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
|
|
@ -35,17 +33,13 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.feature.swap.domain.converters.SwapCurrencyConverter
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
import com.tangem.feature.swap.domain.models.toStringWithRightOffset
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import com.tangem.lib.crypto.models.ProxyAmount
|
||||
import com.tangem.lib.crypto.models.ProxyFee
|
||||
import com.tangem.lib.crypto.models.ProxyFees
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -57,11 +51,11 @@ import java.math.RoundingMode
|
|||
|
||||
@Suppress("LargeClass", "LongParameterList")
|
||||
internal class SwapInteractorImpl @AssistedInject constructor(
|
||||
private val transactionManager: TransactionManager,
|
||||
private val userWalletManager: UserWalletManager,
|
||||
private val repository: SwapRepository,
|
||||
private val allowPermissionsHandler: AllowPermissionsHandler,
|
||||
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
|
||||
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
|
||||
private val sendTransactionUseCase: SendTransactionUseCase,
|
||||
private val createTransactionUseCase: CreateTransactionUseCase,
|
||||
private val createTransferTransactionUseCase: CreateTransferTransactionUseCase,
|
||||
|
|
@ -78,6 +72,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
private val initialToCurrencyResolver: InitialToCurrencyResolver,
|
||||
private val validateTransactionUseCase: ValidateTransactionUseCase,
|
||||
private val estimateFeeUseCase: EstimateFeeUseCase,
|
||||
private val getFeeUseCase: GetFeeUseCase,
|
||||
private val getEthSpecificFeeUseCase: GetEthSpecificFeeUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
|
||||
private val amountFormatter: AmountFormatter,
|
||||
|
|
@ -88,7 +84,6 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
GetSelectedAppCurrencyUseCase(appCurrencyRepository)
|
||||
}
|
||||
|
||||
private val swapCurrencyConverter = SwapCurrencyConverter()
|
||||
private val hundredPercent = BigInteger("100")
|
||||
|
||||
private val userWallet
|
||||
|
|
@ -218,10 +213,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
}
|
||||
|
||||
val approveTransaction = createApprovalTransactionUseCase(
|
||||
fee = getFeeForTransaction(
|
||||
fee = permissionOptions.txFee,
|
||||
blockchain = Blockchain.fromId(permissionOptions.fromToken.network.id.value),
|
||||
),
|
||||
fee = permissionOptions.txFee.fee,
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = permissionOptions.fromToken as CryptoCurrency.Token,
|
||||
amount = amount?.value,
|
||||
|
|
@ -338,10 +330,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
|
||||
if (isAllowedToSpend && allowPermissionsHandler.isAddressAllowanceInProgress(fromTokenAddress)) {
|
||||
allowPermissionsHandler.removeAddressFromProgress(fromTokenAddress)
|
||||
transactionManager.updateWalletManager(
|
||||
networkId,
|
||||
fromToken.currency.network.derivationPath.value,
|
||||
)
|
||||
fetchCurrencyStatusUseCase(userWalletId, fromToken.currency.id, true)
|
||||
}
|
||||
return if (isAllowedToSpend && isBalanceWithoutFeeEnough) {
|
||||
provider to loadDexSwapData(
|
||||
|
|
@ -538,7 +527,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
swapData = requireNotNull(swapData),
|
||||
currencyToSendStatus = currencyToSend,
|
||||
currencyToGetStatus = currencyToGet,
|
||||
fee = fee,
|
||||
txFee = fee,
|
||||
amountToSwap = amountToSwap,
|
||||
)
|
||||
}
|
||||
|
|
@ -593,7 +582,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
currencyToSendStatus: CryptoCurrencyStatus,
|
||||
currencyToGetStatus: CryptoCurrencyStatus,
|
||||
amountToSwap: String,
|
||||
fee: TxFee,
|
||||
txFee: TxFee,
|
||||
): SwapTransactionState {
|
||||
val amountDecimal = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format" }
|
||||
val amount = SwapAmount(amountDecimal, currencyToSendStatus.currency.decimals)
|
||||
|
|
@ -603,15 +592,12 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
val amountToSend = createNativeAmountForDex(swapData.transaction.txValue, currencyToSendStatus.currency.network)
|
||||
val txData = createTransactionUseCase(
|
||||
amount = amountToSend,
|
||||
fee = getFeeForTransaction(
|
||||
fee = fee,
|
||||
blockchain = Blockchain.fromId(currencyToSendStatus.currency.network.id.value),
|
||||
),
|
||||
fee = txFee.fee,
|
||||
memo = null,
|
||||
destination = swapData.transaction.txTo,
|
||||
userWalletId = userWalletId,
|
||||
network = currencyToSendStatus.currency.network,
|
||||
txExtras = createDexTxExtras(dataToSign, currencyToSendStatus.currency.network, fee.gasLimit),
|
||||
txExtras = createDexTxExtras(dataToSign, currencyToSendStatus.currency.network, txFee.fee.getGasLimit()),
|
||||
).getOrElse {
|
||||
Timber.e(it, "Failed to create swap dex tx data")
|
||||
return SwapTransactionState.Error.UnknownError
|
||||
|
|
@ -706,10 +692,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
|
||||
val txData = createTransferTransactionUseCase(
|
||||
amount = amount.value.convertToSdkAmount(currencyToSend.currency),
|
||||
fee = getFeeForTransaction(
|
||||
fee = txFee,
|
||||
blockchain = Blockchain.fromId(currencyToSend.currency.network.id.value),
|
||||
),
|
||||
fee = txFee.fee,
|
||||
memo = exchangeDataCex.txExtraId,
|
||||
destination = exchangeDataCex.txTo,
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -777,235 +760,6 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun getFeeForTransaction(fee: TxFee, blockchain: Blockchain): Fee {
|
||||
val feeAmountValue = fee.feeValue
|
||||
val feeAmount = Amount(
|
||||
value = fee.feeValue,
|
||||
currencySymbol = fee.cryptoSymbol,
|
||||
decimals = fee.decimals,
|
||||
type = AmountType.Coin,
|
||||
)
|
||||
|
||||
return if (blockchain.isEvm()) {
|
||||
val feeAmountWithDecimals = feeAmountValue.movePointRight(fee.decimals)
|
||||
|
||||
Fee.Ethereum.Legacy(
|
||||
amount = feeAmount,
|
||||
gasLimit = fee.gasLimit.toBigInteger(),
|
||||
gasPrice = (feeAmountWithDecimals / fee.gasLimit.toBigDecimal()).toBigInteger(),
|
||||
)
|
||||
} else {
|
||||
when (blockchain) {
|
||||
// region Blockchains with their own fees
|
||||
VeChain,
|
||||
VeChainTestnet,
|
||||
-> {
|
||||
Fee.VeChain(
|
||||
amount = feeAmount,
|
||||
gasPriceCoef = Fee.VeChain.getGasPriceCoef(fee.gasLimit.toLong(), fee.feeValue),
|
||||
gasLimit = fee.gasLimit.toLong(),
|
||||
)
|
||||
}
|
||||
Aptos,
|
||||
AptosTestnet,
|
||||
-> {
|
||||
val gasUnitPrice = fee.feeValue.divide(
|
||||
fee.gasLimit.toBigDecimal(),
|
||||
Aptos.decimals(),
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
|
||||
Fee.Aptos(
|
||||
amount = feeAmount,
|
||||
gasUnitPrice = gasUnitPrice
|
||||
.movePointRight(Aptos.decimals())
|
||||
.toLong(),
|
||||
gasLimit = fee.gasLimit.toLong(),
|
||||
)
|
||||
}
|
||||
Filecoin,
|
||||
-> {
|
||||
val gasUnitPrice = fee.feeValue.divide(
|
||||
BigDecimal(fee.gasLimit),
|
||||
Filecoin.decimals(),
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
val feeParams = requireNotNull(fee.params as? TxFee.Params.Filecoin)
|
||||
|
||||
Fee.Filecoin(
|
||||
amount = feeAmount,
|
||||
gasUnitPrice = gasUnitPrice
|
||||
.movePointRight(Filecoin.decimals())
|
||||
.toLong(),
|
||||
gasLimit = fee.gasLimit.toLong(),
|
||||
gasPremium = feeParams.gasPremium,
|
||||
)
|
||||
}
|
||||
Sui,
|
||||
SuiTestnet,
|
||||
-> {
|
||||
val feeParams = requireNotNull(fee.params as? TxFee.Params.Sui)
|
||||
|
||||
Fee.Sui(
|
||||
amount = feeAmount,
|
||||
gasPrice = feeParams.gasPrice,
|
||||
gasBudget = feeParams.gasBudget,
|
||||
)
|
||||
}
|
||||
Hedera,
|
||||
HederaTestnet,
|
||||
-> {
|
||||
val feeParams = requireNotNull(fee.params as? TxFee.Params.Hedera)
|
||||
Fee.Hedera(amount = feeAmount, additionalHBARFee = feeParams.additionalHBARFee)
|
||||
}
|
||||
// endregion
|
||||
// region Blockchains with common fees or EVM-like fees
|
||||
Unknown,
|
||||
Arbitrum,
|
||||
ArbitrumTestnet,
|
||||
Avalanche,
|
||||
AvalancheTestnet,
|
||||
Binance,
|
||||
BinanceTestnet,
|
||||
BSC,
|
||||
BSCTestnet,
|
||||
Bitcoin,
|
||||
BitcoinTestnet,
|
||||
BitcoinCash,
|
||||
BitcoinCashTestnet,
|
||||
Cardano,
|
||||
Cosmos,
|
||||
CosmosTestnet,
|
||||
Dogecoin,
|
||||
Ducatus,
|
||||
Ethereum,
|
||||
EthereumTestnet,
|
||||
EthereumClassic,
|
||||
EthereumClassicTestnet,
|
||||
Fantom,
|
||||
FantomTestnet,
|
||||
Litecoin,
|
||||
Near,
|
||||
NearTestnet,
|
||||
Polkadot,
|
||||
PolkadotTestnet,
|
||||
Kava,
|
||||
KavaTestnet,
|
||||
Kusama,
|
||||
Polygon,
|
||||
PolygonTestnet,
|
||||
RSK,
|
||||
Sei,
|
||||
SeiTestnet,
|
||||
Stellar,
|
||||
StellarTestnet,
|
||||
Solana,
|
||||
SolanaTestnet,
|
||||
Tezos,
|
||||
Tron,
|
||||
TronTestnet,
|
||||
XRP,
|
||||
Gnosis,
|
||||
Dash,
|
||||
Optimism,
|
||||
OptimismTestnet,
|
||||
Dischain,
|
||||
EthereumPow,
|
||||
EthereumPowTestnet,
|
||||
Kaspa,
|
||||
KaspaTestnet,
|
||||
Telos,
|
||||
TelosTestnet,
|
||||
TON,
|
||||
TONTestnet,
|
||||
Ravencoin,
|
||||
RavencoinTestnet,
|
||||
TerraV1,
|
||||
TerraV2,
|
||||
Cronos,
|
||||
AlephZero,
|
||||
AlephZeroTestnet,
|
||||
OctaSpace,
|
||||
OctaSpaceTestnet,
|
||||
Chia,
|
||||
ChiaTestnet,
|
||||
Decimal,
|
||||
DecimalTestnet,
|
||||
XDC,
|
||||
XDCTestnet,
|
||||
Playa3ull,
|
||||
Shibarium,
|
||||
ShibariumTestnet,
|
||||
Algorand,
|
||||
AlgorandTestnet,
|
||||
Aurora,
|
||||
AuroraTestnet,
|
||||
Areon,
|
||||
AreonTestnet,
|
||||
PulseChain,
|
||||
PulseChainTestnet,
|
||||
ZkSyncEra,
|
||||
ZkSyncEraTestnet,
|
||||
Nexa,
|
||||
NexaTestnet,
|
||||
Moonbeam,
|
||||
MoonbeamTestnet,
|
||||
Manta,
|
||||
MantaTestnet,
|
||||
PolygonZkEVM,
|
||||
PolygonZkEVMTestnet,
|
||||
Radiant,
|
||||
Fact0rn,
|
||||
Base,
|
||||
BaseTestnet,
|
||||
Moonriver,
|
||||
MoonriverTestnet,
|
||||
Mantle,
|
||||
MantleTestnet,
|
||||
Flare,
|
||||
FlareTestnet,
|
||||
Taraxa,
|
||||
TaraxaTestnet,
|
||||
Koinos,
|
||||
KoinosTestnet,
|
||||
Joystream,
|
||||
Bittensor,
|
||||
Blast,
|
||||
BlastTestnet,
|
||||
Cyber,
|
||||
CyberTestnet,
|
||||
InternetComputer,
|
||||
EnergyWebChain,
|
||||
EnergyWebChainTestnet,
|
||||
EnergyWebX,
|
||||
EnergyWebXTestnet,
|
||||
Casper,
|
||||
CasperTestnet,
|
||||
Core,
|
||||
CoreTestnet,
|
||||
Xodex,
|
||||
Canxium,
|
||||
Chiliz,
|
||||
ChilizTestnet,
|
||||
Alephium,
|
||||
AlephiumTestnet,
|
||||
Clore,
|
||||
VanarChain,
|
||||
VanarChainTestnet,
|
||||
OdysseyChain, OdysseyChainTestnet,
|
||||
Bitrock, BitrockTestnet,
|
||||
Sonic, SonicTestnet,
|
||||
ApeChain, ApeChainTestnet,
|
||||
Scroll, ScrollTestnet,
|
||||
ZkLinkNova, ZkLinkNovaTestnet,
|
||||
Pepecoin, PepecoinTestnet,
|
||||
-> Fee.Common(feeAmount)
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeSwapTransaction(
|
||||
currencyToSend: CryptoCurrencyStatus,
|
||||
currencyToGet: CryptoCurrencyStatus,
|
||||
|
|
@ -1418,17 +1172,14 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
val otherNativeFee = transaction.otherNativeFeeWei
|
||||
?.movePointLeft(nativeCoinDecimals)
|
||||
?: BigDecimal.ZERO
|
||||
val txFeeState = when (
|
||||
val feeData = getFeeDataForDexSwap(
|
||||
networkId = networkId,
|
||||
val txFeeState = getFeeDataForDexSwap(
|
||||
network = fromToken.currency.network,
|
||||
transaction = transaction,
|
||||
fromToken = fromToken.currency,
|
||||
cardId = userWallet.scanResponse.card.cardId,
|
||||
)
|
||||
) {
|
||||
is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken.currency, otherNativeFee)
|
||||
is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken.currency, otherNativeFee)
|
||||
}
|
||||
.patchTransactionFeeForSwap(INCREASE_GAS_LIMIT_FOR_DEX)
|
||||
.toTxFeeState(fromToken.currency, otherNativeFee)
|
||||
|
||||
val includeFeeInAmount = IncludeFeeInAmount.Excluded // exclude for dex
|
||||
val feeByPriority = selectFeeByType(feeType = selectedFee, txFeeState = txFeeState)
|
||||
val feeToCheckFunds = feeByPriority + (otherNativeFee ?: BigDecimal.ZERO)
|
||||
|
|
@ -1492,17 +1243,13 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private suspend fun getFeeDataForDexSwap(
|
||||
networkId: String,
|
||||
network: Network,
|
||||
transaction: ExpressTransactionModel.DEX,
|
||||
fromToken: CryptoCurrency,
|
||||
cardId: String?,
|
||||
): ProxyFees {
|
||||
if (cardId != null && isDemoCardUseCase(cardId)) {
|
||||
return getDemoFees(fromToken)
|
||||
}
|
||||
): TransactionFee {
|
||||
return try {
|
||||
val nativeBalance = userWalletManager.getNativeTokenBalance(
|
||||
networkId = networkId,
|
||||
networkId = network.backendId,
|
||||
derivationPath = fromToken.network.derivationPath.value,
|
||||
) ?: ProxyAmount.empty()
|
||||
val amountToSend = createNativeAmountForDex(transaction.txValue, fromToken.network)
|
||||
|
|
@ -1510,21 +1257,30 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
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),
|
||||
|
||||
val extras = createTransactionExtrasUseCase(
|
||||
data = transaction.txData,
|
||||
network = network,
|
||||
).getOrNull() ?: error("unable to create extras")
|
||||
|
||||
val transactionData = TransactionData.Uncompiled(
|
||||
amount = amountToSend,
|
||||
destinationAddress = transaction.txTo,
|
||||
increaseBy = INCREASE_GAS_LIMIT_BY,
|
||||
callData = CompiledSmartContractCallData(transaction.txData.hexToBytes()),
|
||||
derivationPath = fromToken.network.derivationPath.value,
|
||||
fee = null,
|
||||
sourceAddress = transaction.txFrom,
|
||||
extras = extras,
|
||||
)
|
||||
getFeeUseCase(
|
||||
transactionData = transactionData,
|
||||
network = network,
|
||||
userWallet = userWallet,
|
||||
).getOrNull() ?: error("unable to calculate fee")
|
||||
} catch (e: IllegalStateException) {
|
||||
transactionManager.getFeeForGas(
|
||||
networkId = networkId,
|
||||
gas = transaction.gas.multiply(INCREASE_GAS_LIMIT_BY.toBigInteger()).divide(100.toBigInteger()),
|
||||
derivationPath = fromToken.network.derivationPath.value,
|
||||
)
|
||||
getEthSpecificFeeUseCase(
|
||||
userWallet = userWallet,
|
||||
cryptoCurrency = fromToken,
|
||||
gasLimit = transaction.gas,
|
||||
).getOrNull() ?: error("can't get fee for getEthSpecificFeeUseCase")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1575,7 +1331,11 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
): TxFeeState {
|
||||
return txFeeResult?.fold(
|
||||
ifLeft = { TxFeeState.Empty },
|
||||
ifRight = { txFee -> txFee.toTxFeeState(fromToken.currency, null) },
|
||||
ifRight = { txFee ->
|
||||
txFee
|
||||
.patchTransactionFeeForSwap(INCREASE_GAS_LIMIT_FOR_SEND)
|
||||
.toTxFeeState(fromToken.currency, null)
|
||||
},
|
||||
) ?: TxFeeState.Empty
|
||||
}
|
||||
|
||||
|
|
@ -1625,20 +1385,27 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
amount = swapAmount.value.convertToSdkAmount(fromToken),
|
||||
blockchain = Blockchain.fromId(fromToken.network.id.value),
|
||||
)
|
||||
val cardId = userWallet.scanResponse.card.cardId
|
||||
val feeData = if (isDemoCardUseCase(cardId)) {
|
||||
getDemoFees(fromTokenStatus.currency)
|
||||
} else {
|
||||
try {
|
||||
transactionManager.getFee(
|
||||
networkId = networkId,
|
||||
amountToSend = createNativeAmountForDex("0", fromToken.network),
|
||||
currencyToSend = swapCurrencyConverter.convert(repository.getNativeTokenForNetwork(networkId)),
|
||||
destinationAddress = fromToken.getContractAddress(),
|
||||
increaseBy = INCREASE_GAS_LIMIT_BY,
|
||||
val feeData = try {
|
||||
val extras = createTransactionExtrasUseCase(
|
||||
callData = callData,
|
||||
derivationPath = derivationPath,
|
||||
network = fromToken.network,
|
||||
).getOrNull() ?: error("unable to create extras")
|
||||
|
||||
val fromAddress = requireNotNull(
|
||||
fromTokenStatus.value.networkAddress?.defaultAddress?.value,
|
||||
) { "networkAddress cant be null" }
|
||||
val transactionData = TransactionData.Uncompiled(
|
||||
amount = createNativeAmountForDex("0", fromToken.network),
|
||||
destinationAddress = fromToken.getContractAddress(),
|
||||
fee = null,
|
||||
sourceAddress = fromAddress,
|
||||
extras = extras,
|
||||
)
|
||||
getFeeUseCase(
|
||||
transactionData = transactionData,
|
||||
network = fromToken.network,
|
||||
userWallet = userWallet,
|
||||
).getOrNull() ?: error("unable to calculate fee")
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to get fee")
|
||||
// it's impossible next steps without fee
|
||||
|
|
@ -1649,11 +1416,11 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
expressDataError = ExpressDataError.UnknownError,
|
||||
)
|
||||
}
|
||||
}
|
||||
val feeState = when (feeData) {
|
||||
is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken)
|
||||
is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken)
|
||||
}
|
||||
|
||||
val feeState = feeData
|
||||
.patchTransactionFeeForSwap(INCREASE_GAS_LIMIT_FOR_DEX)
|
||||
.toTxFeeState(fromToken, null)
|
||||
|
||||
val fee = when (feeState) {
|
||||
TxFeeState.Empty -> BigDecimal.ZERO
|
||||
is TxFeeState.MultipleFeeState -> feeState.normalFee.feeValue
|
||||
|
|
@ -1683,120 +1450,6 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun ProxyFees.MultipleFees.proxyFeesToFeeState(
|
||||
fromToken: CryptoCurrency,
|
||||
otherNativeFee: BigDecimal? = null,
|
||||
): TxFeeState {
|
||||
val otherNativeFeeValue = otherNativeFee ?: BigDecimal.ZERO
|
||||
val normalFeeValue = this.minFee.fee.value // in swap for normal use min fee
|
||||
val normalFeeGas = this.minFee.gasLimit.toInt()
|
||||
val priorityFeeValue = this.normalFee.fee.value // in swap for priority use normal fee
|
||||
val priorityFeeGas = this.normalFee.gasLimit.toInt()
|
||||
// region fees to use
|
||||
val feesFiat = getFormattedFiatFees(fromToken, normalFeeValue, priorityFeeValue)
|
||||
val normalFiatFee = requireNotNull(feesFiat.getOrNull(0)) { "feesFiat item 0 couldn't be null" }
|
||||
val priorityFiatFee = requireNotNull(feesFiat.getOrNull(1)) { "feesFiat item 1 couldn't be null" }
|
||||
val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = normalFeeValue,
|
||||
decimals = minFee.fee.decimals,
|
||||
)
|
||||
val priorityCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = priorityFeeValue,
|
||||
decimals = normalFee.fee.decimals,
|
||||
)
|
||||
// endregion
|
||||
// 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,
|
||||
)
|
||||
// endregion
|
||||
return TxFeeState.MultipleFeeState(
|
||||
normalFee = TxFee(
|
||||
feeValue = normalFeeValue,
|
||||
gasLimit = normalFeeGas,
|
||||
feeFiatFormatted = normalFiatFee,
|
||||
feeCryptoFormatted = normalCryptoFee,
|
||||
feeIncludeOtherNativeFee = normalFeeValue + otherNativeFeeValue,
|
||||
feeFiatFormattedWithNative = normalFiatFeeWithNative,
|
||||
feeCryptoFormattedWithNative = normalCryptoFeeWithNative,
|
||||
decimals = minFee.fee.decimals,
|
||||
cryptoSymbol = minFee.fee.currencySymbol,
|
||||
feeType = FeeType.NORMAL,
|
||||
params = getSwapFeeParams(minFee),
|
||||
),
|
||||
priorityFee = TxFee(
|
||||
feeValue = priorityFeeValue,
|
||||
gasLimit = priorityFeeGas,
|
||||
feeFiatFormatted = priorityFiatFee,
|
||||
feeCryptoFormatted = priorityCryptoFee,
|
||||
feeIncludeOtherNativeFee = priorityFeeValue + otherNativeFeeValue,
|
||||
feeFiatFormattedWithNative = priorityFiatFeeWithNative,
|
||||
feeCryptoFormattedWithNative = priorityCryptoFeeWithNative,
|
||||
decimals = normalFee.fee.decimals,
|
||||
cryptoSymbol = normalFee.fee.currencySymbol,
|
||||
feeType = FeeType.PRIORITY,
|
||||
params = getSwapFeeParams(normalFee),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun ProxyFees.SingleFee.proxyFeesToFeeState(
|
||||
fromToken: CryptoCurrency,
|
||||
otherNativeFee: BigDecimal? = null,
|
||||
): TxFeeState {
|
||||
val otherNativeFeeValue = otherNativeFee ?: BigDecimal.ZERO
|
||||
val normalFeeValue = this.singleFee.fee.value
|
||||
val normalFeeGas = this.singleFee.gasLimit.toInt()
|
||||
val feesFiat = getFormattedFiatFees(fromToken, normalFeeValue)
|
||||
val normalFiatFee = requireNotNull(feesFiat.getOrNull(0)) { "feesFiat item 0 couldn't be null" }
|
||||
val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = normalFeeValue,
|
||||
decimals = singleFee.fee.decimals,
|
||||
)
|
||||
// region fees include otherNativeFee
|
||||
val feesFiatWithNative = getFormattedFiatFees(
|
||||
fromToken = fromToken,
|
||||
normalFeeValue + otherNativeFeeValue,
|
||||
normalFeeValue + otherNativeFeeValue,
|
||||
)
|
||||
val normalFiatFeeWithNative =
|
||||
requireNotNull(feesFiatWithNative.getOrNull(0)) { "feesFiat item 0 couldn't be null" }
|
||||
val normalCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = normalFeeValue + otherNativeFeeValue,
|
||||
decimals = singleFee.fee.decimals,
|
||||
)
|
||||
// endregion
|
||||
return TxFeeState.SingleFeeState(
|
||||
fee = TxFee(
|
||||
feeValue = normalFeeValue,
|
||||
gasLimit = normalFeeGas,
|
||||
feeFiatFormatted = normalFiatFee,
|
||||
feeCryptoFormatted = normalCryptoFee,
|
||||
feeIncludeOtherNativeFee = normalFeeValue + otherNativeFeeValue,
|
||||
feeFiatFormattedWithNative = normalFiatFeeWithNative,
|
||||
feeCryptoFormattedWithNative = normalCryptoFeeWithNative,
|
||||
decimals = singleFee.fee.decimals,
|
||||
cryptoSymbol = singleFee.fee.currencySymbol,
|
||||
feeType = FeeType.NORMAL,
|
||||
params = getSwapFeeParams(singleFee),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private suspend fun TransactionFee.toTxFeeState(
|
||||
fromToken: CryptoCurrency,
|
||||
|
|
@ -1805,20 +1458,18 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
val otherNativeFeeValue = otherNativeFee ?: BigDecimal.ZERO
|
||||
return when (this) {
|
||||
is TransactionFee.Choosable -> {
|
||||
val normalFee = this.normal.increaseGasLimitBy(INCREASE_GAS_LIMIT_FOR_SEND)
|
||||
val priorityFee = this.priority.increaseGasLimitBy(INCREASE_GAS_LIMIT_FOR_SEND)
|
||||
val feeNormal = normalFee.amount.value ?: BigDecimal.ZERO
|
||||
val feePriority = priorityFee.amount.value ?: BigDecimal.ZERO
|
||||
val feeNormal = this.normal.amount.value ?: BigDecimal.ZERO
|
||||
val feePriority = this.priority.amount.value ?: BigDecimal.ZERO
|
||||
val normalFiatValue = getFormattedFiatFees(fromToken, feeNormal)[0]
|
||||
val priorityFiatValue = getFormattedFiatFees(fromToken, feePriority)[0]
|
||||
|
||||
val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = feeNormal,
|
||||
decimals = normalFee.amount.decimals,
|
||||
decimals = this.normal.amount.decimals,
|
||||
)
|
||||
val priorityCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = feePriority,
|
||||
decimals = priorityFee.amount.decimals,
|
||||
decimals = this.priority.amount.decimals,
|
||||
)
|
||||
|
||||
// region otherNativeFee
|
||||
|
|
@ -1829,39 +1480,35 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
|
||||
val normalCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = normalFeeWithOtherNative,
|
||||
decimals = normalFee.amount.decimals,
|
||||
decimals = this.normal.amount.decimals,
|
||||
)
|
||||
val priorityCryptoFeeWithNative = amountFormatter.formatBigDecimalAmountToUI(
|
||||
amount = priorityFeeWithOtherNative,
|
||||
decimals = priorityFee.amount.decimals,
|
||||
decimals = this.priority.amount.decimals,
|
||||
)
|
||||
// endregion
|
||||
TxFeeState.MultipleFeeState(
|
||||
normalFee = TxFee(
|
||||
feeValue = feeNormal,
|
||||
gasLimit = normalFee.getGasLimit(),
|
||||
feeFiatFormatted = normalFiatValue,
|
||||
feeCryptoFormatted = normalCryptoFee,
|
||||
feeIncludeOtherNativeFee = normalFeeWithOtherNative,
|
||||
feeFiatFormattedWithNative = normalFiatValueWithNative,
|
||||
feeCryptoFormattedWithNative = normalCryptoFeeWithNative,
|
||||
decimals = normalFee.amount.decimals,
|
||||
cryptoSymbol = normalFee.amount.currencySymbol,
|
||||
cryptoSymbol = this.normal.amount.currencySymbol,
|
||||
feeType = FeeType.NORMAL,
|
||||
params = getSwapFeeParams(normalFee),
|
||||
fee = this.normal,
|
||||
),
|
||||
priorityFee = TxFee(
|
||||
feeValue = feePriority,
|
||||
gasLimit = priorityFee.getGasLimit(),
|
||||
feeFiatFormatted = priorityFiatValue,
|
||||
feeCryptoFormatted = priorityCryptoFee,
|
||||
feeIncludeOtherNativeFee = priorityFeeWithOtherNative,
|
||||
feeFiatFormattedWithNative = priorityFiatValueWithNative,
|
||||
feeCryptoFormattedWithNative = priorityCryptoFeeWithNative,
|
||||
decimals = priorityFee.amount.decimals,
|
||||
cryptoSymbol = priorityFee.amount.currencySymbol,
|
||||
cryptoSymbol = this.priority.amount.currencySymbol,
|
||||
feeType = FeeType.PRIORITY,
|
||||
params = getSwapFeeParams(priorityFee),
|
||||
fee = this.priority,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -1884,59 +1531,20 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
TxFeeState.SingleFeeState(
|
||||
fee = TxFee(
|
||||
feeValue = this.normal.amount.value ?: BigDecimal.ZERO,
|
||||
gasLimit = this.normal.getGasLimit(),
|
||||
feeFiatFormatted = normalFiatValue,
|
||||
feeCryptoFormatted = normalCryptoFee,
|
||||
feeIncludeOtherNativeFee = normalFeeWithOtherNative,
|
||||
feeFiatFormattedWithNative = normalFiatValueWithNative,
|
||||
feeCryptoFormattedWithNative = normalCryptoFeeWithNative,
|
||||
decimals = normal.amount.decimals,
|
||||
cryptoSymbol = normal.amount.currencySymbol,
|
||||
feeType = FeeType.NORMAL,
|
||||
params = getSwapFeeParams(normal),
|
||||
fee = this.normal,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSwapFeeParams(fee: Fee): TxFee.Params? = when (fee) {
|
||||
is Fee.Filecoin -> TxFee.Params.Filecoin(
|
||||
gasPremium = fee.gasPremium,
|
||||
)
|
||||
is Fee.Sui -> TxFee.Params.Sui(
|
||||
gasPrice = fee.gasPrice,
|
||||
gasBudget = fee.gasBudget,
|
||||
)
|
||||
is Fee.Hedera -> TxFee.Params.Hedera(
|
||||
additionalHBARFee = fee.additionalHBARFee,
|
||||
)
|
||||
is Fee.Aptos,
|
||||
is Fee.Bitcoin,
|
||||
is Fee.CardanoToken,
|
||||
is Fee.Common,
|
||||
is Fee.Ethereum.EIP1559,
|
||||
is Fee.Ethereum.Legacy,
|
||||
is Fee.Kaspa,
|
||||
is Fee.Tron,
|
||||
is Fee.VeChain,
|
||||
is Fee.Alephium,
|
||||
-> null
|
||||
}
|
||||
|
||||
private fun getSwapFeeParams(proxyFee: ProxyFee): TxFee.Params? = when (proxyFee) {
|
||||
is ProxyFee.Filecoin -> TxFee.Params.Filecoin(
|
||||
gasPremium = proxyFee.gasPremium,
|
||||
)
|
||||
is ProxyFee.Sui -> TxFee.Params.Sui(
|
||||
gasPrice = proxyFee.gasPrice,
|
||||
gasBudget = proxyFee.gasBudget,
|
||||
)
|
||||
is ProxyFee.CardanoToken,
|
||||
is ProxyFee.Common,
|
||||
-> null
|
||||
}
|
||||
|
||||
private fun createNativeAmountForDex(txValueAmount: String, network: Network): Amount {
|
||||
val nativeDecimals = Blockchain.fromNetworkId(network.backendId)?.decimals()
|
||||
?: error("Blockchain not found")
|
||||
|
|
@ -1950,7 +1558,50 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
}
|
||||
|
||||
/**
|
||||
* Workaround to increase gas limit cause we calculate fee for random address
|
||||
* We need to increase gasLimit for Ethereum fees for 2 cases
|
||||
*
|
||||
* DEX: for dex calculated gasLimit for given data might be changed when transaction processing
|
||||
* for that case dex providers recommend to increase gasLimit for few percents to ensure transaction completes
|
||||
*
|
||||
* CEX: for that case we calculate fee for random generated address and gasLimit might be different for it
|
||||
* and result address to send. That's why we should increase gasLimit a little
|
||||
*
|
||||
*/
|
||||
private fun TransactionFee.patchTransactionFeeForSwap(increaseBy: Int): TransactionFee {
|
||||
return when (this) {
|
||||
is TransactionFee.Choosable -> {
|
||||
this.copy(
|
||||
minimum = this.minimum.increaseEthGasLimitInNeeded(increaseBy),
|
||||
normal = this.normal.increaseEthGasLimitInNeeded(increaseBy),
|
||||
priority = this.priority.increaseEthGasLimitInNeeded(increaseBy),
|
||||
)
|
||||
}
|
||||
is TransactionFee.Single -> this.copy(normal = this.normal.increaseEthGasLimitInNeeded(increaseBy))
|
||||
}
|
||||
}
|
||||
|
||||
private fun Fee.increaseEthGasLimitInNeeded(increaseBy: Int): Fee {
|
||||
return when (this) {
|
||||
is Fee.Ethereum.EIP1559,
|
||||
is Fee.Ethereum.Legacy,
|
||||
-> this.increaseGasLimitBy(increaseBy)
|
||||
is Fee.Alephium,
|
||||
is Fee.Aptos,
|
||||
is Fee.Bitcoin,
|
||||
is Fee.CardanoToken,
|
||||
is Fee.Common,
|
||||
is Fee.Filecoin,
|
||||
is Fee.Hedera,
|
||||
is Fee.Kaspa,
|
||||
is Fee.Sui,
|
||||
is Fee.Tron,
|
||||
is Fee.VeChain,
|
||||
-> this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase gasLimit for Fee.Ethereum
|
||||
*/
|
||||
private fun Fee.increaseGasLimitBy(percentage: Int): Fee {
|
||||
if (this !is Fee.Ethereum) return this
|
||||
|
|
@ -1973,13 +1624,13 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
return cryptoCurrencyStatuses.value.pendingTransactions.any { it.isOutgoing }
|
||||
}
|
||||
|
||||
private fun Fee.getGasLimit(): Int {
|
||||
private fun Fee.getGasLimit(): Int? {
|
||||
return when (this) {
|
||||
is Fee.Ethereum -> gasLimit.toInt()
|
||||
is Fee.VeChain -> gasLimit.toInt()
|
||||
is Fee.Aptos -> gasLimit.toInt()
|
||||
is Fee.Filecoin -> gasLimit.toInt()
|
||||
else -> 0
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2163,36 +1814,10 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getDemoFees(cryptoCurrency: CryptoCurrency): ProxyFees.MultipleFees {
|
||||
val demoFee = ProxyAmount(
|
||||
currencySymbol = cryptoCurrency.symbol,
|
||||
value = minDemoFee,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
)
|
||||
return ProxyFees.MultipleFees(
|
||||
minFee = ProxyFee.Common(
|
||||
gasLimit = 1.toBigInteger(),
|
||||
fee = demoFee,
|
||||
),
|
||||
normalFee = ProxyFee.Common(
|
||||
gasLimit = 1.toBigInteger(),
|
||||
fee = demoFee.copy(value = normalDemoFee),
|
||||
),
|
||||
priorityFee = ProxyFee.Common(
|
||||
gasLimit = 1.toBigInteger(),
|
||||
fee = demoFee.copy(value = priorityDemoFee),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val INCREASE_GAS_LIMIT_BY = 112 // 12%
|
||||
private const val INCREASE_GAS_LIMIT_FOR_DEX = 112 // 12%
|
||||
private const val INCREASE_GAS_LIMIT_FOR_SEND = 105 // 5%
|
||||
private const val INFINITY_SYMBOL = "∞"
|
||||
|
||||
private val minDemoFee = "0.0001".toBigDecimal()
|
||||
private val normalDemoFee = "0.0002".toBigDecimal()
|
||||
private val priorityDemoFee = "0.0003".toBigDecimal()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.feature.swap.domain.di
|
|||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
import com.tangem.feature.swap.domain.*
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -25,14 +24,6 @@ internal class SwapDomainModule {
|
|||
return factory
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBlockchainInteractor(transactionManager: TransactionManager): BlockchainInteractor {
|
||||
return DefaultBlockchainInteractor(
|
||||
transactionManager = transactionManager,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetCryptoCurrencyStatusUseCase(
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ dependencies {
|
|||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.promo)
|
||||
implementation(projects.domain.promo.models)
|
||||
implementation(projects.domain.txhistory)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
|
||||
/** Feature modules */
|
||||
implementation(projects.features.swap.domain)
|
||||
|
|
|
|||
|
|
@ -35,12 +35,12 @@ import com.tangem.domain.tokens.*
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.swap.analytics.StoriesEvents
|
||||
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.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.ExpressException
|
||||
|
|
@ -80,7 +80,6 @@ typealias SuccessLoadedSwapData = Map<SwapProvider, SwapState.QuotesLoadedState>
|
|||
internal class SwapModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val blockchainInteractor: BlockchainInteractor,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val analyticsErrorEventHandler: AnalyticsErrorHandler,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
|
|
@ -93,6 +92,7 @@ internal class SwapModel @Inject constructor(
|
|||
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase,
|
||||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||
private val shouldShowStoriesUseCase: ShouldShowStoriesUseCase,
|
||||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
|
|
@ -135,7 +135,7 @@ internal class SwapModel @Inject constructor(
|
|||
stateBuilder.createInitialLoadingState(
|
||||
initialCurrencyFrom = initialCurrencyFrom,
|
||||
initialCurrencyTo = initialCurrencyTo,
|
||||
fromNetworkInfo = blockchainInteractor.getBlockchainInfo(initialCurrencyFrom.network.backendId),
|
||||
fromNetworkInfo = initialCurrencyFrom.getNetworkInfo(),
|
||||
),
|
||||
)
|
||||
private set
|
||||
|
|
@ -261,9 +261,7 @@ internal class SwapModel @Inject constructor(
|
|||
uiState = stateBuilder.createInitialLoadingState(
|
||||
initialCurrencyFrom = initialCurrencyFrom,
|
||||
initialCurrencyTo = initialCurrencyTo,
|
||||
fromNetworkInfo = blockchainInteractor.getBlockchainInfo(
|
||||
initialCurrencyFrom.network.backendId,
|
||||
),
|
||||
fromNetworkInfo = initialCurrencyFrom.getNetworkInfo(),
|
||||
)
|
||||
initTokens(isReverseFromTo)
|
||||
}
|
||||
|
|
@ -603,10 +601,14 @@ internal class SwapModel @Inject constructor(
|
|||
when (it) {
|
||||
is SwapTransactionState.TxSent -> {
|
||||
sendSuccessSwapEvent(fromCurrency.currency, fee.feeType)
|
||||
val url = blockchainInteractor.getExplorerTransactionLink(
|
||||
networkId = fromCurrency.currency.network.backendId,
|
||||
val url = getExplorerTransactionUrlUseCase(
|
||||
txHash = it.txHash,
|
||||
)
|
||||
networkId = fromCurrency.currency.network.id,
|
||||
).getOrElse {
|
||||
Timber.i("tx hash explore not supported")
|
||||
""
|
||||
}
|
||||
|
||||
updateWalletBalance()
|
||||
uiState = stateBuilder.createSuccessState(
|
||||
uiState = uiState,
|
||||
|
|
@ -1356,6 +1358,13 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrency.getNetworkInfo(): NetworkInfo {
|
||||
return NetworkInfo(
|
||||
name = this.network.name,
|
||||
blockchainId = this.network.id.value,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val INITIAL_AMOUNT = ""
|
||||
const val UPDATE_DELAY = 10000L
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
package com.tangem.lib.crypto
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.smartcontract.SmartContractCallData
|
||||
import com.tangem.lib.crypto.models.*
|
||||
import java.math.BigInteger
|
||||
|
||||
interface TransactionManager {
|
||||
|
||||
/**
|
||||
* Get fee
|
||||
*
|
||||
* @param networkId network id of blockchain
|
||||
* @param amountToSend amount
|
||||
* @param currencyToSend currency to send in tx
|
||||
* @param destinationAddress address to send tx
|
||||
* @param increaseBy percents in format 125 = 25%
|
||||
* @param callData tx smart contract call data
|
||||
* @param derivationPath derivation path
|
||||
* @return
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun getFee(
|
||||
networkId: String,
|
||||
amountToSend: Amount,
|
||||
currencyToSend: Currency,
|
||||
destinationAddress: String,
|
||||
increaseBy: Int?,
|
||||
callData: SmartContractCallData?,
|
||||
derivationPath: String?,
|
||||
): ProxyFees
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun getFeeForGas(networkId: String, gas: BigInteger, derivationPath: String?): ProxyFees
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun updateWalletManager(networkId: String, derivationPath: String?)
|
||||
|
||||
/**
|
||||
* In app blockchain id, actual in blockchain sdk, not the same as networkId
|
||||
*
|
||||
* workaround till not use backend only and not integrated server vs sdk
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
fun getBlockchainInfo(networkId: String): ProxyNetworkInfo
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
fun getExplorerTransactionLink(networkId: String, txAddress: String): String
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
sealed interface ProxyFee {
|
||||
|
||||
val gasLimit: BigInteger
|
||||
val fee: ProxyAmount
|
||||
|
||||
data class Common(
|
||||
override val gasLimit: BigInteger,
|
||||
override val fee: ProxyAmount,
|
||||
) : ProxyFee
|
||||
|
||||
data class CardanoToken(
|
||||
override val gasLimit: BigInteger,
|
||||
override val fee: ProxyAmount,
|
||||
val minAdaValue: BigDecimal,
|
||||
) : ProxyFee
|
||||
|
||||
data class Filecoin(
|
||||
override val gasLimit: BigInteger,
|
||||
override val fee: ProxyAmount,
|
||||
val gasPremium: Long,
|
||||
) : ProxyFee
|
||||
|
||||
data class Sui(
|
||||
override val gasLimit: BigInteger,
|
||||
override val fee: ProxyAmount,
|
||||
val gasPrice: Long,
|
||||
val gasBudget: Long,
|
||||
) : ProxyFee
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
sealed class ProxyFees {
|
||||
|
||||
data class MultipleFees(
|
||||
val minFee: ProxyFee,
|
||||
val normalFee: ProxyFee,
|
||||
val priorityFee: ProxyFee,
|
||||
) : ProxyFees()
|
||||
|
||||
data class SingleFee(
|
||||
val singleFee: ProxyFee,
|
||||
) : ProxyFees()
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
data class ProxyNetworkInfo(
|
||||
val name: String,
|
||||
val blockchainId: String,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue