Updated on 2026-08-14

This commit is contained in:
Tangem 2024-05-07 11:01:09 +05:00
parent ce9cfb001d
commit 43b055feb9
8 changed files with 116 additions and 31 deletions

View file

@ -17,6 +17,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.math.BigDecimal
internal class DefaultTransactionRepository( internal class DefaultTransactionRepository(
private val walletManagersFacade: WalletManagersFacade, private val walletManagersFacade: WalletManagersFacade,
@ -30,6 +31,8 @@ internal class DefaultTransactionRepository(
destination: String, destination: String,
userWalletId: UserWalletId, userWalletId: UserWalletId,
network: Network, network: Network,
isSwap: Boolean,
hash: String?,
): TransactionData? = withContext(coroutineDispatcherProvider.io) { ): TransactionData? = withContext(coroutineDispatcherProvider.io) {
val blockchain = Blockchain.fromId(network.id.value) val blockchain = Blockchain.fromId(network.id.value)
val walletManager = walletManagersFacade.getOrCreateWalletManager( val walletManager = walletManagersFacade.getOrCreateWalletManager(
@ -38,7 +41,13 @@ internal class DefaultTransactionRepository(
derivationPath = network.derivationPath.value, 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), extras = getMemoExtras(network.id.value, memo),
) )
} }
@ -81,4 +90,19 @@ internal class DefaultTransactionRepository(
else -> null 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,
)
}
}
}
} }

View file

