Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-28 14:22:23 +05:00
parent 8adc79a8c3
commit f4bb2bbc2d
21 changed files with 327 additions and 236 deletions

View file

@ -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(

View file

@ -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"))

View file

@ -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) {

View file

@ -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<Unit> = 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)

View file

@ -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<TransactionFee> {
return getFee(amount, walletManager.wallet.address)
override suspend fun estimateFee(
amount: Amount,
destination: String,
callData: SmartContractCallData?,
): Result<TransactionFee> {
return getFee(amount, walletManager.wallet.address, callData)
}
override suspend fun send(

View file

@ -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,
)
}

View file

@ -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<Unit>
suspend fun sendTransaction(
@ -66,9 +71,8 @@ interface TransactionRepository {
): com.tangem.blockchain.extensions.Result<TransactionsSendResult>
fun createTransactionDataExtras(
data: String,
callData: SmartContractCallData,
network: Network,
transactionType: TransactionType,
nonce: BigInteger?,
gasLimit: BigInteger?,
): TransactionExtras

View file

@ -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,
)
}
}

View file

@ -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" }
}
}

View file

@ -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,
)
}
}

View file

@ -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,
)
}
}

View file

@ -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,

View file

@ -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,
),
)
},
)
}

View file

@ -21,8 +21,6 @@ class ValidateTransactionUseCase(
destination: String,
userWalletId: UserWalletId,
network: Network,
isSwap: Boolean = false,
hash: String? = null,
): Either<Throwable, Unit> {
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() })
}
}

View file

@ -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,

View file

@ -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" }

View file

@ -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,

View file

@ -82,7 +82,6 @@ data class TokenSwapInfo(
data class RequestApproveStateData(
val fee: TxFeeState,
val approveData: String,
val fromTokenAmount: SwapAmount,
val spenderAddress: String,
)

View file

@ -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<CryptoCurrency.ID, Quote.Value> {
val set = ids.mapNotNull { it.rawCurrencyId }
.toSet()

View file

@ -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 ^

View file

@ -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