Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-04 16:41:33 +05:00
parent dbb8ab5d50
commit 611d980d5e
9 changed files with 54 additions and 157 deletions

View file

@ -64,6 +64,12 @@ internal object TransactionDomainModule {
return CreateTransactionUseCase(transactionRepository)
}
@Provides
@Singleton
fun provideEstimateFeeUseCase(walletManagersFacade: WalletManagersFacade): EstimateFeeUseCase {
return EstimateFeeUseCase(walletManagersFacade)
}
@Provides
@Singleton
fun provideIsFeeApproximateUseCase(feeRepository: FeeRepository): IsFeeApproximateUseCase {

View file

@ -6,6 +6,7 @@ import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.walletmanager.DefaultWalletManagersFacade
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -23,12 +24,14 @@ internal object WalletManagersFacadeModule {
userWalletsStore: UserWalletsStore,
assetLoader: AssetLoader,
blockchainSDKFactory: BlockchainSDKFactory,
dispatchers: CoroutineDispatcherProvider,
): WalletManagersFacade {
return DefaultWalletManagersFacade(
walletManagersStore = walletManagersStore,
userWalletsStore = userWalletsStore,
assetLoader = assetLoader,
blockchainSDKFactory = blockchainSDKFactory,
dispatchers = dispatchers,
)
}
}

View file

