Updated on 2026-08-14
This commit is contained in:
commit
c4a3a8d108
16 changed files with 132 additions and 51 deletions
|
|
@ -72,7 +72,7 @@ class WalletConnectSdkHelper {
|
|||
}
|
||||
|
||||
// TODO move fee calculation to SDK getFee() [REDACTED_JIRA]
|
||||
val gasLimit = getGasLimitFromTx(value, walletManager, transaction)
|
||||
val gasLimit = getGasLimitFromTx(value, walletManager, transaction, blockchain)
|
||||
val gasPrice = getGasPrice(walletManager, transaction)
|
||||
|
||||
val feeDecimal = (gasLimit * gasPrice).movePointLeft(decimals)
|
||||
|
|
@ -170,6 +170,7 @@ class WalletConnectSdkHelper {
|
|||
value: BigDecimal,
|
||||
walletManager: WalletManager,
|
||||
transaction: WcEthereumTransaction,
|
||||
blockchain: Blockchain,
|
||||
): BigDecimal {
|
||||
return transaction.gas?.hexToBigDecimal()
|
||||
?: transaction.gasLimit?.hexToBigDecimal()
|
||||
|
|
@ -177,7 +178,7 @@ class WalletConnectSdkHelper {
|
|||
value = value,
|
||||
walletManager = walletManager,
|
||||
transaction = transaction,
|
||||
)
|
||||
).increaseForMantleIfNeeded(blockchain)
|
||||
}
|
||||
|
||||
private suspend fun getGasLimitFromBlockchain(
|
||||
|
|
@ -200,6 +201,15 @@ class WalletConnectSdkHelper {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO Workaround for Mantle. Remove after [REDACTED_JIRA]
|
||||
private fun BigDecimal.increaseForMantleIfNeeded(blockchain: Blockchain): BigDecimal {
|
||||
return if (blockchain == Blockchain.Mantle) {
|
||||
this.multiply(MANTLE_FEE_ESTIMATE_MULTIPLIER)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendTransaction(data: WcTransactionData, cardId: String?): String? {
|
||||
val result = (data.walletManager as TransactionSender).send(
|
||||
transactionData = data.transaction,
|
||||
|
|
@ -437,6 +447,6 @@ class WalletConnectSdkHelper {
|
|||
const val HEX_PREFIX = "0x"
|
||||
const val DEFAULT_MAX_GASLIMIT = 350000
|
||||
// TODO remove after [REDACTED_JIRA]
|
||||
private val MANTLE_FEE_ESTIMATE_MULTIPLIER = BigDecimal("1.6")
|
||||
private val MANTLE_FEE_ESTIMATE_MULTIPLIER = BigDecimal("1.8")
|
||||
}
|
||||
}
|
||||
|
|
@ -272,22 +272,24 @@ class TransactionManagerImpl(
|
|||
* @param blockchain
|
||||
*/
|
||||
private fun createMultipleProxyFees(gasPrice: BigInteger, gasLimit: BigInteger, blockchain: Blockchain): ProxyFees {
|
||||
val gasPriceNormal = gasPrice.increaseBigIntegerByPercents(MULTIPLIER_GAS_PRICE_FOR_NORMAL_FEE)
|
||||
val patchedGasLimit = gasLimit.toBigDecimal().increaseForMantleIfNeeded(blockchain).toBigInteger()
|
||||
val gasPriceNormal = gasPrice
|
||||
.increaseBigIntegerByPercents(MULTIPLIER_GAS_PRICE_FOR_NORMAL_FEE)
|
||||
val gasPricePriority = gasPrice.increaseBigIntegerByPercents(MULTIPLIER_GAS_PRICE_FOR_PRIORITY_FEE)
|
||||
val feeMin = gasLimit.multiply(gasPrice).toBigDecimal(
|
||||
val feeMin = patchedGasLimit.multiply(gasPrice).toBigDecimal(
|
||||
scale = blockchain.decimals(),
|
||||
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
|
||||
)
|
||||
val feeNormal = gasLimit.multiply(gasPriceNormal).toBigDecimal(
|
||||
).increaseForMantleIfNeeded(blockchain)
|
||||
val feeNormal = patchedGasLimit.multiply(gasPriceNormal).toBigDecimal(
|
||||
scale = blockchain.decimals(),
|
||||
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
|
||||
)
|
||||
val feePriority = gasLimit.multiply(gasPricePriority).toBigDecimal(
|
||||
).increaseForMantleIfNeeded(blockchain)
|
||||
val feePriority = patchedGasLimit.multiply(gasPricePriority).toBigDecimal(
|
||||
scale = blockchain.decimals(),
|
||||
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
|
||||
)
|
||||
).increaseForMantleIfNeeded(blockchain)
|
||||
val minFee = ProxyFee.Common(
|
||||
gasLimit = gasLimit,
|
||||
gasLimit = patchedGasLimit,
|
||||
fee = ProxyAmount(
|
||||
currencySymbol = blockchain.currency,
|
||||
value = feeMin,
|
||||
|
|
@ -295,7 +297,7 @@ class TransactionManagerImpl(
|
|||
),
|
||||
)
|
||||
val normalFee = ProxyFee.Common(
|
||||
gasLimit = gasLimit,
|
||||
gasLimit = patchedGasLimit,
|
||||
fee = ProxyAmount(
|
||||
currencySymbol = blockchain.currency,
|
||||
value = feeNormal,
|
||||
|
|
@ -303,7 +305,7 @@ class TransactionManagerImpl(
|
|||
),
|
||||
)
|
||||
val priorityFee = ProxyFee.Common(
|
||||
gasLimit = gasLimit,
|
||||
gasLimit = patchedGasLimit,
|
||||
fee = ProxyAmount(
|
||||
currencySymbol = blockchain.currency,
|
||||
value = feePriority,
|
||||
|
|
@ -339,8 +341,18 @@ class TransactionManagerImpl(
|
|||
}
|
||||
}
|
||||
|
||||
// TODO Workaround for Mantle. Remove after [REDACTED_JIRA]
|
||||
private fun BigDecimal.increaseForMantleIfNeeded(blockchain: Blockchain): BigDecimal {
|
||||
return if (blockchain == Blockchain.Mantle) {
|
||||
this.multiply(MANTLE_FEE_ESTIMATE_MULTIPLIER)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MULTIPLIER_GAS_PRICE_FOR_NORMAL_FEE = 150 // 50%
|
||||
private const val MULTIPLIER_GAS_PRICE_FOR_PRIORITY_FEE = 200 // 50%
|
||||
private val MANTLE_FEE_ESTIMATE_MULTIPLIER = BigDecimal("1.8")
|
||||
}
|
||||
}
|
||||
|
|
@ -118,6 +118,7 @@ class ResponseCryptoCurrenciesFactory {
|
|||
// get name and symbol from enum Blockchain until backend renamed
|
||||
// [REDACTED_JIRA]
|
||||
Blockchain.Dischain,
|
||||
Blockchain.Polygon,
|
||||
-> this.currency
|
||||
else -> responseToken.symbol
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ sealed class CryptoCurrencyWarning {
|
|||
|
||||
data object BeaconChainShutdown : CryptoCurrencyWarning()
|
||||
|
||||
data object MigrationMaticToPol : CryptoCurrencyWarning()
|
||||
|
||||
/**
|
||||
* Shows a warning about an available fee resource for a transaction in several blockchains (ex. Koinos)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ class GetCurrencyWarningsUseCase(
|
|||
getNetworkNoAccountWarning(currencyStatus),
|
||||
getBeaconChainShutdownWarning(currency.network.id),
|
||||
getAssetRequirementsWarning(userWalletId = userWalletId, currency = currency),
|
||||
getMigrationFromMaticToPolWarning(currency),
|
||||
)
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
|
@ -304,7 +305,19 @@ class GetCurrencyWarningsUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getMigrationFromMaticToPolWarning(currency: CryptoCurrency): CryptoCurrencyWarning? {
|
||||
return if (currency.symbol == MATIC_SYMBOL && !BlockchainUtils.isPolygonChain(currency.network.id.value)) {
|
||||
CryptoCurrencyWarning.MigrationMaticToPol
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun BigDecimal?.isZero(): Boolean {
|
||||
return this?.signum() == 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MATIC_SYMBOL = "MATIC"
|
||||
}
|
||||
}
|
||||
|
|
@ -1120,6 +1120,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
isAllowedToSpend = isAllowedToSpend,
|
||||
spenderAddress = quoteModel.allowanceContract,
|
||||
)
|
||||
if (state !is SwapState.QuotesLoadedState) return state
|
||||
state.copy(
|
||||
preparedSwapConfigState = state.preparedSwapConfigState.copy(
|
||||
isAllowedToSpend = isAllowedToSpend,
|
||||
|
|
@ -1153,18 +1154,32 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
}
|
||||
},
|
||||
ifLeft = { error ->
|
||||
val rates = getQuotes(fromToken.currency.id)
|
||||
val fromTokenSwapInfo = TokenSwapInfo(
|
||||
tokenAmount = amount,
|
||||
amountFiat = rates[fromToken.currency.id]?.fiatRate?.multiply(amount.value)
|
||||
?: BigDecimal.ZERO,
|
||||
cryptoCurrencyStatus = fromToken,
|
||||
createSwapErrorWith(
|
||||
fromToken = fromToken,
|
||||
amount = amount,
|
||||
includeFeeInAmount = includeFeeInAmount,
|
||||
dataError = error,
|
||||
)
|
||||
return SwapState.SwapError(fromTokenSwapInfo, error, includeFeeInAmount)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun createSwapErrorWith(
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
amount: SwapAmount,
|
||||
includeFeeInAmount: IncludeFeeInAmount,
|
||||
dataError: DataError,
|
||||
): SwapState.SwapError {
|
||||
val rates = getQuotes(fromToken.currency.id)
|
||||
val fromTokenSwapInfo = TokenSwapInfo(
|
||||
tokenAmount = amount,
|
||||
amountFiat = rates[fromToken.currency.id]?.fiatRate?.multiply(amount.value)
|
||||
?: BigDecimal.ZERO,
|
||||
cryptoCurrencyStatus = fromToken,
|
||||
)
|
||||
return SwapState.SwapError(fromTokenSwapInfo, dataError, includeFeeInAmount)
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private suspend fun getIncludeFeeInAmount(
|
||||
networkId: String,
|
||||
|
|
@ -1460,7 +1475,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
quotesLoadedState: SwapState.QuotesLoadedState,
|
||||
spenderAddress: String?,
|
||||
isAllowedToSpend: Boolean,
|
||||
): SwapState.QuotesLoadedState {
|
||||
): SwapState {
|
||||
val fromToken = fromTokenStatus.currency
|
||||
if (isAllowedToSpend) {
|
||||
return quotesLoadedState.copy(
|
||||
|
|
@ -1504,15 +1519,19 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to get fee")
|
||||
null
|
||||
// it's impossible next steps without fee
|
||||
return createSwapErrorWith(
|
||||
fromToken = fromTokenStatus,
|
||||
amount = swapAmount,
|
||||
includeFeeInAmount = IncludeFeeInAmount.Excluded,
|
||||
dataError = DataError.UnknownError,
|
||||
)
|
||||
}
|
||||
}
|
||||
val feeState = feeData?.let {
|
||||
when (feeData) {
|
||||
is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken)
|
||||
is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken)
|
||||
}
|
||||
} ?: TxFeeState.Empty
|
||||
val feeState = when (feeData) {
|
||||
is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken)
|
||||
is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken)
|
||||
}
|
||||
val fee = when (feeState) {
|
||||
TxFeeState.Empty -> BigDecimal.ZERO
|
||||
is TxFeeState.MultipleFeeState -> feeState.normalFee.feeValue
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import com.tangem.feature.swap.presentation.R
|
|||
fun SwapSuccessScreen(state: SwapSuccessStateHolder, onBack: () -> Unit) {
|
||||
Scaffold(
|
||||
modifier = Modifier.systemBarsPadding(),
|
||||
backgroundColor = TangemTheme.colors.background.tertiary,
|
||||
backgroundColor = TangemTheme.colors.background.secondary,
|
||||
content = { padding ->
|
||||
SwapSuccessScreenContent(padding = padding, state = state)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ internal class TokenDetailsNotificationsAnalyticsSender(
|
|||
is TokenDetailsNotification.NetworkShutdown,
|
||||
is TokenDetailsNotification.HederaAssociateWarning,
|
||||
is TokenDetailsNotification.KoinosMana,
|
||||
is TokenDetailsNotification.MigrationMaticToPol,
|
||||
-> null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,4 +201,9 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
|
|||
formatArgs = wrappedList(manaBalanceAmount, maxManaBalanceAmount),
|
||||
),
|
||||
)
|
||||
|
||||
data object MigrationMaticToPol : Warning(
|
||||
title = resourceReference(id = R.string.warning_matic_migration_title),
|
||||
subtitle = resourceReference(id = R.string.warning_matic_migration_message),
|
||||
)
|
||||
}
|
||||
|
|
@ -122,6 +122,7 @@ internal class TokenDetailsNotificationConverter(
|
|||
""
|
||||
},
|
||||
)
|
||||
is CryptoCurrencyWarning.MigrationMaticToPol -> MigrationMaticToPol
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ internal class WalletStateController @Inject constructor() {
|
|||
|
||||
val value: WalletScreenState get() = uiState.value
|
||||
|
||||
val isInitialized: Boolean
|
||||
get() = value.selectedWalletIndex != NOT_INITIALIZED_WALLET_INDEX
|
||||
|
||||
private val mutableUiState: MutableStateFlow<WalletScreenState> = MutableStateFlow(value = getInitialState())
|
||||
|
||||
fun update(function: (WalletScreenState) -> WalletScreenState) {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@ package com.tangem.feature.wallet.presentation.wallet.ui
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.TweenSpec
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
|
|
@ -220,12 +223,9 @@ private fun WalletContent(
|
|||
}
|
||||
|
||||
if (marketsEntryComponent != null) {
|
||||
val bottomSheetState = remember {
|
||||
mutableStateOf(BottomSheetState.COLLAPSED)
|
||||
}
|
||||
var headerSize by remember {
|
||||
mutableStateOf(0.dp)
|
||||
}
|
||||
val bottomSheetState = remember { mutableStateOf(COLLAPSED) }
|
||||
|
||||
var headerSize by remember { mutableStateOf(0.dp) }
|
||||
|
||||
BaseScaffoldWithMarkets(
|
||||
state = state,
|
||||
|
|
@ -241,17 +241,15 @@ private fun WalletContent(
|
|||
modifier = Modifier,
|
||||
)
|
||||
},
|
||||
) {
|
||||
scaffoldContent()
|
||||
}
|
||||
content = scaffoldContent,
|
||||
)
|
||||
} else {
|
||||
BaseScaffold(
|
||||
state = state,
|
||||
selectedWallet = selectedWallet,
|
||||
snackbarHostState = snackbarHostState,
|
||||
) {
|
||||
scaffoldContent()
|
||||
}
|
||||
content = scaffoldContent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -274,16 +272,21 @@ private fun BaseScaffold(
|
|||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
val manageTokensButtonConfig by remember(state.selectedWalletIndex) {
|
||||
mutableStateOf(
|
||||
(state.wallets[state.selectedWalletIndex] as? WalletState.MultiCurrency)?.manageTokensButtonConfig,
|
||||
)
|
||||
}
|
||||
val manageTokensButtonConfig by rememberUpdatedState(
|
||||
newValue = (state.wallets[state.selectedWalletIndex] as? WalletState.MultiCurrency)
|
||||
?.manageTokensButtonConfig,
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = manageTokensButtonConfig != null,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
val config = manageTokensButtonConfig ?: return@AnimatedVisibility
|
||||
|
||||
manageTokensButtonConfig?.let {
|
||||
ManageTokensButton(
|
||||
modifier = Modifier.navigationBarsPadding(),
|
||||
onClick = it.onClick,
|
||||
onClick = config.onClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ import androidx.lifecycle.viewModelScope
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.settings.*
|
||||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||
import com.tangem.domain.settings.IsWalletsScrollPreviewEnabled
|
||||
import com.tangem.domain.settings.ShouldAskPermissionUseCase
|
||||
import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase
|
||||
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
|
|
@ -190,6 +193,8 @@ internal class WalletViewModel @Inject constructor(
|
|||
|
||||
/** Change selected wallet state if selected wallet [selectedWalletId] was changed in the background */
|
||||
private suspend fun changeSelectedWalletState(selectedWalletId: UserWalletId) {
|
||||
if (!stateHolder.isInitialized) return
|
||||
|
||||
if (screenLifecycleProvider.isBackgroundState.value && selectedWalletId != stateHolder.getSelectedWalletId()) {
|
||||
stateHolder.value.wallets
|
||||
.indexOfFirstOrNull { prevState -> prevState.walletCardState.id == selectedWalletId }
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ markdown = "0.7.2"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "develop-755"
|
||||
tangemBlockchainSdk = "develop-759"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-378"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ fun Blockchain.toCoinId(): String {
|
|||
Blockchain.Bittensor -> "bittensor"
|
||||
Blockchain.Filecoin -> "filecoin"
|
||||
Blockchain.Blast, Blockchain.BlastTestnet -> "blast-ethereum"
|
||||
Blockchain.Cyber, Blockchain.CyberTestnet -> "cyberconnect"
|
||||
Blockchain.Cyber, Blockchain.CyberTestnet -> "cyber-ethereum"
|
||||
Blockchain.Sei, Blockchain.SeiTestnet -> "sei-network"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,12 @@ object BlockchainUtils {
|
|||
return blockchain == Blockchain.Binance || blockchain == Blockchain.BinanceTestnet
|
||||
}
|
||||
|
||||
/** If current [networkId] is Polygon */
|
||||
fun isPolygonChain(networkId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(networkId)
|
||||
return blockchain == Blockchain.Polygon || blockchain == Blockchain.PolygonTestnet
|
||||
}
|
||||
|
||||
fun isTron(networkId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(networkId)
|
||||
return blockchain == Blockchain.Tron || blockchain == Blockchain.TronTestnet
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue