Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-18 17:01:35 +03:00
parent a80542d5da
commit 3d832ddd3e
13 changed files with 88 additions and 24 deletions

View file

@ -9,6 +9,7 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.shorted
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import java.math.BigDecimal
sealed class NotificationUM(val config: NotificationConfig) {
@ -307,4 +308,17 @@ sealed class NotificationUM(val config: NotificationConfig) {
),
)
}
sealed interface Solana {
data class RentInfo(
private val rentInfo: CryptoCurrencyWarning.Rent,
) : Info(
title = TextReference.Res(R.string.warning_rent_fee_title),
subtitle = TextReference.Res(
id = R.string.warning_solana_rent_fee_message,
formatArgs = wrappedList(rentInfo.rent, rentInfo.exemptionAmount),
),
)
}
}

View file

@ -19,6 +19,7 @@ dependencies {
implementation(projects.domain.tokens.models)
implementation(projects.domain.txhistory.models)
implementation(projects.domain.wallets.models)
implementation(projects.domain.staking.models)
/** Project - Data */
implementation(projects.core.datasource)

View file

@ -6,13 +6,17 @@ import com.tangem.blockchain.common.MinimumSendAmountProvider
import com.tangem.blockchain.common.ReserveAmountProvider
import com.tangem.blockchain.common.UtxoAmountLimitProvider
import com.tangem.data.tokens.converters.UtxoConverter
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.CurrencyAmount
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.isZero
import kotlinx.coroutines.withContext
import java.math.BigDecimal
@ -118,4 +122,23 @@ internal class DefaultCurrencyChecksRepository(
}
return utxoAmount?.let(UtxoConverter()::convert)
}
override suspend fun getRentInfoWarning(
userWalletId: UserWalletId,
currencyStatus: CryptoCurrencyStatus,
balanceAfterTransaction: BigDecimal?,
): CryptoCurrencyWarning.Rent? {
val rentData = walletManagersFacade.getRentInfo(userWalletId, currencyStatus.currency.network) ?: return null
val balanceValue = currencyStatus.value as? CryptoCurrencyStatus.Loaded ?: return null
val stakingTotalBalance =
(balanceValue.yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance() ?: BigDecimal.ZERO
val amount = balanceAfterTransaction ?: balanceValue.amount
return when {
amount.isZero() && stakingTotalBalance.isZero() -> null
amount < rentData.exemptionAmount && stakingTotalBalance.isZero() -> {
CryptoCurrencyWarning.Rent(rentData.rent, rentData.exemptionAmount)
}
else -> null
}
}
}

View file