@ -1,19 +1,10 @@
package com.tangem.tap.proxy
import androidx.core.text.isDigitsOnly
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.Message
import com.tangem.blockchain.blockchains.algorand.AlgorandTransactionExtras
import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras
import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.blockchains.hedera.HederaTransactionExtras
import com.tangem.blockchain.blockchains.optimism.EthereumOptimisticRollupWalletManager
import com.tangem.blockchain.blockchains.stellar.StellarMemo
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
import com.tangem.blockchain.blockchains.ton.TonTransactionExtras
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
@ -65,31 +56,6 @@ class TransactionManagerImpl(
)
}
override suspend fun sendTransaction(
txData: SwapTxData,
isSwap: Boolean,
derivationPath: String?,
analyticsData: AnalyticsData,
): SendTxResult {
val blockchain = requireNotNull(Blockchain.fromNetworkId(txData.networkId)) { "blockchain not found" }
val walletManager = getActualWalletManager(blockchain, derivationPath)
walletManager.update()
val amount = if (isSwap) {
createAmountForSwap(txData.amountToSend, txData.currencyToSend, blockchain)
} else {
createAmount(txData.amountToSend, txData.currencyToSend, blockchain)
}
return sendTransactionInternal(
walletManager = walletManager,
amount = amount,
blockchain = blockchain,
feeAmount = txData.feeAmount,
gasLimit = txData.gasLimit,
destinationAddress = txData.destinationAddress,
dataToSign = txData.dataToSign,
)
}
@Suppress("LongParameterList")
private suspend fun sendTransactionInternal(
walletManager: WalletManager,
@ -125,39 +91,11 @@ class TransactionManagerImpl(
}
}
override fun getMemoExtras(networkId: String, memo: String?): TransactionExtras? {
val blockchain = Blockchain.fromNetworkId(networkId)
if (memo == null) return null
return when (blockchain) {
Blockchain.Stellar -> {
val xlmMemo = if (memo.isNotEmpty() && memo.isDigitsOnly()) {
StellarMemo.Id(memo.toBigInteger())
} else {
StellarMemo.Text(memo)
}
StellarTransactionExtras(xlmMemo)
}
Blockchain.Binance -> BinanceTransactionExtras(memo)
Blockchain.XRP -> memo.toLongOrNull()?.let { XrpTransactionBuilder.XrpTransactionExtras(it) }
Blockchain.Cosmos -> CosmosTransactionExtras(memo)
Blockchain.TON -> TonTransactionExtras(memo)
Blockchain.Hedera -> HederaTransactionExtras(memo)
Blockchain.Algorand -> AlgorandTransactionExtras(memo)
else -> null
}
}
override suspend fun updateWalletManager(networkId: String, derivationPath: String?) {
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
getActualWalletManager(blockchain, derivationPath).update()
}
override fun calculateFee(networkId: String, gasPrice: String, estimatedGas: Int): BigDecimal {
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
val gasPriceValue = requireNotNull(gasPrice.toLongOrNull()) { "gasprice should be Long" }
return (gasPriceValue * estimatedGas).toBigDecimal().movePointLeft(blockchain.decimals())
}
@Throws(IllegalStateException::class)
override suspend fun getFee(
networkId: String,
@ -512,25 +450,6 @@ class TransactionManagerImpl(
}
}
private fun createAmountForSwap(amount: BigDecimal, currency: Currency?, blockchain: Blockchain): Amount {
return when (currency) {
is Currency.NativeToken,
null,
-> {
Amount(value = amount, blockchain = blockchain)
}
is Currency.NonNativeToken -> {
// 1. when creates swap amount for NonNativeToken, amount should be ZERO
// 2. Amount has .Coin type, as workaround to use destinationAddress in bsdk, not contractAddress
Amount(
currencySymbol = currency.symbol,
value = BigDecimal.ZERO,
decimals = currency.decimalCount,
)
}
}
}
private fun convertNonNativeToken(token: Currency.NonNativeToken): Token {
return Token(
name = token.name,

View file

@ -36,20 +36,23 @@ import com.tangem.domain.walletmanager.utils.*
import com.tangem.domain.walletmanager.utils.WalletManagerFactory
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.math.BigDecimal
import java.util.EnumSet
@Suppress("LargeClass", "TooManyFunctions", "LongParameterList")
@Suppress("LargeClass", "TooManyFunctions")
// FIXME: Move to its own module and make internal
@Deprecated("Inject the WalletManagerFacade interface using DI instead")
class DefaultWalletManagersFacade(
private val walletManagersStore: WalletManagersStore,
private val userWalletsStore: UserWalletsStore,
private val assetLoader: AssetLoader,
private val dispatchers: CoroutineDispatcherProvider,
blockchainSDKFactory: BlockchainSDKFactory,
) : WalletManagersFacade {
@ -467,7 +470,7 @@ class DefaultWalletManagersFacade(
amount: Amount,
userWalletId: UserWalletId,
network: Network,
): Result<TransactionFee>? {
): Result<TransactionFee>? = withContext(dispatchers.io) {
val blockchain = Blockchain.fromId(network.id.value)
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
@ -477,7 +480,7 @@ class DefaultWalletManagersFacade(
val destination = estimationFeeAddressFactory.makeAddress(blockchain)
return (walletManager as? TransactionSender)?.estimateFee(
(walletManager as? TransactionSender)?.estimateFee(
amount = amount,
destination = destination,
)

View file

@ -12,10 +12,8 @@ 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.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import java.math.BigDecimal
/**
@ -23,7 +21,6 @@ import java.math.BigDecimal
*/
class EstimateFeeUseCase(
private val walletManagersFacade: WalletManagersFacade,
private val dispatcher: CoroutineDispatcherProvider,
) {
suspend operator fun invoke(
amount: BigDecimal,
@ -43,7 +40,7 @@ class EstimateFeeUseCase(
null -> GetFeeError.UnknownError.left()
}
emit(maybeFee)
}.flowOn(dispatcher.io)
}
}
private fun convertCryptoCurrencyToAmount(cryptoCurrency: CryptoCurrency, amount: BigDecimal) = Amount(

View file

@ -16,7 +16,7 @@ class ValidateTransactionUseCase(
@Suppress("LongParameterList")
suspend operator fun invoke(
amount: Amount,
fee: Fee,
fee: Fee?,
memo: String?,
destination: String,
userWalletId: UserWalletId,

View file

@ -24,13 +24,12 @@ 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.tokens.utils.convertToAmount
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
import com.tangem.domain.transaction.usecase.EstimateFeeUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
@ -46,7 +45,6 @@ import com.tangem.lib.crypto.TransactionManager
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.lib.crypto.models.*
import com.tangem.lib.crypto.models.transactions.SendTxResult
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.firstOrNull
import timber.log.Timber
@ -63,24 +61,19 @@ internal class SwapInteractorImpl @Inject constructor(
private val allowPermissionsHandler: AllowPermissionsHandler,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
private val walletManagersFacade: WalletManagersFacade,
private val sendTransactionUseCase: SendTransactionUseCase,
private val createTransactionUseCase: CreateTransactionUseCase,
private val quotesRepository: QuotesRepository,
private val dispatcher: CoroutineDispatcherProvider,
private val swapTransactionRepository: SwapTransactionRepository,
private val currencyChecksRepository: CurrencyChecksRepository,
private val appCurrencyRepository: AppCurrencyRepository,
private val currenciesRepository: CurrenciesRepository,
private val initialToCurrencyResolver: InitialToCurrencyResolver,
private val demoConfig: DemoConfig,
private val transactionRepository: TransactionRepository,
private val validateTransactionUseCase: ValidateTransactionUseCase,
private val estimateFeeUseCase: EstimateFeeUseCase,
) : SwapInteractor {
private val estimateFeeUseCase by lazy(LazyThreadSafetyMode.NONE) {
EstimateFeeUseCase(walletManagersFacade, dispatcher)
}
private val getSelectedAppCurrencyUseCase by lazy(LazyThreadSafetyMode.NONE) {
GetSelectedAppCurrencyUseCase(appCurrencyRepository)
}
@ -475,34 +468,33 @@ internal class SwapInteractorImpl @Inject constructor(
userWalletId: UserWalletId,
minAdaValue: BigDecimal?,
) {
transactionRepository.validateTransaction(
validateTransactionUseCase(
amount = amount.value.convertToAmount(fromToken),
fee = null,
memo = null,
destination = getTokenAddress(fromToken),
userWalletId = userWalletId,
network = fromToken.network,
)
.fold(
onFailure = {
addCardanoTransactionValidationError(
warnings = warnings,
error = it as? BlockchainSdkError.Cardano ?: return@fold,
fromToken = fromToken,
userWalletId = userWalletId,
).fold(
ifLeft = {
addCardanoTransactionValidationError(
warnings = warnings,
error = it as? BlockchainSdkError.Cardano ?: return@fold,
fromToken = fromToken,
userWalletId = userWalletId,
)
},
ifRight = {
minAdaValue?.let {
warnings.add(
Warning.Cardano.MinAdaValueCharged(
tokenName = fromToken.name,
minAdaValue = minAdaValue.parseBigDecimal(fromToken.decimals),
),
)
},
onSuccess = {
minAdaValue?.let {
warnings.add(
Warning.Cardano.MinAdaValueCharged(
tokenName = fromToken.name,
minAdaValue = minAdaValue.parseBigDecimal(fromToken.decimals),
),
)
}
},
)
}
},
)
}
private suspend fun addCardanoTransactionValidationError(
@ -706,28 +698,27 @@ internal class SwapInteractorImpl @Inject constructor(
val exchangeDataCex =
exchangeData.transaction as? ExpressTransactionModel.CEX ?: return SwapTransactionState.UnknownError
val txExtras = transactionManager.getMemoExtras(
currencyToSend.currency.network.backendId,
exchangeDataCex.txExtraId,
)
val cardId = getSelectedWallet()?.scanResponse?.card?.cardId ?: return SwapTransactionState.UnknownError
if (txExtras == null && exchangeDataCex.txExtraId != null && !demoConfig.isDemoCardId(cardId)) {
return SwapTransactionState.UnknownError
}
if (demoConfig.isDemoCardId(cardId)) return SwapTransactionState.UnknownError
val txData = createTransactionUseCase(
amount = amount.value.convertToAmount(currencyToSend.currency),
fee = getFeeForTransaction(
fee = txFee,
blockchain = Blockchain.fromId(currencyToSend.currency.network.id.value),
),
memo = null,
memo = exchangeDataCex.txExtraId,
destination = exchangeDataCex.txTo,
userWalletId = userWalletId,
network = currencyToSend.currency.network,
).getOrElse {
Timber.e(it)
return SwapTransactionState.UnknownError
}.copy(extras = txExtras)
}
if (txData.extras == null && exchangeDataCex.txExtraId != null) {
return SwapTransactionState.UnknownError
}
val result = sendTransactionUseCase(
txData = txData,

View file

@ -12,7 +12,9 @@ import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
import com.tangem.domain.transaction.usecase.EstimateFeeUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
@ -46,11 +48,10 @@ class SwapDomainModule {
swapTransactionRepository: SwapTransactionRepository,
appCurrencyRepository: AppCurrencyRepository,
currencyChecksRepository: CurrencyChecksRepository,
walletManagersFacade: WalletManagersFacade,
coroutineDispatcherProvider: CoroutineDispatcherProvider,
initialToCurrencyResolver: InitialToCurrencyResolver,
currenciesRepository: CurrenciesRepository,
transactionRepository: TransactionRepository,
validateTransactionUseCase: ValidateTransactionUseCase,
estimateFeeUseCase: EstimateFeeUseCase,
): SwapInteractor {
return SwapInteractorImpl(
transactionManager = transactionManager,
@ -62,15 +63,14 @@ class SwapDomainModule {
sendTransactionUseCase = sendTransactionUseCase,
createTransactionUseCase = createTransactionUseCase,
quotesRepository = quotesRepository,
walletManagersFacade = walletManagersFacade,
dispatcher = coroutineDispatcherProvider,
swapTransactionRepository = swapTransactionRepository,
appCurrencyRepository = appCurrencyRepository,
currencyChecksRepository = currencyChecksRepository,
currenciesRepository = currenciesRepository,
initialToCurrencyResolver = initialToCurrencyResolver,
demoConfig = DemoConfig(),
transactionRepository = transactionRepository,
validateTransactionUseCase = validateTransactionUseCase,
estimateFeeUseCase = estimateFeeUseCase,
)
}

View file

@ -1,6 +1,5 @@
package com.tangem.lib.crypto
import com.tangem.blockchain.common.TransactionExtras
import com.tangem.lib.crypto.models.*
import com.tangem.lib.crypto.models.transactions.SendTxResult
import java.math.BigDecimal
@ -14,22 +13,6 @@ interface TransactionManager {
analyticsData: AnalyticsData,
): SendTxResult
/**
* Send transaction
*
* @param txData data to build a tx
* @param derivationPath for select right walletManager
* @param analyticsData data for send analytics event
* @return result of transaction
*/
@Throws(IllegalStateException::class)
suspend fun sendTransaction(
txData: SwapTxData,
isSwap: Boolean,
derivationPath: String?,
analyticsData: AnalyticsData,
): SendTxResult
/**
* Get fee
*
@ -57,8 +40,6 @@ interface TransactionManager {
@Throws(IllegalStateException::class)
suspend fun updateWalletManager(networkId: String, derivationPath: String?)
fun calculateFee(networkId: String, gasPrice: String, estimatedGas: Int): BigDecimal
/**
* In app blockchain id, actual in blockchain sdk, not the same as networkId
*
@ -69,7 +50,4 @@ interface TransactionManager {
@Throws(IllegalStateException::class)
fun getExplorerTransactionLink(networkId: String, txAddress: String): String
// TODO: move to another place to use as in Send feature
fun getMemoExtras(networkId: String, memo: String?): TransactionExtras?
}