Updated on 2026-08-14
This commit is contained in:
parent
94579f4af8
commit
f69ee8bece
5 changed files with 98 additions and 21 deletions
|
|
@ -23,6 +23,7 @@ internal object SwapAmountQuoteUtils {
|
|||
|
||||
private val PRICE_IMPACT_AMOUNT_MIN_THRESHOLD = 25.toBigDecimal() // in USD
|
||||
private val PRICE_IMPACT_AMOUNT_MAX_THRESHOLD = 5000.toBigDecimal() // in USD
|
||||
private val PRICE_IMPACT_AMOUNT_LOW_THRESHOLD = 100_000.toBigDecimal() // in USD
|
||||
private val PRICE_IMPACT_LOW_THRESHOLD = 0.1.toBigDecimal() // 10%
|
||||
private val PRICE_IMPACT_HIGH_THRESHOLD = 0.5.toBigDecimal() // 50%
|
||||
|
||||
|
|
@ -46,10 +47,10 @@ internal object SwapAmountQuoteUtils {
|
|||
secondaryCryptoCurrencyStatus?.value?.fiatRate to primaryCryptoCurrencyStatus.value.fiatRate
|
||||
}
|
||||
|
||||
val fromRateUsd = if (swapDirection == SwapDirection.Direct) {
|
||||
primaryFiatRateUSD
|
||||
val (fromRateUsd, toRateUsd) = if (swapDirection == SwapDirection.Direct) {
|
||||
primaryFiatRateUSD to secondaryFiatRateUSD
|
||||
} else {
|
||||
secondaryFiatRateUSD
|
||||
secondaryFiatRateUSD to primaryFiatRateUSD
|
||||
}
|
||||
|
||||
val fromTokenFiatValue = fromRate?.let { fromAmount.multiply(fromRate).orZero() }
|
||||
|
|
@ -65,12 +66,15 @@ internal object SwapAmountQuoteUtils {
|
|||
}
|
||||
|
||||
val fromAmountUSD = fromAmount.multiply(fromRateUsd.orZero())
|
||||
val toAmountUSD = toAmount.multiply(toRateUsd.orZero())
|
||||
val amountDiff = fromAmountUSD - toAmountUSD
|
||||
|
||||
val type = when {
|
||||
value == null -> PriceImpact.Type.NONE
|
||||
value < PRICE_IMPACT_LOW_THRESHOLD -> PriceImpact.Type.LOW
|
||||
value in PRICE_IMPACT_LOW_THRESHOLD..PRICE_IMPACT_HIGH_THRESHOLD -> PriceImpact.Type.MEDIUM
|
||||
else -> PriceImpact.Type.HIGH
|
||||
value < PRICE_IMPACT_LOW_THRESHOLD &&
|
||||
amountDiff <= PRICE_IMPACT_AMOUNT_LOW_THRESHOLD -> PriceImpact.Type.LOW
|
||||
value > PRICE_IMPACT_HIGH_THRESHOLD -> PriceImpact.Type.HIGH
|
||||
else -> PriceImpact.Type.MEDIUM
|
||||
}
|
||||
|
||||
val amountSignificance = when {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.ACCOUNT_DERIVATION_TO
|
|||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_MESSAGE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.PROVIDER
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.RATE_TYPE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.RECEIVE_BLOCKCHAIN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.RECEIVE_TOKEN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.SEND_BLOCKCHAIN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.SEND_TOKEN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.RATE_TYPE
|
||||
import com.tangem.core.analytics.models.AppsFlyerIncludedEvent
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
|
|||
|
|
@ -1962,7 +1962,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
fromTokenAmount = fromTokenAmount.value,
|
||||
fromQuoteStatus = rates[fromToken.id],
|
||||
toTokenAmount = toTokenAmount.value,
|
||||
toRate = rates[toToken.id]?.fiatRate,
|
||||
toQuoteStatus = rates[toToken.id],
|
||||
),
|
||||
swapDataModel = swapData,
|
||||
swapProvider = provider,
|
||||
|
|
@ -2436,10 +2436,11 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
fromTokenAmount: BigDecimal,
|
||||
fromQuoteStatus: QuoteStatus.Data?,
|
||||
toTokenAmount: BigDecimal,
|
||||
toRate: BigDecimal?,
|
||||
toQuoteStatus: QuoteStatus.Data?,
|
||||
): PriceImpact {
|
||||
if (fromQuoteStatus == null) return PriceImpact.Empty
|
||||
|
||||
val toRate = toQuoteStatus?.fiatRate
|
||||
val fromTokenFiatValue = fromTokenAmount.multiply(fromQuoteStatus.fiatRate)
|
||||
val toTokenFiatValue = toRate?.let { toTokenAmount.multiply(toRate) } ?: return PriceImpact.Empty
|
||||
|
||||
|
|
@ -2450,6 +2451,12 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
} else {
|
||||
PRICE_IMPACT_AMOUNT_MIN_THRESHOLD
|
||||
}
|
||||
val toAmountUSD = if (toQuoteStatus.fiatRateUSD != BigDecimal.ZERO) {
|
||||
toTokenAmount.multiply(toQuoteStatus.fiatRateUSD)
|
||||
} else {
|
||||
PRICE_IMPACT_AMOUNT_MIN_THRESHOLD
|
||||
}
|
||||
val amountDiff = fromAmountUSD - toAmountUSD
|
||||
|
||||
val amountSignificance = when {
|
||||
fromAmountUSD <= PRICE_IMPACT_AMOUNT_MIN_THRESHOLD -> PriceImpact.AmountSignificance.LOW
|
||||
|
|
@ -2458,9 +2465,10 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
}
|
||||
|
||||
val type = when {
|
||||
value < PRICE_IMPACT_LOW_THRESHOLD -> PriceImpact.Type.LOW
|
||||
value in PRICE_IMPACT_LOW_THRESHOLD..PRICE_IMPACT_HIGH_THRESHOLD -> PriceImpact.Type.MEDIUM
|
||||
else -> PriceImpact.Type.HIGH
|
||||
value < PRICE_IMPACT_LOW_THRESHOLD &&
|
||||
amountDiff <= PRICE_IMPACT_AMOUNT_LOW_THRESHOLD -> PriceImpact.Type.LOW
|
||||
value > PRICE_IMPACT_HIGH_THRESHOLD -> PriceImpact.Type.HIGH
|
||||
else -> PriceImpact.Type.MEDIUM
|
||||
}
|
||||
|
||||
return PriceImpact(
|
||||
|
|
@ -2539,6 +2547,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
private const val INCREASE_GAS_LIMIT_FOR_SEND = 105 // 5%
|
||||
private val PRICE_IMPACT_AMOUNT_MIN_THRESHOLD = 25.toBigDecimal() // in USD
|
||||
private val PRICE_IMPACT_AMOUNT_MAX_THRESHOLD = 5000.toBigDecimal() // in USD
|
||||
private val PRICE_IMPACT_AMOUNT_LOW_THRESHOLD = 100_000.toBigDecimal() // in USD
|
||||
private val PRICE_IMPACT_LOW_THRESHOLD = 0.1.toBigDecimal() // 10%
|
||||
private val PRICE_IMPACT_HIGH_THRESHOLD = 0.5.toBigDecimal() // 50%
|
||||
private const val INFINITY_SYMBOL = "∞"
|
||||
|
|
|
|||
|
|
@ -161,6 +161,41 @@ sealed class SwapEvents(
|
|||
),
|
||||
)
|
||||
|
||||
class HighPriceImpact(
|
||||
val sendToken: String,
|
||||
val receiveToken: String,
|
||||
val sendBlockchain: String,
|
||||
val receiveBlockchain: String,
|
||||
val providerName: String,
|
||||
) : SwapEvents(
|
||||
event = "Notice - High price impact",
|
||||
params = mapOf(
|
||||
SEND_TOKEN to sendToken,
|
||||
RECEIVE_TOKEN to receiveToken,
|
||||
"Send Blockchain" to sendBlockchain,
|
||||
"Receive Blockchain" to receiveBlockchain,
|
||||
PROVIDER to providerName,
|
||||
),
|
||||
)
|
||||
|
||||
class TradeTooLarge(
|
||||
val sendToken: String,
|
||||
val receiveToken: String,
|
||||
val sendBlockchain: String,
|
||||
val receiveBlockchain: String,
|
||||
val providerName: String,
|
||||
|
||||
) : SwapEvents(
|
||||
event = "Notice - Trade too large",
|
||||
params = mapOf(
|
||||
SEND_TOKEN to sendToken,
|
||||
RECEIVE_TOKEN to receiveToken,
|
||||
"Send Blockchain" to sendBlockchain,
|
||||
"Receive Blockchain" to receiveBlockchain,
|
||||
PROVIDER to providerName,
|
||||
),
|
||||
)
|
||||
|
||||
class NoticeProviderError(
|
||||
val sendToken: String,
|
||||
val receiveToken: String,
|
||||
|
|
|
|||
|
|
@ -82,11 +82,7 @@ import com.tangem.feature.swap.domain.models.ExpressException
|
|||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import com.tangem.feature.swap.models.SwapAlertUM
|
||||
import com.tangem.feature.swap.models.SwapCardState
|
||||
import com.tangem.feature.swap.models.SwapStateHolder
|
||||
import com.tangem.feature.swap.models.TransactionCardType
|
||||
import com.tangem.feature.swap.models.UiActions
|
||||
import com.tangem.feature.swap.models.*
|
||||
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||
import com.tangem.feature.swap.router.SwapNavScreen
|
||||
import com.tangem.feature.swap.router.SwapRouter
|
||||
|
|
@ -712,7 +708,7 @@ internal class SwapModel @Inject constructor(
|
|||
onSuccess = { providersState ->
|
||||
if (providersState.isNotEmpty()) {
|
||||
val (provider, state) = updateLoadedQuotes(providersState)
|
||||
setupLoadedState(provider, state, fromToken)
|
||||
setupLoadedState(provider = provider, state = state, fromToken = fromToken, toToken = toToken)
|
||||
val successStates = providersState.getLastLoadedSuccessStates()
|
||||
val pricesLowerBest = getPricesLowerBest(provider.providerId, successStates)
|
||||
uiState = stateBuilder.updateProvidersBottomSheetContent(
|
||||
|
|
@ -739,11 +735,16 @@ internal class SwapModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun setupLoadedState(provider: SwapProvider, state: SwapState, fromToken: CryptoCurrencyStatus) {
|
||||
private fun setupLoadedState(
|
||||
provider: SwapProvider,
|
||||
state: SwapState,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
toToken: CryptoCurrencyStatus?,
|
||||
) {
|
||||
when (state) {
|
||||
is SwapState.QuotesLoadedState -> {
|
||||
setupQuotesLoadedUiState(provider, state, fromToken)
|
||||
sendAnalyticsForNotifications(fromToken)
|
||||
sendAnalyticsForNotifications(provider, fromToken, toToken)
|
||||
updatePermissionNotificationState(state)
|
||||
}
|
||||
is SwapState.EmptyAmountState -> {
|
||||
|
|
@ -780,7 +781,11 @@ internal class SwapModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun sendAnalyticsForNotifications(fromToken: CryptoCurrencyStatus) {
|
||||
private fun sendAnalyticsForNotifications(
|
||||
provider: SwapProvider,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
toToken: CryptoCurrencyStatus?,
|
||||
) {
|
||||
if (uiState.notifications.any { it is SwapNotificationUM.Error.UnableToCoverFeeWarning }) {
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.NoticeNotEnoughFee(
|
||||
|
|
@ -789,6 +794,28 @@ internal class SwapModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
if (toToken != null && uiState.notifications.any { it is SwapNotificationUM.Warning.HighPriceImpact }) {
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.HighPriceImpact(
|
||||
sendToken = fromToken.currency.symbol,
|
||||
receiveToken = toToken.currency.network.name,
|
||||
sendBlockchain = fromToken.currency.symbol,
|
||||
receiveBlockchain = toToken.currency.network.name,
|
||||
providerName = provider.name,
|
||||
),
|
||||
)
|
||||
}
|
||||
if (toToken != null && uiState.notifications.any { it is SwapNotificationUM.Warning.TradeTooHigh }) {
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.TradeTooLarge(
|
||||
sendToken = fromToken.currency.symbol,
|
||||
receiveToken = toToken.currency.network.name,
|
||||
sendBlockchain = fromToken.currency.symbol,
|
||||
receiveBlockchain = toToken.currency.network.name,
|
||||
providerName = provider.name,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePermissionNotificationState(state: SwapState.QuotesLoadedState) {
|
||||
|
|
@ -1719,6 +1746,7 @@ internal class SwapModel @Inject constructor(
|
|||
val provider = findAndSelectProvider(providerId)
|
||||
val swapState = dataState.lastLoadedSwapStates[provider]
|
||||
val fromToken = dataState.fromCryptoCurrency
|
||||
val toToken = dataState.toCryptoCurrency
|
||||
if (provider != null && swapState != null && fromToken != null) {
|
||||
modelScope.launch {
|
||||
feeSelectorRepository.state.value = FeeSelectorUM.Loading
|
||||
|
|
@ -1730,6 +1758,7 @@ internal class SwapModel @Inject constructor(
|
|||
provider = provider,
|
||||
state = swapState,
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue