Updated on 2026-08-14
This commit is contained in:
commit
da717845f0
538 changed files with 7350 additions and 5597 deletions
|
|
@ -113,7 +113,7 @@ internal class StakingAnalyticSender(
|
|||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
|
||||
return when (value.actionType) {
|
||||
StakingActionCommonType.Enter -> StakingActionType.STAKE
|
||||
is StakingActionCommonType.Enter -> StakingActionType.STAKE
|
||||
is StakingActionCommonType.Exit -> StakingActionType.UNSTAKE
|
||||
is StakingActionCommonType.Pending -> confirmationState?.pendingAction?.type ?: StakingActionType.UNKNOWN
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.features.staking.impl.di
|
||||
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.staking.api.StakingComponent
|
||||
import com.tangem.features.staking.impl.DefaultStakingComponent
|
||||
|
|
@ -29,10 +29,10 @@ internal interface StakingModule {
|
|||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
@InstallIn(ModelComponent::class)
|
||||
internal interface StakingComponentModule {
|
||||
|
||||
@Binds
|
||||
@ComponentScoped
|
||||
@ModelScoped
|
||||
fun bindRouter(impl: DefaultStakingRouter): InnerStakingRouter
|
||||
}
|
||||
|
|
@ -2,13 +2,13 @@ package com.tangem.features.staking.impl.navigation
|
|||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import javax.inject.Inject
|
||||
|
||||
@ComponentScoped
|
||||
@ModelScoped
|
||||
internal class DefaultStakingRouter @Inject constructor(
|
||||
private val urlOpener: UrlOpener,
|
||||
private val router: AppRouter,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomS
|
|||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.api.ParamsInterceptorHolder
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
|
|
@ -33,7 +33,6 @@ import com.tangem.domain.staking.model.StakingApproval
|
|||
import com.tangem.domain.staking.model.stakekit.*
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -42,8 +41,6 @@ import com.tangem.domain.transaction.error.GetFeeError
|
|||
import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetAllowanceUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
|
|
@ -69,6 +66,7 @@ import com.tangem.features.staking.impl.presentation.state.transformers.validato
|
|||
import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.isSingleAction
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.withStubUnstakeAction
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.*
|
||||
import com.tangem.utils.extensions.isSingleItem
|
||||
|
|
@ -87,7 +85,7 @@ import kotlin.properties.Delegates
|
|||
|
||||
@Suppress("LargeClass", "TooManyFunctions", "LongParameterList")
|
||||
@Stable
|
||||
@ComponentScoped
|
||||
@ModelScoped
|
||||
internal class StakingModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
private val stateController: StakingStateController,
|
||||
|
|
@ -106,7 +104,6 @@ internal class StakingModel @Inject constructor(
|
|||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
|
||||
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
|
||||
private val validateTransactionUseCase: ValidateTransactionUseCase,
|
||||
private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
|
||||
private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase,
|
||||
private val isAnyTokenStakedUseCase: IsAnyTokenStakedUseCase,
|
||||
|
|
@ -122,7 +119,7 @@ internal class StakingModel @Inject constructor(
|
|||
private val shareManager: ShareManager,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
private val innerRouter: InnerStakingRouter,
|
||||
private val appRouter: AppRouter,
|
||||
appRouter: AppRouter,
|
||||
) : Model(), StakingClickIntents {
|
||||
|
||||
val uiState: StateFlow<StakingUiState> = stateController.uiState
|
||||
|
|
@ -242,7 +239,7 @@ internal class StakingModel @Inject constructor(
|
|||
return
|
||||
}
|
||||
isInitialInfoStep && noBalanceState -> {
|
||||
stateController.update(
|
||||
val list = buildList {
|
||||
SetConfirmationStateInitTransformer(
|
||||
isEnter = true,
|
||||
isExplicitExit = false,
|
||||
|
|
@ -251,8 +248,27 @@ internal class StakingModel @Inject constructor(
|
|||
stakingApproval = stakingApproval,
|
||||
stakingAllowance = stakingAllowance,
|
||||
yieldArgs = yield.args,
|
||||
),
|
||||
)
|
||||
).let(::add)
|
||||
if (BlockchainUtils.isSkipAmountEnter(uiState.value.cryptoCurrencyBlockchainId)) {
|
||||
ValidatorSelectChangeTransformer(
|
||||
selectedValidator = yield.preferredValidators.firstOrNull(),
|
||||
yield = yield,
|
||||
).let(::add)
|
||||
SetAmountDataTransformer(
|
||||
clickIntents = this@StakingModel,
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
userWalletProvider = Provider { userWallet },
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
).let(::add)
|
||||
AmountMaxValueStateTransformer(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
minimumTransactionAmount = minimumTransactionAmount,
|
||||
actionType = uiState.value.actionType,
|
||||
yield = yield,
|
||||
).let(::add)
|
||||
}
|
||||
}
|
||||
stateController.updateAll(*list.toTypedArray())
|
||||
}
|
||||
}
|
||||
stakingStateRouter.onNextClick()
|
||||
|
|
@ -400,7 +416,7 @@ internal class StakingModel @Inject constructor(
|
|||
StakingEvent.ShowAlert(StakingAlertUM.NoAvailableValidators),
|
||||
)
|
||||
} else {
|
||||
if (uiState.value.actionType == StakingActionCommonType.Enter) {
|
||||
if (uiState.value.actionType is StakingActionCommonType.Enter) {
|
||||
stateController.updateAll(
|
||||
ValidatorSelectChangeTransformer(
|
||||
selectedValidator = null,
|
||||
|
|
@ -636,16 +652,6 @@ internal class StakingModel @Inject constructor(
|
|||
} else {
|
||||
null
|
||||
}
|
||||
val validation = amount?.let {
|
||||
validateTransactionUseCase(
|
||||
userWalletId = userWalletId,
|
||||
amount = amount.convertToSdkAmount(cryptoCurrencyStatus.currency),
|
||||
fee = feeState?.fee,
|
||||
memo = null,
|
||||
destination = "",
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).leftOrNull()
|
||||
}
|
||||
|
||||
val balanceAfterTransaction = calculateBalanceAfterTransaction(
|
||||
amount = amount.orZero(),
|
||||
|
|
@ -666,7 +672,6 @@ internal class StakingModel @Inject constructor(
|
|||
appCurrencyProvider = Provider { appCurrency },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
currencyWarning = currencyWarning,
|
||||
validatorError = validation,
|
||||
currencyCheck = currencyStatus,
|
||||
isSubtractAvailable = isAmountSubtractAvailable,
|
||||
feeError = feeError,
|
||||
|
|
@ -715,7 +720,7 @@ internal class StakingModel @Inject constructor(
|
|||
): BigDecimal? {
|
||||
// TODO split for different networks
|
||||
val subtractedBalanceAmount = when (actionType) {
|
||||
StakingActionCommonType.Enter -> checkAndCalculateSubtractedAmount(
|
||||
is StakingActionCommonType.Enter -> checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
amountValue = amount.orZero(),
|
||||
|
|
@ -1024,7 +1029,7 @@ internal class StakingModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun isExplicitExit(balanceType: BalanceType, pendingAction: PendingAction?): Boolean {
|
||||
return balanceType == BalanceType.STAKED && pendingAction?.type != StakingActionType.RESTAKE
|
||||
return balanceType == BalanceType.STAKED && pendingAction?.type?.isRestake == false
|
||||
}
|
||||
|
||||
private fun isAssentState(): Boolean {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,14 @@ internal object StakingNotification {
|
|||
title = resourceReference(R.string.common_error),
|
||||
subtitle = subtitle,
|
||||
)
|
||||
|
||||
data class CardanoMinimumBalance(
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference,
|
||||
) : StakingNotification.Error(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Warning(
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ internal class StakingStateController @Inject constructor(
|
|||
isBalanceHidden = false,
|
||||
event = consumedEvent(),
|
||||
bottomSheetConfig = null,
|
||||
actionType = StakingActionCommonType.Enter,
|
||||
actionType = StakingActionCommonType.Enter(skipEnterAmount = false),
|
||||
buttonsState = NavigationButtonsState.Empty,
|
||||
balanceState = null,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@ internal class StakingStateRouter(
|
|||
fun onNextClick() {
|
||||
when (stateController.value.currentStep) {
|
||||
StakingStep.InitialInfo -> when (val actionType = stateController.value.actionType) {
|
||||
StakingActionCommonType.Enter -> showAmount()
|
||||
is StakingActionCommonType.Exit -> if (actionType.partiallyUnstakeDisabled) {
|
||||
showConfirmation()
|
||||
} else {
|
||||
showAmount()
|
||||
is StakingActionCommonType.Enter -> {
|
||||
if (actionType.skipEnterAmount) showConfirmation() else showAmount()
|
||||
}
|
||||
is StakingActionCommonType.Exit -> {
|
||||
if (actionType.partiallyUnstakeDisabled) showConfirmation() else showAmount()
|
||||
}
|
||||
StakingActionCommonType.Pending.Other,
|
||||
StakingActionCommonType.Pending.Rewards,
|
||||
|
|
@ -53,7 +53,9 @@ internal class StakingStateRouter(
|
|||
-> showInitial()
|
||||
StakingStep.Confirmation -> {
|
||||
when (val actionType = uiState.actionType) {
|
||||
StakingActionCommonType.Enter -> showAmount()
|
||||
is StakingActionCommonType.Enter -> {
|
||||
if (actionType.skipEnterAmount) showInitial() else showAmount()
|
||||
}
|
||||
is StakingActionCommonType.Pending -> showInitial()
|
||||
is StakingActionCommonType.Exit -> {
|
||||
if (actionType.partiallyUnstakeDisabled) showInitial() else showAmount()
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
|
|||
|
||||
val validatorAddress = validatorState.chosenValidator.address
|
||||
|
||||
val isEnter = state.actionType == StakingActionCommonType.Enter
|
||||
val isEnter = state.actionType is StakingActionCommonType.Enter
|
||||
val isApprovalNeeded = confirmationState.isApprovalNeeded
|
||||
val isAllowanceNotEnough = confirmationState.allowance < amount
|
||||
if (isEnter && isApprovalNeeded && isAllowanceNotEnough) {
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
val txUrl = getExplorerTransactionUrlUseCase(
|
||||
txHash = transactionHashes.last(),
|
||||
networkId = cryptoCurrencyStatus.currency.network.id,
|
||||
).getOrElse { "" }
|
||||
).getOrNull() ?: ""
|
||||
|
||||
balanceUpdater.fullUpdate()
|
||||
onSendSuccess(txUrl)
|
||||
|
|
@ -280,7 +280,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
private fun getAmount(amountState: AmountState.Data, fee: Fee, reduceAmountBy: BigDecimal?): BigDecimal {
|
||||
val amountValue = amountState.amountTextField.cryptoAmount.value ?: error("No amount value")
|
||||
val feeValue = fee.amount.value ?: error("No fee value")
|
||||
val isEnterAction = stateController.value.actionType == StakingActionCommonType.Enter
|
||||
val isEnterAction = stateController.value.actionType is StakingActionCommonType.Enter
|
||||
|
||||
return checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable && isEnterAction,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ internal class SetAmountDataTransformer(
|
|||
stringReference(userWalletProvider().name)
|
||||
}
|
||||
val cryptoBalanceValue = cryptoCurrencyStatusProvider().value
|
||||
val (amount, fiatAmount) = if (prevState.actionType != StakingActionCommonType.Enter) {
|
||||
val (amount, fiatAmount) = if (prevState.actionType !is StakingActionCommonType.Enter) {
|
||||
prevState.balanceState?.cryptoAmount to prevState.balanceState?.fiatAmount
|
||||
} else {
|
||||
cryptoBalanceValue.amount to cryptoBalanceValue.fiatAmount
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.getPendingActionTitle
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -37,7 +38,7 @@ internal class SetButtonsStateTransformer(
|
|||
return prevState.copy(buttonsState = buttonsState)
|
||||
}
|
||||
|
||||
private fun getPrimaryButton(prevState: StakingUiState): NavigationButton {
|
||||
private fun getPrimaryButton(prevState: StakingUiState): NavigationButton? {
|
||||
val confirmState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val innerConfirmState = confirmState?.innerState
|
||||
|
||||
|
|
@ -54,7 +55,7 @@ internal class SetButtonsStateTransformer(
|
|||
showProgress = isInProgress,
|
||||
isEnabled = prevState.isButtonEnabled(),
|
||||
onClick = { prevState.onPrimaryClick() },
|
||||
)
|
||||
).takeIf { prevState.isPrimaryButtonVisible() }
|
||||
}
|
||||
|
||||
private fun getPrevButton(prevState: StakingUiState): NavigationButton? {
|
||||
|
|
@ -125,7 +126,7 @@ internal class SetButtonsStateTransformer(
|
|||
resourceReference(R.string.common_close)
|
||||
} else {
|
||||
when (actionType) {
|
||||
StakingActionCommonType.Enter -> {
|
||||
is StakingActionCommonType.Enter -> {
|
||||
val amount = amountState.amountTextField.cryptoAmount.value.orZero()
|
||||
if (confirmationState.isApprovalNeeded && confirmationState.allowance < amount) {
|
||||
resourceReference(R.string.give_permission_title)
|
||||
|
|
@ -162,7 +163,7 @@ internal class SetButtonsStateTransformer(
|
|||
clickIntents.onNextClick()
|
||||
} else {
|
||||
val amount = amountState.amountTextField.cryptoAmount.value.orZero()
|
||||
val isEnterAction = actionType == StakingActionCommonType.Enter
|
||||
val isEnterAction = actionType is StakingActionCommonType.Enter
|
||||
if (isEnterAction && confirmationState.isApprovalNeeded && confirmationState.allowance < amount) {
|
||||
clickIntents.showApprovalBottomSheet()
|
||||
} else {
|
||||
|
|
@ -185,6 +186,14 @@ internal class SetButtonsStateTransformer(
|
|||
-> true
|
||||
}
|
||||
|
||||
private fun StakingUiState.isPrimaryButtonVisible(): Boolean {
|
||||
val initialState = initialInfoState as? StakingStates.InitialInfoState.Data
|
||||
val hasNotStaking = initialState?.yieldBalance == InnerYieldBalanceState.Empty
|
||||
val isCardano = BlockchainUtils.isCardano(cryptoCurrencyBlockchainId)
|
||||
|
||||
return hasNotStaking || !(isCardano && currentStep == StakingStep.InitialInfo)
|
||||
}
|
||||
|
||||
private fun StakingUiState.isButtonEnabled(): Boolean {
|
||||
return when (currentStep) {
|
||||
StakingStep.InitialInfo -> initialInfoState.isPrimaryButtonEnabled
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.tangem.utils.transformer.Transformer
|
|||
internal object SetConfirmationStateEmptyTransformer : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
return prevState.copy(
|
||||
actionType = StakingActionCommonType.Enter,
|
||||
actionType = StakingActionCommonType.Enter(skipEnterAmount = false),
|
||||
validatorState = StakingStates.ValidatorState.Empty(),
|
||||
confirmationState = StakingStates.ConfirmationState.Empty(),
|
||||
balanceState = null,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.features.staking.impl.presentation.state.*
|
|||
import com.tangem.features.staking.impl.presentation.state.utils.isCompositePendingActions
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.isTronStakedBalance
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isSkipAmountEnter
|
||||
import com.tangem.utils.extensions.isPositive
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -44,11 +45,17 @@ internal class SetConfirmationStateInitTransformer(
|
|||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val actionType = when {
|
||||
isEnter -> StakingActionCommonType.Enter
|
||||
isImplicitExit || isExplicitExit -> StakingActionCommonType.Exit(isPartialUnstakeDisabled(prevState))
|
||||
isEnter -> StakingActionCommonType.Enter(
|
||||
skipEnterAmount = isSkipAmountEnter(prevState.cryptoCurrencyBlockchainId),
|
||||
)
|
||||
isImplicitExit || isExplicitExit -> StakingActionCommonType.Exit(isPartiallyUnstakeDisabled(prevState))
|
||||
else -> when (pendingAction?.type) {
|
||||
StakingActionType.STAKE -> StakingActionCommonType.Enter
|
||||
StakingActionType.UNSTAKE -> StakingActionCommonType.Exit(isPartialUnstakeDisabled(prevState))
|
||||
StakingActionType.STAKE -> StakingActionCommonType.Enter(
|
||||
skipEnterAmount = isSkipAmountEnter(prevState.cryptoCurrencyBlockchainId),
|
||||
)
|
||||
StakingActionType.UNSTAKE -> StakingActionCommonType.Exit(
|
||||
partiallyUnstakeDisabled = isPartiallyUnstakeDisabled(prevState),
|
||||
)
|
||||
StakingActionType.CLAIM_REWARDS,
|
||||
StakingActionType.RESTAKE_REWARDS,
|
||||
-> StakingActionCommonType.Pending.Rewards
|
||||
|
|
@ -71,7 +78,7 @@ internal class SetConfirmationStateInitTransformer(
|
|||
transactionDoneState = TransactionDoneState.Empty,
|
||||
isApprovalNeeded = stakingApproval is StakingApproval.Needed,
|
||||
allowance = stakingAllowance,
|
||||
isAmountEditable = actionType == StakingActionCommonType.Enter ||
|
||||
isAmountEditable = actionType is StakingActionCommonType.Enter ||
|
||||
actionType is StakingActionCommonType.Exit &&
|
||||
!actionType.partiallyUnstakeDisabled,
|
||||
reduceAmountBy = null,
|
||||
|
|
@ -81,7 +88,7 @@ internal class SetConfirmationStateInitTransformer(
|
|||
)
|
||||
}
|
||||
|
||||
private fun isPartialUnstakeDisabled(state: StakingUiState): Boolean {
|
||||
private fun isPartiallyUnstakeDisabled(state: StakingUiState): Boolean {
|
||||
val isSolana = BlockchainUtils.isSolana(state.cryptoCurrencyBlockchainId)
|
||||
val isValidatorPreferred = balanceState?.validator?.preferred == true
|
||||
if (isSolana && !isValidatorPreferred) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import com.tangem.common.ui.amountScreen.models.AmountState
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
|
|
@ -35,14 +36,15 @@ internal class SetConfirmationStateLoadingTransformer(
|
|||
private fun getFooter(state: StakingUiState): TextReference {
|
||||
val amountState = state.amountState as? AmountState.Data
|
||||
|
||||
val isEnterAction = state.actionType == StakingActionCommonType.Enter
|
||||
val isEnterAction = state.actionType is StakingActionCommonType.Enter
|
||||
|
||||
val amountDecimal = amountState?.amountTextField?.fiatAmount?.value
|
||||
val amountValue = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = amountDecimal,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
val amountValue = amountDecimal.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
}
|
||||
val rewardSchedule = getRewardScheduleText(
|
||||
rewardSchedule = yield.metadata.rewardSchedule,
|
||||
networkId = cryptoCurrency.network.id.value,
|
||||
|
|
|
|||
|
|
@ -1,30 +1,33 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.remove
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountStateConverter
|
||||
import com.tangem.common.ui.amountScreen.models.AmountParameters
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceItem
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStep
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
||||
import com.tangem.features.staking.impl.presentation.state.converters.RewardsValidatorStateConverter
|
||||
import com.tangem.features.staking.impl.presentation.state.converters.YieldBalancesConverter
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.getRewardScheduleText
|
||||
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isPolkadot
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
|
@ -145,7 +148,8 @@ internal class SetInitialDataStateTransformer(
|
|||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): RoundedListWithDividersItemData? {
|
||||
val minimumCryptoAmount = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum ?: return null
|
||||
if (!isPolkadot(cryptoCurrencyStatus.currency.network.id.value)) return null
|
||||
val blockchainId = cryptoCurrencyStatus.currency.network.id.value
|
||||
if (!showMinimumRequirementInfo(blockchainId)) return null
|
||||
|
||||
val formattedAmount = minimumCryptoAmount.format { crypto(cryptoCurrencyStatus.currency) }
|
||||
|
||||
|
|
@ -239,6 +243,10 @@ internal class SetInitialDataStateTransformer(
|
|||
return resourceReference(R.string.common_range, wrappedList(formattedMinApr, formattedMaxApr))
|
||||
}
|
||||
|
||||
private fun showMinimumRequirementInfo(blockchainId: String): Boolean {
|
||||
return blockchainId == Blockchain.Polkadot.id || blockchainId == Blockchain.Cardano.id
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val EQUALITY_THRESHOLD = BigDecimal(1E-10)
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ internal object SetTitleTransformer : Transformer<StakingUiState> {
|
|||
|
||||
StakingStep.Confirmation -> {
|
||||
when (actionType) {
|
||||
StakingActionCommonType.Enter -> resourceReference(
|
||||
is StakingActionCommonType.Enter -> resourceReference(
|
||||
R.string.staking_title_stake,
|
||||
wrappedList(prevState.cryptoCurrencyName),
|
||||
)
|
||||
|
|
@ -53,7 +53,7 @@ internal object SetTitleTransformer : Transformer<StakingUiState> {
|
|||
}
|
||||
}
|
||||
|
||||
val subtitle = if (currentStep == StakingStep.Confirmation && actionType == StakingActionCommonType.Enter) {
|
||||
val subtitle = if (currentStep == StakingStep.Confirmation && actionType is StakingActionCommonType.Enter) {
|
||||
stringReference(prevState.walletName)
|
||||
} else {
|
||||
null
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
|||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTron
|
||||
import com.tangem.utils.extensions.isPositive
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -57,7 +58,7 @@ internal class AmountRequirementStateTransformer(
|
|||
amountState.amountTextField.isError -> amountState.amountTextField.error
|
||||
requirementError != null -> requirementError
|
||||
isIntegerOnlyError -> when (actionType) {
|
||||
StakingActionCommonType.Enter -> resourceReference(
|
||||
is StakingActionCommonType.Enter -> resourceReference(
|
||||
R.string.staking_amount_tron_integer_error,
|
||||
wrappedList(roundedDownCrypto),
|
||||
)
|
||||
|
|
@ -92,7 +93,7 @@ internal class AmountRequirementStateTransformer(
|
|||
if (isAlreadyErrorState || isAmountZero) return null
|
||||
|
||||
return when (actionType) {
|
||||
StakingActionCommonType.Enter -> {
|
||||
is StakingActionCommonType.Enter -> {
|
||||
val enterRequirements = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]
|
||||
enterRequirements?.getError(amountDecimal, R.string.staking_amount_requirement_error)
|
||||
}
|
||||
|
|
@ -107,7 +108,7 @@ internal class AmountRequirementStateTransformer(
|
|||
private fun isIntegerOnlyError(amountState: AmountState.Data, actionType: StakingActionCommonType): Boolean {
|
||||
val cryptoAmountValue = amountState.amountTextField.cryptoAmount.value ?: return false
|
||||
|
||||
val isEnterOrExit = actionType == StakingActionCommonType.Enter || actionType is StakingActionCommonType.Exit
|
||||
val isEnterOrExit = actionType is StakingActionCommonType.Enter || actionType is StakingActionCommonType.Exit
|
||||
val isTron = isTron(cryptoCurrencyStatus.currency.network.id.value)
|
||||
|
||||
val isIntegerOnly = cryptoAmountValue.isZero() || cryptoAmountValue.remainder(BigDecimal.ONE).isZero()
|
||||
|
|
@ -116,17 +117,30 @@ internal class AmountRequirementStateTransformer(
|
|||
}
|
||||
|
||||
private fun AddressArgument.getError(amount: BigDecimal, @StringRes errorTextRes: Int): TextReference? {
|
||||
val isExceedsRequirements = maximum?.compareTo(amount) == -1 ||
|
||||
minimum?.compareTo(amount) == 1
|
||||
val isExceedsMinRequirement = minimum?.compareTo(amount) == 1
|
||||
val isExceedsMaxRequirement = if (maximum?.isPositive() == true) {
|
||||
maximum?.compareTo(amount) == -1
|
||||
} else {
|
||||
cryptoCurrencyStatus.value.amount?.compareTo(amount) == 1
|
||||
}
|
||||
|
||||
return resourceReference(
|
||||
errorTextRes,
|
||||
wrappedList(
|
||||
val errorText = when {
|
||||
isExceedsMinRequirement -> {
|
||||
minimum.format {
|
||||
crypto(cryptoCurrencyStatus.currency)
|
||||
},
|
||||
),
|
||||
).takeIf { required && isExceedsRequirements }
|
||||
}
|
||||
}
|
||||
isExceedsMaxRequirement -> {
|
||||
maximum.format {
|
||||
crypto(cryptoCurrencyStatus.currency)
|
||||
}
|
||||
}
|
||||
else -> ""
|
||||
}
|
||||
return resourceReference(
|
||||
errorTextRes,
|
||||
wrappedList(errorText),
|
||||
).takeIf { required && (isExceedsMinRequirement || isExceedsMaxRequirement) }
|
||||
}
|
||||
|
||||
data class Data(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.notifications
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification
|
||||
|
|
@ -11,7 +10,6 @@ import com.tangem.common.ui.notifications.NotificationsFactory.addFeeUnreachable
|
|||
import com.tangem.common.ui.notifications.NotificationsFactory.addRentExemptionNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountErrorNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
|
|
@ -40,7 +38,6 @@ internal class AddStakingNotificationsTransformer(
|
|||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
private val currencyWarning: CryptoCurrencyWarning?,
|
||||
private val validatorError: Throwable?,
|
||||
private val feeError: GetFeeError?,
|
||||
private val currencyCheck: CryptoCurrencyCheck,
|
||||
private val isSubtractAvailable: Boolean,
|
||||
|
|
@ -65,7 +62,7 @@ internal class AddStakingNotificationsTransformer(
|
|||
val feeValue = feeState?.fee?.amount?.value.orZero()
|
||||
val reduceAmountBy = confirmationState.reduceAmountBy.orZero()
|
||||
|
||||
val isEnterAction = prevState.actionType == StakingActionCommonType.Enter
|
||||
val isEnterAction = prevState.actionType is StakingActionCommonType.Enter
|
||||
val isFeeCoverage = checkFeeCoverage(
|
||||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
|
|
@ -81,7 +78,7 @@ internal class AddStakingNotificationsTransformer(
|
|||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
reduceAmountBy = reduceAmountBy,
|
||||
).max(minimumRequirement)
|
||||
)
|
||||
} else {
|
||||
// No amount is taken from account balance on exit or pending actions
|
||||
BigDecimal.ZERO
|
||||
|
|
@ -212,15 +209,6 @@ internal class AddStakingNotificationsTransformer(
|
|||
appCurrency = appCurrency,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
)
|
||||
|
||||
// blockchain specific
|
||||
addValidateTransactionNotifications(
|
||||
dustValue = currencyCheck.dustValue.orZero(),
|
||||
minAdaValue = (feeState?.fee as? Fee.CardanoToken)?.minAdaValue,
|
||||
validationError = validatorError,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
onReduceClick = prevState.clickIntents::onAmountReduceToClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addStakeExceedBalanceNotification(
|
||||
|
|
@ -236,7 +224,7 @@ internal class AddStakingNotificationsTransformer(
|
|||
|
||||
val showNotification = sendingAmount + feeAmount > balance
|
||||
if (showNotification) {
|
||||
val notification = if (actionType == StakingActionCommonType.Enter) {
|
||||
val notification = if (actionType is StakingActionCommonType.Enter) {
|
||||
NotificationUM.Error.TotalExceedsBalance
|
||||
} else {
|
||||
with(cryptoCurrencyStatus.currency) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.tangem.features.staking.impl.R
|
|||
import com.tangem.features.staking.impl.presentation.state.StakingNotification
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isCardano
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isCosmos
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTron
|
||||
import com.tangem.utils.Provider
|
||||
|
|
@ -44,9 +45,12 @@ internal class StakingInfoNotificationsFactory(
|
|||
addStakingLowBalanceNotification(prevState, actionAmount)
|
||||
|
||||
when (prevState.actionType) {
|
||||
StakingActionCommonType.Enter -> addEnterInfoNotifications(sendingAmount, feeValue)
|
||||
is StakingActionCommonType.Enter -> addEnterInfoNotifications(sendingAmount, feeValue)
|
||||
is StakingActionCommonType.Exit -> addExitInfoNotifications()
|
||||
is StakingActionCommonType.Pending -> addPendingInfoNotifications(prevState)
|
||||
is StakingActionCommonType.Pending -> {
|
||||
addCardanoRestakeMinimumAmountNotification(feeValue)
|
||||
addPendingInfoNotifications(prevState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +74,9 @@ internal class StakingInfoNotificationsFactory(
|
|||
sendingAmount: BigDecimal,
|
||||
feeValue: BigDecimal,
|
||||
) {
|
||||
addCardanoStakeMinimumAmountNotification(feeValue)
|
||||
addTronRevoteNotification()
|
||||
addCardanoStakeNotification()
|
||||
addStakingEntireBalanceNotification(sendingAmount, feeValue)
|
||||
}
|
||||
|
||||
|
|
@ -149,6 +155,48 @@ internal class StakingInfoNotificationsFactory(
|
|||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addCardanoStakeNotification() {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val isCardano = isCardano(cryptoCurrencyStatus.currency.network.id.value)
|
||||
|
||||
if (isCardano) {
|
||||
add(
|
||||
StakingNotification.Info.Ordinary(
|
||||
title = resourceReference(R.string.staking_notification_additional_ada_deposit_title),
|
||||
text = resourceReference(R.string.staking_notification_additional_ada_deposit_text),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addCardanoStakeMinimumAmountNotification(feeValue: BigDecimal) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val isCardano = isCardano(cryptoCurrencyStatus.currency.network.id.value)
|
||||
val balance = cryptoCurrencyStatus.value.amount.orZero()
|
||||
if (isCardano && balance - feeValue < MINIMUM_STAKE_BALANCE) {
|
||||
add(
|
||||
StakingNotification.Error.CardanoMinimumBalance(
|
||||
title = resourceReference(R.string.staking_notification_minimum_balance_title),
|
||||
subtitle = resourceReference(R.string.staking_notification_minimum_stake_ada_text),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addCardanoRestakeMinimumAmountNotification(feeValue: BigDecimal) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val isCardano = isCardano(cryptoCurrencyStatus.currency.network.id.value)
|
||||
val balance = cryptoCurrencyStatus.value.amount.orZero()
|
||||
if (isCardano && balance - feeValue < MINIMUM_RESTAKE_BALANCE) {
|
||||
add(
|
||||
StakingNotification.Error.CardanoMinimumBalance(
|
||||
title = resourceReference(R.string.staking_notification_minimum_restake_ada_title),
|
||||
subtitle = resourceReference(R.string.staking_notification_minimum_restake_ada_text),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addStakingEntireBalanceNotification(
|
||||
sendingAmount: BigDecimal,
|
||||
feeValue: BigDecimal,
|
||||
|
|
@ -158,7 +206,7 @@ internal class StakingInfoNotificationsFactory(
|
|||
|
||||
val isEntireBalance = sendingAmount.plus(feeValue) == balance
|
||||
|
||||
if (isEntireBalance && isSubtractAvailable) {
|
||||
if (isEntireBalance && isSubtractAvailable && !isCardano(cryptoCurrencyStatus.currency.network.id.value)) {
|
||||
add(StakingNotification.Info.StakeEntireBalance)
|
||||
}
|
||||
}
|
||||
|
|
@ -179,4 +227,9 @@ internal class StakingInfoNotificationsFactory(
|
|||
add(StakingNotification.Warning.LowStakedBalance)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val MINIMUM_STAKE_BALANCE = "5".toBigDecimal()
|
||||
val MINIMUM_RESTAKE_BALANCE = "3".toBigDecimal()
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ internal class ValidatorSelectChangeTransformer(
|
|||
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
|
||||
val isRestake = prevState.actionType == StakingActionCommonType.Pending.Restake
|
||||
val isEnter = prevState.actionType == StakingActionCommonType.Enter
|
||||
val isEnter = prevState.actionType is StakingActionCommonType.Enter
|
||||
val isFromInfoScreen = prevState.currentStep == StakingStep.InitialInfo
|
||||
val isVoteLocked = confirmationState?.pendingAction?.type == StakingActionType.VOTE_LOCKED
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.domain.staking.model.stakekit.PendingAction
|
|||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isBSC
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isCardano
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTron
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -37,14 +38,12 @@ internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (t
|
|||
internal fun isSingleAction(networkId: String, activeStake: BalanceState): Boolean {
|
||||
val isSingleAction = activeStake.pendingActions.size <= 1 // Either single or none pending actions
|
||||
val isCompositePendingActions = isCompositePendingActions(networkId, activeStake.pendingActions)
|
||||
val isBscRestake = isBSC(networkId) && activeStake.pendingActions.any {
|
||||
it.type == StakingActionType.RESTAKE
|
||||
}
|
||||
val isRestake = activeStake.pendingActions.any { it.type.isRestake }
|
||||
|
||||
return isSingleAction && !isBscRestake || isCompositePendingActions
|
||||
return isSingleAction && !isRestake || isCompositePendingActions
|
||||
}
|
||||
|
||||
internal fun withStubUnstakeAction(networkId: String, activeStake: BalanceState) = if (isBSC(networkId)) {
|
||||
internal fun withStubUnstakeAction(networkId: String, activeStake: BalanceState) = if (isStubUnstakeAction(networkId)) {
|
||||
activeStake.pendingActions.plus(
|
||||
PendingAction(
|
||||
type = StakingActionType.UNSTAKE,
|
||||
|
|
@ -65,4 +64,8 @@ internal fun isCompositePendingActions(networkId: String, pendingActions: Immuta
|
|||
isSolana(networkId) -> pendingActions?.any { it.type == StakingActionType.WITHDRAW } == true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isStubUnstakeAction(networkId: String): Boolean {
|
||||
return isBSC(networkId) || isCardano(networkId)
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import androidx.compose.animation.AnimatedVisibility
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
|
|
@ -72,6 +73,7 @@ internal fun StakingConfirmationContent(
|
|||
)
|
||||
StakingFeeBlock(feeState = state.feeState, isTransactionSent = isTransactionSent)
|
||||
NotificationsBlock(notifications = state.notifications)
|
||||
Spacer(Modifier)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue