Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-03 18:47:59 +02:00
parent 994186039e
commit 259953954a
13 changed files with 303 additions and 41 deletions

View file

@ -25,6 +25,7 @@ import com.tangem.domain.models.currency.yieldSupplyKey
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingOption
import com.tangem.domain.staking.model.optionOrNull
import com.tangem.domain.staking.model.common.RewardInfo
import com.tangem.domain.staking.model.common.RewardType
import com.tangem.lib.crypto.BlockchainUtils
@ -244,14 +245,21 @@ class TokenItemStateConverter(
currencyStatus: CryptoCurrencyStatus,
stakingApyMap: Map<CryptoCurrency, StakingAvailability>,
): StakingLocalInfo {
val stakingAvailability = stakingApyMap[currencyStatus.currency] as? StakingAvailability.Available
val availability = stakingApyMap[currencyStatus.currency]
val option = availability?.optionOrNull
?: return StakingLocalInfo(rate = null, isActive = false, rewardType = null)
val stakingBalance = currencyStatus.value.stakingBalance as? StakingBalance.Data
val stakeKitBalance = stakingBalance as? StakingBalance.Data.StakeKit
val p2pEthPoolBalance = stakingBalance as? StakingBalance.Data.P2PEthPool
val isActive = stakeKitBalance != null || p2pEthPoolBalance != null
val rateInfo = when (val stakingOptions = stakingAvailability.option) {
// Full = no free capacity: show the badge only for tokens that already have a stake.
if (availability is StakingAvailability.Full && !isActive) {
return StakingLocalInfo(rate = null, isActive = false, rewardType = null)
}
val rateInfo = when (val stakingOptions = option) {
is StakingOption.P2PEthPool -> {
RewardInfo(
rate = stakingOptions.apy,
@ -282,7 +290,7 @@ class TokenItemStateConverter(
return StakingLocalInfo(
rate = rateInfo?.rate,
isActive = stakeKitBalance != null || p2pEthPoolBalance != null,
isActive = isActive,
rewardType = rateInfo?.type,
)
}

View file

@ -201,7 +201,7 @@ internal class DefaultP2PEthPoolRepository(
else -> {
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, vaults, limits)
if (integration.areAllTargetsFull) {
StakingAvailability.Unavailable
StakingAvailability.Full(StakingOption.P2PEthPool(vaults))
} else {
StakingAvailability.Available(StakingOption.P2PEthPool(vaults))
}
@ -216,7 +216,7 @@ internal class DefaultP2PEthPoolRepository(
val limits = getVaultLimitsSyncOrNull() ?: return StakingAvailability.TemporaryUnavailable
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, vaults, limits)
return if (integration.areAllTargetsFull) {
StakingAvailability.Unavailable
StakingAvailability.Full(StakingOption.P2PEthPool(vaults))
} else {
StakingAvailability.Available(StakingOption.P2PEthPool(vaults))
}

View file

@ -0,0 +1,120 @@
package com.tangem.data.staking
import com.google.common.truth.Truth.assertThat
import com.tangem.datasource.api.ethpool.P2PEthPoolApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.token.P2PEthPoolVaultsStore
import com.tangem.datasource.local.token.P2PVaultLimitsStore
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import com.tangem.domain.staking.model.ethpool.VaultLimitInfo
import com.tangem.domain.staking.toggles.StakingFeatureToggles
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import java.math.BigDecimal
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class DefaultP2PEthPoolRepositoryAvailabilityTest {
private val api = mockk<P2PEthPoolApi>(relaxed = true)
private val vaultsStore = mockk<P2PEthPoolVaultsStore>(relaxed = true)
private val limitsStore = mockk<P2PVaultLimitsStore>(relaxed = true)
private val tangemTechApi = mockk<TangemTechApi>(relaxed = true)
private val featureToggles = mockk<StakingFeatureToggles>(relaxed = true)
private val repository = DefaultP2PEthPoolRepository(
p2pEthPoolApi = api,
p2pEthPoolVaultsStore = vaultsStore,
p2pVaultLimitsStore = limitsStore,
tangemTechApi = tangemTechApi,
dispatchers = TestingCoroutineDispatcherProvider(),
stakingFeatureToggles = featureToggles,
)
private fun buildVault(address: String, totalAssets: String) = P2PEthPoolVault(
vaultAddress = address,
displayName = "Vault",
apy = BigDecimal("4.5"),
baseApy = BigDecimal("4.0"),
capacity = BigDecimal("1000"),
totalAssets = BigDecimal(totalAssets),
feePercent = BigDecimal("10"),
isPrivate = false,
isGenesis = false,
isSmoothingPool = true,
isErc20 = false,
tokenName = null,
tokenSymbol = null,
createdAt = 0L,
)
private fun limits(address: String, limit: String) =
mapOf(address.lowercase() to VaultLimitInfo(limit = BigDecimal(limit), coefficient = null))
@Test
fun `all vaults full - emits Full with option`() = runTest {
every { vaultsStore.get() } returns flowOf(listOf(buildVault("0xABC", totalAssets = "999.95")))
every { limitsStore.get() } returns MutableStateFlow(limits("0xABC", limit = "1000")) // remaining 0.05 <= 0.1
val result = repository.getStakingAvailability().first()
assertThat(result).isInstanceOf(StakingAvailability.Full::class.java)
}
@Test
fun `capacity available - emits Available`() = runTest {
every { vaultsStore.get() } returns flowOf(listOf(buildVault("0xABC", totalAssets = "100")))
every { limitsStore.get() } returns MutableStateFlow(limits("0xABC", limit = "1000")) // remaining 900 > 0.1
val result = repository.getStakingAvailability().first()
assertThat(result).isInstanceOf(StakingAvailability.Available::class.java)
}
@Test
fun `sync - all vaults full - returns Full with option`() = runTest {
coEvery { vaultsStore.getSync() } returns listOf(buildVault("0xABC", totalAssets = "999.95"))
coEvery { limitsStore.getSyncOrNull() } returns limits("0xABC", limit = "1000") // remaining 0.05 <= 0.1
val result = repository.getStakingAvailabilitySync()
assertThat(result).isInstanceOf(StakingAvailability.Full::class.java)
}
@Test
fun `sync - capacity available - returns Available`() = runTest {
coEvery { vaultsStore.getSync() } returns listOf(buildVault("0xABC", totalAssets = "100"))
coEvery { limitsStore.getSyncOrNull() } returns limits("0xABC", limit = "1000") // remaining 900 > 0.1
val result = repository.getStakingAvailabilitySync()
assertThat(result).isInstanceOf(StakingAvailability.Available::class.java)
}
@Test
fun `sync - empty vaults - returns TemporaryUnavailable`() = runTest {
coEvery { vaultsStore.getSync() } returns emptyList()
val result = repository.getStakingAvailabilitySync()
assertThat(result).isInstanceOf(StakingAvailability.TemporaryUnavailable::class.java)
}
@Test
fun `sync - limits not loaded - returns TemporaryUnavailable`() = runTest {
coEvery { vaultsStore.getSync() } returns listOf(buildVault("0xABC", totalAssets = "100"))
coEvery { limitsStore.getSyncOrNull() } returns null
val result = repository.getStakingAvailabilitySync()
assertThat(result).isInstanceOf(StakingAvailability.TemporaryUnavailable::class.java)
}
}

View file

@ -108,7 +108,7 @@ class P2PEthPoolIntegration(
private const val MAX_AMOUNT_SCALE = 1
private val DEFAULT_MINIMUM_STAKE = BigDecimal("0.01")
private val DEFAULT_MINIMUM_UNSTAKE = BigDecimal("0.01")
private val AVAILABILITY_THRESHOLD = BigDecimal("2")
private val AVAILABILITY_THRESHOLD = BigDecimal("0.1")
private const val TERMS_OF_SERVICE_URL = "https://www.p2p.org/terms-of-use"
private const val PRIVACY_POLICY_URL = "https://www.p2p.org/privacy-policy"

View file

@ -4,7 +4,23 @@ sealed class StakingAvailability {
data class Available(val option: StakingOption) : StakingAvailability()
/**
* Integration exists and APY is known, but there is no free capacity (all vaults full).
* Existing stakes stay visible; new stakes are not offered. P2P ETH only.
*/
data class Full(val option: StakingOption) : StakingAvailability()
data object Unavailable : StakingAvailability()
data object TemporaryUnavailable : StakingAvailability()
}
/** Staking option if the integration is known (Available or Full), else null. */
val StakingAvailability.optionOrNull: StakingOption?
get() = when (this) {
is StakingAvailability.Available -> option
is StakingAvailability.Full -> option
StakingAvailability.Unavailable,
StakingAvailability.TemporaryUnavailable,
-> null
}

View file

@ -67,24 +67,6 @@ internal class P2PEthPoolIntegrationTest {
assertThat(integration.enterArgs!!.amountRequirement!!.maximum).isNull()
}
@Test
fun `vault with exactly 2 ETH remaining - not available, max is null`() {
val vaults = listOf(buildVault("0xABC", capacity = "100", totalAssets = "48"))
val limits = buildLimits("0xABC" to BigDecimal("50"))
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, vaults, limits)
assertThat(integration.enterArgs!!.amountRequirement!!.maximum).isNull()
}
@Test
fun `vault with less than 2 ETH remaining - not available, max is null`() {
val vaults = listOf(buildVault("0xABC", capacity = "100", totalAssets = "48.5"))
val limits = buildLimits("0xABC" to BigDecimal("50"))
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, vaults, limits)
assertThat(integration.enterArgs!!.amountRequirement!!.maximum).isNull()
}
@Test
fun `multiple available vaults - uses minimum remaining space`() {
val vault1 = buildVault("0xA", capacity = "100", totalAssets = "10")
@ -99,27 +81,36 @@ internal class P2PEthPoolIntegrationTest {
@Nested
inner class Availability {
@Test
fun `all vaults full - areAllTargetsFull is true`() {
fun `vault absent from limits - areAllTargetsFull is true`() {
val vaults = listOf(buildVault("0xABC", capacity = "100", totalAssets = "48"))
val limits = buildLimits("0xABC" to BigDecimal("50"))
val limits = emptyMap<String, VaultLimitInfo>()
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, vaults, limits)
assertThat(integration.areAllTargetsFull).isTrue()
}
@Test
fun `vault with remaining between 0_1 and 2 ETH - also considered full`() {
val vaults = listOf(buildVault("0xABC", capacity = "100", totalAssets = "48.5"))
val limits = buildLimits("0xABC" to BigDecimal("50"))
fun `vault with exactly 0_1 ETH remaining - not available`() {
val vaults = listOf(buildVault("0xABC", capacity = "100", totalAssets = "49.9"))
val limits = buildLimits("0xABC" to BigDecimal("50")) // remaining = 0.1
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, vaults, limits)
assertThat(integration.areAllTargetsFull).isTrue()
}
@Test
fun `vault with remaining just above 0_1 ETH - available (regression [REDACTED_TASK_KEY])`() {
val vaults = listOf(buildVault("0xABC", capacity = "400", totalAssets = "321.895202388313423922"))
val limits = buildLimits("0xABC" to BigDecimal("322.1")) // remaining ≈ 0.2048 > 0.1
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, vaults, limits)
assertThat(integration.areAllTargetsFull).isFalse()
}
@Test
fun `at least one vault available - areAllTargetsFull is false`() {
val vault1 = buildVault("0xA", capacity = "100", totalAssets = "48.5") // full (1.5 remaining < 2)
val vault2 = buildVault("0xB", capacity = "100", totalAssets = "10") // available (40 remaining > 2)
val vault1 = buildVault("0xA", capacity = "100", totalAssets = "49.95") // full (0.05 remaining < 0.1)
val vault2 = buildVault("0xB", capacity = "100", totalAssets = "10") // available (40 remaining > 0.1)
val limits = buildLimits("0xa" to BigDecimal("50"), "0xb" to BigDecimal("50"))
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, listOf(vault1, vault2), limits)
@ -128,8 +119,8 @@ internal class P2PEthPoolIntegrationTest {
@Test
fun `preferred targets only contains available vaults`() {
val vault1 = buildVault("0xA", capacity = "100", totalAssets = "48.5") // full
val vault2 = buildVault("0xB", capacity = "100", totalAssets = "10") // available
val vault1 = buildVault("0xA", capacity = "100", totalAssets = "49.95") // full (0.05 remaining ≤ 0.1)
val vault2 = buildVault("0xB", capacity = "100", totalAssets = "10") // available (40 remaining > 0.1)
val limits = buildLimits("0xa" to BigDecimal("50"), "0xb" to BigDecimal("50"))
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, listOf(vault1, vault2), limits)

View file

@ -184,6 +184,7 @@ internal open class BaseActionsFactory(
unavailabilityReason = ScenarioUnavailabilityReason.StakingUnavailable(currency.name),
option = null,
)
is StakingAvailability.Full -> null
StakingAvailability.Unavailable -> null
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.common.routing.deeplink.DeeplinkConst.WALLET_ID_KEY
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.optionOrNull
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
@ -70,12 +71,12 @@ internal class DefaultStakingDeepLinkHandler @AssistedInject constructor(
return@launch
}
val availability = getStakingAvailabilityUseCase.invokeSync(
val availability: StakingAvailability? = getStakingAvailabilityUseCase.invokeSync(
userWalletId = selectedUserWalletId,
cryptoCurrency = cryptoCurrency,
).getOrNull()
val option = (availability as? StakingAvailability.Available)?.option
val option = availability?.optionOrNull
if (option == null) {
TangemLogger.e("Staking is unavailable for ${cryptoCurrency.name}")
return@launch

View file

@ -68,6 +68,7 @@ import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
import com.tangem.domain.staking.GetStakingEntryInfoUseCase
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.optionOrNull
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.TokenActionsState
@ -1148,7 +1149,7 @@ internal class TokenDetailsModel @Inject constructor(
modelScope.launch {
getStakingAvailabilityUseCase.invokeSync(userWalletId, cryptoCurrency)
.onRight { availability ->
val option = (availability as? StakingAvailability.Available)?.option
val option = availability.optionOrNull
if (option != null) {
router.openStaking(
userWalletId = userWalletId,

View file

@ -52,6 +52,7 @@ internal class TokenDetailsStakingInfoConverter(
return when (stakingAvailability) {
StakingAvailability.TemporaryUnavailable -> StakingBlockUM.TemporaryUnavailable
StakingAvailability.Unavailable -> null
is StakingAvailability.Full -> getStakedBlockOrNull(status)
is StakingAvailability.Available -> getStakingInfoBlock(status, state)
}
}
@ -107,6 +108,39 @@ internal class TokenDetailsStakingInfoConverter(
}
}
private fun getStakedBlockOrNull(status: CryptoCurrencyStatus): StakingBlockUM? {
val stakingBalance = status.value.stakingBalance as? StakingBalance.Data
val stakingCryptoAmount = stakingBalance?.getTotalStakingBalance(status.currency.network.rawId)
val hasPendingBalances = when (stakingBalance) {
is StakingBalance.Data.StakeKit -> stakingBalance.balance.items.isNotEmpty()
is StakingBalance.Data.P2PEthPool -> !stakingBalance.unstakingAmount.isNullOrZero()
null -> false
}
return when {
!stakingCryptoAmount.isNullOrZero() -> getStakedBlockWithFiatAmount(
status = status,
stakingAmount = stakingCryptoAmount,
rewardAmount = when (stakingBalance) {
is StakingBalance.Data.StakeKit -> stakingBalance.getRewardStakingBalance()
is StakingBalance.Data.P2PEthPool -> stakingBalance.totalRewards
else -> BigDecimal.ZERO
},
)
// Pending-only path: reachable for StakeKit (pending items are not part of the total);
// for P2PEthPool the unstaking amount is already included in getTotalStakingBalance above.
hasPendingBalances -> getStakedBlockWithFiatAmount(
status = status,
stakingAmount = when (stakingBalance) {
is StakingBalance.Data.StakeKit -> stakingBalance.balance.items.sumOf { it.amount }
is StakingBalance.Data.P2PEthPool -> stakingBalance.unstakingAmount
null -> BigDecimal.ZERO
},
rewardAmount = null,
)
else -> null
}
}
private fun isStakingButtonEnabled(status: CryptoCurrencyStatus): Boolean {
return status.value is CryptoCurrencyStatus.Loaded ||
status.value is CryptoCurrencyStatus.NoQuote ||

View file

@ -50,6 +50,7 @@ internal class UpdateStakingNotificationTransformer(
return when (val availability = stakingAvailability) {
StakingAvailability.TemporaryUnavailable -> buildTemporaryUnavailable()
StakingAvailability.Unavailable -> null
is StakingAvailability.Full -> buildActiveBlockOrNull(isBalanceHidden)
is StakingAvailability.Available -> getStakingInfoBlock(availability, isBalanceHidden)
}
}
@ -106,6 +107,27 @@ internal class UpdateStakingNotificationTransformer(
}
}
private fun buildActiveBlockOrNull(isBalanceHidden: Boolean): EarnBlockUM? {
val status = cryptoCurrencyStatus
val stakingBalance = status.value.stakingBalance as? StakingBalance.Data
val stakingCryptoAmount = stakingBalance?.getTotalStakingBalance(status.currency.network.rawId)
return when {
!stakingCryptoAmount.isNullOrZero() -> buildActiveBlock(
stakingAmount = stakingCryptoAmount,
rewardAmount = stakingBalance.getRewardAmount(),
isBalanceHidden = isBalanceHidden,
)
// Pending-only path: reachable for StakeKit (pending items are not part of the total);
// for P2PEthPool the unstaking amount is already included in getTotalStakingBalance above.
stakingBalance.hasPendingBalances() -> buildActiveBlock(
stakingAmount = stakingBalance.getPendingAmount(),
rewardAmount = null,
isBalanceHidden = isBalanceHidden,
)
else -> null
}
}
private fun isStakingButtonEnabled(status: CryptoCurrencyStatus): Boolean {
return status.value is CryptoCurrencyStatus.Loaded ||
status.value is CryptoCurrencyStatus.NoQuote ||

View file

@ -84,11 +84,39 @@ class UpdateStakingNotificationTransformerTest {
assertThat(content.trailingUM).isInstanceOf(EarnBlockUM.TrailingUM.Button::class.java)
}
@Test
fun `GIVEN Full AND no active stake WHEN transform THEN earnBlockState is null`() {
val transformer = createTransformer(
availability = fullOption(BigDecimal("4.2")),
entryInfo = null,
)
val result = transformer.transform(initialState())
assertThat(result.earnBlockState).isNull()
}
@Test
fun `GIVEN Full AND active stake WHEN transform THEN active balance block`() {
val transformer = createTransformer(
availability = fullOption(BigDecimal("4.2")),
entryInfo = null,
status = buildStatusWithStake(stakedAmount = BigDecimal("5")),
)
val result = transformer.transform(initialState())
assertThat(result.earnBlockState).isInstanceOf(EarnBlockUM.Content::class.java)
val content = result.earnBlockState as EarnBlockUM.Content
assertThat(content.trailingUM).isInstanceOf(EarnBlockUM.TrailingUM.Balance::class.java)
}
private fun createTransformer(
availability: StakingAvailability,
entryInfo: StakingEntryInfo?,
status: CryptoCurrencyStatus = buildStatus(),
) = UpdateStakingNotificationTransformer(
cryptoCurrencyStatus = buildStatus(),
cryptoCurrencyStatus = status,
stakingAvailability = availability,
stakingEntryInfo = entryInfo,
appCurrency = AppCurrency.Default,
@ -122,6 +150,38 @@ class UpdateStakingNotificationTransformerTest {
return StakingAvailability.Available(option = option)
}
private fun fullOption(apy: BigDecimal): StakingAvailability.Full {
val option = mockk<StakingOption>(relaxed = true) {
every { this@mockk.apy } returns apy
}
return StakingAvailability.Full(option = option)
}
private fun buildStatusWithStake(stakedAmount: BigDecimal): CryptoCurrencyStatus {
val network = mockk<Network>(relaxed = true) {
every { rawId } returns "solana"
every { isTestnet } returns false
}
val currency = mockk<CryptoCurrency.Coin>(relaxed = true) {
every { symbol } returns "SOL"
every { decimals } returns 9
every { this@mockk.network } returns network
every { id.isCoin } returns true
}
val stakingBalance = mockk<StakingBalance.Data.P2PEthPool>(relaxed = true) {
every { totalStaked } returns stakedAmount
every { unstakingAmount } returns BigDecimal.ZERO
every { withdrawableAmount } returns BigDecimal.ZERO
every { totalRewards } returns BigDecimal.ZERO
}
val value = mockk<CryptoCurrencyStatus.Value>(relaxed = true) {
every { this@mockk.stakingBalance } returns stakingBalance
every { fiatRate } returns BigDecimal.ONE
every { yieldSupplyStatus } returns null
}
return CryptoCurrencyStatus(currency = currency, value = value)
}
private fun initialState(): TokenDetailsUM = TokenDetailsUM(
topAppBarUM = TokenDetailsTopAppBarUM(
titleState = TitleState.Simple(tokenName = "Solana"),

View file

@ -12,6 +12,7 @@ import com.tangem.domain.models.currency.yieldSupplyKey
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingOption
import com.tangem.domain.staking.model.optionOrNull
import com.tangem.domain.staking.model.common.RewardInfo
import com.tangem.domain.staking.model.common.RewardType
import com.tangem.lib.crypto.BlockchainUtils
@ -77,14 +78,21 @@ internal class EarnApyConverter(
currencyStatus: CryptoCurrencyStatus,
stakingApyMap: Map<CryptoCurrency, StakingAvailability>,
): StakingLocalInfo {
val stakingAvailability = stakingApyMap[currencyStatus.currency] as? StakingAvailability.Available
val availability = stakingApyMap[currencyStatus.currency]
val option = availability?.optionOrNull
?: return StakingLocalInfo(rate = null, isActive = false, rewardType = null)
val stakingBalance = currencyStatus.value.stakingBalance as? StakingBalance.Data
val stakeKitBalance = stakingBalance as? StakingBalance.Data.StakeKit
val p2pEthPoolBalance = stakingBalance as? StakingBalance.Data.P2PEthPool
val isActive = stakeKitBalance != null || p2pEthPoolBalance != null
val rateInfo = when (val stakingOptions = stakingAvailability.option) {
// Full = no free capacity: show the badge only for tokens that already have a stake.
if (availability is StakingAvailability.Full && !isActive) {
return StakingLocalInfo(rate = null, isActive = false, rewardType = null)
}
val rateInfo = when (val stakingOptions = option) {
is StakingOption.P2PEthPool -> {
RewardInfo(
rate = stakingOptions.apy,
@ -115,7 +123,7 @@ internal class EarnApyConverter(
return StakingLocalInfo(
rate = rateInfo?.rate,
isActive = stakeKitBalance != null || p2pEthPoolBalance != null,
isActive = isActive,
rewardType = rateInfo?.type,
)
}