Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-30 17:01:38 +05:00
parent f377a40b36
commit 4be3782a0a
9 changed files with 117 additions and 8 deletions

View file

@ -43,6 +43,7 @@ dependencies {
implementation(projects.domain.wallets.models)
implementation(projects.domain.transaction.models)
implementation(projects.domain.express.models)
implementation(projects.domain.account.status)
/** Data */
implementation(projects.data.common)

View file

@ -8,6 +8,10 @@ import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectList
import com.tangem.datasource.local.preferences.utils.getObjectListSync
import com.tangem.datasource.local.preferences.utils.getObjectMap
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
@ -19,11 +23,14 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
internal class DefaultSwapTransactionRepository(
private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider,
private val singleAccountListSupplier: SingleAccountListSupplier,
private val accountsFeatureToggles: AccountsFeatureToggles,
responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
networkFactory: NetworkFactory,
) : SwapTransactionRepository {
@ -40,6 +47,8 @@ internal class DefaultSwapTransactionRepository(
userWalletId: UserWalletId,
fromCryptoCurrency: CryptoCurrency,
toCryptoCurrency: CryptoCurrency,
fromAccount: Account.CryptoPortfolio?,
toAccount: Account.CryptoPortfolio?,
transaction: SavedSwapTransactionModel,
) {
transaction.status?.let {
@ -73,12 +82,16 @@ internal class DefaultSwapTransactionRepository(
userWalletId = userWalletId,
fromCryptoCurrency = fromCryptoCurrency,
toCryptoCurrency = toCryptoCurrency,
fromAccount = fromAccount,
toAccount = toAccount,
transactions = tokenTransactions,
) ?: listOf(
converter.default(
userWalletId = userWalletId,
fromCryptoCurrency = fromCryptoCurrency,
toCryptoCurrency = toCryptoCurrency,
fromAccount = fromAccount,
toAccount = toAccount,
tokenTransactions = listOf(transaction),
),
),
@ -97,7 +110,12 @@ internal class DefaultSwapTransactionRepository(
flow2 = appPreferencesStore.getObjectMap<ExchangeStatusModel>(
key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY,
),
) { savedTransactions, txStatuses ->
flow3 = if (accountsFeatureToggles.isFeatureEnabled) {
singleAccountListSupplier(SingleAccountListProducer.Params(userWallet.walletId))
} else {
flowOf(null)
},
) { savedTransactions, txStatuses, accountList ->
val currencyToTxs = savedTransactions?.filter {
val isUserWallet = it.userWalletId == userWallet.walletId.stringValue
@ -115,6 +133,7 @@ internal class DefaultSwapTransactionRepository(
converter.convertBack(
value = it,
userWallet = userWallet,
accountList = accountList,
txStatuses = txStatuses,
onFilter = { it.swapTxTypeDTO == SwapTxTypeDTO.Swap },
)
@ -124,6 +143,7 @@ internal class DefaultSwapTransactionRepository(
converter.convertBack(
value = it,
userWallet = userWallet,
accountList = accountList,
txStatuses = txStatuses,
)
}.orEmpty()
@ -223,10 +243,13 @@ internal class DefaultSwapTransactionRepository(
fromCryptoCurrencyId == fromCurrencyId.value
}
@Suppress("LongParameterList")
private fun List<SavedSwapTransactionListModelInner>.updateList(
userWalletId: UserWalletId,
fromCryptoCurrency: CryptoCurrency,
toCryptoCurrency: CryptoCurrency,
fromAccount: Account.CryptoPortfolio?,
toAccount: Account.CryptoPortfolio?,
transactions: List<SavedSwapTransactionModel>,
): List<SavedSwapTransactionListModelInner> {
return addOrReplace(
@ -234,6 +257,8 @@ internal class DefaultSwapTransactionRepository(
userWalletId = userWalletId,
fromCryptoCurrency = fromCryptoCurrency,
toCryptoCurrency = toCryptoCurrency,
fromAccount = fromAccount,
toAccount = toAccount,
tokenTransactions = transactions,
),
predicate = {

View file

@ -6,6 +6,9 @@ import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.data.common.currency.UserTokensResponseFactory
import com.tangem.data.common.network.NetworkFactory
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
@ -29,33 +32,37 @@ internal class SavedSwapTransactionListConverter(
toCryptoCurrencyId = value.toCryptoCurrencyId,
fromTokensResponse = userTokensResponseFactory.createResponseToken(
currency = value.fromCryptoCurrency,
accountId = null,
accountId = value.fromAccount?.accountId,
),
toTokensResponse = userTokensResponseFactory.createResponseToken(
currency = value.toCryptoCurrency,
accountId = null,
accountId = value.toAccount?.accountId,
),
transactions = value.transactions,
)
fun convertBack(
value: SavedSwapTransactionListModelInner,
accountList: AccountList?,
userWallet: UserWallet,
txStatuses: Map<String, ExchangeStatusModel>,
onFilter: (SavedSwapTransactionModel) -> Boolean = { true },
): SavedSwapTransactionListModel? {
val fromToken = value.fromTokensResponse
val toToken = value.toTokensResponse
val fromDerivationIndex = fromToken?.getDerivationIndex()
val toDerivationIndex = toToken?.getDerivationIndex()
return if (fromToken == null || toToken == null) {
null
} else {
val fromNetwork = createSwapTransactionNetwork(fromToken, userWallet) ?: return null
val fromNetwork = createSwapTransactionNetwork(fromToken, userWallet, fromDerivationIndex) ?: return null
val fromCryptoCurrency = responseCryptoCurrenciesFactory.createCurrency(
responseToken = fromToken,
userWallet = userWallet,
network = fromNetwork,
) ?: return null
val toNetwork = createSwapTransactionNetwork(fromToken, userWallet) ?: return null
val toNetwork = createSwapTransactionNetwork(toToken, userWallet, toDerivationIndex) ?: return null
val toCryptoCurrency = responseCryptoCurrenciesFactory.createCurrency(
responseToken = toToken,
userWallet = userWallet,
@ -81,14 +88,25 @@ internal class SavedSwapTransactionListConverter(
toCryptoCurrencyId = value.toCryptoCurrencyId,
fromCryptoCurrency = fromCryptoCurrency,
toCryptoCurrency = toCryptoCurrency,
fromAccount = findAccountByDerivationIndex(
accountList = accountList,
derivationIndex = fromDerivationIndex,
),
toAccount = findAccountByDerivationIndex(
accountList = accountList,
derivationIndex = toDerivationIndex,
),
)
}
}
@Suppress("LongParameterList")
fun default(
userWalletId: UserWalletId,
fromCryptoCurrency: CryptoCurrency,
toCryptoCurrency: CryptoCurrency,
fromAccount: Account.CryptoPortfolio?,
toAccount: Account.CryptoPortfolio?,
tokenTransactions: List<SavedSwapTransactionModel>,
) = SavedSwapTransactionListModelInner(
userWalletId = userWalletId.stringValue,
@ -96,13 +114,20 @@ internal class SavedSwapTransactionListConverter(
toCryptoCurrencyId = toCryptoCurrency.id.value,
fromTokensResponse = userTokensResponseFactory.createResponseToken(
currency = fromCryptoCurrency,
accountId = null,
accountId = fromAccount?.accountId,
),
toTokensResponse = userTokensResponseFactory.createResponseToken(
currency = toCryptoCurrency,
accountId = toAccount?.accountId,
),
toTokensResponse = userTokensResponseFactory.createResponseToken(currency = toCryptoCurrency, accountId = null),
transactions = tokenTransactions,
)
private fun createSwapTransactionNetwork(token: UserTokensResponse.Token, userWallet: UserWallet): Network? {
private fun createSwapTransactionNetwork(
token: UserTokensResponse.Token,
userWallet: UserWallet,
accountIndex: DerivationIndex?,
): Network? {
val blockchain = Blockchain.fromNetworkId(token.networkId) ?: return null
return if (token.derivationPath == null) {
@ -116,7 +141,20 @@ internal class SavedSwapTransactionListConverter(
blockchain = blockchain,
extraDerivationPath = token.derivationPath,
userWallet = userWallet,
accountIndex = accountIndex,
)
}
}
private fun findAccountByDerivationIndex(
accountList: AccountList?,
derivationIndex: DerivationIndex?,
): Account.CryptoPortfolio? {
return accountList?.accounts?.asSequence()?.filterIsInstance<Account.CryptoPortfolio>()
?.firstOrNull { it.derivationIndex == derivationIndex }
}
private fun UserTokensResponse.Token.getDerivationIndex(): DerivationIndex? {
return accountId?.toIntOrNull()?.let { DerivationIndex(it).getOrNull() }
}
}

View file

@ -10,6 +10,8 @@ import com.tangem.datasource.crypto.DataSignatureVerifier
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.feature.swap.DefaultSwapRepository
@ -62,12 +64,16 @@ internal class SwapDataModule {
appPreferencesStore: AppPreferencesStore,
responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
networkFactory: NetworkFactory,
singleAccountListSupplier: SingleAccountListSupplier,
accountsFeatureToggles: AccountsFeatureToggles,
dispatcherProvider: CoroutineDispatcherProvider,
): SwapTransactionRepository {
return DefaultSwapTransactionRepository(
appPreferencesStore = appPreferencesStore,
responseCryptoCurrenciesFactory = responseCryptoCurrenciesFactory,
networkFactory = networkFactory,
singleAccountListSupplier = singleAccountListSupplier,
accountsFeatureToggles = accountsFeatureToggles,
dispatchers = dispatcherProvider,
)
}

View file

@ -3,6 +3,7 @@ package com.tangem.feature.swap.domain.models.domain
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import java.math.BigDecimal
@ -12,6 +13,8 @@ data class SavedSwapTransactionListModel(
val toCryptoCurrencyId: String,
val fromCryptoCurrency: CryptoCurrency,
val toCryptoCurrency: CryptoCurrency,
val fromAccount: Account.CryptoPortfolio?,
val toAccount: Account.CryptoPortfolio?,
val transactions: List<SavedSwapTransactionModel>,
)

View file

@ -71,6 +71,8 @@ interface SwapInteractor {
swapData: SwapDataModel?,
currencyToSend: CryptoCurrencyStatus,
currencyToGet: CryptoCurrencyStatus,
fromAccount: Account.CryptoPortfolio?,
toAccount: Account.CryptoPortfolio?,
amountToSwap: String,
includeFeeInAmount: IncludeFeeInAmount,
fee: TxFee,

View file

@ -714,6 +714,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
swapData: SwapDataModel?,
currencyToSend: CryptoCurrencyStatus,
currencyToGet: CryptoCurrencyStatus,
fromAccount: Account.CryptoPortfolio?,
toAccount: Account.CryptoPortfolio?,
amountToSwap: String,
includeFeeInAmount: IncludeFeeInAmount,
fee: TxFee,
@ -749,6 +751,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
onSwapCex(
currencyToSend = currencyToSend,
currencyToGet = currencyToGet,
fromAccount = fromAccount,
toAccount = toAccount,
amount = amountToSwapWithFee,
txFee = fee,
swapProvider = swapProvider,
@ -764,6 +768,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
swapData = requireNotNull(swapData),
currencyToSendStatus = currencyToSend,
currencyToGetStatus = currencyToGet,
fromAccount = fromAccount,
toAccount = toAccount,
amountToSwap = amountToSwap,
)
} else {
@ -773,6 +779,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
swapData = requireNotNull(swapData),
currencyToSendStatus = currencyToSend,
currencyToGetStatus = currencyToGet,
fromAccount = fromAccount,
toAccount = toAccount,
txFee = fee,
amountToSwap = amountToSwap,
)
@ -828,6 +836,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
swapData: SwapDataModel,
currencyToSendStatus: CryptoCurrencyStatus,
currencyToGetStatus: CryptoCurrencyStatus,
fromAccount: Account.CryptoPortfolio?,
toAccount: Account.CryptoPortfolio?,
amountToSwap: String,
txFee: TxFee,
): SwapTransactionState {
@ -856,6 +866,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
swapData = swapData,
currencyToSendStatus = currencyToSendStatus,
currencyToGetStatus = currencyToGetStatus,
fromAccount = fromAccount,
toAccount = toAccount,
amount = amount,
derivationPath = derivationPath,
txData = txData,
@ -869,6 +881,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
swapData: SwapDataModel,
currencyToSendStatus: CryptoCurrencyStatus,
currencyToGetStatus: CryptoCurrencyStatus,
fromAccount: Account.CryptoPortfolio?,
toAccount: Account.CryptoPortfolio?,
amountToSwap: String,
): SwapTransactionState {
val dexTransaction = swapData.transaction as? ExpressTransactionModel.DEX
@ -885,6 +899,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
swapData = swapData,
currencyToSendStatus = currencyToSendStatus,
currencyToGetStatus = currencyToGetStatus,
fromAccount = fromAccount,
toAccount = toAccount,
amount = amount,
derivationPath = derivationPath,
txData = compiledTransaction,
@ -898,6 +914,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
swapData: SwapDataModel,
currencyToSendStatus: CryptoCurrencyStatus,
currencyToGetStatus: CryptoCurrencyStatus,
fromAccount: Account.CryptoPortfolio?,
toAccount: Account.CryptoPortfolio?,
amount: SwapAmount,
derivationPath: String?,
txData: TransactionData,
@ -924,6 +942,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
storeSwapTransaction(
currencyToSend = currencyToSendStatus,
currencyToGet = currencyToGetStatus,
fromAccount = fromAccount,
toAccount = toAccount,
amount = amount,
swapProvider = provider,
swapDataModel = swapData,
@ -962,6 +982,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
private suspend fun onSwapCex(
currencyToSend: CryptoCurrencyStatus,
currencyToGet: CryptoCurrencyStatus,
fromAccount: Account.CryptoPortfolio?,
toAccount: Account.CryptoPortfolio?,
amount: SwapAmount,
txFee: TxFee,
swapProvider: SwapProvider,
@ -1033,6 +1055,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
storeSwapTransaction(
currencyToSend = currencyToSend,
currencyToGet = currencyToGet,
fromAccount = fromAccount,
toAccount = toAccount,
amount = amount,
swapProvider = swapProvider,
swapDataModel = exchangeData,
@ -1066,6 +1090,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
private suspend fun storeSwapTransaction(
currencyToSend: CryptoCurrencyStatus,
currencyToGet: CryptoCurrencyStatus,
fromAccount: Account.CryptoPortfolio?,
toAccount: Account.CryptoPortfolio?,
amount: SwapAmount,
swapProvider: SwapProvider,
swapDataModel: SwapDataModel,
@ -1078,6 +1104,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
userWalletId = userWalletId,
fromCryptoCurrency = currencyToSend.currency,
toCryptoCurrency = currencyToGet.currency,
fromAccount = fromAccount,
toAccount = toAccount,
transaction = SavedSwapTransactionModel(
txId = swapDataModel.transaction.txId,
provider = swapProvider,

View file

@ -1,5 +1,6 @@
package com.tangem.feature.swap.domain
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
@ -11,10 +12,13 @@ import kotlinx.coroutines.flow.Flow
interface SwapTransactionRepository {
@Suppress("LongParameterList")
suspend fun storeTransaction(
userWalletId: UserWalletId,
fromCryptoCurrency: CryptoCurrency,
toCryptoCurrency: CryptoCurrency,
fromAccount: Account.CryptoPortfolio?,
toAccount: Account.CryptoPortfolio?,
transaction: SavedSwapTransactionModel,
)

View file

@ -714,6 +714,8 @@ internal class SwapModel @Inject constructor(
currencyToSend = fromCurrency,
currencyToGet = requireNotNull(dataState.toCryptoCurrency),
amountToSwap = requireNotNull(dataState.amount),
fromAccount = dataState.fromAccount,
toAccount = dataState.toAccount,
includeFeeInAmount = lastLoadedQuotesState.preparedSwapConfigState.includeFeeInAmount,
fee = fee,
expressOperationType = ExpressOperationType.SWAP,