From 190e5ee2c64531559ae75b0019f467f2603cf079 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Sep 2023 17:58:14 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 19 +++++ .../DefaultWalletManagersFacade.kt | 52 +++++++++++- .../walletmanager/WalletManagersFacade.kt | 17 ++++ .../models/warnings/CryptoCurrencyWarning.kt | 28 +++++++ .../tokens/GetCurrencyWarningsUseCase.kt | 84 +++++++++++++++++++ 5 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/warnings/CryptoCurrencyWarning.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index b8046288d6..352c3c3353 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -6,6 +6,7 @@ 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.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -58,6 +59,24 @@ internal object TokensDomainModule { return GetCurrencyStatusUpdatesUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers) } + @Provides + @ViewModelScoped + fun provideGetCurrencyWarningsUseCase( + walletManagersFacade: WalletManagersFacade, + currenciesRepository: CurrenciesRepository, + quotesRepository: QuotesRepository, + networksRepository: NetworksRepository, + dispatchers: CoroutineDispatcherProvider, + ): GetCurrencyWarningsUseCase { + return GetCurrencyWarningsUseCase( + walletManagersFacade = walletManagersFacade, + currenciesRepository = currenciesRepository, + quotesRepository = quotesRepository, + networksRepository = networksRepository, + dispatchers = dispatchers, + ) + } + @Provides @ViewModelScoped fun provideGetPrimaryCurrencyUseCase( diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 1bbb724d3d..90455b7b92 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -1,8 +1,8 @@ package com.tangem.domain.walletmanager -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.WalletManager +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 import com.tangem.blockchain.extensions.Result import com.tangem.crypto.hdWallet.DerivationPath @@ -13,14 +13,17 @@ import com.tangem.domain.common.util.hasDerivation import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.models.CryptoCurrency import com.tangem.domain.tokens.models.Network +import com.tangem.domain.tokens.models.warnings.CryptoCurrencyWarning 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.UpdateWalletManagerResult import com.tangem.domain.walletmanager.utils.* +import com.tangem.domain.walletmanager.utils.WalletManagerFactory import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import timber.log.Timber +import java.math.BigDecimal // FIXME: Move to its own module and make internal @Deprecated("Inject the WalletManagerFacade interface using DI instead") @@ -218,6 +221,49 @@ class DefaultWalletManagersFacade( .orEmpty() } + override suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): CryptoCurrencyWarning.Rent? { + val userWallet = getUserWallet(userWalletId) + val blockchain = Blockchain.fromId(network.id.value) + val manager = getOrCreateWalletManager( + userWallet = userWallet, + blockchain = blockchain, + derivationPath = network.derivationPath.value, + ) + if (manager !is RentProvider) return null + + 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 } + + 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 + } + is Result.Failure -> null + } + } + + override suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? { + val userWallet = getUserWallet(userWalletId) + val blockchain = Blockchain.fromId(network.id.value) + val manager = getOrCreateWalletManager( + userWallet = userWallet, + blockchain = blockchain, + derivationPath = network.derivationPath.value, + ) + + return if (manager is ExistentialDepositProvider) manager.getExistentialDeposit() else null + } + private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set) { if (tokens.isEmpty()) return diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt index dbccd352a6..a958a783aa 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt @@ -3,14 +3,17 @@ package com.tangem.domain.walletmanager import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.Address +import com.tangem.blockchain.blockchains.solana.RentProvider import com.tangem.domain.tokens.models.CryptoCurrency import com.tangem.domain.tokens.models.Network +import com.tangem.domain.tokens.models.warnings.CryptoCurrencyWarning 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.UpdateWalletManagerResult import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId +import java.math.BigDecimal // TODO: Move to its own module /** @@ -79,4 +82,18 @@ interface WalletManagersFacade { * @param network network of currency */ suspend fun getAddress(userWalletId: UserWalletId, network: Network): List
+ + /** + * Returns info about rent if wallet manager implemented [RentProvider], otherwise null + * + * @param userWalletId selected wallet id + * @param network network of currency + */ + 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. + */ + suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/warnings/CryptoCurrencyWarning.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/warnings/CryptoCurrencyWarning.kt new file mode 100644 index 0000000000..538d306a82 --- /dev/null +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/warnings/CryptoCurrencyWarning.kt @@ -0,0 +1,28 @@ +package com.tangem.domain.tokens.models.warnings + +import com.tangem.domain.tokens.models.CryptoCurrency +import java.math.BigDecimal + +sealed class CryptoCurrencyWarning { + + data class ExistentialDeposit( + val currencyName: String, + val edStringValueWithSymbol: String, + ) : CryptoCurrencyWarning() + + data class BalanceNotEnoughForFee( + val currency: CryptoCurrency, + val blockchainFullName: String, + val blockchainSymbol: String, + ) : CryptoCurrencyWarning() + + object SomeNetworksUnreachable : CryptoCurrencyWarning() + + /** + * 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 Rent(val rent: BigDecimal, val exemptionAmount: BigDecimal) : CryptoCurrencyWarning() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt new file mode 100644 index 0000000000..f2b2d1f70f --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -0,0 +1,84 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.models.CryptoCurrency +import com.tangem.domain.tokens.models.Network +import com.tangem.domain.tokens.models.warnings.CryptoCurrencyWarning +import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.* +import java.math.BigDecimal + +class GetCurrencyWarningsUseCase( + private val walletManagersFacade: WalletManagersFacade, + private val currenciesRepository: CurrenciesRepository, + private val quotesRepository: QuotesRepository, + private val networksRepository: NetworksRepository, + private val dispatchers: CoroutineDispatcherProvider, +) { + + suspend operator fun invoke(userWallet: UserWallet, currency: CryptoCurrency): Flow> { + return combine( + getFeeWarningFlow( + userWalletId = userWallet.walletId, + networkId = currency.network.id, + currencyId = currency.id, + ), + flowOf(walletManagersFacade.getRentInfo(userWallet.walletId, currency.network)), + flowOf(walletManagersFacade.getExistentialDeposit(userWallet.walletId, currency.network)), + ) { maybeFeeWarning, maybeRentWarning, maybeEdWarning -> + setOfNotNull( + maybeRentWarning, + maybeEdWarning?.let { + CryptoCurrencyWarning.ExistentialDeposit( + currencyName = currency.name, + edStringValueWithSymbol = "${it.toPlainString()} ${currency.symbol}", + ) + }, + maybeFeeWarning, + ) + }.flowOn(dispatchers.io) + } + + private suspend fun getFeeWarningFlow( + userWalletId: UserWalletId, + networkId: Network.ID, + currencyId: CryptoCurrency.ID, + ): Flow { + val operations = CurrenciesStatusesOperations( + currenciesRepository = currenciesRepository, + quotesRepository = quotesRepository, + networksRepository = networksRepository, + userWalletId = userWalletId, + ) + + return combine( + operations.getCurrencyStatusFlow(currencyId).map { it.getOrNull() }, + operations.getNetworkCoinFlow(networkId).map { it.getOrNull() }, + ) { tokenStatus, coinStatus -> + when { + tokenStatus != null && coinStatus != null -> { + if (!tokenStatus.value.amount.isZero() && coinStatus.value.amount.isZero()) { + CryptoCurrencyWarning.BalanceNotEnoughForFee( + currency = tokenStatus.currency, + blockchainFullName = coinStatus.currency.name, + blockchainSymbol = coinStatus.currency.symbol, + ) + } else { + null + } + } + else -> CryptoCurrencyWarning.SomeNetworksUnreachable + } + } + } + + private fun BigDecimal?.isZero(): Boolean { + return this?.compareTo(BigDecimal.ZERO) == 0 + } +} \ No newline at end of file