Updated on 2026-08-14

This commit is contained in:
Tangem 2024-05-07 18:19:56 +05:00
commit 326352be30
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.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,
)
}
}
}
}

View file

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

View file

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

View file

@ -26,4 +26,6 @@ sealed class SwapTransactionState {
data object UnknownError : 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.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
}
},

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

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? {
return when (dataError) {
is DataError.SwapsAreUnavailableNowError -> resourceReference(

View file

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