diff --git a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt index 9870501655..ae906c4a76 100644 --- a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt +++ b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt @@ -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" } } diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index 3e76a02638..45734e3796 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -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 { diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt deleted file mode 100644 index 396fc681f2..0000000000 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ /dev/null @@ -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") - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt index e9bcad2a6b..4d59c1f56e 100644 --- a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt +++ b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt @@ -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, - ) - } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt index 56acd23a53..df1e2e84bd 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt @@ -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? diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index ba14ca68b2..765662941b 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -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, diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionDataExtrasUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionDataExtrasUseCase.kt index c63384ba5e..b6d704cbbc 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionDataExtrasUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionDataExtrasUseCase.kt @@ -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, + ) + }, + ) { + raise(it) + } + } + + operator fun invoke( + callData: SmartContractCallData, + network: Network, + gasLimit: BigInteger? = null, + nonce: BigInteger? = null, + ) = either { + catch( + { transactionRepository.createTransactionDataExtras( - callData = CompiledSmartContractCallData(data.hexToBytes()), + callData = callData, network = network, nonce = nonce, gasLimit = gasLimit, - ), - ) { "Failed to create transaction" } + ) + }, + ) { + raise(it) } + } } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetEthSpecificFeeUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetEthSpecificFeeUseCase.kt new file mode 100644 index 0000000000..d688ccbd9b --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetEthSpecificFeeUseCase.kt @@ -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% + } +} \ No newline at end of file diff --git a/features/swap/domain/models/build.gradle.kts b/features/swap/domain/models/build.gradle.kts index 6ebbca292e..e4496dc053 100644 --- a/features/swap/domain/models/build.gradle.kts +++ b/features/swap/domain/models/build.gradle.kts @@ -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) diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt index f6aa62d8cd..be89782000 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt @@ -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 diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/BlockchainInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/BlockchainInteractor.kt deleted file mode 100644 index ceab0eb503..0000000000 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/BlockchainInteractor.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/DefaultBlockchainInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/DefaultBlockchainInteractor.kt deleted file mode 100644 index 284b1572cb..0000000000 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/DefaultBlockchainInteractor.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 34954d7dc3..6d119df85d 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -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, - 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) - } + val txFeeState = getFeeDataForDexSwap( + network = fromToken.currency.network, + transaction = transaction, + fromToken = fromToken.currency, + ) + .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,35 +1385,42 @@ 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, - callData = callData, - derivationPath = derivationPath, - ) - } catch (e: Exception) { - Timber.e(e, "Failed to get fee") - // it's impossible next steps without fee - return createSwapErrorWith( - fromToken = fromTokenStatus, - amount = swapAmount, - includeFeeInAmount = IncludeFeeInAmount.Excluded, - expressDataError = ExpressDataError.UnknownError, - ) - } - } - val feeState = when (feeData) { - is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken) - is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken) + val feeData = try { + val extras = createTransactionExtrasUseCase( + callData = callData, + 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 + return createSwapErrorWith( + fromToken = fromTokenStatus, + amount = swapAmount, + includeFeeInAmount = IncludeFeeInAmount.Excluded, + expressDataError = ExpressDataError.UnknownError, + ) } + + 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 diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index 13135efa71..610aa556a8 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -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( diff --git a/features/swap/impl/build.gradle.kts b/features/swap/impl/build.gradle.kts index a07e4897ad..74a34ecd45 100644 --- a/features/swap/impl/build.gradle.kts +++ b/features/swap/impl/build.gradle.kts @@ -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) diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index ea78698ede..38779307ed 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -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 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 diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt deleted file mode 100644 index baed3f3725..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFee.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFee.kt deleted file mode 100644 index 6cb324fcef..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFee.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFees.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFees.kt deleted file mode 100644 index b8b3dfa295..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFees.kt +++ /dev/null @@ -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() -} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyNetworkInfo.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyNetworkInfo.kt deleted file mode 100644 index dcbade5046..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyNetworkInfo.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.lib.crypto.models - -data class ProxyNetworkInfo( - val name: String, - val blockchainId: String, -) \ No newline at end of file