@ -18,6 +18,8 @@ interface TransactionRepository {
destination: String, destination: String,
userWalletId: UserWalletId, userWalletId: UserWalletId,
network: Network, network: Network,
isSwap: Boolean,
hash: String?,
): TransactionData? ): TransactionData?
suspend fun sendTransaction( suspend fun sendTransaction(

View file

@ -22,6 +22,8 @@ class CreateTransactionUseCase(
destination: String, destination: String,
userWalletId: UserWalletId, userWalletId: UserWalletId,
network: Network, network: Network,
isSwap: Boolean = false,
hash: String? = null,
) = Either.catch { ) = Either.catch {
requireNotNull( requireNotNull(
transactionRepository.createTransaction( transactionRepository.createTransaction(
@ -31,6 +33,8 @@ class CreateTransactionUseCase(
destination = destination, destination = destination,
userWalletId = userWalletId, userWalletId = userWalletId,
network = network, network = network,
isSwap = isSwap,
hash = hash,
), ),
) { "Failed to create transaction" } ) { "Failed to create transaction" }
} }

View file

@ -26,4 +26,6 @@ sealed class SwapTransactionState {
data object UnknownError : SwapTransactionState() data object UnknownError : SwapTransactionState()
data class ExpressError(val dataError: DataError) : SwapTransactionState() data class ExpressError(val dataError: DataError) : SwapTransactionState()
data object DemoMode : SwapTransactionState()
} }

View file

@ -11,6 +11,7 @@ import com.tangem.blockchainsdk.utils.minimalAmount
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.appcurrency.extenstions.unwrap
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus 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.repository.QuotesRepository
import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.tokens.utils.convertToAmount
import com.tangem.domain.transaction.error.SendTransactionError 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.EstimateFeeUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade 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.feature.swap.domain.models.ui.*
import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.TransactionManager
import com.tangem.lib.crypto.UserWalletManager 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.lib.crypto.models.transactions.SendTxResult
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.isNullOrZero import com.tangem.utils.isNullOrZero
@ -59,6 +64,7 @@ internal class SwapInteractorImpl @Inject constructor(
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase, private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
private val walletManagersFacade: WalletManagersFacade, private val walletManagersFacade: WalletManagersFacade,
private val sendTransactionUseCase: SendTransactionUseCase, private val sendTransactionUseCase: SendTransactionUseCase,
private val createTransactionUseCase: CreateTransactionUseCase,
private val quotesRepository: QuotesRepository, private val quotesRepository: QuotesRepository,
private val dispatcher: CoroutineDispatcherProvider, private val dispatcher: CoroutineDispatcherProvider,
private val swapTransactionRepository: SwapTransactionRepository, private val swapTransactionRepository: SwapTransactionRepository,
@ -66,6 +72,7 @@ internal class SwapInteractorImpl @Inject constructor(
private val appCurrencyRepository: AppCurrencyRepository, private val appCurrencyRepository: AppCurrencyRepository,
private val currenciesRepository: CurrenciesRepository, private val currenciesRepository: CurrenciesRepository,
private val initialToCurrencyResolver: InitialToCurrencyResolver, private val initialToCurrencyResolver: InitialToCurrencyResolver,
private val demoConfig: DemoConfig,
) : SwapInteractor { ) : SwapInteractor {
private val estimateFeeUseCase by lazy(LazyThreadSafetyMode.NONE) { private val estimateFeeUseCase by lazy(LazyThreadSafetyMode.NONE) {
@ -473,6 +480,7 @@ internal class SwapInteractorImpl @Inject constructor(
currencyToGet = currencyToGet.currency, currencyToGet = currencyToGet.currency,
amountToSwap = amountToSwap, amountToSwap = amountToSwap,
fee = fee, fee = fee,
userWalletId = requireNotNull(getSelectedWallet()).walletId,
) )
} }
} }
@ -523,29 +531,35 @@ internal class SwapInteractorImpl @Inject constructor(
currencyToGet: CryptoCurrency, currencyToGet: CryptoCurrency,
amountToSwap: String, amountToSwap: String,
fee: TxFee, fee: TxFee,
userWalletId: UserWalletId,
): SwapTransactionState { ): SwapTransactionState {
val amountDecimal = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format" } val amountDecimal = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format" }
val amount = SwapAmount(amountDecimal, currencyToSend.decimals) val amount = SwapAmount(amountDecimal, currencyToSend.decimals)
val derivationPath = currencyToSend.network.derivationPath.value val derivationPath = currencyToSend.network.derivationPath.value
val result = transactionManager.sendTransaction( val txData = createTransactionUseCase(
txData = SwapTxData( amount = amount.value.convertToAmount(currencyToSend),
networkId = networkId, fee = getFeeForTransaction(
amountToSend = amountDecimal, fee = fee,
currencyToSend = swapCurrencyConverter.convert(currencyToSend), blockchain = Blockchain.fromId(currencyToSend.network.id.value),
feeAmount = fee.feeValue,
gasLimit = fee.gasLimit,
destinationAddress = swapData.transaction.txTo,
dataToSign = (swapData.transaction as ExpressTransactionModel.DEX).txData,
), ),
memo = null,
destination = swapData.transaction.txTo,
userWalletId = userWalletId,
network = currencyToSend.network,
hash = (swapData.transaction as ExpressTransactionModel.DEX).txData,
isSwap = true, isSwap = true,
derivationPath = derivationPath, ).getOrElse {
analyticsData = AnalyticsData( Timber.e(it)
feeType = fee.feeType.getNameForAnalytics(), return SwapTransactionState.UnknownError
tokenSymbol = currencyToSend.symbol, }
),
val result = sendTransactionUseCase(
txData = txData,
userWallet = requireNotNull(getSelectedWallet()),
network = currencyToSend.network,
) )
return when (result) { return result.fold(
is SendTxResult.Success -> { ifRight = {
storeLastCryptoCurrencyId(currencyToGet) storeLastCryptoCurrencyId(currencyToGet)
SwapTransactionState.TxSent( SwapTransactionState.TxSent(
fromAmount = amountFormatter.formatSwapAmountToUI( fromAmount = amountFormatter.formatSwapAmountToUI(
@ -561,13 +575,18 @@ internal class SwapInteractorImpl @Inject constructor(
txHash = userWalletManager.getLastTransactionHash(networkId, derivationPath).orEmpty(), txHash = userWalletManager.getLastTransactionHash(networkId, derivationPath).orEmpty(),
timestamp = System.currentTimeMillis(), timestamp = System.currentTimeMillis(),
) )
} },
SendTxResult.UserCancelledError -> SwapTransactionState.UserCancelled ifLeft = {
is SendTxResult.BlockchainSdkError -> SwapTransactionState.BlockchainError when (it) {
is SendTxResult.TangemSdkError -> SwapTransactionState.TangemSdkError SendTransactionError.UserCancelledError -> SwapTransactionState.UserCancelled
is SendTxResult.NetworkError -> SwapTransactionState.NetworkError is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError
is SendTxResult.UnknownError -> SwapTransactionState.UnknownError is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError
} is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError
is SendTransactionError.DemoCardError -> SwapTransactionState.DemoMode
else -> SwapTransactionState.UnknownError
}
},
)
} }
@Suppress("LongMethod") @Suppress("LongMethod")
@ -602,10 +621,11 @@ internal class SwapInteractorImpl @Inject constructor(
currencyToSend.currency.network.backendId, currencyToSend.currency.network.backendId,
exchangeDataCex.txExtraId, 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 return SwapTransactionState.UnknownError
} }
val txData = walletManagersFacade.createTransaction( val txData = createTransactionUseCase(
amount = amount.value.convertToAmount(currencyToSend.currency), amount = amount.value.convertToAmount(currencyToSend.currency),
fee = getFeeForTransaction( fee = getFeeForTransaction(
fee = txFee, fee = txFee,
@ -615,12 +635,13 @@ internal class SwapInteractorImpl @Inject constructor(
destination = exchangeDataCex.txTo, destination = exchangeDataCex.txTo,
userWalletId = userWalletId, userWalletId = userWalletId,
network = currencyToSend.currency.network, network = currencyToSend.currency.network,
)?.copy( ).getOrElse {
extras = txExtras, Timber.e(it)
) return SwapTransactionState.UnknownError
}.copy(extras = txExtras)
val result = sendTransactionUseCase( val result = sendTransactionUseCase(
requireNotNull(txData), txData = txData,
userWallet = requireNotNull(getSelectedWallet()), userWallet = requireNotNull(getSelectedWallet()),
network = currencyToSend.currency.network, network = currencyToSend.currency.network,
) )
@ -635,6 +656,7 @@ internal class SwapInteractorImpl @Inject constructor(
is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError
is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError
is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError
is SendTransactionError.DemoCardError -> SwapTransactionState.DemoMode
else -> SwapTransactionState.UnknownError else -> SwapTransactionState.UnknownError
} }
}, },

