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 55d35c9244..3e76a02638 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,15 @@ internal object TransactionDomainModule { ) } + @Provides + @Singleton + fun provideTransferGetFeeUseCase(walletManagersFacade: WalletManagersFacade): GetTransferFeeUseCase { + return GetTransferFeeUseCase( + walletManagersFacade = walletManagersFacade, + demoConfig = DemoConfig(), + ) + } + @Provides @Singleton fun provideSendTransactionUseCase( @@ -148,6 +157,14 @@ internal object TransactionDomainModule { return GetAllowanceUseCase(transactionRepository) } + @Provides + @Singleton + fun provideCreateTransferTransactionUseCase( + transactionRepository: TransactionRepository, + ): CreateTransferTransactionUseCase { + return CreateTransferTransactionUseCase(transactionRepository) + } + @Provides @Singleton fun providePrepareForSendUseCase( diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index cc73660628..ea5b87285c 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -5,6 +5,7 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumUtils import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.smartcontract.CompiledSmartContractCallData import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.extensions.* import com.tangem.blockchainsdk.utils.fromNetworkId @@ -101,7 +102,7 @@ class WalletConnectSdkHelper { sourceAddress = transaction.from, destinationAddress = destinationAddress, extras = EthereumTransactionExtras( - data = transaction.data.removePrefix(HEX_PREFIX).hexToBytes(), + callData = CompiledSmartContractCallData(transaction.data.removePrefix(HEX_PREFIX).hexToBytes()), gasLimit = gasLimit.toBigInteger(), nonce = transaction.nonce?.hexToBigDecimal()?.toBigInteger(), ), @@ -191,7 +192,7 @@ class WalletConnectSdkHelper { val gasLimitResult = (walletManager as? EthereumGasLoader)?.getGasLimit( amount = Amount(value, walletManager.wallet.blockchain), destination = transaction.to ?: "", - data = transaction.data, + callData = CompiledSmartContractCallData(transaction.data.hexToBytes()), ) return when (gasLimitResult) { is Result.Success -> gasLimitResult.data.toBigDecimal().multiply(BigDecimal("1.2")) diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index 621ce6f4e9..396fc681f2 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -3,6 +3,7 @@ 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 @@ -43,7 +44,7 @@ class TransactionManagerImpl( currencyToSend: Currency, destinationAddress: String, increaseBy: Int?, - data: String?, + callData: SmartContractCallData?, derivationPath: String?, ): ProxyFees { val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } @@ -54,7 +55,7 @@ class TransactionManagerImpl( walletManager = walletManager, amount = amountToSend, destinationAddress = destinationAddress, - data = data, + callData = callData, ) } return getFeeForEthereumBlockchain( @@ -62,7 +63,7 @@ class TransactionManagerImpl( blockchain = blockchain, amountToSend = amountToSend, destinationAddress = destinationAddress, - data = data, + callData = callData, increaseBy = increaseBy, ) } else { @@ -160,14 +161,14 @@ class TransactionManagerImpl( blockchain: Blockchain, amountToSend: Amount, destinationAddress: String, - data: String?, + callData: SmartContractCallData?, increaseBy: Int?, ): ProxyFees { val gasLimit = getGasLimit( evmWalletManager = walletManager, amount = amountToSend, destinationAddress = destinationAddress, - data = data, + callData = callData, ).increaseBigIntegerByPercents(increaseBy) return when (val gasPrice = walletManager.getGasPrice()) { is Result.Success -> { @@ -183,16 +184,16 @@ class TransactionManagerImpl( walletManager: EthereumOptimisticRollupWalletManager, amount: Amount, destinationAddress: String, - data: String?, + callData: SmartContractCallData?, ): ProxyFees { - val fee = if (data.isNullOrEmpty()) { + val fee = if (callData == null) { walletManager.getFee(amount, destinationAddress) } else { - walletManager.getFee(amount, destinationAddress, data) + walletManager.getFee(amount, destinationAddress, callData) } return when (fee) { is Result.Success -> { - val choosableFee = fee.data + val choosableFee = fee.data as? TransactionFee.Choosable ?: error("Incorrect fee type") val minProxyFee = ProxyFee.Common( gasLimit = (choosableFee.minimum as Fee.Ethereum).gasLimit, @@ -223,9 +224,9 @@ class TransactionManagerImpl( evmWalletManager: EthereumWalletManager, amount: Amount, destinationAddress: String, - data: String?, + callData: SmartContractCallData?, ): BigInteger { - val result = if (data.isNullOrEmpty()) { + val result = if (callData == null) { evmWalletManager.getGasLimit( amount = amount, destination = destinationAddress, @@ -234,7 +235,7 @@ class TransactionManagerImpl( evmWalletManager.getGasLimit( amount = amount, destination = destinationAddress, - data = data, + callData = callData, ) } when (result) { diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index 6d3ddca431..da6951cafd 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -14,14 +14,14 @@ import com.tangem.blockchain.blockchains.ton.TonTransactionExtras import com.tangem.blockchain.blockchains.tron.TronTransactionExtras import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.smartcontract.SmartContractCallData +import com.tangem.blockchain.common.smartcontract.SmartContractCallDataProviderFactory import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.common.extensions.hexToBytes import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.transaction.TransactionRepository -import com.tangem.domain.transaction.models.TransactionType import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -29,7 +29,6 @@ import kotlinx.coroutines.withContext import timber.log.Timber import java.math.BigDecimal import java.math.BigInteger -import com.tangem.blockchain.blockchains.tron.TransactionType as SdkTransactionType internal class DefaultTransactionRepository( private val walletManagersFacade: WalletManagersFacade, @@ -45,7 +44,6 @@ internal class DefaultTransactionRepository( userWalletId: UserWalletId, network: Network, txExtras: TransactionExtras?, - hash: String?, ): TransactionData.Uncompiled = withContext(coroutineDispatcherProvider.io) { val blockchain = Blockchain.fromId(network.id.value) val walletManager = walletManagersFacade.getOrCreateWalletManager( @@ -54,14 +52,50 @@ internal class DefaultTransactionRepository( derivationPath = network.derivationPath.value, ) ?: error("Wallet manager not found") - return@withContext walletManager.createTransactionDataInternal( + return@withContext walletManager.createTransaction( amount = amount, fee = fee, - memo = memo, destination = destination, + ).copy( + extras = txExtras ?: getMemoExtras(networkId = network.id.value, memo), + ) + } + + override suspend fun createTransferTransaction( + amount: Amount, + fee: Fee, + memo: String?, + destination: String, + userWalletId: UserWalletId, + network: Network, + ): TransactionData.Uncompiled = withContext(coroutineDispatcherProvider.io) { + val blockchain = Blockchain.fromId(network.id.value) + + val callData = SmartContractCallDataProviderFactory.getTokenTransferCallData( + destinationAddress = destination, + amount = amount, + blockchain = blockchain, + ) + + val extras = if (amount.type is AmountType.Token && callData != null) { + createTransactionDataExtras( + callData = callData, + network = network, + nonce = null, + gasLimit = null, + ) + } else { + null + } + + return@withContext createTransaction( + amount = amount, + fee = fee, + memo = null, + destination = destination, + userWalletId = userWalletId, network = network, - txExtras = txExtras, - hash = hash, + txExtras = getMemoExtras(networkId = network.id.value, memo = memo) ?: extras, ) } @@ -73,25 +107,16 @@ internal class DefaultTransactionRepository( spenderAddress: String, userWalletId: UserWalletId, network: Network, - hash: String?, ): TransactionData.Uncompiled = withContext(coroutineDispatcherProvider.io) { val blockchain = Blockchain.fromId(network.id.value) - val walletManager = walletManagersFacade.getOrCreateWalletManager( - userWalletId = userWalletId, - blockchain = blockchain, - derivationPath = network.derivationPath.value, - ) ?: error("Wallet manager not found") - val approver = walletManager as? Approver ?: error("Cannot cast to Approver") - - val approvalData = approver.getApproveData( - spenderAddress = spenderAddress, - value = approvalAmount, - ) val extras = createTransactionDataExtras( - data = approvalData, + callData = SmartContractCallDataProviderFactory.getApprovalCallData( + spenderAddress = spenderAddress, + amount = approvalAmount, + blockchain = blockchain, + ), network = network, - transactionType = TransactionType.APPROVE, nonce = null, gasLimit = null, ) @@ -104,7 +129,6 @@ internal class DefaultTransactionRepository( userWalletId = userWalletId, network = network, txExtras = extras, - hash = hash, ) } @@ -115,9 +139,6 @@ internal class DefaultTransactionRepository( destination: String, userWalletId: UserWalletId, network: Network, - isSwap: Boolean, - txExtras: TransactionExtras?, - hash: String?, ): Result = withContext(coroutineDispatcherProvider.io) { val blockchain = Blockchain.fromId(network.id.value) val walletManager = walletManagersStore.getSyncOrNull( @@ -129,14 +150,12 @@ internal class DefaultTransactionRepository( val validator = walletManager as? TransactionValidator if (validator != null) { - val transactionData = walletManager.createTransactionDataInternal( + val transactionData = walletManager.createTransaction( amount = amount, fee = fee ?: Fee.Common(amount = amount), - memo = memo, destination = destination, - network = network, - txExtras = txExtras, - hash = hash, + ).copy( + extras = getMemoExtras(networkId = network.id.value, memo = memo), ) validator.validate(transactionData = transactionData) @@ -178,9 +197,8 @@ internal class DefaultTransactionRepository( } override fun createTransactionDataExtras( - data: String, + callData: SmartContractCallData, network: Network, - transactionType: TransactionType, nonce: BigInteger?, gasLimit: BigInteger?, ): TransactionExtras { @@ -189,15 +207,14 @@ internal class DefaultTransactionRepository( return when { blockchain.isEvm() -> { EthereumTransactionExtras( - data = data.hexToBytes(), + callData = callData, gasLimit = gasLimit, nonce = nonce, ) } blockchain == Blockchain.Tron -> { TronTransactionExtras( - data = data.hexToBytes(), - txType = convertToSdkTransactionType(transactionType), + callData = callData, ) } else -> error("Data extras not supported for $blockchain") @@ -226,33 +243,6 @@ internal class DefaultTransactionRepository( ) } - private fun convertToSdkTransactionType(transactionType: TransactionType): SdkTransactionType { - return when (transactionType) { - TransactionType.APPROVE -> SdkTransactionType.APPROVE - } - } - - @Suppress("LongParameterList") - private fun WalletManager.createTransactionDataInternal( - amount: Amount, - fee: Fee, - memo: String?, - destination: String, - network: Network, - txExtras: TransactionExtras?, - hash: String?, - ): TransactionData.Uncompiled { - if (txExtras != null && memo != null) { - // throw error for now to avoid programmers errors when use extras - error("Both txExtras and memo provided, use only one of them") - } - val extras = txExtras ?: getMemoExtras(network.id.value, memo) - return createTransaction(amount, fee, destination).copy( - hash = hash, - extras = extras, - ) - } - @Suppress("CyclomaticComplexMethod") private fun getMemoExtras(networkId: String, memo: String?): TransactionExtras? { val blockchain = Blockchain.fromId(networkId) diff --git a/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt b/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt index 5c667f1f53..8ed8931539 100644 --- a/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt +++ b/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt @@ -1,6 +1,7 @@ package com.tangem.domain.demo 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.common.transaction.TransactionSendResult @@ -24,8 +25,12 @@ class DemoTransactionSender(private val walletManager: WalletManager) : Transact ) } - override suspend fun estimateFee(amount: Amount, destination: String): Result { - return getFee(amount, walletManager.wallet.address) + override suspend fun estimateFee( + amount: Amount, + destination: String, + callData: SmartContractCallData?, + ): Result { + return getFee(amount, walletManager.wallet.address, callData) } override suspend fun send( 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 d15b9f68bd..1e16b212a1 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 @@ -10,6 +10,7 @@ import com.tangem.blockchain.common.address.Address import com.tangem.blockchain.common.address.AddressType import com.tangem.blockchain.common.address.EstimationFeeAddressFactory import com.tangem.blockchain.common.pagination.Page +import com.tangem.blockchain.common.smartcontract.SmartContractCallDataProviderFactory import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.trustlines.AssetRequirementsManager @@ -488,9 +489,20 @@ class DefaultWalletManagersFacade( val destination = estimationFeeAddressFactory.makeAddress(blockchain) + val callData = if (amount.type is AmountType.Token) { + SmartContractCallDataProviderFactory.getTokenTransferCallData( + destinationAddress = destination, + amount = amount, + blockchain = blockchain, + ) + } else { + null + } + (walletManager as? TransactionSender)?.estimateFee( amount = amount, destination = destination, + callData = callData, ) } diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt index 892fa06f78..9ef573a666 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt @@ -1,12 +1,12 @@ package com.tangem.domain.transaction 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.TransactionSendResult import com.tangem.blockchain.common.transaction.TransactionsSendResult import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network -import com.tangem.domain.transaction.models.TransactionType import com.tangem.domain.wallets.models.UserWalletId import java.math.BigDecimal import java.math.BigInteger @@ -22,7 +22,16 @@ interface TransactionRepository { userWalletId: UserWalletId, network: Network, txExtras: TransactionExtras?, - hash: String?, + ): TransactionData.Uncompiled + + @Suppress("LongParameterList") + suspend fun createTransferTransaction( + amount: Amount, + fee: Fee, + memo: String?, + destination: String, + userWalletId: UserWalletId, + network: Network, ): TransactionData.Uncompiled @Suppress("LongParameterList") @@ -34,7 +43,6 @@ interface TransactionRepository { spenderAddress: String, userWalletId: UserWalletId, network: Network, - hash: String?, ): TransactionData.Uncompiled @Suppress("LongParameterList") @@ -45,9 +53,6 @@ interface TransactionRepository { destination: String, userWalletId: UserWalletId, network: Network, - isSwap: Boolean = false, - txExtras: TransactionExtras?, - hash: String? = null, ): Result suspend fun sendTransaction( @@ -66,9 +71,8 @@ interface TransactionRepository { ): com.tangem.blockchain.extensions.Result fun createTransactionDataExtras( - data: String, + callData: SmartContractCallData, network: Network, - transactionType: TransactionType, nonce: BigInteger?, gasLimit: BigInteger?, ): TransactionExtras diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateApprovalTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateApprovalTransactionUseCase.kt index 65d69a9d0a..d3b136ff3e 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateApprovalTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateApprovalTransactionUseCase.kt @@ -8,6 +8,9 @@ import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.wallets.models.UserWalletId import java.math.BigDecimal +/** + * Use case to create and get approval transaction + */ class CreateApprovalTransactionUseCase( private val transactionRepository: TransactionRepository, ) { @@ -20,7 +23,6 @@ class CreateApprovalTransactionUseCase( fee: Fee, contractAddress: String, spenderAddress: String, - hash: String? = null, ) = Either.catch { transactionRepository.createApprovalTransaction( amount = BigDecimal.ZERO.convertToSdkAmount(cryptoCurrency), @@ -30,7 +32,6 @@ class CreateApprovalTransactionUseCase( userWalletId = userWalletId, network = cryptoCurrency.network, fee = fee, - hash = hash, ) } } \ No newline at end of file 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 52d686e403..c63384ba5e 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,30 +1,25 @@ package com.tangem.domain.transaction.usecase import arrow.core.Either +import com.tangem.blockchain.common.smartcontract.CompiledSmartContractCallData +import com.tangem.common.extensions.hexToBytes import com.tangem.domain.tokens.model.Network import com.tangem.domain.transaction.TransactionRepository -import com.tangem.domain.transaction.models.TransactionType import java.math.BigInteger class CreateTransactionDataExtrasUseCase( private val transactionRepository: TransactionRepository, ) { - operator fun invoke( - data: String, - network: Network, - transactionType: TransactionType, - gasLimit: BigInteger? = null, - nonce: BigInteger? = null, - ) = Either.catch { - requireNotNull( - transactionRepository.createTransactionDataExtras( - data = data, - network = network, - transactionType = transactionType, - nonce = nonce, - gasLimit = gasLimit, - ), - ) { "Failed to create transaction" } - } + operator fun invoke(data: String, network: Network, gasLimit: BigInteger? = null, nonce: BigInteger? = null) = + Either.catch { + requireNotNull( + transactionRepository.createTransactionDataExtras( + callData = CompiledSmartContractCallData(data.hexToBytes()), + network = network, + nonce = nonce, + gasLimit = gasLimit, + ), + ) { "Failed to create transaction" } + } } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt index 87f10ea630..a9d080eb2e 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransactionUseCase.kt @@ -8,6 +8,12 @@ import com.tangem.domain.tokens.model.Network import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.wallets.models.UserWalletId +/** + * Use case to create and get transaction + * + * !!!IMPORTANT + * Use when transaction data is already compiled by external service or provider + */ class CreateTransactionUseCase( private val transactionRepository: TransactionRepository, ) { @@ -24,7 +30,6 @@ class CreateTransactionUseCase( userWalletId: UserWalletId, network: Network, txExtras: TransactionExtras? = null, - hash: String? = null, ) = Either.catch { transactionRepository.createTransaction( amount = amount, @@ -34,7 +39,6 @@ class CreateTransactionUseCase( userWalletId = userWalletId, network = network, txExtras = txExtras, - hash = hash, ) } } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransferTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransferTransactionUseCase.kt new file mode 100644 index 0000000000..64457a93b8 --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateTransferTransactionUseCase.kt @@ -0,0 +1,41 @@ +package com.tangem.domain.transaction.usecase + +import arrow.core.Either +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.transaction.TransactionRepository +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Use case to create and get transfer transaction + * + * !!!IMPORTANT + * Use when transaction data is compiled by us using BlockchainSDK methods + */ +class CreateTransferTransactionUseCase( + private val transactionRepository: TransactionRepository, +) { + + /** + * [REDACTED_TODO_COMMENT] + */ + @Suppress("LongParameterList") + suspend operator fun invoke( + amount: Amount, + fee: Fee, + memo: String?, + destination: String, + userWalletId: UserWalletId, + network: Network, + ) = Either.catch { + transactionRepository.createTransferTransaction( + amount = amount, + fee = fee, + memo = memo, + destination = destination, + userWalletId = userWalletId, + network = network, + ) + } +} \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt index 548a43ab6c..c370ac644d 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt @@ -17,6 +17,9 @@ import java.math.BigDecimal /** * Use case to get transaction fee + * + * !!!IMPORTANT!!! + * Use when transaction data is already compiled by external service or provider */ class GetFeeUseCase( private val walletManagersFacade: WalletManagersFacade, diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetTransferFeeUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetTransferFeeUseCase.kt new file mode 100644 index 0000000000..0a1582967d --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetTransferFeeUseCase.kt @@ -0,0 +1,102 @@ +package com.tangem.domain.transaction.usecase + +import arrow.core.raise.catch +import arrow.core.raise.either +import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.smartcontract.SmartContractCallDataProviderFactory +import com.tangem.blockchain.extensions.Result +import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.demo.DemoTransactionSender +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.transaction.error.GetFeeError +import com.tangem.domain.transaction.error.mapToFeeError +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.UserWallet +import java.math.BigDecimal + +/** + * Use case to get transfer transaction fee + * + * !!!IMPORTANT + * Use when transaction data is compiled by us using BlockchainSDK methods + */ +class GetTransferFeeUseCase( + private val walletManagersFacade: WalletManagersFacade, + private val demoConfig: DemoConfig, +) { + suspend operator fun invoke( + amount: BigDecimal, + destination: String, + userWallet: UserWallet, + cryptoCurrency: CryptoCurrency, + ) = either { + catch( + block = { + val amountData = convertCryptoCurrencyToAmount(cryptoCurrency, amount) + + val result = if (demoConfig.isDemoCardId(userWallet.scanResponse.card.cardId)) { + demoTransactionSender(userWallet, cryptoCurrency).getFee( + amount = amountData, + destination = destination, + ) + } else { + val walletManager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWallet.walletId, + network = cryptoCurrency.network, + ) + val smartContractCallData = if (amountData.type is AmountType.Token) { + SmartContractCallDataProviderFactory.getTokenTransferCallData( + amount = amountData, + destinationAddress = destination, + blockchain = Blockchain.fromId(cryptoCurrency.network.id.value), + ) + } else { + null + } + + (walletManager as? TransactionSender)?.getFee( + amount = amountData, + destination = destination, + callData = smartContractCallData, + ) ?: error("Fee is null") + } + + val maybeFee = when (result) { + is Result.Success -> result.data + is Result.Failure -> raise(result.mapToFeeError()) + } + maybeFee + }, + catch = { + raise(GetFeeError.DataError(it)) + }, + ) + } + + private suspend fun demoTransactionSender( + userWallet: UserWallet, + cryptoCurrency: CryptoCurrency, + ): DemoTransactionSender { + return DemoTransactionSender( + walletManagersFacade + .getOrCreateWalletManager(userWallet.walletId, cryptoCurrency.network) + ?: error("WalletManager is null"), + ) + } + + private fun convertCryptoCurrencyToAmount(cryptoCurrency: CryptoCurrency, amount: BigDecimal) = Amount( + currencySymbol = cryptoCurrency.symbol, + value = amount, + decimals = cryptoCurrency.decimals, + type = when (cryptoCurrency) { + is CryptoCurrency.Coin -> AmountType.Coin + is CryptoCurrency.Token -> AmountType.Token( + token = Token( + symbol = cryptoCurrency.symbol, + contractAddress = cryptoCurrency.contractAddress, + decimals = cryptoCurrency.decimals, + ), + ) + }, + ) +} \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/ValidateTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/ValidateTransactionUseCase.kt index 61b2f26817..11b429be0f 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/ValidateTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/ValidateTransactionUseCase.kt @@ -21,8 +21,6 @@ class ValidateTransactionUseCase( destination: String, userWalletId: UserWalletId, network: Network, - isSwap: Boolean = false, - hash: String? = null, ): Either { return transactionRepository.validateTransaction( amount = amount, @@ -31,10 +29,6 @@ class ValidateTransactionUseCase( destination = destination, userWalletId = userWalletId, network = network, - isSwap = isSwap, - txExtras = null, - hash = hash, - ) - .fold(onSuccess = { Unit.right() }, onFailure = { it.left() }) + ).fold(onSuccess = { Unit.right() }, onFailure = { it.left() }) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt index 9f4e696e39..fbcd15cc1f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/model/SendModel.kt @@ -92,9 +92,9 @@ internal class SendModel @Inject constructor( private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, private val getTxHistoryItemsUseCase: GetTxHistoryItemsUseCase, private val getFixedTxHistoryItemsUseCase: GetFixedTxHistoryItemsUseCase, - private val getFeeUseCase: GetFeeUseCase, + private val getTransferFeeUseCase: GetTransferFeeUseCase, private val sendTransactionUseCase: SendTransactionUseCase, - private val createTransactionUseCase: CreateTransactionUseCase, + private val createTransferTransactionUseCase: CreateTransferTransactionUseCase, private val validateWalletAddressUseCase: ValidateWalletAddressUseCase, private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, @@ -826,7 +826,7 @@ internal class SendModel @Inject constructor( val recipientState = uiState.value.getRecipientState(isFromConfirmation) ?: return null val amount = amountState.amountTextField.cryptoAmount.value ?: return null - return getFeeUseCase.invoke( + return getTransferFeeUseCase.invoke( amount = amount, destination = recipientState.addressTextField.value, userWallet = userWallet, @@ -929,7 +929,7 @@ internal class SendModel @Inject constructor( ) modelScope.launch { - createTransactionUseCase( + createTransferTransactionUseCase( amount = receivingAmount.convertToSdkAmount(cryptoCurrency), fee = fee, memo = memo, diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt index 8e465b9c2b..f872cd9c2c 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt @@ -344,47 +344,6 @@ internal class DefaultSwapRepository( ) } - override suspend fun getApproveData( - userWalletId: UserWalletId, - networkId: String, - derivationPath: String?, - currency: CryptoCurrency, - amount: BigDecimal?, - spenderAddress: String, - ): String { - val blockchain = - requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } - val walletManager = walletManagersFacade.getOrCreateWalletManager( - userWalletId = userWalletId, - blockchain = blockchain, - derivationPath = derivationPath, - ) - - return (walletManager as? Approver)?.getApproveData( - spenderAddress, - amount?.let { convertToAmount(it, currency) }, - ) ?: error("Cannot cast to Approver") - } - - private fun convertToAmount(amount: BigDecimal, currency: CryptoCurrency): Amount { - return Amount( - currencySymbol = currency.symbol, - value = amount, - decimals = currency.decimals, - type = if (currency is CryptoCurrency.Token) { - AmountType.Token( - Token( - symbol = currency.symbol, - contractAddress = currency.contractAddress, - decimals = currency.decimals, - ), - ) - } else { - AmountType.Coin - }, - ) - } - override fun getNativeTokenForNetwork(networkId: String): CryptoCurrency { val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } diff --git a/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt b/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt index 46ebd1d768..bacf1b8df5 100644 --- a/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt +++ b/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt @@ -41,17 +41,6 @@ interface SwapRepository { spenderAddress: String, ): BigDecimal - @Suppress("LongParameterList") - @Throws(IllegalStateException::class) - suspend fun getApproveData( - userWalletId: UserWalletId, - networkId: String, - derivationPath: String?, - currency: CryptoCurrency, - amount: BigDecimal?, - spenderAddress: String, - ): String - @Suppress("LongParameterList") suspend fun getExchangeData( fromContractAddress: String, 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 a4336b8eae..f6aa62d8cd 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 @@ -82,7 +82,6 @@ data class TokenSwapInfo( data class RequestApproveStateData( val fee: TxFeeState, - val approveData: String, val fromTokenAmount: SwapAmount, val spenderAddress: String, ) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 041739a131..d5b93c4276 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 @@ -7,10 +7,14 @@ import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain.* 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.core.ui.utils.BigDecimalFormatter +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 import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.appcurrency.repository.AppCurrencyRepository @@ -25,7 +29,6 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.transaction.error.GetFeeError -import com.tangem.domain.transaction.models.TransactionType import com.tangem.domain.transaction.usecase.* import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.wallets.models.UserWallet @@ -61,7 +64,9 @@ internal class SwapInteractorImpl @AssistedInject constructor( private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase, private val sendTransactionUseCase: SendTransactionUseCase, private val createTransactionUseCase: CreateTransactionUseCase, + private val createTransferTransactionUseCase: CreateTransferTransactionUseCase, private val createTransactionExtrasUseCase: CreateTransactionDataExtrasUseCase, + private val createApprovalTransactionUseCase: CreateApprovalTransactionUseCase, private val isDemoCardUseCase: IsDemoCardUseCase, private val quotesRepository: QuotesRepository, private val quotesRepositoryV2: QuotesRepositoryV2, @@ -202,32 +207,20 @@ internal class SwapInteractorImpl @AssistedInject constructor( networkId: String, permissionOptions: PermissionOptions, ): SwapTransactionState { - val derivationPath = permissionOptions.fromToken.network.derivationPath.value - val dataToSign = if (permissionOptions.approveType == SwapApproveType.UNLIMITED) { - getApproveData( - networkId = networkId, - derivationPath = derivationPath, - fromToken = permissionOptions.fromToken, - spenderAddress = permissionOptions.spenderAddress, - ) - } else { - permissionOptions.approveData.approveData + val amount = permissionOptions.approveData.fromTokenAmount.takeIf { + permissionOptions.approveType == SwapApproveType.LIMITED } - val approveTransaction = createTransactionUseCase( - amount = BigDecimal.ZERO.convertToSdkAmount(permissionOptions.fromToken), + + val approveTransaction = createApprovalTransactionUseCase( fee = getFeeForTransaction( fee = permissionOptions.txFee, blockchain = Blockchain.fromId(permissionOptions.fromToken.network.id.value), ), - memo = null, - destination = getTokenAddress(permissionOptions.fromToken), - network = permissionOptions.fromToken.network, userWalletId = userWalletId, - txExtras = createDexTxExtras( - dataToSign, - permissionOptions.fromToken.network, - permissionOptions.txFee.gasLimit, - ), + cryptoCurrency = permissionOptions.fromToken as CryptoCurrency.Token, + amount = amount?.value, + contractAddress = permissionOptions.forTokenContractAddress, + spenderAddress = permissionOptions.spenderAddress, ).getOrElse { Timber.e(it, "Failed to create approveTransaction") return SwapTransactionState.Error.UnknownError @@ -613,7 +606,6 @@ internal class SwapInteractorImpl @AssistedInject constructor( userWalletId = userWalletId, network = currencyToSendStatus.currency.network, txExtras = createDexTxExtras(dataToSign, currencyToSendStatus.currency.network, fee.gasLimit), - hash = dataToSign, ).getOrElse { Timber.e(it, "Failed to create swap dex tx data") return SwapTransactionState.Error.UnknownError @@ -669,7 +661,6 @@ internal class SwapInteractorImpl @AssistedInject constructor( return createTransactionExtrasUseCase( data = data, network = network, - transactionType = TransactionType.APPROVE, gasLimit = gasLimit?.toBigInteger(), ).getOrNull() ?: error("failed to create extras") } @@ -705,7 +696,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( if (isDemoCardUseCase(cardId)) return SwapTransactionState.Error.UnknownError - val txData = createTransactionUseCase( + val txData = createTransferTransactionUseCase( amount = amount.value.convertToSdkAmount(currencyToSend.currency), fee = getFeeForTransaction( fee = txFee, @@ -1087,11 +1078,12 @@ internal class SwapInteractorImpl @AssistedInject constructor( private suspend fun createEmptyAmountState(): SwapState { val appCurrency = getSelectedAppCurrencyUseCase.unwrap() return SwapState.EmptyAmountState( - zeroAmountEquivalent = BigDecimalFormatter.formatFiatAmount( - fiatAmount = BigDecimal.ZERO, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ), + zeroAmountEquivalent = BigDecimal.ZERO.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + }, ) } @@ -1373,11 +1365,12 @@ internal class SwapInteractorImpl @AssistedInject constructor( val rates = getQuotes(feeCurrencyId) return rates[feeCurrencyId]?.fiatRate?.let { rate -> fees.map { fee -> - BigDecimalFormatter.formatFiatAmount( - fiatAmount = rate.multiply(fee), - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ) + rate.multiply(fee).format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + } } }.orEmpty() } @@ -1513,7 +1506,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( currencyToSend = swapCurrencyConverter.convert(fromToken), destinationAddress = transaction.txTo, increaseBy = INCREASE_GAS_LIMIT_BY, - data = transaction.txData, + callData = CompiledSmartContractCallData(transaction.txData.hexToBytes()), derivationPath = fromToken.network.derivationPath.value, ) } catch (e: IllegalStateException) { @@ -1617,12 +1610,10 @@ internal class SwapInteractorImpl @AssistedInject constructor( } val derivationPath = fromToken.network.derivationPath.value // setting up amount for approve with given amount for swap [SwapApproveType.Limited] - val transactionData = getApproveData( - networkId = networkId, - derivationPath = derivationPath, - fromToken = fromToken, - swapAmount = swapAmount, + val callData = SmartContractCallDataProviderFactory.getApprovalCallData( spenderAddress = requireNotNull(spenderAddress) { "Spender address is null" }, + amount = swapAmount.value.convertToSdkAmount(fromToken), + blockchain = Blockchain.fromId(fromToken.network.id.value), ) val cardId = userWallet.scanResponse.card.cardId val feeData = if (isDemoCardUseCase(cardId)) { @@ -1635,7 +1626,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( currencyToSend = swapCurrencyConverter.convert(repository.getNativeTokenForNetwork(networkId)), destinationAddress = fromToken.getContractAddress(), increaseBy = INCREASE_GAS_LIMIT_BY, - data = transactionData, + callData = callData, derivationPath = derivationPath, ) } catch (e: Exception) { @@ -1672,7 +1663,6 @@ internal class SwapInteractorImpl @AssistedInject constructor( spenderAddress = getTokenAddress(fromToken), requestApproveData = RequestApproveStateData( fee = feeState, - approveData = transactionData, fromTokenAmount = swapAmount, spenderAddress = spenderAddress, ), @@ -2140,23 +2130,6 @@ internal class SwapInteractorImpl @AssistedInject constructor( return PriceImpact.Value(value) } - private suspend fun getApproveData( - networkId: String, - derivationPath: String?, - fromToken: CryptoCurrency, - swapAmount: SwapAmount? = null, - spenderAddress: String, - ): String { - return repository.getApproveData( - userWalletId = userWalletId, - networkId = networkId, - derivationPath = derivationPath, - currency = fromToken, - amount = swapAmount?.value, - spenderAddress = spenderAddress, - ) - } - private suspend fun getQuotes(vararg ids: CryptoCurrency.ID): Map { val set = ids.mapNotNull { it.rawCurrencyId } .toSet() diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 7698d4b61b..ec47aff56d 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1033" +tangemBlockchainSdk = "develop-1034" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-455" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt index 8bdae098a7..baed3f3725 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt @@ -1,6 +1,7 @@ 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 @@ -14,7 +15,7 @@ interface TransactionManager { * @param currencyToSend currency to send in tx * @param destinationAddress address to send tx * @param increaseBy percents in format 125 = 25% - * @param data data for tx + * @param callData tx smart contract call data * @param derivationPath derivation path * @return */ @@ -26,7 +27,7 @@ interface TransactionManager { currencyToSend: Currency, destinationAddress: String, increaseBy: Int?, - data: String?, + callData: SmartContractCallData?, derivationPath: String?, ): ProxyFees