Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-25 20:31:57 +05:00
parent 321caf73a8
commit 4c373e7543
9 changed files with 99 additions and 95 deletions

View file

@ -27,7 +27,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.swap.converters.*
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.ExpressDataError
import com.tangem.feature.swap.domain.models.ExpressException
import com.tangem.feature.swap.domain.models.createFromAmountWithOffset
import com.tangem.feature.swap.domain.models.domain.*
@ -196,7 +196,7 @@ internal class DefaultSwapRepository @Inject constructor(
toDecimals: Int,
providerId: String,
rateType: RateType,
): Either<DataError, QuoteModel> {
): Either<ExpressDataError, QuoteModel> {
return withContext(coroutineDispatcher.io) {
try {
val response = tangemExpressApi.getExchangeQuote(
@ -234,7 +234,7 @@ internal class DefaultSwapRepository @Inject constructor(
toAddress: String,
refundAddress: String?, // for cex only
refundExtraId: String?, // for cex only
): Either<DataError, SwapDataModel> {
): Either<ExpressDataError, SwapDataModel> {
return withContext(coroutineDispatcher.io) {
try {
val requestId = UUID.randomUUID().toString()
@ -256,12 +256,12 @@ internal class DefaultSwapRepository @Inject constructor(
).getOrThrow()
if (dataSignatureVerifier.verifySignature(response.signature, response.txDetailsJson)) {
val txDetails = parseTxDetails(response.txDetailsJson)
?: return@withContext DataError.UnknownError.left()
?: return@withContext ExpressDataError.UnknownError.left()
if (txDetails.requestId != requestId) {
return@withContext DataError.InvalidRequestIdError().left()
return@withContext ExpressDataError.InvalidRequestIdError().left()
}
if (!toAddress.equals(txDetails.payoutAddress, ignoreCase = true)) {
return@withContext DataError.InvalidPayoutAddressError().left()
return@withContext ExpressDataError.InvalidPayoutAddressError().left()
}
expressDataConverter.convert(
ExchangeDataResponseWithTxDetails(
@ -270,7 +270,7 @@ internal class DefaultSwapRepository @Inject constructor(
),
).right()
} else {
DataError.InvalidSignatureError().left()
ExpressDataError.InvalidSignatureError().left()
}
} catch (ex: Exception) {
getDataError(ex).left()
@ -285,7 +285,7 @@ internal class DefaultSwapRepository @Inject constructor(
payInAddress: String,
txHash: String,
payInExtraId: String?,
): Either<DataError, Unit> = withContext(coroutineDispatcher.io) {
): Either<ExpressDataError, Unit> = withContext(coroutineDispatcher.io) {
try {
tangemExpressApi.exchangeSent(
ExchangeSentRequestBody(
@ -400,11 +400,11 @@ internal class DefaultSwapRepository @Inject constructor(
)
}
private fun getDataError(ex: Exception): DataError {
private fun getDataError(ex: Exception): ExpressDataError {
return if (ex is ApiResponseError.HttpException) {
errorsDataConverter.convert(ex.errorBody ?: "")
} else {
DataError.UnknownError
ExpressDataError.UnknownError
}
}
}

View file

@ -3,76 +3,77 @@ package com.tangem.feature.swap.converters
import com.squareup.moshi.JsonAdapter
import com.tangem.datasource.api.express.models.response.ExpressError
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.ExpressDataError
import com.tangem.feature.swap.domain.models.createFromAmountWithOffset
import com.tangem.utils.converter.Converter
internal class ErrorsDataConverter(
private val jsonAdapter: JsonAdapter<ExpressErrorResponse>,
) : Converter<String, DataError> {
) : Converter<String, ExpressDataError> {
@Suppress("MagicNumber", "CyclomaticComplexMethod")
override fun convert(value: String): DataError {
override fun convert(value: String): ExpressDataError {
try {
val error = jsonAdapter.fromJson(value)?.error ?: return DataError.UnknownError
val error = jsonAdapter.fromJson(value)?.error ?: return ExpressDataError.UnknownError
return when (error.code) {
2010 -> DataError.BadRequest(code = error.code)
2200 -> DataError.SwapsAreUnavailableNowError(code = error.code)
2210 -> DataError.ExchangeProviderNotFoundError(code = error.code)
2220 -> DataError.ExchangeProviderNotActiveError(code = error.code)
2230 -> DataError.ExchangeProviderNotAvailableError(code = error.code)
2231 -> DataError.ExchangeProviderProviderInternalError(code = error.code)
2240 -> DataError.ExchangeNotPossibleError(code = error.code)
2010 -> ExpressDataError.BadRequest(code = error.code)
2200 -> ExpressDataError.SwapsAreUnavailableNowError(code = error.code)
2210 -> ExpressDataError.ExchangeProviderNotFoundError(code = error.code)
2220 -> ExpressDataError.ExchangeProviderNotActiveError(code = error.code)
2230 -> ExpressDataError.ExchangeProviderNotAvailableError(code = error.code)
2231 -> ExpressDataError.ExchangeProviderProviderInternalError(code = error.code)
2240 -> ExpressDataError.ExchangeNotPossibleError(code = error.code)
2250 -> tryParseExchangeTooSmallAmountError(error = error)
2251 -> tryParseExchangeTooBigAmountError(error = error)
2260 -> tryParseExchangeNotEnoughAllowanceError(error = error)
2270 -> DataError.ExchangeNotEnoughBalanceError(code = error.code)
2280 -> DataError.ExchangeInvalidAddressError(code = error.code)
2270 -> ExpressDataError.ExchangeNotEnoughBalanceError(code = error.code)
2280 -> ExpressDataError.ExchangeInvalidAddressError(code = error.code)
2290 -> tryParseExchangeInvalidFromDecimalsError(error = error)
else -> DataError.UnknownErrorWithCode(error.code)
else -> ExpressDataError.UnknownErrorWithCode(error.code)
}
} catch (e: Exception) {
return DataError.UnknownError
return ExpressDataError.UnknownError
}
}
private fun tryParseExchangeTooSmallAmountError(error: ExpressError): DataError {
val minAmount = error.value?.minAmount ?: return DataError.UnknownErrorWithCode(error.code)
val decimals = error.value?.decimals ?: return DataError.UnknownErrorWithCode(error.code)
private fun tryParseExchangeTooSmallAmountError(error: ExpressError): ExpressDataError {
val minAmount = error.value?.minAmount ?: return ExpressDataError.UnknownErrorWithCode(error.code)
val decimals = error.value?.decimals ?: return ExpressDataError.UnknownErrorWithCode(error.code)
return DataError.ExchangeTooSmallAmountError(
return ExpressDataError.ExchangeTooSmallAmountError(
code = error.code,
amount = createFromAmountWithOffset(minAmount, decimals),
)
}
private fun tryParseExchangeTooBigAmountError(error: ExpressError): DataError {
val minAmount = error.value?.maxAmount ?: return DataError.UnknownErrorWithCode(error.code)
val decimals = error.value?.decimals ?: return DataError.UnknownErrorWithCode(error.code)
private fun tryParseExchangeTooBigAmountError(error: ExpressError): ExpressDataError {
val minAmount = error.value?.maxAmount ?: return ExpressDataError.UnknownErrorWithCode(error.code)
val decimals = error.value?.decimals ?: return ExpressDataError.UnknownErrorWithCode(error.code)
return DataError.ExchangeTooBigAmountError(
return ExpressDataError.ExchangeTooBigAmountError(
code = error.code,
amount = createFromAmountWithOffset(minAmount, decimals),
)
}
private fun tryParseExchangeNotEnoughAllowanceError(error: ExpressError): DataError {
val currentAllowance = error.value?.currentAllowance ?: return DataError.UnknownErrorWithCode(error.code)
private fun tryParseExchangeNotEnoughAllowanceError(error: ExpressError): ExpressDataError {
val currentAllowance = error.value?.currentAllowance ?: return ExpressDataError.UnknownErrorWithCode(error.code)
return DataError.ExchangeNotEnoughAllowanceError(
return ExpressDataError.ExchangeNotEnoughAllowanceError(
code = error.code,
currentAllowance = currentAllowance,
)
}
private fun tryParseExchangeInvalidFromDecimalsError(error: ExpressError): DataError {
val receivedFromDecimals = error.value?.receivedFromDecimals ?: return DataError.UnknownErrorWithCode(
private fun tryParseExchangeInvalidFromDecimalsError(error: ExpressError): ExpressDataError {
val receivedFromDecimals = error.value?.receivedFromDecimals ?: return ExpressDataError.UnknownErrorWithCode(
code = error.code,
)
val expressFromDecimals = error.value?.expressFromDecimals ?: return DataError.UnknownErrorWithCode(error.code)
val expressFromDecimals =
error.value?.expressFromDecimals ?: return ExpressDataError.UnknownErrorWithCode(error.code)
return DataError.ExchangeInvalidFromDecimalsError(
return ExpressDataError.ExchangeInvalidFromDecimalsError(
code = error.code,
receivedFromDecimals = receivedFromDecimals,
expressFromDecimals = expressFromDecimals,

View file

@ -3,7 +3,7 @@ package com.tangem.feature.swap.domain.api
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.DataError
import com.tangem.feature.swap.domain.models.ExpressDataError
import com.tangem.feature.swap.domain.models.domain.*
import java.math.BigDecimal
@ -27,7 +27,7 @@ interface SwapRepository {
toDecimals: Int,
providerId: String,
rateType: RateType,
): Either<DataError, QuoteModel>
): Either<ExpressDataError, QuoteModel>
@Suppress("LongParameterList")
@Throws(IllegalStateException::class)
@ -66,7 +66,7 @@ interface SwapRepository {
toAddress: String,
refundAddress: String? = null, // for cex only
refundExtraId: String? = null, // for cex only
): Either<DataError, SwapDataModel>
): Either<ExpressDataError, SwapDataModel>
// TODO: Add target error handling, remove either ([REDACTED_JIRA])
@Suppress("LongParameterList")
@ -77,7 +77,7 @@ interface SwapRepository {
payInAddress: String,
txHash: String,
payInExtraId: String?,
): Either<DataError, Unit>
): Either<ExpressDataError, Unit>
fun getNativeTokenForNetwork(networkId: String): CryptoCurrency
}

View file

@ -2,49 +2,52 @@ package com.tangem.feature.swap.domain.models
import java.math.BigDecimal
sealed class DataError {
sealed class ExpressDataError {
abstract val code: Int
data class BadRequest(override val code: Int) : DataError()
data class BadRequest(override val code: Int) : ExpressDataError()
data class SwapsAreUnavailableNowError(override val code: Int) : DataError()
data class SwapsAreUnavailableNowError(override val code: Int) : ExpressDataError()
data class ExchangeProviderNotFoundError(override val code: Int) : DataError()
data class ExchangeProviderNotFoundError(override val code: Int) : ExpressDataError()
data class ExchangeProviderNotActiveError(override val code: Int) : DataError()
data class ExchangeProviderNotActiveError(override val code: Int) : ExpressDataError()
data class ExchangeProviderNotAvailableError(override val code: Int) : DataError()
data class ExchangeProviderNotAvailableError(override val code: Int) : ExpressDataError()
data class ExchangeProviderProviderInternalError(override val code: Int) : DataError()
data class ExchangeProviderProviderInternalError(override val code: Int) : ExpressDataError()
data class ExchangeNotPossibleError(override val code: Int) : DataError()
data class ExchangeNotPossibleError(override val code: Int) : ExpressDataError()
data class ExchangeTooSmallAmountError(override val code: Int, val amount: SwapAmount) : DataError()
data class ExchangeTooSmallAmountError(override val code: Int, val amount: SwapAmount) : ExpressDataError()
data class ExchangeTooBigAmountError(override val code: Int, val amount: SwapAmount) : DataError()
data class ExchangeTooBigAmountError(override val code: Int, val amount: SwapAmount) : ExpressDataError()
data class ExchangeNotEnoughAllowanceError(override val code: Int, val currentAllowance: BigDecimal) : DataError()
data class ExchangeNotEnoughAllowanceError(
override val code: Int,
val currentAllowance: BigDecimal,
) : ExpressDataError()
data class ExchangeNotEnoughBalanceError(override val code: Int) : DataError()
data class ExchangeNotEnoughBalanceError(override val code: Int) : ExpressDataError()
data class ExchangeInvalidAddressError(override val code: Int) : DataError()
data class ExchangeInvalidAddressError(override val code: Int) : ExpressDataError()
data class ExchangeInvalidFromDecimalsError(
override val code: Int,
val receivedFromDecimals: Int,
val expressFromDecimals: Int,
) : DataError()
) : ExpressDataError()
data class UnknownErrorWithCode(override val code: Int) : DataError()
data class UnknownErrorWithCode(override val code: Int) : ExpressDataError()
data class InvalidSignatureError(override val code: Int = 990) : DataError()
data class InvalidSignatureError(override val code: Int = 990) : ExpressDataError()
data class InvalidRequestIdError(override val code: Int = 991) : DataError()
data class InvalidRequestIdError(override val code: Int = 991) : ExpressDataError()
data class InvalidPayoutAddressError(override val code: Int = 992) : DataError()
data class InvalidPayoutAddressError(override val code: Int = 992) : ExpressDataError()
data object UnknownError : DataError() {
data object UnknownError : ExpressDataError() {
override val code: Int = -1
}
}

View file

@ -1,3 +1,3 @@
package com.tangem.feature.swap.domain.models
class ExpressException(val dataError: DataError) : Exception()
class ExpressException(val expressDataError: ExpressDataError) : Exception()

View file

@ -1,7 +1,7 @@
package com.tangem.feature.swap.domain.models.ui
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.ExpressDataError
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.*
import java.math.BigDecimal
@ -34,7 +34,7 @@ sealed interface SwapState {
data class SwapError(
val fromTokenInfo: TokenSwapInfo,
val error: DataError,
val error: ExpressDataError,
val includeFeeInAmount: IncludeFeeInAmount,
) : SwapState
}

View file

@ -30,7 +30,7 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.converters.SwapCurrencyConverter
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.ExpressDataError
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.*
import com.tangem.feature.swap.domain.models.toStringWithRightOffset
@ -1057,7 +1057,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
@Suppress("LongMethod")
private suspend fun getQuotesState(
provider: SwapProvider,
quoteDataModel: Either<DataError, QuoteModel>,
quoteDataModel: Either<ExpressDataError, QuoteModel>,
amount: SwapAmount,
fromToken: CryptoCurrencyStatus,
toToken: CryptoCurrencyStatus,
@ -1136,7 +1136,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
fromToken = fromToken,
amount = amount,
includeFeeInAmount = includeFeeInAmount,
dataError = error,
expressDataError = error,
)
},
)
@ -1146,7 +1146,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
fromToken: CryptoCurrencyStatus,
amount: SwapAmount,
includeFeeInAmount: IncludeFeeInAmount,
dataError: DataError,
expressDataError: ExpressDataError,
): SwapState.SwapError {
val rates = getQuotes(fromToken.currency.id)
val fromTokenSwapInfo = TokenSwapInfo(
@ -1155,7 +1155,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
?: BigDecimal.ZERO,
cryptoCurrencyStatus = fromToken,
)
return SwapState.SwapError(fromTokenSwapInfo, dataError, includeFeeInAmount)
return SwapState.SwapError(fromTokenSwapInfo, expressDataError, includeFeeInAmount)
}
@Suppress("CyclomaticComplexMethod")
@ -1502,7 +1502,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
fromToken = fromTokenStatus,
amount = swapAmount,
includeFeeInAmount = IncludeFeeInAmount.Excluded,
dataError = DataError.UnknownError,
expressDataError = ExpressDataError.UnknownError,
)
}
}

View file

@ -16,7 +16,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter
import com.tangem.feature.swap.converters.TokensDataConverter
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.ExpressDataError
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.*
import com.tangem.feature.swap.domain.models.formatToUIRepresentation
@ -632,13 +632,13 @@ internal class StateBuilder(
fromToken: TokenSwapInfo,
toToken: CryptoCurrencyStatus?,
includeFeeInAmount: IncludeFeeInAmount,
dataError: DataError,
expressDataError: ExpressDataError,
isReverseSwapPossible: Boolean,
): SwapStateHolder {
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
val warnings = mutableListOf<SwapWarning>()
warnings.add(getWarningForError(dataError, fromToken.cryptoCurrencyStatus.currency))
warnings.add(getWarningForError(expressDataError, fromToken.cryptoCurrencyStatus.currency))
if (includeFeeInAmount is IncludeFeeInAmount.Included && uiStateHolder.fee is FeeItemState.Content) {
val feeCoverageNotification = createNetworkFeeCoverageNotificationConfig(
uiStateHolder.fee.amountCrypto,
@ -649,7 +649,7 @@ internal class StateBuilder(
val providerState = getProviderStateForError(
swapProvider = swapProvider,
fromToken = fromToken.cryptoCurrencyStatus.currency,
dataError = dataError,
expressDataError = expressDataError,
onProviderClick = actions.onProviderClick,
selectionType = ProviderState.SelectionType.CLICK,
)
@ -705,28 +705,28 @@ internal class StateBuilder(
private fun getProviderStateForError(
swapProvider: SwapProvider,
fromToken: CryptoCurrency,
dataError: DataError,
expressDataError: ExpressDataError,
onProviderClick: (String) -> Unit,
selectionType: ProviderState.SelectionType,
): ProviderState {
return when (dataError) {
is DataError.ExchangeTooSmallAmountError -> {
return when (expressDataError) {
is ExpressDataError.ExchangeTooSmallAmountError -> {
swapProvider.convertToAvailableFromProviderState(
swapProvider = swapProvider,
alertText = resourceReference(
R.string.express_provider_min_amount,
wrappedList(dataError.amount.getFormattedCryptoAmount(fromToken)),
wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)),
),
selectionType = selectionType,
onProviderClick = onProviderClick,
)
}
is DataError.ExchangeTooBigAmountError -> {
is ExpressDataError.ExchangeTooBigAmountError -> {
swapProvider.convertToAvailableFromProviderState(
swapProvider = swapProvider,
alertText = resourceReference(
R.string.express_provider_max_amount,
wrappedList(dataError.amount.getFormattedCryptoAmount(fromToken)),
wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)),
),
selectionType = selectionType,
onProviderClick = onProviderClick,
@ -738,25 +738,25 @@ internal class StateBuilder(
}
}
private fun getWarningForError(dataError: DataError, fromToken: CryptoCurrency): SwapWarning {
val providerErrorMessage = getProviderErrorMessage(dataError)
val providerErrorTitle = getProviderErrorTitle(dataError)
return when (dataError) {
is DataError.ExchangeTooSmallAmountError -> SwapWarning.GeneralError(
private fun getWarningForError(expressDataError: ExpressDataError, fromToken: CryptoCurrency): SwapWarning {
val providerErrorMessage = getExpressErrorMessage(expressDataError)
val providerErrorTitle = getExpressErrorTitle(expressDataError)
return when (expressDataError) {
is ExpressDataError.ExchangeTooSmallAmountError -> SwapWarning.GeneralError(
notificationConfig = NotificationConfig(
title = resourceReference(
id = R.string.warning_express_too_minimal_amount_title,
formatArgs = wrappedList(dataError.amount.getFormattedCryptoAmount(fromToken)),
formatArgs = wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)),
),
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
iconResId = R.drawable.ic_alert_circle_24,
),
)
is DataError.ExchangeTooBigAmountError -> SwapWarning.GeneralError(
is ExpressDataError.ExchangeTooBigAmountError -> SwapWarning.GeneralError(
notificationConfig = NotificationConfig(
title = resourceReference(
id = R.string.warning_express_too_maximum_amount_title,
formatArgs = wrappedList(dataError.amount.getFormattedCryptoAmount(fromToken)),
formatArgs = wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)),
),
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
iconResId = R.drawable.ic_alert_circle_24,
@ -1399,7 +1399,7 @@ internal class StateBuilder(
is SwapState.SwapError -> getProviderStateForError(
swapProvider = provider,
fromToken = state.fromTokenInfo.cryptoCurrencyStatus.currency,
dataError = state.error,
expressDataError = state.error,
onProviderClick = onProviderSelect,
selectionType = ProviderState.SelectionType.SELECT,
)

View file

@ -36,7 +36,7 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.swap.analytics.SwapEvents
import com.tangem.feature.swap.domain.BlockchainInteractor
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.ExpressDataError
import com.tangem.feature.swap.domain.models.ExpressException
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.*
@ -243,7 +243,7 @@ internal class SwapViewModel @Inject constructor(
uiState = stateBuilder.createInitialErrorState(
uiState,
(it as? ExpressException)?.dataError?.code ?: DataError.UnknownError.code,
(it as? ExpressException)?.expressDataError?.code ?: ExpressDataError.UnknownError.code,
) {
uiState = stateBuilder.createInitialLoadingState(
initialCurrency = initialCryptoCurrency,
@ -431,7 +431,7 @@ internal class SwapViewModel @Inject constructor(
swapProvider = provider,
fromToken = state.fromTokenInfo,
toToken = dataState.toCryptoCurrency,
dataError = state.error,
expressDataError = state.error,
includeFeeInAmount = state.includeFeeInAmount,
isReverseSwapPossible = isReverseSwapPossible(),
)
@ -440,7 +440,7 @@ internal class SwapViewModel @Inject constructor(
}
}
private fun sendErrorAnalyticsEvent(error: DataError, provider: SwapProvider) {
private fun sendErrorAnalyticsEvent(error: ExpressDataError, provider: SwapProvider) {
val receiveToken = dataState.toCryptoCurrency?.currency?.let {
"${it.network.backendId}:${it.symbol}"
}