Updated on 2026-08-14

This commit is contained in:
Tangem 2024-03-02 15:36:13 +05:00
parent 613bb9226f
commit 3f1102f232
20 changed files with 247 additions and 172 deletions

View file

@ -21,7 +21,6 @@ import com.tangem.blockchain.externallinkprovider.TxExploreState
import com.tangem.blockchain.network.ResultChecker import com.tangem.blockchain.network.ResultChecker
import com.tangem.common.core.TangemSdkError import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.hexToBytes
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
@ -39,7 +38,6 @@ import java.math.RoundingMode
@Suppress("LargeClass") @Suppress("LargeClass")
class TransactionManagerImpl( class TransactionManagerImpl(
private val appStateHolder: AppStateHolder, private val appStateHolder: AppStateHolder,
private val analytics: AnalyticsEventHandler,
private val cardSdkConfigRepository: CardSdkConfigRepository, private val cardSdkConfigRepository: CardSdkConfigRepository,
private val walletManagersFacade: WalletManagersFacade, private val walletManagersFacade: WalletManagersFacade,
) : TransactionManager { ) : TransactionManager {
@ -144,10 +142,6 @@ class TransactionManagerImpl(
} }
} }
override fun getNativeTokenDecimals(networkId: String): Int {
return Blockchain.fromNetworkId(networkId)?.decimals() ?: error("blockchain not found")
}
override suspend fun updateWalletManager(networkId: String, derivationPath: String?) { override suspend fun updateWalletManager(networkId: String, derivationPath: String?) {
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
getActualWalletManager(blockchain, derivationPath).update() getActualWalletManager(blockchain, derivationPath).update()
@ -205,7 +199,6 @@ class TransactionManagerImpl(
return ProxyNetworkInfo( return ProxyNetworkInfo(
name = blockchain.fullName, name = blockchain.fullName,
blockchainId = blockchain.id, blockchainId = blockchain.id,
blockchainCurrency = blockchain.currency,
) )
} }

View file

@ -164,11 +164,6 @@ class UserWalletManagerImpl(
} }
} }
override fun getNetworkCurrency(networkId: String): String {
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
return blockchain.currency
}
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
private suspend fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager { private suspend fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager {
val selectedUserWallet = requireNotNull( val selectedUserWallet = requireNotNull(

View file

@ -1,6 +1,5 @@
package com.tangem.tap.proxy.di package com.tangem.tap.proxy.di
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
@ -43,13 +42,11 @@ internal object ProxyModule {
@Singleton @Singleton
fun provideTransactionManager( fun provideTransactionManager(
appStateHolder: AppStateHolder, appStateHolder: AppStateHolder,
analytics: AnalyticsEventHandler,
cardSdkConfigRepository: CardSdkConfigRepository, cardSdkConfigRepository: CardSdkConfigRepository,
walletManagersFacade: WalletManagersFacade, walletManagersFacade: WalletManagersFacade,
): TransactionManager { ): TransactionManager {
return TransactionManagerImpl( return TransactionManagerImpl(
appStateHolder = appStateHolder, appStateHolder = appStateHolder,
analytics = analytics,
cardSdkConfigRepository = cardSdkConfigRepository, cardSdkConfigRepository = cardSdkConfigRepository,
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
) )

View file

@ -3,8 +3,8 @@ package com.tangem.domain.tokens.model
import java.math.BigDecimal import java.math.BigDecimal
sealed class FeePaidCurrency { sealed class FeePaidCurrency {
object Coin : FeePaidCurrency() data object Coin : FeePaidCurrency()
object SameCurrency : FeePaidCurrency() data object SameCurrency : FeePaidCurrency()
data class Token( data class Token(
val tokenId: CryptoCurrency.ID, val tokenId: CryptoCurrency.ID,
val name: String, val name: String,

View file

@ -35,14 +35,14 @@ class GetFeePaidCryptoCurrencyStatusSyncUseCase(
return either { return either {
when (feePaidCurrency) { when (feePaidCurrency) {
FeePaidCurrency.Coin -> getCryptoCurrency(operations, cryptoCurrency.id) FeePaidCurrency.Coin -> getCryptoCurrencyStatus(operations, cryptoCurrency.id)
FeePaidCurrency.SameCurrency -> cryptoCurrencyStatus FeePaidCurrency.SameCurrency -> cryptoCurrencyStatus
is FeePaidCurrency.Token -> getCryptoCurrency(operations, feePaidCurrency.tokenId) is FeePaidCurrency.Token -> getCryptoCurrencyStatus(operations, feePaidCurrency.tokenId)
} }
} }
} }
private suspend fun getCryptoCurrency( private suspend fun getCryptoCurrencyStatus(
operations: CurrenciesStatusesOperations, operations: CurrenciesStatusesOperations,
cryptoCurrencyId: CryptoCurrency.ID, cryptoCurrencyId: CryptoCurrency.ID,
): CryptoCurrencyStatus? { ): CryptoCurrencyStatus? {

View file

@ -3,5 +3,4 @@ package com.tangem.feature.swap.domain.models.domain
data class NetworkInfo( data class NetworkInfo(
val name: String, val name: String,
val blockchainId: String, val blockchainId: String,
val blockchainCurrency: String,
) )

View file

@ -7,19 +7,18 @@ import com.tangem.feature.swap.domain.models.SwapAmount
* *
* @property isAllowedToSpend shows is token allowed to spend * @property isAllowedToSpend shows is token allowed to spend
* @property isBalanceEnough shows is balance of token enough * @property isBalanceEnough shows is balance of token enough
* @property isFeeEnough shows is amount of main coin enough for fee
*/ */
// todo Refactor this state // todo Refactor this state
data class PreparedSwapConfigState( data class PreparedSwapConfigState(
val isAllowedToSpend: Boolean, val isAllowedToSpend: Boolean,
val isBalanceEnough: Boolean, val isBalanceEnough: Boolean,
val isFeeEnough: Boolean, val feeState: SwapFeeState,
val hasOutgoingTransaction: Boolean, val hasOutgoingTransaction: Boolean,
val includeFeeInAmount: IncludeFeeInAmount, val includeFeeInAmount: IncludeFeeInAmount,
) )
sealed class IncludeFeeInAmount { sealed class IncludeFeeInAmount {
data class Included(val amountSubtractFee: SwapAmount) : IncludeFeeInAmount() data class Included(val amountSubtractFee: SwapAmount) : IncludeFeeInAmount()
object Excluded : IncludeFeeInAmount() data object Excluded : IncludeFeeInAmount()
object BalanceNotEnough : IncludeFeeInAmount() data object BalanceNotEnough : IncludeFeeInAmount()
} }

View file

@ -0,0 +1,12 @@
package com.tangem.feature.swap.domain.models.domain
import com.tangem.domain.tokens.model.CryptoCurrency
sealed class SwapFeeState {
data object Enough : SwapFeeState()
data class NotEnough(
val feeCurrency: CryptoCurrency? = null,
val currencyName: String? = null,
val currencySymbol: String? = null,
) : SwapFeeState()
}

View file

@ -3,10 +3,7 @@ package com.tangem.feature.swap.domain.models.ui
import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.swap.domain.models.DataError import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.IncludeFeeInAmount import com.tangem.feature.swap.domain.models.domain.*
import com.tangem.feature.swap.domain.models.domain.PreparedSwapConfigState
import com.tangem.feature.swap.domain.models.domain.SwapDataModel
import com.tangem.feature.swap.domain.models.domain.Warning
import java.math.BigDecimal import java.math.BigDecimal
sealed interface SwapState { sealed interface SwapState {
@ -15,11 +12,10 @@ sealed interface SwapState {
val fromTokenInfo: TokenSwapInfo, val fromTokenInfo: TokenSwapInfo,
val toTokenInfo: TokenSwapInfo, val toTokenInfo: TokenSwapInfo,
val priceImpact: PriceImpact, val priceImpact: PriceImpact,
val networkCurrency: String,
val preparedSwapConfigState: PreparedSwapConfigState = PreparedSwapConfigState( val preparedSwapConfigState: PreparedSwapConfigState = PreparedSwapConfigState(
isAllowedToSpend = false, isAllowedToSpend = false,
isBalanceEnough = false, isBalanceEnough = false,
isFeeEnough = false, feeState = SwapFeeState.NotEnough(),
hasOutgoingTransaction = false, hasOutgoingTransaction = false,
includeFeeInAmount = IncludeFeeInAmount.Excluded, includeFeeInAmount = IncludeFeeInAmount.Excluded,
), ),
@ -98,7 +94,7 @@ sealed class TxFeeState {
val fee: TxFee, val fee: TxFee,
) : TxFeeState() ) : TxFeeState()
object Empty : TxFeeState() data object Empty : TxFeeState()
} }
data class TxFee( data class TxFee(

View file

@ -13,7 +13,6 @@ internal class DefaultBlockchainInteractor @Inject constructor(
NetworkInfo( NetworkInfo(
name = it.name, name = it.name,
blockchainId = it.blockchainId, blockchainId = it.blockchainId,
blockchainCurrency = it.blockchainCurrency,
) )
} }
} }

View file

@ -13,7 +13,9 @@ import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.FeePaidCurrency
import com.tangem.domain.tokens.model.Quote import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.tokens.utils.convertToAmount
@ -61,6 +63,7 @@ internal class SwapInteractorImpl @Inject constructor(
private val swapTransactionRepository: SwapTransactionRepository, private val swapTransactionRepository: SwapTransactionRepository,
private val currencyChecksRepository: CurrencyChecksRepository, private val currencyChecksRepository: CurrencyChecksRepository,
private val appCurrencyRepository: AppCurrencyRepository, private val appCurrencyRepository: AppCurrencyRepository,
private val currenciesRepository: CurrenciesRepository,
private val initialToCurrencyResolver: InitialToCurrencyResolver, private val initialToCurrencyResolver: InitialToCurrencyResolver,
) : SwapInteractor { ) : SwapInteractor {
@ -251,7 +254,7 @@ internal class SwapInteractorImpl @Inject constructor(
if (amountDecimal == null || amountDecimal.signum() == 0) { if (amountDecimal == null || amountDecimal.signum() == 0) {
return providers.associateWith { createEmptyAmountState() } return providers.associateWith { createEmptyAmountState() }
} }
val amount = SwapAmount(amountDecimal, getTokenDecimals(fromToken.currency)) val amount = SwapAmount(amountDecimal, fromToken.currency.decimals)
val isBalanceWithoutFeeEnough = isBalanceEnough(fromToken, amount, null) val isBalanceWithoutFeeEnough = isBalanceEnough(fromToken, amount, null)
val networkId = fromToken.currency.network.backendId val networkId = fromToken.currency.network.backendId
when (provider.type) { when (provider.type) {
@ -433,7 +436,7 @@ internal class SwapInteractorImpl @Inject constructor(
return when (swapProvider.type) { return when (swapProvider.type) {
ExchangeProviderType.CEX -> { ExchangeProviderType.CEX -> {
val amountDecimal = toBigDecimalOrNull(amountToSwap) val amountDecimal = toBigDecimalOrNull(amountToSwap)
val amount = SwapAmount(requireNotNull(amountDecimal), getTokenDecimals(currencyToSend.currency)) val amount = SwapAmount(requireNotNull(amountDecimal), currencyToSend.currency.decimals)
val amountToSwapWithFee = if (includeFeeInAmount is IncludeFeeInAmount.Included) { val amountToSwapWithFee = if (includeFeeInAmount is IncludeFeeInAmount.Included) {
includeFeeInAmount.amountSubtractFee includeFeeInAmount.amountSubtractFee
} else { } else {
@ -471,17 +474,28 @@ internal class SwapInteractorImpl @Inject constructor(
if (amountDecimal == null || amountDecimal.signum() == 0) { if (amountDecimal == null || amountDecimal.signum() == 0) {
return state return state
} }
val amount = SwapAmount(amountDecimal, getTokenDecimals(fromToken.currency)) val amount = SwapAmount(amountDecimal, fromToken.currency.decimals)
val includeFeeInAmount = getIncludeFeeInAmount( val includeFeeInAmount = getIncludeFeeInAmount(
networkId = fromToken.currency.network.backendId, networkId = fromToken.currency.network.backendId,
txFee = state.txFee, txFee = state.txFee,
amount = amount, amount = amount,
fromToken = fromToken.currency, fromToken = fromToken.currency,
) )
val fee = when (val txFee = state.txFee) {
TxFeeState.Empty -> BigDecimal.ZERO
is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue
is TxFeeState.SingleFeeState -> txFee.fee.feeValue
}
val feeState = getFeeState(
fee = fee,
spendAmount = amount,
networkId = fromToken.currency.network.backendId,
fromTokenStatus = fromToken,
)
return state.copy( return state.copy(
permissionState = PermissionDataState.Empty, permissionState = PermissionDataState.Empty,
preparedSwapConfigState = state.preparedSwapConfigState.copy( preparedSwapConfigState = state.preparedSwapConfigState.copy(
isFeeEnough = includeFeeInAmount !is IncludeFeeInAmount.BalanceNotEnough, feeState = feeState,
isBalanceEnough = includeFeeInAmount !is IncludeFeeInAmount.BalanceNotEnough, isBalanceEnough = includeFeeInAmount !is IncludeFeeInAmount.BalanceNotEnough,
includeFeeInAmount = includeFeeInAmount, includeFeeInAmount = includeFeeInAmount,
), ),
@ -497,7 +511,7 @@ internal class SwapInteractorImpl @Inject constructor(
fee: TxFee, fee: TxFee,
): TxState { ): TxState {
val amountDecimal = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format" } val amountDecimal = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format" }
val amount = SwapAmount(amountDecimal, getTokenDecimals(currencyToSend)) val amount = SwapAmount(amountDecimal, currencyToSend.decimals)
val derivationPath = currencyToSend.network.derivationPath.value val derivationPath = currencyToSend.network.derivationPath.value
val result = transactionManager.sendTransaction( val result = transactionManager.sendTransaction(
txData = SwapTxData( txData = SwapTxData(
@ -713,7 +727,7 @@ internal class SwapInteractorImpl @Inject constructor(
@Deprecated("used in old swap mechanism") @Deprecated("used in old swap mechanism")
override fun getTokenBalance(token: CryptoCurrencyStatus): SwapAmount { override fun getTokenBalance(token: CryptoCurrencyStatus): SwapAmount {
return SwapAmount(token.value.amount ?: BigDecimal.ZERO, getTokenDecimals(token.currency)) return SwapAmount(token.value.amount ?: BigDecimal.ZERO, token.currency.decimals)
} }
@Deprecated("used in old swap mechanism") @Deprecated("used in old swap mechanism")
@ -734,14 +748,6 @@ internal class SwapInteractorImpl @Inject constructor(
return repository.getNativeTokenForNetwork(networkId) return repository.getNativeTokenForNetwork(networkId)
} }
private fun getTokenDecimals(token: CryptoCurrency): Int {
return if (token is CryptoCurrency.Token) {
token.decimals
} else {
transactionManager.getNativeTokenDecimals(token.network.backendId)
}
}
private suspend fun isAllowedToSpend( private suspend fun isAllowedToSpend(
networkId: String, networkId: String,
fromToken: CryptoCurrency, fromToken: CryptoCurrency,
@ -755,7 +761,7 @@ internal class SwapInteractorImpl @Inject constructor(
userWalletId = userWallet.walletId, userWalletId = userWallet.walletId,
networkId = networkId, networkId = networkId,
derivationPath = fromToken.network.derivationPath.value, derivationPath = fromToken.network.derivationPath.value,
tokenDecimalCount = getTokenDecimals(fromToken), tokenDecimalCount = fromToken.decimals,
tokenAddress = getTokenAddress(fromToken), tokenAddress = getTokenAddress(fromToken),
spenderAddress = spenderAddress, spenderAddress = spenderAddress,
) )
@ -797,7 +803,7 @@ internal class SwapInteractorImpl @Inject constructor(
val toToken = toTokenStatus.currency val toToken = toTokenStatus.currency
return coroutineScope { return coroutineScope {
val txFee = if (provider.type == ExchangeProviderType.CEX) { val txFee = if (provider.type == ExchangeProviderType.CEX) {
getFeeForCex(amount, fromTokenStatus, networkId) getFeeForCex(amount, fromTokenStatus)
} else { } else {
TxFeeState.Empty TxFeeState.Empty
} }
@ -885,10 +891,21 @@ internal class SwapInteractorImpl @Inject constructor(
) )
} }
ExchangeProviderType.CEX -> { ExchangeProviderType.CEX -> {
val fee = when (txFee) {
TxFeeState.Empty -> BigDecimal.ZERO
is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue
is TxFeeState.SingleFeeState -> txFee.fee.feeValue
}
val feeState = getFeeState(
fee = fee,
spendAmount = amount,
networkId = networkId,
fromTokenStatus = fromToken,
)
swapState.copy( swapState.copy(
permissionState = PermissionDataState.Empty, permissionState = PermissionDataState.Empty,
preparedSwapConfigState = PreparedSwapConfigState( preparedSwapConfigState = PreparedSwapConfigState(
isFeeEnough = includeFeeInAmount !is IncludeFeeInAmount.BalanceNotEnough, feeState = feeState,
isAllowedToSpend = isAllowedToSpend, isAllowedToSpend = isAllowedToSpend,
isBalanceEnough = isBalanceWithoutFeeEnough, isBalanceEnough = isBalanceWithoutFeeEnough,
hasOutgoingTransaction = hasOutgoingTransaction(fromToken), hasOutgoingTransaction = hasOutgoingTransaction(fromToken),
@ -911,26 +928,47 @@ internal class SwapInteractorImpl @Inject constructor(
) )
} }
@Suppress("CyclomaticComplexMethod")
private suspend fun getIncludeFeeInAmount( private suspend fun getIncludeFeeInAmount(
networkId: String, networkId: String,
txFee: TxFeeState, txFee: TxFeeState,
amount: SwapAmount, amount: SwapAmount,
fromToken: CryptoCurrency, fromToken: CryptoCurrency,
): IncludeFeeInAmount {
val feeValue = when (txFee) {
TxFeeState.Empty -> BigDecimal.ZERO
is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue
is TxFeeState.SingleFeeState -> txFee.fee.feeValue
}
val feePaidCurrency = getFeePaidCurrency(
userWalletId = requireNotNull(getSelectedWallet()).walletId,
currency = fromToken,
)
return when (feePaidCurrency) {
is FeePaidCurrency.Token -> {
if (feePaidCurrency.balance > feeValue) {
IncludeFeeInAmount.Excluded
} else {
IncludeFeeInAmount.BalanceNotEnough
}
}
else -> getIncludeFeeAmountForCoinFee(networkId, amount, feeValue, fromToken)
}
}
private suspend fun getIncludeFeeAmountForCoinFee(
networkId: String,
amount: SwapAmount,
feeValue: BigDecimal,
fromToken: CryptoCurrency,
): IncludeFeeInAmount { ): IncludeFeeInAmount {
val tokenForFeeBalance = val tokenForFeeBalance =
userWalletManager.getNativeTokenBalance( userWalletManager.getNativeTokenBalance(
networkId, networkId,
fromToken.network.derivationPath.value, fromToken.network.derivationPath.value,
) ?: ProxyAmount.empty() ) ?: ProxyAmount.empty()
val feeValue = when (txFee) {
TxFeeState.Empty -> BigDecimal.ZERO
is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue
is TxFeeState.SingleFeeState -> txFee.fee.feeValue
}
val amountWithFee = amount.value + feeValue val amountWithFee = amount.value + feeValue
return when { return when {
fromToken is CryptoCurrency.Token -> { fromToken is CryptoCurrency.Token -> {
if (feeValue > tokenForFeeBalance.value || tokenForFeeBalance.value.signum() == 0) { if (feeValue > tokenForFeeBalance.value || tokenForFeeBalance.value.signum() == 0) {
@ -950,7 +988,7 @@ internal class SwapInteractorImpl @Inject constructor(
IncludeFeeInAmount.Included( IncludeFeeInAmount.Included(
SwapAmount( SwapAmount(
tokenForFeeBalance.value - feeValue, tokenForFeeBalance.value - feeValue,
transactionManager.getNativeTokenDecimals(networkId), getNativeToken(fromToken.network.backendId).decimals,
), ),
) )
} else { } else {
@ -960,11 +998,18 @@ internal class SwapInteractorImpl @Inject constructor(
} }
} }
private suspend fun getFormattedFiatFees(networkId: String, vararg fees: BigDecimal): List<String> { private suspend fun getFormattedFiatFees(fromToken: CryptoCurrency, vararg fees: BigDecimal): List<String> {
val appCurrency = getSelectedAppCurrencyUseCase.unwrap() val appCurrency = getSelectedAppCurrencyUseCase.unwrap()
val nativeToken = repository.getNativeTokenForNetwork(networkId) val feePaidCurrency = getFeePaidCurrency(
val rates = getQuotes(nativeToken.id) userWalletId = requireNotNull(getSelectedWallet()).walletId,
return rates[nativeToken.id]?.fiatRate?.let { rate -> currency = fromToken,
)
val feeCurrencyId: CryptoCurrency.ID = when (feePaidCurrency) {
is FeePaidCurrency.Token -> feePaidCurrency.tokenId
else -> getNativeToken(networkId = fromToken.network.backendId).id
}
val rates = getQuotes(feeCurrencyId)
return rates[feeCurrencyId]?.fiatRate?.let { rate ->
fees.map { fee -> fees.map { fee ->
fee.toFiatString(rate, appCurrency.symbol, true) fee.toFiatString(rate, appCurrency.symbol, true)
} }
@ -1006,16 +1051,16 @@ internal class SwapInteractorImpl @Inject constructor(
derivationPath = fromToken.currency.network.derivationPath.value, derivationPath = fromToken.currency.network.derivationPath.value,
) )
val txFeeState = when (feeData) { val txFeeState = when (feeData) {
is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(networkId) is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken.currency)
is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(networkId) is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken.currency)
} }
val feeByPriority = selectFeeByType(feeType = selectedFee, txFeeState = txFeeState) val feeByPriority = selectFeeByType(feeType = selectedFee, txFeeState = txFeeState)
val isBalanceIncludeFeeEnough = isBalanceEnough(fromToken, amount, feeByPriority) val isBalanceIncludeFeeEnough = isBalanceEnough(fromToken, amount, feeByPriority)
val isFeeEnough = checkFeeIsEnough( val feeState = getFeeState(
fee = feeByPriority, fee = feeByPriority,
spendAmount = amount, spendAmount = amount,
networkId = networkId, networkId = networkId,
fromToken = fromToken.currency, fromTokenStatus = fromToken,
) )
val swapState = updateBalances( val swapState = updateBalances(
networkId = networkId, networkId = networkId,
@ -1032,7 +1077,7 @@ internal class SwapInteractorImpl @Inject constructor(
preparedSwapConfigState = PreparedSwapConfigState( preparedSwapConfigState = PreparedSwapConfigState(
isAllowedToSpend = true, isAllowedToSpend = true,
isBalanceEnough = isBalanceIncludeFeeEnough, isBalanceEnough = isBalanceIncludeFeeEnough,
isFeeEnough = isFeeEnough, feeState = feeState,
hasOutgoingTransaction = hasOutgoingTransaction(fromToken), hasOutgoingTransaction = hasOutgoingTransaction(fromToken),
includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex
), ),
@ -1089,17 +1134,12 @@ internal class SwapInteractorImpl @Inject constructor(
toTokenAmount = toTokenAmount.value, toTokenAmount = toTokenAmount.value,
toRate = rates[toToken.id]?.fiatRate?.toDouble() ?: 0.0, toRate = rates[toToken.id]?.fiatRate?.toDouble() ?: 0.0,
), ),
networkCurrency = userWalletManager.getNetworkCurrency(networkId),
swapDataModel = swapData, swapDataModel = swapData,
txFee = txFeeState, txFee = txFeeState,
) )
} }
private suspend fun getFeeForCex( private suspend fun getFeeForCex(amount: SwapAmount, fromToken: CryptoCurrencyStatus): TxFeeState {
amount: SwapAmount,
fromToken: CryptoCurrencyStatus,
networkId: String,
): TxFeeState {
getSelectedWalletSyncUseCase().getOrNull()?.walletId?.let { userWalletId -> getSelectedWalletSyncUseCase().getOrNull()?.walletId?.let { userWalletId ->
val txFeeResult = estimateFeeUseCase( val txFeeResult = estimateFeeUseCase(
amount = amount.value, amount = amount.value,
@ -1111,7 +1151,7 @@ internal class SwapInteractorImpl @Inject constructor(
TxFeeState.Empty TxFeeState.Empty
}, },
ifRight = { txFee -> ifRight = { txFee ->
txFee.toTxFeeState(networkId) txFee.toTxFeeState(fromToken.currency)
}, },
) ?: TxFeeState.Empty ) ?: TxFeeState.Empty
} }
@ -1170,8 +1210,8 @@ internal class SwapInteractorImpl @Inject constructor(
} }
val feeState = feeData?.let { val feeState = feeData?.let {
when (feeData) { when (feeData) {
is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(networkId) is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken)
is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(networkId) is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken)
} }
} ?: TxFeeState.Empty } ?: TxFeeState.Empty
val fee = when (feeState) { val fee = when (feeState) {
@ -1179,11 +1219,11 @@ internal class SwapInteractorImpl @Inject constructor(
is TxFeeState.MultipleFeeState -> feeState.normalFee.feeValue is TxFeeState.MultipleFeeState -> feeState.normalFee.feeValue
is TxFeeState.SingleFeeState -> feeState.fee.feeValue is TxFeeState.SingleFeeState -> feeState.fee.feeValue
} }
val isFeeEnough = checkFeeIsEnough( val swapFeeState = getFeeState(
fee = fee, fee = fee,
spendAmount = SwapAmount.zeroSwapAmount(), spendAmount = SwapAmount.zeroSwapAmount(),
networkId = networkId, networkId = networkId,
fromToken = fromToken, fromTokenStatus = fromTokenStatus,
) )
return quotesLoadedState.copy( return quotesLoadedState.copy(
permissionState = PermissionDataState.PermissionReadyForRequest( permissionState = PermissionDataState.PermissionReadyForRequest(
@ -1199,28 +1239,26 @@ internal class SwapInteractorImpl @Inject constructor(
), ),
), ),
preparedSwapConfigState = quotesLoadedState.preparedSwapConfigState.copy( preparedSwapConfigState = quotesLoadedState.preparedSwapConfigState.copy(
isFeeEnough = isFeeEnough, feeState = swapFeeState,
), ),
) )
} }
private suspend fun ProxyFees.MultipleFees.proxyFeesToFeeState(networkId: String): TxFeeState { private suspend fun ProxyFees.MultipleFees.proxyFeesToFeeState(fromToken: CryptoCurrency): TxFeeState {
val normalFeeValue = this.minFee.fee.value // in swap for normal use min fee val normalFeeValue = this.minFee.fee.value // in swap for normal use min fee
val normalFeeGas = this.minFee.gasLimit.toInt() val normalFeeGas = this.minFee.gasLimit.toInt()
val priorityFeeValue = this.normalFee.fee.value // in swap for priority use normal fee val priorityFeeValue = this.normalFee.fee.value // in swap for priority use normal fee
val priorityFeeGas = this.normalFee.gasLimit.toInt() val priorityFeeGas = this.normalFee.gasLimit.toInt()
val feesFiat = getFormattedFiatFees(networkId, normalFeeValue, priorityFeeValue) val feesFiat = getFormattedFiatFees(fromToken, normalFeeValue, priorityFeeValue)
val normalFiatFee = requireNotNull(feesFiat.getOrNull(0)) { "feesFiat item 0 couldn't be null" } val normalFiatFee = requireNotNull(feesFiat.getOrNull(0)) { "feesFiat item 0 couldn't be null" }
val priorityFiatFee = requireNotNull(feesFiat.getOrNull(1)) { "feesFiat item 1 couldn't be null" } val priorityFiatFee = requireNotNull(feesFiat.getOrNull(1)) { "feesFiat item 1 couldn't be null" }
val networkCurrency = userWalletManager.getNetworkCurrency(networkId)
val decimals = transactionManager.getNativeTokenDecimals(networkId)
val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI( val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
amount = normalFeeValue, amount = normalFeeValue,
decimals = decimals, decimals = minFee.fee.decimals,
) )
val priorityCryptoFee = amountFormatter.formatBigDecimalAmountToUI( val priorityCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
amount = priorityFeeValue, amount = priorityFeeValue,
decimals = decimals, decimals = normalFee.fee.decimals,
) )
return TxFeeState.MultipleFeeState( return TxFeeState.MultipleFeeState(
normalFee = TxFee( normalFee = TxFee(
@ -1228,8 +1266,8 @@ internal class SwapInteractorImpl @Inject constructor(
gasLimit = normalFeeGas, gasLimit = normalFeeGas,
feeFiatFormatted = normalFiatFee, feeFiatFormatted = normalFiatFee,
feeCryptoFormatted = normalCryptoFee, feeCryptoFormatted = normalCryptoFee,
decimals = decimals, decimals = minFee.fee.decimals,
cryptoSymbol = networkCurrency, cryptoSymbol = minFee.fee.currencySymbol,
feeType = FeeType.NORMAL, feeType = FeeType.NORMAL,
), ),
priorityFee = TxFee( priorityFee = TxFee(
@ -1237,23 +1275,21 @@ internal class SwapInteractorImpl @Inject constructor(
gasLimit = priorityFeeGas, gasLimit = priorityFeeGas,
feeFiatFormatted = priorityFiatFee, feeFiatFormatted = priorityFiatFee,
feeCryptoFormatted = priorityCryptoFee, feeCryptoFormatted = priorityCryptoFee,
decimals = decimals, decimals = normalFee.fee.decimals,
cryptoSymbol = networkCurrency, cryptoSymbol = normalFee.fee.currencySymbol,
feeType = FeeType.PRIORITY, feeType = FeeType.PRIORITY,
), ),
) )
} }
private suspend fun ProxyFees.SingleFee.proxyFeesToFeeState(networkId: String): TxFeeState { private suspend fun ProxyFees.SingleFee.proxyFeesToFeeState(fromToken: CryptoCurrency): TxFeeState {
val normalFeeValue = this.singleFee.fee.value val normalFeeValue = this.singleFee.fee.value
val normalFeeGas = this.singleFee.gasLimit.toInt() val normalFeeGas = this.singleFee.gasLimit.toInt()
val networkCurrency = userWalletManager.getNetworkCurrency(networkId) val feesFiat = getFormattedFiatFees(fromToken, normalFeeValue)
val feesFiat = getFormattedFiatFees(networkId, normalFeeValue)
val normalFiatFee = requireNotNull(feesFiat.getOrNull(0)) { "feesFiat item 0 couldn't be null" } val normalFiatFee = requireNotNull(feesFiat.getOrNull(0)) { "feesFiat item 0 couldn't be null" }
val decimals = transactionManager.getNativeTokenDecimals(networkId)
val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI( val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
amount = normalFeeValue, amount = normalFeeValue,
decimals = decimals, decimals = singleFee.fee.decimals,
) )
return TxFeeState.SingleFeeState( return TxFeeState.SingleFeeState(
fee = TxFee( fee = TxFee(
@ -1261,32 +1297,30 @@ internal class SwapInteractorImpl @Inject constructor(
gasLimit = normalFeeGas, gasLimit = normalFeeGas,
feeFiatFormatted = normalFiatFee, feeFiatFormatted = normalFiatFee,
feeCryptoFormatted = normalCryptoFee, feeCryptoFormatted = normalCryptoFee,
decimals = decimals, decimals = singleFee.fee.decimals,
cryptoSymbol = networkCurrency, cryptoSymbol = singleFee.fee.currencySymbol,
feeType = FeeType.NORMAL, feeType = FeeType.NORMAL,
), ),
) )
} }
private suspend fun TransactionFee.toTxFeeState(networkId: String): TxFeeState { private suspend fun TransactionFee.toTxFeeState(fromToken: CryptoCurrency): TxFeeState {
val networkCurrency = userWalletManager.getNetworkCurrency(networkId)
val decimals = transactionManager.getNativeTokenDecimals(networkId)
return when (this) { return when (this) {
is TransactionFee.Choosable -> { is TransactionFee.Choosable -> {
val normalFee = this.normal.increaseGasLimitBy(INCREASE_GAS_LIMIT_FOR_SEND) val normalFee = this.normal.increaseGasLimitBy(INCREASE_GAS_LIMIT_FOR_SEND)
val priorityFee = this.priority.increaseGasLimitBy(INCREASE_GAS_LIMIT_FOR_SEND) val priorityFee = this.priority.increaseGasLimitBy(INCREASE_GAS_LIMIT_FOR_SEND)
val feeNormal = normalFee.amount.value ?: BigDecimal.ZERO val feeNormal = normalFee.amount.value ?: BigDecimal.ZERO
val feePriority = priorityFee.amount.value ?: BigDecimal.ZERO val feePriority = priorityFee.amount.value ?: BigDecimal.ZERO
val normalFiatValue = getFormattedFiatFees(networkId, feeNormal)[0] val normalFiatValue = getFormattedFiatFees(fromToken, feeNormal)[0]
val priorityFiatValue = getFormattedFiatFees(networkId, feePriority)[0] val priorityFiatValue = getFormattedFiatFees(fromToken, feePriority)[0]
val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI( val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
amount = feeNormal, amount = feeNormal,
decimals = decimals, decimals = normalFee.amount.decimals,
) )
val priorityCryptoFee = amountFormatter.formatBigDecimalAmountToUI( val priorityCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
amount = feePriority, amount = feePriority,
decimals = decimals, decimals = priorityFee.amount.decimals,
) )
TxFeeState.MultipleFeeState( TxFeeState.MultipleFeeState(
normalFee = TxFee( normalFee = TxFee(
@ -1294,8 +1328,8 @@ internal class SwapInteractorImpl @Inject constructor(
gasLimit = normalFee.getGasLimit(), gasLimit = normalFee.getGasLimit(),
feeFiatFormatted = normalFiatValue, feeFiatFormatted = normalFiatValue,
feeCryptoFormatted = normalCryptoFee, feeCryptoFormatted = normalCryptoFee,
decimals = decimals, decimals = normalFee.amount.decimals,
cryptoSymbol = networkCurrency, cryptoSymbol = normalFee.amount.currencySymbol,
feeType = FeeType.NORMAL, feeType = FeeType.NORMAL,
), ),
priorityFee = TxFee( priorityFee = TxFee(
@ -1303,18 +1337,18 @@ internal class SwapInteractorImpl @Inject constructor(
gasLimit = priorityFee.getGasLimit(), gasLimit = priorityFee.getGasLimit(),
feeFiatFormatted = priorityFiatValue, feeFiatFormatted = priorityFiatValue,
feeCryptoFormatted = priorityCryptoFee, feeCryptoFormatted = priorityCryptoFee,
decimals = decimals, decimals = priorityFee.amount.decimals,
cryptoSymbol = networkCurrency, cryptoSymbol = priorityFee.amount.currencySymbol,
feeType = FeeType.PRIORITY, feeType = FeeType.PRIORITY,
), ),
) )
} }
is TransactionFee.Single -> { is TransactionFee.Single -> {
val feeNormal = this.normal.amount.value ?: BigDecimal.ZERO val feeNormal = this.normal.amount.value ?: BigDecimal.ZERO
val normalFiatValue = getFormattedFiatFees(networkId, feeNormal)[0] val normalFiatValue = getFormattedFiatFees(fromToken, feeNormal)[0]
val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI( val normalCryptoFee = amountFormatter.formatBigDecimalAmountToUI(
amount = feeNormal, amount = feeNormal,
decimals = transactionManager.getNativeTokenDecimals(networkId), decimals = this.normal.amount.decimals,
) )
TxFeeState.SingleFeeState( TxFeeState.SingleFeeState(
fee = TxFee( fee = TxFee(
@ -1322,8 +1356,8 @@ internal class SwapInteractorImpl @Inject constructor(
gasLimit = this.normal.getGasLimit(), gasLimit = this.normal.getGasLimit(),
feeFiatFormatted = normalFiatValue, feeFiatFormatted = normalFiatValue,
feeCryptoFormatted = normalCryptoFee, feeCryptoFormatted = normalCryptoFee,
decimals = decimals, decimals = normal.amount.decimals,
cryptoSymbol = networkCurrency, cryptoSymbol = normal.amount.currencySymbol,
feeType = FeeType.NORMAL, feeType = FeeType.NORMAL,
), ),
) )
@ -1375,15 +1409,35 @@ internal class SwapInteractorImpl @Inject constructor(
} }
} }
private fun isBalanceEnough(fromToken: CryptoCurrencyStatus, amount: SwapAmount, fee: BigDecimal?): Boolean { private suspend fun isBalanceEnough(
fromToken: CryptoCurrencyStatus,
amount: SwapAmount,
fee: BigDecimal?,
): Boolean {
val tokenBalance = getTokenBalance(fromToken).value val tokenBalance = getTokenBalance(fromToken).value
return if (fromToken.currency is CryptoCurrency.Token) { val feePaidCurrency = getFeePaidCurrency(
tokenBalance >= amount.value userWalletId = requireNotNull(getSelectedWallet()).walletId,
} else { currency = fromToken.currency,
tokenBalance > amount.value.plus(fee ?: BigDecimal.ZERO) )
return when (feePaidCurrency) {
is FeePaidCurrency.Token -> tokenBalance >= amount.value
else -> {
if (fromToken.currency is CryptoCurrency.Token) {
tokenBalance >= amount.value
} else {
tokenBalance > amount.value.plus(fee ?: BigDecimal.ZERO)
}
}
} }
} }
private suspend fun getFeePaidCurrency(userWalletId: UserWalletId, currency: CryptoCurrency): FeePaidCurrency {
return currenciesRepository.getFeePaidCurrency(
userWalletId = userWalletId,
currency = currency,
)
}
private suspend fun getWalletAddress(networkId: String, derivationPath: String?): String { private suspend fun getWalletAddress(networkId: String, derivationPath: String?): String {
return userWalletManager.getWalletAddress(networkId, derivationPath) return userWalletManager.getWalletAddress(networkId, derivationPath)
} }
@ -1399,36 +1453,72 @@ internal class SwapInteractorImpl @Inject constructor(
} }
} }
private suspend fun checkFeeIsEnough( private fun toBigDecimalOrNull(amountToSwap: String): BigDecimal? {
return amountToSwap.replace(",", ".").toBigDecimalOrNull()
}
private suspend fun getFeeState(
fee: BigDecimal?, fee: BigDecimal?,
spendAmount: SwapAmount, spendAmount: SwapAmount,
networkId: String, networkId: String,
fromToken: CryptoCurrency, fromTokenStatus: CryptoCurrencyStatus,
): Boolean { ): SwapFeeState {
val userWalletId = requireNotNull(getSelectedWallet()).walletId
if (fee == null) { if (fee == null) {
return false return SwapFeeState.NotEnough()
} }
val nativeTokenBalance = userWalletManager.getNativeTokenBalance(
networkId,
fromToken.network.derivationPath.value,
)
val percentsToFeeIncrease = BigDecimal.ONE
return when (fromToken) {
is CryptoCurrency.Coin -> {
nativeTokenBalance?.let { balance ->
return balance.value.minus(spendAmount.value) > fee.multiply(percentsToFeeIncrease)
} ?: false
}
is CryptoCurrency.Token -> {
nativeTokenBalance?.let { balance ->
return balance.value > fee.multiply(percentsToFeeIncrease)
} ?: false
}
}
}
private fun toBigDecimalOrNull(amountToSwap: String): BigDecimal? { val percentsToFeeIncrease = BigDecimal.ONE
return amountToSwap.replace(",", ".").toBigDecimalOrNull() return when (val feePaidCurrency = getFeePaidCurrency(userWalletId, fromTokenStatus.currency)) {
FeePaidCurrency.Coin -> {
val nativeTokenBalance = userWalletManager.getNativeTokenBalance(
networkId,
fromTokenStatus.currency.network.derivationPath.value,
)
nativeTokenBalance?.let { balance ->
if (balance.value.minus(spendAmount.value) > fee.multiply(percentsToFeeIncrease)) {
SwapFeeState.Enough
} else {
val nativeToken = getNativeToken(fromTokenStatus.currency.network.backendId)
SwapFeeState.NotEnough(
feeCurrency = nativeToken,
currencyName = nativeToken.network.name,
currencySymbol = nativeToken.symbol,
)
}
} ?: SwapFeeState.NotEnough()
}
FeePaidCurrency.SameCurrency -> {
val balance = fromTokenStatus.value.amount ?: return SwapFeeState.NotEnough()
if (balance.minus(spendAmount.value) > fee.multiply(percentsToFeeIncrease)) {
SwapFeeState.Enough
} else {
SwapFeeState.NotEnough(
feeCurrency = fromTokenStatus.currency,
currencyName = fromTokenStatus.currency.network.name,
currencySymbol = fromTokenStatus.currency.symbol,
)
}
}
is FeePaidCurrency.Token -> {
if (feePaidCurrency.balance > fee.multiply(percentsToFeeIncrease)) {
SwapFeeState.Enough
} else {
val token = currenciesRepository
.getMultiCurrencyWalletCurrenciesSync(userWalletId)
.find {
it is CryptoCurrency.Token &&
it.contractAddress.equals(feePaidCurrency.contractAddress, ignoreCase = true) &&
it.network.derivationPath == fromTokenStatus.currency.network.derivationPath
}
SwapFeeState.NotEnough(
feeCurrency = token,
currencyName = feePaidCurrency.name,
currencySymbol = feePaidCurrency.symbol,
)
}
}
}
} }
private fun calculatePriceImpact( private fun calculatePriceImpact(

View file

@ -47,6 +47,7 @@ class SwapDomainModule {
walletManagersFacade: WalletManagersFacade, walletManagersFacade: WalletManagersFacade,
coroutineDispatcherProvider: CoroutineDispatcherProvider, coroutineDispatcherProvider: CoroutineDispatcherProvider,
initialToCurrencyResolver: InitialToCurrencyResolver, initialToCurrencyResolver: InitialToCurrencyResolver,
currenciesRepository: CurrenciesRepository,
): SwapInteractor { ): SwapInteractor {
return SwapInteractorImpl( return SwapInteractorImpl(
transactionManager = transactionManager, transactionManager = transactionManager,
@ -62,6 +63,7 @@ class SwapDomainModule {
swapTransactionRepository = swapTransactionRepository, swapTransactionRepository = swapTransactionRepository,
appCurrencyRepository = appCurrencyRepository, appCurrencyRepository = appCurrencyRepository,
currencyChecksRepository = currencyChecksRepository, currencyChecksRepository = currencyChecksRepository,
currenciesRepository = currenciesRepository,
initialToCurrencyResolver = initialToCurrencyResolver, initialToCurrencyResolver = initialToCurrencyResolver,
) )
} }

View file

@ -15,7 +15,6 @@ import com.tangem.feature.swap.models.states.ProviderState
data class SwapStateHolder( data class SwapStateHolder(
val sendCardData: SwapCardState, val sendCardData: SwapCardState,
val receiveCardData: SwapCardState, val receiveCardData: SwapCardState,
val networkCurrency: String,
val blockchainId: String, // not the same as networkId, its local id in app val blockchainId: String, // not the same as networkId, its local id in app
val warnings: List<SwapWarning> = emptyList(), val warnings: List<SwapWarning> = emptyList(),
val alert: SwapWarning.GenericWarning? = null, val alert: SwapWarning.GenericWarning? = null,

View file

@ -1,5 +1,6 @@
package com.tangem.feature.swap.models package com.tangem.feature.swap.models
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.domain.models.ui.TxFee import com.tangem.feature.swap.domain.models.ui.TxFee
data class UiActions( data class UiActions(
@ -20,7 +21,7 @@ data class UiActions(
val onSelectFeeType: (TxFee) -> Unit, val onSelectFeeType: (TxFee) -> Unit,
val onProviderClick: (String) -> Unit, val onProviderClick: (String) -> Unit,
val onProviderSelect: (String) -> Unit, val onProviderSelect: (String) -> Unit,
val onBuyClick: () -> Unit, val onBuyClick: (CryptoCurrency) -> Unit,
val onPolicyClick: (String) -> Unit, val onPolicyClick: (String) -> Unit,
val onTosClick: (String) -> Unit, val onTosClick: (String) -> Unit,
val onReceiveCardWarningClick: () -> Unit, val onReceiveCardWarningClick: () -> Unit,

View file

@ -79,7 +79,6 @@ internal class StateBuilder(
isBalanceHidden = true, isBalanceHidden = true,
), ),
fee = FeeItemState.Empty, fee = FeeItemState.Empty,
networkCurrency = networkInfo.blockchainCurrency,
swapButton = SwapButton(enabled = false, onClick = {}), swapButton = SwapButton(enabled = false, onClick = {}),
onRefresh = {}, onRefresh = {},
onBackClicked = actions.onBackClicked, onBackClicked = actions.onBackClicked,
@ -250,7 +249,6 @@ internal class StateBuilder(
balance = toCurrencyStatus.getFormattedAmount(isNeedSymbol = false), balance = toCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
isBalanceHidden = isBalanceHiddenProvider(), isBalanceHidden = isBalanceHiddenProvider(),
), ),
networkCurrency = quoteModel.networkCurrency,
warnings = warnings, warnings = warnings,
permissionState = convertPermissionState( permissionState = convertPermissionState(
lastPermissionState = uiStateHolder.permissionState, lastPermissionState = uiStateHolder.permissionState,
@ -317,7 +315,7 @@ internal class StateBuilder(
val warnings = mutableListOf<SwapWarning>() val warnings = mutableListOf<SwapWarning>()
addDomainWarnings(quoteModel, warnings) addDomainWarnings(quoteModel, warnings)
if (!quoteModel.preparedSwapConfigState.isAllowedToSpend && if (!quoteModel.preparedSwapConfigState.isAllowedToSpend &&
quoteModel.preparedSwapConfigState.isFeeEnough && quoteModel.preparedSwapConfigState.feeState is SwapFeeState.Enough &&
quoteModel.permissionState is PermissionDataState.PermissionReadyForRequest quoteModel.permissionState is PermissionDataState.PermissionReadyForRequest
) { ) {
warnings.add( warnings.add(
@ -412,7 +410,8 @@ internal class StateBuilder(
fromToken: CryptoCurrency, fromToken: CryptoCurrency,
warnings: MutableList<SwapWarning>, warnings: MutableList<SwapWarning>,
) { ) {
if (!quoteModel.preparedSwapConfigState.isFeeEnough && val feeEnoughState = quoteModel.preparedSwapConfigState.feeState
if (feeEnoughState is SwapFeeState.NotEnough &&
quoteModel.preparedSwapConfigState.isBalanceEnough && quoteModel.preparedSwapConfigState.isBalanceEnough &&
quoteModel.permissionState !is PermissionDataState.PermissionLoading quoteModel.permissionState !is PermissionDataState.PermissionLoading
) { ) {
@ -420,7 +419,9 @@ internal class StateBuilder(
SwapWarning.UnableToCoverFeeWarning( SwapWarning.UnableToCoverFeeWarning(
createUnableToCoverFeeNotificationConfig( createUnableToCoverFeeNotificationConfig(
fromToken = fromToken, fromToken = fromToken,
onBuyClick = actions.onBuyClick, feeCurrency = feeEnoughState.feeCurrency,
currencyName = feeEnoughState.currencyName ?: fromToken.network.name,
currencySymbol = feeEnoughState.currencySymbol ?: fromToken.network.currencySymbol,
), ),
), ),
) )
@ -438,7 +439,7 @@ internal class StateBuilder(
IncludeFeeInAmount.Excluded -> IncludeFeeInAmount.Excluded ->
preparedSwapConfigState.isAllowedToSpend && preparedSwapConfigState.isAllowedToSpend &&
preparedSwapConfigState.isBalanceEnough && preparedSwapConfigState.isBalanceEnough &&
preparedSwapConfigState.isFeeEnough preparedSwapConfigState.feeState is SwapFeeState.Enough
is IncludeFeeInAmount.Included -> true is IncludeFeeInAmount.Included -> true
} }
} }
@ -1219,8 +1220,16 @@ internal class StateBuilder(
private fun createUnableToCoverFeeNotificationConfig( private fun createUnableToCoverFeeNotificationConfig(
fromToken: CryptoCurrency, fromToken: CryptoCurrency,
onBuyClick: () -> Unit, feeCurrency: CryptoCurrency?,
currencyName: String,
currencySymbol: String,
): NotificationConfig { ): NotificationConfig {
val buttonState = feeCurrency?.let {
NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.common_buy_currency, wrappedList(currencySymbol)),
onClick = { actions.onBuyClick(it) },
)
}
return NotificationConfig( return NotificationConfig(
title = resourceReference( title = resourceReference(
R.string.warning_express_not_enough_fee_for_token_tx_title, R.string.warning_express_not_enough_fee_for_token_tx_title,
@ -1228,13 +1237,10 @@ internal class StateBuilder(
), ),
subtitle = resourceReference( subtitle = resourceReference(
R.string.warning_express_not_enough_fee_for_token_tx_description, R.string.warning_express_not_enough_fee_for_token_tx_description,
wrappedList(fromToken.network.name, fromToken.network.currencySymbol), wrappedList(currencyName, currencySymbol),
), ),
iconResId = fromToken.networkIconResId, iconResId = fromToken.networkIconResId,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( buttonsState = buttonState,
text = resourceReference(R.string.common_buy_currency, wrappedList(fromToken.network.currencySymbol)),
onClick = onBuyClick,
),
) )
} }

View file

@ -490,7 +490,6 @@ private val state = SwapStateHolder(
), ),
), ),
), ),
networkCurrency = "MATIC",
swapButton = SwapButton(enabled = true, onClick = {}), swapButton = SwapButton(enabled = true, onClick = {}),
onRefresh = {}, onRefresh = {},
onBackClicked = {}, onBackClicked = {},

View file

@ -932,12 +932,11 @@ internal class SwapViewModel @Inject constructor(
) )
} }
}, },
onBuyClick = { onBuyClick = { currency ->
swapInteractor.getSelectedWallet()?.let { swapInteractor.getSelectedWallet()?.let { userWallet ->
val fromToken = dataState.fromCryptoCurrency ?: return@let
swapRouter.openTokenDetails( swapRouter.openTokenDetails(
it.walletId, userWalletId = userWallet.walletId,
swapInteractor.getNativeToken(fromToken.currency.network.backendId), currency = currency,
) )
} }
}, },

View file

@ -54,9 +54,6 @@ interface TransactionManager {
derivationPath: String?, derivationPath: String?,
): ProxyFees ): ProxyFees
@Throws(IllegalStateException::class)
fun getNativeTokenDecimals(networkId: String): Int
@Throws(IllegalStateException::class) @Throws(IllegalStateException::class)
suspend fun updateWalletManager(networkId: String, derivationPath: String?) suspend fun updateWalletManager(networkId: String, derivationPath: String?)

View file

@ -59,13 +59,6 @@ interface UserWalletManager {
@Throws(IllegalStateException::class) @Throws(IllegalStateException::class)
suspend fun getNativeTokenBalance(networkId: String, derivationPath: String?): ProxyAmount? suspend fun getNativeTokenBalance(networkId: String, derivationPath: String?): ProxyAmount?
/**
* @param networkId
* @return currency name
*/
@Throws(IllegalStateException::class)
fun getNetworkCurrency(networkId: String): String
@Throws(IllegalStateException::class) @Throws(IllegalStateException::class)
suspend fun getLastTransactionHash(networkId: String, derivationPath: String?): String? suspend fun getLastTransactionHash(networkId: String, derivationPath: String?): String?
} }

View file

@ -3,5 +3,4 @@ package com.tangem.lib.crypto.models
data class ProxyNetworkInfo( data class ProxyNetworkInfo(
val name: String, val name: String,
val blockchainId: String, val blockchainId: String,
val blockchainCurrency: String,
) )