diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index e94a8d074c..5b2488a1f6 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationParams import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.domain.common.CardDTO @@ -124,10 +125,29 @@ class UserWalletManagerImpl( return walletManager?.wallet?.recentTransactions?.lastOrNull()?.hash?.let { HEX_PREFIX + it } } - override fun getCurrentWalletTokensBalance(networkId: String): Map { + override suspend fun getCurrentWalletTokensBalance( + networkId: String, + extraTokens: List, + ): Map { val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } val walletManager = getActualWalletManager(blockchain) - return walletManager.wallet.amounts.map { entry -> + + // workaround for get balance for tokens that doesn't exist in wallet + val extraTokensToLoadBalance = extraTokens + .filterIsInstance() + .map { + Token( + symbol = it.symbol, + contractAddress = it.contractAddress, + decimals = it.decimalCount, + ) + } + .filter { + !walletManager.cardTokens.contains(it) + } + walletManager.addTokens(extraTokensToLoadBalance) + walletManager.update() + val balances = walletManager.wallet.amounts.map { entry -> val amount = entry.value amount.currencySymbol to ProxyAmount( amount.currencySymbol, @@ -135,6 +155,8 @@ class UserWalletManagerImpl( amount.decimals, ) }.toMap() + extraTokensToLoadBalance.forEach { walletManager.removeToken(it) } + return balances } override fun getNativeTokenBalance(networkId: String): ProxyAmount? { @@ -185,7 +207,7 @@ class UserWalletManagerImpl( private fun addNonNativeTokenToWalletAction(token: NonNativeToken, card: CardDTO, blockchain: Blockchain): Action { return WalletAction.MultiWallet.AddToken( - token = com.tangem.blockchain.common.Token( + token = Token( id = token.id, name = token.name, symbol = token.symbol, diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/InputFormatting.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/InputFormatting.kt index edd2270947..e588b3585c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/InputFormatting.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/InputFormatting.kt @@ -10,7 +10,7 @@ package com.tangem.core.ui.utils */ fun getValidatedNumberWithFixedDecimals(text: String, decimals: Int): String { val comma = ',' - val dot = ',' + val dot = '.' val filteredChars = text.replace(comma, dot).filterIndexed { index, c -> val isOneOrZeroPoint = c == dot && index != 0 && text.count { it == dot } <= 1 val isIndexPointIndex = c == dot && index != 0 && text.indexOf(dot) == index diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 6758297e91..ddba5d7da9 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -22,7 +22,6 @@ import com.tangem.feature.swap.domain.models.ui.TokensDataState import com.tangem.feature.swap.domain.models.ui.TxState import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager -import com.tangem.lib.crypto.models.ProxyAmount import com.tangem.lib.crypto.models.ProxyFiatCurrency import com.tangem.lib.crypto.models.transactions.SendTxResult import com.tangem.utils.toFiatString @@ -64,11 +63,13 @@ internal class SwapInteractorImpl @Inject constructor( .filter { !loadedOnWalletsMap.contains(it.symbol) } - cache.cacheLoadedTokens(loadedTokens) - cache.cacheInWalletTokens(tokensInWallet) - val tokensBalance = userWalletManager.getCurrentWalletTokensBalance(networkId) + val tokensBalance = userWalletManager.getCurrentWalletTokensBalance(networkId, emptyList()) + .mapValues { SwapAmount(it.value.value, it.value.decimals) } val appCurrency = userWalletManager.getUserAppCurrency() val rates = repository.getRates(appCurrency.code, tokensInWallet.map { it.id }) + cache.cacheLoadedTokens(loadedTokens) + cache.cacheBalances(tokensBalance) + cache.cacheInWalletTokens(tokensInWallet) return TokensDataState( preselectTokens = PreselectTokens( fromToken = initialCurrency, @@ -93,7 +94,8 @@ internal class SwapInteractorImpl @Inject constructor( it.name.lowercase().contains(searchQueryLowerCase) || it.symbol.lowercase().contains(searchQueryLowerCase) } - val tokensBalance = userWalletManager.getCurrentWalletTokensBalance(networkId) + val tokensBalance = userWalletManager.getCurrentWalletTokensBalance(networkId, emptyList()) + .mapValues { SwapAmount(it.value.value, it.value.decimals) } val appCurrency = userWalletManager.getUserAppCurrency() val rates = repository.getRates(appCurrency.code, tokensInWallet.map { it.id }) return FoundTokensState( @@ -142,16 +144,17 @@ internal class SwapInteractorImpl @Inject constructor( toToken: Currency, amountToSwap: String, ): SwapState { + syncWalletBalanceForTokens(networkId, listOf(fromToken, toToken)) val amountDecimal = amountToSwap.toBigDecimalOrNull() if (amountDecimal == null || amountDecimal.compareTo(BigDecimal.ZERO) == 0) { - return createEmptyAmountState(networkId, fromToken, toToken) + return createEmptyAmountState(fromToken, toToken) } val amount = SwapAmount(amountDecimal, getTokenDecimals(fromToken)) val fromTokenAddress = getTokenAddress(fromToken) val toTokenAddress = getTokenAddress(toToken) val isAllowedToSpend = checkAllowance(networkId, fromTokenAddress) val fee = getAndUpdateFee(networkId, fromToken) - val isBalanceEnough = isBalanceEnough(fromToken, networkId, amount, fee) + val isBalanceEnough = isBalanceEnough(fromToken, amount, fee) val isFeeEnough = checkFeeIsEnough(fee, amount, networkId, fromToken) if (isAllowedToSpend && allowPermissionsHandler.isAddressAllowanceInProgress(fromTokenAddress)) { allowPermissionsHandler.removeAddressFromProgress(fromTokenAddress) @@ -227,9 +230,7 @@ internal class SwapInteractorImpl @Inject constructor( } override fun getTokenBalance(token: Currency): SwapAmount { - return userWalletManager.getCurrentWalletTokensBalance(token.networkId)[token.symbol]?.let { - SwapAmount(it.value, it.decimals) - } ?: SwapAmount(BigDecimal.ZERO, getTokenDecimals(token)) + return cache.getBalanceForToken(token.symbol) ?: SwapAmount(BigDecimal.ZERO, getTokenDecimals(token)) } override fun isAvailableToSwap(networkId: String): Boolean { @@ -266,7 +267,7 @@ internal class SwapInteractorImpl @Inject constructor( private fun getTokensWithBalance( tokens: List, - balances: Map, + balances: Map, rates: Map, appCurrency: ProxyFiatCurrency, ): List { @@ -275,7 +276,7 @@ internal class SwapInteractorImpl @Inject constructor( TokenWithBalance( token = it, tokenBalanceData = TokenBalanceData( - amount = balance?.let { amount -> amountFormatter.formatProxyAmountToUI(amount, "") }, + amount = balance?.let { amount -> amountFormatter.formatSwapAmountToUI(amount, "") }, amountEquivalent = balance?.value?.toFiatString( rates[it.id]?.toBigDecimal() ?: BigDecimal.ZERO, appCurrency.symbol, @@ -314,18 +315,12 @@ internal class SwapInteractorImpl @Inject constructor( } private fun createEmptyAmountState( - networkId: String, fromToken: Currency, toToken: Currency, ): SwapState { - val tokensBalance = userWalletManager.getCurrentWalletTokensBalance(networkId) val appCurrency = userWalletManager.getUserAppCurrency() - val fromTokenBalance = tokensBalance[fromToken.symbol]?.let { - SwapAmount(it.value, it.decimals) - } - val toTokenBalance = tokensBalance[toToken.symbol]?.let { - SwapAmount(it.value, it.decimals) - } + val fromTokenBalance = cache.getBalanceForToken(fromToken.symbol) + val toTokenBalance = cache.getBalanceForToken(toToken.symbol) return SwapState.EmptyAmountState( fromTokenWalletBalance = fromTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") }.orEmpty(), toTokenWalletBalance = toTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") }.orEmpty(), @@ -471,18 +466,14 @@ internal class SwapInteractorImpl @Inject constructor( val appCurrency = userWalletManager.getUserAppCurrency() val nativeToken = userWalletManager.getNativeTokenForNetwork(networkId) val rates = repository.getRates(appCurrency.code, listOf(fromToken.id, toToken.id, nativeToken.id)) - val tokensBalance = userWalletManager.getCurrentWalletTokensBalance(networkId) - val fromTokenBalance = tokensBalance[fromToken.symbol]?.let { - amountFormatter.formatProxyAmountToUI(it, "") - } - val toTokenBalance = tokensBalance[toToken.symbol]?.let { - amountFormatter.formatProxyAmountToUI(it, "") - } + val fromTokenBalance = cache.getBalanceForToken(fromToken.symbol) + val toTokenBalance = cache.getBalanceForToken(toToken.symbol) return SwapState.QuotesLoadedState( fromTokenInfo = TokenSwapInfo( tokenAmount = fromTokenAmount, coinId = fromToken.id, - tokenWalletBalance = fromTokenBalance ?: ZERO_BALANCE, + tokenWalletBalance = fromTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") } + ?: ZERO_BALANCE, tokenFiatBalance = fromTokenAmount.value.toFiatString( rates[fromToken.id]?.toBigDecimal() ?: BigDecimal.ZERO, appCurrency.symbol, @@ -491,7 +482,8 @@ internal class SwapInteractorImpl @Inject constructor( toTokenInfo = TokenSwapInfo( tokenAmount = toTokenAmount, coinId = toToken.id, - tokenWalletBalance = toTokenBalance ?: ZERO_BALANCE, + tokenWalletBalance = toTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") } + ?: ZERO_BALANCE, tokenFiatBalance = toTokenAmount.value.toFiatString( rates[toToken.id]?.toBigDecimal() ?: BigDecimal.ZERO, appCurrency.symbol, @@ -513,6 +505,12 @@ internal class SwapInteractorImpl @Inject constructor( transactionData: ApproveModel, formattedFee: String, ): SwapState.QuotesLoadedState { + val isTokenZeroBalance = getTokenBalance(fromToken).value.compareTo(BigDecimal.ZERO) == 0 + if (isTokenZeroBalance) { + return quotesLoadedState.copy( + permissionState = PermissionDataState.Empty, + ) + } if (allowPermissionsHandler.isAddressAllowanceInProgress(getTokenAddress(fromToken))) { return quotesLoadedState.copy( permissionState = PermissionDataState.PermissionLoading, @@ -533,9 +531,17 @@ internal class SwapInteractorImpl @Inject constructor( ) } - private fun isBalanceEnough(fromToken: Currency, networkId: String, amount: SwapAmount, fee: BigDecimal?): Boolean { - val tokenBalance = - userWalletManager.getCurrentWalletTokensBalance(networkId)[fromToken.symbol]?.value ?: BigDecimal.ZERO + private suspend fun syncWalletBalanceForTokens(networkId: String, tokens: List) { + val tokensBalance = + userWalletManager.getCurrentWalletTokensBalance( + networkId = networkId, + extraTokens = tokens.map { cryptoCurrencyConverter.convert(it) }, + ) + cache.cacheBalances(tokensBalance.mapValues { SwapAmount(it.value.value, it.value.decimals) }) + } + + private fun isBalanceEnough(fromToken: Currency, amount: SwapAmount, fee: BigDecimal?): Boolean { + val tokenBalance = getTokenBalance(fromToken).value return if (fromToken is Currency.NonNativeToken) { tokenBalance >= amount.value } else { @@ -581,7 +587,6 @@ internal class SwapInteractorImpl @Inject constructor( } ?: false } } - return false } @Suppress("MagicNumber") diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt index 20cba789db..4736dbf25b 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCache.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.domain.cache +import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.Currency import java.math.BigDecimal @@ -8,9 +9,11 @@ interface SwapDataCache { fun cacheAvailableToSwapTokens(networkId: String, tokens: List) fun cacheInWalletTokens(tokens: List) fun cacheLoadedTokens(tokens: List) + fun cacheBalances(balances: Map) fun cacheLastFeeForNetwork(fee: BigDecimal, networkId: String) fun getAvailableTokens(networkId: String): List fun getInWalletTokens(): List fun getLoadedTokens(): List + fun getBalanceForToken(symbol: String): SwapAmount? fun getLastFeeForNetwork(networkId: String): BigDecimal? } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt index bf4ec2aef7..55792cbf26 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/cache/SwapDataCacheImpl.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.domain.cache +import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.Currency import java.math.BigDecimal @@ -7,6 +8,7 @@ class SwapDataCacheImpl : SwapDataCache { private val availableTokensForNetwork: MutableMap> = mutableMapOf() private val feesForNetworks: MutableMap = mutableMapOf() + private val tokensBalances: MutableMap = mutableMapOf() private val lastInWalletTokens = mutableListOf() private val lastLoadedTokens = mutableListOf() @@ -32,6 +34,14 @@ class SwapDataCacheImpl : SwapDataCache { return lastLoadedTokens } + override fun getBalanceForToken(symbol: String): SwapAmount? { + return tokensBalances[symbol] + } + + override fun cacheBalances(balances: Map) { + tokensBalances.putAll(balances) + } + override fun cacheAvailableToSwapTokens(networkId: String, tokens: List) { availableTokensForNetwork[networkId] = tokens } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/AmountFormatter.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/AmountFormatter.kt index 26eff81e56..5defcf6bf3 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/AmountFormatter.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/AmountFormatter.kt @@ -1,7 +1,6 @@ package com.tangem.feature.swap.domain.models.ui import com.tangem.feature.swap.domain.models.SwapAmount -import com.tangem.lib.crypto.models.ProxyAmount import com.tangem.utils.toFormattedCurrencyString import java.math.BigDecimal @@ -18,17 +17,6 @@ class AmountFormatter { return swapAmount.value.toFormattedCurrencyString(swapAmount.decimals, currency) } - /** - * Use to convert crypto amount [ProxyAmount] to UI representation - * - * @param proxyAmount [ProxyAmount] - * @param currency currency symbol - * @return formatted [String] - */ - fun formatProxyAmountToUI(proxyAmount: ProxyAmount, currency: String): String { - return proxyAmount.value.toFormattedCurrencyString(proxyAmount.decimals, currency) - } - /** * Use to convert ONLY crypto amount [BigDecimal] to UI representation * diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index b6e5eaee19..77b3f9517f 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -110,7 +110,10 @@ internal class StateBuilder(val actions: UiActions) { fromToken: Currency, ): SwapStateHolder { val warnings = mutableListOf() - if (!quoteModel.preparedSwapConfigState.isAllowedToSpend && quoteModel.preparedSwapConfigState.isFeeEnough) { + if (!quoteModel.preparedSwapConfigState.isAllowedToSpend && + quoteModel.preparedSwapConfigState.isFeeEnough && + quoteModel.permissionState is PermissionDataState.PermissionReadyForRequest + ) { warnings.add(SwapWarning.PermissionNeeded(fromToken.symbol)) } if (!quoteModel.preparedSwapConfigState.isBalanceEnough) { diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt index 86a6075341..0762d247e5 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt @@ -12,8 +12,10 @@ interface UserWalletManager { /** * Returns all user tokens (merged from local and backend) */ + @Throws(IllegalStateException::class) suspend fun getUserTokens(networkId: String): List + @Throws(IllegalStateException::class) fun getNativeTokenForNetwork(networkId: String): Currency /** @@ -26,6 +28,7 @@ interface UserWalletManager { * * @param currency to receive referral payments */ + @Throws(IllegalStateException::class) suspend fun isTokenAdded(currency: Currency): Boolean /** @@ -51,14 +54,16 @@ interface UserWalletManager { * @return map of */ @Throws(IllegalStateException::class) - fun getCurrentWalletTokensBalance(networkId: String): Map + suspend fun getCurrentWalletTokensBalance(networkId: String, extraTokens: List): Map + @Throws(IllegalStateException::class) fun getNativeTokenBalance(networkId: String): ProxyAmount? /** * @param networkId * @return currency name */ + @Throws(IllegalStateException::class) fun getNetworkCurrency(networkId: String): String /**