diff --git a/src/mappings/era/ValidatorEraInfoDataSource.ts b/src/mappings/era/ValidatorEraInfoDataSource.ts index 6220e87..a65bfa0 100644 --- a/src/mappings/era/ValidatorEraInfoDataSource.ts +++ b/src/mappings/era/ValidatorEraInfoDataSource.ts @@ -1,7 +1,9 @@ import {StakeTarget} from "./EraInfoDataSource"; import {CachingEraInfoDataSource} from "./CachingEraInfoDataSource"; -import {BigFromINumber} from "../utils"; +import {BigFromINumber, SpStakingPagedExposureMetadata, SpStakingExposurePage} from "../utils"; import {PalletStakingExposure} from "@polkadot/types/lookup"; +import {Option} from "@polkadot/types-codec"; +import {INumber} from "@polkadot/types-codec/types/interfaces"; export class ValidatorEraInfoDataSource extends CachingEraInfoDataSource { @@ -20,6 +22,14 @@ export class ValidatorEraInfoDataSource extends CachingEraInfoDataSource { protected async fetchEraStakers(): Promise { const era = await this.era() + if (api.query.staking.erasStakersOverview) { + return await this.fetchEraStakersPaged(era); + } else { + return await this.fetchEraStakersClipped(era); + } + } + + private async fetchEraStakersClipped(era: number): Promise { const exposures = await api.query.staking.erasStakersClipped.entries(era) return exposures.map(([key, exp]) => { @@ -43,4 +53,43 @@ export class ValidatorEraInfoDataSource extends CachingEraInfoDataSource { }) } + private async fetchEraStakersPaged(era: number): Promise { + const overview = await api.query.staking.erasStakersOverview.entries(era) + const pages = await api.query.staking.erasStakersPaged.entries(era) + + const othersCounted = pages.reduce((accumulator, [key, exp]) => { + const exposure = (exp as unknown as Option).unwrap() + const [, validatorId, pageId] = key.args + const pageNumber = (pageId as INumber).toNumber() + const validatorAddress = validatorId.toString() + + const others = exposure.others.map(({who, value}) => { + return { + address: who.toString(), + amount: value.toBigInt() + } + }); + + (accumulator[validatorAddress] = accumulator[validatorAddress] || {})[pageNumber] = others; + return accumulator; + }, {}) + + return overview.map(([key, exp]) => { + const exposure = (exp as unknown as Option).unwrap() + const [, validatorId] = key.args + let validatorAddress = validatorId.toString() + + let others = [] + for (let i = 0; i < exposure.pageCount.toNumber(); ++i) { + others.push(...othersCounted[validatorAddress][i]) + }; + + return { + address: validatorAddress, + selfStake: exposure.own.toBigInt(), + totalStake: BigFromINumber(exposure.total), + others: others + } + }); + } } \ No newline at end of file diff --git a/src/mappings/utils.ts b/src/mappings/utils.ts index b4d564d..1b95997 100644 --- a/src/mappings/utils.ts +++ b/src/mappings/utils.ts @@ -1,7 +1,7 @@ import {INumber} from "@polkadot/types-codec/types/interfaces"; import {Big} from "big.js" -import {Perbill, Percent} from "@polkadot/types/interfaces/runtime/types"; -import {Compact} from '@polkadot/types-codec' +import {Perbill, Percent, AccountId32} from "@polkadot/types/interfaces/runtime/types"; +import {Compact, Struct, Vec} from '@polkadot/types-codec' export function BigFromINumber(number: INumber): Big { return Big(number.toString()) @@ -65,4 +65,21 @@ export function toPlanks(amount: Big): Big { export function aprToApy(apr: number): number { return Math.exp(apr) - 1.0 +} + +export interface SpStakingPagedExposureMetadata extends Struct { + readonly total: INumber; + readonly own: INumber; + readonly nominatorCount: INumber; + readonly pageCount: INumber; +} + +interface SpStakingIndividualExposure extends Struct { + readonly who: AccountId32; + readonly value: INumber; +} + +export interface SpStakingExposurePage extends Struct { + readonly pageTotal: INumber; + readonly others: Vec; } \ No newline at end of file