From 43b055feb97ab24a226505ed34689b4ee759dd89 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 7 May 2024 11:01:09 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../DefaultTransactionRepository.kt | 26 +++++- .../transaction/TransactionRepository.kt | 2 + .../usecase/CreateTransactionUseCase.kt | 4 + .../domain/models/ui/SwapTransactionState.kt | 2 + .../feature/swap/domain/SwapInteractorImpl.kt | 82 ++++++++++++------- .../swap/domain/di/SwapDomainModule.kt | 13 +++ .../tangem/feature/swap/ui/StateBuilder.kt | 12 +++ .../feature/swap/viewmodels/SwapViewModel.kt | 6 ++ 8 files changed, 116 insertions(+), 31 deletions(-) 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 3fe078eaad..934a88f5c9 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 @@ -17,6 +17,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext +import java.math.BigDecimal internal class DefaultTransactionRepository( private val walletManagersFacade: WalletManagersFacade, @@ -30,6 +31,8 @@ internal class DefaultTransactionRepository( destination: String, userWalletId: UserWalletId, network: Network, + isSwap: Boolean, + hash: String?, ): TransactionData? = withContext(coroutineDispatcherProvider.io) { val blockchain = Blockchain.fromId(network.id.value) val walletManager = walletManagersFacade.getOrCreateWalletManager( @@ -38,7 +41,13 @@ internal class DefaultTransactionRepository( derivationPath = network.derivationPath.value, ) - return@withContext walletManager?.createTransaction(amount, fee, destination)?.copy( + val txAmount = if (isSwap) { + createAmountForSwap(amount) + } else { + amount + } + return@withContext walletManager?.createTransaction(txAmount, fee, destination)?.copy( + hash = hash, extras = getMemoExtras(network.id.value, memo), ) } @@ -81,4 +90,19 @@ internal class DefaultTransactionRepository( else -> null } } + + private fun createAmountForSwap(amount: Amount): Amount { + return when (amount.type) { + is AmountType.Coin -> amount + else -> { + // 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 = amount.currencySymbol, + value = BigDecimal.ZERO, + decimals = amount.decimals, + ) + } + } + } } \ No newline at end of file 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 70c8b7d0ae..c28e9dbea7 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 @@ -18,6 +18,8 @@ interface TransactionRepository { destination: String, userWalletId: UserWalletId, network: Network, + isSwap: Boolean, + hash: String?, ): TransactionData? suspend fun sendTransaction( 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 55c5e0cd88..9fea02c12f 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 @@ -22,6 +22,8 @@ class CreateTransactionUseCase( destination: String, userWalletId: UserWalletId, network: Network, + isSwap: Boolean = false, + hash: String? = null, ) = Either.catch { requireNotNull( transactionRepository.createTransaction( @@ -31,6 +33,8 @@ class CreateTransactionUseCase( destination = destination, userWalletId = userWalletId, network = network, + isSwap = isSwap, + hash = hash, ), ) { "Failed to create transaction" } } diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapTransactionState.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapTransactionState.kt index 6ba68ddd31..bd29c42170 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapTransactionState.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapTransactionState.kt @@ -26,4 +26,6 @@ sealed class SwapTransactionState { data object UnknownError : SwapTransactionState() data class ExpressError(val dataError: DataError) : SwapTransactionState() + + data object DemoMode : SwapTransactionState() } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index bea16b1e5c..8deb74e13e 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 @@ -11,6 +11,7 @@ import com.tangem.blockchainsdk.utils.minimalAmount import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.appcurrency.repository.AppCurrencyRepository +import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -21,6 +22,7 @@ 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.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 @@ -36,7 +38,10 @@ import com.tangem.feature.swap.domain.models.toStringWithRightOffset import com.tangem.feature.swap.domain.models.ui.* import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager -import com.tangem.lib.crypto.models.* +import com.tangem.lib.crypto.models.AnalyticsData +import com.tangem.lib.crypto.models.ApproveTxData +import com.tangem.lib.crypto.models.ProxyAmount +import com.tangem.lib.crypto.models.ProxyFees import com.tangem.lib.crypto.models.transactions.SendTxResult import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.isNullOrZero @@ -59,6 +64,7 @@ internal class SwapInteractorImpl @Inject constructor( 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, @@ -66,6 +72,7 @@ internal class SwapInteractorImpl @Inject constructor( private val appCurrencyRepository: AppCurrencyRepository, private val currenciesRepository: CurrenciesRepository, private val initialToCurrencyResolver: InitialToCurrencyResolver, + private val demoConfig: DemoConfig, ) : SwapInteractor { private val estimateFeeUseCase by lazy(LazyThreadSafetyMode.NONE) { @@ -473,6 +480,7 @@ internal class SwapInteractorImpl @Inject constructor( currencyToGet = currencyToGet.currency, amountToSwap = amountToSwap, fee = fee, + userWalletId = requireNotNull(getSelectedWallet()).walletId, ) } } @@ -523,29 +531,35 @@ internal class SwapInteractorImpl @Inject constructor( currencyToGet: CryptoCurrency, amountToSwap: String, fee: TxFee, + userWalletId: UserWalletId, ): SwapTransactionState { val amountDecimal = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format" } val amount = SwapAmount(amountDecimal, currencyToSend.decimals) val derivationPath = currencyToSend.network.derivationPath.value - val result = transactionManager.sendTransaction( - txData = SwapTxData( - networkId = networkId, - amountToSend = amountDecimal, - currencyToSend = swapCurrencyConverter.convert(currencyToSend), - feeAmount = fee.feeValue, - gasLimit = fee.gasLimit, - destinationAddress = swapData.transaction.txTo, - dataToSign = (swapData.transaction as ExpressTransactionModel.DEX).txData, + val txData = createTransactionUseCase( + amount = amount.value.convertToAmount(currencyToSend), + fee = getFeeForTransaction( + fee = fee, + blockchain = Blockchain.fromId(currencyToSend.network.id.value), ), + memo = null, + destination = swapData.transaction.txTo, + userWalletId = userWalletId, + network = currencyToSend.network, + hash = (swapData.transaction as ExpressTransactionModel.DEX).txData, isSwap = true, - derivationPath = derivationPath, - analyticsData = AnalyticsData( - feeType = fee.feeType.getNameForAnalytics(), - tokenSymbol = currencyToSend.symbol, - ), + ).getOrElse { + Timber.e(it) + return SwapTransactionState.UnknownError + } + + val result = sendTransactionUseCase( + txData = txData, + userWallet = requireNotNull(getSelectedWallet()), + network = currencyToSend.network, ) - return when (result) { - is SendTxResult.Success -> { + return result.fold( + ifRight = { storeLastCryptoCurrencyId(currencyToGet) SwapTransactionState.TxSent( fromAmount = amountFormatter.formatSwapAmountToUI( @@ -561,13 +575,18 @@ internal class SwapInteractorImpl @Inject constructor( txHash = userWalletManager.getLastTransactionHash(networkId, derivationPath).orEmpty(), timestamp = System.currentTimeMillis(), ) - } - SendTxResult.UserCancelledError -> SwapTransactionState.UserCancelled - is SendTxResult.BlockchainSdkError -> SwapTransactionState.BlockchainError - is SendTxResult.TangemSdkError -> SwapTransactionState.TangemSdkError - is SendTxResult.NetworkError -> SwapTransactionState.NetworkError - is SendTxResult.UnknownError -> SwapTransactionState.UnknownError - } + }, + ifLeft = { + when (it) { + SendTransactionError.UserCancelledError -> SwapTransactionState.UserCancelled + is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError + is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError + is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError + is SendTransactionError.DemoCardError -> SwapTransactionState.DemoMode + else -> SwapTransactionState.UnknownError + } + }, + ) } @Suppress("LongMethod") @@ -602,10 +621,11 @@ internal class SwapInteractorImpl @Inject constructor( currencyToSend.currency.network.backendId, exchangeDataCex.txExtraId, ) - if (txExtras == null && exchangeDataCex.txExtraId != null) { + val cardId = getSelectedWallet()?.scanResponse?.card?.cardId ?: return SwapTransactionState.UnknownError + if (txExtras == null && exchangeDataCex.txExtraId != null && !demoConfig.isDemoCardId(cardId)) { return SwapTransactionState.UnknownError } - val txData = walletManagersFacade.createTransaction( + val txData = createTransactionUseCase( amount = amount.value.convertToAmount(currencyToSend.currency), fee = getFeeForTransaction( fee = txFee, @@ -615,12 +635,13 @@ internal class SwapInteractorImpl @Inject constructor( destination = exchangeDataCex.txTo, userWalletId = userWalletId, network = currencyToSend.currency.network, - )?.copy( - extras = txExtras, - ) + ).getOrElse { + Timber.e(it) + return SwapTransactionState.UnknownError + }.copy(extras = txExtras) val result = sendTransactionUseCase( - requireNotNull(txData), + txData = txData, userWallet = requireNotNull(getSelectedWallet()), network = currencyToSend.currency.network, ) @@ -635,6 +656,7 @@ internal class SwapInteractorImpl @Inject constructor( is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError + is SendTransactionError.DemoCardError -> SwapTransactionState.DemoMode else -> SwapTransactionState.UnknownError } }, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index fc191d1d2d..9c2290da01 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -11,6 +11,7 @@ import com.tangem.domain.tokens.repository.CurrencyChecksRepository 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.SendTransactionUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager @@ -40,6 +41,7 @@ class SwapDomainModule { @SwapScope getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase, @SwapScope sendTransactionUseCase: SendTransactionUseCase, + @SwapScope createTransactionUseCase: CreateTransactionUseCase, quotesRepository: QuotesRepository, swapTransactionRepository: SwapTransactionRepository, appCurrencyRepository: AppCurrencyRepository, @@ -57,6 +59,7 @@ class SwapDomainModule { getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, getMultiCryptoCurrencyStatusUseCase = getCryptoCurrencyStatusUseCase, sendTransactionUseCase = sendTransactionUseCase, + createTransactionUseCase = createTransactionUseCase, quotesRepository = quotesRepository, walletManagersFacade = walletManagersFacade, dispatcher = coroutineDispatcherProvider, @@ -65,6 +68,7 @@ class SwapDomainModule { currencyChecksRepository = currencyChecksRepository, currenciesRepository = currenciesRepository, initialToCurrencyResolver = initialToCurrencyResolver, + demoConfig = DemoConfig(), ) } @@ -120,6 +124,15 @@ class SwapDomainModule { return IsDemoCardUseCase(config = DemoConfig()) } + @SwapScope + @Provides + @Singleton + fun provideCreateTransactionUseCase(transactionRepository: TransactionRepository): CreateTransactionUseCase { + return CreateTransactionUseCase( + transactionRepository = transactionRepository, + ) + } + @SwapScope @Provides @Singleton diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index ec0eeb5e66..b4557850a7 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -984,6 +984,18 @@ internal class StateBuilder( ) } + fun createDemoModeAlert(uiState: SwapStateHolder, onAlertClick: () -> Unit): SwapStateHolder { + return uiState.copy( + alert = SwapWarning.GenericWarning( + title = resourceReference(id = R.string.warning_demo_mode_title), + message = resourceReference(id = R.string.warning_demo_mode_message), + onClick = onAlertClick, + type = GenericWarningType.OTHER, + ), + changeCardsButtonState = ChangeCardsButtonState.ENABLED, + ) + } + private fun getProviderErrorMessage(dataError: DataError): TextReference? { return when (dataError) { is DataError.SwapsAreUnavailableNowError -> resourceReference( diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index e78235321b..2adc1f3505 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -540,6 +540,12 @@ internal class SwapViewModel @Inject constructor( is SwapTransactionState.UserCancelled -> { startLoadingQuotesFromLastState() } + is SwapTransactionState.DemoMode -> { + startLoadingQuotesFromLastState() + uiState = stateBuilder.createDemoModeAlert(uiState) { + uiState = stateBuilder.clearAlert(uiState) + } + } else -> { startLoadingQuotesFromLastState() uiState = stateBuilder.createErrorTransaction(uiState, it) {