Move api-derive to correct location (#4343)
* Move api-derive to correct location * Adjust (linting ok) * No composite * ApiDerive split * Adjust * Still WIP, getting closer * It lints * derive build issues sorted * It builds :) * Add system_health to mock * api-augment usage * Cleanups * Adjust paths * linting * Some <'s * Revert <>'s * linting * ordering * Augmentation tweaks * yarn.lock * export
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Balance } from '@polkadot/types/interfaces';
|
||||
import type { PalletStakingStakingLedger, PalletStakingUnlockChunk } from '@polkadot/types/lookup';
|
||||
import type { DeriveApi, DeriveSessionInfo, DeriveStakingAccount, DeriveStakingKeys, DeriveStakingQuery, DeriveUnlocking } from '../types';
|
||||
|
||||
import { combineLatest, map, switchMap } from 'rxjs';
|
||||
|
||||
import { BN, BN_ZERO } from '@polkadot/util';
|
||||
|
||||
import { firstMemo, memo } from '../util';
|
||||
|
||||
const QUERY_OPTS = {
|
||||
withDestination: true,
|
||||
withLedger: true,
|
||||
withNominations: true,
|
||||
withPrefs: true
|
||||
};
|
||||
|
||||
function groupByEra (list: PalletStakingUnlockChunk[]): Record<string, BN> {
|
||||
return list.reduce((map: Record<string, BN>, { era, value }): Record<string, BN> => {
|
||||
const key = era.toString();
|
||||
|
||||
map[key] = (map[key] || BN_ZERO).add(value.unwrap());
|
||||
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function calculateUnlocking (api: DeriveApi, stakingLedger: PalletStakingStakingLedger | undefined, sessionInfo: DeriveSessionInfo): DeriveUnlocking[] | undefined {
|
||||
const results = Object
|
||||
.entries(groupByEra(
|
||||
(stakingLedger?.unlocking || []).filter(({ era }) => era.unwrap().gt(sessionInfo.activeEra))
|
||||
))
|
||||
.map(([eraString, value]): DeriveUnlocking => ({
|
||||
remainingEras: new BN(eraString).isub(sessionInfo.activeEra),
|
||||
value: api.registry.createType('Balance', value)
|
||||
}));
|
||||
|
||||
return results.length
|
||||
? results
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function redeemableSum (api: DeriveApi, stakingLedger: PalletStakingStakingLedger | undefined, sessionInfo: DeriveSessionInfo): Balance {
|
||||
return api.registry.createType('Balance', (stakingLedger?.unlocking || [] as PalletStakingUnlockChunk[]).reduce((total, { era, value }): BN => {
|
||||
return sessionInfo.activeEra.gte(era.unwrap())
|
||||
? total.iadd(value.unwrap())
|
||||
: total;
|
||||
}, new BN(0)));
|
||||
}
|
||||
|
||||
function parseResult (api: DeriveApi, sessionInfo: DeriveSessionInfo, keys: DeriveStakingKeys, query: DeriveStakingQuery): DeriveStakingAccount {
|
||||
return {
|
||||
...keys,
|
||||
...query,
|
||||
redeemable: redeemableSum(api, query.stakingLedger, sessionInfo),
|
||||
unlocking: calculateUnlocking(api, query.stakingLedger, sessionInfo)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description From a list of stashes, fill in all the relevant staking details
|
||||
*/
|
||||
export function accounts (instanceId: string, api: DeriveApi): (accountIds: (Uint8Array | string)[]) => Observable<DeriveStakingAccount[]> {
|
||||
return memo(instanceId, (accountIds: (Uint8Array | string)[]): Observable<DeriveStakingAccount[]> =>
|
||||
api.derive.session.info().pipe(
|
||||
switchMap((sessionInfo) =>
|
||||
combineLatest([
|
||||
api.derive.staking.keysMulti(accountIds),
|
||||
api.derive.staking.queryMulti(accountIds, QUERY_OPTS)
|
||||
]).pipe(
|
||||
map(([keys, queries]) =>
|
||||
queries.map((q, index) => parseResult(api, sessionInfo, keys[index], q))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description From a stash, retrieve the controllerId and fill in all the relevant staking details
|
||||
*/
|
||||
export const account = firstMemo(
|
||||
(api: DeriveApi, accountId: Uint8Array | string) =>
|
||||
api.derive.staking.accounts([accountId])
|
||||
);
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { EraIndex } from '@polkadot/types/interfaces';
|
||||
|
||||
import { deriveCache } from '../util';
|
||||
|
||||
export function getEraCache <T> (CACHE_KEY: string, era: EraIndex, withActive?: boolean): [string, T | undefined] {
|
||||
const cacheKey = `${CACHE_KEY}-${era.toString()}`;
|
||||
|
||||
return [
|
||||
cacheKey,
|
||||
withActive
|
||||
? undefined
|
||||
: deriveCache.get<T>(cacheKey)
|
||||
];
|
||||
}
|
||||
|
||||
export function getEraMultiCache <T> (CACHE_KEY: string, eras: EraIndex[], withActive?: boolean): T[] {
|
||||
const cached: T[] = withActive
|
||||
? []
|
||||
: eras
|
||||
.map((e) => deriveCache.get<T>(`${CACHE_KEY}-${e.toString()}`))
|
||||
.filter((v): v is T => !!v);
|
||||
|
||||
return cached;
|
||||
}
|
||||
|
||||
export function setEraCache <T extends { era: EraIndex }> (cacheKey: string, withActive: boolean, value: T): T {
|
||||
!withActive && deriveCache.set(cacheKey, value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function setEraMultiCache <T extends { era: EraIndex }> (CACHE_KEY: string, withActive: boolean, values: T[]): T[] {
|
||||
!withActive && values.forEach((v) => deriveCache.set(`${CACHE_KEY}-${v.era.toString()}`, v));
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
export function filterCachedEras <T extends { era: EraIndex }> (eras: EraIndex[], cached: T[], query: T[]): T[] {
|
||||
return eras.map((e) =>
|
||||
cached.find(({ era }) => e.eq(era)) ||
|
||||
query.find(({ era }) => e.eq(era)) as T
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { PalletStakingEraRewardPoints } from '@polkadot/types/lookup';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @description Retrieve the staking overview, including elected and points earned
|
||||
*/
|
||||
export function currentPoints (instanceId: string, api: DeriveApi): () => Observable<PalletStakingEraRewardPoints> {
|
||||
return memo(instanceId, (): Observable<PalletStakingEraRewardPoints> =>
|
||||
api.derive.session.indexes().pipe(
|
||||
switchMap(({ activeEra }) =>
|
||||
api.query.staking.erasRewardPoints(activeEra)
|
||||
)
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveStakingElected, StakingQueryFlags } from '../types';
|
||||
|
||||
import { map, switchMap } from 'rxjs';
|
||||
|
||||
import { arrayFlatten } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
const DEFAULT_FLAGS = { withController: true, withExposure: true, withPrefs: true };
|
||||
|
||||
function combineAccounts (nextElected: AccountId[], validators: AccountId[]): AccountId[] {
|
||||
return arrayFlatten([nextElected, validators.filter((v) => !nextElected.find((n) => n.eq(v)))]);
|
||||
}
|
||||
|
||||
export function electedInfo (instanceId: string, api: DeriveApi): (flags?: StakingQueryFlags) => Observable<DeriveStakingElected> {
|
||||
return memo(instanceId, (flags: StakingQueryFlags = DEFAULT_FLAGS): Observable<DeriveStakingElected> =>
|
||||
api.derive.staking.validators().pipe(
|
||||
switchMap(({ nextElected, validators }): Observable<DeriveStakingElected> =>
|
||||
api.derive.staking.queryMulti(combineAccounts(nextElected, validators), flags).pipe(
|
||||
map((info): DeriveStakingElected => ({
|
||||
info,
|
||||
nextElected,
|
||||
validators
|
||||
}))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { StorageKey } from '@polkadot/types';
|
||||
import type { AccountId, EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { PalletStakingExposure } from '@polkadot/types/lookup';
|
||||
import type { DeriveApi, DeriveEraExposure, DeriveEraNominatorExposure, DeriveEraValidatorExposure } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { getEraCache, setEraCache } from './cache';
|
||||
import { combineEras, erasHistoricApply, singleEra } from './util';
|
||||
|
||||
type KeysAndExposures = [StorageKey<[EraIndex, AccountId]>, PalletStakingExposure][];
|
||||
|
||||
const CACHE_KEY = 'eraExposure';
|
||||
|
||||
function mapStakers (era: EraIndex, stakers: KeysAndExposures): DeriveEraExposure {
|
||||
const nominators: DeriveEraNominatorExposure = {};
|
||||
const validators: DeriveEraValidatorExposure = {};
|
||||
|
||||
stakers.forEach(([key, exposure]): void => {
|
||||
const validatorId = key.args[1].toString();
|
||||
|
||||
validators[validatorId] = exposure;
|
||||
|
||||
exposure.others.forEach(({ who }, validatorIndex): void => {
|
||||
const nominatorId = who.toString();
|
||||
|
||||
nominators[nominatorId] = nominators[nominatorId] || [];
|
||||
nominators[nominatorId].push({ validatorId, validatorIndex });
|
||||
});
|
||||
});
|
||||
|
||||
return { era, nominators, validators };
|
||||
}
|
||||
|
||||
export function _eraExposure (instanceId: string, api: DeriveApi): (era: EraIndex, withActive?: boolean) => Observable<DeriveEraExposure> {
|
||||
return memo(instanceId, (era: EraIndex, withActive = false): Observable<DeriveEraExposure> => {
|
||||
const [cacheKey, cached] = getEraCache<DeriveEraExposure>(CACHE_KEY, era, withActive);
|
||||
|
||||
return cached
|
||||
? of(cached)
|
||||
: api.query.staking.erasStakersClipped.entries(era).pipe(
|
||||
map((r) => setEraCache(cacheKey, withActive, mapStakers(era, r)))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export const eraExposure = singleEra('_eraExposure');
|
||||
export const _erasExposure = combineEras('_eraExposure');
|
||||
export const erasExposure = erasHistoricApply('_erasExposure');
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, u32 } from '@polkadot/types';
|
||||
import type { ActiveEraInfo, EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { BN_ONE, BN_ZERO } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
export function erasHistoric (instanceId: string, api: DeriveApi): (withActive?: boolean) => Observable<EraIndex[]> {
|
||||
return memo(instanceId, (withActive?: boolean): Observable<EraIndex[]> =>
|
||||
api.queryMulti<[Option<ActiveEraInfo>, u32]>([
|
||||
api.query.staking.activeEra,
|
||||
api.query.staking.historyDepth
|
||||
]).pipe(
|
||||
map(([activeEraOpt, historyDepth]): EraIndex[] => {
|
||||
const result: EraIndex[] = [];
|
||||
const max = historyDepth.toNumber();
|
||||
const activeEra: BN = activeEraOpt.unwrapOrDefault().index;
|
||||
let lastEra = activeEra;
|
||||
|
||||
while (lastEra.gte(BN_ZERO) && (result.length < max)) {
|
||||
if ((lastEra !== activeEra) || (withActive === true)) {
|
||||
result.push(api.registry.createType('EraIndex', lastEra));
|
||||
}
|
||||
|
||||
lastEra = lastEra.sub(BN_ONE);
|
||||
}
|
||||
|
||||
// go from oldest to newest
|
||||
return result.reverse();
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { PalletStakingEraRewardPoints } from '@polkadot/types/lookup';
|
||||
import type { DeriveApi, DeriveEraPoints, DeriveEraValPoints } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { BN_ZERO } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { filterCachedEras, getEraMultiCache, setEraMultiCache } from './cache';
|
||||
import { erasHistoricApply, filterEras } from './util';
|
||||
|
||||
const CACHE_KEY = 'eraPoints';
|
||||
|
||||
function mapValidators ({ individual }: PalletStakingEraRewardPoints): DeriveEraValPoints {
|
||||
return [...individual.entries()]
|
||||
.filter(([, points]) => points.gt(BN_ZERO))
|
||||
.reduce((result: DeriveEraValPoints, [validatorId, points]): DeriveEraValPoints => {
|
||||
result[validatorId.toString()] = points;
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function mapPoints (eras: EraIndex[], points: PalletStakingEraRewardPoints[]): DeriveEraPoints[] {
|
||||
return eras.map((era, index): DeriveEraPoints => ({
|
||||
era,
|
||||
eraPoints: points[index].total,
|
||||
validators: mapValidators(points[index])
|
||||
}));
|
||||
}
|
||||
|
||||
export function _erasPoints (instanceId: string, api: DeriveApi): (eras: EraIndex[], withActive: boolean) => Observable<DeriveEraPoints[]> {
|
||||
return memo(instanceId, (eras: EraIndex[], withActive: boolean): Observable<DeriveEraPoints[]> => {
|
||||
if (!eras.length) {
|
||||
return of([]);
|
||||
}
|
||||
|
||||
const cached = getEraMultiCache<DeriveEraPoints>(CACHE_KEY, eras, withActive);
|
||||
const remaining = filterEras(eras, cached);
|
||||
|
||||
return !remaining.length
|
||||
? of(cached)
|
||||
: api.query.staking.erasRewardPoints.multi(remaining).pipe(
|
||||
map((p) => filterCachedEras(eras, cached, setEraMultiCache(CACHE_KEY, withActive, mapPoints(remaining, p))))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export const erasPoints = erasHistoricApply('_erasPoints');
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { StorageKey } from '@polkadot/types';
|
||||
import type { EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { PalletStakingValidatorPrefs } from '@polkadot/types/lookup';
|
||||
import type { DeriveApi, DeriveEraPrefs, DeriveEraValPrefs } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { getEraCache, setEraCache } from './cache';
|
||||
import { combineEras, erasHistoricApply, singleEra } from './util';
|
||||
|
||||
const CACHE_KEY = 'eraPrefs';
|
||||
|
||||
function mapPrefs (era: EraIndex, all: [StorageKey, PalletStakingValidatorPrefs][]): DeriveEraPrefs {
|
||||
const validators: DeriveEraValPrefs = {};
|
||||
|
||||
all.forEach(([key, prefs]): void => {
|
||||
validators[key.args[1].toString()] = prefs;
|
||||
});
|
||||
|
||||
return { era, validators };
|
||||
}
|
||||
|
||||
export function _eraPrefs (instanceId: string, api: DeriveApi): (era: EraIndex, withActive: boolean) => Observable<DeriveEraPrefs> {
|
||||
return memo(instanceId, (era: EraIndex, withActive: boolean): Observable<DeriveEraPrefs> => {
|
||||
const [cacheKey, cached] = getEraCache<DeriveEraPrefs>(CACHE_KEY, era, withActive);
|
||||
|
||||
return cached
|
||||
? of(cached)
|
||||
: api.query.staking.erasValidatorPrefs.entries(era).pipe(
|
||||
map((r) => setEraCache(cacheKey, withActive, mapPrefs(era, r)))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export const eraPrefs = singleEra('_eraPrefs');
|
||||
export const _erasPrefs = combineEras('_eraPrefs');
|
||||
export const erasPrefs = erasHistoricApply('_erasPrefs');
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { Balance, EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveEraRewards } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { filterCachedEras, getEraMultiCache, setEraMultiCache } from './cache';
|
||||
import { erasHistoricApply, filterEras } from './util';
|
||||
|
||||
const CACHE_KEY = 'eraRewards';
|
||||
|
||||
function mapRewards (eras: EraIndex[], optRewards: Option<Balance>[]): DeriveEraRewards[] {
|
||||
return eras.map((era, index): DeriveEraRewards => ({
|
||||
era,
|
||||
eraReward: optRewards[index].unwrapOrDefault()
|
||||
}));
|
||||
}
|
||||
|
||||
export function _erasRewards (instanceId: string, api: DeriveApi): (eras: EraIndex[], withActive: boolean) => Observable<DeriveEraRewards[]> {
|
||||
return memo(instanceId, (eras: EraIndex[], withActive: boolean): Observable<DeriveEraRewards[]> => {
|
||||
if (!eras.length) {
|
||||
return of([]);
|
||||
}
|
||||
|
||||
const cached = getEraMultiCache<DeriveEraRewards>(CACHE_KEY, eras, withActive);
|
||||
const remaining = filterEras(eras, cached);
|
||||
|
||||
if (!remaining.length) {
|
||||
return of(cached);
|
||||
}
|
||||
|
||||
return api.query.staking.erasValidatorReward.multi(remaining).pipe(
|
||||
map((r) => filterCachedEras(eras, cached, setEraMultiCache(CACHE_KEY, withActive, mapRewards(remaining, r))))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export const erasRewards = erasHistoricApply('_erasRewards');
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, StorageKey } from '@polkadot/types';
|
||||
import type { BalanceOf, EraIndex, Perbill } from '@polkadot/types/interfaces';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
import type { DeriveApi, DeriveEraSlashes, DeriveEraValSlash } from '../types';
|
||||
|
||||
import { combineLatest, map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { getEraCache, setEraCache } from './cache';
|
||||
import { combineEras, erasHistoricApply, singleEra } from './util';
|
||||
|
||||
const CACHE_KEY = 'eraSlashes';
|
||||
|
||||
function mapSlashes (era: EraIndex, noms: [StorageKey, Option<BalanceOf>][], vals: [StorageKey, Option<ITuple<[Perbill, BalanceOf]>>][]): DeriveEraSlashes {
|
||||
const nominators: DeriveEraValSlash = {};
|
||||
const validators: DeriveEraValSlash = {};
|
||||
|
||||
noms.forEach(([key, optBalance]): void => {
|
||||
nominators[key.args[1].toString()] = optBalance.unwrap();
|
||||
});
|
||||
|
||||
vals.forEach(([key, optRes]): void => {
|
||||
validators[key.args[1].toString()] = optRes.unwrapOrDefault()[1];
|
||||
});
|
||||
|
||||
return { era, nominators, validators };
|
||||
}
|
||||
|
||||
export function _eraSlashes (instanceId: string, api: DeriveApi): (era: EraIndex, withActive: boolean) => Observable<DeriveEraSlashes> {
|
||||
return memo(instanceId, (era: EraIndex, withActive: boolean): Observable<DeriveEraSlashes> => {
|
||||
const [cacheKey, cached] = getEraCache<DeriveEraSlashes>(CACHE_KEY, era, withActive);
|
||||
|
||||
return cached
|
||||
? of(cached)
|
||||
: combineLatest([
|
||||
api.query.staking.nominatorSlashInEra.entries(era),
|
||||
api.query.staking.validatorSlashInEra.entries(era)
|
||||
]).pipe(
|
||||
map(([n, v]) => setEraCache(cacheKey, withActive, mapSlashes(era, n, v)))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export const eraSlashes = singleEra('_eraSlashes');
|
||||
export const _erasSlashes = combineEras('_eraSlashes');
|
||||
export const erasSlashes = erasHistoricApply('_erasSlashes');
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './account';
|
||||
export * from './currentPoints';
|
||||
export * from './erasExposure';
|
||||
export * from './erasHistoric';
|
||||
export * from './erasPoints';
|
||||
export * from './erasPrefs';
|
||||
export * from './erasRewards';
|
||||
export * from './erasSlashes';
|
||||
export * from './electedInfo';
|
||||
export * from './keys';
|
||||
export * from './overview';
|
||||
export * from './ownExposure';
|
||||
export * from './ownSlashes';
|
||||
export * from './query';
|
||||
export * from './stakerExposure';
|
||||
export * from './stakerPoints';
|
||||
export * from './stakerPrefs';
|
||||
export * from './stakerRewards';
|
||||
export * from './stakerSlashes';
|
||||
export * from './stashes';
|
||||
export * from './validators';
|
||||
export * from './waitingInfo';
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, Vec } from '@polkadot/types';
|
||||
import type { AccountId } from '@polkadot/types/interfaces';
|
||||
import type { NodeRuntimeSessionKeys } from '@polkadot/types/lookup';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
import type { DeriveApi } from '../types';
|
||||
import type { DeriveStakingKeys } from './types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { firstMemo, memo } from '../util';
|
||||
|
||||
function extractsIds (stashId: Uint8Array | string, queuedKeys: [AccountId, NodeRuntimeSessionKeys | AccountId[]][], nextKeys: Option<NodeRuntimeSessionKeys>): DeriveStakingKeys {
|
||||
const sessionIds = (queuedKeys.find(([currentId]) => currentId.eq(stashId)) || [undefined, [] as AccountId[]])[1];
|
||||
const nextSessionIds = nextKeys.unwrapOr([] as AccountId[]);
|
||||
|
||||
return {
|
||||
nextSessionIds: Array.isArray(nextSessionIds)
|
||||
? nextSessionIds
|
||||
: [...nextSessionIds.values()] as AccountId[],
|
||||
sessionIds: Array.isArray(sessionIds)
|
||||
? sessionIds
|
||||
: [...sessionIds.values()] as AccountId[]
|
||||
};
|
||||
}
|
||||
|
||||
export const keys = firstMemo(
|
||||
(api: DeriveApi, stashId: Uint8Array | string) =>
|
||||
api.derive.staking.keysMulti([stashId])
|
||||
);
|
||||
|
||||
export function keysMulti (instanceId: string, api: DeriveApi): (stashIds: (Uint8Array | string)[]) => Observable<DeriveStakingKeys[]> {
|
||||
return memo(instanceId, (stashIds: (Uint8Array | string)[]): Observable<DeriveStakingKeys[]> =>
|
||||
stashIds.length
|
||||
? api.query.session.queuedKeys().pipe(
|
||||
switchMap((queuedKeys): Observable<[Vec<ITuple<[AccountId, NodeRuntimeSessionKeys]>>, Option<NodeRuntimeSessionKeys>[]]> =>
|
||||
combineLatest([
|
||||
of(queuedKeys),
|
||||
api.consts.session?.dedupKeyPrefix
|
||||
? api.query.session.nextKeys.multi(stashIds.map((stashId) => [api.consts.session.dedupKeyPrefix, stashId]))
|
||||
: api.query.session.nextKeys.multi(stashIds)
|
||||
])
|
||||
),
|
||||
map(([queuedKeys, nextKeys]) =>
|
||||
stashIds.map((stashId, index) => extractsIds(stashId, queuedKeys, nextKeys[index]))
|
||||
)
|
||||
)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { DeriveApi, DeriveStakingOverview } from '../types';
|
||||
|
||||
import { combineLatest, map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @description Retrieve the staking overview, including elected and points earned
|
||||
*/
|
||||
export function overview (instanceId: string, api: DeriveApi): () => Observable<DeriveStakingOverview> {
|
||||
return memo(instanceId, (): Observable<DeriveStakingOverview> =>
|
||||
combineLatest([
|
||||
api.derive.session.indexes(),
|
||||
api.derive.staking.validators()
|
||||
]).pipe(
|
||||
map(([indexes, { nextElected, validators }]): DeriveStakingOverview => ({
|
||||
...indexes,
|
||||
nextElected,
|
||||
validators
|
||||
}))
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { QueryableStorageMultiArg } from '@polkadot/api-base/types';
|
||||
import type { EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { PalletStakingExposure } from '@polkadot/types/lookup';
|
||||
import type { DeriveApi, DeriveOwnExposure } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { firstMemo, memo } from '../util';
|
||||
import { erasHistoricApplyAccount } from './util';
|
||||
|
||||
export function _ownExposures (instanceId: string, api: DeriveApi): (accountId: Uint8Array | string, eras: EraIndex[], withActive: boolean) => Observable<DeriveOwnExposure[]> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
return memo(instanceId, (accountId: Uint8Array | string, eras: EraIndex[], _withActive: boolean): Observable<DeriveOwnExposure[]> =>
|
||||
eras.length
|
||||
? api.queryMulti<PalletStakingExposure[]>([
|
||||
...eras.map((e): QueryableStorageMultiArg<'rxjs'> => [api.query.staking.erasStakersClipped, [e, accountId]]),
|
||||
...eras.map((e): QueryableStorageMultiArg<'rxjs'> => [api.query.staking.erasStakers, [e, accountId]])
|
||||
]).pipe(
|
||||
map((all): DeriveOwnExposure[] =>
|
||||
eras.map((era, index) => ({ clipped: all[index], era, exposure: all[eras.length + index] }))
|
||||
)
|
||||
)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
|
||||
export const ownExposure = firstMemo(
|
||||
(api: DeriveApi, accountId: Uint8Array | string, era: EraIndex) =>
|
||||
api.derive.staking._ownExposures(accountId, [era], true)
|
||||
);
|
||||
|
||||
export const ownExposures = erasHistoricApplyAccount('_ownExposures');
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { QueryableStorageMultiArg } from '@polkadot/api-base/types';
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { BalanceOf, EraIndex, Perbill } from '@polkadot/types/interfaces';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
import type { DeriveApi, DeriveStakerSlashes } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { firstMemo, memo } from '../util';
|
||||
import { erasHistoricApplyAccount } from './util';
|
||||
|
||||
export function _ownSlashes (instanceId: string, api: DeriveApi): (accountId: Uint8Array | string, eras: EraIndex[], withActive: boolean) => Observable<DeriveStakerSlashes[]> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
return memo(instanceId, (accountId: Uint8Array | string, eras: EraIndex[], _withActive: boolean): Observable<DeriveStakerSlashes[]> =>
|
||||
eras.length
|
||||
? api.queryMulti<(Option<ITuple<[Perbill, BalanceOf]>> | Option<BalanceOf>)[]>([
|
||||
...eras.map((e): QueryableStorageMultiArg<'rxjs'> => [api.query.staking.validatorSlashInEra, [e, accountId]]),
|
||||
...eras.map((e): QueryableStorageMultiArg<'rxjs'> => [api.query.staking.nominatorSlashInEra, [e, accountId]])
|
||||
]).pipe(
|
||||
map((values): DeriveStakerSlashes[] =>
|
||||
eras.map((era, index) => ({
|
||||
era,
|
||||
total: values[index].isSome
|
||||
? (values[index].unwrap() as ITuple<[Perbill, BalanceOf]>)[1]
|
||||
: (values[index + eras.length].unwrapOrDefault() as BalanceOf)
|
||||
}))
|
||||
)
|
||||
)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
|
||||
export const ownSlash = firstMemo(
|
||||
(api: DeriveApi, accountId: Uint8Array | string, era: EraIndex) =>
|
||||
api.derive.staking._ownSlashes(accountId, [era], true)
|
||||
);
|
||||
|
||||
export const ownSlashes = erasHistoricApplyAccount('_ownSlashes');
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { AccountId, EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { PalletStakingExposure, PalletStakingNominations, PalletStakingRewardDestination, PalletStakingStakingLedger, PalletStakingValidatorPrefs } from '@polkadot/types/lookup';
|
||||
import type { DeriveApi, DeriveStakingQuery, StakingQueryFlags } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { firstMemo, memo } from '../util';
|
||||
|
||||
function parseDetails (stashId: AccountId, controllerIdOpt: Option<AccountId> | null, nominatorsOpt: Option<PalletStakingNominations>, rewardDestination: PalletStakingRewardDestination, validatorPrefs: PalletStakingValidatorPrefs, exposure: PalletStakingExposure, stakingLedgerOpt: Option<PalletStakingStakingLedger>): DeriveStakingQuery {
|
||||
return {
|
||||
accountId: stashId,
|
||||
controllerId: controllerIdOpt && controllerIdOpt.unwrapOr(null),
|
||||
exposure,
|
||||
nominators: nominatorsOpt.isSome
|
||||
? nominatorsOpt.unwrap().targets
|
||||
: [],
|
||||
rewardDestination,
|
||||
stakingLedger: stakingLedgerOpt.unwrapOrDefault(),
|
||||
stashId,
|
||||
validatorPrefs
|
||||
};
|
||||
}
|
||||
|
||||
function getLedgers (api: DeriveApi, optIds: (Option<AccountId> | null)[], { withLedger = false }: StakingQueryFlags): Observable<Option<PalletStakingStakingLedger>[]> {
|
||||
const ids = optIds
|
||||
.filter((o): o is Option<AccountId> => withLedger && !!o && o.isSome)
|
||||
.map((o) => o.unwrap());
|
||||
const emptyLed = api.registry.createType<Option<PalletStakingStakingLedger>>('Option<StakingLedger>');
|
||||
|
||||
return (
|
||||
ids.length
|
||||
? api.query.staking.ledger.multi(ids)
|
||||
: of([])
|
||||
).pipe(
|
||||
map((optLedgers): Option<PalletStakingStakingLedger>[] => {
|
||||
let offset = -1;
|
||||
|
||||
return optIds.map((o): Option<PalletStakingStakingLedger> =>
|
||||
o && o.isSome
|
||||
? optLedgers[++offset] || emptyLed
|
||||
: emptyLed
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function getStashInfo (api: DeriveApi, stashIds: AccountId[], activeEra: EraIndex, { withController, withDestination, withExposure, withLedger, withNominations, withPrefs }: StakingQueryFlags): Observable<[(Option<AccountId> | null)[], Option<PalletStakingNominations>[], PalletStakingRewardDestination[], PalletStakingValidatorPrefs[], PalletStakingExposure[]]> {
|
||||
const emptyNoms = api.registry.createType<Option<PalletStakingNominations>>('Option<Nominations>');
|
||||
const emptyRewa = api.registry.createType<PalletStakingRewardDestination>('RewardDestination');
|
||||
const emptyExpo = api.registry.createType<PalletStakingExposure>('Exposure');
|
||||
const emptyPrefs = api.registry.createType<PalletStakingValidatorPrefs>('ValidatorPrefs');
|
||||
|
||||
return combineLatest([
|
||||
withController || withLedger
|
||||
? api.query.staking.bonded.multi(stashIds)
|
||||
: of(stashIds.map(() => null)),
|
||||
withNominations
|
||||
? api.query.staking.nominators.multi(stashIds)
|
||||
: of(stashIds.map(() => emptyNoms)),
|
||||
withDestination
|
||||
? api.query.staking.payee.multi(stashIds)
|
||||
: of(stashIds.map(() => emptyRewa)),
|
||||
withPrefs
|
||||
? api.query.staking.validators.multi(stashIds)
|
||||
: of(stashIds.map(() => emptyPrefs)),
|
||||
withExposure
|
||||
? api.query.staking.erasStakers.multi(stashIds.map((stashId) => [activeEra, stashId]))
|
||||
: of(stashIds.map(() => emptyExpo))
|
||||
]);
|
||||
}
|
||||
|
||||
function getBatch (api: DeriveApi, activeEra: EraIndex, stashIds: AccountId[], flags: StakingQueryFlags): Observable<DeriveStakingQuery[]> {
|
||||
return getStashInfo(api, stashIds, activeEra, flags).pipe(
|
||||
switchMap(([controllerIdOpt, nominatorsOpt, rewardDestination, validatorPrefs, exposure]): Observable<DeriveStakingQuery[]> =>
|
||||
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])
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
/**
|
||||
* @description From a stash, retrieve the controllerId and all relevant details
|
||||
*/
|
||||
export const query = firstMemo(
|
||||
(api: DeriveApi, accountId: Uint8Array | string, flags: StakingQueryFlags) =>
|
||||
api.derive.staking.queryMulti([accountId], flags)
|
||||
);
|
||||
|
||||
export function queryMulti (instanceId: string, api: DeriveApi): (accountIds: (Uint8Array | string)[], flags: StakingQueryFlags) => Observable<DeriveStakingQuery[]> {
|
||||
return memo(instanceId, (accountIds: (Uint8Array | string)[], flags: StakingQueryFlags): Observable<DeriveStakingQuery[]> =>
|
||||
accountIds.length
|
||||
? api.derive.session.indexes().pipe(
|
||||
switchMap(({ activeEra }): Observable<DeriveStakingQuery[]> => {
|
||||
const stashIds = accountIds.map((accountId) => api.registry.createType('AccountId', accountId));
|
||||
|
||||
return getBatch(api, activeEra, stashIds, flags);
|
||||
})
|
||||
)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi } from '../types';
|
||||
import type { DeriveEraValidatorExposure, DeriveStakerExposure } from './types';
|
||||
|
||||
import { map, switchMap } from 'rxjs';
|
||||
|
||||
import { firstMemo, memo } from '../util';
|
||||
|
||||
export function _stakerExposures (instanceId: string, api: DeriveApi): (accountIds: (Uint8Array | string)[], eras: EraIndex[], withActive?: boolean) => Observable<DeriveStakerExposure[][]> {
|
||||
return memo(instanceId, (accountIds: (Uint8Array | string)[], eras: EraIndex[], withActive = false): Observable<DeriveStakerExposure[][]> => {
|
||||
const stakerIds = accountIds.map((a) => api.registry.createType('AccountId', a).toString());
|
||||
|
||||
return api.derive.staking._erasExposure(eras, withActive).pipe(
|
||||
map((exposures): DeriveStakerExposure[][] =>
|
||||
stakerIds.map((stakerId) =>
|
||||
exposures.map(({ era, nominators: allNominators, validators: allValidators }): DeriveStakerExposure => {
|
||||
const isValidator = !!allValidators[stakerId];
|
||||
const validators: DeriveEraValidatorExposure = {};
|
||||
const nominating = allNominators[stakerId] || [];
|
||||
|
||||
if (isValidator) {
|
||||
validators[stakerId] = allValidators[stakerId];
|
||||
} else if (nominating) {
|
||||
nominating.forEach(({ validatorId }): void => {
|
||||
validators[validatorId] = allValidators[validatorId];
|
||||
});
|
||||
}
|
||||
|
||||
return { era, isEmpty: !Object.keys(validators).length, isValidator, nominating, validators };
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function stakerExposures (instanceId: string, api: DeriveApi): (accountIds: (Uint8Array | string)[], withActive?: boolean) => Observable<DeriveStakerExposure[][]> {
|
||||
return memo(instanceId, (accountIds: (Uint8Array | string)[], withActive = false): Observable<DeriveStakerExposure[][]> =>
|
||||
api.derive.staking.erasHistoric(withActive).pipe(
|
||||
switchMap((eras) => api.derive.staking._stakerExposures(accountIds, eras, withActive))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export const stakerExposure = firstMemo(
|
||||
(api: DeriveApi, accountId: Uint8Array | string, withActive?: boolean) =>
|
||||
api.derive.staking.stakerExposures([accountId], withActive)
|
||||
);
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveStakerPoints } from '../types';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { erasHistoricApplyAccount } from './util';
|
||||
|
||||
export function _stakerPoints (instanceId: string, api: DeriveApi): (accountId: Uint8Array | string, eras: EraIndex[], withActive: boolean) => Observable<DeriveStakerPoints[]> {
|
||||
return memo(instanceId, (accountId: Uint8Array | string, eras: EraIndex[], withActive: boolean): Observable<DeriveStakerPoints[]> => {
|
||||
const stakerId = api.registry.createType('AccountId', accountId).toString();
|
||||
|
||||
return api.derive.staking._erasPoints(eras, withActive).pipe(
|
||||
map((points): DeriveStakerPoints[] =>
|
||||
points.map(({ era, eraPoints, validators }): DeriveStakerPoints => ({
|
||||
era,
|
||||
eraPoints,
|
||||
points: validators[stakerId] || api.registry.createType('RewardPoint')
|
||||
}))
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export const stakerPoints = erasHistoricApplyAccount('_stakerPoints');
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveStakerPrefs } from '../types';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { erasHistoricApplyAccount } from './util';
|
||||
|
||||
export function _stakerPrefs (instanceId: string, api: DeriveApi): (accountId: Uint8Array | string, eras: EraIndex[], withActive: boolean) => Observable<DeriveStakerPrefs[]> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
return memo(instanceId, (accountId: Uint8Array | string, eras: EraIndex[], _withActive: boolean): Observable<DeriveStakerPrefs[]> =>
|
||||
api.query.staking.erasValidatorPrefs.multi(eras.map((e) => [e, accountId])).pipe(
|
||||
map((all): DeriveStakerPrefs[] =>
|
||||
all.map((validatorPrefs, index): DeriveStakerPrefs => ({
|
||||
era: eras[index],
|
||||
validatorPrefs
|
||||
}))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export const stakerPrefs = erasHistoricApplyAccount('_stakerPrefs');
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId, EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { PalletStakingStakingLedger } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { DeriveApi, DeriveEraPoints, DeriveEraPrefs, DeriveEraRewards, DeriveEraValPrefs, DeriveStakerExposure, DeriveStakerReward, DeriveStakerRewardValidator } from '../types';
|
||||
import type { DeriveStakingQuery } from './types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { BN_BILLION, BN_ZERO } from '@polkadot/util';
|
||||
|
||||
import { firstMemo, memo } from '../util';
|
||||
|
||||
type ErasResult = [DeriveEraPoints[], DeriveEraPrefs[], DeriveEraRewards[]];
|
||||
|
||||
function parseRewards (api: DeriveApi, stashId: AccountId, [erasPoints, erasPrefs, erasRewards]: ErasResult, exposures: DeriveStakerExposure[]): DeriveStakerReward[] {
|
||||
return exposures.map(({ era, isEmpty, isValidator, nominating, validators: eraValidators }): DeriveStakerReward => {
|
||||
const { eraPoints, validators: allValPoints } = erasPoints.find((p) => p.era.eq(era)) || { eraPoints: BN_ZERO, validators: {} };
|
||||
const { eraReward } = erasRewards.find((r) => r.era.eq(era)) || { eraReward: api.registry.createType('Balance') };
|
||||
const { validators: allValPrefs } = erasPrefs.find((p) => p.era.eq(era)) || { validators: {} as DeriveEraValPrefs };
|
||||
const validators: Record<string, DeriveStakerRewardValidator> = {};
|
||||
const stakerId = stashId.toString();
|
||||
|
||||
Object.entries(eraValidators).forEach(([validatorId, exposure]): void => {
|
||||
const valPoints = allValPoints[validatorId] || BN_ZERO;
|
||||
const valComm = allValPrefs[validatorId]?.commission.unwrap() || BN_ZERO;
|
||||
const expTotal = exposure.total?.unwrap() || BN_ZERO;
|
||||
let avail = BN_ZERO;
|
||||
let value: BN | undefined;
|
||||
|
||||
if (!(expTotal.isZero() || valPoints.isZero() || eraPoints.isZero())) {
|
||||
avail = eraReward.mul(valPoints).div(eraPoints);
|
||||
|
||||
const valCut = valComm.mul(avail).div(BN_BILLION);
|
||||
let staked: BN;
|
||||
|
||||
if (validatorId === stakerId) {
|
||||
staked = exposure.own.unwrap();
|
||||
} else {
|
||||
const stakerExp = exposure.others.find(({ who }) => who.eq(stakerId));
|
||||
|
||||
staked = stakerExp
|
||||
? stakerExp.value.unwrap()
|
||||
: BN_ZERO;
|
||||
}
|
||||
|
||||
value = avail.sub(valCut).imul(staked).div(expTotal).iadd(validatorId === stakerId ? valCut : BN_ZERO);
|
||||
}
|
||||
|
||||
validators[validatorId] = {
|
||||
total: api.registry.createType('Balance', avail),
|
||||
value: api.registry.createType('Balance', value)
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
era,
|
||||
eraReward,
|
||||
isEmpty,
|
||||
isValidator,
|
||||
nominating,
|
||||
validators
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function allUniqValidators (rewards: DeriveStakerReward[][]): [string[], string[][]] {
|
||||
return rewards.reduce(([all, perStash]: [string[], string[][]], rewards) => {
|
||||
const uniq: string[] = [];
|
||||
|
||||
perStash.push(uniq);
|
||||
rewards.forEach(({ validators }) =>
|
||||
Object.keys(validators).forEach((validatorId): void => {
|
||||
if (!uniq.includes(validatorId)) {
|
||||
uniq.push(validatorId);
|
||||
|
||||
if (!all.includes(validatorId)) {
|
||||
all.push(validatorId);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return [all, perStash];
|
||||
}, [[], []]);
|
||||
}
|
||||
|
||||
function removeClaimed (validators: string[], queryValidators: DeriveStakingQuery[], reward: DeriveStakerReward): void {
|
||||
const rm: string[] = [];
|
||||
|
||||
Object.keys(reward.validators).forEach((validatorId): void => {
|
||||
const index = validators.indexOf(validatorId);
|
||||
|
||||
if (index !== -1) {
|
||||
const valLedger = queryValidators[index].stakingLedger;
|
||||
|
||||
if (valLedger?.claimedRewards.some((e) => reward.era.eq(e))) {
|
||||
rm.push(validatorId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rm.forEach((validatorId): void => {
|
||||
delete reward.validators[validatorId];
|
||||
});
|
||||
}
|
||||
|
||||
function filterRewards (eras: EraIndex[], valInfo: [string, DeriveStakingQuery][], { rewards, stakingLedger }: { rewards: DeriveStakerReward[]; stakingLedger: PalletStakingStakingLedger }): DeriveStakerReward[] {
|
||||
const filter = eras.filter((e) => !stakingLedger.claimedRewards.some((s) => s.eq(e)));
|
||||
const validators = valInfo.map(([v]) => v);
|
||||
const queryValidators = valInfo.map(([, q]) => q);
|
||||
|
||||
return rewards
|
||||
.filter(({ isEmpty }) => !isEmpty)
|
||||
.filter((reward): boolean => {
|
||||
if (!filter.some((e) => reward.era.eq(e))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
removeClaimed(validators, queryValidators, reward);
|
||||
|
||||
return true;
|
||||
})
|
||||
.filter(({ validators }) => Object.keys(validators).length !== 0)
|
||||
.map((reward) => ({
|
||||
...reward,
|
||||
nominators: reward.nominating.filter((n) => reward.validators[n.validatorId])
|
||||
}));
|
||||
}
|
||||
|
||||
export function _stakerRewardsEras (instanceId: string, api: DeriveApi): (eras: EraIndex[], withActive?: boolean) => Observable<ErasResult> {
|
||||
return memo(instanceId, (eras: EraIndex[], withActive = false): Observable<ErasResult> =>
|
||||
combineLatest([
|
||||
api.derive.staking._erasPoints(eras, withActive),
|
||||
api.derive.staking._erasPrefs(eras, withActive),
|
||||
api.derive.staking._erasRewards(eras, withActive)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
export function _stakerRewards (instanceId: string, api: DeriveApi): (accountIds: (Uint8Array | string)[], eras: EraIndex[], withActive?: boolean) => Observable<DeriveStakerReward[][]> {
|
||||
return memo(instanceId, (accountIds: (Uint8Array | string)[], eras: EraIndex[], withActive = false): Observable<DeriveStakerReward[][]> =>
|
||||
combineLatest([
|
||||
api.derive.staking.queryMulti(accountIds, { withLedger: true }),
|
||||
api.derive.staking._stakerExposures(accountIds, eras, withActive),
|
||||
api.derive.staking._stakerRewardsEras(eras, withActive)
|
||||
]).pipe(
|
||||
switchMap(([queries, exposures, erasResult]): Observable<DeriveStakerReward[][]> => {
|
||||
const allRewards = queries.map(({ stakingLedger, stashId }, index): DeriveStakerReward[] =>
|
||||
(!stashId || !stakingLedger)
|
||||
? []
|
||||
: parseRewards(api, stashId, erasResult, exposures[index])
|
||||
);
|
||||
|
||||
if (withActive) {
|
||||
return of(allRewards);
|
||||
}
|
||||
|
||||
const [allValidators, stashValidators] = allUniqValidators(allRewards);
|
||||
|
||||
return api.derive.staking.queryMulti(allValidators, { withLedger: true }).pipe(
|
||||
map((queriedVals): DeriveStakerReward[][] =>
|
||||
queries.map(({ stakingLedger }, index): DeriveStakerReward[] =>
|
||||
filterRewards(
|
||||
eras,
|
||||
stashValidators[index].map((validatorId): [string, DeriveStakingQuery] => [
|
||||
validatorId,
|
||||
queriedVals.find((q) => q.accountId.eq(validatorId)) as DeriveStakingQuery
|
||||
]),
|
||||
{
|
||||
rewards: allRewards[index],
|
||||
stakingLedger
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export const stakerRewards = firstMemo(
|
||||
(api: DeriveApi, accountId: Uint8Array | string, withActive?: boolean) =>
|
||||
api.derive.staking.erasHistoric(withActive).pipe(
|
||||
switchMap((eras) => api.derive.staking._stakerRewards([accountId], eras, withActive))
|
||||
)
|
||||
);
|
||||
|
||||
export function stakerRewardsMultiEras (instanceId: string, api: DeriveApi): (accountIds: (Uint8Array | string)[], eras: EraIndex[]) => Observable<DeriveStakerReward[][]> {
|
||||
return memo(instanceId, (accountIds: (Uint8Array | string)[], eras: EraIndex[]): Observable<DeriveStakerReward[][]> =>
|
||||
accountIds.length && eras.length
|
||||
? api.derive.staking._stakerRewards(accountIds, eras, false)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
|
||||
export function stakerRewardsMulti (instanceId: string, api: DeriveApi): (accountIds: (Uint8Array | string)[], withActive?: boolean) => Observable<DeriveStakerReward[][]> {
|
||||
return memo(instanceId, (accountIds: (Uint8Array | string)[], withActive = false): Observable<DeriveStakerReward[][]> =>
|
||||
api.derive.staking.erasHistoric(withActive).pipe(
|
||||
switchMap((eras) => api.derive.staking.stakerRewardsMultiEras(accountIds, eras))
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveStakerSlashes } from '../types';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { erasHistoricApplyAccount } from './util';
|
||||
|
||||
export function _stakerSlashes (instanceId: string, api: DeriveApi): (accountId: Uint8Array | string, eras: EraIndex[], withActive: boolean) => Observable<DeriveStakerSlashes[]> {
|
||||
return memo(instanceId, (accountId: Uint8Array | string, eras: EraIndex[], withActive: boolean): Observable<DeriveStakerSlashes[]> => {
|
||||
const stakerId = api.registry.createType('AccountId', accountId).toString();
|
||||
|
||||
return api.derive.staking._erasSlashes(eras, withActive).pipe(
|
||||
map((slashes): DeriveStakerSlashes[] =>
|
||||
slashes.map(({ era, nominators, validators }): DeriveStakerSlashes => ({
|
||||
era,
|
||||
total: nominators[stakerId] || validators[stakerId] || api.registry.createType('Balance')
|
||||
}))
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export const stakerSlashes = erasHistoricApplyAccount('_stakerSlashes');
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { map, startWith, switchMap } from 'rxjs';
|
||||
|
||||
import { drr, memo } from '../util';
|
||||
|
||||
function onBondedEvent (api: DeriveApi): Observable<number> {
|
||||
let current = Date.now();
|
||||
|
||||
return api.query.system.events().pipe(
|
||||
map((events): number => {
|
||||
current = events.filter(({ event, phase }): boolean => {
|
||||
try {
|
||||
return phase.isApplyExtrinsic &&
|
||||
event.section === 'staking' &&
|
||||
event.method === 'Bonded';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
? Date.now()
|
||||
: current;
|
||||
|
||||
return current;
|
||||
}),
|
||||
startWith(current),
|
||||
drr({ skipTimeout: true })
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Retrieve the list of all validator stashes
|
||||
*/
|
||||
export function stashes (instanceId: string, api: DeriveApi): () => Observable<AccountId[]> {
|
||||
return memo(instanceId, (): Observable<AccountId[]> =>
|
||||
onBondedEvent(api).pipe(
|
||||
switchMap(() => api.query.staking.validators.keys()),
|
||||
map((keys) => keys.map(({ args: [v] }) => v).filter((a) => a))
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AccountId, Balance, EraIndex, RewardPoint } from '@polkadot/types/interfaces';
|
||||
import type { PalletStakingExposure, PalletStakingRewardDestination, PalletStakingStakingLedger, PalletStakingValidatorPrefs } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { DeriveSessionIndexes } from '../session/types';
|
||||
|
||||
export type DeriveEraValPoints = Record<string, RewardPoint>;
|
||||
|
||||
export type DeriveEraValPrefs = Record<string, PalletStakingValidatorPrefs>;
|
||||
|
||||
export type DeriveEraValSlash = Record<string, Balance>;
|
||||
|
||||
export interface DeriveEraPoints {
|
||||
era: EraIndex;
|
||||
eraPoints: RewardPoint;
|
||||
validators: DeriveEraValPoints;
|
||||
}
|
||||
|
||||
export interface DeriveEraPrefs {
|
||||
era: EraIndex;
|
||||
validators: DeriveEraValPrefs;
|
||||
}
|
||||
|
||||
export interface DeriveEraRewards {
|
||||
era: EraIndex;
|
||||
eraReward: Balance;
|
||||
}
|
||||
|
||||
export interface DeriveEraSlashes {
|
||||
era: EraIndex;
|
||||
nominators: DeriveEraValSlash;
|
||||
validators: DeriveEraValSlash;
|
||||
}
|
||||
|
||||
export interface DeriveStakerPoints {
|
||||
era: EraIndex;
|
||||
eraPoints: RewardPoint;
|
||||
points: RewardPoint;
|
||||
}
|
||||
|
||||
export interface DeriveOwnExposure {
|
||||
clipped: PalletStakingExposure;
|
||||
era: EraIndex;
|
||||
exposure: PalletStakingExposure;
|
||||
}
|
||||
|
||||
export interface DeriveEraExposureNominating {
|
||||
validatorId: string;
|
||||
validatorIndex: number;
|
||||
}
|
||||
|
||||
export type DeriveEraNominatorExposure = Record<string, DeriveEraExposureNominating[]>;
|
||||
|
||||
export type DeriveEraValidatorExposure = Record<string, PalletStakingExposure>;
|
||||
|
||||
export interface DeriveEraExposure {
|
||||
era: EraIndex;
|
||||
nominators: DeriveEraNominatorExposure;
|
||||
validators: DeriveEraValidatorExposure;
|
||||
}
|
||||
|
||||
export interface DeriveStakerExposure {
|
||||
era: EraIndex;
|
||||
isEmpty: boolean;
|
||||
isValidator: boolean;
|
||||
nominating: DeriveEraExposureNominating[];
|
||||
validators: DeriveEraValidatorExposure;
|
||||
}
|
||||
|
||||
export interface DeriveStakerPrefs {
|
||||
era: EraIndex;
|
||||
validatorPrefs: PalletStakingValidatorPrefs;
|
||||
}
|
||||
|
||||
export interface DeriveStakerRewardValidator {
|
||||
total: Balance;
|
||||
value: Balance;
|
||||
}
|
||||
|
||||
export interface DeriveStakerReward {
|
||||
era: EraIndex;
|
||||
eraReward: Balance;
|
||||
isEmpty: boolean;
|
||||
isValidator: boolean;
|
||||
nominating: DeriveEraExposureNominating[];
|
||||
validators: Record<string, DeriveStakerRewardValidator>;
|
||||
}
|
||||
|
||||
export interface DeriveStakerSlashes {
|
||||
era: EraIndex;
|
||||
total: Balance;
|
||||
}
|
||||
|
||||
export type DeriveOwnSlashes = DeriveStakerSlashes;
|
||||
|
||||
export interface DeriveStakingKeys {
|
||||
nextSessionIds: AccountId[];
|
||||
sessionIds: AccountId[];
|
||||
}
|
||||
|
||||
export interface DeriveStakingValidators {
|
||||
nextElected: AccountId[];
|
||||
validators: AccountId[];
|
||||
}
|
||||
|
||||
export interface DeriveStakingStash {
|
||||
controllerId: AccountId | null;
|
||||
exposure: PalletStakingExposure;
|
||||
nominators: AccountId[];
|
||||
rewardDestination: PalletStakingRewardDestination;
|
||||
stashId: AccountId;
|
||||
validatorPrefs: PalletStakingValidatorPrefs;
|
||||
}
|
||||
|
||||
export interface DeriveStakingQuery extends DeriveStakingStash {
|
||||
accountId: AccountId;
|
||||
stakingLedger: PalletStakingStakingLedger;
|
||||
}
|
||||
|
||||
export interface DeriveStakingElected {
|
||||
info: DeriveStakingQuery[];
|
||||
nextElected: AccountId[];
|
||||
validators: AccountId[];
|
||||
}
|
||||
|
||||
export interface DeriveStakingWaiting {
|
||||
info: DeriveStakingQuery[];
|
||||
waiting: AccountId[];
|
||||
}
|
||||
|
||||
export interface DeriveUnlocking {
|
||||
remainingEras: BN;
|
||||
value: Balance;
|
||||
}
|
||||
|
||||
export interface DeriveStakingAccount extends DeriveStakingQuery, DeriveStakingKeys {
|
||||
redeemable?: Balance;
|
||||
unlocking?: DeriveUnlocking[];
|
||||
}
|
||||
|
||||
export interface DeriveStakingOverview extends DeriveSessionIndexes {
|
||||
nextElected: AccountId[];
|
||||
validators: AccountId[];
|
||||
}
|
||||
|
||||
export interface StakingQueryFlags {
|
||||
withController?: boolean;
|
||||
withDestination?: boolean;
|
||||
withExposure?: boolean;
|
||||
withLedger?: boolean;
|
||||
withNominations?: boolean;
|
||||
withPrefs?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { ObsInnerType } from '@polkadot/api-base/types';
|
||||
import type { EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { ExactDerive } from '../derive';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { combineLatest, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
type ApplyReturn<T extends keyof ExactDerive['staking']> = ReturnType<ExactDerive['staking'][T]>;
|
||||
|
||||
export function filterEras <T extends { era: EraIndex }> (eras: EraIndex[], list: T[]): EraIndex[] {
|
||||
return eras.filter((e) => !list.some(({ era }) => e.eq(era)));
|
||||
}
|
||||
|
||||
export function erasHistoricApply <F extends '_erasExposure' | '_erasPoints' | '_erasPrefs' | '_erasRewards' | '_erasSlashes'> (fn: F): (instanceId: string, api: DeriveApi) => (withActive?: boolean) => ApplyReturn<F> {
|
||||
return (instanceId: string, api: DeriveApi) =>
|
||||
// Cannot quite get the typing right, but it is right in the code
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
memo(instanceId, (withActive = false) =>
|
||||
api.derive.staking.erasHistoric(withActive).pipe(
|
||||
switchMap((e) => api.derive.staking[fn](e, withActive))
|
||||
)
|
||||
) as any;
|
||||
}
|
||||
|
||||
export function erasHistoricApplyAccount <F extends '_ownExposures' | '_ownSlashes' | '_stakerPoints' | '_stakerPrefs' | '_stakerSlashes'> (fn: F): (instanceId: string, api: DeriveApi) => (accountId: string | Uint8Array, withActive?: boolean) => ApplyReturn<F> {
|
||||
return (instanceId: string, api: DeriveApi) =>
|
||||
// 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) =>
|
||||
api.derive.staking.erasHistoric(withActive).pipe(
|
||||
switchMap((e) => api.derive.staking[fn](accountId, e, withActive))
|
||||
)
|
||||
) as any;
|
||||
}
|
||||
|
||||
export function singleEra <F extends '_eraExposure' | '_eraPrefs' | '_eraSlashes'> (fn: F): (instanceId: string, api: DeriveApi) => (era: EraIndex) => ApplyReturn<F> {
|
||||
return (instanceId: string, api: DeriveApi) =>
|
||||
// Cannot quite get the typing right, but it is right in the code
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
memo(instanceId, (era: EraIndex) =>
|
||||
api.derive.staking[fn](era, true)
|
||||
) as any;
|
||||
}
|
||||
|
||||
export function combineEras <F extends '_eraExposure' | '_eraPrefs' | '_eraSlashes'> (fn: F): (instanceId: string, api: DeriveApi) => (eras: EraIndex[], withActive: boolean) => Observable<ObsInnerType<ApplyReturn<F>>[]> {
|
||||
return (instanceId: string, api: DeriveApi) =>
|
||||
// Cannot quite get the typing right, but it is right in the code
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
memo(instanceId, (eras: EraIndex[], withActive: boolean) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
eras.length
|
||||
? combineLatest(eras.map((e) => api.derive.staking[fn](e, withActive)))
|
||||
: of([])
|
||||
) as any;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveStakingValidators } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
export function nextElected (instanceId: string, api: DeriveApi): () => Observable<AccountId[]> {
|
||||
return memo(instanceId, (): Observable<AccountId[]> =>
|
||||
api.query.staking.erasStakers
|
||||
? 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)),
|
||||
map((keys) => keys.map(({ args: [, accountId] }) => accountId))
|
||||
)
|
||||
: api.query.staking.currentElected<AccountId[]>()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Retrieve latest list of validators
|
||||
*/
|
||||
export function validators (instanceId: string, api: DeriveApi): () => Observable<DeriveStakingValidators> {
|
||||
return memo(instanceId, (): Observable<DeriveStakingValidators> =>
|
||||
// Sadly the node-template is (for some obscure reason) not comprehensive, so while the derive works
|
||||
// in all actual real-world deployed chains, it does create some confusion for limited template chains
|
||||
combineLatest([
|
||||
api.query.session
|
||||
? api.query.session.validators()
|
||||
: of([]),
|
||||
api.query.staking
|
||||
? api.derive.staking.nextElected()
|
||||
: of([])
|
||||
]).pipe(
|
||||
map(([validators, nextElected]): DeriveStakingValidators => ({
|
||||
nextElected: nextElected.length
|
||||
? nextElected
|
||||
: validators,
|
||||
validators
|
||||
}))
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { DeriveApi, DeriveStakingWaiting, StakingQueryFlags } from '../types';
|
||||
|
||||
import { combineLatest, map, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
const DEFAULT_FLAGS = { withController: true, withPrefs: true };
|
||||
|
||||
export function waitingInfo (instanceId: string, api: DeriveApi): (flags?: StakingQueryFlags) => Observable<DeriveStakingWaiting> {
|
||||
return memo(instanceId, (flags: StakingQueryFlags = DEFAULT_FLAGS): Observable<DeriveStakingWaiting> =>
|
||||
combineLatest([
|
||||
api.derive.staking.validators(),
|
||||
api.derive.staking.stashes()
|
||||
]).pipe(
|
||||
switchMap(([{ nextElected }, stashes]): Observable<DeriveStakingWaiting> => {
|
||||
const elected = nextElected.map((a) => a.toString());
|
||||
const waiting = stashes.filter((v) => !elected.includes(v.toString()));
|
||||
|
||||
return api.derive.staking.queryMulti(waiting, flags).pipe(
|
||||
map((info): DeriveStakingWaiting => ({
|
||||
info,
|
||||
waiting
|
||||
}))
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user