Updated on 2026-08-14

This commit is contained in:
Tangem 2023-02-16 15:27:52 +03:00
parent 7b15bb555e
commit 7f3d810755
13 changed files with 104 additions and 14 deletions

View file

@ -335,6 +335,8 @@
<string name="swapping_error_wrapper">Error: %s</string>
<string name="swapping_generic_error">There was an error. Please try again.</string>
<string name="swapping_give_permission">Give Permission</string>
<string name="swapping_high_price_impact">High price impact!</string>
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
<string name="swapping_insufficient_funds">Insufficient funds</string>
<string name="swapping_not_enough_funds_for_fee">Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.</string>
<string name="swapping_pending_transaction_subtitle">Transaction in progress...</string>

View file

@ -335,6 +335,8 @@
<string name="swapping_error_wrapper">Error: %s</string>
<string name="swapping_generic_error">There was an error. Please try again.</string>
<string name="swapping_give_permission">Give Permission</string>
<string name="swapping_high_price_impact">High price impact!</string>
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
<string name="swapping_insufficient_funds">Insufficient funds</string>
<string name="swapping_not_enough_funds_for_fee">Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.</string>
<string name="swapping_pending_transaction_subtitle">Transaction in progress...</string>

View file

@ -335,6 +335,8 @@
<string name="swapping_error_wrapper">Error: %s</string>
<string name="swapping_generic_error">There was an error. Please try again.</string>
<string name="swapping_give_permission">Give Permission</string>
<string name="swapping_high_price_impact">High price impact!</string>
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
<string name="swapping_insufficient_funds">Insufficient funds</string>
<string name="swapping_not_enough_funds_for_fee">Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.</string>
<string name="swapping_pending_transaction_subtitle">Transaction in progress...</string>

View file

@ -335,6 +335,8 @@
<string name="swapping_error_wrapper">Ошибка: %s</string>
<string name="swapping_generic_error">Произошла ошибка. Пожалуйста, попробуйте еще раз.</string>
<string name="swapping_give_permission">Дать разрешение</string>
<string name="swapping_high_price_impact">Сильные колебания цены!</string>
<string name="swapping_high_price_impact_description">Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму.</string>
<string name="swapping_insufficient_funds">Недостаточно средств</string>
<string name="swapping_not_enough_funds_for_fee">Недостаточно средств для оплаты на вашем %1$s кошельке для создания транзакции. Сначала пополните свой %2$s кошелек.</string>
<string name="swapping_pending_transaction_subtitle">Транзакция в процессе…</string>

View file

@ -335,6 +335,8 @@
<string name="swapping_error_wrapper">錯誤: %s</string>
<string name="swapping_generic_error">有錯誤。請再試一遍</string>
<string name="swapping_give_permission">賦予權限</string>
<string name="swapping_high_price_impact">High price impact!</string>
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
<string name="swapping_insufficient_funds">餘額不足</string>
<string name="swapping_not_enough_funds_for_fee">您的 %1$s 錢包中沒有足夠的資金來創建交易。首先為您的 %2$s 錢包充值</string>
<string name="swapping_pending_transaction_subtitle">交易進行中...</string>

View file

@ -335,6 +335,8 @@
<string name="swapping_error_wrapper">Error: %s</string>
<string name="swapping_generic_error">There was an error. Please try again.</string>
<string name="swapping_give_permission">Give Permission</string>
<string name="swapping_high_price_impact">High price impact!</string>
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
<string name="swapping_insufficient_funds">Insufficient funds</string>
<string name="swapping_not_enough_funds_for_fee">Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.</string>
<string name="swapping_pending_transaction_subtitle">Transaction in progress...</string>

View file

@ -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 -> ""
},
)

View file

@ -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"

View file

@ -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,

View file

@ -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 {

View file

@ -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
}
}

View file

@ -161,6 +161,7 @@ private fun MainInfo(state: SwapStateHolder) {
getActiveIconRes(state.blockchainId)
}
val (topCard, bottomCard, button) = createRefs()
val priceImpactWarning = state.warnings.filterIsInstance<SwapWarning.HighPriceImpact>().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),

View file

@ -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,
)
}