Updated on 2026-08-14

This commit is contained in:
Tangem 2023-02-07 14:59:17 +03:00
parent 91c52c039c
commit ae758bc308
8 changed files with 87 additions and 51 deletions

View file

@ -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<String, ProxyAmount> {
override suspend fun getCurrentWalletTokensBalance(
networkId: String,
extraTokens: List<Currency>,
): Map<String, ProxyAmount> {
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<NonNativeToken>()
.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,

View file

@ -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

View file

@ -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<Currency>,
balances: Map<String, ProxyAmount>,
balances: Map<String, SwapAmount>,
rates: Map<String, Double>,
appCurrency: ProxyFiatCurrency,
): List<TokenWithBalance> {
@ -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<Currency>) {
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")

View file

@ -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<Currency>)
fun cacheInWalletTokens(tokens: List<Currency>)
fun cacheLoadedTokens(tokens: List<Currency>)
fun cacheBalances(balances: Map<String, SwapAmount>)
fun cacheLastFeeForNetwork(fee: BigDecimal, networkId: String)
fun getAvailableTokens(networkId: String): List<Currency>
fun getInWalletTokens(): List<Currency>
fun getLoadedTokens(): List<Currency>
fun getBalanceForToken(symbol: String): SwapAmount?
fun getLastFeeForNetwork(networkId: String): BigDecimal?
}

View file

@ -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<String, List<Currency>> = mutableMapOf()
private val feesForNetworks: MutableMap<String, BigDecimal> = mutableMapOf()
private val tokensBalances: MutableMap<String, SwapAmount> = mutableMapOf()
private val lastInWalletTokens = mutableListOf<Currency>()
private val lastLoadedTokens = mutableListOf<Currency>()
@ -32,6 +34,14 @@ class SwapDataCacheImpl : SwapDataCache {
return lastLoadedTokens
}
override fun getBalanceForToken(symbol: String): SwapAmount? {
return tokensBalances[symbol]
}
override fun cacheBalances(balances: Map<String, SwapAmount>) {
tokensBalances.putAll(balances)
}
override fun cacheAvailableToSwapTokens(networkId: String, tokens: List<Currency>) {
availableTokensForNetwork[networkId] = tokens
}

View file

@ -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
*

View file

@ -110,7 +110,10 @@ internal class StateBuilder(val actions: UiActions) {
fromToken: Currency,
): SwapStateHolder {
val warnings = mutableListOf<SwapWarning>()
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) {

View file

@ -12,8 +12,10 @@ interface UserWalletManager {
/**
* Returns all user tokens (merged from local and backend)
*/
@Throws(IllegalStateException::class)
suspend fun getUserTokens(networkId: String): List<Currency>
@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 <Symbol, [ProxyAmount]>
*/
@Throws(IllegalStateException::class)
fun getCurrentWalletTokensBalance(networkId: String): Map<String, ProxyAmount>
suspend fun getCurrentWalletTokensBalance(networkId: String, extraTokens: List<Currency>): Map<String, ProxyAmount>
@Throws(IllegalStateException::class)
fun getNativeTokenBalance(networkId: String): ProxyAmount?
/**
* @param networkId
* @return currency name
*/
@Throws(IllegalStateException::class)
fun getNetworkCurrency(networkId: String): String
/**