diff --git a/packages/api-derive/src/staking/ownExposure.ts b/packages/api-derive/src/staking/ownExposure.ts index 1423af15fbf7..b20c057a9387 100644 --- a/packages/api-derive/src/staking/ownExposure.ts +++ b/packages/api-derive/src/staking/ownExposure.ts @@ -2,7 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 import type { Observable } from 'rxjs'; +import type { Option, u32 } from '@polkadot/types'; import type { EraIndex } from '@polkadot/types/interfaces'; +import type { SpStakingExposurePage } from '@polkadot/types/lookup'; +import type { AnyNumber } from '@polkadot/types-codec/types'; import type { DeriveApi, DeriveOwnExposure } from '../types.js'; import { combineLatest, map, of } from 'rxjs'; @@ -10,24 +13,28 @@ import { combineLatest, map, of } from 'rxjs'; import { firstMemo, memo } from '../util/index.js'; import { erasHistoricApplyAccount } from './util.js'; -export function _ownExposures (instanceId: string, api: DeriveApi): (accountId: Uint8Array | string, eras: EraIndex[], withActive: boolean) => Observable { - return memo(instanceId, (accountId: Uint8Array | string, eras: EraIndex[], _withActive: boolean): Observable => - eras.length +export function _ownExposures (instanceId: string, api: DeriveApi): (accountId: Uint8Array | string, eras: EraIndex[], withActive: boolean, page: u32 | AnyNumber) => Observable { + return memo(instanceId, (accountId: Uint8Array | string, eras: EraIndex[], _withActive: boolean, page: u32 | AnyNumber): Observable => { + return eras.length ? combineLatest([ + // Backwards and forward compat for historical integrity when using `erasHistoricApplyAccount` combineLatest(eras.map((e) => api.query.staking.erasStakersClipped(e, accountId))), - combineLatest(eras.map((e) => api.query.staking.erasStakers(e, accountId))) + combineLatest(eras.map((e) => api.query.staking.erasStakers(e, accountId))), + combineLatest(eras.map((e) => api.query.staking.erasStakersPaged>(e, accountId, page))), + combineLatest(eras.map((e) => api.query.staking.erasStakersOverview(e, accountId))) ]).pipe( - map(([clp, exp]): DeriveOwnExposure[] => - eras.map((era, index) => ({ clipped: clp[index], era, exposure: exp[index] })) + map(([clp, exp, paged, expMeta]): DeriveOwnExposure[] => + eras.map((era, index) => ({ clipped: clp[index], era, exposure: exp[index], exposureMeta: expMeta[index], exposurePaged: paged[index] })) ) ) - : of([]) + : of([]); + } ); } export const ownExposure = /*#__PURE__*/ firstMemo( - (api: DeriveApi, accountId: Uint8Array | string, era: EraIndex) => - api.derive.staking._ownExposures(accountId, [era], true) + (api: DeriveApi, accountId: Uint8Array | string, era: EraIndex, page?: u32 | AnyNumber) => + api.derive.staking._ownExposures(accountId, [era], true, page || 0) ); export const ownExposures = /*#__PURE__*/ erasHistoricApplyAccount('_ownExposures'); diff --git a/packages/api-derive/src/staking/query.ts b/packages/api-derive/src/staking/query.ts index f850e8a89114..796ddd61ea99 100644 --- a/packages/api-derive/src/staking/query.ts +++ b/packages/api-derive/src/staking/query.ts @@ -2,9 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 import type { Observable } from 'rxjs'; -import type { Option } from '@polkadot/types'; +import type { Option, u32 } from '@polkadot/types'; import type { AccountId, EraIndex } from '@polkadot/types/interfaces'; -import type { PalletStakingNominations, PalletStakingRewardDestination, PalletStakingStakingLedger, PalletStakingValidatorPrefs, SpStakingExposure } from '@polkadot/types/lookup'; +import type { PalletStakingNominations, PalletStakingRewardDestination, PalletStakingStakingLedger, PalletStakingValidatorPrefs, SpStakingExposurePage, SpStakingPagedExposureMetadata } from '@polkadot/types/lookup'; +import type { AnyNumber } from '@polkadot/types-codec/types'; import type { DeriveApi, DeriveStakingQuery, StakingQueryFlags } from '../types.js'; import { combineLatest, map, of, switchMap } from 'rxjs'; @@ -19,11 +20,12 @@ function rewardDestinationCompat (rewardDestination: PalletStakingRewardDestinat : (rewardDestination as PalletStakingRewardDestination); } -function parseDetails (stashId: AccountId, controllerIdOpt: Option | null, nominatorsOpt: Option, rewardDestinationOpts: Option | PalletStakingRewardDestination, validatorPrefs: PalletStakingValidatorPrefs, exposure: SpStakingExposure, stakingLedgerOpt: Option): DeriveStakingQuery { +function parseDetails (stashId: AccountId, controllerIdOpt: Option | null, nominatorsOpt: Option, rewardDestinationOpts: Option | PalletStakingRewardDestination, validatorPrefs: PalletStakingValidatorPrefs, exposure: Option, stakingLedgerOpt: Option, exposureMeta: Option): DeriveStakingQuery { return { accountId: stashId, controllerId: controllerIdOpt?.unwrapOr(null) || null, - exposure, + exposureMeta, + exposurePaged: exposure, nominators: nominatorsOpt.isSome ? nominatorsOpt.unwrap().targets : [], @@ -57,11 +59,12 @@ function getLedgers (api: DeriveApi, optIds: (Option | null)[], { wit ); } -function getStashInfo (api: DeriveApi, stashIds: AccountId[], activeEra: EraIndex, { withController, withDestination, withExposure, withLedger, withNominations, withPrefs }: StakingQueryFlags): Observable<[(Option | null)[], Option[], Option[], PalletStakingValidatorPrefs[], SpStakingExposure[]]> { +function getStashInfo (api: DeriveApi, stashIds: AccountId[], activeEra: EraIndex, { withController, withDestination, withExposure, withExposureMeta, withLedger, withNominations, withPrefs }: StakingQueryFlags, page: u32 | AnyNumber): Observable<[(Option | null)[], Option[], Option[], PalletStakingValidatorPrefs[], Option[], Option[]]> { const emptyNoms = api.registry.createType>('Option'); const emptyRewa = api.registry.createType>('RewardDestination'); - const emptyExpo = api.registry.createType('Exposure'); + const emptyExpo = api.registry.createType>('Option'); const emptyPrefs = api.registry.createType('ValidatorPrefs'); + const emptyExpoMeta = api.registry.createType>('Option'); return combineLatest([ withController || withLedger @@ -77,18 +80,21 @@ function getStashInfo (api: DeriveApi, stashIds: AccountId[], activeEra: EraInde ? combineLatest(stashIds.map((s) => api.query.staking.validators(s))) : of(stashIds.map(() => emptyPrefs)), withExposure - ? combineLatest(stashIds.map((s) => api.query.staking.erasStakers(activeEra, s))) - : of(stashIds.map(() => emptyExpo)) + ? combineLatest(stashIds.map((s) => api.query.staking.erasStakersPaged>(activeEra, s, page))) + : of(stashIds.map(() => emptyExpo)), + withExposureMeta + ? combineLatest(stashIds.map((s) => api.query.staking.erasStakersOverview(activeEra, s))) + : of(stashIds.map(() => emptyExpoMeta)) ]); } -function getBatch (api: DeriveApi, activeEra: EraIndex, stashIds: AccountId[], flags: StakingQueryFlags): Observable { - return getStashInfo(api, stashIds, activeEra, flags).pipe( - switchMap(([controllerIdOpt, nominatorsOpt, rewardDestination, validatorPrefs, exposure]): Observable => +function getBatch (api: DeriveApi, activeEra: EraIndex, stashIds: AccountId[], flags: StakingQueryFlags, page: u32 | AnyNumber): Observable { + return getStashInfo(api, stashIds, activeEra, flags, page).pipe( + switchMap(([controllerIdOpt, nominatorsOpt, rewardDestination, validatorPrefs, exposure, exposureMeta]): Observable => getLedgers(api, controllerIdOpt, flags).pipe( map((stakingLedgerOpts) => stashIds.map((stashId, index) => - parseDetails(stashId, controllerIdOpt[index], nominatorsOpt[index], rewardDestination[index], validatorPrefs[index], exposure[index], stakingLedgerOpts[index]) + parseDetails(stashId, controllerIdOpt[index], nominatorsOpt[index], rewardDestination[index], validatorPrefs[index], exposure[index], stakingLedgerOpts[index], exposureMeta[index]) ) ) ) @@ -101,18 +107,19 @@ function getBatch (api: DeriveApi, activeEra: EraIndex, stashIds: AccountId[], f * @description From a stash, retrieve the controllerId and all relevant details */ export const query = /*#__PURE__*/ firstMemo( - (api: DeriveApi, accountId: Uint8Array | string, flags: StakingQueryFlags) => - api.derive.staking.queryMulti([accountId], flags) + (api: DeriveApi, accountId: Uint8Array | string, flags: StakingQueryFlags, page?: u32) => + api.derive.staking.queryMulti([accountId], flags, page) ); -export function queryMulti (instanceId: string, api: DeriveApi): (accountIds: (Uint8Array | string)[], flags: StakingQueryFlags) => Observable { - return memo(instanceId, (accountIds: (Uint8Array | string)[], flags: StakingQueryFlags): Observable => +export function queryMulti (instanceId: string, api: DeriveApi): (accountIds: (Uint8Array | string)[], flags: StakingQueryFlags, page?: u32 | AnyNumber) => Observable { + return memo(instanceId, (accountIds: (Uint8Array | string)[], flags: StakingQueryFlags, page?: u32 | AnyNumber): Observable => api.derive.session.indexes().pipe( switchMap(({ activeEra }): Observable => { const stashIds = accountIds.map((a) => api.registry.createType('AccountId', a)); + const p = page || 0; return stashIds.length - ? getBatch(api, activeEra, stashIds, flags) + ? getBatch(api, activeEra, stashIds, flags, p) : of([]); }) ) diff --git a/packages/api-derive/src/staking/types.ts b/packages/api-derive/src/staking/types.ts index 8438bc150031..6f844987ab04 100644 --- a/packages/api-derive/src/staking/types.ts +++ b/packages/api-derive/src/staking/types.ts @@ -1,8 +1,9 @@ // Copyright 2017-2024 @polkadot/api-derive authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { Option } from '@polkadot/types'; import type { AccountId, Balance, EraIndex, RewardPoint } from '@polkadot/types/interfaces'; -import type { PalletStakingRewardDestination, PalletStakingStakingLedger, PalletStakingValidatorPrefs, SpStakingExposure, SpStakingExposurePage } from '@polkadot/types/lookup'; +import type { PalletStakingRewardDestination, PalletStakingStakingLedger, PalletStakingValidatorPrefs, SpStakingExposure, SpStakingExposurePage, SpStakingPagedExposureMetadata } from '@polkadot/types/lookup'; import type { BN } from '@polkadot/util'; import type { DeriveSessionIndexes } from '../session/types.js'; @@ -42,8 +43,10 @@ export interface DeriveStakerPoints { export interface DeriveOwnExposure { clipped: SpStakingExposure; + exposurePaged: Option; era: EraIndex; exposure: SpStakingExposure; + exposureMeta: Option; } export interface DeriveEraExposureNominating { @@ -115,7 +118,8 @@ export interface DeriveStakingValidators { export interface DeriveStakingStash { controllerId: AccountId | null; - exposure: SpStakingExposure; + exposurePaged: Option; + exposureMeta: Option; nominators: AccountId[]; rewardDestination: PalletStakingRewardDestination | null; stashId: AccountId; @@ -160,4 +164,5 @@ export interface StakingQueryFlags { withLedger?: boolean; withNominations?: boolean; withPrefs?: boolean; + withExposureMeta?: boolean; } diff --git a/packages/api-derive/src/staking/util.ts b/packages/api-derive/src/staking/util.ts index 2a9a5c20470c..343cc7b75126 100644 --- a/packages/api-derive/src/staking/util.ts +++ b/packages/api-derive/src/staking/util.ts @@ -3,7 +3,9 @@ import type { Observable } from 'rxjs'; import type { ObsInnerType } from '@polkadot/api-base/types'; +import type { u32 } from '@polkadot/types'; import type { EraIndex } from '@polkadot/types/interfaces'; +import type { AnyNumber } from '@polkadot/types-codec/types'; import type { ExactDerive } from '../derive.js'; import type { DeriveApi } from '../types.js'; @@ -60,9 +62,9 @@ export function erasHistoricApplyAccount // Cannot quite get the typing right, but it is right in the code // eslint-disable-next-line @typescript-eslint/no-unsafe-return - memo(instanceId, (accountId: string | Uint8Array, withActive = false) => + memo(instanceId, (accountId: string | Uint8Array, withActive = false, page?: u32 | AnyNumber) => api.derive.staking.erasHistoric(withActive).pipe( - switchMap((e) => api.derive.staking[fn](accountId, e, withActive)) + switchMap((e) => api.derive.staking[fn](accountId, e, withActive, page || 0)) ) ) as any; } diff --git a/packages/api-derive/src/staking/validators.ts b/packages/api-derive/src/staking/validators.ts index 88e7c4c89414..90362f0129eb 100644 --- a/packages/api-derive/src/staking/validators.ts +++ b/packages/api-derive/src/staking/validators.ts @@ -11,11 +11,11 @@ import { memo } from '../util/index.js'; export function nextElected (instanceId: string, api: DeriveApi): () => Observable { return memo(instanceId, (): Observable => - api.query.staking.erasStakers + api.query.staking.erasStakersPaged ? api.derive.session.indexes().pipe( // only populate for next era in the last session, so track both here - entries are not // subscriptions, so we need a trigger - currentIndex acts as that trigger to refresh - switchMap(({ currentEra }) => api.query.staking.erasStakers.keys(currentEra)), + switchMap(({ currentEra }) => api.query.staking.erasStakersPaged.keys(currentEra)), map((keys) => keys.map(({ args: [, accountId] }) => accountId)) ) : api.query.staking['currentElected']()