Fix nomination pool interactors to use parent chain for exposures

For Asset Hub nomination pools, elected exposures must be queried from
the parent relay chain where validators actually exist.
This commit is contained in:
2026-02-09 21:06:14 +03:00
parent 97d2c803bf
commit 4cd4999e04
4 changed files with 28 additions and 6 deletions
@@ -56,6 +56,7 @@ import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.pools.
import io.novafoundation.nova.feature_staking_impl.presentation.nominationPools.common.PoolDisplayFormatter import io.novafoundation.nova.feature_staking_impl.presentation.nominationPools.common.PoolDisplayFormatter
import io.novafoundation.nova.feature_staking_impl.presentation.nominationPools.common.RealPoolDisplayFormatter import io.novafoundation.nova.feature_staking_impl.presentation.nominationPools.common.RealPoolDisplayFormatter
import io.novafoundation.nova.runtime.call.MultiChainRuntimeCallsApi import io.novafoundation.nova.runtime.call.MultiChainRuntimeCallsApi
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.di.LOCAL_STORAGE_SOURCE import io.novafoundation.nova.runtime.di.LOCAL_STORAGE_SOURCE
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
@@ -151,12 +152,14 @@ class NominationPoolModule {
poolAccountDerivation: PoolAccountDerivation, poolAccountDerivation: PoolAccountDerivation,
relaychainStakingInteractor: StakingInteractor, relaychainStakingInteractor: StakingInteractor,
nominationPoolMemberUseCase: NominationPoolMemberUseCase, nominationPoolMemberUseCase: NominationPoolMemberUseCase,
chainRegistry: ChainRegistry,
): NominationPoolsNetworkInfoInteractor = RealNominationPoolsNetworkInfoInteractor( ): NominationPoolsNetworkInfoInteractor = RealNominationPoolsNetworkInfoInteractor(
relaychainStakingSharedComputation = relaychainStakingSharedComputation, relaychainStakingSharedComputation = relaychainStakingSharedComputation,
nominationPoolGlobalsRepository = nominationPoolGlobalsRepository, nominationPoolGlobalsRepository = nominationPoolGlobalsRepository,
poolAccountDerivation = poolAccountDerivation, poolAccountDerivation = poolAccountDerivation,
relaychainStakingInteractor = relaychainStakingInteractor, relaychainStakingInteractor = relaychainStakingInteractor,
nominationPoolMemberUseCase = nominationPoolMemberUseCase nominationPoolMemberUseCase = nominationPoolMemberUseCase,
chainRegistry = chainRegistry
) )
@Provides @Provides
@@ -18,6 +18,7 @@ import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.main.alerts.NominationPoolAlert.RedeemTokens import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.main.alerts.NominationPoolAlert.RedeemTokens
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.main.alerts.NominationPoolAlert.WaitingForNextEra import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.main.alerts.NominationPoolAlert.WaitingForNextEra
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.main.alerts.RealNominationPoolsAlertsInteractor.AlertsResolutionContext.PoolContext import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.main.alerts.RealNominationPoolsAlertsInteractor.AlertsResolutionContext.PoolContext
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.stakingBackingChainId
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novasama.substrate_sdk_android.hash.isPositive import io.novasama.substrate_sdk_android.hash.isPositive
import io.novasama.substrate_sdk_android.runtime.AccountId import io.novasama.substrate_sdk_android.runtime.AccountId
@@ -49,11 +50,13 @@ class RealNominationPoolsAlertsInteractor(
return flowOfAll { return flowOfAll {
val poolId = poolMember.poolId val poolId = poolMember.poolId
val poolStash = poolAccountDerivation.bondedAccountOf(poolId, chain.id) val poolStash = poolAccountDerivation.bondedAccountOf(poolId, chain.id)
// For nomination pools on parachains (like Asset Hub), get exposures from parent relay chain
val stakingBackingChainId = chain.stakingBackingChainId(Chain.Asset.StakingType.NOMINATION_POOLS)
combine( combine(
nominationPoolsSharedComputation.participatingPoolNominationsFlow(poolStash, poolId, chain.id, shareComputationScope), nominationPoolsSharedComputation.participatingPoolNominationsFlow(poolStash, poolId, chain.id, shareComputationScope),
nominationPoolsSharedComputation.unbondingPoolsFlow(poolId, chain.id, shareComputationScope), nominationPoolsSharedComputation.unbondingPoolsFlow(poolId, chain.id, shareComputationScope),
stakingSharedComputation.electedExposuresWithActiveEraFlow(chain.id, shareComputationScope), stakingSharedComputation.electedExposuresWithActiveEraFlow(stakingBackingChainId, shareComputationScope),
) { poolNominations, unbondingPools, (eraStakers, activeEra) -> ) { poolNominations, unbondingPools, (eraStakers, activeEra) ->
val alertsContext = AlertsResolutionContext( val alertsContext = AlertsResolutionContext(
eraStakers = eraStakers, eraStakers = eraStakers,
@@ -12,6 +12,9 @@ import io.novafoundation.nova.feature_staking_impl.domain.StakingInteractor
import io.novafoundation.nova.feature_staking_impl.domain.common.StakingSharedComputation import io.novafoundation.nova.feature_staking_impl.domain.common.StakingSharedComputation
import io.novafoundation.nova.feature_staking_impl.domain.common.electedExposuresInActiveEraFlow import io.novafoundation.nova.feature_staking_impl.domain.common.electedExposuresInActiveEraFlow
import io.novafoundation.nova.feature_staking_impl.domain.model.NetworkInfo import io.novafoundation.nova.feature_staking_impl.domain.model.NetworkInfo
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.stakingBackingChainId
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.feature_staking_impl.domain.model.StakingPeriod import io.novafoundation.nova.feature_staking_impl.domain.model.StakingPeriod
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common.NominationPoolMemberUseCase import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common.NominationPoolMemberUseCase
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
@@ -19,6 +22,8 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import java.math.BigInteger import java.math.BigInteger
@@ -35,6 +40,7 @@ class RealNominationPoolsNetworkInfoInteractor(
private val poolAccountDerivation: PoolAccountDerivation, private val poolAccountDerivation: PoolAccountDerivation,
private val relaychainStakingInteractor: StakingInteractor, private val relaychainStakingInteractor: StakingInteractor,
private val nominationPoolMemberUseCase: NominationPoolMemberUseCase, private val nominationPoolMemberUseCase: NominationPoolMemberUseCase,
private val chainRegistry: ChainRegistry,
) : NominationPoolsNetworkInfoInteractor { ) : NominationPoolsNetworkInfoInteractor {
override fun observeShouldShowNetworkInfo(): Flow<Boolean> { override fun observeShouldShowNetworkInfo(): Flow<Boolean> {
@@ -44,9 +50,13 @@ class RealNominationPoolsNetworkInfoInteractor(
override fun observeNetworkInfo( override fun observeNetworkInfo(
chainId: ChainId, chainId: ChainId,
sharedComputationScope: CoroutineScope sharedComputationScope: CoroutineScope
): Flow<NetworkInfo> { ): Flow<NetworkInfo> = flow {
return combine( // For nomination pools on parachains (like Asset Hub), get exposures from parent relay chain
relaychainStakingSharedComputation.electedExposuresInActiveEraFlow(chainId, sharedComputationScope), val chain = chainRegistry.getChain(chainId)
val stakingBackingChainId = chain.stakingBackingChainId(Chain.Asset.StakingType.NOMINATION_POOLS)
val networkInfoFlow = combine(
relaychainStakingSharedComputation.electedExposuresInActiveEraFlow(stakingBackingChainId, sharedComputationScope),
nominationPoolGlobalsRepository.observeMinJoinBond(chainId), nominationPoolGlobalsRepository.observeMinJoinBond(chainId),
nominationPoolGlobalsRepository.lastPoolIdFlow(chainId), nominationPoolGlobalsRepository.lastPoolIdFlow(chainId),
lockupDurationFlow(sharedComputationScope), lockupDurationFlow(sharedComputationScope),
@@ -59,6 +69,8 @@ class RealNominationPoolsNetworkInfoInteractor(
nominatorsCount = null nominatorsCount = null
) )
} }
emitAll(networkInfoFlow)
} }
private fun lockupDurationFlow(sharedComputationScope: CoroutineScope) = flowOf { relaychainStakingInteractor.getLockupDuration(sharedComputationScope) } private fun lockupDurationFlow(sharedComputationScope: CoroutineScope) = flowOf { relaychainStakingInteractor.getLockupDuration(sharedComputationScope) }
@@ -9,8 +9,10 @@ import io.novafoundation.nova.feature_staking_api.domain.model.EraIndex
import io.novafoundation.nova.feature_staking_api.domain.model.Exposure import io.novafoundation.nova.feature_staking_api.domain.model.Exposure
import io.novafoundation.nova.feature_staking_api.domain.model.Nominations import io.novafoundation.nova.feature_staking_api.domain.model.Nominations
import io.novafoundation.nova.feature_staking_impl.data.StakingOption import io.novafoundation.nova.feature_staking_impl.data.StakingOption
import io.novafoundation.nova.feature_staking_impl.data.stakingType
import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.models.PoolMember import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.models.PoolMember
import io.novafoundation.nova.feature_staking_impl.domain.common.StakingSharedComputation import io.novafoundation.nova.feature_staking_impl.domain.common.StakingSharedComputation
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.stakingBackingChainId
import io.novafoundation.nova.feature_staking_impl.domain.common.isWaiting import io.novafoundation.nova.feature_staking_impl.domain.common.isWaiting
import io.novafoundation.nova.feature_staking_impl.domain.model.StakeSummary import io.novafoundation.nova.feature_staking_impl.domain.model.StakeSummary
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common.NominationPoolSharedComputation import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common.NominationPoolSharedComputation
@@ -48,12 +50,14 @@ class RealNominationPoolStakeSummaryInteractor(
sharedComputationScope: CoroutineScope, sharedComputationScope: CoroutineScope,
): Flow<StakeSummary<PoolMemberStatus>> = flowOfAll { ): Flow<StakeSummary<PoolMemberStatus>> = flowOfAll {
val chainId = stakingOption.assetWithChain.chain.id val chainId = stakingOption.assetWithChain.chain.id
// For nomination pools on parachains (like Asset Hub), get exposures from parent relay chain
val stakingBackingChainId = stakingOption.assetWithChain.chain.stakingBackingChainId(stakingOption.stakingType)
val poolStash = poolAccountDerivation.bondedAccountOf(poolMember.poolId, chainId) val poolStash = poolAccountDerivation.bondedAccountOf(poolMember.poolId, chainId)
combineTransform( combineTransform(
nominationPoolSharedComputation.participatingBondedPoolStateFlow(poolStash, poolMember.poolId, chainId, sharedComputationScope), nominationPoolSharedComputation.participatingBondedPoolStateFlow(poolStash, poolMember.poolId, chainId, sharedComputationScope),
nominationPoolSharedComputation.participatingPoolNominationsFlow(poolStash, poolMember.poolId, chainId, sharedComputationScope), nominationPoolSharedComputation.participatingPoolNominationsFlow(poolStash, poolMember.poolId, chainId, sharedComputationScope),
stakingSharedComputation.electedExposuresWithActiveEraFlow(chainId, sharedComputationScope) stakingSharedComputation.electedExposuresWithActiveEraFlow(stakingBackingChainId, sharedComputationScope)
) { bondedPoolState, poolNominations, (eraStakers, activeEra) -> ) { bondedPoolState, poolNominations, (eraStakers, activeEra) ->
val activeStaked = bondedPoolState.amountOf(poolMember.points) val activeStaked = bondedPoolState.amountOf(poolMember.points)