Updated on 2026-08-14
This commit is contained in:
parent
41648e4ffc
commit
3dc6ff9548
12 changed files with 400 additions and 1 deletions
|
|
@ -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<ExpressProvider> {
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ExpressErrorResponse>,
|
||||
) : Converter<String, ExpressError> {
|
||||
|
||||
@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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ExchangeProvider, ExpressProvider> {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ExpressRateType> = 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?,
|
||||
)
|
||||
|
|
@ -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"),
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<ExpressProvider>
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue