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:
Jaco
2021-12-18 09:18:55 +02:00
committed by GitHub
parent 7b8a6e89d9
commit 4913cf65fd
178 changed files with 720 additions and 685 deletions
@@ -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])
);
+46
View File
@@ -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');
+25
View File
@@ -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';
+53
View File
@@ -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');
+112
View File
@@ -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))
)
);
}
+155
View File
@@ -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;
}
+61
View File
@@ -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
}))
);
})
)
);
}