diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt index b56262221a..b5ef12459c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt @@ -49,6 +49,6 @@ interface TangemExpressApi { @Query("toAddress") toAddress: String, ): ApiResponse - @GET("exchange-result") - suspend fun getExchangeResults(@Query("txId") txId: String): ApiResponse + @GET("exchange-status") + suspend fun getExchangeStatus(@Query("txId") txId: String): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeResultsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeResultsResponse.kt deleted file mode 100644 index c9b10f881e..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeResultsResponse.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.tangem.datasource.api.express.models.response - -import com.squareup.moshi.Json - -data class ExchangeResultsResponse( - @Json(name = "status") - val status: ExchangeResultsStatus, - - @Json(name = "externalStatus") - val externalStatus: String, - - @Json(name = "externalTxUrl") - val externalTxUrl: String, - - @Json(name = "error") - val error: ExchangeResultsError?, -) - -enum class ExchangeResultsStatus { - @Json(name = "processing") - PROCESSING, - - @Json(name = "done") - DONE, - - @Json(name = "failed") - FAILED, - - @Json(name = "refunded") - REFUNDED, - - @Json(name = "verificationRequired") - VERIFICATION_REQUIRED, -} - -data class ExchangeResultsError( - @Json(name = "code") - val code: Int, - - @Json(name = "description") - val description: String, -) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt new file mode 100644 index 0000000000..94cd9cbf1f --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt @@ -0,0 +1,59 @@ +package com.tangem.datasource.api.express.models.response + +import com.squareup.moshi.Json + +data class ExchangeStatusResponse( + + @Json(name = "providerId") + val providerId: String, + + @Json(name = "externalTxId") + val externalTxId: String, + + @Json(name = "externalTxStatus") + val externalStatus: ExchangeStatus, + + @Json(name = "externalTxUrl") + val externalTxUrl: String, + + @Json(name = "error") + val error: ExchangeStatusError?, +) + +enum class ExchangeStatus { + + @Json(name = "new") + NEW, + + @Json(name = "waiting") + WAITING, + + @Json(name = "confirming") + CONFIRMING, + + @Json(name = "exchanging") + EXCHANGING, + + @Json(name = "sending") + SENDING, + + @Json(name = "finished") + FINISHED, + + @Json(name = "failed") + FAILED, + + @Json(name = "refunded") + REFUNDED, + + @Json(name = "verifying") + VERIFYING, +} + +data class ExchangeStatusError( + @Json(name = "code") + val code: Int, + + @Json(name = "description") + val description: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index d81065c147..7f43d0a7e8 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -32,6 +32,8 @@ object PreferencesKeys { val SELECTED_APP_CURRENCY_KEY by lazy { stringPreferencesKey(name = "selectedAppCurrency") } val BALANCE_HIDING_SETTINGS_KEY by lazy { stringPreferencesKey(name = "balanceHidingSettings") } + + val SWAP_TRANSACTIONS_KEY by lazy { stringPreferencesKey(name = "swapTransactions") } } /** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore */ diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index 2d753c6fa2..53dddfc5a9 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -8,15 +8,19 @@ plugins { dependencies { + /** AndroidX */ + implementation(deps.androidx.datastore) + /** Project*/ - implementation(project(":core:datasource")) - implementation(project(":core:utils")) - implementation(project(":features:swap:domain")) + implementation(projects.core.datasource) + implementation(projects.core.utils) + implementation(projects.features.swap.domain) /** Network */ implementation(deps.retrofit) implementation(deps.moshi) implementation(deps.moshi.kotlin) + implementation(deps.arrow.core) /** Domain */ implementation(projects.domain.tokens.models) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt new file mode 100644 index 0000000000..63c8aecb72 --- /dev/null +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt @@ -0,0 +1,163 @@ +package com.tangem.feature.swap + +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.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.swap.domain.SwapTransactionRepository +import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel +import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel +import com.tangem.utils.extensions.addOrReplace +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class DefaultSwapTransactionRepository( + private val appPreferencesStore: AppPreferencesStore, +) : SwapTransactionRepository { + + override suspend fun storeTransaction( + userWalletId: UserWalletId, + fromCryptoCurrencyId: CryptoCurrency.ID, + toCryptoCurrencyId: CryptoCurrency.ID, + transaction: SavedSwapTransactionModel, + ) { + appPreferencesStore.editData { mutablePreferences -> + val savedTransactions: List? = mutablePreferences.getObjectList( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + ) + + val tokenTransactions = savedTransactions + ?.firstOrNull { + it.checkId( + checkUserWalletId = userWalletId, + fromCurrencyId = fromCryptoCurrencyId, + toCurrencyId = toCryptoCurrencyId, + ) + } + ?.transactions + ?.addOrReplace( + item = transaction, + predicate = { it.txId == transaction.txId }, + ) ?: listOf(transaction) + + mutablePreferences.setObject( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + value = savedTransactions?.updateList( + userWalletId = userWalletId, + fromCryptoCurrencyId = fromCryptoCurrencyId, + toCryptoCurrencyId = toCryptoCurrencyId, + transactions = tokenTransactions, + ) ?: listOf( + SavedSwapTransactionListModel( + userWalletId = userWalletId.stringValue, + fromCryptoCurrencyId = fromCryptoCurrencyId.value, + toCryptoCurrencyId = toCryptoCurrencyId.value, + transactions = tokenTransactions, + ), + ), + ) + } + } + + override fun getTransactions( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + ): Flow?> { + return appPreferencesStore.getObjectList( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + ).map { savedTransactions -> + savedTransactions + ?.filter { + it.userWalletId == userWalletId.stringValue && + ( + it.toCryptoCurrencyId == cryptoCurrencyId.value || + it.fromCryptoCurrencyId == cryptoCurrencyId.value + ) + } + } + } + + override suspend fun removeTransaction( + userWalletId: UserWalletId, + fromCryptoCurrencyId: CryptoCurrency.ID, + toCryptoCurrencyId: CryptoCurrency.ID, + txId: String, + ) { + appPreferencesStore.editData { mutablePreferences -> + val savedList: List? = mutablePreferences.getObjectList( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + ) + val tokenTransactions = savedList + ?.first { + it.checkId( + checkUserWalletId = userWalletId, + fromCurrencyId = fromCryptoCurrencyId, + toCurrencyId = toCryptoCurrencyId, + ) + } + ?.transactions + ?.dropWhile { it.txId == txId } + + val editedList = + if (tokenTransactions.isNullOrEmpty()) { + savedList?.dropWhile { + it.checkId( + checkUserWalletId = userWalletId, + fromCurrencyId = fromCryptoCurrencyId, + toCurrencyId = toCryptoCurrencyId, + ) + } + } else { + savedList.updateList( + userWalletId = userWalletId, + fromCryptoCurrencyId = fromCryptoCurrencyId, + toCryptoCurrencyId = toCryptoCurrencyId, + transactions = tokenTransactions, + ) + } + + if (editedList.isNullOrEmpty()) { + mutablePreferences.remove(key = PreferencesKeys.SWAP_TRANSACTIONS_KEY) + } else { + mutablePreferences.setObject( + key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, + value = editedList, + ) + } + } + } + + private fun SavedSwapTransactionListModel.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, + fromCryptoCurrencyId: CryptoCurrency.ID, + toCryptoCurrencyId: CryptoCurrency.ID, + transactions: List, + ): List { + return addOrReplace( + item = SavedSwapTransactionListModel( + userWalletId = userWalletId.stringValue, + fromCryptoCurrencyId = fromCryptoCurrencyId.value, + toCryptoCurrencyId = toCryptoCurrencyId.value, + transactions = transactions, + ), + predicate = { + it.checkId( + checkUserWalletId = userWalletId, + fromCurrencyId = fromCryptoCurrencyId, + toCurrencyId = toCryptoCurrencyId, + ) + }, + ) + } +} \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index 9ea783fc6b..338fafe196 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -1,5 +1,8 @@ package com.tangem.feature.swap +import arrow.core.Either +import arrow.core.raise.catch +import arrow.core.raise.either import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Approver import com.tangem.blockchain.common.Blockchain @@ -52,6 +55,7 @@ internal class SwapRepositoryImpl @Inject constructor( private val leastTokenInfoConverter = LeastTokenInfoConverter() private val swapPairInfoConverter = SwapPairInfoConverter() private val cryptoCurrencyFactory = CryptoCurrencyFactory() + private val exchangeStatusConverter = ExchangeStatusConverter() override suspend fun getPairs( initialCurrency: LeastTokenInfo, @@ -103,6 +107,25 @@ internal class SwapRepositoryImpl @Inject constructor( ).getOrThrow() } + override suspend fun getExchangeStatus(txId: String): Either { + return withContext(coroutineDispatcher.io) { + either { + catch( + { + exchangeStatusConverter.convert( + tangemExpressApi + .getExchangeStatus(txId) + .getOrThrow(), + ) + }, + { + raise(UnknownError(it.message)) + }, + ) + } + } + } + override suspend fun getRates(currencyId: String, tokenIds: List): Map { // workaround cause backend do not return arbitrum and optimism rates val addedTokens = if (tokenIds.contains(OPTIMISM_ID) || tokenIds.contains(ARBITRUM_ID)) { diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt new file mode 100644 index 0000000000..f5901e8c69 --- /dev/null +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt @@ -0,0 +1,19 @@ +package com.tangem.feature.swap.converters + +import com.tangem.datasource.api.express.models.response.ExchangeStatusResponse +import com.tangem.feature.swap.domain.models.domain.ExchangeStatus +import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel +import com.tangem.utils.converter.Converter + +internal class ExchangeStatusConverter : Converter { + override fun convert(value: ExchangeStatusResponse): ExchangeStatusModel { + return ExchangeStatusModel( + providerId = value.providerId, + status = ExchangeStatus.values().firstOrNull { + it.name.lowercase() == value.externalStatus.name.lowercase() + }, + txId = value.externalTxId, + txUrl = value.externalTxUrl, + ) + } +} \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt index 4c34db7b17..b30b20f817 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt @@ -7,11 +7,14 @@ import com.tangem.datasource.api.oneinch.OneInchApiFactory import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.di.NetworkMoshi +import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.feature.swap.SwapRepositoryImpl import com.tangem.feature.swap.converters.ErrorsDataConverter +import com.tangem.feature.swap.DefaultSwapTransactionRepository import com.tangem.feature.swap.domain.SwapRepository +import com.tangem.feature.swap.domain.SwapTransactionRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -47,6 +50,14 @@ internal class SwapDataModule { ) } + @Provides + @Singleton + fun provideSwapTransactionRepository(appPreferencesStore: AppPreferencesStore): SwapTransactionRepository { + return DefaultSwapTransactionRepository( + appPreferencesStore = appPreferencesStore, + ) + } + @Provides @Singleton internal fun provideErrorsConverter(@NetworkMoshi moshi: Moshi): ErrorsDataConverter { diff --git a/features/swap/domain/build.gradle.kts b/features/swap/domain/build.gradle.kts index 4f4d42fe13..f3841b400d 100644 --- a/features/swap/domain/build.gradle.kts +++ b/features/swap/domain/build.gradle.kts @@ -27,9 +27,11 @@ dependencies { implementation(projects.domain.legacy) implementation(projects.domain.demo) implementation(projects.domain.card) + implementation(projects.domain.appCurrency.models) /** Core modules */ implementation(projects.core.utils) + implementation(projects.core.ui) /** Feature Apis */ implementation(projects.features.wallet.api) @@ -40,4 +42,5 @@ dependencies { implementation(deps.arrow.core) implementation(deps.timber) implementation(deps.tangem.blockchain) + implementation(deps.moshi) } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index 13650fe4fe..c96bb13b19 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.domain +import arrow.core.Either import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.domain.models.data.AggregatedSwapDataModel @@ -14,6 +15,8 @@ interface SwapRepository { suspend fun getExchangeableTokens(networkId: String): List + suspend fun getExchangeStatus(txId: String): Either + @Suppress("LongParameterList") suspend fun findBestQuote( fromContractAddress: String, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt new file mode 100644 index 0000000000..d89914d537 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt @@ -0,0 +1,29 @@ +package com.tangem.feature.swap.domain + +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel +import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel +import kotlinx.coroutines.flow.Flow + +interface SwapTransactionRepository { + + suspend fun storeTransaction( + userWalletId: UserWalletId, + fromCryptoCurrencyId: CryptoCurrency.ID, + toCryptoCurrencyId: CryptoCurrency.ID, + transaction: SavedSwapTransactionModel, + ) + + fun getTransactions( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + ): Flow?> + + suspend fun removeTransaction( + userWalletId: UserWalletId, + fromCryptoCurrencyId: CryptoCurrency.ID, + toCryptoCurrencyId: CryptoCurrency.ID, + txId: String, + ) +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt index a74e8fbb6a..dc5c5738be 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt @@ -1,7 +1,7 @@ package com.tangem.feature.swap.domain.models.domain data class ExchangeStatusModel( - val providerId: Int, + val providerId: String, val status: ExchangeStatus? = null, val txId: String? = null, val txUrl: String? = null, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SavedSwapTransactionListModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SavedSwapTransactionListModel.kt new file mode 100644 index 0000000000..6b59b50197 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SavedSwapTransactionListModel.kt @@ -0,0 +1,19 @@ +package com.tangem.feature.swap.domain.models.domain + +data class SavedSwapTransactionListModel( + val userWalletId: String, + val fromCryptoCurrencyId: String, + val toCryptoCurrencyId: String, + val transactions: List, +) + +data class SavedSwapTransactionModel( + val txId: String, + val timestamp: Long, + val fromCryptoAmount: String, + val toCryptoAmount: String, + val provider: SwapProvider, + val toFiatAmount: String? = null, + val fromFiatAmount: String? = null, + val status: ExchangeStatusModel? = null, +) \ No newline at end of file