Updated on 2026-08-14
This commit is contained in:
parent
3c3af4cb34
commit
6a5cb8f8d5
14 changed files with 341 additions and 48 deletions
|
|
@ -49,6 +49,6 @@ interface TangemExpressApi {
|
|||
@Query("toAddress") toAddress: String,
|
||||
): ApiResponse<ExchangeDataResponse>
|
||||
|
||||
@GET("exchange-result")
|
||||
suspend fun getExchangeResults(@Query("txId") txId: String): ApiResponse<ExchangeResultsResponse>
|
||||
@GET("exchange-status")
|
||||
suspend fun getExchangeStatus(@Query("txId") txId: String): ApiResponse<ExchangeStatusResponse>
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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<Preferences> */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<SavedSwapTransactionListModel>? = 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<List<SavedSwapTransactionListModel>?> {
|
||||
return appPreferencesStore.getObjectList<SavedSwapTransactionListModel>(
|
||||
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<SavedSwapTransactionListModel>? = 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<SavedSwapTransactionListModel>.updateList(
|
||||
userWalletId: UserWalletId,
|
||||
fromCryptoCurrencyId: CryptoCurrency.ID,
|
||||
toCryptoCurrencyId: CryptoCurrency.ID,
|
||||
transactions: List<SavedSwapTransactionModel>,
|
||||
): List<SavedSwapTransactionListModel> {
|
||||
return addOrReplace(
|
||||
item = SavedSwapTransactionListModel(
|
||||
userWalletId = userWalletId.stringValue,
|
||||
fromCryptoCurrencyId = fromCryptoCurrencyId.value,
|
||||
toCryptoCurrencyId = toCryptoCurrencyId.value,
|
||||
transactions = transactions,
|
||||
),
|
||||
predicate = {
|
||||
it.checkId(
|
||||
checkUserWalletId = userWalletId,
|
||||
fromCurrencyId = fromCryptoCurrencyId,
|
||||
toCurrencyId = toCryptoCurrencyId,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UnknownError, ExchangeStatusModel> {
|
||||
return withContext(coroutineDispatcher.io) {
|
||||
either {
|
||||
catch(
|
||||
{
|
||||
exchangeStatusConverter.convert(
|
||||
tangemExpressApi
|
||||
.getExchangeStatus(txId)
|
||||
.getOrThrow(),
|
||||
)
|
||||
},
|
||||
{
|
||||
raise(UnknownError(it.message))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getRates(currencyId: String, tokenIds: List<String>): Map<String, Double> {
|
||||
// workaround cause backend do not return arbitrum and optimism rates
|
||||
val addedTokens = if (tokenIds.contains(OPTIMISM_ID) || tokenIds.contains(ARBITRUM_ID)) {
|
||||
|
|
|
|||
|
|
@ -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<ExchangeStatusResponse, ExchangeStatusModel> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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<Currency>
|
||||
|
||||
suspend fun getExchangeStatus(txId: String): Either<UnknownError, ExchangeStatusModel>
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun findBestQuote(
|
||||
fromContractAddress: String,
|
||||
|
|
|
|||
|
|
@ -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<List<SavedSwapTransactionListModel>?>
|
||||
|
||||
suspend fun removeTransaction(
|
||||
userWalletId: UserWalletId,
|
||||
fromCryptoCurrencyId: CryptoCurrency.ID,
|
||||
toCryptoCurrencyId: CryptoCurrency.ID,
|
||||
txId: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<SavedSwapTransactionModel>,
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue