Updated on 2026-08-14

This commit is contained in:
Tangem 2024-02-05 16:34:22 +03:00
parent 18d7ee1ec6
commit 6411d7810a
15 changed files with 195 additions and 136 deletions

View file

@ -99,6 +99,7 @@ internal object TokensDomainModule {
networksRepository: NetworksRepository,
marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
swapRepository: SwapRepository,
currencyChecksRepository: CurrencyChecksRepository,
showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
dispatchers: CoroutineDispatcherProvider,
): GetCurrencyWarningsUseCase {
@ -108,6 +109,7 @@ internal object TokensDomainModule {
quotesRepository = quotesRepository,
networksRepository = networksRepository,
marketCryptoCurrencyRepository = marketCryptoCurrencyRepository,
currencyChecksRepository = currencyChecksRepository,
swapRepository = swapRepository,
showSwapPromoTokenUseCase = showSwapPromoTokenUseCase,
dispatchers = dispatchers,

View file

@ -0,0 +1,14 @@
package com.tangem.data.tokens.converters
import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.common.UtxoAmountLimit as BlockchainUtxoAmountLimit
internal class UtxoConverter : Converter<BlockchainUtxoAmountLimit, UtxoAmountLimit> {
override fun convert(value: BlockchainUtxoAmountLimit): UtxoAmountLimit {
return UtxoAmountLimit(
maxLimit = value.maxLimit,
maxAmount = value.maxAmount,
)
}
}

View file

@ -113,4 +113,10 @@ internal object TokensDataModule {
): NetworksCompatibilityRepository {
return DefaultNetworksCompatibilityRepository(userWalletsStore = userWalletsStore, dispatchers = dispatchers)
}
@Provides
@Singleton
fun provideCurrencyChecksRepository(walletManagersFacade: WalletManagersFacade): CurrencyChecksRepository {
return DefaultCurrencyChecksRepository(walletManagersFacade = walletManagersFacade)
}
}

View file

@ -0,0 +1,70 @@
package com.tangem.data.tokens.repository
import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
import com.tangem.blockchain.common.ReserveAmountProvider
import com.tangem.blockchain.common.UtxoAmountLimitProvider
import com.tangem.data.tokens.converters.UtxoConverter
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import java.math.BigDecimal
internal class DefaultCurrencyChecksRepository(
private val walletManagersFacade: WalletManagersFacade,
) : CurrencyChecksRepository {
override suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? {
val manager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return if (manager is ExistentialDepositProvider) manager.getExistentialDeposit() else null
}
override suspend fun getDustValue(userWalletId: UserWalletId, network: Network): BigDecimal? {
val manager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return manager?.dustValue
}
override suspend fun getReserveAmount(userWalletId: UserWalletId, network: Network): BigDecimal? {
val manager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return if (manager is ReserveAmountProvider) manager.getReserveAmount() else null
}
override suspend fun checkIfAccountFunded(userWalletId: UserWalletId, network: Network, address: String): Boolean {
val manager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return if (manager is ReserveAmountProvider) manager.isAccountFunded(address) else true
}
override suspend fun checkUtxoAmountLimit(
userWalletId: UserWalletId,
network: Network,
amount: BigDecimal,
fee: BigDecimal,
): UtxoAmountLimit? {
val manager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
val utxoAmount = if (manager is UtxoAmountLimitProvider) {
manager.checkUtxoAmountLimit(amount, fee)
} else {
null
}
return utxoAmount?.let(UtxoConverter()::convert)
}
}

View file

@ -5,7 +5,6 @@ import arrow.core.raise.either
import arrow.core.raise.ensureNotNull
import arrow.core.right
import com.squareup.moshi.Moshi
import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.Address
@ -321,6 +320,16 @@ class DefaultWalletManagersFacade(
return walletManager
}
@Deprecated("Will be removed in future")
override suspend fun getOrCreateWalletManager(userWalletId: UserWalletId, network: Network): WalletManager? {
val blockchain = Blockchain.fromId(network.id.value)
return getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
}
@Deprecated("Will be removed in future")
override suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List<WalletManager> {
return walletManagersStore.getAllSync(userWalletId)
@ -376,63 +385,6 @@ class DefaultWalletManagersFacade(
}
}
@Deprecated("Will be removed in future")
override suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? {
val manager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return if (manager is ExistentialDepositProvider) manager.getExistentialDeposit() else null
}
@Deprecated("Will be removed in future")
override suspend fun getDustValue(userWalletId: UserWalletId, network: Network): BigDecimal? {
val blockchain = Blockchain.fromId(network.id.value)
val manager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
return manager?.dustValue
}
@Deprecated("Will be removed in future")
override suspend fun getReserveAmount(userWalletId: UserWalletId, network: Network): BigDecimal? {
val manager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return if (manager is ReserveAmountProvider) manager.getReserveAmount() else null
}
@Deprecated("Will be removed in future")
override suspend fun checkIfAccountFunded(userWalletId: UserWalletId, network: Network, address: String): Boolean {
val manager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return if (manager is ReserveAmountProvider) manager.isAccountFunded(address) else true
}
@Deprecated("Will be removed in future")
override suspend fun checkUtxoAmountLimit(
userWalletId: UserWalletId,
network: Network,
amount: BigDecimal,
fee: BigDecimal,
): UtxoAmountLimit? {
val manager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return if (manager is UtxoAmountLimitProvider) manager.checkUtxoAmountLimit(amount, fee) else null
}
@Deprecated("Will be removed in future")
override fun getAll(userWalletId: UserWalletId): Flow<List<WalletManager>> {
return walletManagersStore.getAll(userWalletId)
@ -605,13 +557,4 @@ class DefaultWalletManagersFacade(
walletManager.addTokens(tokensToAdd)
}
private suspend fun getOrCreateWalletManager(userWalletId: UserWalletId, network: Network): WalletManager? {
val blockchain = Blockchain.fromId(network.id.value)
return getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
}
}

View file

@ -108,6 +108,9 @@ interface WalletManagersFacade {
derivationPath: String?,
): WalletManager?
@Deprecated("Will be removed in future")
suspend fun getOrCreateWalletManager(userWalletId: UserWalletId, network: Network): WalletManager?
@Deprecated("Will be removed in future")
suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List<WalletManager>
@ -136,47 +139,6 @@ interface WalletManagersFacade {
*/
suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): CryptoCurrencyWarning.Rent?
/**
* Returns value which indicates if the account balance drops below the existential deposit value, it will be
* deactivated and any remaining funds will be destroyed.
*
* [REDACTED_TODO_COMMENT]
*/
@Deprecated("Will be removed in future")
suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal?
@Deprecated("Will be removed in future")
suspend fun getDustValue(userWalletId: UserWalletId, network: Network): BigDecimal?
/**
* Returns reserve amount which is required to create an account
*
* [REDACTED_TODO_COMMENT]
*/
@Deprecated("Will be removed in future")
suspend fun getReserveAmount(userWalletId: UserWalletId, network: Network): BigDecimal?
/**
* Returns true if account with [address] was reserved with minimum amount
*
* [REDACTED_TODO_COMMENT]
*/
@Deprecated("Will be removed in future")
suspend fun checkIfAccountFunded(userWalletId: UserWalletId, network: Network, address: String): Boolean
/**
* Checks if transaction amount is within the UTXO limit
*
* [REDACTED_TODO_COMMENT]
*/
@Deprecated("Will be removed in future")
suspend fun checkUtxoAmountLimit(
userWalletId: UserWalletId,
network: Network,
amount: BigDecimal,
fee: BigDecimal,
): UtxoAmountLimit?
@Deprecated("Will be removed in future")
fun getAll(userWalletId: UserWalletId): Flow<List<WalletManager>>

View file

@ -0,0 +1,14 @@
package com.tangem.domain.tokens.model.blockchains
import java.math.BigDecimal
/**
* Model stores utxo limits
*
* @property maxLimit utxo limit
* @property maxAmount max amount
*/
data class UtxoAmountLimit(
val maxLimit: BigDecimal,
val maxAmount: BigDecimal,
)

View file

@ -7,10 +7,7 @@ import com.tangem.domain.tokens.model.FeePaidCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.repository.*
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.swap.domain.api.SwapRepository
@ -31,6 +28,7 @@ class GetCurrencyWarningsUseCase(
private val marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
private val showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
private val dispatchers: CoroutineDispatcherProvider,
private val currencyChecksRepository: CurrencyChecksRepository,
) {
suspend operator fun invoke(
@ -56,7 +54,7 @@ class GetCurrencyWarningsUseCase(
isSingleWalletWithTokens = isSingleWalletWithTokens,
),
flowOf(walletManagersFacade.getRentInfo(userWalletId, currency.network)),
flowOf(walletManagersFacade.getExistentialDeposit(userWalletId, currency.network)),
flowOf(currencyChecksRepository.getExistentialDeposit(userWalletId, currency.network)),
getSwapPromoNotificationWarning(
operations = operations,
userWalletId = userWalletId,

View file

@ -0,0 +1,32 @@
package com.tangem.domain.tokens.repository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
import com.tangem.domain.wallets.models.UserWalletId
import java.math.BigDecimal
interface CurrencyChecksRepository {
/**
* Returns value which indicates if the account balance drops below the existential deposit value, it will be
* deactivated and any remaining funds will be destroyed.
*/
suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal?
/** Returns dust value */
suspend fun getDustValue(userWalletId: UserWalletId, network: Network): BigDecimal?
/** Returns reserve amount which is required to create an account */
suspend fun getReserveAmount(userWalletId: UserWalletId, network: Network): BigDecimal?
/** Returns true if account with [address] was reserved with minimum amount */
suspend fun checkIfAccountFunded(userWalletId: UserWalletId, network: Network, address: String): Boolean
/** Checks if transaction amount is within the UTXO limit */
suspend fun checkUtxoAmountLimit(
userWalletId: UserWalletId,
network: Network,
amount: BigDecimal,
fee: BigDecimal,
): UtxoAmountLimit?
}

View file

@ -4,10 +4,11 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
import com.tangem.utils.isNullOrZero
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -21,7 +22,7 @@ internal class SendNotificationFactory(
private val coinCryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val currentStateProvider: Provider<SendUiState>,
private val userWalletProvider: Provider<UserWallet>,
private val walletManagersFacade: WalletManagersFacade,
private val currencyChecksRepository: CurrencyChecksRepository,
private val clickIntents: SendClickIntents,
) {
@ -39,6 +40,7 @@ internal class SendNotificationFactory(
addExceedBalanceNotification(feeAmount, sendAmount)
addInvalidAmountNotification(feeState.isSubtract, sendAmount)
addMinimumAmountErrorNotification(feeAmount, sendAmount)
addDustWarningNotification(feeAmount, sendAmount)
addReserveAmountErrorNotification(recipientState.addressTextField.value)
addTransactionLimitErrorNotification(feeAmount, sendAmount)
// warnings
@ -117,12 +119,12 @@ internal class SendNotificationFactory(
private suspend fun MutableList<SendNotification>.addReserveAmountErrorNotification(recipientAddress: String) {
val userWalletId = userWalletProvider().walletId
val cryptoCurrency = cryptoCurrencyStatusProvider().currency
val isAccountFunded = walletManagersFacade.checkIfAccountFunded(
val isAccountFunded = currencyChecksRepository.checkIfAccountFunded(
userWalletId,
cryptoCurrency.network,
recipientAddress,
)
val minimumAmount = walletManagersFacade.getReserveAmount(userWalletId, cryptoCurrency.network)
val minimumAmount = currencyChecksRepository.getReserveAmount(userWalletId, cryptoCurrency.network)
if (!isAccountFunded && minimumAmount != null && minimumAmount > BigDecimal.ZERO) {
add(
SendNotification.Error.ReserveAmountError(
@ -141,7 +143,7 @@ internal class SendNotificationFactory(
) {
val userWalletId = userWalletProvider().walletId
val cryptoCurrency = cryptoCurrencyStatusProvider().currency
val utxoLimit = walletManagersFacade.checkUtxoAmountLimit(
val utxoLimit = currencyChecksRepository.checkUtxoAmountLimit(
userWalletId = userWalletId,
network = cryptoCurrency.network,
amount = receivedAmount,
@ -173,7 +175,7 @@ internal class SendNotificationFactory(
} else {
feeAmount + receivedAmount
}
val currencyDeposit = walletManagersFacade.getExistentialDeposit(
val currencyDeposit = currencyChecksRepository.getExistentialDeposit(
userWalletId,
cryptoCurrency.network,
)
@ -210,6 +212,29 @@ internal class SendNotificationFactory(
}
}
private suspend fun MutableList<SendNotification>.addDustWarningNotification(
feeAmount: BigDecimal,
receivedAmount: BigDecimal,
) {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val dustValue = currencyChecksRepository.getDustValue(
userWalletProvider().walletId,
cryptoCurrencyStatus.currency.network,
)
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
if (dustValue != null && !balance.isNullOrZero() && receivedAmount < balance) {
val totalAmount = feeAmount + receivedAmount
val change = balance - totalAmount
val isChangeLowerThanDust = change < dustValue && change != BigDecimal.ZERO
val isShowWarning = totalAmount < dustValue || isChangeLowerThanDust
if (isShowWarning) {
add(
SendNotification.Error.MinimumAmountError(dustValue.toPlainString()),
)
}
}
}
companion object {
private const val CARDANO_MINIMUM = "1"
private const val DOGECOIN_MINIMUM = "0.01"

View file

@ -21,6 +21,7 @@ import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.utils.convertToAmount
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
import com.tangem.domain.transaction.usecase.GetFeeUseCase
@ -74,6 +75,7 @@ internal class SendViewModel @Inject constructor(
private val walletManagersFacade: WalletManagersFacade,
private val reduxStateHolder: ReduxStateHolder,
private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase,
currencyChecksRepository: CurrencyChecksRepository,
isFeeApproximateUseCase: IsFeeApproximateUseCase,
getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
validateWalletMemoUseCase: ValidateWalletMemoUseCase,
@ -136,7 +138,7 @@ internal class SendViewModel @Inject constructor(
coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus },
currentStateProvider = Provider { uiState },
userWalletProvider = Provider { userWallet },
walletManagersFacade = walletManagersFacade,
currencyChecksRepository = currencyChecksRepository,
clickIntents = this,
)

View file

@ -23,7 +23,6 @@ import com.tangem.datasource.crypto.DataSignatureVerifier
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.models.UserWalletId
@ -298,14 +297,6 @@ internal class DefaultSwapRepository @Inject constructor(
}
}
override suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? {
return walletManagersFacade.getExistentialDeposit(userWalletId, network)
}
override suspend fun getDustValue(userWalletId: UserWalletId, network: Network): BigDecimal? {
return walletManagersFacade.getDustValue(userWalletId, network)
}
private fun parseTxDetails(txDetailsJson: String): TxDetails? {
return try {
txDetailsMoshiAdapter.fromJson(txDetailsJson)

View file

@ -2,7 +2,6 @@ package com.tangem.feature.swap.domain.api
import arrow.core.Either
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.domain.*
@ -69,8 +68,4 @@ interface SwapRepository {
): Either<DataError, SwapDataModel>
fun getNativeTokenForNetwork(networkId: String): CryptoCurrency
suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal?
suspend fun getDustValue(userWalletId: UserWalletId, network: Network): BigDecimal?
}

View file

@ -16,6 +16,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.utils.convertToAmount
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.domain.transaction.usecase.EstimateFeeUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
@ -58,6 +59,7 @@ internal class SwapInteractorImpl @Inject constructor(
private val quotesRepository: QuotesRepository,
private val dispatcher: CoroutineDispatcherProvider,
private val swapTransactionRepository: SwapTransactionRepository,
private val currencyChecksRepository: CurrencyChecksRepository,
private val appCurrencyRepository: AppCurrencyRepository,
private val initialToCurrencyResolver: InitialToCurrencyResolver,
) : SwapInteractor {
@ -380,7 +382,7 @@ internal class SwapInteractorImpl @Inject constructor(
amount: SwapAmount,
fromToken: CryptoCurrency,
) {
val existentialDeposit = repository.getExistentialDeposit(userWalletId, fromToken.network)
val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, fromToken.network)
if (existentialDeposit != null) {
val nativeBalance = userWalletManager.getNativeTokenBalance(
fromToken.network.backendId,
@ -404,7 +406,7 @@ internal class SwapInteractorImpl @Inject constructor(
is TxFeeState.MultipleFeeState -> feeState.priorityFee.feeValue
is TxFeeState.SingleFeeState -> feeState.fee.feeValue
}
val dust = repository.getDustValue(userWalletId, fromTokenStatus.currency.network)
val dust = currencyChecksRepository.getDustValue(userWalletId, fromTokenStatus.currency.network)
val balance = fromTokenStatus.value.amount ?: BigDecimal.ZERO
if (dust != null &&
!balance.isNullOrZero() &&

View file

@ -7,6 +7,7 @@ import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.tokens.GetCardTokensListUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.transaction.TransactionRepository
@ -42,6 +43,7 @@ class SwapDomainModule {
quotesRepository: QuotesRepository,
swapTransactionRepository: SwapTransactionRepository,
appCurrencyRepository: AppCurrencyRepository,
currencyChecksRepository: CurrencyChecksRepository,
walletManagersFacade: WalletManagersFacade,
coroutineDispatcherProvider: CoroutineDispatcherProvider,
initialToCurrencyResolver: InitialToCurrencyResolver,
@ -59,6 +61,7 @@ class SwapDomainModule {
dispatcher = coroutineDispatcherProvider,
swapTransactionRepository = swapTransactionRepository,
appCurrencyRepository = appCurrencyRepository,
currencyChecksRepository = currencyChecksRepository,
initialToCurrencyResolver = initialToCurrencyResolver,
)
}