View file

@ -11,6 +11,7 @@ import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
@ -40,6 +41,7 @@ class SwapDomainModule {
@SwapScope getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, @SwapScope getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase, getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
@SwapScope sendTransactionUseCase: SendTransactionUseCase, @SwapScope sendTransactionUseCase: SendTransactionUseCase,
@SwapScope createTransactionUseCase: CreateTransactionUseCase,
quotesRepository: QuotesRepository, quotesRepository: QuotesRepository,
swapTransactionRepository: SwapTransactionRepository, swapTransactionRepository: SwapTransactionRepository,
appCurrencyRepository: AppCurrencyRepository, appCurrencyRepository: AppCurrencyRepository,
@ -57,6 +59,7 @@ class SwapDomainModule {
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
getMultiCryptoCurrencyStatusUseCase = getCryptoCurrencyStatusUseCase, getMultiCryptoCurrencyStatusUseCase = getCryptoCurrencyStatusUseCase,
sendTransactionUseCase = sendTransactionUseCase, sendTransactionUseCase = sendTransactionUseCase,
createTransactionUseCase = createTransactionUseCase,
quotesRepository = quotesRepository, quotesRepository = quotesRepository,
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
dispatcher = coroutineDispatcherProvider, dispatcher = coroutineDispatcherProvider,
@ -65,6 +68,7 @@ class SwapDomainModule {
currencyChecksRepository = currencyChecksRepository, currencyChecksRepository = currencyChecksRepository,
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
initialToCurrencyResolver = initialToCurrencyResolver, initialToCurrencyResolver = initialToCurrencyResolver,
demoConfig = DemoConfig(),
) )
} }
@ -120,6 +124,15 @@ class SwapDomainModule {
return IsDemoCardUseCase(config = DemoConfig()) return IsDemoCardUseCase(config = DemoConfig())
} }
@SwapScope
@Provides
@Singleton
fun provideCreateTransactionUseCase(transactionRepository: TransactionRepository): CreateTransactionUseCase {
return CreateTransactionUseCase(
transactionRepository = transactionRepository,
)
}
@SwapScope @SwapScope
@Provides @Provides
@Singleton @Singleton

View file

@ -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? { private fun getProviderErrorMessage(dataError: DataError): TextReference? {
return when (dataError) { return when (dataError) {
is DataError.SwapsAreUnavailableNowError -> resourceReference( is DataError.SwapsAreUnavailableNowError -> resourceReference(

View file

@ -540,6 +540,12 @@ internal class SwapViewModel @Inject constructor(
is SwapTransactionState.UserCancelled -> { is SwapTransactionState.UserCancelled -> {
startLoadingQuotesFromLastState() startLoadingQuotesFromLastState()
} }
is SwapTransactionState.DemoMode -> {
startLoadingQuotesFromLastState()
uiState = stateBuilder.createDemoModeAlert(uiState) {
uiState = stateBuilder.clearAlert(uiState)
}
}
else -> { else -> {
startLoadingQuotesFromLastState() startLoadingQuotesFromLastState()
uiState = stateBuilder.createErrorTransaction(uiState, it) { uiState = stateBuilder.createErrorTransaction(uiState, it) {