diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml
index 6dfe9b86ab..ab179ea317 100644
--- a/core/res/src/main/res/values-de/strings.xml
+++ b/core/res/src/main/res/values-de/strings.xml
@@ -335,6 +335,8 @@
Error: %s
There was an error. Please try again.
Give Permission
+ High price impact!
+ Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.
Insufficient funds
Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.
Transaction in progress...
diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml
index 58fc19b1e4..76d8e32bc5 100644
--- a/core/res/src/main/res/values-fr/strings.xml
+++ b/core/res/src/main/res/values-fr/strings.xml
@@ -335,6 +335,8 @@
Error: %s
There was an error. Please try again.
Give Permission
+ High price impact!
+ Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.
Insufficient funds
Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.
Transaction in progress...
diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml
index 225e2544a9..751cf170b2 100644
--- a/core/res/src/main/res/values-it/strings.xml
+++ b/core/res/src/main/res/values-it/strings.xml
@@ -335,6 +335,8 @@
Error: %s
There was an error. Please try again.
Give Permission
+ High price impact!
+ Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.
Insufficient funds
Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.
Transaction in progress...
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index 47116aa82e..4772adc1cc 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -335,6 +335,8 @@
Ошибка: %s
Произошла ошибка. Пожалуйста, попробуйте еще раз.
Дать разрешение
+ Сильные колебания цены!
+ Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму.
Недостаточно средств
Недостаточно средств для оплаты на вашем %1$s кошельке для создания транзакции. Сначала пополните свой %2$s кошелек.
Транзакция в процессе…
diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml
index 6699c46a3c..6b98188598 100644
--- a/core/res/src/main/res/values-zh-rTW/strings.xml
+++ b/core/res/src/main/res/values-zh-rTW/strings.xml
@@ -335,6 +335,8 @@
錯誤: %s
有錯誤。請再試一遍
賦予權限
+ High price impact!
+ Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.
餘額不足
您的 %1$s 錢包中沒有足夠的資金來創建交易。首先為您的 %2$s 錢包充值
交易進行中...
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index fcaa91827d..c3a6b51606 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -335,6 +335,8 @@
Error: %s
There was an error. Please try again.
Give Permission
+ High price impact!
+ Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.
Insufficient funds
Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.
Transaction in progress...
diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt
index ce10a3e537..5017af8c99 100644
--- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt
+++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt
@@ -67,8 +67,6 @@ import com.tangem.feature.referral.presentation.R
import com.valentinilk.shimmer.shimmer
import kotlinx.coroutines.launch
-private const val GOOGLE_DOC_LINK_WRAPPER = "https://docs.google.com/viewer?url=%s&embedded=false"
-
/**
* Referral program screen for participant and non-participant
*
@@ -87,8 +85,8 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder) {
sheetContent = {
AgreementBottomSheetContent(
url = when (val state = stateHolder.referralInfoState) {
- is ReferralInfoState.NonParticipantContent -> String.format(GOOGLE_DOC_LINK_WRAPPER, state.url)
- is ReferralInfoState.ParticipantContent -> String.format(GOOGLE_DOC_LINK_WRAPPER, state.url)
+ is ReferralInfoState.NonParticipantContent -> state.url
+ is ReferralInfoState.ParticipantContent -> state.url
is ReferralInfoState.Loading -> ""
},
)
diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt
index 381ba9784e..9f042170b7 100644
--- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt
+++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt
@@ -26,6 +26,7 @@ import com.tangem.lib.crypto.models.ProxyFiatCurrency
import com.tangem.lib.crypto.models.transactions.SendTxResult
import com.tangem.utils.toFiatString
import java.math.BigDecimal
+import java.math.RoundingMode
import javax.inject.Inject
@Suppress("LargeClass")
@@ -496,6 +497,12 @@ internal class SwapInteractorImpl @Inject constructor(
),
),
fee = formattedFee,
+ priceImpact = calculatePriceImpact(
+ fromTokenAmount = fromTokenAmount.value,
+ fromRate = rates[fromToken.id] ?: 0.0,
+ toTokenAmount = toTokenAmount.value,
+ toRate = rates[toToken.id] ?: 0.0,
+ ),
networkCurrency = userWalletManager.getNetworkCurrency(networkId),
preparedSwapConfigState = preparedSwapConfigState,
swapDataModel = swapDataModel,
@@ -605,6 +612,17 @@ internal class SwapInteractorImpl @Inject constructor(
return amountToSwap.replace(",", ".").toBigDecimalOrNull()
}
+ private fun calculatePriceImpact(
+ fromTokenAmount: BigDecimal,
+ fromRate: Double,
+ toTokenAmount: BigDecimal,
+ toRate: Double,
+ ): Float {
+ val toTokenFiatValue = toTokenAmount.multiply(toRate.toBigDecimal()).setScale(2, RoundingMode.HALF_UP)
+ val fromTokenFiatValue = fromTokenAmount.multiply(fromRate.toBigDecimal()).setScale(2, RoundingMode.HALF_UP)
+ return (BigDecimal.ONE - toTokenFiatValue / fromTokenFiatValue).toFloat()
+ }
+
companion object {
private const val DEFAULT_SLIPPAGE = 2
private const val ZERO_BALANCE = "0"
diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt
index 879dc1f124..1515683e56 100644
--- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt
+++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt
@@ -12,6 +12,7 @@ sealed interface SwapState {
val fromTokenInfo: TokenSwapInfo,
val toTokenInfo: TokenSwapInfo,
val fee: String,
+ val priceImpact: Float,
val networkCurrency: String,
val preparedSwapConfigState: PreparedSwapConfigState,
val permissionState: PermissionDataState = PermissionDataState.Empty,
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt
index 81d124d753..43a1953668 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt
@@ -84,7 +84,12 @@ sealed interface SwapWarning {
val onClick: () -> Unit,
) : SwapWarning
// data class RateExpired(val onClick: () -> Unit) : SwapWarning
- // object HighPriceImpact : SwapWarning
+ /**
+ * High price impact warning
+ *
+ * @property priceImpact in format = 10 (means 10%)
+ */
+ data class HighPriceImpact(val priceImpact: Int) : SwapWarning
}
enum class GenericWarningType {
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
index 08ae435304..4692699435 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
@@ -124,6 +124,10 @@ internal class StateBuilder(val actions: UiActions) {
if (!quoteModel.preparedSwapConfigState.isBalanceEnough) {
warnings.add(SwapWarning.InsufficientFunds)
}
+
+ if (quoteModel.priceImpact > PRICE_IMPACT_THRESHOLD) {
+ warnings.add(SwapWarning.HighPriceImpact((quoteModel.priceImpact * HUNDRED_PERCENTS).toInt()))
+ }
val feeState = if (quoteModel.preparedSwapConfigState.isFeeEnough) {
FeeState.Loaded(tangemFee = quoteModel.tangemFee, fee = quoteModel.fee)
} else {
@@ -358,5 +362,7 @@ internal class StateBuilder(val actions: UiActions) {
const val ADDRESS_MIN_LENGTH = 11
const val ADDRESS_FIRST_PART_LENGTH = 7
const val ADDRESS_SECOND_PART_LENGTH = 4
+ private const val PRICE_IMPACT_THRESHOLD = 0.1
+ private const val HUNDRED_PERCENTS = 100
}
}
\ No newline at end of file
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt
index afce1309c0..6a14863ede 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt
@@ -161,6 +161,7 @@ private fun MainInfo(state: SwapStateHolder) {
getActiveIconRes(state.blockchainId)
}
val (topCard, bottomCard, button) = createRefs()
+ val priceImpactWarning = state.warnings.filterIsInstance().firstOrNull()
TransactionCard(
type = state.sendCardData.type,
balance = state.sendCardData.balance,
@@ -168,6 +169,7 @@ private fun MainInfo(state: SwapStateHolder) {
amountEquivalent = state.sendCardData.amountEquivalent,
tokenIconUrl = state.sendCardData.tokenIconUrl,
tokenCurrency = state.sendCardData.tokenCurrency,
+ priceImpact = priceImpactWarning,
networkIconRes = if (state.sendCardData.isNotNativeToken) networkIconRes else null,
iconPlaceholder = state.sendCardData.coinId?.let {
getActiveIconResByCoinId(it, state.networkId)
@@ -185,6 +187,7 @@ private fun MainInfo(state: SwapStateHolder) {
amountEquivalent = state.receiveCardData.amountEquivalent,
tokenIconUrl = state.receiveCardData.tokenIconUrl,
tokenCurrency = state.receiveCardData.tokenCurrency,
+ priceImpact = priceImpactWarning,
networkIconRes = if (state.receiveCardData.isNotNativeToken) networkIconRes else null,
iconPlaceholder = state.receiveCardData.coinId?.let {
getActiveIconResByCoinId(it, state.networkId)
@@ -289,9 +292,12 @@ private fun SwapWarnings(
) {
warnings.forEach { warning ->
when (warning) {
- // SwapWarning.HighPriceImpact -> {
- // WarningCard(title = stringResource(id = R.string.), description = stringResource(id = R.string.))
- // }
+ is SwapWarning.HighPriceImpact -> {
+ WarningCard(
+ title = stringResource(id = R.string.swapping_high_price_impact),
+ description = stringResource(id = R.string.swapping_high_price_impact_description),
+ )
+ }
is SwapWarning.PermissionNeeded -> {
WarningCard(
title = stringResource(id = R.string.swapping_permission_header),
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt
index 7bcea09007..1e886b5303 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt
@@ -32,6 +32,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@@ -45,7 +48,9 @@ import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.components.SpacerH4
import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.SpacerW16
+import com.tangem.core.ui.components.SpacerW4
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.feature.swap.models.SwapWarning
import com.tangem.feature.swap.models.TransactionCardType
import com.valentinilk.shimmer.shimmer
@@ -57,6 +62,7 @@ fun TransactionCard(
tokenIconUrl: String,
tokenCurrency: String,
amountEquivalent: String?,
+ priceImpact: SwapWarning.HighPriceImpact?,
textFieldValue: TextFieldValue?,
modifier: Modifier = Modifier,
@DrawableRes iconPlaceholder: Int? = null,
@@ -82,6 +88,7 @@ fun TransactionCard(
type = type,
amountEquivalent = amountEquivalent,
textFieldValue = textFieldValue,
+ priceImpact = priceImpact,
)
}
@@ -171,6 +178,7 @@ private fun Header(
private fun Content(
type: TransactionCardType,
amountEquivalent: String?,
+ priceImpact: SwapWarning.HighPriceImpact?,
textFieldValue: TextFieldValue?,
) {
Row(
@@ -227,12 +235,34 @@ private fun Content(
SpacerH8()
if (amountEquivalent != null) {
- Text(
- text = amountEquivalent,
- color = TangemTheme.colors.text.tertiary,
- style = TangemTheme.typography.body2,
- modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20),
- )
+ if (type is TransactionCardType.ReceiveCard && priceImpact != null) {
+ Row {
+ Text(
+ text = makePriceImpactBalanceWarning(amountEquivalent, priceImpact.priceImpact),
+ color = TangemTheme.colors.text.tertiary,
+ style = TangemTheme.typography.body2,
+ modifier = Modifier
+ .defaultMinSize(minHeight = TangemTheme.dimens.size20)
+ .align(Alignment.CenterVertically),
+ )
+ SpacerW4()
+ Icon(
+ painter = painterResource(id = R.drawable.ic_alert_24),
+ contentDescription = null,
+ tint = TangemTheme.colors.icon.attention,
+ modifier = Modifier
+ .size(size = TangemTheme.dimens.size20)
+ .align(Alignment.CenterVertically),
+ )
+ }
+ } else {
+ Text(
+ text = amountEquivalent,
+ color = TangemTheme.colors.text.tertiary,
+ style = TangemTheme.typography.body2,
+ modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20),
+ )
+ }
} else {
Box(
modifier = Modifier
@@ -339,6 +369,19 @@ fun ChangeTokenSelector() {
}
}
+@Composable
+private fun makePriceImpactBalanceWarning(value: String, priceImpactPercents: Int): AnnotatedString {
+ val fullValue = "$value (-$priceImpactPercents%)"
+ return buildAnnotatedString {
+ append(fullValue)
+ addStyle(
+ style = SpanStyle(color = TangemTheme.colors.text.attention),
+ start = value.length,
+ end = fullValue.length,
+ )
+ }
+}
+
@Composable
private fun TokenImageShimmer(
modifier: Modifier = Modifier,
@@ -384,6 +427,7 @@ private fun TransactionCardPreview() {
onChangeTokenClick = {},
balance = "123",
textFieldValue = TextFieldValue(),
+ priceImpact = null,
)
}