Updated on 2026-08-14
This commit is contained in:
parent
ce9cfb001d
commit
43b055feb9
8 changed files with 116 additions and 31 deletions
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,8 @@ interface TransactionRepository {
|
|||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
isSwap: Boolean,
|
||||
hash: String?,
|
||||
): TransactionData?
|
||||
|
||||
suspend fun sendTransaction(
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,4 +26,6 @@ sealed class SwapTransactionState {
|
|||
data object UnknownError : SwapTransactionState()
|
||||
|
||||
data class ExpressError(val dataError: DataError) : SwapTransactionState()
|
||||
|
||||
data object DemoMode : SwapTransactionState()
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue