Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-30 12:29:05 +05:00
parent 4c51193221
commit c8a181c829
13 changed files with 135 additions and 73 deletions

View file

@ -13,7 +13,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectList import com.tangem.datasource.local.preferences.utils.getObjectList
import com.tangem.datasource.local.preferences.utils.getObjectListSync import com.tangem.datasource.local.preferences.utils.getObjectListSync
import com.tangem.datasource.local.preferences.utils.getObjectMapSync import com.tangem.datasource.local.preferences.utils.getObjectMap
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
@ -24,9 +24,8 @@ import com.tangem.domain.swap.models.SwapTransactionModel
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.addOrReplace import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
internal class DefaultSwapTransactionRepository( internal class DefaultSwapTransactionRepository(
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
@ -95,36 +94,34 @@ internal class DefaultSwapTransactionRepository(
} }
} }
override suspend fun getTransactions( override fun getTransactions(
userWallet: UserWallet, userWallet: UserWallet,
cryptoCurrencyId: CryptoCurrency.ID, cryptoCurrencyId: CryptoCurrency.ID,
): Flow<List<SwapTransactionListModel>?> { ): Flow<List<SwapTransactionListModel>?> = combine(
return withContext(dispatchers.io) { flow = appPreferencesStore.getObjectList<SwapTransactionListDTO>(
val txStatuses = appPreferencesStore.getObjectMapSync<SwapStatusDTO>( key = PreferencesKeys.SWAP_TRANSACTIONS_KEY,
key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, ),
) flow2 = appPreferencesStore.getObjectMap<SwapStatusDTO>(
appPreferencesStore.getObjectList<SwapTransactionListDTO>( key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY,
key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, ),
).map { savedTransactions -> ) { savedTransactions, txStatuses ->
val currencyTxs = savedTransactions val currencyTxs = savedTransactions
?.filter { ?.filter {
it.userWalletId == userWallet.walletId.stringValue && it.userWalletId == userWallet.walletId.stringValue &&
( (
it.toCryptoCurrencyId == cryptoCurrencyId.value || it.toCryptoCurrencyId == cryptoCurrencyId.value ||
it.fromCryptoCurrencyId == cryptoCurrencyId.value it.fromCryptoCurrencyId == cryptoCurrencyId.value
) )
} }
currencyTxs?.mapNotNull { currencyTxs?.mapNotNull {
listConverter.convertBack( listConverter.convertBack(
value = it, value = it,
userWallet = userWallet, userWallet = userWallet,
txStatuses = txStatuses, txStatuses = txStatuses,
) )
}
}.flowOn(dispatchers.io)
} }
} }.flowOn(dispatchers.default)
override suspend fun removeTransaction( override suspend fun removeTransaction(
userWalletId: UserWalletId, userWalletId: UserWalletId,
@ -188,7 +185,7 @@ internal class DefaultSwapTransactionRepository(
) )
val updatesMap = savedMap.toMutableMap() val updatesMap = savedMap.toMutableMap()
updatesMap[txId] = savedStatusConverter.convertBack( updatesMap[txId] = savedStatusConverter.convert(
status.copy( status.copy(
refundTokensResponse = refundTokenCurrency?.let { refundTokensResponse = refundTokenCurrency?.let {
userTokensResponseFactory.createResponseToken(refundTokenCurrency) userTokensResponseFactory.createResponseToken(refundTokenCurrency)

View file

@ -6,9 +6,9 @@ import com.tangem.domain.swap.models.SwapStatus
import com.tangem.domain.swap.models.SwapStatusModel import com.tangem.domain.swap.models.SwapStatusModel
import com.tangem.utils.converter.TwoWayConverter import com.tangem.utils.converter.TwoWayConverter
internal class SavedSwapStatusConverter : TwoWayConverter<SwapStatusDTO, SwapStatusModel> { internal class SavedSwapStatusConverter : TwoWayConverter<SwapStatusModel, SwapStatusDTO> {
override fun convert(value: SwapStatusDTO) = SwapStatusModel( override fun convertBack(value: SwapStatusDTO) = SwapStatusModel(
providerId = value.providerId, providerId = value.providerId,
status = SwapStatus.entries.firstOrNull { status = SwapStatus.entries.firstOrNull {
it.name.lowercase() == value.status?.name?.lowercase() it.name.lowercase() == value.status?.name?.lowercase()
@ -22,7 +22,7 @@ internal class SavedSwapStatusConverter : TwoWayConverter<SwapStatusDTO, SwapSta
averageDuration = value.averageDuration, averageDuration = value.averageDuration,
) )
override fun convertBack(value: SwapStatusModel) = SwapStatusDTO( override fun convert(value: SwapStatusModel) = SwapStatusDTO(
providerId = value.providerId, providerId = value.providerId,
status = SavedSwapStatus.entries.firstOrNull { status = SavedSwapStatus.entries.firstOrNull {
it.name.lowercase() == value.status?.name?.lowercase() it.name.lowercase() == value.status?.name?.lowercase()

View file

@ -3,8 +3,10 @@ package com.tangem.data.swap.converter.transaction
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.data.swap.models.SwapStatusDTO import com.tangem.data.swap.models.SwapStatusDTO
import com.tangem.data.swap.models.SwapTransactionDTO import com.tangem.data.swap.models.SwapTransactionDTO
import com.tangem.data.swap.models.SwapTxTypeDTO
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.swap.models.SwapTransactionModel import com.tangem.domain.swap.models.SwapTransactionModel
import com.tangem.domain.swap.models.SwapTxType
import com.tangem.utils.converter.TwoWayConverter import com.tangem.utils.converter.TwoWayConverter
internal class SavedSwapTransactionConverter( internal class SavedSwapTransactionConverter(
@ -21,7 +23,10 @@ internal class SavedSwapTransactionConverter(
fromCryptoAmount = value.fromCryptoAmount, fromCryptoAmount = value.fromCryptoAmount,
toCryptoAmount = value.toCryptoAmount, toCryptoAmount = value.toCryptoAmount,
provider = value.provider, provider = value.provider,
status = value.status, status = value.status?.let(statusConverter::convert),
swapTxType = SwapTxTypeDTO.entries.firstOrNull {
it.name.lowercase() == value.swapTxType?.name?.lowercase()
},
) )
override fun convertBack(value: SwapTransactionDTO) = SwapTransactionModel( override fun convertBack(value: SwapTransactionDTO) = SwapTransactionModel(
@ -30,7 +35,10 @@ internal class SavedSwapTransactionConverter(
fromCryptoAmount = value.fromCryptoAmount, fromCryptoAmount = value.fromCryptoAmount,
toCryptoAmount = value.toCryptoAmount, toCryptoAmount = value.toCryptoAmount,
provider = value.provider, provider = value.provider,
status = value.status, status = value.status?.let(statusConverter::convertBack),
swapTxType = SwapTxType.entries.firstOrNull {
it.name.lowercase() == value.swapTxType?.name?.lowercase()
},
) )
fun convertBack( fun convertBack(
@ -53,7 +61,10 @@ internal class SavedSwapTransactionConverter(
fromCryptoAmount = value.fromCryptoAmount, fromCryptoAmount = value.fromCryptoAmount,
toCryptoAmount = value.toCryptoAmount, toCryptoAmount = value.toCryptoAmount,
provider = value.provider, provider = value.provider,
status = statusWithRefundCurrency?.let(statusConverter::convert), status = statusWithRefundCurrency?.let(statusConverter::convertBack),
swapTxType = SwapTxType.entries.firstOrNull {
it.name.lowercase() == value.swapTxType?.name?.lowercase()
},
) )
} }
} }

View file

@ -4,7 +4,6 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.express.models.ExpressProvider import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.swap.models.SwapStatusModel
import java.math.BigDecimal import java.math.BigDecimal
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@ -36,5 +35,7 @@ internal data class SwapTransactionDTO(
@Json(name = "provider") @Json(name = "provider")
val provider: ExpressProvider, val provider: ExpressProvider,
@Json(name = "status") @Json(name = "status")
val status: SwapStatusModel? = null, val status: SwapStatusDTO? = null,
@Json(name = "swapTxType")
val swapTxType: SwapTxTypeDTO? = SwapTxTypeDTO.Swap,
) )

View file

@ -0,0 +1,13 @@
package com.tangem.data.swap.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
internal enum class SwapTxTypeDTO {
@Json(name = "Swap")
Swap,
@Json(name = "SendWithSwap")
SendWithSwap,
}

View file

@ -26,4 +26,5 @@ data class SwapTransactionModel(
val toCryptoAmount: BigDecimal, val toCryptoAmount: BigDecimal,
val provider: ExpressProvider, val provider: ExpressProvider,
val status: SwapStatusModel? = null, val status: SwapStatusModel? = null,
val swapTxType: SwapTxType? = SwapTxType.Swap,
) )

View file

@ -0,0 +1,6 @@
package com.tangem.domain.swap.models
enum class SwapTxType {
Swap,
SendWithSwap,
}

View file

@ -1,11 +1,11 @@
package com.tangem.domain.swap package com.tangem.domain.swap
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.swap.models.SwapStatusModel import com.tangem.domain.swap.models.SwapStatusModel
import com.tangem.domain.swap.models.SwapTransactionListModel import com.tangem.domain.swap.models.SwapTransactionListModel
import com.tangem.domain.swap.models.SwapTransactionModel import com.tangem.domain.swap.models.SwapTransactionModel
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
/** /**
@ -34,7 +34,7 @@ interface SwapTransactionRepository {
* @param userWallet selected user wallet * @param userWallet selected user wallet
* @param cryptoCurrencyId transactions for specific crypto currency * @param cryptoCurrencyId transactions for specific crypto currency
*/ */
suspend fun getTransactions( fun getTransactions(
userWallet: UserWallet, userWallet: UserWallet,
cryptoCurrencyId: CryptoCurrency.ID, cryptoCurrencyId: CryptoCurrency.ID,
): Flow<List<SwapTransactionListModel>?> ): Flow<List<SwapTransactionListModel>?>

View file

@ -8,10 +8,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.swap.SwapErrorResolver import com.tangem.domain.swap.SwapErrorResolver
import com.tangem.domain.swap.SwapRepositoryV2 import com.tangem.domain.swap.SwapRepositoryV2
import com.tangem.domain.swap.SwapTransactionRepository import com.tangem.domain.swap.SwapTransactionRepository
import com.tangem.domain.swap.models.SwapDataTransactionModel import com.tangem.domain.swap.models.*
import com.tangem.domain.swap.models.SwapStatus
import com.tangem.domain.swap.models.SwapStatusModel
import com.tangem.domain.swap.models.SwapTransactionModel
@Suppress("LongParameterList") @Suppress("LongParameterList")
class SwapTransactionSentUseCase( class SwapTransactionSentUseCase(
@ -28,15 +25,8 @@ class SwapTransactionSentUseCase(
provider: ExpressProvider, provider: ExpressProvider,
txHash: String, txHash: String,
timestamp: Long, timestamp: Long,
swapTxType: SwapTxType,
) = Either.catch { ) = Either.catch {
swapRepositoryV2.swapTransactionSent(
userWallet = userWallet,
fromCryptoCurrencyStatus = fromCryptoCurrencyStatus,
toAddress = swapDataTransactionModel.txTo,
txId = swapDataTransactionModel.txId,
txHash = txHash,
txExtraId = swapDataTransactionModel.txExtraId,
)
if (provider.type.shouldStoreSwapTransaction()) { if (provider.type.shouldStoreSwapTransaction()) {
swapTransactionRepository.storeTransaction( swapTransactionRepository.storeTransaction(
userWalletId = userWallet.walletId, userWalletId = userWallet.walletId,
@ -56,12 +46,22 @@ class SwapTransactionSentUseCase(
txExternalId = (swapDataTransactionModel as? SwapDataTransactionModel.CEX)?.externalTxId, txExternalId = (swapDataTransactionModel as? SwapDataTransactionModel.CEX)?.externalTxId,
averageDuration = null, averageDuration = null,
), ),
swapTxType = swapTxType,
), ),
) )
} }
swapTransactionRepository.storeLastSwappedCryptoCurrencyId( swapTransactionRepository.storeLastSwappedCryptoCurrencyId(
userWalletId = userWallet.walletId, userWalletId = userWallet.walletId,
cryptoCurrencyId = toCryptoCurrencyStatus.currency.id, cryptoCurrencyId = toCryptoCurrencyStatus.currency.id,
) )
swapRepositoryV2.swapTransactionSent(
userWallet = userWallet,
fromCryptoCurrencyStatus = fromCryptoCurrencyStatus,
toAddress = swapDataTransactionModel.txTo,
txId = swapDataTransactionModel.txId,
txHash = txHash,
txExtraId = swapDataTransactionModel.txExtraId,
)
}.mapLeft(swapErrorResolver::resolve) }.mapLeft(swapErrorResolver::resolve)
} }

View file

@ -9,6 +9,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.swap.models.SwapDataModel import com.tangem.domain.swap.models.SwapDataModel
import com.tangem.domain.swap.models.SwapDataTransactionModel import com.tangem.domain.swap.models.SwapDataTransactionModel
import com.tangem.domain.swap.models.SwapTxType
import com.tangem.domain.swap.usecase.GetSwapDataUseCase import com.tangem.domain.swap.usecase.GetSwapDataUseCase
import com.tangem.domain.swap.usecase.SwapTransactionSentUseCase import com.tangem.domain.swap.usecase.SwapTransactionSentUseCase
import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.domain.transaction.error.SendTransactionError
@ -158,6 +159,7 @@ internal class SwapTransactionSender @AssistedInject constructor(
provider = provider, provider = provider,
txHash = txHash, txHash = txHash,
timestamp = timestamp, timestamp = timestamp,
swapTxType = SwapTxType.SendWithSwap,
) )
onSendSuccess(txHash, timestamp, swapData) onSendSuccess(txHash, timestamp, swapData)
}, },

View file

@ -92,21 +92,37 @@ internal class DefaultSwapTransactionRepository(
key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY,
), ),
) { savedTransactions, txStatuses -> ) { savedTransactions, txStatuses ->
val currencyTxs = savedTransactions?.filter {
it.userWalletId == userWallet.walletId.stringValue && val currencyToTxs = savedTransactions?.filter {
( val isUserWallet = it.userWalletId == userWallet.walletId.stringValue
it.toCryptoCurrencyId == cryptoCurrencyId.value || val toCurrency = it.toCryptoCurrencyId == cryptoCurrencyId.value
it.fromCryptoCurrencyId == cryptoCurrencyId.value isUserWallet && toCurrency
)
} }
currencyTxs?.mapNotNull { val currencyFromTxs = savedTransactions?.filter {
val isUserWallet = it.userWalletId == userWallet.walletId.stringValue
val fromCurrency = it.fromCryptoCurrencyId == cryptoCurrencyId.value
isUserWallet && fromCurrency
}
val toTxs = currencyToTxs?.mapNotNull {
converter.convertBack(
value = it,
userWallet = userWallet,
txStatuses = txStatuses,
onFilter = { it.swapTxTypeDTO == SwapTxTypeDTO.Swap },
)
}.orEmpty()
val fromTxs = currencyFromTxs?.mapNotNull {
converter.convertBack( converter.convertBack(
value = it, value = it,
userWallet = userWallet, userWallet = userWallet,
txStatuses = txStatuses, txStatuses = txStatuses,
) )
} }.orEmpty()
fromTxs + toTxs
} }
.flowOn(dispatchers.default) .flowOn(dispatchers.default)
} }

