Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-24 16:13:32 +05:00
parent 74f71080ea
commit 43975742d5
19 changed files with 93 additions and 53 deletions

View file

@ -8,7 +8,7 @@ sealed class NavigationButtonsState {
data object Empty : NavigationButtonsState()
data class Data(
val primaryButton: NavigationButton,
val primaryButton: NavigationButton?,
val prevButton: NavigationButton?,
val extraButtons: ImmutableList<NavigationButton>,
val txUrl: String? = null,

View file

@ -237,7 +237,7 @@ internal class DefaultStakingRepository(
): StakingAction {
return withContext(dispatchers.io) {
val response = when (params.actionCommonType) {
StakingActionCommonType.Enter -> stakeKitApi.createEnterAction(
is StakingActionCommonType.Enter -> stakeKitApi.createEnterAction(
createActionRequestBody(
userWalletId,
network,
@ -267,7 +267,7 @@ internal class DefaultStakingRepository(
): StakingGasEstimate {
return withContext(dispatchers.io) {
val gasEstimateDTO = when (params.actionCommonType) {
StakingActionCommonType.Enter -> stakeKitApi.estimateGasOnEnter(
is StakingActionCommonType.Enter -> stakeKitApi.estimateGasOnEnter(
createActionRequestBody(
userWalletId,
network,
@ -582,13 +582,14 @@ internal class DefaultStakingRepository(
-> TransactionData.Compiled.Data.Bytes(unsignedTransaction.hexToBytes())
Blockchain.BSC,
Blockchain.Ethereum,
Blockchain.TON,
Blockchain.Cardano,
-> TransactionData.Compiled.Data.RawString(unsignedTransaction)
Blockchain.Tron -> {
val tronStakeKitTransaction = tronStakeKitTransactionAdapter.fromJson(unsignedTransaction)
?: error("Failed to parse Tron StakeKit transaction")
TransactionData.Compiled.Data.RawString(tronStakeKitTransaction.rawDataHex)
}
Blockchain.TON -> TransactionData.Compiled.Data.RawString(unsignedTransaction)
else -> error("Unsupported blockchain")
}
}
@ -647,6 +648,7 @@ internal class DefaultStakingRepository(
const val KAVA_INTEGRATION_ID = "kava-kava-native-staking"
const val NEAR_INTEGRATION_ID = "near-near-native-staking"
const val TEZOS_INTEGRATION_ID = "tezos-xtz-native-staking"
const val CARDANO_INTEGRATION_ID = "cardano-ada-native-staking"
const val ETHEREUM_POLYGON_APPROVE_SPENDER = "0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908"
@ -669,6 +671,7 @@ internal class DefaultStakingRepository(
// Blockchain.Kava.run { id + toCoinId() } to KAVA_INTEGRATION_ID,
// Blockchain.Near.run { id + toCoinId() } to NEAR_INTEGRATION_ID,
// Blockchain.Tezos.run { id + toCoinId() } to TEZOS_INTEGRATION_ID,
Blockchain.Cardano.run { id + toCoinId() } to CARDANO_INTEGRATION_ID,
)
}
}

View file

@ -2,7 +2,7 @@ package com.tangem.domain.staking.model.stakekit.action
sealed class StakingActionCommonType {
data object Enter : StakingActionCommonType()
data class Enter(val skipEnterAmount: Boolean) : StakingActionCommonType()
data class Exit(val partiallyUnstakeDisabled: Boolean) : StakingActionCommonType()
sealed class Pending : StakingActionCommonType() {
data object Restake : Pending()

View file

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

View file

@ -397,7 +397,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,
@ -712,7 +712,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(),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -44,11 +44,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 = isAmountEnterSkipped(prevState),
)
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 = isAmountEnterSkipped(prevState),
)
StakingActionType.UNSTAKE -> StakingActionCommonType.Exit(
partiallyUnstakeDisabled = isPartiallyUnstakeDisabled(prevState),
)
StakingActionType.CLAIM_REWARDS,
StakingActionType.RESTAKE_REWARDS,
-> StakingActionCommonType.Pending.Rewards
@ -71,7 +77,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 +87,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) {
@ -94,4 +100,8 @@ internal class SetConfirmationStateInitTransformer(
val max = exitAmount.maximum ?: return false
return !min.isPositive() && !max.isPositive()
}
private fun isAmountEnterSkipped(state: StakingUiState): Boolean {
return BlockchainUtils.isCardano(state.cryptoCurrencyBlockchainId)
}
}

View file

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

View file

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

View file

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

View file

@ -65,7 +65,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,
@ -236,7 +236,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) {

View file

@ -44,7 +44,7 @@ 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)
}

View file

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