Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-26 17:07:40 +02:00
parent e04ac1fb2c
commit 99d32963b5
20 changed files with 336 additions and 27 deletions

View file

@ -219,6 +219,9 @@ interface TangemTechApi {
@POST("v2/transaction-events")
suspend fun transactionEvents(@Body name: TransactionEventBody): ApiResponse<Unit>
@GET("v1/coins/settings")
suspend fun getCoinsSettings(): ApiResponse<CoinsSettingsResponse>
// region Earn
@GET("v1/earn/markets")
suspend fun getEarnTokens(

View file

@ -0,0 +1,22 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class CoinsSettingsResponse(
@Json(name = "staking") val staking: StakingSettingsDTO?,
)
@JsonClass(generateAdapter = true)
data class StakingSettingsDTO(
@Json(name = "vaults") val vaults: List<VaultSettingsDTO> = emptyList(),
)
@JsonClass(generateAdapter = true)
data class VaultSettingsDTO(
@Json(name = "vaultAddress") val vaultAddress: String,
@Json(name = "limit") val limit: BigDecimal?,
@Json(name = "coefficient") val coefficient: BigDecimal?,
)

View file

@ -0,0 +1,15 @@
package com.tangem.datasource.local.token
import com.tangem.datasource.local.datastore.RuntimeStateStore
import com.tangem.domain.staking.model.ethpool.VaultLimitInfo
import javax.inject.Inject
import javax.inject.Singleton
/**
* In-memory store for P2P vault limits from Tangem API /v1/coins/settings.
* Map key is vaultAddress.lowercase(). Null map value means limits not yet fetched.
* A missing key means the vault is full (null-limit vaults are excluded at fetch time).
*/
@Singleton
class P2PVaultLimitsStore @Inject constructor() :
RuntimeStateStore<Map<String, VaultLimitInfo>?> by RuntimeStateStore(defaultValue = null)

View file

@ -7,29 +7,35 @@ import arrow.core.raise.either
import arrow.core.raise.ensure
import com.tangem.data.staking.converters.ethpool.*
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.ethpool.P2PEthPoolApi
import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolBroadcastRequest
import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolTransactionRequest
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolResponse
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolTransactionResponse
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.models.staking.P2PEthPoolStakingAccount
import com.tangem.domain.staking.model.P2PEthPoolIntegration
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.domain.staking.model.StakingOption
import com.tangem.domain.staking.model.ethpool.P2PEthPoolBroadcastResult
import com.tangem.domain.staking.model.ethpool.P2PEthPoolNetwork
import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
import com.tangem.domain.staking.model.ethpool.P2PEthPoolUnsignedTx
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import com.tangem.domain.staking.model.ethpool.VaultLimitInfo
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.domain.staking.toggles.StakingFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
/**
@ -38,6 +44,8 @@ import kotlinx.coroutines.withContext
internal class DefaultP2PEthPoolRepository(
private val p2pEthPoolApi: P2PEthPoolApi,
private val p2pEthPoolVaultsStore: P2PEthPoolVaultsStore,
private val p2pVaultLimitsStore: P2PVaultLimitsStore,
private val tangemTechApi: TangemTechApi,
private val dispatchers: CoroutineDispatcherProvider,
private val stakingFeatureToggles: StakingFeatureToggles,
) : P2PEthPoolRepository {
@ -183,21 +191,32 @@ internal class DefaultP2PEthPoolRepository(
}
override fun getStakingAvailability(): Flow<StakingAvailability> {
return getVaultsFlow()
.distinctUntilChanged()
.map { vaults ->
if (vaults.isEmpty()) {
return@map StakingAvailability.TemporaryUnavailable
return combine(
getVaultsFlow().distinctUntilChanged(),
getVaultLimitsFlow().distinctUntilChanged(),
) { vaults, limits ->
when {
vaults.isEmpty() -> StakingAvailability.TemporaryUnavailable
limits == null -> StakingAvailability.TemporaryUnavailable
else -> {
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, vaults, limits)
if (integration.areAllTargetsFull) {
StakingAvailability.Unavailable
} else {
StakingAvailability.Available(StakingOption.P2PEthPool(vaults))
}
}
}
}.distinctUntilChanged()
}
override suspend fun getStakingAvailabilitySync(): StakingAvailability {
val vaults = getVaultsSync()
return if (vaults.isEmpty()) {
StakingAvailability.TemporaryUnavailable
if (vaults.isEmpty()) return StakingAvailability.TemporaryUnavailable
val limits = getVaultLimitsSyncOrNull() ?: return StakingAvailability.TemporaryUnavailable
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, vaults, limits)
return if (integration.areAllTargetsFull) {
StakingAvailability.Unavailable
} else {
StakingAvailability.Available(StakingOption.P2PEthPool(vaults))
}
@ -206,4 +225,33 @@ internal class DefaultP2PEthPoolRepository(
override suspend fun getVaultsSync(): List<P2PEthPoolVault> {
return p2pEthPoolVaultsStore.getSync()
}
override suspend fun fetchVaultLimits() {
runSuspendCatching {
val response = withContext(dispatchers.io) {
tangemTechApi.getCoinsSettings().getOrThrow()
}
val vaults = response.staking?.vaults.orEmpty()
val limits = vaults
.mapNotNull { vault ->
val limit = vault.limit ?: return@mapNotNull null
vault.vaultAddress.lowercase() to VaultLimitInfo(
limit = limit,
coefficient = vault.coefficient,
)
}
.toMap()
p2pVaultLimitsStore.store(limits)
}.onFailure { e ->
TangemLogger.e("Error fetching P2P vault limits: ${e.message}", e)
}
}
override fun getVaultLimitsFlow(): Flow<Map<String, VaultLimitInfo>?> {
return p2pVaultLimitsStore.get()
}
override suspend fun getVaultLimitsSyncOrNull(): Map<String, VaultLimitInfo>? {
return p2pVaultLimitsStore.getSyncOrNull()
}
}

View file

@ -12,9 +12,11 @@ import com.tangem.data.staking.utils.DefaultStakingCleaner
import com.tangem.datasource.api.ethpool.P2PEthPoolApi
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.stakekit.models.response.model.error.StakeKitErrorResponse
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.token.P2PEthPoolVaultsStore
import com.tangem.datasource.local.token.P2PVaultLimitsStore
import com.tangem.datasource.local.token.StakingActionsStore
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.StakingIdFactory
@ -77,12 +79,16 @@ internal object StakingDataModule {
fun provideP2PEthPoolRepository(
p2pEthPoolApi: P2PEthPoolApi,
p2pEthPoolVaultsStore: P2PEthPoolVaultsStore,
p2pVaultLimitsStore: P2PVaultLimitsStore,
tangemTechApi: TangemTechApi,
dispatchers: CoroutineDispatcherProvider,
stakingFeatureToggles: StakingFeatureToggles,
): P2PEthPoolRepository {
return DefaultP2PEthPoolRepository(
p2pEthPoolApi = p2pEthPoolApi,
p2pEthPoolVaultsStore = p2pEthPoolVaultsStore,
p2pVaultLimitsStore = p2pVaultLimitsStore,
tangemTechApi = tangemTechApi,
dispatchers = dispatchers,
stakingFeatureToggles = stakingFeatureToggles,
)

View file

@ -7,7 +7,9 @@ import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolNetworkDTO
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolResponse
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolVaultDTO
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolVaultsResponse
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.StakingIntegrationID
import com.tangem.domain.staking.model.ethpool.P2PEthPoolNetwork
import com.tangem.domain.staking.toggles.StakingFeatureToggles
@ -30,12 +32,16 @@ internal class P2PEthPoolVaultFilterTest {
private val api = mockk<P2PEthPoolApi>()
private val store = mockk<P2PEthPoolVaultsStore>(relaxed = true)
private val limitsStore = mockk<P2PVaultLimitsStore>(relaxed = true)
private val tangemTechApi = mockk<TangemTechApi>(relaxed = true)
private val featureToggles = mockk<StakingFeatureToggles> {
every { isIntegrationEnabled(StakingIntegrationID.P2PEthPool) } returns true
}
private val repository = DefaultP2PEthPoolRepository(
p2pEthPoolApi = api,
p2pEthPoolVaultsStore = store,
p2pVaultLimitsStore = limitsStore,
tangemTechApi = tangemTechApi,
dispatchers = TestingCoroutineDispatcherProvider(),
stakingFeatureToggles = featureToggles,
)

View file

@ -0,0 +1,16 @@
package com.tangem.domain.staking.model.ethpool
import java.math.BigDecimal
/**
* Per-vault capacity limits from Tangem API /v1/coins/settings.
*
* @property limit max stakeable amount in ETH (pre-computed as MAX_Threshold - TVL).
* Vaults absent from the API response or with null limit are not stored.
* @property coefficient threshold multiplier (e.g. 1.25×); optional server-side field,
* reserved for future use, not used in client-side calculations
*/
data class VaultLimitInfo(
val limit: BigDecimal,
val coefficient: BigDecimal?,
)

View file

@ -26,6 +26,7 @@ class FetchStakingOptionsUseCase(
coroutineScope {
launch { stakeKitRepository.fetchYields() }
launch { p2pEthPoolRepository.fetchVaults() }
launch { p2pEthPoolRepository.fetchVaultLimits() }
}
},
catch = { stakingErrorResolver.resolve(it) },

View file

@ -7,7 +7,9 @@ import com.tangem.domain.staking.model.common.RewardSchedule
import com.tangem.domain.staking.model.common.StakingActionArgs
import com.tangem.domain.staking.model.common.StakingAmountRequirement
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import com.tangem.domain.staking.model.ethpool.VaultLimitInfo
import java.math.BigDecimal
import java.math.RoundingMode
/**
* StakingIntegration implementation for P2PEthPool pooled staking.
@ -16,6 +18,7 @@ import java.math.BigDecimal
class P2PEthPoolIntegration(
override val integrationId: StakingIntegrationID,
private val vaults: List<P2PEthPoolVault>,
private val vaultLimits: Map<String, VaultLimitInfo>,
) : StakingIntegration {
// Basic
@ -30,9 +33,11 @@ class P2PEthPoolIntegration(
vault.toStakingTarget()
}
override val preferredTargets: List<StakingTarget> = targets
override val preferredTargets: List<StakingTarget> = vaults
.filter { isVaultAvailable(it) }
.map { it.toStakingTarget() }
override val areAllTargetsFull: Boolean = false
override val areAllTargetsFull: Boolean = preferredTargets.isEmpty()
// Enter/Exit Args
@ -45,7 +50,7 @@ class P2PEthPoolIntegration(
override val enterArgs: StakingActionArgs = StakingActionArgs(
amountRequirement = StakingAmountRequirement(
isRequired = true,
minimum = DEFAULT_MINIMUM_STAKE,
minimum = enterMinimumAmount,
maximum = calculateMaximumStakeAmount(),
),
isPartialAmountDisabled = false,
@ -82,19 +87,27 @@ class P2PEthPoolIntegration(
override fun getCurrentToken(rawCurrencyId: CryptoCurrency.RawID?): YieldToken = token
private fun isVaultAvailable(vault: P2PEthPoolVault): Boolean {
val info = vaultLimits[vault.vaultAddress.lowercase()] ?: return false
return info.limit - vault.totalAssets > AVAILABILITY_THRESHOLD
}
private fun calculateMaximumStakeAmount(): BigDecimal? {
return vaults
.filter { isVaultAvailable(it) }
.mapNotNull { vault ->
val availableCapacity = vault.capacity - vault.totalAssets
if (availableCapacity > BigDecimal.ZERO) availableCapacity else null
vaultLimits[vault.vaultAddress.lowercase()]?.let { it.limit - vault.totalAssets }
}
.maxOrNull()
.minOrNull()
?.setScale(MAX_AMOUNT_SCALE, RoundingMode.FLOOR)
}
companion object {
private const val MIN_COOLDOWN_DAYS = 1
private const val MAX_COOLDOWN_DAYS = 4
private const val MAX_AMOUNT_SCALE = 1
private val DEFAULT_MINIMUM_STAKE = BigDecimal("0.01")
private val AVAILABILITY_THRESHOLD = BigDecimal("2")
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

@ -8,6 +8,7 @@ import com.tangem.domain.staking.model.ethpool.P2PEthPoolNetwork
import com.tangem.domain.staking.model.ethpool.P2PEthPoolUnsignedTx
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
import com.tangem.domain.staking.model.ethpool.VaultLimitInfo
import com.tangem.domain.staking.model.stakekit.StakingError
import kotlinx.coroutines.flow.Flow
@ -131,6 +132,25 @@ interface P2PEthPoolRepository {
*/
suspend fun getVaultsSync(): List<P2PEthPoolVault>
/**
* Fetch and store vault limits from Tangem API /v1/coins/settings
*/
suspend fun fetchVaultLimits()
/**
* Get flow of cached vault limits.
*
* @return Flow of map from vaultAddress.lowercase() to VaultLimitInfo, null if not yet fetched
*/
fun getVaultLimitsFlow(): Flow<Map<String, VaultLimitInfo>?>
/**
* Get cached vault limits synchronously.
*
* @return Map from vaultAddress.lowercase() to VaultLimitInfo, null if not yet fetched
*/
suspend fun getVaultLimitsSyncOrNull(): Map<String, VaultLimitInfo>?
/**
* Check P2PEthPool staking availability by finding public vault
*

View file

@ -0,0 +1,150 @@
package com.tangem.domain.staking.model
import com.google.common.truth.Truth.assertThat
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import com.tangem.domain.staking.model.ethpool.VaultLimitInfo
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import java.math.BigDecimal
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class P2PEthPoolIntegrationTest {
private fun buildVault(
address: String,
capacity: String,
totalAssets: String,
) = P2PEthPoolVault(
vaultAddress = address,
displayName = "Test Vault",
apy = BigDecimal("4.5"),
baseApy = BigDecimal("4.0"),
capacity = BigDecimal(capacity),
totalAssets = BigDecimal(totalAssets),
feePercent = BigDecimal("0.1"),
isPrivate = false,
isGenesis = false,
isSmoothingPool = true,
isErc20 = false,
tokenName = null,
tokenSymbol = null,
createdAt = 0L,
)
private fun buildLimits(vararg pairs: Pair<String, BigDecimal>) =
pairs.associate { (addr, limit) ->
addr.lowercase() to VaultLimitInfo(limit = limit, coefficient = BigDecimal("1.25"))
}
@Nested
inner class MaximumAmount {
@Test
fun `vault available - uses remaining space as max, rounded down to 0_1 ETH`() {
val vaults = listOf(buildVault("0xABC", capacity = "100", totalAssets = "10"))
val limits = buildLimits("0xABC" to BigDecimal("50"))
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, vaults, limits)
assertThat(integration.enterArgs!!.amountRequirement!!.maximum).isEqualTo(BigDecimal("40.0"))
}
@Test
fun `remaining with fractional ETH - floored to 0_1 ETH precision`() {
val vaults = listOf(buildVault("0xABC", capacity = "100", totalAssets = "10"))
val limits = buildLimits("0xABC" to BigDecimal("22.37"))
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, vaults, limits)
assertThat(integration.enterArgs!!.amountRequirement!!.maximum).isEqualTo(BigDecimal("12.3"))
}
@Test
fun `vault absent from limits map - treated as full, max is null`() {
val vaults = listOf(buildVault("0xABC", capacity = "100", totalAssets = "30"))
val limits = emptyMap<String, VaultLimitInfo>()
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, vaults, limits)
assertThat(integration.areAllTargetsFull).isTrue()
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")
val vault2 = buildVault("0xB", capacity = "100", totalAssets = "20")
val limits = buildLimits("0xa" to BigDecimal("50"), "0xb" to BigDecimal("50"))
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, listOf(vault1, vault2), limits)
assertThat(integration.enterArgs!!.amountRequirement!!.maximum).isEqualTo(BigDecimal("30.0"))
}
}
@Nested
inner class Availability {
@Test
fun `all vaults full - areAllTargetsFull is true`() {
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.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"))
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, vaults, limits)
assertThat(integration.areAllTargetsFull).isTrue()
}
@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 limits = buildLimits("0xa" to BigDecimal("50"), "0xb" to BigDecimal("50"))
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, listOf(vault1, vault2), limits)
assertThat(integration.areAllTargetsFull).isFalse()
}
@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 limits = buildLimits("0xa" to BigDecimal("50"), "0xb" to BigDecimal("50"))
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, listOf(vault1, vault2), limits)
assertThat(integration.preferredTargets).hasSize(1)
assertThat(integration.preferredTargets.first().address).isEqualTo("0xB")
}
}
@Nested
inner class MinimumAmount {
@Test
fun `minimum stake is 0_01 ETH`() {
val integration = P2PEthPoolIntegration(StakingIntegrationID.P2PEthPool, emptyList(), emptyMap())
assertThat(integration.enterMinimumAmount).isEqualTo(BigDecimal("0.01"))
}
}
}

View file

@ -174,17 +174,17 @@ internal open class BaseActionsFactory(
protected fun createStakingAction(
currency: CryptoCurrency,
stakingAvailability: StakingAvailability,
): ActionState.Stake {
return if (stakingAvailability is StakingAvailability.Available) {
ActionState.Stake(
): ActionState.Stake? {
return when (stakingAvailability) {
is StakingAvailability.Available -> ActionState.Stake(
unavailabilityReason = ScenarioUnavailabilityReason.None,
option = stakingAvailability.option,
)
} else {
ActionState.Stake(
StakingAvailability.TemporaryUnavailable -> ActionState.Stake(
unavailabilityReason = ScenarioUnavailabilityReason.StakingUnavailable(currency.name),
option = null,
)
StakingAvailability.Unavailable -> null
}
}

View file

@ -81,7 +81,7 @@ internal class CommonActionsFactory(
// region Stake
createStakingAction(currency = cryptoCurrencyStatus.currency, stakingAvailability = stakingAvailability)
.addByReason()
?.addByReason()
// endregion
val sendUnavailabilityReason = sendUnavailabilityReasonDeferred.await()

View file

@ -97,7 +97,7 @@ internal class OutdatedDataActionsFactory(
stakingAvailability = stakingAvailability,
)
stakingAction.addByReason()
stakingAction?.addByReason()
} else {
val stakingAction = ActionState.Stake(
unavailabilityReason = ScenarioUnavailabilityReason.UsedOutdatedData,

View file

@ -22,8 +22,8 @@ import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.earn.EarnNetworks
import com.tangem.domain.models.earn.EarnTokenWithCurrency
import com.tangem.domain.models.earn.PreselectedEarnType
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
import com.tangem.domain.models.earn.PreselectedEarnType
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager.AnalyticsParams.Companion.CategoryEarn
import com.tangem.features.feed.components.earn.DefaultEarnComponent
import com.tangem.features.feed.components.earn.EarnNetworkFilterComponent

View file

@ -182,7 +182,8 @@ internal class StakingModel @Inject constructor(
}
StakingIntegrationID.P2PEthPool -> {
val vaults = p2pEthPoolRepository.getVaultsSync()
P2PEthPoolIntegration(integrationId, vaults)
val limits = p2pEthPoolRepository.getVaultLimitsSyncOrNull().orEmpty()
P2PEthPoolIntegration(integrationId, vaults, limits)
}
}
}

View file

@ -57,6 +57,7 @@ internal sealed class StakingStates {
val yieldBalance: InnerYieldBalanceState,
val pullToRefreshConfig: PullToRefreshConfig,
val legalUrls: LegalUrls,
val areAllTargetsFull: Boolean = false,
) : InitialInfoState()
data class LegalUrls(

View file

@ -166,7 +166,11 @@ internal class SetButtonsStateTransformer(
val hasNotStaking = initialState?.yieldBalance == InnerYieldBalanceState.Empty
val isCardano = BlockchainUtils.isCardano(cryptoCurrencyBlockchainId)
return !hasNotStaking && isCardano && currentStep == StakingStep.InitialInfo
if (!hasNotStaking && isCardano && currentStep == StakingStep.InitialInfo) return true
val hasStaking = initialState?.yieldBalance is InnerYieldBalanceState.Data
val areAllTargetsFull = initialState?.areAllTargetsFull == true
return hasStaking && areAllTargetsFull && currentStep == StakingStep.InitialInfo
}
private fun StakingUiState.isApprovalRequired(): Boolean {

View file

@ -110,6 +110,7 @@ internal class SetInitialDataStateTransformer(
termsOfServiceUrl = integration.legalUrls.termsOfServiceUrl,
privacyPolicyUrl = integration.legalUrls.privacyPolicyUrl,
),
areAllTargetsFull = integration.areAllTargetsFull,
)
}

View file

@ -133,6 +133,7 @@ internal class StakingModelTransactionTest : StakingModelTestBase() {
integrationId = StakingIntegrationID.P2PEthPool,
)
coEvery { p2pEthPoolRepository.getVaultsSync() } returns emptyList()
coEvery { p2pEthPoolRepository.getVaultLimitsSyncOrNull() } returns emptyMap()
val uiStateFlow = MutableStateFlow(initialUiState)
every { stateController.uiState } returns uiStateFlow
coEvery {
@ -218,6 +219,7 @@ internal class StakingModelTransactionTest : StakingModelTestBase() {
integrationId = StakingIntegrationID.P2PEthPool,
)
coEvery { p2pEthPoolRepository.getVaultsSync() } returns emptyList()
coEvery { p2pEthPoolRepository.getVaultLimitsSyncOrNull() } returns emptyMap()
val uiStateFlow = MutableStateFlow(initialUiState)
every { stateController.uiState } returns uiStateFlow
coEvery {