@ -25,11 +25,11 @@ import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryState
import com.tangem.domain.walletmanager.model.RentData
import com.tangem.domain.walletmanager.model.SmartContractMethod
import com.tangem.domain.walletmanager.model.TokenInfo
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
@ -413,7 +413,7 @@ class DefaultWalletManagersFacade(
return manager?.wallet?.addresses.orEmpty()
}
override suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): CryptoCurrencyWarning.Rent? {
override suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): RentData? {
val manager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
@ -422,24 +422,7 @@ class DefaultWalletManagersFacade(
return when (val result = manager.minimalBalanceForRentExemption()) {
is Result.Success -> {
val balance = manager.wallet.fundsAvailable(AmountType.Coin)
val outgoingTxs = manager.wallet.recentTransactions
.filter {
it.sourceAddress == manager.wallet.address &&
it.amount.type == AmountType.Coin &&
it.status != TransactionStatus.Confirmed
}
val rentExempt = result.data
val setRent = if (outgoingTxs.isEmpty()) {
balance < rentExempt
} else {
val outgoingAmount = outgoingTxs.sumOf { it.amount.value ?: BigDecimal.ZERO }
val rest = balance.minus(outgoingAmount)
balance < rest
}
if (setRent) CryptoCurrencyWarning.Rent(manager.rentAmount(), rentExempt) else null
RentData(manager.rentAmount(), result.data)
}
is Result.Failure -> null
}

View file

@ -12,11 +12,11 @@ import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryState
import com.tangem.domain.walletmanager.model.RentData
import com.tangem.domain.walletmanager.model.TokenInfo
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
import com.tangem.domain.wallets.models.UserWalletId
@ -140,7 +140,7 @@ interface WalletManagersFacade {
* @param userWalletId selected wallet id
* @param network network of currency
*/
suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): CryptoCurrencyWarning.Rent?
suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): RentData?
@Deprecated("Will be removed in future")
fun getAll(userWalletId: UserWalletId): Flow<List<WalletManager>>

View file

@ -0,0 +1,11 @@
package com.tangem.domain.walletmanager.model
import java.math.BigDecimal
/**
* Represents wallet blockchain rent
* @param rent Amount that will be charged in overtime if the blockchain does not have an amount greater than
* the [exemptionAmount]
* @param exemptionAmount Amount that should be on the blockchain balance not to pay rent
*/
data class RentData(val rent: BigDecimal, val exemptionAmount: BigDecimal)

View file

@ -10,4 +10,5 @@ data class CryptoCurrencyCheck(
val existentialDeposit: BigDecimal?,
val utxoAmountLimit: UtxoAmountLimit?,
val isAccountFunded: Boolean,
val rentWarning: CryptoCurrencyWarning.Rent?,
)

View file

@ -18,6 +18,7 @@ class GetCurrencyCheckUseCase(
currencyStatus: CryptoCurrencyStatus,
amount: BigDecimal?,
fee: BigDecimal?,
balanceAfterTransaction: BigDecimal?,
recipientAddress: String? = null,
): CryptoCurrencyCheck {
return withContext(dispatchers.io) {
@ -26,6 +27,8 @@ class GetCurrencyCheckUseCase(
val reserveAmount = currencyChecksRepository.getReserveAmount(userWalletId, network)
val minimumSendAmount = currencyChecksRepository.getMinimumSendAmount(userWalletId, network)
val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, network)
val rentWarning =
currencyChecksRepository.getRentInfoWarning(userWalletId, currencyStatus, balanceAfterTransaction)
val isAccountFunded = recipientAddress?.let {
currencyChecksRepository.checkIfAccountFunded(
userWalletId,
@ -51,6 +54,7 @@ class GetCurrencyCheckUseCase(
existentialDeposit = existentialDeposit,
utxoAmountLimit = utxoAmountLimit,
isAccountFunded = isAccountFunded,
rentWarning = rentWarning,
)
}
}

View file

@ -58,7 +58,7 @@ class GetCurrencyWarningsUseCase(
derivationPath = derivationPath,
isSingleWalletWithTokens = isSingleWalletWithTokens,
),
flowOf(walletManagersFacade.getRentInfo(userWalletId, currency.network)),
flowOf(currencyChecksRepository.getRentInfoWarning(userWalletId, currencyStatus, null)),
flowOf(currencyChecksRepository.getExistentialDeposit(userWalletId, currency.network)),
flowOf(currencyChecksRepository.getFeeResourceAmount(userWalletId, currency.network)),
getSwapPromoNotificationWarning(

View file

@ -1,8 +1,10 @@
package com.tangem.domain.tokens.repository
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.CurrencyAmount
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.wallets.models.UserWalletId
import java.math.BigDecimal
@ -39,4 +41,14 @@ interface CurrencyChecksRepository {
amount: BigDecimal,
fee: BigDecimal,
): UtxoAmountLimit?
/**
* Checks if need warning for Solana rent
* @param balanceAfterTransaction if non null, checks with balance remains after transaction
*/
suspend fun getRentInfoWarning(
userWalletId: UserWalletId,
currencyStatus: CryptoCurrencyStatus,
balanceAfterTransaction: BigDecimal?,
): CryptoCurrencyWarning.Rent?
}

View file

@ -22,6 +22,7 @@ import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase
import com.tangem.domain.tokens.GetCurrencyCheckUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase
import com.tangem.domain.utils.convertToSdkAmount
@ -93,12 +94,15 @@ internal class SendNotificationFactory(
val feeError = (feeState.feeSelectorState as? FeeSelectorState.Error)?.error
val recipientAddress = state.recipientState?.addressTextField?.value
val statusValue = cryptoCurrencyStatus.value as? CryptoCurrencyStatus.Loaded
val balanceAfterTransaction = statusValue?.let { it.amount - sendingAmount - feeValue }
val currencyCheck = getCurrencyCheckUseCase(
userWalletId = userWalletId,
currencyStatus = cryptoCurrencyStatus,
amount = sendingAmount,
fee = feeValue,
recipientAddress = recipientAddress,
balanceAfterTransaction = balanceAfterTransaction,
)
buildList {
addErrorNotifications(
@ -221,6 +225,11 @@ internal class SendNotificationFactory(
network = cryptoCurrencyStatus.currency.network,
).leftOrNull()
}
addRentExemptionNotification(
rentWarning = currencyCheck.rentWarning,
)
addExistentialWarningNotification(
existentialDeposit = currencyCheck.existentialDeposit,
feeAmount = feeState.fee?.amount?.value.orZero(),
@ -303,4 +312,9 @@ internal class SendNotificationFactory(
add(NotificationUM.Warning.TooHigh(diff))
}
}
private fun MutableList<NotificationUM>.addRentExemptionNotification(rentWarning: CryptoCurrencyWarning.Rent?) {
if (rentWarning == null) return
add(NotificationUM.Solana.RentInfo(rentWarning))
}
}

View file

@ -44,7 +44,7 @@ internal fun LazyListScope.notifications(
is NotificationUM.Warning,
-> null
is NotificationUM.Error -> TangemTheme.colors.icon.warning
is NotificationUM.Info -> null
is NotificationUM.Info -> TangemTheme.colors.icon.accent
},
isEnabled = !isClickDisabled,
)

View file

@ -638,6 +638,7 @@ internal class StakingViewModel @Inject constructor(
currencyStatus = cryptoCurrencyStatus,
amount = amount,
fee = fee,
balanceAfterTransaction = null, // ignore this in staking
)
stateController.update(
AddStakingNotificationsTransformer(