diff --git a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapTransactionRepository.kt b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapTransactionRepository.kt new file mode 100644 index 0000000000..5329db524a --- /dev/null +++ b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapTransactionRepository.kt @@ -0,0 +1,289 @@ +package com.tangem.data.swap + +import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.data.common.currency.UserTokensResponseFactory +import com.tangem.data.swap.converter.transaction.SavedSwapStatusConverter +import com.tangem.data.swap.converter.transaction.SavedSwapTransactionConverter +import com.tangem.data.swap.converter.transaction.SavedSwapTransactionListConverter +import com.tangem.data.swap.models.LastSwappedCryptoCurrencyDTO +import com.tangem.data.swap.models.SwapStatusDTO +import com.tangem.data.swap.models.SwapTransactionDTO +import com.tangem.data.swap.models.SwapTransactionListDTO +import com.tangem.datasource.local.preferences.AppPreferencesStore +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.getObjectMapSync +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.swap.SwapTransactionRepository +import com.tangem.domain.swap.models.SwapStatusModel +import com.tangem.domain.swap.models.SwapTransactionListModel +import com.tangem.domain.swap.models.SwapTransactionModel +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.requireColdWallet +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.extensions.addOrReplace +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext + +internal class DefaultSwapTransactionRepository( + private val appPreferencesStore: AppPreferencesStore, + private val dispatchers: CoroutineDispatcherProvider, + excludedBlockchains: ExcludedBlockchains, +) : SwapTransactionRepository { + + private val listConverter by lazy(LazyThreadSafetyMode.NONE) { + SavedSwapTransactionListConverter(excludedBlockchains) + } + private val converter by lazy(LazyThreadSafetyMode.NONE) { + SavedSwapTransactionConverter(excludedBlockchains) + } + private val savedStatusConverter by lazy(LazyThreadSafetyMode.NONE) { + SavedSwapStatusConverter() + } + private val userTokensResponseFactory = UserTokensResponseFactory() + + override suspend fun storeTransaction( + userWalletId: UserWalletId, + fromCryptoCurrency: CryptoCurrency, + toCryptoCurrency: CryptoCurrency, + transaction: SwapTransactionModel, + ) { + transaction.status?.let { + storeTransactionState( + txId = transaction.txId, + status = it, + refundTokenCurrency = null, + ) + } + appPreferencesStore.editData { mutablePreferences -> + val savedTransactions: List? = mutablePreferences.getObjectList( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + ) + val tokenTransactions = savedTransactions + ?.firstOrNull { + it.checkId( + checkUserWalletId = userWalletId, + fromCurrencyId = fromCryptoCurrency.id, + toCurrencyId = toCryptoCurrency.id, + ) + } + ?.transactions + ?.addOrReplace( + item = converter.convert(transaction), + predicate = { it.txId == transaction.txId }, + ) ?: listOf(converter.convert(transaction)) + + mutablePreferences.setObject( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + value = savedTransactions?.updateList( + userWalletId = userWalletId, + fromCryptoCurrency = fromCryptoCurrency, + toCryptoCurrency = toCryptoCurrency, + transactions = tokenTransactions, + ) ?: listOf( + listConverter.default( + userWalletId = userWalletId, + fromCryptoCurrency = fromCryptoCurrency, + toCryptoCurrency = toCryptoCurrency, + tokenTransactions = listOf(converter.convert(transaction)), + ), + ), + ) + } + } + + override suspend fun getTransactions( + userWallet: UserWallet, + cryptoCurrencyId: CryptoCurrency.ID, + ): Flow?> { + return withContext(dispatchers.io) { + val txStatuses = appPreferencesStore.getObjectMapSync( + key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, + ) + appPreferencesStore.getObjectList( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + ).map { savedTransactions -> + val currencyTxs = savedTransactions + ?.filter { + it.userWalletId == userWallet.walletId.stringValue && + ( + it.toCryptoCurrencyId == cryptoCurrencyId.value || + it.fromCryptoCurrencyId == cryptoCurrencyId.value + ) + } + + currencyTxs?.mapNotNull { + listConverter.convertBack( + value = it, + scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY] + txStatuses = txStatuses, + ) + } + }.flowOn(dispatchers.io) + } + } + + override suspend fun removeTransaction( + userWalletId: UserWalletId, + fromCryptoCurrency: CryptoCurrency, + toCryptoCurrency: CryptoCurrency, + txId: String, + ) { + clearTransactionsStatuses(txId = txId) + appPreferencesStore.editData { mutablePreferences -> + val savedList: List? = mutablePreferences.getObjectList( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + ) + val tokenTransactions = savedList + ?.firstOrNull { + it.checkId( + checkUserWalletId = userWalletId, + fromCurrencyId = fromCryptoCurrency.id, + toCurrencyId = toCryptoCurrency.id, + ) + } + ?.transactions + ?.filterNot { it.txId == txId } + + val editedList = + if (tokenTransactions.isNullOrEmpty()) { + savedList?.filterNot { + it.checkId( + checkUserWalletId = userWalletId, + fromCurrencyId = fromCryptoCurrency.id, + toCurrencyId = toCryptoCurrency.id, + ) + } + } else { + savedList.updateList( + userWalletId = userWalletId, + fromCryptoCurrency = fromCryptoCurrency, + toCryptoCurrency = toCryptoCurrency, + transactions = tokenTransactions, + ) + } + + if (editedList.isNullOrEmpty()) { + mutablePreferences.remove(key = PreferencesKeys.SWAP_TRANSACTIONS_KEY) + } else { + mutablePreferences.setObject( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + value = editedList, + ) + } + } + } + + override suspend fun storeTransactionState( + txId: String, + status: SwapStatusModel, + refundTokenCurrency: CryptoCurrency?, + ) { + appPreferencesStore.editData { mutablePreferences -> + val savedMap = mutablePreferences.getObjectMap( + key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, + ) + + val updatesMap = savedMap.toMutableMap() + updatesMap[txId] = savedStatusConverter.convertBack( + status.copy( + refundTokensResponse = refundTokenCurrency?.let { + userTokensResponseFactory.createResponseToken(refundTokenCurrency) + }, + ), + ) + + mutablePreferences.setObjectMap( + key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, + value = updatesMap, + ) + } + } + + override suspend fun getLastSwappedCryptoCurrencyId(userWalletId: UserWalletId): String? { + val lastSwappedCurrencies = appPreferencesStore.getObjectListSync( + key = PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY, + ) + + return lastSwappedCurrencies.find { userWalletId.stringValue == it.userWalletId }?.cryptoCurrencyId + } + + override suspend fun storeLastSwappedCryptoCurrencyId( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + ) { + appPreferencesStore.editData { mutablePreferences -> + val lastSwappedCryptoCurrencies: List? = mutablePreferences.getObjectList( + key = PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY, + ) + + val newList = if (lastSwappedCryptoCurrencies != null) { + lastSwappedCryptoCurrencies.filter { + it.userWalletId != userWalletId.stringValue + } + LastSwappedCryptoCurrencyDTO(userWalletId.stringValue, cryptoCurrencyId.value) + } else { + listOf(LastSwappedCryptoCurrencyDTO(userWalletId.stringValue, cryptoCurrencyId.value)) + } + + mutablePreferences.setObjectList( + key = PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY, + value = newList, + ) + } + } + + private fun SwapTransactionListDTO.checkId( + checkUserWalletId: UserWalletId, + fromCurrencyId: CryptoCurrency.ID, + toCurrencyId: CryptoCurrency.ID, + ): Boolean { + return userWalletId == checkUserWalletId.stringValue && + toCryptoCurrencyId == toCurrencyId.value && + fromCryptoCurrencyId == fromCurrencyId.value + } + + private fun List.updateList( + userWalletId: UserWalletId, + fromCryptoCurrency: CryptoCurrency, + toCryptoCurrency: CryptoCurrency, + transactions: List, + ): List { + return addOrReplace( + item = listConverter.default( + userWalletId = userWalletId, + fromCryptoCurrency = fromCryptoCurrency, + toCryptoCurrency = toCryptoCurrency, + tokenTransactions = transactions, + ), + predicate = { + it.checkId( + checkUserWalletId = userWalletId, + fromCurrencyId = fromCryptoCurrency.id, + toCurrencyId = toCryptoCurrency.id, + ) + }, + ) + } + + private suspend fun clearTransactionsStatuses(txId: String) { + appPreferencesStore.editData { mutablePreferences -> + val savedList = mutablePreferences.getObjectMap( + key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, + ) + val editedList = savedList.filterNot { it.key == txId } + + if (editedList.isEmpty()) { + mutablePreferences.remove(key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY) + } else { + mutablePreferences.setObjectMap( + key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, + value = editedList, + ) + } + } + } +} \ No newline at end of file diff --git a/data/swap/src/main/java/com/tangem/data/swap/converter/SwapStatusConverter.kt b/data/swap/src/main/java/com/tangem/data/swap/converter/SwapStatusConverter.kt new file mode 100644 index 0000000000..33ae4fa064 --- /dev/null +++ b/data/swap/src/main/java/com/tangem/data/swap/converter/SwapStatusConverter.kt @@ -0,0 +1,24 @@ +package com.tangem.data.swap.converter + +import com.tangem.datasource.api.express.models.response.ExchangeStatusResponse +import com.tangem.domain.swap.models.SwapStatus +import com.tangem.domain.swap.models.SwapStatusModel +import com.tangem.utils.converter.Converter + +internal class SwapStatusConverter : Converter { + override fun convert(value: ExchangeStatusResponse): SwapStatusModel { + return SwapStatusModel( + providerId = value.providerId, + status = SwapStatus.entries.firstOrNull { + it.name.lowercase() == value.status.name.lowercase() + }, + txId = value.externalTxId, + txExternalUrl = value.externalTxUrl, + txExternalId = value.externalTxId, + refundNetwork = value.refundNetwork, + refundContractAddress = value.refundContractAddress, + createdAt = value.createdAt, + averageDuration = value.averageDuration, + ) + } +} \ No newline at end of file diff --git a/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapStatusConverter.kt b/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapStatusConverter.kt new file mode 100644 index 0000000000..c3d8f36903 --- /dev/null +++ b/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapStatusConverter.kt @@ -0,0 +1,38 @@ +package com.tangem.data.swap.converter.transaction + +import com.tangem.data.swap.models.SavedSwapStatus +import com.tangem.data.swap.models.SwapStatusDTO +import com.tangem.domain.swap.models.SwapStatus +import com.tangem.domain.swap.models.SwapStatusModel +import com.tangem.utils.converter.TwoWayConverter + +internal class SavedSwapStatusConverter : TwoWayConverter { + + override fun convert(value: SwapStatusDTO) = SwapStatusModel( + providerId = value.providerId, + status = SwapStatus.entries.firstOrNull { + it.name.lowercase() == value.status?.name?.lowercase() + }, + txId = value.txExternalId, + txExternalUrl = value.txExternalUrl, + txExternalId = value.txExternalId, + refundNetwork = value.refundNetwork, + refundContractAddress = value.refundContractAddress, + createdAt = value.createdAt, + averageDuration = value.averageDuration, + ) + + override fun convertBack(value: SwapStatusModel) = SwapStatusDTO( + providerId = value.providerId, + status = SavedSwapStatus.entries.firstOrNull { + it.name.lowercase() == value.status?.name?.lowercase() + }, + txId = value.txExternalId, + txExternalUrl = value.txExternalUrl, + txExternalId = value.txExternalId, + refundNetwork = value.refundNetwork, + refundContractAddress = value.refundContractAddress, + createdAt = value.createdAt, + averageDuration = value.averageDuration, + ) +} \ No newline at end of file diff --git a/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionConverter.kt b/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionConverter.kt new file mode 100644 index 0000000000..ab7b21e61d --- /dev/null +++ b/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionConverter.kt @@ -0,0 +1,60 @@ +package com.tangem.data.swap.converter.transaction + +import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory +import com.tangem.data.swap.models.SwapStatusDTO +import com.tangem.data.swap.models.SwapTransactionDTO +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.swap.models.SwapTransactionModel +import com.tangem.utils.converter.TwoWayConverter + +internal class SavedSwapTransactionConverter( + excludedBlockchains: ExcludedBlockchains, +) : TwoWayConverter { + private val responseCryptoCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains) + private val statusConverter by lazy(LazyThreadSafetyMode.NONE) { + SavedSwapStatusConverter() + } + + override fun convert(value: SwapTransactionModel) = SwapTransactionDTO( + txId = value.txId, + timestamp = value.timestamp, + fromCryptoAmount = value.fromCryptoAmount, + toCryptoAmount = value.toCryptoAmount, + provider = value.provider, + status = value.status, + ) + + override fun convertBack(value: SwapTransactionDTO) = SwapTransactionModel( + txId = value.txId, + timestamp = value.timestamp, + fromCryptoAmount = value.fromCryptoAmount, + toCryptoAmount = value.toCryptoAmount, + provider = value.provider, + status = value.status, + ) + + fun convertBack( + value: SwapTransactionDTO, + scanResponse: ScanResponse, + txStatuses: Map, + ): SwapTransactionModel { + val status = txStatuses[value.txId] + val refundCurrency = status?.refundTokensResponse?.let { id -> + responseCryptoCurrenciesFactory.createCurrency( + responseToken = id, + scanResponse = scanResponse, + ) + } + val statusWithRefundCurrency = status?.copy(refundCurrency = refundCurrency) + + return SwapTransactionModel( + txId = value.txId, + timestamp = value.timestamp, + fromCryptoAmount = value.fromCryptoAmount, + toCryptoAmount = value.toCryptoAmount, + provider = value.provider, + status = statusWithRefundCurrency?.let(statusConverter::convert), + ) + } +} \ No newline at end of file diff --git a/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionListConverter.kt b/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionListConverter.kt new file mode 100644 index 0000000000..176c1eb8f6 --- /dev/null +++ b/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionListConverter.kt @@ -0,0 +1,83 @@ +package com.tangem.data.swap.converter.transaction + +import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory +import com.tangem.data.common.currency.UserTokensResponseFactory +import com.tangem.data.swap.models.SwapStatusDTO +import com.tangem.data.swap.models.SwapTransactionDTO +import com.tangem.data.swap.models.SwapTransactionListDTO +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.swap.models.SwapTransactionListModel +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.converter.Converter + +internal class SavedSwapTransactionListConverter( + excludedBlockchains: ExcludedBlockchains, +) : Converter { + + private val responseCryptoCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains) + private val userTokensResponseFactory = UserTokensResponseFactory() + private val savedSwapTransactionConverter by lazy(LazyThreadSafetyMode.NONE) { + SavedSwapTransactionConverter(excludedBlockchains) + } + + override fun convert(value: SwapTransactionListModel) = SwapTransactionListDTO( + userWalletId = value.userWalletId, + fromCryptoCurrencyId = value.fromCryptoCurrencyId, + toCryptoCurrencyId = value.toCryptoCurrencyId, + fromTokensResponse = userTokensResponseFactory.createResponseToken( + value.fromCryptoCurrency, + ), + toTokensResponse = userTokensResponseFactory.createResponseToken( + value.toCryptoCurrency, + ), + transactions = savedSwapTransactionConverter.convertList(value.transactions), + ) + + fun convertBack( + value: SwapTransactionListDTO, + scanResponse: ScanResponse, + txStatuses: Map, + ): SwapTransactionListModel? { + val fromToken = value.fromTokensResponse + val toToken = value.toTokensResponse + return if (fromToken == null || toToken == null) { + null + } else { + val fromCryptoCurrency = responseCryptoCurrenciesFactory.createCurrency( + responseToken = fromToken, + scanResponse = scanResponse, + ) ?: return null + val toCryptoCurrency = responseCryptoCurrenciesFactory.createCurrency( + responseToken = toToken, + scanResponse = scanResponse, + ) ?: return null + + return SwapTransactionListModel( + transactions = value.transactions.map { tx -> + savedSwapTransactionConverter.convertBack(tx, scanResponse, txStatuses) + }, + userWalletId = value.userWalletId, + fromCryptoCurrencyId = value.fromCryptoCurrencyId, + toCryptoCurrencyId = value.toCryptoCurrencyId, + fromCryptoCurrency = fromCryptoCurrency, + toCryptoCurrency = toCryptoCurrency, + ) + } + } + + fun default( + userWalletId: UserWalletId, + fromCryptoCurrency: CryptoCurrency, + toCryptoCurrency: CryptoCurrency, + tokenTransactions: List, + ) = SwapTransactionListDTO( + userWalletId = userWalletId.stringValue, + fromCryptoCurrencyId = fromCryptoCurrency.id.value, + toCryptoCurrencyId = toCryptoCurrency.id.value, + fromTokensResponse = userTokensResponseFactory.createResponseToken(fromCryptoCurrency), + toTokensResponse = userTokensResponseFactory.createResponseToken(toCryptoCurrency), + transactions = tokenTransactions, + ) +} \ No newline at end of file diff --git a/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt b/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt index 0ee1971b25..bd75b9d1ad 100644 --- a/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt +++ b/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt @@ -1,9 +1,11 @@ package com.tangem.data.swap.di import com.squareup.moshi.Moshi +import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.data.express.converter.ExpressErrorConverter import com.tangem.data.swap.DefaultSwapErrorResolver import com.tangem.data.swap.DefaultSwapRepositoryV2 +import com.tangem.data.swap.DefaultSwapTransactionRepository import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.express.models.response.ExpressErrorResponse import com.tangem.datasource.di.NetworkMoshi @@ -11,6 +13,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.express.ExpressRepository import com.tangem.domain.swap.SwapErrorResolver import com.tangem.domain.swap.SwapRepositoryV2 +import com.tangem.domain.swap.SwapTransactionRepository import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -49,4 +52,18 @@ internal object SwapDataModule { currencyStatusOperations = currencyStatusOperations, ) } + + @Provides + @Singleton + fun provideSwapTransactionRepository( + coroutineDispatcher: CoroutineDispatcherProvider, + appPreferencesStore: AppPreferencesStore, + excludedBlockchains: ExcludedBlockchains, + ): SwapTransactionRepository { + return DefaultSwapTransactionRepository( + appPreferencesStore = appPreferencesStore, + dispatchers = coroutineDispatcher, + excludedBlockchains = excludedBlockchains, + ) + } } \ No newline at end of file diff --git a/data/swap/src/main/java/com/tangem/data/swap/models/LastSwappedCryptoCurrencyDTO.kt b/data/swap/src/main/java/com/tangem/data/swap/models/LastSwappedCryptoCurrencyDTO.kt new file mode 100644 index 0000000000..61d738acd0 --- /dev/null +++ b/data/swap/src/main/java/com/tangem/data/swap/models/LastSwappedCryptoCurrencyDTO.kt @@ -0,0 +1,12 @@ +package com.tangem.data.swap.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class LastSwappedCryptoCurrencyDTO( + @Json(name = "userWalletId") + val userWalletId: String, + @Json(name = "cryptoCurrencyId") + val cryptoCurrencyId: String, +) \ No newline at end of file diff --git a/data/swap/src/main/java/com/tangem/data/swap/models/SwapStatusDTO.kt b/data/swap/src/main/java/com/tangem/data/swap/models/SwapStatusDTO.kt new file mode 100644 index 0000000000..45c37a2005 --- /dev/null +++ b/data/swap/src/main/java/com/tangem/data/swap/models/SwapStatusDTO.kt @@ -0,0 +1,78 @@ +package com.tangem.data.swap.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.domain.models.currency.CryptoCurrency +import org.joda.time.DateTime + +@JsonClass(generateAdapter = true) +internal data class SwapStatusDTO( + @Json(name = "providerId") + val providerId: String, + @Json(name = "status") + val status: SavedSwapStatus? = null, + @Json(name = "txId") + val txId: String? = null, + @Json(name = "txExternalUrl") + val txExternalUrl: String? = null, + @Json(name = "txExternalId") + val txExternalId: String? = null, + @Json(name = "refundNetwork") + val refundNetwork: String? = null, + @Json(name = "refundContractAddress") + val refundContractAddress: String? = null, + @Json(name = "refundTokensResponse") + val refundTokensResponse: UserTokensResponse.Token? = null, + @Json(ignore = true) + val refundCurrency: CryptoCurrency? = null, + @Json(name = "createdAt") + val createdAt: DateTime? = null, + @Json(name = "averageDuration") + val averageDuration: Int? = null, +) + +@JsonClass(generateAdapter = false) +internal enum class SavedSwapStatus { + @Json(name = "New") + New, + + @Json(name = "Waiting") + Waiting, + + @Json(name = "WaitingTxHash") + WaitingTxHash, + + @Json(name = "Confirming") + Confirming, + + @Json(name = "Verifying") + Verifying, + + @Json(name = "Exchanging") + Exchanging, + + @Json(name = "Failed") + Failed, + + @Json(name = "Sending") + Sending, + + @Json(name = "Finished") + Finished, + + @Json(name = "Refunded") + Refunded, + + @Json(name = "Cancelled") + Cancelled, + + @Json(name = "TxFailed") + TxFailed, + + @Json(name = "Unknown") + Unknown, + + @Json(name = "Paused") + Paused, +} \ No newline at end of file diff --git a/data/swap/src/main/java/com/tangem/data/swap/models/SwapTransactionListDTO.kt b/data/swap/src/main/java/com/tangem/data/swap/models/SwapTransactionListDTO.kt new file mode 100644 index 0000000000..e5249da91f --- /dev/null +++ b/data/swap/src/main/java/com/tangem/data/swap/models/SwapTransactionListDTO.kt @@ -0,0 +1,40 @@ +package com.tangem.data.swap.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.swap.models.SwapStatusModel +import java.math.BigDecimal + +@JsonClass(generateAdapter = true) +internal data class SwapTransactionListDTO( + @Json(name = "userWalletId") + val userWalletId: String, + @Json(name = "fromCryptoCurrencyId") + val fromCryptoCurrencyId: String, + @Json(name = "toCryptoCurrencyId") + val toCryptoCurrencyId: String, + @Json(name = "fromTokensResponse") + val fromTokensResponse: UserTokensResponse.Token? = null, + @Json(name = "toTokensResponse") + val toTokensResponse: UserTokensResponse.Token? = null, + @Json(name = "transactions") + val transactions: List, +) + +@JsonClass(generateAdapter = true) +internal data class SwapTransactionDTO( + @Json(name = "txId") + val txId: String, + @Json(name = "timestamp") + val timestamp: Long, + @Json(name = "fromCryptoAmount") + val fromCryptoAmount: BigDecimal, + @Json(name = "toCryptoAmount") + val toCryptoAmount: BigDecimal, + @Json(name = "provider") + val provider: ExpressProvider, + @Json(name = "status") + val status: SwapStatusModel? = null, +) \ No newline at end of file diff --git a/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapStatusModel.kt b/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapStatusModel.kt new file mode 100644 index 0000000000..53cfd45e13 --- /dev/null +++ b/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapStatusModel.kt @@ -0,0 +1,62 @@ +package com.tangem.domain.swap.models + +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.domain.models.currency.CryptoCurrency +import org.joda.time.DateTime + +/** + * Swapped status model + */ +data class SwapStatusModel( + val providerId: String, + val status: SwapStatus? = null, + val txId: String? = null, + val txExternalUrl: String? = null, + val txExternalId: String? = null, + val refundNetwork: String? = null, + val refundContractAddress: String? = null, + val refundTokensResponse: UserTokensResponse.Token? = null, + val refundCurrency: CryptoCurrency? = null, + val createdAt: DateTime? = null, + val averageDuration: Int? = null, +) { + val hasLongTime: Boolean + get() = if (createdAt != null && averageDuration != null) { + DateTime.now().minusSeconds(averageDuration * 5) > createdAt + } else { + false + } +} + +enum class SwapStatus { + New, + Waiting, + WaitingTxHash, + Confirming, + Verifying, + Exchanging, + Failed, + Sending, + Finished, + Refunded, + Cancelled, + TxFailed, + Unknown, + Paused, + ; + + val isTerminal: Boolean + get() = this == Refunded || + this == Finished || + this == Cancelled || + this == TxFailed || + this == Paused || + this == Unknown + + val isAutoDisposable: Boolean + get() = this == Finished + + companion object { + fun SwapStatus?.isFailed(): Boolean = this == Failed || this == TxFailed + } +} \ No newline at end of file diff --git a/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapTransactionListModel.kt b/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapTransactionListModel.kt new file mode 100644 index 0000000000..e296ca6fe3 --- /dev/null +++ b/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapTransactionListModel.kt @@ -0,0 +1,29 @@ +package com.tangem.domain.swap.models + +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.models.currency.CryptoCurrency +import java.math.BigDecimal + +/** + * List of saved swap transactions + */ +data class SwapTransactionListModel( + val userWalletId: String, + val fromCryptoCurrencyId: String, + val toCryptoCurrencyId: String, + val fromCryptoCurrency: CryptoCurrency, + val toCryptoCurrency: CryptoCurrency, + val transactions: List, +) + +/** + * Saved swap transactions + */ +data class SwapTransactionModel( + val txId: String, + val timestamp: Long, + val fromCryptoAmount: BigDecimal, + val toCryptoAmount: BigDecimal, + val provider: ExpressProvider, + val status: SwapStatusModel? = null, +) \ No newline at end of file diff --git a/domain/swap/src/main/java/com/tangem/domain/swap/SwapTransactionRepository.kt b/domain/swap/src/main/java/com/tangem/domain/swap/SwapTransactionRepository.kt new file mode 100644 index 0000000000..5efa512937 --- /dev/null +++ b/domain/swap/src/main/java/com/tangem/domain/swap/SwapTransactionRepository.kt @@ -0,0 +1,80 @@ +package com.tangem.domain.swap + +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.swap.models.SwapStatusModel +import com.tangem.domain.swap.models.SwapTransactionListModel +import com.tangem.domain.swap.models.SwapTransactionModel +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +/** + * Swap repository for statuses + */ +interface SwapTransactionRepository { + + /** + * Store new swap transaction + * + * @param userWalletId selected user wallet id + * @param fromCryptoCurrency currency swap from + * @param toCryptoCurrency currency swap to + * @param transaction swap transaction + */ + suspend fun storeTransaction( + userWalletId: UserWalletId, + fromCryptoCurrency: CryptoCurrency, + toCryptoCurrency: CryptoCurrency, + transaction: SwapTransactionModel, + ) + + /** + * Get list of swap transactions + * + * @param userWallet selected user wallet + * @param cryptoCurrencyId transactions for specific crypto currency + */ + suspend fun getTransactions( + userWallet: UserWallet, + cryptoCurrencyId: CryptoCurrency.ID, + ): Flow?> + + /** + * Remove stores swap transaction + * + * @param userWalletId selected user wallet id + * @param fromCryptoCurrency currency swap from + * @param toCryptoCurrency currency swap to + * @param txId transaction id to remove + */ + suspend fun removeTransaction( + userWalletId: UserWalletId, + fromCryptoCurrency: CryptoCurrency, + toCryptoCurrency: CryptoCurrency, + txId: String, + ) + + /** + * Update swap transaction + * + * @param txId transaction id to update + * @param status new transaction status + * @param refundTokenCurrency refund token + */ + suspend fun storeTransactionState(txId: String, status: SwapStatusModel, refundTokenCurrency: CryptoCurrency?) + + /** + * Save last swapped crypto currency token + * + * @param userWalletId selected user wallet id + * @param cryptoCurrencyId last swapped currency id + */ + suspend fun storeLastSwappedCryptoCurrencyId(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID) + + /** + * Get last swapped crypto currency token + * + * @param userWalletId selected user wallet id + */ + suspend fun getLastSwappedCryptoCurrencyId(userWalletId: UserWalletId): String? +} \ No newline at end of file