View file

@ -34,6 +34,7 @@ internal class SavedSwapTransactionListConverter(
value: SavedSwapTransactionListModelInner, value: SavedSwapTransactionListModelInner,
userWallet: UserWallet, userWallet: UserWallet,
txStatuses: Map<String, ExchangeStatusModel>, txStatuses: Map<String, ExchangeStatusModel>,
onFilter: (SavedSwapTransactionModel) -> Boolean = { true },
): SavedSwapTransactionListModel? { ): SavedSwapTransactionListModel? {
val fromToken = value.fromTokensResponse val fromToken = value.fromTokensResponse
val toToken = value.toTokensResponse val toToken = value.toTokensResponse
@ -50,17 +51,19 @@ internal class SavedSwapTransactionListConverter(
) ?: return null ) ?: return null
return SavedSwapTransactionListModel( return SavedSwapTransactionListModel(
transactions = value.transactions.map { tx -> transactions = value.transactions
val status = txStatuses[tx.txId] .filter(onFilter)
val refundCurrency = status?.refundTokensResponse?.let { id -> .map { tx ->
responseCryptoCurrenciesFactory.createCurrency( val status = txStatuses[tx.txId]
responseToken = id, val refundCurrency = status?.refundTokensResponse?.let { id ->
userWallet = userWallet, responseCryptoCurrenciesFactory.createCurrency(
) responseToken = id,
} userWallet = userWallet,
val statusWithRefundCurrency = status?.copy(refundCurrency = refundCurrency) )
tx.copy(status = statusWithRefundCurrency) }
}, val statusWithRefundCurrency = status?.copy(refundCurrency = refundCurrency)
tx.copy(status = statusWithRefundCurrency)
},
userWalletId = value.userWalletId, userWalletId = value.userWalletId,
fromCryptoCurrencyId = value.fromCryptoCurrencyId, fromCryptoCurrencyId = value.fromCryptoCurrencyId,
toCryptoCurrencyId = value.toCryptoCurrencyId, toCryptoCurrencyId = value.toCryptoCurrencyId,

View file

@ -50,4 +50,16 @@ data class SavedSwapTransactionModel(
val provider: SwapProvider, val provider: SwapProvider,
@Json(name = "status") @Json(name = "status")
val status: ExchangeStatusModel? = null, val status: ExchangeStatusModel? = null,
) @Json(name = "swapTxType")
val swapTxTypeDTO: SwapTxTypeDTO? = SwapTxTypeDTO.Swap,
)
// TODO refactor to use separate models to store
@JsonClass(generateAdapter = false)
enum class SwapTxTypeDTO {
@Json(name = "Swap")
Swap,
@Json(name = "SendWithSwap")
SendWithSwap,
}