From 3dc6ff9548cf80b90fe27739be3bd46801483bad Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Jun 2025 18:21:03 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../data/express/DefaultExpressRepository.kt | 36 ++++++++ .../converter/ExpressErrorConverter.kt | 91 +++++++++++++++++++ .../converter/ExpressProviderConverter.kt | 33 +++++++ .../data/express/di/ExpressDataModule.kt | 43 +++++++++ .../error/DefaultExpressErrorResolver.kt | 20 ++++ .../domain/express/models/ExpressError.kt | 64 +++++++++++++ .../domain/express/models/ExpressProvider.kt | 43 +++++++++ .../express/models/ExpressProviderType.kt | 24 +++++ .../domain/express/models/ExpressRateType.kt | 18 ++++ .../domain/express/ExpressErrorResolver.kt | 11 +++ .../domain/express/ExpressRepository.kt | 17 ++++ features/swap-v2/impl/build.gradle.kts | 1 - 12 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 data/express/src/main/java/com/tangem/data/express/DefaultExpressRepository.kt create mode 100644 data/express/src/main/java/com/tangem/data/express/converter/ExpressErrorConverter.kt create mode 100644 data/express/src/main/java/com/tangem/data/express/converter/ExpressProviderConverter.kt create mode 100644 data/express/src/main/java/com/tangem/data/express/di/ExpressDataModule.kt create mode 100644 data/express/src/main/java/com/tangem/data/express/error/DefaultExpressErrorResolver.kt create mode 100644 domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressError.kt create mode 100644 domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressProvider.kt create mode 100644 domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressProviderType.kt create mode 100644 domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressRateType.kt create mode 100644 domain/express/src/main/java/com/tangem/domain/express/ExpressErrorResolver.kt create mode 100644 domain/express/src/main/java/com/tangem/domain/express/ExpressRepository.kt diff --git a/data/express/src/main/java/com/tangem/data/express/DefaultExpressRepository.kt b/data/express/src/main/java/com/tangem/data/express/DefaultExpressRepository.kt new file mode 100644 index 0000000000..4963d92a82 --- /dev/null +++ b/data/express/src/main/java/com/tangem/data/express/DefaultExpressRepository.kt @@ -0,0 +1,36 @@ +package com.tangem.data.express + +import com.tangem.data.common.api.safeApiCall +import com.tangem.data.express.converter.ExpressProviderConverter +import com.tangem.datasource.api.common.response.getOrThrow +import com.tangem.datasource.api.express.TangemExpressApi +import com.tangem.datasource.exchangeservice.swap.ExpressUtils +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.express.ExpressRepository +import com.tangem.domain.wallets.models.UserWallet +import timber.log.Timber + +internal class DefaultExpressRepository( + private val tangemExpressApi: TangemExpressApi, + private val appPreferencesStore: AppPreferencesStore, +) : ExpressRepository { + + override suspend fun getProviders(userWallet: UserWallet): List { + return safeApiCall( + call = { + tangemExpressApi.getProviders( + userWalletId = userWallet.walletId.stringValue, + refCode = ExpressUtils.getRefCode( + userWallet = userWallet, + appPreferencesStore = appPreferencesStore, + ), + ).getOrThrow().map(ExpressProviderConverter()::convert) + }, + onError = { + Timber.w(it, "Unable to fetch express providers") + throw it + }, + ) + } +} \ No newline at end of file diff --git a/data/express/src/main/java/com/tangem/data/express/converter/ExpressErrorConverter.kt b/data/express/src/main/java/com/tangem/data/express/converter/ExpressErrorConverter.kt new file mode 100644 index 0000000000..b191da795b --- /dev/null +++ b/data/express/src/main/java/com/tangem/data/express/converter/ExpressErrorConverter.kt @@ -0,0 +1,91 @@ +package com.tangem.data.express.converter + +import com.squareup.moshi.JsonAdapter +import com.tangem.datasource.api.express.models.response.ExpressErrorResponse +import com.tangem.domain.express.models.ExpressError +import com.tangem.utils.converter.Converter +import com.tangem.datasource.api.express.models.response.ExpressError as ExpressErrorDTO + +class ExpressErrorConverter( + private val jsonAdapter: JsonAdapter, +) : Converter { + + @Suppress("MagicNumber", "CyclomaticComplexMethod") + override fun convert(value: String): ExpressError { + try { + val error: ExpressErrorDTO = jsonAdapter.fromJson(value)?.error ?: return ExpressError.UnknownError + + return when (error.code) { + 2010 -> ExpressError.BadRequest(code = error.code) + 2020 -> ExpressError.Forbidden(code = error.code) + + 2200 -> ExpressError.InternalError(code = error.code) + 2210 -> ExpressError.ProviderNotFoundError(code = error.code) + 2220 -> ExpressError.ProviderNotActiveError(code = error.code) + 2230 -> ExpressError.ProviderNotAvailableError(code = error.code) + 2231 -> ExpressError.ProviderInternalError(code = error.code) + 2240 -> ExpressError.ExchangeNotPossibleError(code = error.code) + 2250 -> error.toTooSmallAmountError() + 2251 -> error.toTooBigAmountError() + 2260 -> error.toNotEnoughAllowanceError() + 2270 -> ExpressError.NotEnoughBalanceError(code = error.code) + 2280 -> ExpressError.InvalidAddressError(code = error.code) + 2290 -> error.toInvalidFromDecimalsError() + + 2320 -> error.toProviderDifferentAmountError() + else -> ExpressError.DataError(error.code, error.description) + } + } catch (e: Exception) { + return ExpressError.UnknownError + } + } + + private fun ExpressErrorDTO.toTooSmallAmountError(): ExpressError { + val minAmount = value?.minAmount ?: return ExpressError.DataError(code, description) + val decimals = value?.decimals ?: return ExpressError.DataError(code, description) + val requiredAmount = minAmount.toBigDecimalOrNull()?.movePointLeft(decimals) + ?: return ExpressError.DataError(code, description) + + return ExpressError.AmountError.TooSmallError(code = code, amount = requiredAmount) + } + + private fun ExpressErrorDTO.toTooBigAmountError(): ExpressError { + val maxAmount = value?.maxAmount ?: return ExpressError.DataError(code, description) + val decimals = value?.decimals ?: return ExpressError.DataError(code, description) + val requiredAmount = maxAmount.toBigDecimalOrNull()?.movePointLeft(decimals) + ?: return ExpressError.DataError(code, description) + + return ExpressError.AmountError.TooBigError(code = code, amount = requiredAmount) + } + + private fun ExpressErrorDTO.toNotEnoughAllowanceError(): ExpressError { + val currentAllowance = value?.currentAllowance ?: return ExpressError.DataError(code, description) + + return ExpressError.AmountError.NotEnoughAllowanceError(code = code, amount = currentAllowance) + } + + private fun ExpressErrorDTO.toInvalidFromDecimalsError(): ExpressError { + val receivedFromDecimals = value?.receivedFromDecimals ?: return ExpressError.DataError(code, description) + val expressFromDecimals = value?.expressFromDecimals ?: return ExpressError.DataError(code, description) + + return ExpressError.InvalidFromDecimalsError( + code = code, + receivedFromDecimals = receivedFromDecimals, + expressFromDecimals = expressFromDecimals, + ) + } + + private fun ExpressErrorDTO.toProviderDifferentAmountError(): ExpressError { + val decimals = value?.decimals ?: return ExpressError.DataError(code, description) + val fromAmount = value?.fromAmount?.toBigDecimalOrNull() ?: return ExpressError.DataError(code, description) + val fromAmountProvider = value?.fromAmountProvider?.toBigDecimalOrNull() + ?: return ExpressError.DataError(code, description) + + return ExpressError.ProviderDifferentAmountError( + code = code, + decimals = decimals, + fromAmount = fromAmount.movePointLeft(decimals), + fromProviderAmount = fromAmountProvider.movePointLeft(decimals), + ) + } +} \ No newline at end of file diff --git a/data/express/src/main/java/com/tangem/data/express/converter/ExpressProviderConverter.kt b/data/express/src/main/java/com/tangem/data/express/converter/ExpressProviderConverter.kt new file mode 100644 index 0000000000..36c5e88460 --- /dev/null +++ b/data/express/src/main/java/com/tangem/data/express/converter/ExpressProviderConverter.kt @@ -0,0 +1,33 @@ +package com.tangem.data.express.converter + +import com.tangem.datasource.api.express.models.response.ExchangeProvider +import com.tangem.datasource.api.express.models.response.ExchangeProviderType +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.express.models.ExpressProviderType +import com.tangem.utils.converter.Converter + +internal class ExpressProviderConverter : Converter { + + override fun convert(value: ExchangeProvider): ExpressProvider { + return ExpressProvider( + providerId = value.id, + rateTypes = emptyList(), + name = value.name, + type = convertExchangeType(value.type), + imageLarge = value.imageLargeUrl, + termsOfUse = value.termsOfUse, + privacyPolicy = value.privacyPolicy, + isRecommended = value.isRecommended, + slippage = value.slippage, + ) + } + + private fun convertExchangeType(type: ExchangeProviderType): ExpressProviderType { + return when (type) { + ExchangeProviderType.DEX -> ExpressProviderType.DEX + ExchangeProviderType.CEX -> ExpressProviderType.CEX + ExchangeProviderType.DEX_BRIDGE -> ExpressProviderType.DEX_BRIDGE + ExchangeProviderType.ONRAMP -> ExpressProviderType.ONRAMP + } + } +} \ No newline at end of file diff --git a/data/express/src/main/java/com/tangem/data/express/di/ExpressDataModule.kt b/data/express/src/main/java/com/tangem/data/express/di/ExpressDataModule.kt new file mode 100644 index 0000000000..75bfe50bf5 --- /dev/null +++ b/data/express/src/main/java/com/tangem/data/express/di/ExpressDataModule.kt @@ -0,0 +1,43 @@ +package com.tangem.data.express.di + +import com.squareup.moshi.Moshi +import com.tangem.data.express.DefaultExpressRepository +import com.tangem.data.express.converter.ExpressErrorConverter +import com.tangem.data.express.error.DefaultExpressErrorResolver +import com.tangem.datasource.api.express.TangemExpressApi +import com.tangem.datasource.api.express.models.response.ExpressErrorResponse +import com.tangem.datasource.di.NetworkMoshi +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.domain.express.ExpressErrorResolver +import com.tangem.domain.express.ExpressRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object ExpressDataModule { + + @Provides + @Singleton + fun provideExpressErrorResolver(@NetworkMoshi moshi: Moshi): ExpressErrorResolver { + val jsonAdapter = moshi.adapter(ExpressErrorResponse::class.java) + return DefaultExpressErrorResolver( + ExpressErrorConverter(jsonAdapter), + ) + } + + @Provides + @Singleton + fun provideExpressRepository( + tangemExpressApi: TangemExpressApi, + appPreferencesStore: AppPreferencesStore, + ): ExpressRepository { + return DefaultExpressRepository( + tangemExpressApi = tangemExpressApi, + appPreferencesStore = appPreferencesStore, + ) + } +} \ No newline at end of file diff --git a/data/express/src/main/java/com/tangem/data/express/error/DefaultExpressErrorResolver.kt b/data/express/src/main/java/com/tangem/data/express/error/DefaultExpressErrorResolver.kt new file mode 100644 index 0000000000..96ca3d07a8 --- /dev/null +++ b/data/express/src/main/java/com/tangem/data/express/error/DefaultExpressErrorResolver.kt @@ -0,0 +1,20 @@ +package com.tangem.data.express.error + +import com.tangem.data.express.converter.ExpressErrorConverter +import com.tangem.datasource.api.common.response.ApiResponseError +import com.tangem.domain.express.ExpressErrorResolver +import com.tangem.domain.express.models.ExpressError + +internal class DefaultExpressErrorResolver( + private val expressErrorConverter: ExpressErrorConverter, +) : ExpressErrorResolver { + + override fun resolve(throwable: Throwable): ExpressError { + return when (throwable) { + is ApiResponseError.HttpException -> { + expressErrorConverter.convert(throwable.errorBody.orEmpty()) + } + else -> ExpressError.UnknownError + } + } +} \ No newline at end of file diff --git a/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressError.kt b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressError.kt new file mode 100644 index 0000000000..7d153e9bf1 --- /dev/null +++ b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressError.kt @@ -0,0 +1,64 @@ +package com.tangem.domain.express.models + +import java.math.BigDecimal + +sealed class ExpressError { + + abstract val code: Int + + data class DataError( + override val code: Int, + val description: String?, + ) : ExpressError() + + data class BadRequest(override val code: Int) : ExpressError() + + data class Forbidden(override val code: Int) : ExpressError() + + data class InternalError(override val code: Int) : ExpressError() + + data class ProviderNotFoundError(override val code: Int) : ExpressError() + + data class ProviderNotActiveError(override val code: Int) : ExpressError() + + data class ProviderNotAvailableError(override val code: Int) : ExpressError() + + data class ProviderInternalError(override val code: Int) : ExpressError() + + data class ExchangeNotPossibleError(override val code: Int) : ExpressError() + + data class NotEnoughBalanceError(override val code: Int) : ExpressError() + + data class InvalidAddressError(override val code: Int) : ExpressError() + + sealed class AmountError : ExpressError() { + abstract val amount: BigDecimal + + data class TooSmallError(override val code: Int, override val amount: BigDecimal) : AmountError() + data class TooBigError(override val code: Int, override val amount: BigDecimal) : AmountError() + data class NotEnoughAllowanceError(override val code: Int, override val amount: BigDecimal) : AmountError() + } + + data class ProviderDifferentAmountError( + override val code: Int, + val fromAmount: BigDecimal, + val fromProviderAmount: BigDecimal, + val decimals: Int, + ) : ExpressError() + + data class InvalidFromDecimalsError( + override val code: Int, + val receivedFromDecimals: Int, + val expressFromDecimals: Int, + ) : ExpressError() + + data class InvalidSignatureError(override val code: Int = 990) : ExpressError() + + data class InvalidRequestIdError(override val code: Int = 991) : ExpressError() + + data class InvalidPayoutAddressError(override val code: Int = 992) : ExpressError() + + data object UnknownError : ExpressError() { + override val code: Int = -1 + } +} \ No newline at end of file diff --git a/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressProvider.kt b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressProvider.kt new file mode 100644 index 0000000000..9a26b89580 --- /dev/null +++ b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressProvider.kt @@ -0,0 +1,43 @@ +package com.tangem.domain.express.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.math.BigDecimal + +/** + * Express provider data model + * + * @property providerId provider id + * @property rateTypes supported rate types + * @property name provider name + * @property type provider type + * @property imageLarge provider large logo image + * @property termsOfUse terms of use link + * @property privacyPolicy privacy policy link + * @property isRecommended flag that indicates if this provider is recommended + * @property slippage provider slippage + * + * Uses to store transaction data in datastore, when extends - should always add default value + * to support backward compatibility + */ +@JsonClass(generateAdapter = true) +data class ExpressProvider( + @Json(name = "providerId") + val providerId: String, + @Json(name = "rateTypes") + val rateTypes: List = emptyList(), + @Json(name = "name") + val name: String, + @Json(name = "type") + val type: ExpressProviderType, + @Json(name = "imageLarge") + val imageLarge: String, + @Json(name = "termsOfUse") + val termsOfUse: String?, + @Json(name = "privacyPolicy") + val privacyPolicy: String?, + @Json(name = "isRecommended") + val isRecommended: Boolean = false, + @Json(name = "slippage") + val slippage: BigDecimal?, +) \ No newline at end of file diff --git a/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressProviderType.kt b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressProviderType.kt new file mode 100644 index 0000000000..3c77da80fb --- /dev/null +++ b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressProviderType.kt @@ -0,0 +1,24 @@ +package com.tangem.domain.express.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Express provider type + * + * @property typeName provider type name + */ +@JsonClass(generateAdapter = false) +enum class ExpressProviderType(val typeName: String) { + @Json(name = "DEX") + DEX(typeName = "DEX"), + + @Json(name = "CEX") + CEX(typeName = "CEX"), + + @Json(name = "DEX_BRIDGE") + DEX_BRIDGE(typeName = "DEX/Bridge"), + + @Json(name = "onramp") + ONRAMP(typeName = "ONRAMP"), +} \ No newline at end of file diff --git a/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressRateType.kt b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressRateType.kt new file mode 100644 index 0000000000..4b9efd00b0 --- /dev/null +++ b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressRateType.kt @@ -0,0 +1,18 @@ +package com.tangem.domain.express.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Rate type. + * + * Current implementation contains only float type, fixed will be supported later. + */ +@JsonClass(generateAdapter = false) +enum class ExpressRateType { + @Json(name = "FLOAT") + Float, + + @Json(name = "FIXED") + Fixed, +} \ No newline at end of file diff --git a/domain/express/src/main/java/com/tangem/domain/express/ExpressErrorResolver.kt b/domain/express/src/main/java/com/tangem/domain/express/ExpressErrorResolver.kt new file mode 100644 index 0000000000..05e981033d --- /dev/null +++ b/domain/express/src/main/java/com/tangem/domain/express/ExpressErrorResolver.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.express + +import com.tangem.domain.express.models.ExpressError + +/** + * Common express error resolver + */ +interface ExpressErrorResolver { + + fun resolve(throwable: Throwable): ExpressError +} \ No newline at end of file diff --git a/domain/express/src/main/java/com/tangem/domain/express/ExpressRepository.kt b/domain/express/src/main/java/com/tangem/domain/express/ExpressRepository.kt new file mode 100644 index 0000000000..d7c6518b46 --- /dev/null +++ b/domain/express/src/main/java/com/tangem/domain/express/ExpressRepository.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.express + +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.wallets.models.UserWallet + +/** + * Express repository + */ +interface ExpressRepository { + + /** + * Returns list of express providers + * + * @param userWallet selected user wallet info + */ + suspend fun getProviders(userWallet: UserWallet): List +} \ No newline at end of file diff --git a/features/swap-v2/impl/build.gradle.kts b/features/swap-v2/impl/build.gradle.kts index cb247c509f..456c959e50 100644 --- a/features/swap-v2/impl/build.gradle.kts +++ b/features/swap-v2/impl/build.gradle.kts @@ -23,7 +23,6 @@ dependencies { implementation(projects.features.swapV2.api) implementation(projects.features.manageTokens.api) implementation(projects.features.sendV2.api) - implementation(projects.features.feeSelector.api) /** Core */ implementation(projects.core.decompose)