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

@ -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
} else {
StakingAvailability.Available(StakingOption.P2PEthPool(vaults))
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,
)