Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-25 11:36:31 +01:00
commit c5cab520f7
7 changed files with 85 additions and 32 deletions

View file

@ -4,7 +4,10 @@ import java.math.BigDecimal
sealed class Warning {
data class ExistentialDepositWarning(val existentialDeposit: BigDecimal) : Warning()
data class ExistentialDepositWarning(
val existentialDeposit: BigDecimal,
val minAvailableAmount: BigDecimal,
) : Warning()
data class MinAmountWarning(val dustValue: BigDecimal) : Warning()

View file

@ -376,7 +376,7 @@ internal class SwapInteractorImpl @Inject constructor(
val fromToken = fromTokenStatus.currency
val userWalletId = getSelectedWallet()?.walletId ?: return emptyList()
val warnings = mutableListOf<Warning>()
manageExistentialDepositWarning(warnings, userWalletId, amount, fromToken)
manageExistentialDepositWarning(warnings, userWalletId, amount, fromToken, feeState)
manageDustWarning(warnings, feeState, userWalletId, fromTokenStatus, amount)
manageReduceAmountWarning(warnings, fromTokenStatus, amount)
return warnings
@ -387,6 +387,7 @@ internal class SwapInteractorImpl @Inject constructor(
userWalletId: UserWalletId,
amount: SwapAmount,
fromToken: CryptoCurrency,
txFee: TxFeeState,
) {
val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, fromToken.network)
if (existentialDeposit != null) {
@ -394,8 +395,18 @@ internal class SwapInteractorImpl @Inject constructor(
fromToken.network.backendId,
fromToken.network.derivationPath.value,
) ?: ProxyAmount.empty()
if (nativeBalance.value.minus(amount.value) < existentialDeposit) {
warnings.add(Warning.ExistentialDepositWarning(existentialDeposit))
// ignore if amount is bigger than balance
if (amount.value > nativeBalance.value) {
return
}
val fee = when (txFee) {
TxFeeState.Empty -> BigDecimal.ZERO
is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue
is TxFeeState.SingleFeeState -> txFee.fee.feeValue
}
val minAvailableAmount = nativeBalance.value - existentialDeposit - fee
if (nativeBalance.value.minus(amount.value + fee) < existentialDeposit) {
warnings.add(Warning.ExistentialDepositWarning(existentialDeposit, minAvailableAmount))
}
}
}

View file

@ -15,6 +15,7 @@ data class UiActions(
val onBackClicked: () -> Unit,
val onMaxAmountSelected: () -> Unit,
val onReduceAmount: (SwapAmount) -> Unit,
val onLeaveExistentialDeposit: (SwapAmount) -> Unit,
val onReduceAmountIgnoreClick: () -> Unit,
val openPermissionBottomSheet: () -> Unit,
val onChangeApproveType: (ApproveType) -> Unit,

View file

@ -365,21 +365,7 @@ internal class StateBuilder(
quoteModel.warnings.forEach {
when (it) {
is Warning.ExistentialDepositWarning -> {
warnings.add(
SwapWarning.GeneralInformational(
NotificationConfig(
title = resourceReference(R.string.warning_existential_deposit_title),
subtitle = resourceReference(
R.string.warning_existential_deposit_message,
wrappedList(
quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency.name,
it.existentialDeposit.toPlainString(),
),
),
iconResId = R.drawable.ic_alert_circle_24,
),
),
)
warnings.add(createLeaveExistentialDepositWarning(quoteModel, it))
}
is Warning.MinAmountWarning -> {
warnings.add(
@ -444,6 +430,41 @@ internal class StateBuilder(
}
}
private fun createLeaveExistentialDepositWarning(
quoteModel: SwapState.QuotesLoadedState,
domainWarning: Warning.ExistentialDepositWarning,
): SwapWarning {
val fromCurrency = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency
val deposit = BigDecimalFormatter.formatCryptoAmountUncapped(
cryptoAmount = domainWarning.existentialDeposit,
cryptoCurrency = fromCurrency,
)
return SwapWarning.GeneralError(
NotificationConfig(
title = resourceReference(R.string.send_notification_existential_deposit_title),
subtitle = resourceReference(
R.string.send_notification_existential_deposit_text,
wrappedList(deposit),
),
iconResId = R.drawable.ic_alert_circle_24,
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
text = resourceReference(
R.string.send_notification_leave_button,
wrappedList(deposit),
),
onClick = {
actions.onLeaveExistentialDeposit(
SwapAmount(
domainWarning.minAvailableAmount,
fromCurrency.decimals,
),
)
},
),
),
)
}
private fun maybeAddPermissionNeededWarning(
quoteModel: SwapState.QuotesLoadedState,
warnings: MutableList<SwapWarning>,
@ -469,19 +490,25 @@ internal class StateBuilder(
when (quoteModel.preparedSwapConfigState.includeFeeInAmount) {
is IncludeFeeInAmount.Included -> {
val fee = selectFeeByType(selectedFeeType, quoteModel.txFee) ?: return
warnings.add(
SwapWarning.GeneralWarning(
createNetworkFeeCoverageNotificationConfig(
fee.feeCryptoFormatted,
fee.feeFiatFormatted,
if (needShowNetworkFeeCoverageWarningShow(quoteModel)) {
warnings.add(
SwapWarning.GeneralWarning(
createNetworkFeeCoverageNotificationConfig(
fee.feeCryptoFormatted,
fee.feeFiatFormatted,
),
),
),
)
)
}
}
else -> Unit
}
}
private fun needShowNetworkFeeCoverageWarningShow(quoteModel: SwapState.QuotesLoadedState): Boolean {
return quoteModel.warnings.none { it is Warning.ExistentialDepositWarning }
}
private fun selectFeeByType(feeType: FeeType, txFeeState: TxFeeState): TxFee? {
return when (txFeeState) {
TxFeeState.Empty -> null
@ -543,7 +570,12 @@ internal class StateBuilder(
// check has has outgoing transaction
if (preparedSwapConfigState.hasOutgoingTransaction) return false
// check has MinAmountWarning warning
if (quoteModel.warnings.filterIsInstance<Warning.MinAmountWarning>().isNotEmpty()) return false
val hasCriticalWarning = quoteModel.warnings.any {
it is Warning.MinAmountWarning || it is Warning.ExistentialDepositWarning
}
if (hasCriticalWarning) return false
return when (preparedSwapConfigState.includeFeeInAmount) {
IncludeFeeInAmount.BalanceNotEnough -> false
IncludeFeeInAmount.Excluded ->

View file

@ -283,7 +283,10 @@ private fun Content(
if (amountEquivalent != null) {
if (type is TransactionCardType.ReadOnly) {
Row(modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20)) {
Row(
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20),
verticalAlignment = Alignment.CenterVertically,
) {
if (priceImpact is PriceImpact.Value) {
Text(
text = makePriceImpactBalanceWarning(
@ -292,8 +295,6 @@ private fun Content(
),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
modifier = Modifier
.align(Alignment.CenterVertically),
)
} else {
AnimatedContent(targetState = amountEquivalent, label = "") {
@ -313,7 +314,7 @@ private fun Content(
modifier = Modifier.size(size = TangemTheme.dimens.size20),
) {
Icon(
painter = painterResource(id = R.drawable.ic_alert_24),
painter = painterResource(id = R.drawable.ic_information_24),
contentDescription = null,
tint = if (priceImpact is PriceImpact.Value) {
TangemTheme.colors.text.attention

View file

@ -817,6 +817,10 @@ internal class SwapViewModel @Inject constructor(
onAmountChanged(newAmount.formatToUIRepresentation())
}
private fun onLeaveExistentialDepositClicked(newAmount: SwapAmount) {
onAmountChanged(newAmount.formatToUIRepresentation())
}
@Suppress("UnusedPrivateMember")
private fun onAmountSelected(selected: Boolean) {
if (selected) {
@ -881,6 +885,7 @@ internal class SwapViewModel @Inject constructor(
},
onMaxAmountSelected = ::onMaxAmountClicked,
onReduceAmount = ::onReduceAmountClicked,
onLeaveExistentialDeposit = ::onLeaveExistentialDepositClicked,
onReduceAmountIgnoreClick = {
uiState = uiState.copy(
reduceAmountIgnore = true,

View file

@ -86,7 +86,7 @@ markdown = "0.7.2"
# endregion Other libraries
# region Tangem
tangemBlockchainSdk = "release-app_5.11-676"
tangemBlockchainSdk = "release-app_5.11-686"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "release-app_5.11-365"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^