Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-11 11:40:39 +02:00
parent f8a9cac801
commit f72c0f448e
13 changed files with 409 additions and 116 deletions

View file

@ -38,7 +38,6 @@ class AmountStateConverter(
private val iconStateConverter: CryptoCurrencyToIconStateConverter,
private val isBalanceHidden: Boolean,
private val accountTitleUM: AccountTitleUM,
private val isMaxButtonVisible: Boolean = true,
) : Converter<AmountParameters, AmountState> {
private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) {
@ -73,7 +72,6 @@ class AmountStateConverter(
amountTextField = amountFieldConverter.convert(value.value),
isPrimaryButtonEnabled = false,
appCurrency = appCurrency,
isMaxButtonVisible = isMaxButtonVisible,
)
}
}

View file

@ -24,7 +24,6 @@ sealed class AmountState {
* @param isEditingDisabled indicated whether amount is editable
* @param reduceAmountBy reduces amount to be sent by specified value
* @param isIgnoreReduce ignores reduce amount value
* @param isMaxButtonVisible indicates whether the "Max" button is shown
*/
data class Data(
override val isPrimaryButtonEnabled: Boolean,
@ -38,7 +37,6 @@ sealed class AmountState {
val isEditingDisabled: Boolean = false,
val reduceAmountBy: BigDecimal = BigDecimal.ZERO,
val isIgnoreReduce: Boolean = false,
val isMaxButtonVisible: Boolean = true,
) : AmountState()
data object Empty : AmountState() {

View file

@ -88,7 +88,6 @@ internal fun LazyListScope.amountFieldV2(
@Composable
private fun AmountInfo(amountUM: AmountState, onMaxAmountClick: () -> Unit, modifier: Modifier = Modifier) {
val tokenIconState = (amountUM as? AmountState.Data)?.tokenIconState ?: CurrencyIconState.Loading
val isMaxButtonVisible = (amountUM as? AmountState.Data)?.isMaxButtonVisible != false
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
@ -107,7 +106,6 @@ private fun AmountInfo(amountUM: AmountState, onMaxAmountClick: () -> Unit, modi
amountUM = amountUM,
modifier = Modifier.weight(1f),
)
if (isMaxButtonVisible) {
Text(
text = stringResourceSafe(R.string.send_max_amount),
style = TangemTheme.typography.caption1,
@ -126,7 +124,6 @@ private fun AmountInfo(amountUM: AmountState, onMaxAmountClick: () -> Unit, modi
)
}
}
}
@Composable
private fun AmountInfoMain(amountUM: AmountState, modifier: Modifier = Modifier) {

View file

@ -1,62 +0,0 @@
package com.tangem.common.ui.amountScreen.converters
import com.google.common.truth.Truth.assertThat
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
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.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Test
import java.math.BigDecimal
internal class AmountStateConverterTest {
private val currency = mockk<CryptoCurrency.Coin> {
every { symbol } returns "ETH"
every { decimals } returns 18
every { name } returns "Ethereum"
}
private val status = CryptoCurrencyStatus(currency = currency, value = mockk(relaxed = true))
private val iconStateConverter = mockk<CryptoCurrencyToIconStateConverter> {
every { convert(any<CryptoCurrency>()) } returns CurrencyIconState.Loading
}
private val clickIntents = mockk<AmountScreenClickIntents>(relaxed = true)
private val accountTitleUM = mockk<AccountTitleUM>()
private fun convert(isMaxButtonVisible: Boolean = true): AmountState = AmountStateConverter(
clickIntents = clickIntents,
appCurrency = AppCurrency.Default,
cryptoCurrencyStatus = status,
maxEnterAmount = EnterAmountBoundary(
amount = BigDecimal.ONE,
fiatAmount = BigDecimal.TEN,
fiatRate = BigDecimal.ONE,
),
iconStateConverter = iconStateConverter,
isBalanceHidden = false,
accountTitleUM = accountTitleUM,
isMaxButtonVisible = isMaxButtonVisible,
).convert(AmountParameters(title = stringReference("Wallet"), value = ""))
@Test
fun `GIVEN no isMaxButtonVisible param WHEN convert THEN Data isMaxButtonVisible is true`() {
val result = convert()
assertThat((result as AmountState.Data).isMaxButtonVisible).isTrue()
}
@Test
fun `GIVEN isMaxButtonVisible false WHEN convert THEN Data isMaxButtonVisible is false`() {
val result = convert(isMaxButtonVisible = false)
assertThat((result as AmountState.Data).isMaxButtonVisible).isFalse()
}
}

View file

@ -181,6 +181,19 @@ sealed class StakingAnalyticsEvent(
AnalyticsParam.BLOCKCHAIN to blockchain,
),
)
data class SumLimitError(
val token: String,
val blockchain: String,
val maxAmount: String,
) : StakingAnalyticsEvent(
event = "Error - Sum Limit",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
AnalyticsParam.BLOCKCHAIN to blockchain,
AnalyticsParam.ERROR_MESSAGE to "Maximum amount: $maxAmount",
),
)
}
enum class StakeScreenSource {

View file

@ -97,6 +97,7 @@ import com.tangem.features.staking.impl.presentation.state.utils.withStubUnstake
import com.tangem.lib.crypto.BlockchainUtils.isTon
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
import com.tangem.utils.extensions.isPositive
import com.tangem.utils.extensions.isSingleItem
import com.tangem.utils.extensions.orZero
import com.tangem.utils.logging.TangemLogger
@ -642,6 +643,31 @@ internal class StakingModel @Inject constructor(
integration = integration,
),
)
checkSumLimitExceeded()
}
private fun checkSumLimitExceeded() {
val maxLimit = (integration as? P2PEthPoolIntegration)
?.enterArgs?.amountRequirement?.maximum
?.takeIf { it.isPositive() }
?: return
val enteredAmount = (uiState.value.amountState as? AmountState.Data)
?.amountTextField?.cryptoAmount?.value
?: return
if (enteredAmount > maxLimit) {
// Pass the max limit as a stable crypto-formatted value (e.g. "0.15 ETH"); the event
// builds the hardcoded English "Error Message" from it, so the model needs no resources.
val formattedMax = maxLimit.format { crypto(cryptoCurrencyStatus.currency) }
analyticsEventHandler.send(
StakingAnalyticsEvent.SumLimitError(
token = cryptoCurrencyStatus.currency.symbol,
blockchain = cryptoCurrencyStatus.currency.network.name,
maxAmount = formattedMax,
),
)
}
}
override fun onAmountPasteTriggerDismiss() {
@ -658,6 +684,7 @@ internal class StakingModel @Inject constructor(
integration = integration,
),
)
checkSumLimitExceeded()
}
override fun onCurrencyChangeClick(isFiat: Boolean) {
@ -1099,7 +1126,12 @@ internal class StakingModel @Inject constructor(
}
override fun showPrimaryClickAlert() {
messageSender.send(StakingAlertUM.stakeMoreClickUnavailable(cryptoCurrencyStatus.currency))
val message = if (integration is P2PEthPoolIntegration) {
StakingAlertUM.stakeMoreClickUnavailableNoTargets()
} else {
StakingAlertUM.stakeMoreClickUnavailable(cryptoCurrencyStatus.currency)
}
messageSender.send(message)
}
override fun onOpenLearnMoreAboutApproveClick() {

View file

@ -54,6 +54,11 @@ internal object StakingAlertUM {
),
)
fun stakeMoreClickUnavailableNoTargets(): DialogMessage = DialogMessage(
title = null,
message = resourceReference(R.string.staking_no_validators_error_message),
)
fun rewardsMinimumRequirementsError(cryptoCurrencyName: String, cryptoAmountValue: String): DialogMessage =
DialogMessage(
title = null,

View file

@ -19,7 +19,6 @@ import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.staking.StakingBalanceEntry
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.staking.model.P2PEthPoolIntegration
import com.tangem.domain.staking.model.Period
import com.tangem.domain.staking.model.StakingIntegration
import com.tangem.domain.staking.model.StakingTarget
@ -262,7 +261,6 @@ internal class SetInitialDataStateTransformer(
walletTitle = stringReference(userWalletProvider().name),
prefixText = resourceReference(R.string.common_from),
).convert(account),
isMaxButtonVisible = integration !is P2PEthPoolIntegration,
).convert(
AmountParameters(
title = stringReference(userWalletProvider().name),

View file

@ -96,12 +96,18 @@ internal class AmountRequirementStateTransformer(
return when (actionType) {
is StakingActionCommonType.Enter -> {
val enterRequirements = integration.enterArgs?.amountRequirement
enterRequirements?.getError(amountDecimal, R.string.staking_amount_requirement_error)
integration.enterArgs?.amountRequirement?.getError(
amount = amountDecimal,
minErrorRes = R.string.staking_amount_requirement_error,
maxErrorRes = R.string.staking_max_amount_requirement_error,
)
}
is StakingActionCommonType.Exit -> {
val exitRequirements = integration.exitArgs?.amountRequirement
exitRequirements?.getError(amountDecimal, R.string.staking_unstake_amount_requirement_error)
integration.exitArgs?.amountRequirement?.getError(
amount = amountDecimal,
minErrorRes = R.string.staking_unstake_amount_requirement_error,
maxErrorRes = R.string.staking_max_amount_requirement_error,
)
}
else -> null
}
@ -118,31 +124,25 @@ internal class AmountRequirementStateTransformer(
return isEnterOrExit && isTron && !isIntegerOnly
}
private fun StakingAmountRequirement.getError(amount: BigDecimal, @StringRes errorTextRes: Int): TextReference? {
private fun StakingAmountRequirement.getError(
amount: BigDecimal,
@StringRes minErrorRes: Int,
@StringRes maxErrorRes: Int,
): TextReference? {
if (!isRequired) return null
val isExceedsMinRequirement = minimum?.compareTo(amount) == 1
val isExceedsMaxRequirement = if (maximum?.isPositive() == true) {
maximum?.compareTo(amount) == -1
} else {
maxAmount.amount?.compareTo(amount) == -1
val effectiveMax = maximum?.takeIf { it.isPositive() } ?: maxAmount.amount
val isExceedsMaxRequirement = effectiveMax?.compareTo(amount) == -1
val (errorRes, boundary) = when {
isExceedsMinRequirement -> minErrorRes to minimum
isExceedsMaxRequirement -> maxErrorRes to effectiveMax
else -> return null
}
val errorText = when {
isExceedsMinRequirement -> {
minimum.format {
crypto(cryptoCurrencyStatus.currency)
}
}
isExceedsMaxRequirement -> {
maximum.format {
crypto(cryptoCurrencyStatus.currency)
}
}
else -> ""
}
return resourceReference(
errorTextRes,
wrappedList(errorText),
).takeIf { isRequired && (isExceedsMinRequirement || isExceedsMaxRequirement) }
val formatted = boundary.format { crypto(cryptoCurrencyStatus.currency) }
return resourceReference(errorRes, wrappedList(formatted))
}
data class Data(

View file

@ -0,0 +1,131 @@
package com.tangem.features.staking.impl.presentation.model
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent
import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import com.tangem.domain.staking.model.ethpool.VaultLimitInfo
import com.tangem.domain.tokens.model.Amount
import com.tangem.features.staking.impl.presentation.state.StakingStep
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.math.BigDecimal
/**
* Model-level tests for the P2P ETH pool staking integration:
* verifies that [StakingAnalyticsEvent.SumLimitError] is sent when the entered amount
* exceeds the vault's computed maximum (= limit totalAssets).
*
* Fixture:
* vault address = "0xabc" totalAssets = 5
* limit "0xabc" limit = 10
* maximum = 10 5 = 5.0 (scale=1, RoundingMode.FLOOR)
*/
@OptIn(ExperimentalCoroutinesApi::class)
internal class StakingModelP2PSumLimitTest : StakingModelTestBase() {
override val testIntegrationId: StakingIntegrationID = StakingIntegrationID.P2PEthPool
private val vaultAddress = "0xabc"
private val testVault = P2PEthPoolVault(
vaultAddress = vaultAddress,
displayName = "Test Vault",
apy = BigDecimal("4.5"),
baseApy = BigDecimal("4.0"),
capacity = BigDecimal("1000"),
totalAssets = BigDecimal("5"),
feePercent = BigDecimal("0.1"),
isPrivate = false,
isGenesis = false,
isSmoothingPool = false,
isErc20 = false,
tokenName = null,
tokenSymbol = null,
createdAt = 0L,
)
// maximum = 10 5 = 5.0 (FLOOR scale=1)
private val testLimits = mapOf(
vaultAddress to VaultLimitInfo(limit = BigDecimal("10"), coefficient = null),
)
@BeforeEach
fun setUpP2P() {
coEvery { p2pEthPoolRepository.getVaultsSync() } returns listOf(testVault)
coEvery { p2pEthPoolRepository.getVaultLimitsSyncOrNull() } returns testLimits
}
/**
* Helper: returns a [MutableStateFlow] whose value has [amountState] set to an
* [AmountState.Data] mock with the given [cryptoAmountValue].
* The flow is also wired to [stateController.uiState].
*/
private fun stubUiStateWithCryptoAmount(cryptoAmountValue: BigDecimal): MutableStateFlow<StakingUiState> {
val amountData = mockk<AmountState.Data>(relaxed = true) {
every { amountTextField } returns mockk(relaxed = true) {
every { cryptoAmount } returns Amount(
currencySymbol = "ETH",
value = cryptoAmountValue,
decimals = 18,
)
}
}
val uiStateFlow = MutableStateFlow(
mockk<StakingUiState>(relaxed = true) {
every { currentStep } returns StakingStep.InitialInfo
every { amountState } returns amountData
},
)
every { stateController.uiState } returns uiStateFlow
return uiStateFlow
}
// ----- Test A ---------------------------------------------------------------
@Test
fun `GIVEN P2P vault max=5 WHEN amount 6 entered THEN SumLimitError analytics sent`() = runTest {
stubUiStateWithCryptoAmount(BigDecimal("6"))
val model = createModel(testScope = this)
advanceUntilIdle()
model.onAmountValueChange("6")
verify {
analyticsEventHandler.send(
match { it is StakingAnalyticsEvent.SumLimitError }
)
}
model.onDestroy()
}
// ----- Test B ---------------------------------------------------------------
@Test
fun `GIVEN P2P vault max=5 WHEN amount 4 entered THEN SumLimitError analytics NOT sent`() = runTest {
stubUiStateWithCryptoAmount(BigDecimal("4"))
val model = createModel(testScope = this)
advanceUntilIdle()
model.onAmountValueChange("4")
verify(exactly = 0) {
analyticsEventHandler.send(
match { it is StakingAnalyticsEvent.SumLimitError }
)
}
model.onDestroy()
}
}

View file

@ -57,8 +57,8 @@ internal abstract class StakingModelTestBase {
protected val testUserWalletId = UserWalletId("1234567890ABCDEF")
protected val testCryptoCurrency: CryptoCurrency = mockk(relaxed = true)
private val testIntegrationId = StakingIntegrationID.StakeKit.Coin.Solana
private val testParams = StakingComponent.Params(
protected open val testIntegrationId: StakingIntegrationID = StakingIntegrationID.StakeKit.Coin.Solana
private val testParams get() = StakingComponent.Params(
userWalletId = testUserWalletId,
cryptoCurrency = testCryptoCurrency,
integrationId = testIntegrationId,

View file

@ -0,0 +1,31 @@
package com.tangem.features.staking.impl.presentation.state.events
import com.google.common.truth.Truth.assertThat
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.staking.impl.R
import io.mockk.mockk
import org.junit.jupiter.api.Test
internal class StakingAlertUMTest {
@Test
fun `noTargets dialog uses no validators string and has no title`() {
val message = StakingAlertUM.stakeMoreClickUnavailableNoTargets()
assertThat(message.title).isNull()
assertThat((message.message as TextReference.Res).id)
.isEqualTo(R.string.staking_no_validators_error_message)
}
@Test
fun `default stake more dialog uses stake more unavailability string`() {
val currency: CryptoCurrency = mockk(relaxed = true)
val message = StakingAlertUM.stakeMoreClickUnavailable(currency)
assertThat(message.title).isNull()
assertThat((message.message as TextReference.Res).id)
.isEqualTo(R.string.staking_stake_more_button_unavailability_reason)
}
}

View file

@ -0,0 +1,152 @@
package com.tangem.features.staking.impl.presentation.state.transformers.amount
import com.google.common.truth.Truth.assertThat
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.staking.model.StakingIntegration
import com.tangem.domain.staking.model.common.StakingActionArgs
import com.tangem.domain.staking.model.common.StakingAmountRequirement
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.tokens.model.Amount
import com.tangem.features.staking.impl.R
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Test
import java.math.BigDecimal
internal class AmountRequirementStateTransformerTest {
private val cryptoCurrencyStatus: CryptoCurrencyStatus = mockk(relaxed = true)
private fun amountState(enteredCrypto: BigDecimal): AmountState.Data = AmountState.Data(
isPrimaryButtonEnabled = true,
accountTitleUM = mockk(relaxed = true),
availableBalanceCrypto = mockk(relaxed = true),
availableBalanceFiat = mockk(relaxed = true),
tokenName = mockk(relaxed = true),
tokenIconState = mockk(relaxed = true),
amountTextField = AmountFieldModel(
value = enteredCrypto.toPlainString(),
onValueChange = {},
keyboardOptions = mockk(relaxed = true),
keyboardActions = mockk(relaxed = true),
cryptoAmount = Amount(currencySymbol = "ETH", value = enteredCrypto, decimals = 18),
fiatAmount = Amount(currencySymbol = "USD", value = BigDecimal.ZERO, decimals = 2),
isFiatValue = false,
fiatValue = "0",
isFiatUnavailable = false,
isValuePasted = false,
onValuePastedTriggerDismiss = {},
isError = false,
isWarning = false,
error = stringReference(""),
),
appCurrency = mockk(relaxed = true),
)
private fun enterIntegrationWith(minimum: BigDecimal?, maximum: BigDecimal?): StakingIntegration = mockk {
every { enterArgs } returns StakingActionArgs(
amountRequirement = StakingAmountRequirement(
isRequired = true,
minimum = minimum,
maximum = maximum,
),
isPartialAmountDisabled = false,
)
}
private fun exitIntegrationWith(minimum: BigDecimal?, maximum: BigDecimal?): StakingIntegration = mockk {
every { exitArgs } returns StakingActionArgs(
amountRequirement = StakingAmountRequirement(
isRequired = true,
minimum = minimum,
maximum = maximum,
),
isPartialAmountDisabled = false,
)
}
@Test
fun `WHEN amount exceeds positive maximum THEN max amount error string is used`() {
val transformer = AmountRequirementStateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
maxAmount = EnterAmountBoundary(amount = BigDecimal("100"), fiatAmount = null, fiatRate = null),
integration = enterIntegrationWith(minimum = BigDecimal("0.01"), maximum = BigDecimal("0.15")),
actionType = StakingActionCommonType.Enter(skipEnterAmount = false),
)
val result = transformer.transform(amountState(BigDecimal("0.2"))) as AmountState.Data
assertThat(result.amountTextField.isError).isTrue()
assertThat((result.amountTextField.error as TextReference.Res).id)
.isEqualTo(R.string.staking_max_amount_requirement_error)
}
@Test
fun `WHEN amount below minimum THEN min amount error string is used`() {
val transformer = AmountRequirementStateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
maxAmount = EnterAmountBoundary(amount = BigDecimal("100"), fiatAmount = null, fiatRate = null),
integration = enterIntegrationWith(minimum = BigDecimal("0.1"), maximum = null),
actionType = StakingActionCommonType.Enter(skipEnterAmount = false),
)
val result = transformer.transform(amountState(BigDecimal("0.05"))) as AmountState.Data
assertThat(result.amountTextField.isError).isTrue()
assertThat((result.amountTextField.error as TextReference.Res).id)
.isEqualTo(R.string.staking_amount_requirement_error)
}
@Test
fun `WHEN Exit action and amount below exit minimum THEN unstake min error string is used`() {
val transformer = AmountRequirementStateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
maxAmount = EnterAmountBoundary(amount = BigDecimal("100"), fiatAmount = null, fiatRate = null),
integration = exitIntegrationWith(minimum = BigDecimal("0.1"), maximum = null),
actionType = StakingActionCommonType.Exit(partiallyUnstakeDisabled = false),
)
val result = transformer.transform(amountState(BigDecimal("0.05"))) as AmountState.Data
assertThat(result.amountTextField.isError).isTrue()
assertThat((result.amountTextField.error as TextReference.Res).id)
.isEqualTo(R.string.staking_unstake_amount_requirement_error)
}
@Test
fun `WHEN Enter action and maximum is null and amount exceeds balance cap THEN max error string is used`() {
val transformer = AmountRequirementStateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
maxAmount = EnterAmountBoundary(amount = BigDecimal("0.5"), fiatAmount = null, fiatRate = null),
integration = enterIntegrationWith(minimum = BigDecimal("0.01"), maximum = null),
actionType = StakingActionCommonType.Enter(skipEnterAmount = false),
)
val result = transformer.transform(amountState(BigDecimal("0.6"))) as AmountState.Data
assertThat(result.amountTextField.isError).isTrue()
assertThat((result.amountTextField.error as TextReference.Res).id)
.isEqualTo(R.string.staking_max_amount_requirement_error)
}
@Test
fun `WHEN Exit action and amount exceeds staked balance THEN max amount error string is used`() {
val transformer = AmountRequirementStateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
maxAmount = EnterAmountBoundary(amount = BigDecimal("0.5"), fiatAmount = null, fiatRate = null),
integration = exitIntegrationWith(minimum = BigDecimal("0.01"), maximum = null),
actionType = StakingActionCommonType.Exit(partiallyUnstakeDisabled = false),
)
val result = transformer.transform(amountState(BigDecimal("0.6"))) as AmountState.Data
assertThat(result.amountTextField.isError).isTrue()
assertThat((result.amountTextField.error as TextReference.Res).id)
.isEqualTo(R.string.staking_max_amount_requirement_error)
}
}