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,36 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId, AccountIndex, Address } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { assertReturn, isU8a } from '@polkadot/util';
|
||||
import { decodeAddress } from '@polkadot/util-crypto';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @name accountId
|
||||
* @param {(Address | AccountId | AccountIndex | string | null)} address - An accounts address in various formats.
|
||||
* @description An [[AccountId]]
|
||||
*/
|
||||
export function accountId (instanceId: string, api: DeriveApi): (address?: Address | AccountId | AccountIndex | string | null) => Observable<AccountId> {
|
||||
return memo(instanceId, (address?: Address | AccountId | AccountIndex | string | null): Observable<AccountId> => {
|
||||
const decoded = isU8a(address)
|
||||
? address
|
||||
: decodeAddress((address || '').toString());
|
||||
|
||||
if (decoded.length > 8) {
|
||||
return of(api.registry.createType('AccountId', decoded));
|
||||
}
|
||||
|
||||
const accountIndex = api.registry.createType('AccountIndex', decoded);
|
||||
|
||||
return api.derive.accounts.indexToId(accountIndex.toString()).pipe(
|
||||
map((a) => assertReturn(a, 'Unable to retrieve accountId'))
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId, Address, Balance } from '@polkadot/types/interfaces';
|
||||
import type { DeriveAccountFlags, DeriveApi } from '../types';
|
||||
|
||||
import { combineLatest, map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
type FlagsIntermediate = [
|
||||
[AccountId, Balance][] | undefined,
|
||||
AccountId[],
|
||||
AccountId[],
|
||||
AccountId[],
|
||||
AccountId | undefined
|
||||
];
|
||||
|
||||
function parseFlags (address: AccountId | Address | string | null | undefined, [electionsMembers, councilMembers, technicalCommitteeMembers, societyMembers, sudoKey]: FlagsIntermediate): DeriveAccountFlags {
|
||||
const isIncluded = (id: AccountId | Address | string) =>
|
||||
address
|
||||
? id.toString() === address.toString()
|
||||
: false;
|
||||
|
||||
return {
|
||||
isCouncil: (electionsMembers?.map(([id]) => id) || councilMembers || []).some(isIncluded),
|
||||
isSociety: (societyMembers || []).some(isIncluded),
|
||||
isSudo: sudoKey?.toString() === address?.toString(),
|
||||
isTechCommittee: (technicalCommitteeMembers || []).some(isIncluded)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @name info
|
||||
* @description Returns account membership flags
|
||||
*/
|
||||
export function flags (instanceId: string, api: DeriveApi): (address?: AccountId | Address | string | null) => Observable<DeriveAccountFlags> {
|
||||
return memo(instanceId, (address?: AccountId | Address | string | null): Observable<DeriveAccountFlags> => {
|
||||
const elections = api.query.phragmenElection || api.query.electionsPhragmen || api.query.elections;
|
||||
|
||||
return combineLatest([
|
||||
address && elections?.members
|
||||
? elections.members<[AccountId, Balance][]>()
|
||||
: of(undefined),
|
||||
address && api.query.council?.members
|
||||
? api.query.council.members()
|
||||
: of([]),
|
||||
address && api.query.technicalCommittee?.members
|
||||
? api.query.technicalCommittee.members()
|
||||
: of([]),
|
||||
address && api.query.society?.members
|
||||
? api.query.society.members()
|
||||
: of([]),
|
||||
address && api.query.sudo?.key
|
||||
? api.query.sudo.key()
|
||||
: of(undefined)
|
||||
]).pipe(
|
||||
map((r) => parseFlags(address, r))
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -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 { AccountId, AccountIndex, Address } from '@polkadot/types/interfaces';
|
||||
import type { AccountIdAndIndex, DeriveApi } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { isU8a } from '@polkadot/util';
|
||||
import { decodeAddress } from '@polkadot/util-crypto';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @name idAndIndex
|
||||
* @param {(Address | AccountId | AccountIndex | Uint8Array | string | null)} address - An accounts address in various formats.
|
||||
* @description An array containing the [[AccountId]] and [[AccountIndex]] as optional values.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* api.derive.accounts.idAndIndex('F7Hs', ([id, ix]) => {
|
||||
* console.log(`AccountId #${id} with corresponding AccountIndex ${ix}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function idAndIndex (instanceId: string, api: DeriveApi): (address?: Address | AccountId | AccountIndex | Uint8Array | string | null) => Observable<AccountIdAndIndex> {
|
||||
return memo(instanceId, (address?: Address | AccountId | AccountIndex | Uint8Array | string | null): Observable<AccountIdAndIndex> => {
|
||||
try {
|
||||
// yes, this can fail, don't care too much, catch will catch it
|
||||
const decoded = isU8a(address)
|
||||
? address
|
||||
: decodeAddress((address || '').toString());
|
||||
|
||||
if (decoded.length > 8) {
|
||||
const accountId = api.registry.createType('AccountId', decoded);
|
||||
|
||||
return api.derive.accounts.idToIndex(accountId).pipe(
|
||||
map((accountIndex): AccountIdAndIndex => [accountId, accountIndex])
|
||||
);
|
||||
}
|
||||
|
||||
const accountIndex = api.registry.createType('AccountIndex', decoded);
|
||||
|
||||
return api.derive.accounts.indexToId(accountIndex.toString()).pipe(
|
||||
map((accountId): AccountIdAndIndex => [accountId, accountIndex])
|
||||
);
|
||||
} catch (error) {
|
||||
return of([undefined, undefined]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId, AccountIndex } from '@polkadot/types/interfaces';
|
||||
import type { AccountIndexes, DeriveApi } from '../types';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @name idToIndex
|
||||
* @param {( AccountId | string )} accountId - An accounts Id in different formats.
|
||||
* @returns Returns the corresponding AccountIndex.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* const ALICE = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY';
|
||||
* api.derive.accounts.idToIndex(ALICE, (accountIndex) => {
|
||||
* console.log(`The AccountIndex of ${ALICE} is ${accountIndex}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function idToIndex (instanceId: string, api: DeriveApi): (accountId: AccountId | string) => Observable<AccountIndex | undefined> {
|
||||
return memo(instanceId, (accountId: AccountId | string): Observable<AccountIndex | undefined> =>
|
||||
api.derive.accounts.indexes().pipe(
|
||||
map((indexes: AccountIndexes): AccountIndex | undefined =>
|
||||
(indexes || {})[accountId.toString()]
|
||||
)
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Data } from '@polkadot/types';
|
||||
import type { AccountId } from '@polkadot/types/interfaces';
|
||||
import type { PalletIdentityIdentityInfo, PalletIdentityRegistration } from '@polkadot/types/lookup';
|
||||
import type { Option } from '@polkadot/types-codec';
|
||||
import type { ITuple } from '@polkadot/types-codec/types';
|
||||
import type { DeriveAccountRegistration, DeriveApi, DeriveHasIdentity } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { isHex, u8aToString } from '@polkadot/util';
|
||||
|
||||
import { firstMemo, memo } from '../util';
|
||||
|
||||
type IdentityInfoAdditional = PalletIdentityIdentityInfo['additional'][0];
|
||||
|
||||
const UNDEF_HEX = { toHex: () => undefined };
|
||||
|
||||
function dataAsString (data: Data): string | undefined {
|
||||
return data.isRaw
|
||||
? u8aToString(data.asRaw.toU8a(true))
|
||||
: data.isNone
|
||||
? undefined
|
||||
: data.toHex();
|
||||
}
|
||||
|
||||
function extractOther (additional: IdentityInfoAdditional[]): Record<string, string> {
|
||||
return additional.reduce((other: Record<string, string>, [_key, _value]): Record<string, string> => {
|
||||
const key = dataAsString(_key);
|
||||
const value = dataAsString(_value);
|
||||
|
||||
if (key && value) {
|
||||
other[key] = value;
|
||||
}
|
||||
|
||||
return other;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function extractIdentity (identityOfOpt?: Option<PalletIdentityRegistration>, superOf?: [AccountId, Data]): DeriveAccountRegistration {
|
||||
if (!identityOfOpt?.isSome) {
|
||||
return { judgements: [] };
|
||||
}
|
||||
|
||||
const { info, judgements } = identityOfOpt.unwrap();
|
||||
const topDisplay = dataAsString(info.display);
|
||||
|
||||
return {
|
||||
display: (superOf && dataAsString(superOf[1])) || topDisplay,
|
||||
displayParent: superOf && topDisplay,
|
||||
email: dataAsString(info.email),
|
||||
image: dataAsString(info.image),
|
||||
judgements,
|
||||
legal: dataAsString(info.legal),
|
||||
other: extractOther(info.additional),
|
||||
parent: superOf && superOf[0],
|
||||
pgp: info.pgpFingerprint.unwrapOr(UNDEF_HEX).toHex(),
|
||||
riot: dataAsString(info.riot),
|
||||
twitter: dataAsString(info.twitter),
|
||||
web: dataAsString(info.web)
|
||||
};
|
||||
}
|
||||
|
||||
function getParent (api: DeriveApi, identityOfOpt: Option<PalletIdentityRegistration> | undefined, superOfOpt: Option<ITuple<[AccountId, Data]>> | undefined): Observable<[Option<PalletIdentityRegistration> | undefined, [AccountId, Data] | undefined]> {
|
||||
if (identityOfOpt?.isSome) {
|
||||
// this identity has something set
|
||||
return of([identityOfOpt, undefined]);
|
||||
} else if (superOfOpt?.isSome) {
|
||||
const superOf = superOfOpt.unwrap();
|
||||
|
||||
// we have a super
|
||||
return combineLatest([
|
||||
api.query.identity.identityOf(superOf[0]),
|
||||
of(superOf)
|
||||
]);
|
||||
}
|
||||
|
||||
// nothing of value returned
|
||||
return of([undefined, undefined]);
|
||||
}
|
||||
|
||||
function getBase (api: DeriveApi, accountId?: AccountId | Uint8Array | string): Observable<[Option<PalletIdentityRegistration> | undefined, Option<ITuple<[AccountId, Data]>> | undefined]> {
|
||||
return accountId && api.query.identity?.identityOf
|
||||
? api.queryMulti<[Option<PalletIdentityRegistration>, Option<ITuple<[AccountId, Data]>>]>([
|
||||
[api.query.identity.identityOf, accountId],
|
||||
[api.query.identity.superOf, accountId]
|
||||
])
|
||||
: of([undefined, undefined]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name identity
|
||||
* @description Returns identity info for an account
|
||||
*/
|
||||
export function identity (instanceId: string, api: DeriveApi): (accountId?: AccountId | Uint8Array | string) => Observable<DeriveAccountRegistration> {
|
||||
return memo(instanceId, (accountId?: AccountId | Uint8Array | string): Observable<DeriveAccountRegistration> =>
|
||||
getBase(api, accountId).pipe(
|
||||
switchMap(([identityOfOpt, superOfOpt]) => getParent(api, identityOfOpt, superOfOpt)),
|
||||
map(([identityOfOpt, superOf]) => extractIdentity(identityOfOpt, superOf))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export const hasIdentity = firstMemo(
|
||||
(api: DeriveApi, accountId: AccountId | Uint8Array | string) =>
|
||||
api.derive.accounts.hasIdentityMulti([accountId])
|
||||
);
|
||||
|
||||
export function hasIdentityMulti (instanceId: string, api: DeriveApi): (accountIds: (AccountId | Uint8Array | string)[]) => Observable<DeriveHasIdentity[]> {
|
||||
return memo(instanceId, (accountIds: (AccountId | Uint8Array | string)[]): Observable<DeriveHasIdentity[]> =>
|
||||
api.query.identity?.identityOf
|
||||
? combineLatest([
|
||||
api.query.identity.identityOf.multi(accountIds),
|
||||
api.query.identity.superOf.multi(accountIds)
|
||||
]).pipe(
|
||||
map(([identities, supers]) =>
|
||||
identities.map((identityOfOpt, index): DeriveHasIdentity => {
|
||||
const superOfOpt = supers[index];
|
||||
const parentId = superOfOpt && superOfOpt.isSome
|
||||
? superOfOpt.unwrap()[0].toString()
|
||||
: undefined;
|
||||
let display: string | undefined;
|
||||
|
||||
if (identityOfOpt && identityOfOpt.isSome) {
|
||||
const value = dataAsString(identityOfOpt.unwrap().info.display);
|
||||
|
||||
if (value && !isHex(value)) {
|
||||
display = value;
|
||||
}
|
||||
}
|
||||
|
||||
return { display, hasIdentity: !!(display || parentId), parentId };
|
||||
})
|
||||
)
|
||||
)
|
||||
: of(accountIds.map(() => ({ hasIdentity: false })))
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './accountId';
|
||||
export * from './flags';
|
||||
export * from './idAndIndex';
|
||||
export * from './idToIndex';
|
||||
export * from './identity';
|
||||
export * from './indexToId';
|
||||
export * from './indexes';
|
||||
export * from './info';
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId, AccountIndex } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @name indexToId
|
||||
* @param {( AccountIndex | string )} accountIndex - An accounts index in different formats.
|
||||
* @returns Returns the corresponding AccountId.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* api.derive.accounts.indexToId('F7Hs', (accountId) => {
|
||||
* console.log(`The AccountId of F7Hs is ${accountId}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function indexToId (instanceId: string, api: DeriveApi): (accountIndex: AccountIndex | string) => Observable<AccountId | undefined> {
|
||||
return memo(instanceId, (accountIndex: AccountIndex | string): Observable<AccountId | undefined> =>
|
||||
api.query.indices
|
||||
? api.query.indices.accounts(accountIndex).pipe(
|
||||
map((optResult): AccountId | undefined =>
|
||||
optResult.unwrapOr([])[0]
|
||||
)
|
||||
)
|
||||
: of(undefined)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountIndexes, DeriveApi } from '../types';
|
||||
|
||||
import { map, of, startWith } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
let indicesCache: AccountIndexes | null = null;
|
||||
|
||||
function queryAccounts (api: DeriveApi): Observable<AccountIndexes> {
|
||||
return api.query.indices.accounts.entries().pipe(
|
||||
map((entries): AccountIndexes =>
|
||||
entries.reduce((indexes: AccountIndexes, [key, idOpt]): AccountIndexes => {
|
||||
if (idOpt.isSome) {
|
||||
indexes[idOpt.unwrap()[0].toString()] = api.registry.createType('AccountIndex', key.args[0]);
|
||||
}
|
||||
|
||||
return indexes;
|
||||
}, {})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name indexes
|
||||
* @returns Returns all the indexes on the system.
|
||||
* @description This is an unwieldly query since it loops through
|
||||
* all of the enumsets and returns all of the values found. This could be up to 32k depending
|
||||
* on the number of active accounts in the system
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* api.derive.accounts.indexes((indexes) => {
|
||||
* console.log('All existing AccountIndexes', indexes);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function indexes (instanceId: string, api: DeriveApi): () => Observable<AccountIndexes> {
|
||||
return memo(instanceId, (): Observable<AccountIndexes> =>
|
||||
indicesCache
|
||||
? of(indicesCache)
|
||||
: (
|
||||
api.query.indices
|
||||
? queryAccounts(api).pipe(startWith({}))
|
||||
: of({} as AccountIndexes)
|
||||
).pipe(
|
||||
map((indices): AccountIndexes => {
|
||||
indicesCache = indices;
|
||||
|
||||
return indices;
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Bytes, Option, u32 } from '@polkadot/types';
|
||||
import type { AccountId, AccountIndex, Address, Balance } from '@polkadot/types/interfaces';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
import type { DeriveAccountInfo, DeriveAccountRegistration, DeriveApi } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { u8aToString } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
function retrieveNick (api: DeriveApi, accountId?: AccountId): Observable<string | undefined> {
|
||||
return ((
|
||||
accountId && api.query.nicks?.nameOf
|
||||
? api.query.nicks.nameOf(accountId)
|
||||
: of(undefined)
|
||||
) as Observable<Option<ITuple<[Bytes, Balance]>> | undefined>).pipe(
|
||||
map((nameOf): string | undefined =>
|
||||
nameOf?.isSome
|
||||
? u8aToString(nameOf.unwrap()[0]).substr(0, (api.consts.nicks.maxLength as u32).toNumber())
|
||||
: undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name info
|
||||
* @description Returns aux. info with regards to an account, current that includes the accountId, accountIndex and nickname
|
||||
*/
|
||||
export function info (instanceId: string, api: DeriveApi): (address?: AccountIndex | AccountId | Address | Uint8Array | string | null) => Observable<DeriveAccountInfo> {
|
||||
return memo(instanceId, (address?: AccountIndex | AccountId | Address | Uint8Array | string | null): Observable<DeriveAccountInfo> =>
|
||||
api.derive.accounts.idAndIndex(address).pipe(
|
||||
switchMap(([accountId, accountIndex]): Observable<[Partial<DeriveAccountInfo>, DeriveAccountRegistration, string?]> =>
|
||||
combineLatest([
|
||||
of({ accountId, accountIndex }),
|
||||
api.derive.accounts.identity(accountId),
|
||||
retrieveNick(api, accountId)
|
||||
])
|
||||
),
|
||||
map(([{ accountId, accountIndex }, identity, nickname]): DeriveAccountInfo => ({
|
||||
accountId, accountIndex, identity, nickname
|
||||
}))
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AccountId, AccountIndex, RegistrationJudgement } from '@polkadot/types/interfaces';
|
||||
|
||||
export type AccountIdAndIndex = [AccountId?, AccountIndex?];
|
||||
|
||||
export type AccountIndexes = Record<string, AccountIndex>;
|
||||
|
||||
export interface DeriveAccountRegistration {
|
||||
display?: string;
|
||||
displayParent?: string;
|
||||
email?: string;
|
||||
image?: string;
|
||||
legal?: string;
|
||||
other?: Record<string, string>;
|
||||
parent?: AccountId;
|
||||
pgp?: string;
|
||||
riot?: string;
|
||||
twitter?: string;
|
||||
web?: string;
|
||||
judgements: RegistrationJudgement[];
|
||||
}
|
||||
|
||||
export interface DeriveAccountFlags {
|
||||
isCouncil: boolean;
|
||||
isSociety: boolean;
|
||||
isSudo: boolean;
|
||||
isTechCommittee: boolean;
|
||||
}
|
||||
|
||||
export interface DeriveAccountInfo {
|
||||
accountId?: AccountId;
|
||||
accountIndex?: AccountIndex;
|
||||
identity: DeriveAccountRegistration;
|
||||
nickname?: string;
|
||||
}
|
||||
|
||||
export interface DeriveHasIdentity {
|
||||
display?: string;
|
||||
hasIdentity: boolean;
|
||||
parentId?: string;
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { QueryableStorageEntry } from '@polkadot/api-base/types';
|
||||
import type { AccountData, AccountId, AccountIndex, AccountInfo, Address, Balance, Index } from '@polkadot/types/interfaces';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
import type { DeriveApi, DeriveBalancesAccount, DeriveBalancesAccountData } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { isFunction } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
type BalanceResult = [Balance, Balance, Balance, Balance];
|
||||
|
||||
type Result = [Index, BalanceResult[]];
|
||||
|
||||
type DeriveCustomAccount = DeriveApi['derive'] & {
|
||||
[custom: string]: {
|
||||
customAccount?: DeriveApi['query']['balances']['account']
|
||||
}
|
||||
}
|
||||
|
||||
function zeroBalance (api: DeriveApi) {
|
||||
return api.registry.createType('Balance');
|
||||
}
|
||||
|
||||
function getBalance (api: DeriveApi, [freeBalance, reservedBalance, frozenFee, frozenMisc]: BalanceResult): DeriveBalancesAccountData {
|
||||
const votingBalance = api.registry.createType('Balance', freeBalance.toBn());
|
||||
|
||||
return {
|
||||
freeBalance,
|
||||
frozenFee,
|
||||
frozenMisc,
|
||||
reservedBalance,
|
||||
votingBalance
|
||||
};
|
||||
}
|
||||
|
||||
function calcBalances (api: DeriveApi, [accountId, [accountNonce, [primary, ...additional]]]: [AccountId, Result]): DeriveBalancesAccount {
|
||||
return {
|
||||
accountId,
|
||||
accountNonce,
|
||||
additional: additional.map((b) => getBalance(api, b)),
|
||||
...getBalance(api, primary)
|
||||
};
|
||||
}
|
||||
|
||||
// old
|
||||
function queryBalancesFree (api: DeriveApi, accountId: AccountId): Observable<Result> {
|
||||
return api.queryMulti<[Balance, Balance, Index]>([
|
||||
[api.query.balances.freeBalance, accountId],
|
||||
[api.query.balances.reservedBalance, accountId],
|
||||
[api.query.system.accountNonce, accountId]
|
||||
]).pipe(
|
||||
map(([freeBalance, reservedBalance, accountNonce]): Result => [
|
||||
accountNonce,
|
||||
[[freeBalance, reservedBalance, zeroBalance(api), zeroBalance(api)]]
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
function queryNonceOnly (api: DeriveApi, accountId: AccountId): Observable<Result> {
|
||||
const fill = (nonce: Index): Result => [
|
||||
nonce,
|
||||
[[zeroBalance(api), zeroBalance(api), zeroBalance(api), zeroBalance(api)]]
|
||||
];
|
||||
|
||||
return isFunction(api.query.system.account)
|
||||
? api.query.system.account(accountId).pipe(
|
||||
map(({ nonce }) => fill(nonce))
|
||||
)
|
||||
: isFunction(api.query.system.accountNonce)
|
||||
? api.query.system.accountNonce<Index>(accountId).pipe(
|
||||
map((nonce) => fill(nonce))
|
||||
)
|
||||
: of(fill(api.registry.createType('Index')));
|
||||
}
|
||||
|
||||
function queryBalancesAccount (api: DeriveApi, accountId: AccountId, modules: string[] = ['balances']): Observable<Result> {
|
||||
const balances = modules
|
||||
.map((m): QueryableStorageEntry<'rxjs'> => (api.derive as DeriveCustomAccount)[m]?.customAccount || api.query[m]?.account)
|
||||
.filter((q) => isFunction(q))
|
||||
.map((q): [QueryableStorageEntry<'rxjs'>, AccountId] => [q, accountId]);
|
||||
|
||||
const extract = (nonce: Index, data: AccountData[]): Result => [
|
||||
nonce,
|
||||
data.map(({ feeFrozen, free, miscFrozen, reserved }): BalanceResult => [free, reserved, feeFrozen, miscFrozen])
|
||||
];
|
||||
|
||||
// NOTE this is for the first case where we do have instances specified
|
||||
return balances.length
|
||||
? isFunction(api.query.system.account)
|
||||
? api.queryMulti<[AccountInfo, ...(AccountData[])]>([[api.query.system.account, accountId], ...balances]).pipe(
|
||||
map(([{ nonce }, ...balances]) => extract(nonce, balances))
|
||||
)
|
||||
: api.queryMulti<[Index, ...(AccountData[])]>([[api.query.system.accountNonce, accountId], ...balances]).pipe(
|
||||
map(([nonce, ...balances]) => extract(nonce, balances))
|
||||
)
|
||||
: queryNonceOnly(api, accountId);
|
||||
}
|
||||
|
||||
function querySystemAccount (api: DeriveApi, accountId: AccountId): Observable<Result> {
|
||||
// AccountInfo is current, support old, eg. Edgeware
|
||||
return api.query.system.account<AccountInfo | ITuple<[Index, AccountData]>>(accountId).pipe(
|
||||
map((infoOrTuple): Result => {
|
||||
const data = (infoOrTuple as AccountInfo).nonce
|
||||
? (infoOrTuple as AccountInfo).data
|
||||
: (infoOrTuple as [Index, AccountData])[1];
|
||||
|
||||
const nonce = (infoOrTuple as AccountInfo).nonce || (infoOrTuple as [Index, AccountData])[0];
|
||||
|
||||
if (!data || data.isEmpty) {
|
||||
return [
|
||||
nonce,
|
||||
[[zeroBalance(api), zeroBalance(api), zeroBalance(api), zeroBalance(api)]]
|
||||
];
|
||||
}
|
||||
|
||||
const { feeFrozen, free, miscFrozen, reserved } = data;
|
||||
|
||||
return [
|
||||
nonce,
|
||||
[[free, reserved, feeFrozen, miscFrozen]]
|
||||
];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name account
|
||||
* @param {( AccountIndex | AccountId | Address | string )} address - An accounts Id in different formats.
|
||||
* @returns An object containing the results of various balance queries
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* const ALICE = 'F7Hs';
|
||||
*
|
||||
* api.derive.balances.all(ALICE, ({ accountId, lockedBalance }) => {
|
||||
* console.log(`The account ${accountId} has a locked balance ${lockedBalance} units.`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function account (instanceId: string, api: DeriveApi): (address: AccountIndex | AccountId | Address | string) => Observable<DeriveBalancesAccount> {
|
||||
const balanceInstances = api.registry.getModuleInstances(api.runtimeVersion.specName.toString(), 'balances');
|
||||
|
||||
return memo(instanceId, (address: AccountIndex | AccountId | Address | string): Observable<DeriveBalancesAccount> =>
|
||||
api.derive.accounts.accountId(address).pipe(
|
||||
switchMap((accountId): Observable<[AccountId, Result]> =>
|
||||
(accountId
|
||||
? combineLatest([
|
||||
of(accountId),
|
||||
balanceInstances
|
||||
? queryBalancesAccount(api, accountId, balanceInstances)
|
||||
: isFunction(api.query.system?.account)
|
||||
? querySystemAccount(api, accountId)
|
||||
: isFunction(api.query.balances?.account)
|
||||
? queryBalancesAccount(api, accountId)
|
||||
: isFunction(api.query.balances?.freeBalance)
|
||||
? queryBalancesFree(api, accountId)
|
||||
: queryNonceOnly(api, accountId)
|
||||
])
|
||||
: of([api.registry.createType('AccountId'), [
|
||||
api.registry.createType('Index'),
|
||||
[[zeroBalance(api), zeroBalance(api), zeroBalance(api), zeroBalance(api)]]
|
||||
]])
|
||||
)
|
||||
),
|
||||
map((result): DeriveBalancesAccount => calcBalances(api, result))
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// 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, Vec } from '@polkadot/types';
|
||||
import type { AccountId, AccountIndex, Address, Balance, BalanceLockTo212, BlockNumber, VestingSchedule } from '@polkadot/types/interfaces';
|
||||
import type { PalletBalancesBalanceLock, PalletVestingVestingInfo } from '@polkadot/types/lookup';
|
||||
import type { DeriveApi, DeriveBalancesAccount, DeriveBalancesAccountData, DeriveBalancesAll, DeriveBalancesAllAccountData, DeriveBalancesAllVesting } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { BN, BN_ZERO, bnMax, bnMin, isFunction } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
type BalanceLocksEntry = Vec<PalletBalancesBalanceLock | BalanceLockTo212>;
|
||||
type BalanceLocks = BalanceLocksEntry[];
|
||||
type ResultBalance = [PalletVestingVestingInfo[] | null, ((PalletBalancesBalanceLock | BalanceLockTo212)[])[]];
|
||||
type Result = [DeriveBalancesAccount, BlockNumber, ResultBalance];
|
||||
|
||||
interface AllLocked {
|
||||
allLocked: boolean,
|
||||
lockedBalance: Balance,
|
||||
lockedBreakdown: (PalletBalancesBalanceLock | BalanceLockTo212)[],
|
||||
vestingLocked: Balance
|
||||
}
|
||||
|
||||
type DeriveCustomLocks = DeriveApi['derive'] & {
|
||||
[custom: string]: {
|
||||
customLocks?: DeriveApi['query']['balances']['locks']
|
||||
}
|
||||
}
|
||||
|
||||
const VESTING_ID = '0x76657374696e6720';
|
||||
|
||||
function calcLocked (api: DeriveApi, bestNumber: BlockNumber, locks: (PalletBalancesBalanceLock | BalanceLockTo212)[]): AllLocked {
|
||||
let lockedBalance = api.registry.createType('Balance');
|
||||
let lockedBreakdown: (PalletBalancesBalanceLock | BalanceLockTo212)[] = [];
|
||||
let vestingLocked = api.registry.createType('Balance');
|
||||
let allLocked = false;
|
||||
|
||||
if (Array.isArray(locks)) {
|
||||
// only get the locks that are valid until passed the current block
|
||||
lockedBreakdown = (locks as BalanceLockTo212[]).filter(({ until }): boolean => !until || (bestNumber && until.gt(bestNumber)));
|
||||
allLocked = lockedBreakdown.some(({ amount }) => amount && amount.isMax());
|
||||
vestingLocked = api.registry.createType('Balance', lockedBreakdown.filter(({ id }) => id.eq(VESTING_ID)).reduce((result: BN, { amount }) => result.iadd(amount), new BN(0)));
|
||||
|
||||
// get the maximum of the locks according to https://github.com/paritytech/substrate/blob/master/srml/balances/src/lib.rs#L699
|
||||
const notAll = lockedBreakdown.filter(({ amount }) => amount && !amount.isMax());
|
||||
|
||||
if (notAll.length) {
|
||||
lockedBalance = api.registry.createType('Balance', bnMax(...notAll.map(({ amount }): Balance => amount)));
|
||||
}
|
||||
}
|
||||
|
||||
return { allLocked, lockedBalance, lockedBreakdown, vestingLocked };
|
||||
}
|
||||
|
||||
function calcShared (api: DeriveApi, bestNumber: BlockNumber, data: DeriveBalancesAccountData, locks: (PalletBalancesBalanceLock | BalanceLockTo212)[]): DeriveBalancesAllAccountData {
|
||||
const { allLocked, lockedBalance, lockedBreakdown, vestingLocked } = calcLocked(api, bestNumber, locks);
|
||||
|
||||
return {
|
||||
...data,
|
||||
availableBalance: api.registry.createType('Balance', allLocked ? 0 : bnMax(new BN(0), data.freeBalance.sub(lockedBalance))),
|
||||
lockedBalance,
|
||||
lockedBreakdown,
|
||||
vestingLocked
|
||||
};
|
||||
}
|
||||
|
||||
function calcVesting (bestNumber: BlockNumber, shared: DeriveBalancesAllAccountData, _vesting: PalletVestingVestingInfo[] | null): DeriveBalancesAllVesting {
|
||||
// Calculate the vesting balances,
|
||||
// - offset = balance locked at startingBlock
|
||||
// - perBlock is the unlock amount
|
||||
const vesting = _vesting || [];
|
||||
const isVesting = !shared.vestingLocked.isZero();
|
||||
const vestedBalances = vesting.map(({ locked, perBlock, startingBlock }) =>
|
||||
bestNumber.gt(startingBlock)
|
||||
? bnMin(locked, perBlock.mul(bestNumber.sub(startingBlock)))
|
||||
: BN_ZERO
|
||||
);
|
||||
const vestedBalance = vestedBalances.reduce<BN>((all, value) => all.iadd(value), new BN(0));
|
||||
const vestingTotal = vesting.reduce<BN>((all, { locked }) => all.iadd(locked), new BN(0));
|
||||
|
||||
return {
|
||||
isVesting,
|
||||
vestedBalance,
|
||||
vestedClaimable: isVesting ? shared.vestingLocked.sub(vestingTotal.sub(vestedBalance)) : BN_ZERO,
|
||||
vesting: vesting
|
||||
.map(({ locked, perBlock, startingBlock }, index) => ({
|
||||
endBlock: locked.div(perBlock).iadd(startingBlock),
|
||||
locked,
|
||||
perBlock,
|
||||
startingBlock,
|
||||
vested: vestedBalances[index]
|
||||
}))
|
||||
.filter(({ locked }) => !locked.isZero()),
|
||||
vestingTotal
|
||||
};
|
||||
}
|
||||
|
||||
function calcBalances (api: DeriveApi, [data, bestNumber, [vesting, allLocks]]: Result): DeriveBalancesAll {
|
||||
const shared = calcShared(api, bestNumber, data, allLocks[0]);
|
||||
|
||||
return {
|
||||
...shared,
|
||||
...calcVesting(bestNumber, shared, vesting),
|
||||
accountId: data.accountId,
|
||||
accountNonce: data.accountNonce,
|
||||
additional: allLocks
|
||||
.filter((_, index) => index !== 0)
|
||||
.map((l, index) => calcShared(api, bestNumber, data.additional[index], l))
|
||||
};
|
||||
}
|
||||
|
||||
// old
|
||||
function queryOld (api: DeriveApi, accountId: AccountId): Observable<ResultBalance> {
|
||||
return api.queryMulti<[Vec<PalletBalancesBalanceLock>, Option<VestingSchedule>]>([
|
||||
[api.query.balances.locks, accountId],
|
||||
[api.query.balances.vesting, accountId]
|
||||
]).pipe(
|
||||
map(([locks, optVesting]): ResultBalance => {
|
||||
let vestingNew = null;
|
||||
|
||||
if (optVesting.isSome) {
|
||||
const { offset: locked, perBlock, startingBlock } = optVesting.unwrap();
|
||||
|
||||
vestingNew = api.registry.createType<PalletVestingVestingInfo>('VestingInfo', { locked, perBlock, startingBlock });
|
||||
}
|
||||
|
||||
return [
|
||||
vestingNew
|
||||
? [vestingNew]
|
||||
: null,
|
||||
[locks]
|
||||
];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const isNonNullable = <T>(nullable: T): nullable is NonNullable<T> => !!nullable;
|
||||
|
||||
// current (balances, vesting)
|
||||
function queryCurrent (api: DeriveApi, accountId: AccountId, balanceInstances: string[] = ['balances']): Observable<ResultBalance> {
|
||||
const calls = balanceInstances.map((m): DeriveApi['query']['balances']['locks'] | undefined =>
|
||||
(api.derive as DeriveCustomLocks)[m]?.customLocks || api.query[m as 'balances']?.locks
|
||||
);
|
||||
const lockEmpty = calls.map((c) => !c);
|
||||
const queries = calls.filter(isNonNullable).map((c): QueryableStorageMultiArg<'rxjs'> => [c, accountId]);
|
||||
|
||||
return (
|
||||
api.query.vesting?.vesting
|
||||
? api.queryMulti<[Option<PalletVestingVestingInfo> | Option<Vec<PalletVestingVestingInfo>>, ...BalanceLocks]>([[api.query.vesting.vesting, accountId], ...queries])
|
||||
// TODO We need to check module instances here as well, not only the balances module
|
||||
: queries.length
|
||||
? api.queryMulti<[...(Vec<PalletBalancesBalanceLock>)[]]>(queries).pipe(
|
||||
map((r): [Option<PalletVestingVestingInfo>, ...BalanceLocks] => [api.registry.createType('Option<VestingInfo>'), ...r])
|
||||
)
|
||||
: of([api.registry.createType('Option<VestingInfo>')] as [Option<PalletVestingVestingInfo>])
|
||||
).pipe(
|
||||
map(([opt, ...locks]): ResultBalance => {
|
||||
let offset = -1;
|
||||
const vesting = opt.unwrapOr(null);
|
||||
|
||||
return [
|
||||
vesting
|
||||
? Array.isArray(vesting) ? vesting : [vesting]
|
||||
: null,
|
||||
lockEmpty.map((e) => e ? api.registry.createType<Vec<PalletBalancesBalanceLock>>('Vec<BalanceLock>') : locks[++offset])
|
||||
];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name all
|
||||
* @param {( AccountIndex | AccountId | Address | string )} address - An accounts Id in different formats.
|
||||
* @returns An object containing the results of various balance queries
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* const ALICE = 'F7Hs';
|
||||
*
|
||||
* api.derive.balances.all(ALICE, ({ accountId, lockedBalance }) => {
|
||||
* console.log(`The account ${accountId} has a locked balance ${lockedBalance} units.`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function all (instanceId: string, api: DeriveApi): (address: AccountIndex | AccountId | Address | string) => Observable<DeriveBalancesAll> {
|
||||
const balanceInstances = api.registry.getModuleInstances(api.runtimeVersion.specName.toString(), 'balances');
|
||||
|
||||
return memo(instanceId, (address: AccountIndex | AccountId | Address | string): Observable<DeriveBalancesAll> =>
|
||||
api.derive.balances.account(address).pipe(
|
||||
switchMap((account): Observable<Result> =>
|
||||
(!account.accountId.isEmpty
|
||||
? combineLatest([
|
||||
of(account),
|
||||
api.derive.chain.bestNumber(),
|
||||
isFunction(api.query.system?.account) || isFunction(api.query.balances?.account)
|
||||
? queryCurrent(api, account.accountId, balanceInstances)
|
||||
: queryOld(api, account.accountId)
|
||||
])
|
||||
: of([account, api.registry.createType('BlockNumber'), [null, []]])
|
||||
)
|
||||
),
|
||||
map((result): DeriveBalancesAll => calcBalances(api, result))
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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 { DeriveApi, DeriveFees } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @name fees
|
||||
* @returns An object containing the combined results of the storage queries for
|
||||
* all relevant fees as declared in the substrate chain spec.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* api.derive.balances.fees(({ creationFee, transferFee }) => {
|
||||
* console.log(`The fee for creating a new account on this chain is ${creationFee} units. The fee required for making a transfer is ${transferFee} units.`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function fees (instanceId: string, api: DeriveApi): () => Observable<DeriveFees> {
|
||||
return memo(instanceId, (): Observable<DeriveFees> =>
|
||||
of([
|
||||
// deprecated - remove
|
||||
(api.consts.balances?.creationFee as unknown as Balance) || api.registry.createType('Balance'),
|
||||
(api.consts.balances?.transferFee as unknown as Balance) || api.registry.createType('Balance'),
|
||||
|
||||
// current
|
||||
(api.consts.balances?.existentialDeposit as Balance) || api.registry.createType('Balance'),
|
||||
(api.consts.transactionPayment?.transactionBaseFee as unknown as Balance) || api.registry.createType('Balance'),
|
||||
(api.consts.transactionPayment?.transactionByteFee as Balance) || api.registry.createType('Balance')
|
||||
]).pipe(
|
||||
map(([creationFee, transferFee, existentialDeposit, transactionBaseFee, transactionByteFee]): DeriveFees => ({
|
||||
creationFee,
|
||||
existentialDeposit,
|
||||
transactionBaseFee,
|
||||
transactionByteFee,
|
||||
transferFee
|
||||
}))
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { all } from './all';
|
||||
|
||||
export * from './account';
|
||||
export * from './fees';
|
||||
export * from './votingBalances';
|
||||
|
||||
const votingBalance = all;
|
||||
|
||||
export {
|
||||
all,
|
||||
votingBalance
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AccountId, Balance, BalanceLockTo212, Index } from '@polkadot/types/interfaces';
|
||||
import type { PalletBalancesBalanceLock } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
export interface DeriveBalancesAccountData {
|
||||
freeBalance: Balance;
|
||||
frozenFee: Balance;
|
||||
frozenMisc: Balance;
|
||||
reservedBalance: Balance;
|
||||
votingBalance: Balance;
|
||||
}
|
||||
|
||||
export interface DeriveBalancesAccount extends DeriveBalancesAccountData {
|
||||
accountId: AccountId;
|
||||
accountNonce: Index;
|
||||
additional: DeriveBalancesAccountData[];
|
||||
}
|
||||
|
||||
export interface DeriveBalancesAllAccountData extends DeriveBalancesAccountData {
|
||||
availableBalance: Balance;
|
||||
lockedBalance: Balance;
|
||||
lockedBreakdown: (PalletBalancesBalanceLock | BalanceLockTo212)[];
|
||||
vestingLocked: Balance;
|
||||
}
|
||||
|
||||
export interface DeriveBalancesVesting {
|
||||
startingBlock: BN;
|
||||
endBlock: BN;
|
||||
perBlock: BN;
|
||||
locked: BN;
|
||||
vested: BN;
|
||||
}
|
||||
|
||||
export interface DeriveBalancesAllVesting {
|
||||
isVesting: boolean;
|
||||
vestedBalance: BN;
|
||||
vestedClaimable: BN;
|
||||
vesting: DeriveBalancesVesting[];
|
||||
vestingTotal: BN;
|
||||
}
|
||||
|
||||
export interface DeriveBalancesAll extends DeriveBalancesAccount, DeriveBalancesAllAccountData, DeriveBalancesAllVesting {
|
||||
additional: DeriveBalancesAllAccountData[];
|
||||
}
|
||||
|
||||
export type DeriveBalancesMap = Record<string, DeriveBalancesAll>;
|
||||
@@ -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 { AccountId, AccountIndex, Address } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveBalancesAccount } from '../types';
|
||||
|
||||
import { combineLatest, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
export function votingBalances (instanceId: string, api: DeriveApi): (addresses?: (AccountId | AccountIndex | Address | string)[]) => Observable<DeriveBalancesAccount[]> {
|
||||
return memo(instanceId, (addresses?: (AccountId | AccountIndex | Address | string)[]): Observable<DeriveBalancesAccount[]> =>
|
||||
!addresses || !addresses.length
|
||||
? of([] as DeriveBalancesAccount[])
|
||||
: combineLatest(
|
||||
addresses.map((accountId): Observable<DeriveBalancesAccount> =>
|
||||
api.derive.balances.account(accountId)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SubmittableExtrinsic } from '@polkadot/api-base/types';
|
||||
import type { Bytes, Option, StorageKey } from '@polkadot/types';
|
||||
import type { Bounty, BountyIndex, Proposal, ProposalIndex } from '@polkadot/types/interfaces';
|
||||
import type { Codec, InterfaceTypes } from '@polkadot/types/types';
|
||||
import type { DeriveApi, DeriveCollectiveProposal } from '../types';
|
||||
|
||||
import { firstValueFrom, of } from 'rxjs';
|
||||
|
||||
import { ApiPromise } from '@polkadot/api';
|
||||
|
||||
import { BountyFactory } from '../test/bountyFactory';
|
||||
import { BytesFactory } from '../test/bytesFactory';
|
||||
import { createApiWithAugmentations } from '../test/helpers';
|
||||
import { ProposalFactory } from '../test/proposalFactory';
|
||||
import { bounties } from '.';
|
||||
|
||||
const DEFAULT_PROPOSER = '5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM';
|
||||
|
||||
describe('bounties derive', () => {
|
||||
let storageKey: (index: number) => StorageKey;
|
||||
let defaultBounty: () => Bounty;
|
||||
let emptyOption: <T extends Codec> (typeName: keyof InterfaceTypes) => Option<T>;
|
||||
let optionOf: <T extends Codec> (value: T) => Option<T>;
|
||||
let bountyIndex: (index: number) => BountyIndex;
|
||||
let proposalIndex: (index: number) => ProposalIndex;
|
||||
let bytes: (value: string) => Bytes;
|
||||
let api: ApiPromise;
|
||||
let createProposal: (method: SubmittableExtrinsic<'promise'>) => Proposal;
|
||||
let defaultMockApi: DeriveApi;
|
||||
|
||||
beforeAll(() => {
|
||||
api = createApiWithAugmentations();
|
||||
|
||||
({ bountyIndex, defaultBounty, emptyOption, optionOf, storageKey } = new BountyFactory(api));
|
||||
({ bytes } = new BytesFactory(api.registry));
|
||||
({ createProposal, proposalIndex } = new ProposalFactory(api));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
defaultMockApi = {
|
||||
derive: {
|
||||
council: {
|
||||
proposals: () => of([] as DeriveCollectiveProposal[])
|
||||
}
|
||||
},
|
||||
query: {
|
||||
council: {
|
||||
proposalCount: () => of(proposalIndex(2))
|
||||
},
|
||||
treasury: {
|
||||
bounties: {
|
||||
keys: () => of([storageKey(0), storageKey(1)]),
|
||||
multi: () => of([optionOf(defaultBounty()), optionOf(defaultBounty())])
|
||||
},
|
||||
bountyCount: () => of(bountyIndex(2)),
|
||||
bountyDescriptions: {
|
||||
multi: () => of([
|
||||
optionOf(bytes('make polkadot even better')),
|
||||
optionOf(bytes('some other bounty'))
|
||||
])
|
||||
}
|
||||
}
|
||||
},
|
||||
tx: api.tx
|
||||
} as unknown as DeriveApi;
|
||||
});
|
||||
|
||||
it('creates storage key', function () {
|
||||
expect(storageKey(194).args[0].eq(194)).toBe(true);
|
||||
});
|
||||
|
||||
it('creates proposal', function () {
|
||||
expect(createProposal(api.tx.balances.transfer('5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z', 1))).toBeTruthy();
|
||||
});
|
||||
|
||||
it('combines bounties with descriptions', async () => {
|
||||
const mockApi = {
|
||||
...defaultMockApi,
|
||||
query: {
|
||||
...defaultMockApi.query,
|
||||
treasury: {
|
||||
bounties: {
|
||||
keys: () => of([storageKey(0), storageKey(2), storageKey(3)]),
|
||||
multi: () => of([optionOf(defaultBounty()), emptyOption('Bounty'), optionOf(defaultBounty())])
|
||||
},
|
||||
bountyCount: () => of(bountyIndex(3)),
|
||||
bountyDescriptions: {
|
||||
multi: () => of([
|
||||
optionOf(bytes('make polkadot even better')),
|
||||
optionOf(bytes('this will be totally ignored')),
|
||||
emptyOption('Bytes')
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
} as unknown as DeriveApi;
|
||||
|
||||
const result = await firstValueFrom(bounties('', mockApi)());
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].bounty.proposer.toString()).toEqual(DEFAULT_PROPOSER);
|
||||
expect(result[0].description).toEqual('make polkadot even better');
|
||||
expect(result[0].index.eq(0)).toBe(true);
|
||||
expect(result[1].bounty.proposer.toString()).toEqual(DEFAULT_PROPOSER);
|
||||
expect(result[1].description).toEqual('');
|
||||
expect(result[1].index.eq(3)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns motions', async () => {
|
||||
const result = await firstValueFrom(bounties('', defaultMockApi)());
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].proposals).toHaveLength(0);
|
||||
expect(result[1].proposals).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('when no council, returns bounties without motions', async () => {
|
||||
const mockApi = {
|
||||
...defaultMockApi,
|
||||
query: {
|
||||
...defaultMockApi.query,
|
||||
council: null
|
||||
}
|
||||
} as unknown as DeriveApi;
|
||||
|
||||
const result = await firstValueFrom(bounties('', mockApi)());
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].bounty.proposer.toString()).toEqual(DEFAULT_PROPOSER);
|
||||
expect(result[0].description).toEqual('make polkadot even better');
|
||||
expect(result[0].index.eq(0)).toBe(true);
|
||||
expect(result[0].proposals).toHaveLength(0);
|
||||
expect(result[1].bounty.proposer.toString()).toEqual(DEFAULT_PROPOSER);
|
||||
expect(result[1].description).toEqual('some other bounty');
|
||||
expect(result[1].index.eq(1)).toBe(true);
|
||||
expect(result[1].proposals).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('combines bounties with motions', async () => {
|
||||
const mockApi = {
|
||||
...defaultMockApi,
|
||||
derive: {
|
||||
council: {
|
||||
proposals: () => of([
|
||||
{ proposal: createProposal(api.tx.bounties.approveBounty(1)) },
|
||||
{ proposal: createProposal(api.tx.treasury.approveProposal(1)) }] as DeriveCollectiveProposal[])
|
||||
}
|
||||
}
|
||||
} as unknown as DeriveApi;
|
||||
|
||||
const result = await firstValueFrom(bounties('', mockApi)());
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].proposals).toHaveLength(0);
|
||||
expect(result[1].proposals).toHaveLength(1);
|
||||
expect(result[1].proposals[0].proposal.method).toEqual('approveBounty');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Bytes, Option } from '@polkadot/types';
|
||||
import type { BountyIndex } from '@polkadot/types/interfaces';
|
||||
import type { PalletBountiesBounty } from '@polkadot/types/lookup';
|
||||
import type { DeriveApi, DeriveBounties, DeriveCollectiveProposal } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { filterBountiesProposals } from './helpers/filterBountyProposals';
|
||||
|
||||
type Result = [Option<PalletBountiesBounty>[], Option<Bytes>[], BountyIndex[], DeriveCollectiveProposal[]];
|
||||
|
||||
function parseResult ([maybeBounties, maybeDescriptions, ids, bountyProposals]: Result): DeriveBounties {
|
||||
const bounties: DeriveBounties = [];
|
||||
|
||||
maybeBounties.forEach((bounty, index) => {
|
||||
if (bounty.isSome) {
|
||||
bounties.push({
|
||||
bounty: bounty.unwrap(),
|
||||
description: maybeDescriptions[index].unwrapOrDefault().toUtf8(),
|
||||
index: ids[index],
|
||||
proposals: bountyProposals.filter((bountyProposal) => ids[index].eq(bountyProposal.proposal.args[0]))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return bounties;
|
||||
}
|
||||
|
||||
export function bounties (instanceId: string, api: DeriveApi): () => Observable<DeriveBounties> {
|
||||
const bountyBase = api.query.bounties || api.query.treasury;
|
||||
|
||||
return memo(instanceId, (): Observable<DeriveBounties> =>
|
||||
combineLatest([
|
||||
bountyBase.bountyCount(),
|
||||
api.query.council
|
||||
? api.query.council.proposalCount()
|
||||
: of(0)
|
||||
]).pipe(
|
||||
switchMap(() => combineLatest([
|
||||
bountyBase.bounties.keys(),
|
||||
api.derive.council
|
||||
? api.derive.council.proposals()
|
||||
: of([])
|
||||
])),
|
||||
switchMap(([keys, proposals]): Observable<Result> => {
|
||||
const ids = keys.map(({ args: [id] }) => id);
|
||||
|
||||
return combineLatest([
|
||||
bountyBase.bounties.multi(ids),
|
||||
bountyBase.bountyDescriptions.multi(ids),
|
||||
of(ids),
|
||||
of(filterBountiesProposals(api, proposals))
|
||||
]);
|
||||
}),
|
||||
map(parseResult)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveApi, DeriveCollectiveProposal } from '../../types';
|
||||
|
||||
export function filterBountiesProposals (api: DeriveApi, allProposals: DeriveCollectiveProposal[]): DeriveCollectiveProposal[] {
|
||||
const bountyTxBase = api.tx.bounties ? api.tx.bounties : api.tx.treasury;
|
||||
const bountyProposalCalls = [bountyTxBase.approveBounty, bountyTxBase.closeBounty, bountyTxBase.proposeCurator, bountyTxBase.unassignCurator];
|
||||
|
||||
return allProposals.filter((proposal) => bountyProposalCalls.find((bountyCall) => bountyCall.is(proposal.proposal)));
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './bounties';
|
||||
@@ -1,6 +1,130 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from '@polkadot/api/derive';
|
||||
import type { AnyFunction } from '@polkadot/types/types';
|
||||
import type { DeriveCustom, ExactDerive } from './derive';
|
||||
import type { DeriveApi } from './types';
|
||||
|
||||
export { packageInfo } from './packageInfo';
|
||||
import { lazyDeriveSection } from './util/lazy';
|
||||
import { derive } from './derive';
|
||||
|
||||
export * from './derive';
|
||||
export * from './type';
|
||||
|
||||
interface Avail {
|
||||
instances: string[];
|
||||
methods: string[];
|
||||
withDetect?: boolean;
|
||||
}
|
||||
|
||||
export { lazyDeriveSection };
|
||||
|
||||
// Enable derive only if some of these modules are available
|
||||
const checks: Record<string, Avail> = {
|
||||
contracts: {
|
||||
instances: ['contracts'],
|
||||
methods: []
|
||||
},
|
||||
council: {
|
||||
instances: ['council'],
|
||||
methods: [],
|
||||
withDetect: true
|
||||
},
|
||||
crowdloan: {
|
||||
instances: ['crowdloan'],
|
||||
methods: []
|
||||
},
|
||||
democracy: {
|
||||
instances: ['democracy'],
|
||||
methods: []
|
||||
},
|
||||
elections: {
|
||||
instances: ['phragmenElection', 'electionsPhragmen', 'elections', 'council'],
|
||||
methods: [],
|
||||
withDetect: true
|
||||
},
|
||||
imOnline: {
|
||||
instances: ['imOnline'],
|
||||
methods: []
|
||||
},
|
||||
membership: {
|
||||
instances: ['membership'],
|
||||
methods: []
|
||||
},
|
||||
parachains: {
|
||||
instances: ['parachains', 'registrar'],
|
||||
methods: []
|
||||
},
|
||||
session: {
|
||||
instances: ['session'],
|
||||
methods: []
|
||||
},
|
||||
society: {
|
||||
instances: ['society'],
|
||||
methods: []
|
||||
},
|
||||
staking: {
|
||||
instances: ['staking'],
|
||||
methods: ['erasRewardPoints']
|
||||
},
|
||||
technicalCommittee: {
|
||||
instances: ['technicalCommittee'],
|
||||
methods: [],
|
||||
withDetect: true
|
||||
},
|
||||
treasury: {
|
||||
instances: ['treasury'],
|
||||
methods: []
|
||||
}
|
||||
};
|
||||
|
||||
function getModuleInstances (api: DeriveApi, specName: string, moduleName: string): string[] {
|
||||
return api.registry.getModuleInstances(specName, moduleName) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object that will inject `api` into all the functions inside
|
||||
* `allSections`, and keep the object architecture of `allSections`.
|
||||
*/
|
||||
/** @internal */
|
||||
function injectFunctions (instanceId: string, api: DeriveApi, derives: DeriveCustom): ExactDerive {
|
||||
const result: Record<string, Record<string, AnyFunction>> = {};
|
||||
const names = Object.keys(derives);
|
||||
const keys = Object.keys(api.query);
|
||||
const specName = api.runtimeVersion.specName.toString();
|
||||
|
||||
const filterKeys = (q: string) => keys.includes(q);
|
||||
const filterInstances = (q: string) => getModuleInstances(api, specName, q).some(filterKeys);
|
||||
const filterMethods = (all: string[]) => (m: string) => all.some((q) => keys.includes(q) && api.query[q][m]);
|
||||
const getKeys = (s: string) => Object.keys(derives[s]);
|
||||
const creator = (s: string, m: string) => derives[s][m](instanceId, api);
|
||||
const isIncluded = (c: string) => (!checks[c] || (
|
||||
(checks[c].instances.some(filterKeys) && (
|
||||
!checks[c].methods.length ||
|
||||
checks[c].methods.every(filterMethods(checks[c].instances))
|
||||
)) ||
|
||||
(
|
||||
checks[c].withDetect &&
|
||||
checks[c].instances.some(filterInstances)
|
||||
)
|
||||
));
|
||||
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
const name = names[i];
|
||||
|
||||
isIncluded(name) &&
|
||||
lazyDeriveSection(result, name, getKeys, creator);
|
||||
}
|
||||
|
||||
return result as ExactDerive;
|
||||
}
|
||||
|
||||
// FIXME The return type of this function should be {...ExactDerive, ...DeriveCustom}
|
||||
// For now we just drop the custom derive typings
|
||||
/** @internal */
|
||||
export function getAvailableDerives (instanceId: string, api: DeriveApi, custom: DeriveCustom = {}): ExactDerive {
|
||||
return {
|
||||
...injectFunctions(instanceId, api, derive as DeriveCustom),
|
||||
...injectFunctions(instanceId, api, custom)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { unwrapBlockNumber } from './util';
|
||||
|
||||
/**
|
||||
* @name bestNumber
|
||||
* @returns The latest block number.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* api.derive.chain.bestNumber((blockNumber) => {
|
||||
* console.log(`the current best block is #${blockNumber}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const bestNumber = unwrapBlockNumber(
|
||||
(api: DeriveApi) =>
|
||||
api.derive.chain.subscribeNewHeads()
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { unwrapBlockNumber } from './util';
|
||||
|
||||
/**
|
||||
* @name bestNumberFinalized
|
||||
* @returns A BlockNumber
|
||||
* @description Get the latest finalized block number.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* api.derive.chain.bestNumberFinalized((blockNumber) => {
|
||||
* console.log(`the current finalized block is #${blockNumber}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const bestNumberFinalized = unwrapBlockNumber(
|
||||
(api: DeriveApi) =>
|
||||
api.rpc.chain.subscribeFinalizedHeads()
|
||||
);
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { BlockNumber } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { combineLatest, map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @name bestNumberLag
|
||||
* @returns A number of blocks
|
||||
* @description Calculates the lag between finalized head and best head
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* api.derive.chain.bestNumberLag((lag) => {
|
||||
* console.log(`finalized is ${lag} blocks behind head`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function bestNumberLag (instanceId: string, api: DeriveApi): () => Observable<BlockNumber> {
|
||||
return memo(instanceId, (): Observable<BlockNumber> =>
|
||||
combineLatest([
|
||||
api.derive.chain.bestNumber(),
|
||||
api.derive.chain.bestNumberFinalized()
|
||||
]).pipe(
|
||||
map(([bestNumber, bestNumberFinalized]): BlockNumber =>
|
||||
api.registry.createType('BlockNumber', bestNumber.sub(bestNumberFinalized))
|
||||
)
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { SignedBlockExtended } from '../type/types';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { catchError, combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { createSignedBlockExtended } from '../type';
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @name getBlock
|
||||
* @param {( Uint8Array | string )} hash - A block hash as U8 array or string.
|
||||
* @description Get a specific block (e.g. rpc.chain.getBlock) and extend it with the author
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* const { author, block } = await api.derive.chain.getBlock('0x123...456');
|
||||
*
|
||||
* console.log(`block #${block.header.number} was authored by ${author}`);
|
||||
* ```
|
||||
*/
|
||||
export function getBlock (instanceId: string, api: DeriveApi): (hash: Uint8Array | string) => Observable<SignedBlockExtended | undefined> {
|
||||
return memo(instanceId, (blockHash: Uint8Array | string): Observable<SignedBlockExtended | undefined> =>
|
||||
combineLatest([
|
||||
api.rpc.chain.getBlock(blockHash),
|
||||
api.queryAt(blockHash).pipe(
|
||||
switchMap((queryAt) =>
|
||||
combineLatest([
|
||||
queryAt.system.events(),
|
||||
queryAt.session
|
||||
? queryAt.session.validators()
|
||||
: of([])
|
||||
])
|
||||
)
|
||||
)
|
||||
]).pipe(
|
||||
map(([signedBlock, [events, validators]]) =>
|
||||
createSignedBlockExtended(api.registry, signedBlock, events, validators)
|
||||
),
|
||||
catchError((): Observable<undefined> =>
|
||||
// where rpc.chain.getHeader throws, we will land here - it can happen that
|
||||
// we supplied an invalid hash. (Due to defaults, storage will have an
|
||||
// empty value, so only the RPC is affected). So return undefined
|
||||
of()
|
||||
)
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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 { Registry } from '@polkadot/types/types';
|
||||
import type { HeaderExtended } from '../type/types';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { catchError, combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { createHeaderExtended } from '../type';
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @name getHeader
|
||||
* @param {( Uint8Array | string )} hash - A block hash as U8 array or string.
|
||||
* @returns An array containing the block header and the block author
|
||||
* @description Get a specific block header and extend it with the author
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* const { author, number } = await api.derive.chain.getHeader('0x123...456');
|
||||
*
|
||||
* console.log(`block #${number} was authored by ${author}`);
|
||||
* ```
|
||||
*/
|
||||
export function getHeader (instanceId: string, api: DeriveApi): (blockHash: Uint8Array | string) => Observable<HeaderExtended | undefined> {
|
||||
return memo(instanceId, (blockHash: Uint8Array | string): Observable<HeaderExtended | undefined> =>
|
||||
combineLatest([
|
||||
api.rpc.chain.getHeader(blockHash),
|
||||
api.queryAt(blockHash).pipe(
|
||||
switchMap((queryAt) =>
|
||||
queryAt.session
|
||||
? queryAt.session.validators()
|
||||
: of([] as AccountId[])
|
||||
)
|
||||
)
|
||||
]).pipe(
|
||||
map(([header, validators]) =>
|
||||
createHeaderExtended(header.registry as Registry, header, validators)
|
||||
),
|
||||
catchError((): Observable<undefined> =>
|
||||
// where rpc.chain.getHeader throws, we will land here - it can happen that
|
||||
// we supplied an invalid hash. (Due to defaults, storeage will have an
|
||||
// empty value, so only the RPC is affected). So return undefined
|
||||
of()
|
||||
)
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './bestNumber';
|
||||
export * from './bestNumberFinalized';
|
||||
export * from './bestNumberLag';
|
||||
export * from './getHeader';
|
||||
export * from './getBlock';
|
||||
export * from './subscribeNewBlocks';
|
||||
export * from './subscribeNewHeads';
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Registry } from '@polkadot/types/types';
|
||||
import type { SignedBlockExtended } from '../type/types';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { createSignedBlockExtended } from '../type';
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @name subscribeNewBlocks
|
||||
* @returns The latest block & events for that block
|
||||
*/
|
||||
export function subscribeNewBlocks (instanceId: string, api: DeriveApi): () => Observable<SignedBlockExtended> {
|
||||
return memo(instanceId, (): Observable<SignedBlockExtended> =>
|
||||
api.derive.chain.subscribeNewHeads().pipe(
|
||||
switchMap((header) => {
|
||||
const blockHash = header.createdAtHash || header.hash;
|
||||
|
||||
// we get the block first, setting up the registry
|
||||
return combineLatest([
|
||||
of(header),
|
||||
api.rpc.chain.getBlock(blockHash),
|
||||
api.queryAt(blockHash).pipe(
|
||||
switchMap((queryAt) =>
|
||||
queryAt.system.events()
|
||||
)
|
||||
)
|
||||
]);
|
||||
}),
|
||||
map(([header, block, events]) =>
|
||||
createSignedBlockExtended(block.registry as Registry, block, events, header.validators)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -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 { Registry } from '@polkadot/types/types';
|
||||
import type { HeaderExtended } from '../type/types';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { combineLatest, map, of } from 'rxjs';
|
||||
|
||||
import { createHeaderExtended } from '../type';
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @name subscribeNewHeads
|
||||
* @returns A header with the current header (including extracted author)
|
||||
* @description An observable of the current block header and it's author
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* api.derive.chain.subscribeNewHeads((header) => {
|
||||
* console.log(`block #${header.number} was authored by ${header.author}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function subscribeNewHeads (instanceId: string, api: DeriveApi): () => Observable<HeaderExtended> {
|
||||
return memo(instanceId, (): Observable<HeaderExtended> =>
|
||||
combineLatest([
|
||||
api.rpc.chain.subscribeNewHeads(),
|
||||
api.query.session
|
||||
? api.query.session.validators()
|
||||
: of(undefined)
|
||||
]).pipe(
|
||||
map(([header, validators]): HeaderExtended => {
|
||||
header.createdAtHash = header.hash;
|
||||
|
||||
return createHeaderExtended(header.registry as Registry, header, validators);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -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 { Compact } from '@polkadot/types';
|
||||
import type { BlockNumber } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
// re-export these - since these needs to be resolvable from api-derive, i.e. without this
|
||||
// we would emit code with ../<somewhere>/src embedded in the *.d.ts files
|
||||
export type { BlockNumber } from '@polkadot/types/interfaces';
|
||||
|
||||
export function unwrapBlockNumber <T extends { number: Compact<BlockNumber> }> (fn: (api: DeriveApi) => Observable<T>): (instanceId: string, api: DeriveApi) => () => Observable<BlockNumber> {
|
||||
return (instanceId: string, api: DeriveApi) =>
|
||||
memo(instanceId, () => fn(api).pipe(
|
||||
map((r) => r.number.unwrap())
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { DeriveApi } from '../types';
|
||||
import type { Collective } from './types';
|
||||
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { isFunction } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
export function getInstance (api: DeriveApi, section: string): string {
|
||||
const instances = api.registry.getModuleInstances(api.runtimeVersion.specName.toString(), section);
|
||||
|
||||
return instances && instances.length
|
||||
? instances[0]
|
||||
: section;
|
||||
}
|
||||
|
||||
export function withSection <T, F extends (...args: any[]) => Observable<T>> (_section: Collective, fn: (section: string, api: DeriveApi, instanceId: string) => F): (instanceId: string, api: DeriveApi) => F {
|
||||
return (instanceId: string, api: DeriveApi) =>
|
||||
memo(instanceId, fn(getInstance(api, _section), api, instanceId)) as unknown as F;
|
||||
}
|
||||
|
||||
export function callMethod <T> (method: 'members' | 'proposals' | 'proposalCount', empty: T): (_section: Collective) => (instanceId: string, api: DeriveApi) => () => Observable<T> {
|
||||
return (_section: Collective) =>
|
||||
withSection(_section, (section, api) =>
|
||||
(): Observable<T> =>
|
||||
isFunction(api.query[section]?.[method])
|
||||
? api.query[section as 'council'][method]() as unknown as Observable<T>
|
||||
: of(empty)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './members';
|
||||
export * from './prime';
|
||||
export * from './proposals';
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AccountId } from '@polkadot/types/interfaces';
|
||||
|
||||
import { callMethod } from './helpers';
|
||||
|
||||
// We are re-exporting these from here to ensure that *.d.ts generation is correct
|
||||
export type { AccountId } from '@polkadot/types/interfaces';
|
||||
|
||||
export const members = callMethod<AccountId[]>('members', []);
|
||||
@@ -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 { AccountId } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi } from '../types';
|
||||
import type { Collective } from './types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { isFunction } from '@polkadot/util';
|
||||
|
||||
import { withSection } from './helpers';
|
||||
|
||||
// We are re-exporting these from here to ensure that *.d.ts generation is correct
|
||||
export type { AccountId } from '@polkadot/types/interfaces';
|
||||
|
||||
export function prime (_section: Collective): (instanceId: string, api: DeriveApi) => () => Observable<AccountId | null> {
|
||||
return withSection(_section, (section, api) =>
|
||||
(): Observable<AccountId | null> =>
|
||||
isFunction(api.query[section as 'council']?.prime)
|
||||
? api.query[section as 'council'].prime().pipe(
|
||||
map((optPrime): AccountId | null =>
|
||||
optPrime.unwrapOr(null)
|
||||
)
|
||||
)
|
||||
: of(null)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// 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 { Hash, Proposal, Votes } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveCollectiveProposal } from '../types';
|
||||
import type { Collective } from './types';
|
||||
|
||||
import { catchError, combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { isFunction } from '@polkadot/util';
|
||||
|
||||
import { firstObservable } from '../util';
|
||||
import { callMethod, withSection } from './helpers';
|
||||
|
||||
// We are re-exporting these from here to ensure that *.d.ts generation is correct
|
||||
export type { Option, u32 } from '@polkadot/types';
|
||||
export type { Hash, Proposal, Votes } from '@polkadot/types/interfaces';
|
||||
|
||||
type Result = [(Hash | Uint8Array | string)[], (Option<Proposal> | null)[], Option<Votes>[]];
|
||||
|
||||
function parse (api: DeriveApi, [hashes, proposals, votes]: Result): DeriveCollectiveProposal[] {
|
||||
return proposals
|
||||
.map((o, index): DeriveCollectiveProposal | null =>
|
||||
o && o.isSome
|
||||
? {
|
||||
hash: api.registry.createType('Hash', hashes[index]),
|
||||
proposal: o.unwrap(),
|
||||
votes: votes[index].unwrapOr(null)
|
||||
}
|
||||
: null
|
||||
)
|
||||
.filter((proposal): proposal is DeriveCollectiveProposal => !!proposal);
|
||||
}
|
||||
|
||||
function _proposalsFrom (section: string, api: DeriveApi, hashes: (Hash | Uint8Array | string)[]): Observable<DeriveCollectiveProposal[]> {
|
||||
return (isFunction(api.query[section]?.proposals) && hashes.length
|
||||
? combineLatest([
|
||||
of(hashes),
|
||||
// this should simply be api.query[section].proposalOf.multi<Option<Proposal>>(hashes),
|
||||
// however we have had cases on Edgeware where the indices have moved around after an
|
||||
// upgrade, which results in invalid on-chain data
|
||||
combineLatest(hashes.map((h) =>
|
||||
api.query[section].proposalOf<Option<Proposal>>(h).pipe(
|
||||
catchError(() => of(null))
|
||||
)
|
||||
)),
|
||||
api.query[section].voting.multi<Option<Votes>>(hashes)
|
||||
])
|
||||
: of<Result>([[], [], []])
|
||||
).pipe(
|
||||
map((r) => parse(api, r))
|
||||
);
|
||||
}
|
||||
|
||||
export function hasProposals (_section: Collective): (instanceId: string, api: DeriveApi) => () => Observable<boolean> {
|
||||
return withSection(_section, (section, api) =>
|
||||
(): Observable<boolean> =>
|
||||
of(isFunction(api.query[section]?.proposals))
|
||||
);
|
||||
}
|
||||
|
||||
export function proposals (_section: Collective): (instanceId: string, api: DeriveApi) => () => Observable<DeriveCollectiveProposal[]> {
|
||||
return withSection(_section, (section, api) =>
|
||||
(): Observable<DeriveCollectiveProposal[]> =>
|
||||
api.derive[section as 'council'].proposalHashes().pipe(
|
||||
switchMap((all) => _proposalsFrom(section, api, all))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function proposal (_section: Collective): (instanceId: string, api: DeriveApi) => (hash: Hash | Uint8Array | string) => Observable<DeriveCollectiveProposal | null> {
|
||||
return withSection(_section, (section, api) =>
|
||||
(hash: Hash | Uint8Array | string): Observable<DeriveCollectiveProposal | null> =>
|
||||
isFunction(api.query[section]?.proposals)
|
||||
? firstObservable(_proposalsFrom(section, api, [hash]))
|
||||
: of(null)
|
||||
);
|
||||
}
|
||||
|
||||
export const proposalCount = callMethod<u32 | null>('proposalCount', null);
|
||||
export const proposalHashes = callMethod<Hash[]>('proposals', []);
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export type Collective = 'council' | 'membership' | 'technicalCommittee';
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { DeriveApi, DeriveContractFees } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
type ResultV2 = [BN, BN, BN, BN, BN, BN, BN, BN, BN, BN];
|
||||
|
||||
// query via constants (current applicable path)
|
||||
function queryConstants (api: DeriveApi): Observable<ResultV2> {
|
||||
return of([
|
||||
// deprecated
|
||||
api.consts.contracts.callBaseFee || api.registry.createType('Balance'),
|
||||
api.consts.contracts.contractFee || api.registry.createType('Balance'),
|
||||
api.consts.contracts.creationFee || api.registry.createType('Balance'),
|
||||
api.consts.contracts.transactionBaseFee || api.registry.createType('Balance'),
|
||||
api.consts.contracts.transactionByteFee || api.registry.createType('Balance'),
|
||||
api.consts.contracts.transferFee || api.registry.createType('Balance'),
|
||||
|
||||
// current
|
||||
api.consts.contracts.rentByteFee,
|
||||
api.consts.contracts.rentDepositOffset,
|
||||
api.consts.contracts.surchargeReward,
|
||||
api.consts.contracts.tombstoneDeposit
|
||||
]) as unknown as Observable<ResultV2>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name fees
|
||||
* @returns An object containing the combined results of the queries for
|
||||
* all relevant contract fees as declared in the substrate chain spec.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* api.derive.contracts.fees(([creationFee, transferFee]) => {
|
||||
* console.log(`The fee for creating a new contract on this chain is ${creationFee} units. The fee required to call this contract is ${transferFee} units.`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function fees (instanceId: string, api: DeriveApi): () => Observable<DeriveContractFees> {
|
||||
return memo(instanceId, (): Observable<DeriveContractFees> => {
|
||||
return queryConstants(api).pipe(
|
||||
map(([callBaseFee, contractFee, creationFee, transactionBaseFee, transactionByteFee, transferFee, rentByteFee, rentDepositOffset, surchargeReward, tombstoneDeposit]): DeriveContractFees => ({
|
||||
callBaseFee,
|
||||
contractFee,
|
||||
creationFee,
|
||||
rentByteFee,
|
||||
rentDepositOffset,
|
||||
surchargeReward,
|
||||
tombstoneDeposit,
|
||||
transactionBaseFee,
|
||||
transactionByteFee,
|
||||
transferFee
|
||||
}))
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './fees';
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { hasProposals as collectiveHasProposals, members as collectiveMembers, prime as collectivePrime, proposal as collectiveProposal, proposalCount as collectiveProposalCount, proposalHashes as collectiveProposalHashes, proposals as collectiveProposals } from '../collective';
|
||||
|
||||
export * from './votes';
|
||||
export * from './votesOf';
|
||||
|
||||
export const members = collectiveMembers('council');
|
||||
|
||||
export const hasProposals = collectiveHasProposals('council');
|
||||
export const proposal = collectiveProposal('council');
|
||||
export const proposalCount = collectiveProposalCount('council');
|
||||
export const proposalHashes = collectiveProposalHashes('council');
|
||||
export const proposals = collectiveProposals('council');
|
||||
|
||||
export const prime = collectivePrime('council');
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AccountId, Balance } from '@polkadot/types/interfaces';
|
||||
|
||||
export interface DeriveCouncilVote {
|
||||
stake: Balance;
|
||||
votes: AccountId[];
|
||||
}
|
||||
|
||||
export type DeriveCouncilVotes = [AccountId, DeriveCouncilVote][];
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { QueryableModuleStorage } from '@polkadot/api-base/types';
|
||||
import type { Vec } from '@polkadot/types';
|
||||
import type { AccountId, Balance } from '@polkadot/types/interfaces';
|
||||
import type { PalletElectionsPhragmenVoter } from '@polkadot/types/lookup';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
import type { DeriveApi, DeriveCouncilVote, DeriveCouncilVotes } from '../types';
|
||||
|
||||
import { combineLatest, map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
// Voter is current tuple is 2.x-era
|
||||
type VoteEntry = PalletElectionsPhragmenVoter | ITuple<[Balance, Vec<AccountId>]>;
|
||||
|
||||
function isVoter (value: VoteEntry): value is PalletElectionsPhragmenVoter {
|
||||
return !Array.isArray(value);
|
||||
}
|
||||
|
||||
function retrieveStakeOf (elections: QueryableModuleStorage<'rxjs'>): Observable<[AccountId, Balance][]> {
|
||||
return elections.stakeOf.entries<Balance, [AccountId]>().pipe(
|
||||
map((entries) =>
|
||||
entries.map(([{ args: [accountId] }, stake]) => [accountId, stake])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function retrieveVoteOf (elections: QueryableModuleStorage<'rxjs'>): Observable<[AccountId, AccountId[]][]> {
|
||||
return elections.votesOf.entries<Vec<AccountId>, [AccountId]>().pipe(
|
||||
map((entries) =>
|
||||
entries.map(([{ args: [accountId] }, votes]) => [accountId, votes])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function retrievePrev (api: DeriveApi, elections: QueryableModuleStorage<'rxjs'>): Observable<DeriveCouncilVotes> {
|
||||
return combineLatest([
|
||||
retrieveStakeOf(elections),
|
||||
retrieveVoteOf(elections)
|
||||
]).pipe(
|
||||
map(([stakes, votes]): DeriveCouncilVotes => {
|
||||
const result: DeriveCouncilVotes = [];
|
||||
|
||||
votes.forEach(([voter, votes]): void => {
|
||||
result.push([voter, { stake: api.registry.createType('Balance'), votes }]);
|
||||
});
|
||||
|
||||
stakes.forEach(([staker, stake]): void => {
|
||||
const entry = result.find(([voter]) => voter.eq(staker));
|
||||
|
||||
if (entry) {
|
||||
entry[1].stake = stake;
|
||||
} else {
|
||||
result.push([staker, { stake, votes: [] }]);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function retrieveCurrent (elections: QueryableModuleStorage<'rxjs'>): Observable<DeriveCouncilVotes> {
|
||||
return elections.voting.entries<VoteEntry, [AccountId]>().pipe(
|
||||
map((entries): DeriveCouncilVotes =>
|
||||
entries.map(([{ args: [accountId] }, value]): [AccountId, DeriveCouncilVote] => [
|
||||
accountId,
|
||||
isVoter(value)
|
||||
? { stake: value.stake, votes: value.votes }
|
||||
: { stake: value[0], votes: value[1] }
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function votes (instanceId: string, api: DeriveApi): () => Observable<DeriveCouncilVotes> {
|
||||
const elections = api.query.phragmenElection || api.query.electionsPhragmen || api.query.elections;
|
||||
|
||||
return memo(instanceId, (): Observable<DeriveCouncilVotes> =>
|
||||
elections
|
||||
? elections.stakeOf
|
||||
? retrievePrev(api, elections)
|
||||
: retrieveCurrent(elections)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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, DeriveCouncilVote } from '../types';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
export function votesOf (instanceId: string, api: DeriveApi): (accountId: string | Uint8Array | AccountId) => Observable<DeriveCouncilVote> {
|
||||
return memo(instanceId, (accountId: string | Uint8Array | AccountId): Observable<DeriveCouncilVote> =>
|
||||
api.derive.council.votes().pipe(
|
||||
map((votes): DeriveCouncilVote =>
|
||||
(
|
||||
votes.find(([from]) => from.eq(accountId)) ||
|
||||
[null, { stake: api.registry.createType('Balance'), votes: [] as AccountId[] }]
|
||||
)[1]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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 { PolkadotRuntimeCommonCrowdloanFundInfo } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { u8aConcat, u8aToHex } from '@polkadot/util';
|
||||
import { blake2AsU8a } from '@polkadot/util-crypto';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
function createChildKey ({ trieIndex }: PolkadotRuntimeCommonCrowdloanFundInfo): string {
|
||||
return u8aToHex(
|
||||
u8aConcat(
|
||||
':child_storage:default:',
|
||||
blake2AsU8a(
|
||||
u8aConcat('crowdloan', trieIndex.toU8a())
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function childKey (instanceId: string, api: DeriveApi): (paraId: string | number | BN) => Observable<string | null> {
|
||||
return memo(instanceId, (paraId: string | number | BN): Observable<string | null> =>
|
||||
api.query.crowdloan.funds<Option<PolkadotRuntimeCommonCrowdloanFundInfo>>(paraId).pipe(
|
||||
map((optInfo) =>
|
||||
optInfo.isSome
|
||||
? createChildKey(optInfo.unwrap())
|
||||
: null
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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 { BN } from '@polkadot/util';
|
||||
import type { DeriveApi, DeriveContributions } from '../types';
|
||||
|
||||
import { BehaviorSubject, combineLatest, EMPTY, map, of, startWith, switchMap, tap, toArray } from 'rxjs';
|
||||
|
||||
import { arrayFlatten, isFunction } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { extractContributed } from './util';
|
||||
|
||||
interface Changes {
|
||||
added: string[];
|
||||
blockHash: string;
|
||||
removed: string[];
|
||||
}
|
||||
|
||||
const PAGE_SIZE_K = 1000; // limit aligned with the 1k on the node (trie lookups are heavy)
|
||||
|
||||
function _getUpdates (api: DeriveApi, paraId: string | number | BN): Observable<Changes> {
|
||||
let added: string[] = [];
|
||||
let removed: string[] = [];
|
||||
|
||||
return api.query.system.events().pipe(
|
||||
switchMap((events): Observable<Changes> => {
|
||||
const changes = extractContributed(paraId, events);
|
||||
|
||||
if (changes.added.length || changes.removed.length) {
|
||||
added = added.concat(...changes.added);
|
||||
removed = removed.concat(...changes.removed);
|
||||
|
||||
return of({ added, addedDelta: changes.added, blockHash: events.createdAtHash?.toHex() || '-', removed, removedDelta: changes.removed });
|
||||
}
|
||||
|
||||
return EMPTY;
|
||||
}),
|
||||
startWith({ added, addedDelta: [], blockHash: '-', removed, removedDelta: [] })
|
||||
);
|
||||
}
|
||||
|
||||
function _eventTriggerAll (api: DeriveApi, paraId: string | number | BN): Observable<string> {
|
||||
return api.query.system.events().pipe(
|
||||
switchMap((events): Observable<string> => {
|
||||
const items = events.filter(({ event: { data: [eventParaId], method, section } }) =>
|
||||
section === 'crowdloan' &&
|
||||
['AllRefunded', 'Dissolved', 'PartiallyRefunded'].includes(method) &&
|
||||
eventParaId.eq(paraId)
|
||||
);
|
||||
|
||||
return items.length
|
||||
? of(events.createdAtHash?.toHex() || '-')
|
||||
: EMPTY;
|
||||
}),
|
||||
startWith('-')
|
||||
);
|
||||
}
|
||||
|
||||
function _getKeysPaged (api: DeriveApi, childKey: string): Observable<StorageKey[]> {
|
||||
const startSubject = new BehaviorSubject<string | undefined>(undefined);
|
||||
|
||||
return startSubject.pipe(
|
||||
switchMap((startKey) =>
|
||||
api.rpc.childstate.getKeysPaged(childKey, '0x', PAGE_SIZE_K, startKey)
|
||||
),
|
||||
tap((keys): void => {
|
||||
setTimeout((): void => {
|
||||
keys.length === PAGE_SIZE_K
|
||||
? startSubject.next(keys[PAGE_SIZE_K - 1].toHex())
|
||||
: startSubject.complete();
|
||||
}, 0);
|
||||
}),
|
||||
toArray(), // toArray since we want to startSubject to be completed
|
||||
map((keyArr: StorageKey[][]) => arrayFlatten(keyArr))
|
||||
);
|
||||
}
|
||||
|
||||
function _getAll (api: DeriveApi, paraId: string | number | BN, childKey: string): Observable<string[]> {
|
||||
return _eventTriggerAll(api, paraId).pipe(
|
||||
switchMap(() =>
|
||||
isFunction(api.rpc.childstate.getKeysPaged)
|
||||
? _getKeysPaged(api, childKey)
|
||||
: api.rpc.childstate.getKeys(childKey, '0x')
|
||||
),
|
||||
map((keys) =>
|
||||
keys.map((k) => k.toHex())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function _contributions (api: DeriveApi, paraId: string | number | BN, childKey: string): Observable<DeriveContributions> {
|
||||
return combineLatest([
|
||||
_getAll(api, paraId, childKey),
|
||||
_getUpdates(api, paraId)
|
||||
]).pipe(
|
||||
map(([keys, { added, blockHash, removed }]): DeriveContributions => {
|
||||
const contributorsMap: Record<string, boolean> = {};
|
||||
|
||||
keys.forEach((k): void => {
|
||||
contributorsMap[k] = true;
|
||||
});
|
||||
|
||||
added.forEach((k): void => {
|
||||
contributorsMap[k] = true;
|
||||
});
|
||||
|
||||
removed.forEach((k): void => {
|
||||
delete contributorsMap[k];
|
||||
});
|
||||
|
||||
return {
|
||||
blockHash,
|
||||
contributorsHex: Object.keys(contributorsMap)
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function contributions (instanceId: string, api: DeriveApi): (paraId: string | number | BN) => Observable<DeriveContributions> {
|
||||
return memo(instanceId, (paraId: string | number | BN): Observable<DeriveContributions> =>
|
||||
api.derive.crowdloan.childKey(paraId).pipe(
|
||||
switchMap((childKey) =>
|
||||
childKey
|
||||
? _contributions(api, paraId, childKey)
|
||||
: of({ blockHash: '-', contributorsHex: [] })
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './childKey';
|
||||
export * from './contributions';
|
||||
export * from './ownContributions';
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { DeriveApi, DeriveOwnContributions } from '../types';
|
||||
|
||||
import { combineLatest, EMPTY, map, of, startWith, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { extractContributed } from './util';
|
||||
|
||||
function _getValues (api: DeriveApi, childKey: string, keys: string[]): Observable<DeriveOwnContributions> {
|
||||
// We actually would love to use multi-keys https://github.com/paritytech/substrate/issues/9203
|
||||
return combineLatest(keys.map((k) => api.rpc.childstate.getStorage(childKey, k))).pipe(
|
||||
map((values) =>
|
||||
values
|
||||
.map((v) => api.registry.createType('Option<StorageData>', v))
|
||||
.map((o) =>
|
||||
o.isSome
|
||||
? api.registry.createType('Balance', o.unwrap())
|
||||
: api.registry.createType('Balance')
|
||||
)
|
||||
.reduce((all: DeriveOwnContributions, b, index): DeriveOwnContributions => ({
|
||||
...all,
|
||||
[keys[index]]: b
|
||||
}), {})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function _watchOwnChanges (api: DeriveApi, paraId: string | number | BN, childkey: string, keys: string[]): Observable<DeriveOwnContributions> {
|
||||
return api.query.system.events().pipe(
|
||||
switchMap((events): Observable<DeriveOwnContributions> => {
|
||||
const changes = extractContributed(paraId, events);
|
||||
const filtered = keys.filter((k) =>
|
||||
changes.added.includes(k) ||
|
||||
changes.removed.includes(k)
|
||||
);
|
||||
|
||||
return filtered.length
|
||||
? _getValues(api, childkey, filtered)
|
||||
: EMPTY;
|
||||
}),
|
||||
startWith({})
|
||||
);
|
||||
}
|
||||
|
||||
function _contributions (api: DeriveApi, paraId: string | number | BN, childKey: string, keys: string[]): Observable<DeriveOwnContributions> {
|
||||
return combineLatest([
|
||||
_getValues(api, childKey, keys),
|
||||
_watchOwnChanges(api, paraId, childKey, keys)
|
||||
]).pipe(
|
||||
map(([all, latest]) => ({
|
||||
...all,
|
||||
...latest
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
export function ownContributions (instanceId: string, api: DeriveApi): (paraId: string | number | BN, keys: string[]) => Observable<DeriveOwnContributions> {
|
||||
return memo(instanceId, (paraId: string | number | BN, keys: string[]): Observable<DeriveOwnContributions> =>
|
||||
api.derive.crowdloan.childKey(paraId).pipe(
|
||||
switchMap((childKey) =>
|
||||
childKey && keys.length
|
||||
? _contributions(api, paraId, childKey, keys)
|
||||
: of({})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Balance } from '@polkadot/types/interfaces';
|
||||
|
||||
export interface DeriveContributions {
|
||||
blockHash: string;
|
||||
contributorsHex: string[];
|
||||
}
|
||||
|
||||
export type DeriveOwnContributions = Record<string, Balance>;
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { FrameSystemEventRecord } from '@polkadot/types/lookup';
|
||||
import type { Vec } from '@polkadot/types-codec';
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
interface Changes {
|
||||
added: string[];
|
||||
blockHash: string;
|
||||
removed: string[];
|
||||
}
|
||||
|
||||
export function extractContributed (paraId: string | number | BN, events: Vec<FrameSystemEventRecord>): Changes {
|
||||
const added: string[] = [];
|
||||
const removed: string[] = [];
|
||||
|
||||
return events
|
||||
.filter(({ event: { data: [, eventParaId], method, section } }) =>
|
||||
section === 'crowdloan' &&
|
||||
['Contributed', 'Withdrew'].includes(method) &&
|
||||
eventParaId.eq(paraId)
|
||||
)
|
||||
.reduce((result: Changes, { event: { data: [accountId], method } }): Changes => {
|
||||
if (method === 'Contributed') {
|
||||
result.added.push(accountId.toHex());
|
||||
} else {
|
||||
result.removed.push(accountId.toHex());
|
||||
}
|
||||
|
||||
return result;
|
||||
}, { added, blockHash: events.createdAtHash?.toHex() || '-', removed });
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// 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 { BlockNumber, Hash, ReferendumIndex } from '@polkadot/types/interfaces';
|
||||
import type { PalletSchedulerScheduledV2 } from '@polkadot/types/lookup';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
import type { DeriveApi, DeriveDispatch, DeriveProposalImage } from '../types';
|
||||
|
||||
import { catchError, combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { isFunction, stringToHex } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
const DEMOCRACY_ID = stringToHex('democrac');
|
||||
|
||||
interface SchedulerInfo {
|
||||
at: BlockNumber;
|
||||
imageHash: Hash;
|
||||
index: ReferendumIndex;
|
||||
}
|
||||
|
||||
function queryQueue (api: DeriveApi): Observable<DeriveDispatch[]> {
|
||||
return api.query.democracy.dispatchQueue<Vec<ITuple<[BlockNumber, Hash, ReferendumIndex]>>>().pipe(
|
||||
switchMap((dispatches) =>
|
||||
combineLatest([
|
||||
of(dispatches),
|
||||
api.derive.democracy.preimages(
|
||||
dispatches.map(([, hash]) => hash))
|
||||
])
|
||||
),
|
||||
map(([dispatches, images]) =>
|
||||
dispatches.map(([at, imageHash, index], dispatchIndex): DeriveDispatch => ({
|
||||
at,
|
||||
image: images[dispatchIndex],
|
||||
imageHash,
|
||||
index
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function schedulerEntries (api: DeriveApi): Observable<[BlockNumber[], (Option<PalletSchedulerScheduledV2>[] | null)[]]> {
|
||||
// We don't get entries, but rather we get the keys (triggered via finished referendums) and
|
||||
// the subscribe to those keys - this means we pickup when the schedulers actually executes
|
||||
// at a block, the entry for that block will become empty
|
||||
return api.derive.democracy.referendumsFinished().pipe(
|
||||
switchMap(() =>
|
||||
api.query.scheduler.agenda.keys()
|
||||
),
|
||||
switchMap((keys) => {
|
||||
const blockNumbers = keys.map(({ args: [blockNumber] }) => blockNumber);
|
||||
|
||||
return blockNumbers.length
|
||||
? combineLatest([
|
||||
of(blockNumbers),
|
||||
// this should simply be api.query.scheduler.agenda.multi,
|
||||
// however we have had cases on Darwinia where the indices have moved around after an
|
||||
// upgrade, which results in invalid on-chain data
|
||||
combineLatest(blockNumbers.map((blockNumber) =>
|
||||
// this does create an issue since it discards all at that block
|
||||
api.query.scheduler.agenda(blockNumber).pipe(catchError(() => of(null)))
|
||||
))
|
||||
])
|
||||
: of<[BlockNumber[], null[]]>([[], []]);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function queryScheduler (api: DeriveApi): Observable<DeriveDispatch[]> {
|
||||
return schedulerEntries(api).pipe(
|
||||
switchMap(([blockNumbers, agendas]): Observable<[SchedulerInfo[], (DeriveProposalImage | undefined)[]]> => {
|
||||
const result: SchedulerInfo[] = [];
|
||||
|
||||
blockNumbers.forEach((at, index): void => {
|
||||
(agendas[index] || []).filter((o) => o.isSome).forEach((o): void => {
|
||||
const scheduled = o.unwrap();
|
||||
|
||||
if (scheduled.maybeId.isSome) {
|
||||
const id = scheduled.maybeId.unwrap().toHex();
|
||||
|
||||
id.startsWith(DEMOCRACY_ID) && result.push({ at, imageHash: scheduled.call.args[0] as Hash, index: api.registry.createType('(u64, ReferendumIndex)', id)[1] });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return combineLatest([
|
||||
of(result),
|
||||
result.length
|
||||
? api.derive.democracy.preimages(result.map(({ imageHash }) => imageHash))
|
||||
: of([])
|
||||
]);
|
||||
}),
|
||||
map(([infos, images]): DeriveDispatch[] =>
|
||||
infos.map((info, index) => ({ ...info, image: images[index] }))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function dispatchQueue (instanceId: string, api: DeriveApi): () => Observable<DeriveDispatch[]> {
|
||||
return memo(instanceId, (): Observable<DeriveDispatch[]> =>
|
||||
isFunction(api.query.scheduler?.agenda)
|
||||
? queryScheduler(api)
|
||||
: api.query.democracy.dispatchQueue
|
||||
? queryQueue(api)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './dispatchQueue';
|
||||
export * from './locks';
|
||||
export * from './nextExternal';
|
||||
export * from './preimage';
|
||||
export * from './preimages';
|
||||
export * from './proposals';
|
||||
export * from './referendumIds';
|
||||
export * from './referendums';
|
||||
export * from './referendumsActive';
|
||||
export * from './referendumsFinished';
|
||||
export * from './referendumsInfo';
|
||||
export * from './sqrtElectorate';
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId, ReferendumInfoTo239, Vote } from '@polkadot/types/interfaces';
|
||||
import type { PalletDemocracyReferendumInfo, PalletDemocracyVoteVoting } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { DeriveApi, DeriveDemocracyLock } from '../types';
|
||||
|
||||
import { map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { BN_ZERO, isUndefined } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
type ReferendumInfoFinished = PalletDemocracyReferendumInfo['asFinished'];
|
||||
type VotingDelegating = PalletDemocracyVoteVoting['asDelegating'];
|
||||
type VotingDirect = PalletDemocracyVoteVoting['asDirect'];
|
||||
type VotingDirectVote = VotingDirect['votes'][0];
|
||||
|
||||
const LOCKUPS = [0, 1, 2, 4, 8, 16, 32];
|
||||
|
||||
function parseEnd (api: DeriveApi, vote: Vote, { approved, end }: ReferendumInfoFinished): [BN, BN] {
|
||||
return [
|
||||
end,
|
||||
(approved.isTrue && vote.isAye) || (approved.isFalse && vote.isNay)
|
||||
? end.add(
|
||||
(
|
||||
api.consts.democracy.voteLockingPeriod ||
|
||||
api.consts.democracy.enactmentPeriod
|
||||
).muln(LOCKUPS[vote.conviction.index])
|
||||
)
|
||||
: BN_ZERO
|
||||
];
|
||||
}
|
||||
|
||||
function parseLock (api: DeriveApi, [referendumId, accountVote]: VotingDirectVote, referendum: PalletDemocracyReferendumInfo): DeriveDemocracyLock {
|
||||
const { balance, vote } = accountVote.asStandard;
|
||||
const [referendumEnd, unlockAt] = referendum.isFinished
|
||||
? parseEnd(api, vote, referendum.asFinished)
|
||||
: [BN_ZERO, BN_ZERO];
|
||||
|
||||
return { balance, isDelegated: false, isFinished: referendum.isFinished, referendumEnd, referendumId, unlockAt, vote };
|
||||
}
|
||||
|
||||
function delegateLocks (api: DeriveApi, { balance, conviction, target }: VotingDelegating): Observable<DeriveDemocracyLock[]> {
|
||||
return api.derive.democracy.locks(target).pipe(
|
||||
map((available): DeriveDemocracyLock[] =>
|
||||
available.map(({ isFinished, referendumEnd, referendumId, unlockAt, vote }): DeriveDemocracyLock => ({
|
||||
balance,
|
||||
isDelegated: true,
|
||||
isFinished,
|
||||
referendumEnd,
|
||||
referendumId,
|
||||
unlockAt: unlockAt.isZero()
|
||||
? unlockAt
|
||||
: referendumEnd.add(
|
||||
(
|
||||
api.consts.democracy.voteLockingPeriod ||
|
||||
api.consts.democracy.enactmentPeriod
|
||||
).muln(LOCKUPS[conviction.index])
|
||||
),
|
||||
vote: api.registry.createType('Vote', { aye: vote.isAye, conviction })
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function directLocks (api: DeriveApi, { votes }: VotingDirect): Observable<DeriveDemocracyLock[]> {
|
||||
if (!votes.length) {
|
||||
return of([]);
|
||||
}
|
||||
|
||||
return api.query.democracy.referendumInfoOf.multi(votes.map(([referendumId]) => referendumId)).pipe(
|
||||
map((referendums) =>
|
||||
votes
|
||||
.map((vote, index): [VotingDirectVote, PalletDemocracyReferendumInfo | ReferendumInfoTo239 | null] =>
|
||||
[vote, referendums[index].unwrapOr(null)]
|
||||
)
|
||||
.filter((item): item is [VotingDirectVote, PalletDemocracyReferendumInfo] =>
|
||||
!!item[1] && isUndefined((item[1] as ReferendumInfoTo239).end) && item[0][1].isStandard
|
||||
)
|
||||
.map(([directVote, referendum]) =>
|
||||
parseLock(api, directVote, referendum)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function locks (instanceId: string, api: DeriveApi): (accountId: string | AccountId) => Observable<DeriveDemocracyLock[]> {
|
||||
return memo(instanceId, (accountId: string | AccountId): Observable<DeriveDemocracyLock[]> =>
|
||||
api.query.democracy.votingOf
|
||||
? api.query.democracy.votingOf(accountId).pipe(
|
||||
switchMap((voting): Observable<DeriveDemocracyLock[]> =>
|
||||
voting.isDirect
|
||||
? directLocks(api, voting.asDirect)
|
||||
: voting.isDelegating
|
||||
? delegateLocks(api, voting.asDelegating)
|
||||
: of([])
|
||||
)
|
||||
)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// 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 { H256 } from '@polkadot/types/interfaces';
|
||||
import type { PalletDemocracyVoteThreshold } from '@polkadot/types/lookup';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
import type { DeriveApi, DeriveProposalExternal } from '../types';
|
||||
|
||||
import { map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
function withImage (api: DeriveApi, nextOpt: Option<ITuple<[H256, PalletDemocracyVoteThreshold]>>): Observable<DeriveProposalExternal | null> {
|
||||
if (nextOpt.isNone) {
|
||||
return of(null);
|
||||
}
|
||||
|
||||
const [imageHash, threshold] = nextOpt.unwrap();
|
||||
|
||||
return api.derive.democracy.preimage(imageHash).pipe(
|
||||
map((image): DeriveProposalExternal => ({
|
||||
image,
|
||||
imageHash,
|
||||
threshold
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
export function nextExternal (instanceId: string, api: DeriveApi): () => Observable<DeriveProposalExternal | null> {
|
||||
return memo(instanceId, (): Observable<DeriveProposalExternal | null> =>
|
||||
api.query.democracy?.nextExternal
|
||||
? api.query.democracy.nextExternal().pipe(
|
||||
switchMap((nextOpt) => withImage(api, nextOpt))
|
||||
)
|
||||
: of(null)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Hash } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveProposalImage } from '../types';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { parseImage } from './util';
|
||||
|
||||
export function preimage (instanceId: string, api: DeriveApi): (hash: Hash) => Observable<DeriveProposalImage | undefined> {
|
||||
return memo(instanceId, (hash: Hash): Observable<DeriveProposalImage | undefined> =>
|
||||
api.query.democracy.preimages(hash).pipe(
|
||||
map((imageOpt) => parseImage(api, imageOpt))
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Hash } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveProposalImage } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { parseImage } from './util';
|
||||
|
||||
export function preimages (instanceId: string, api: DeriveApi): (hashes: Hash[]) => Observable<(DeriveProposalImage | undefined)[]> {
|
||||
return memo(instanceId, (hashes: Hash[]): Observable<(DeriveProposalImage | undefined)[]> =>
|
||||
hashes.length
|
||||
? api.query.democracy.preimages.multi(hashes).pipe(
|
||||
map((images): (DeriveProposalImage | undefined)[] =>
|
||||
images.map((imageOpt) => parseImage(api, imageOpt))
|
||||
)
|
||||
)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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, Balance, Hash, PropIndex } from '@polkadot/types/interfaces';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
import type { DeriveApi, DeriveProposal, DeriveProposalImage } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { isFunction } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
type DepositorsNew = Option<ITuple<[Vec<AccountId>, Balance]>>;
|
||||
type DepositorsOld = Option<ITuple<[Balance, Vec<AccountId>]>>;
|
||||
type Depositors = DepositorsNew | DepositorsOld;
|
||||
type Proposals = ITuple<[PropIndex, Hash, AccountId]>[];
|
||||
type Result = [Proposals, (DeriveProposalImage | undefined)[], Depositors[]];
|
||||
|
||||
function isNewDepositors (depositors: ITuple<[Vec<AccountId>, Balance]> | ITuple<[Balance, Vec<AccountId>]>): depositors is ITuple<[Vec<AccountId>, Balance]> {
|
||||
// Detect balance...
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
return isFunction((depositors[1] as Balance).mul);
|
||||
}
|
||||
|
||||
function parse ([proposals, images, optDepositors]: Result): DeriveProposal[] {
|
||||
return proposals
|
||||
.filter(([, , proposer], index): boolean =>
|
||||
!!(optDepositors[index]?.isSome) && !proposer.isEmpty
|
||||
)
|
||||
.map(([index, imageHash, proposer], proposalIndex): DeriveProposal => {
|
||||
const depositors = optDepositors[proposalIndex].unwrap();
|
||||
|
||||
return {
|
||||
...(
|
||||
isNewDepositors(depositors)
|
||||
? { balance: depositors[1], seconds: depositors[0] }
|
||||
: { balance: depositors[0], seconds: depositors[1] }
|
||||
),
|
||||
image: images[proposalIndex],
|
||||
imageHash,
|
||||
index,
|
||||
proposer
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function proposals (instanceId: string, api: DeriveApi): () => Observable<DeriveProposal[]> {
|
||||
return memo(instanceId, (): Observable<DeriveProposal[]> =>
|
||||
isFunction(api.query.democracy?.publicProps) && isFunction(api.query.democracy?.preimages)
|
||||
? api.query.democracy.publicProps().pipe(
|
||||
switchMap((proposals) =>
|
||||
proposals.length
|
||||
? combineLatest([
|
||||
of(proposals),
|
||||
api.derive.democracy.preimages(
|
||||
proposals.map(([, hash]) => hash)),
|
||||
api.query.democracy.depositOf.multi(
|
||||
proposals.map(([index]) => index))
|
||||
])
|
||||
: of<Result>([[], [], []])
|
||||
),
|
||||
map(parse)
|
||||
)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
@@ -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 { ReferendumIndex } from '@polkadot/types/interfaces';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
export function referendumIds (instanceId: string, api: DeriveApi): () => Observable<BN[]> {
|
||||
return memo(instanceId, (): Observable<BN[]> =>
|
||||
api.query.democracy?.lowestUnbaked
|
||||
? api.queryMulti<[ReferendumIndex, ReferendumIndex]>([
|
||||
api.query.democracy.lowestUnbaked,
|
||||
api.query.democracy.referendumCount
|
||||
]).pipe(
|
||||
map(([first, total]): BN[] =>
|
||||
total.gt(first)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
? [...Array(total.sub(first).toNumber())].map((_, i): BN => first.addn(i))
|
||||
: []
|
||||
)
|
||||
)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { DeriveApi, DeriveReferendumExt } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
export function referendums (instanceId: string, api: DeriveApi): () => Observable<DeriveReferendumExt[]> {
|
||||
return memo(instanceId, (): Observable<DeriveReferendumExt[]> =>
|
||||
api.derive.democracy.referendumsActive().pipe(
|
||||
switchMap((referendums) =>
|
||||
referendums.length
|
||||
? combineLatest([
|
||||
of(referendums),
|
||||
api.derive.democracy._referendumsVotes(referendums)
|
||||
])
|
||||
: of([[], []])
|
||||
),
|
||||
map(([referendums, votes]) =>
|
||||
referendums.map((referendum, index): DeriveReferendumExt => ({
|
||||
...referendum,
|
||||
...votes[index]
|
||||
}))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { DeriveApi, DeriveReferendum } from '../types';
|
||||
|
||||
import { of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
export function referendumsActive (instanceId: string, api: DeriveApi): () => Observable<DeriveReferendum[]> {
|
||||
return memo(instanceId, (): Observable<DeriveReferendum[]> =>
|
||||
api.derive.democracy.referendumIds().pipe(
|
||||
switchMap((ids): Observable<DeriveReferendum[]> =>
|
||||
ids.length
|
||||
? api.derive.democracy.referendumsInfo(ids)
|
||||
: of([])
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -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 { PalletDemocracyReferendumInfo } from '@polkadot/types/lookup';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { map, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
type ReferendumInfoFinished = PalletDemocracyReferendumInfo['asFinished'];
|
||||
|
||||
export function referendumsFinished (instanceId: string, api: DeriveApi): () => Observable<ReferendumInfoFinished[]> {
|
||||
return memo(instanceId, (): Observable<ReferendumInfoFinished[]> =>
|
||||
api.derive.democracy.referendumIds().pipe(
|
||||
switchMap((ids) =>
|
||||
api.query.democracy.referendumInfoOf.multi(ids)
|
||||
),
|
||||
map((infos): ReferendumInfoFinished[] =>
|
||||
infos
|
||||
.map((o) => o.unwrapOr(null))
|
||||
.filter((info): info is PalletDemocracyReferendumInfo => !!info && info.isFinished)
|
||||
.map((info) => info.asFinished)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// 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, ReferendumInfoTo239, Vote } from '@polkadot/types/interfaces';
|
||||
import type { PalletDemocracyReferendumInfo, PalletDemocracyVoteVoting } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { DeriveApi, DeriveBalancesAccount, DeriveReferendum, DeriveReferendumVote, DeriveReferendumVotes } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { isFunction } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { calcVotes, getStatus, parseImage } from './util';
|
||||
|
||||
type VotingDelegating = PalletDemocracyVoteVoting['asDelegating'];
|
||||
type VotingDirect = PalletDemocracyVoteVoting['asDirect'];
|
||||
type VotingDirectVote = VotingDirect['votes'][0];
|
||||
|
||||
function votesPrev (api: DeriveApi, referendumId: BN): Observable<DeriveReferendumVote[]> {
|
||||
return api.query.democracy.votersFor<Vec<AccountId>>(referendumId).pipe(
|
||||
switchMap((votersFor): Observable<[Vec<AccountId>, Vote[], DeriveBalancesAccount[]]> =>
|
||||
combineLatest([
|
||||
of(votersFor),
|
||||
votersFor.length
|
||||
? api.query.democracy.voteOf.multi<Vote>(
|
||||
votersFor.map((accountId): [BN | number, AccountId] =>
|
||||
[referendumId, accountId]
|
||||
)
|
||||
)
|
||||
: of([]),
|
||||
api.derive.balances.votingBalances(votersFor)
|
||||
])
|
||||
),
|
||||
map(([votersFor, votes, balances]): DeriveReferendumVote[] =>
|
||||
votersFor.map((accountId, index): DeriveReferendumVote => ({
|
||||
accountId,
|
||||
balance: balances[index].votingBalance || api.registry.createType('Balance'),
|
||||
isDelegating: false,
|
||||
vote: votes[index] || api.registry.createType('Vote')
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function extractVotes (mapped: [AccountId, PalletDemocracyVoteVoting][], referendumId: BN): DeriveReferendumVote[] {
|
||||
return mapped
|
||||
.filter(([, voting]) => voting.isDirect)
|
||||
.map(([accountId, voting]): [AccountId, VotingDirectVote[]] => [
|
||||
accountId,
|
||||
voting.asDirect.votes.filter(([idx]) => idx.eq(referendumId))
|
||||
])
|
||||
.filter(([, directVotes]) => !!directVotes.length)
|
||||
.reduce((result: DeriveReferendumVote[], [accountId, votes]) =>
|
||||
// FIXME We are ignoring split votes
|
||||
votes.reduce((result: DeriveReferendumVote[], [, vote]): DeriveReferendumVote[] => {
|
||||
if (vote.isStandard) {
|
||||
result.push({
|
||||
accountId,
|
||||
isDelegating: false,
|
||||
...vote.asStandard
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}, result), []
|
||||
);
|
||||
}
|
||||
|
||||
function votesCurr (api: DeriveApi, referendumId: BN): Observable<DeriveReferendumVote[]> {
|
||||
return api.query.democracy.votingOf.entries().pipe(
|
||||
map((allVoting): DeriveReferendumVote[] => {
|
||||
const mapped = allVoting.map(([{ args: [accountId] }, voting]): [AccountId, PalletDemocracyVoteVoting] => [accountId, voting]);
|
||||
const votes = extractVotes(mapped, referendumId);
|
||||
const delegations = mapped
|
||||
.filter(([, voting]) => voting.isDelegating)
|
||||
.map(([accountId, voting]): [AccountId, VotingDelegating] => [accountId, voting.asDelegating]);
|
||||
|
||||
// add delegations
|
||||
delegations.forEach(([accountId, { balance, conviction, target }]): void => {
|
||||
// Are we delegating to a delegator
|
||||
const toDelegator = delegations.find(([accountId]) => accountId.eq(target));
|
||||
const to = votes.find(({ accountId }) => accountId.eq(toDelegator ? toDelegator[0] : target));
|
||||
|
||||
// this delegation has a target
|
||||
if (to) {
|
||||
votes.push({
|
||||
accountId,
|
||||
balance,
|
||||
isDelegating: true,
|
||||
vote: api.registry.createType('Vote', { aye: to.vote.isAye, conviction })
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return votes;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function _referendumVotes (instanceId: string, api: DeriveApi): (referendum: DeriveReferendum) => Observable<DeriveReferendumVotes> {
|
||||
return memo(instanceId, (referendum: DeriveReferendum): Observable<DeriveReferendumVotes> =>
|
||||
combineLatest([
|
||||
api.derive.democracy.sqrtElectorate(),
|
||||
isFunction(api.query.democracy.votingOf)
|
||||
? votesCurr(api, referendum.index)
|
||||
: votesPrev(api, referendum.index)
|
||||
]).pipe(
|
||||
map(([sqrtElectorate, votes]) =>
|
||||
calcVotes(sqrtElectorate, referendum, votes)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function _referendumsVotes (instanceId: string, api: DeriveApi): (referendums: DeriveReferendum[]) => Observable<DeriveReferendumVotes[]> {
|
||||
return memo(instanceId, (referendums: DeriveReferendum[]): Observable<DeriveReferendumVotes[]> =>
|
||||
referendums.length
|
||||
? combineLatest(
|
||||
referendums.map((referendum): Observable<DeriveReferendumVotes> =>
|
||||
api.derive.democracy._referendumVotes(referendum)
|
||||
)
|
||||
)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
|
||||
export function _referendumInfo (instanceId: string, api: DeriveApi): (index: BN, info: Option<PalletDemocracyReferendumInfo | ReferendumInfoTo239>) => Observable<DeriveReferendum | null> {
|
||||
return memo(instanceId, (index: BN, info: Option<PalletDemocracyReferendumInfo | ReferendumInfoTo239>): Observable<DeriveReferendum | null> => {
|
||||
const status = getStatus(info);
|
||||
|
||||
return status
|
||||
? api.query.democracy.preimages(status.proposalHash).pipe(
|
||||
map((preimage): DeriveReferendum => ({
|
||||
image: parseImage(api, preimage),
|
||||
imageHash: status.proposalHash,
|
||||
index: api.registry.createType('ReferendumIndex', index),
|
||||
status
|
||||
}))
|
||||
)
|
||||
: of(null);
|
||||
});
|
||||
}
|
||||
|
||||
export function referendumsInfo (instanceId: string, api: DeriveApi): (ids: BN[]) => Observable<DeriveReferendum[]> {
|
||||
return memo(instanceId, (ids: BN[]): Observable<DeriveReferendum[]> =>
|
||||
ids.length
|
||||
? api.query.democracy.referendumInfoOf.multi(ids).pipe(
|
||||
switchMap((infos): Observable<(DeriveReferendum | null)[]> =>
|
||||
combineLatest(
|
||||
ids.map((id, index): Observable<DeriveReferendum | null> =>
|
||||
api.derive.democracy._referendumInfo(id, infos[index])
|
||||
)
|
||||
)
|
||||
),
|
||||
map((infos) =>
|
||||
infos.filter((referendum): referendum is DeriveReferendum => !!referendum)
|
||||
)
|
||||
)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { DeriveApi } from '../types';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { bnSqrt } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
export function sqrtElectorate (instanceId: string, api: DeriveApi): () => Observable<BN> {
|
||||
return memo(instanceId, (): Observable<BN> =>
|
||||
api.query.balances.totalIssuance().pipe(
|
||||
map(bnSqrt)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Vec } from '@polkadot/types';
|
||||
import type { AccountId, Balance, BlockNumber, Hash, PropIndex, Proposal, ReferendumIndex, ReferendumInfoTo239, Vote } from '@polkadot/types/interfaces';
|
||||
import type { PalletDemocracyReferendumStatus, PalletDemocracyVoteThreshold } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
export interface AtBlock {
|
||||
at: BlockNumber;
|
||||
}
|
||||
|
||||
export interface DeriveDemocracyLock {
|
||||
balance: Balance;
|
||||
isDelegated: boolean;
|
||||
isFinished: boolean;
|
||||
referendumEnd: BN;
|
||||
referendumId: ReferendumIndex;
|
||||
unlockAt: BN;
|
||||
vote: Vote;
|
||||
}
|
||||
|
||||
export interface DeriveProposalImage extends AtBlock {
|
||||
balance: Balance;
|
||||
proposal?: Proposal;
|
||||
proposer: AccountId;
|
||||
}
|
||||
|
||||
export interface DeriveDispatch extends AtBlock {
|
||||
index: ReferendumIndex;
|
||||
imageHash: Hash;
|
||||
image?: DeriveProposalImage;
|
||||
}
|
||||
|
||||
export interface DeriveProposal {
|
||||
balance?: Balance;
|
||||
index: PropIndex;
|
||||
image?: DeriveProposalImage;
|
||||
imageHash: Hash;
|
||||
proposer: AccountId;
|
||||
seconds: Vec<AccountId>;
|
||||
}
|
||||
|
||||
export interface DeriveProposalExternal {
|
||||
image?: DeriveProposalImage;
|
||||
imageHash: Hash;
|
||||
threshold: PalletDemocracyVoteThreshold;
|
||||
}
|
||||
|
||||
export interface DeriveReferendum {
|
||||
index: ReferendumIndex;
|
||||
image?: DeriveProposalImage;
|
||||
imageHash: Hash;
|
||||
status: PalletDemocracyReferendumStatus | ReferendumInfoTo239;
|
||||
}
|
||||
|
||||
export interface DeriveReferendumVote {
|
||||
accountId: AccountId;
|
||||
balance: Balance;
|
||||
isDelegating: boolean;
|
||||
vote: Vote;
|
||||
}
|
||||
|
||||
export interface DeriveReferendumVoteState {
|
||||
allAye: DeriveReferendumVote[];
|
||||
allNay: DeriveReferendumVote[];
|
||||
voteCount: number;
|
||||
voteCountAye: number;
|
||||
voteCountNay: number;
|
||||
votedAye: BN;
|
||||
votedNay: BN;
|
||||
votedTotal: BN;
|
||||
}
|
||||
|
||||
export interface DeriveReferendumVotes extends DeriveReferendumVoteState {
|
||||
isPassing: boolean;
|
||||
votes: DeriveReferendumVote[];
|
||||
}
|
||||
|
||||
export interface DeriveReferendumExt extends DeriveReferendum, DeriveReferendumVotes {
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Bytes, Option } from '@polkadot/types';
|
||||
import type { AccountId, Balance, BlockNumber, PreimageStatus, Proposal, ReferendumInfoTo239, Tally } from '@polkadot/types/interfaces';
|
||||
import type { PalletDemocracyReferendumInfo, PalletDemocracyReferendumStatus, PalletDemocracyVoteThreshold } from '@polkadot/types/lookup';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
import type { DeriveApi, DeriveProposalImage, DeriveReferendum, DeriveReferendumVote, DeriveReferendumVotes, DeriveReferendumVoteState } from '../types';
|
||||
|
||||
import { BN, bnSqrt } from '@polkadot/util';
|
||||
|
||||
type PreimageInfo = [Bytes, AccountId, Balance, BlockNumber];
|
||||
type OldPreimage = ITuple<PreimageInfo>;
|
||||
|
||||
interface ApproxState {
|
||||
votedAye: BN;
|
||||
votedNay: BN;
|
||||
votedTotal: BN;
|
||||
}
|
||||
|
||||
function isOldInfo (info: PalletDemocracyReferendumInfo | ReferendumInfoTo239): info is ReferendumInfoTo239 {
|
||||
return !!(info as ReferendumInfoTo239).proposalHash;
|
||||
}
|
||||
|
||||
function isCurrentStatus (status: PalletDemocracyReferendumStatus | ReferendumInfoTo239): status is PalletDemocracyReferendumStatus {
|
||||
return !!(status as PalletDemocracyReferendumStatus).tally;
|
||||
}
|
||||
|
||||
function isCurrentPreimage (api: DeriveApi, imageOpt: Option<OldPreimage> | Option<PreimageStatus>): imageOpt is Option<PreimageStatus> {
|
||||
return !!imageOpt && !api.query.democracy.dispatchQueue;
|
||||
}
|
||||
|
||||
export function compareRationals (n1: BN, d1: BN, n2: BN, d2: BN): boolean {
|
||||
while (true) {
|
||||
const q1 = n1.div(d1);
|
||||
const q2 = n2.div(d2);
|
||||
|
||||
if (q1.lt(q2)) {
|
||||
return true;
|
||||
} else if (q2.lt(q1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const r1 = n1.mod(d1);
|
||||
const r2 = n2.mod(d2);
|
||||
|
||||
if (r2.isZero()) {
|
||||
return false;
|
||||
} else if (r1.isZero()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
n1 = d2;
|
||||
n2 = d1;
|
||||
d1 = r2;
|
||||
d2 = r1;
|
||||
}
|
||||
}
|
||||
|
||||
function calcPassingOther (threshold: PalletDemocracyVoteThreshold, sqrtElectorate: BN, { votedAye, votedNay, votedTotal }: ApproxState): boolean {
|
||||
const sqrtVoters = bnSqrt(votedTotal);
|
||||
|
||||
return sqrtVoters.isZero()
|
||||
? false
|
||||
: threshold.isSuperMajorityApprove
|
||||
? compareRationals(votedNay, sqrtVoters, votedAye, sqrtElectorate)
|
||||
: compareRationals(votedNay, sqrtElectorate, votedAye, sqrtVoters);
|
||||
}
|
||||
|
||||
export function calcPassing (threshold: PalletDemocracyVoteThreshold, sqrtElectorate: BN, state: ApproxState): boolean {
|
||||
return threshold.isSimpleMajority
|
||||
? state.votedAye.gt(state.votedNay)
|
||||
: calcPassingOther(threshold, sqrtElectorate, state);
|
||||
}
|
||||
|
||||
function calcVotesPrev (votesFor: DeriveReferendumVote[]): DeriveReferendumVoteState {
|
||||
return votesFor.reduce((state: DeriveReferendumVoteState, derived): DeriveReferendumVoteState => {
|
||||
const { balance, vote } = derived;
|
||||
const isDefault = vote.conviction.index === 0;
|
||||
const counted = balance
|
||||
.muln(isDefault ? 1 : vote.conviction.index)
|
||||
.divn(isDefault ? 10 : 1);
|
||||
|
||||
if (vote.isAye) {
|
||||
state.allAye.push(derived);
|
||||
state.voteCountAye++;
|
||||
state.votedAye.iadd(counted);
|
||||
} else {
|
||||
state.allNay.push(derived);
|
||||
state.voteCountNay++;
|
||||
state.votedNay.iadd(counted);
|
||||
}
|
||||
|
||||
state.voteCount++;
|
||||
state.votedTotal.iadd(counted);
|
||||
|
||||
return state;
|
||||
}, { allAye: [], allNay: [], voteCount: 0, voteCountAye: 0, voteCountNay: 0, votedAye: new BN(0), votedNay: new BN(0), votedTotal: new BN(0) });
|
||||
}
|
||||
|
||||
function calcVotesCurrent (tally: Tally, votes: DeriveReferendumVote[]): DeriveReferendumVoteState {
|
||||
const allAye: DeriveReferendumVote[] = [];
|
||||
const allNay: DeriveReferendumVote[] = [];
|
||||
|
||||
votes.forEach((derived): void => {
|
||||
if (derived.vote.isAye) {
|
||||
allAye.push(derived);
|
||||
} else {
|
||||
allNay.push(derived);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
allAye,
|
||||
allNay,
|
||||
voteCount: allAye.length + allNay.length,
|
||||
voteCountAye: allAye.length,
|
||||
voteCountNay: allNay.length,
|
||||
votedAye: tally.ayes,
|
||||
votedNay: tally.nays,
|
||||
votedTotal: tally.turnout
|
||||
};
|
||||
}
|
||||
|
||||
export function calcVotes (sqrtElectorate: BN, referendum: DeriveReferendum, votes: DeriveReferendumVote[]): DeriveReferendumVotes {
|
||||
const state = isCurrentStatus(referendum.status)
|
||||
? calcVotesCurrent(referendum.status.tally, votes)
|
||||
: calcVotesPrev(votes);
|
||||
|
||||
return {
|
||||
...state,
|
||||
isPassing: calcPassing(referendum.status.threshold, sqrtElectorate, state),
|
||||
votes
|
||||
};
|
||||
}
|
||||
|
||||
export function getStatus (info: Option<PalletDemocracyReferendumInfo | ReferendumInfoTo239>): PalletDemocracyReferendumStatus | ReferendumInfoTo239 | null {
|
||||
if (info.isNone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const unwrapped = info.unwrap();
|
||||
|
||||
if (isOldInfo(unwrapped)) {
|
||||
return unwrapped;
|
||||
} else if (unwrapped.isOngoing) {
|
||||
return unwrapped.asOngoing;
|
||||
}
|
||||
|
||||
// done, we don't include it here... only currently active
|
||||
return null;
|
||||
}
|
||||
|
||||
function constructProposal (api: DeriveApi, [bytes, proposer, balance, at]: PreimageInfo): DeriveProposalImage {
|
||||
let proposal: Proposal | undefined;
|
||||
|
||||
try {
|
||||
proposal = api.registry.createType('Proposal', bytes.toU8a(true));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return { at, balance, proposal, proposer };
|
||||
}
|
||||
|
||||
export function parseImage (api: DeriveApi, imageOpt: Option<OldPreimage> | Option<PreimageStatus>): DeriveProposalImage | undefined {
|
||||
if (imageOpt.isNone) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCurrentPreimage(api, imageOpt)) {
|
||||
const status = imageOpt.unwrap();
|
||||
|
||||
if (status.isMissing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { data, deposit, provider, since } = status.asAvailable;
|
||||
|
||||
return constructProposal(api, [data, provider, deposit, since]);
|
||||
}
|
||||
|
||||
return constructProposal(api, imageOpt.unwrap());
|
||||
}
|
||||
@@ -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 { AnyFunction } from '@polkadot/types/types';
|
||||
|
||||
import * as accounts from './accounts';
|
||||
import * as balances from './balances';
|
||||
import * as bounties from './bounties';
|
||||
import * as chain from './chain';
|
||||
import * as contracts from './contracts';
|
||||
import * as council from './council';
|
||||
import * as crowdloan from './crowdloan';
|
||||
import * as democracy from './democracy';
|
||||
import * as elections from './elections';
|
||||
import * as imOnline from './imOnline';
|
||||
import * as membership from './membership';
|
||||
import * as parachains from './parachains';
|
||||
import * as session from './session';
|
||||
import * as society from './society';
|
||||
import * as staking from './staking';
|
||||
import * as technicalCommittee from './technicalCommittee';
|
||||
import * as treasury from './treasury';
|
||||
import * as tx from './tx';
|
||||
|
||||
export const derive = { accounts, balances, bounties, chain, contracts, council, crowdloan, democracy, elections, imOnline, membership, parachains, session, society, staking, technicalCommittee, treasury, tx };
|
||||
|
||||
type DeriveSection<Section> = {
|
||||
[M in keyof Section]: Section[M] extends AnyFunction
|
||||
? ReturnType<Section[M]> // ReturnType<Section[Method]> will be the inner function, i.e. without (api) argument
|
||||
: never;
|
||||
};
|
||||
type DeriveAllSections<AllSections> = {
|
||||
[S in keyof AllSections]: DeriveSection<AllSections[S]>
|
||||
};
|
||||
|
||||
type DeriveCreator = (instanceId: string, api: unknown) => (...args: unknown[]) => Observable<any>;
|
||||
|
||||
export type DeriveCustom = Record<string, Record<string, DeriveCreator>>;
|
||||
|
||||
export type ExactDerive = DeriveAllSections<typeof derive>;
|
||||
@@ -1,9 +1,8 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { packageInfo as apiInfo } from '@polkadot/api/packageInfo';
|
||||
import { detectPackage } from '@polkadot/util';
|
||||
|
||||
import { packageInfo } from './packageInfo';
|
||||
|
||||
detectPackage(packageInfo, typeof __dirname !== 'undefined' && __dirname, [apiInfo]);
|
||||
detectPackage(packageInfo, typeof __dirname !== 'undefined' && __dirname, []);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './info';
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { u32, Vec } from '@polkadot/types';
|
||||
import type { AccountId, Balance, BlockNumber } from '@polkadot/types/interfaces';
|
||||
import type { PalletElectionsPhragmenSeatHolder } from '@polkadot/types/lookup';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
import type { DeriveApi } from '../types';
|
||||
import type { DeriveElectionsInfo } from './types';
|
||||
|
||||
import { combineLatest, map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
// SeatHolder is current tuple is 2.x-era Substrate
|
||||
type Member = PalletElectionsPhragmenSeatHolder | ITuple<[AccountId, Balance]>;
|
||||
|
||||
type Candidate = AccountId | ITuple<[AccountId, Balance]>;
|
||||
|
||||
function isSeatHolder (value: Member): value is PalletElectionsPhragmenSeatHolder {
|
||||
return !Array.isArray(value);
|
||||
}
|
||||
|
||||
function isCandidateTuple (value: Candidate): value is ITuple<[AccountId, Balance]> {
|
||||
return Array.isArray(value);
|
||||
}
|
||||
|
||||
function getAccountTuple (value: Member): [AccountId, Balance] {
|
||||
return isSeatHolder(value)
|
||||
? [value.who, value.stake]
|
||||
: value;
|
||||
}
|
||||
|
||||
function getCandidate (value: Candidate): AccountId {
|
||||
return isCandidateTuple(value)
|
||||
? value[0]
|
||||
: value;
|
||||
}
|
||||
|
||||
function sortAccounts ([, balanceA]: [AccountId, Balance], [, balanceB]: [AccountId, Balance]): number {
|
||||
return balanceB.cmp(balanceA);
|
||||
}
|
||||
|
||||
function getConstants (api: DeriveApi, elections: string | null): Partial<DeriveElectionsInfo> {
|
||||
return elections
|
||||
? {
|
||||
candidacyBond: api.consts[elections].candidacyBond as Balance,
|
||||
desiredRunnersUp: api.consts[elections].desiredRunnersUp as u32,
|
||||
desiredSeats: api.consts[elections].desiredMembers as u32,
|
||||
termDuration: api.consts[elections].termDuration as BlockNumber,
|
||||
votingBond: api.consts[elections].votingBond as Balance
|
||||
}
|
||||
: {};
|
||||
}
|
||||
|
||||
function getModules (api: DeriveApi): [string, string | null] {
|
||||
const [council] = api.registry.getModuleInstances(api.runtimeVersion.specName.toString(), 'council') || ['council'];
|
||||
const elections = api.query.phragmenElection
|
||||
? 'phragmenElection'
|
||||
: api.query.electionsPhragmen
|
||||
? 'electionsPhragmen'
|
||||
: api.query.elections
|
||||
? 'elections'
|
||||
: null;
|
||||
|
||||
return [council, elections];
|
||||
}
|
||||
|
||||
/**
|
||||
* @name info
|
||||
* @returns An object containing the combined results of the storage queries for
|
||||
* all relevant election module properties.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* api.derive.elections.info(({ members, candidates }) => {
|
||||
* console.log(`There are currently ${members.length} council members and ${candidates.length} prospective council candidates.`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function info (instanceId: string, api: DeriveApi): () => Observable<DeriveElectionsInfo> {
|
||||
return memo(instanceId, (): Observable<DeriveElectionsInfo> => {
|
||||
const [council, elections] = getModules(api);
|
||||
|
||||
return (
|
||||
elections
|
||||
? api.queryMulti<[Vec<AccountId>, Vec<Candidate>, Vec<Member>, Vec<Member>]>([
|
||||
api.query[council].members,
|
||||
api.query[elections].candidates,
|
||||
api.query[elections].members,
|
||||
api.query[elections].runnersUp
|
||||
])
|
||||
: combineLatest([
|
||||
api.query[council].members<Vec<AccountId>>(),
|
||||
of<Candidate[]>([]),
|
||||
of<Member[]>([]),
|
||||
of<Member[]>([])
|
||||
])
|
||||
).pipe(
|
||||
map(([councilMembers, candidates, members, runnersUp]): DeriveElectionsInfo => ({
|
||||
...getConstants(api, elections),
|
||||
candidateCount: api.registry.createType('u32', candidates.length),
|
||||
candidates: candidates.map(getCandidate),
|
||||
members: members.length
|
||||
? members.map(getAccountTuple).sort(sortAccounts)
|
||||
: councilMembers.map((a): [AccountId, Balance] => [a, api.registry.createType('Balance')]),
|
||||
runnersUp: runnersUp.map(getAccountTuple).sort(sortAccounts)
|
||||
}))
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { u32 } from '@polkadot/types';
|
||||
import type { AccountId, Balance, BlockNumber, SetIndex, VoteIndex } from '@polkadot/types/interfaces';
|
||||
|
||||
export interface DeriveElectionsInfo {
|
||||
candidates: AccountId[];
|
||||
candidateCount: u32;
|
||||
candidacyBond?: Balance;
|
||||
desiredRunnersUp?: u32;
|
||||
desiredSeats?: u32;
|
||||
members: [AccountId, Balance][];
|
||||
nextVoterSet?: SetIndex;
|
||||
runnersUp: [AccountId, Balance][];
|
||||
termDuration?: BlockNumber;
|
||||
voteCount?: VoteIndex;
|
||||
voterCount?: SetIndex;
|
||||
votingBond?: Balance;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './receivedHeartbeats';
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, u32, WrapperOpaque } from '@polkadot/types';
|
||||
import type { AccountId } from '@polkadot/types/interfaces';
|
||||
import type { PalletImOnlineBoundedOpaqueNetworkState } from '@polkadot/types/lookup';
|
||||
import type { DeriveApi, DeriveHeartbeats } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { BN_ZERO } from '@polkadot/util';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
type HeartbeatsOpt = Option<WrapperOpaque<PalletImOnlineBoundedOpaqueNetworkState>>;
|
||||
|
||||
function mapResult ([result, validators, heartbeats, numBlocks]: [DeriveHeartbeats, AccountId[], HeartbeatsOpt[], u32[]]): DeriveHeartbeats {
|
||||
validators.forEach((validator, index): void => {
|
||||
const validatorId = validator.toString();
|
||||
const blockCount = numBlocks[index];
|
||||
const hasMessage = !heartbeats[index].isEmpty;
|
||||
const prev = result[validatorId];
|
||||
|
||||
if (!prev || prev.hasMessage !== hasMessage || !prev.blockCount.eq(blockCount)) {
|
||||
result[validatorId] = {
|
||||
blockCount,
|
||||
hasMessage,
|
||||
isOnline: hasMessage || blockCount.gt(BN_ZERO)
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Return a boolean array indicating whether the passed accounts had received heartbeats in the current session
|
||||
*/
|
||||
export function receivedHeartbeats (instanceId: string, api: DeriveApi): () => Observable<DeriveHeartbeats> {
|
||||
return memo(instanceId, (): Observable<DeriveHeartbeats> =>
|
||||
api.query.imOnline?.receivedHeartbeats
|
||||
? api.derive.staking.overview().pipe(
|
||||
switchMap(({ currentIndex, validators }): Observable<[DeriveHeartbeats, AccountId[], HeartbeatsOpt[], u32[]]> =>
|
||||
combineLatest([
|
||||
of({}),
|
||||
of(validators),
|
||||
api.query.imOnline.receivedHeartbeats.multi(
|
||||
validators.map((_address, index) => [currentIndex, index])),
|
||||
api.query.imOnline.authoredBlocks.multi(
|
||||
validators.map((address) => [currentIndex, address]))
|
||||
])
|
||||
),
|
||||
map(mapResult)
|
||||
)
|
||||
: of({})
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
|
||||
import { from, Observable } from 'rxjs';
|
||||
|
||||
import { ApiRx } from '@polkadot/api';
|
||||
import { MockProvider } from '@polkadot/rpc-provider/mock';
|
||||
import { TypeRegistry } from '@polkadot/types/create';
|
||||
|
||||
import { ExactDerive } from '.';
|
||||
|
||||
const testFunction = (api: ApiRx): any => {
|
||||
return <S extends keyof ExactDerive, M extends keyof (typeof api.derive[S])>(section: S, method: M, inputs: any[]): void => {
|
||||
describe(`derive.${section}.${method}`, (): void => {
|
||||
it('should be a function', (): void => {
|
||||
expect(typeof api.derive[section][method]).toBe('function');
|
||||
});
|
||||
|
||||
it('should return an Observable', (): void => {
|
||||
expect((api.derive[section][method] as any)(...inputs)).toBeInstanceOf(Observable);
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
describe('derive', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
describe('builtin', (): void => {
|
||||
const api = new ApiRx({ provider: new MockProvider(registry), registry });
|
||||
|
||||
beforeAll((done): void => {
|
||||
api.isReady.subscribe(() => done());
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await api.disconnect();
|
||||
});
|
||||
|
||||
testFunction(api)('accounts', 'idAndIndex', []);
|
||||
testFunction(api)('accounts', 'idToIndex', []);
|
||||
testFunction(api)('accounts', 'indexes', []);
|
||||
testFunction(api)('accounts', 'indexToId', []);
|
||||
|
||||
testFunction(api)('balances', 'all', []);
|
||||
testFunction(api)('balances', 'fees', []);
|
||||
testFunction(api)('balances', 'votingBalance', []);
|
||||
testFunction(api)('balances', 'votingBalances', []);
|
||||
|
||||
testFunction(api)('chain', 'bestNumber', []);
|
||||
testFunction(api)('chain', 'bestNumberFinalized', []);
|
||||
|
||||
testFunction(api)('democracy', 'proposals', []);
|
||||
testFunction(api)('democracy', 'referendums', []);
|
||||
|
||||
testFunction(api)('elections', 'info', []);
|
||||
|
||||
testFunction(api)('session', 'eraLength', []);
|
||||
testFunction(api)('session', 'eraProgress', []);
|
||||
testFunction(api)('session', 'sessionProgress', []);
|
||||
|
||||
testFunction(api)('staking', 'account', []);
|
||||
testFunction(api)('staking', 'stashes', []);
|
||||
});
|
||||
|
||||
describe('custom', (): void => {
|
||||
const api = new ApiRx({
|
||||
derives: {
|
||||
balances: {
|
||||
fees: (): any => (): Observable<any> => from(['a', 'b'])
|
||||
},
|
||||
custom: {
|
||||
test: (): any => (): Observable<any> => from([1, 2, 3])
|
||||
}
|
||||
},
|
||||
provider: new MockProvider(registry),
|
||||
registry,
|
||||
throwOnConnect: true
|
||||
});
|
||||
|
||||
beforeAll((done): void => {
|
||||
api.isReady.subscribe(() => done());
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await api.disconnect();
|
||||
});
|
||||
|
||||
// override
|
||||
testFunction(api)('balances', 'fees', ['a', 'b']);
|
||||
|
||||
// new
|
||||
testFunction(api)('custom', 'test', [1, 2, 3]);
|
||||
|
||||
// existing
|
||||
testFunction(api)('chain', 'bestNumber', []);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { hasProposals as collectiveHasProposals, members as collectiveMembers, prime as collectivePrime, proposal as collectiveProposal, proposalCount as collectiveProposalCount, proposalHashes as collectiveProposalHashes, proposals as collectiveProposals } from '../collective';
|
||||
|
||||
export const members = collectiveMembers('membership');
|
||||
|
||||
export const hasProposals = collectiveHasProposals('membership');
|
||||
export const proposal = collectiveProposal('membership');
|
||||
export const proposalCount = collectiveProposalCount('membership');
|
||||
export const proposalHashes = collectiveProposalHashes('membership');
|
||||
export const proposals = collectiveProposals('membership');
|
||||
|
||||
export const prime = collectivePrime('membership');
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './info';
|
||||
export * from './overview';
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { CollatorId, ParaId } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveParachainActive, DeriveParachainFull, DeriveParachainInfo } from '../types';
|
||||
import type { Active, DidUpdate, Heads, ParaInfoResult, PendingSwap, RelayDispatchQueue, RetryQueue, SelectedThreads } from './types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { didUpdateToBool } from './util';
|
||||
|
||||
type Result = [
|
||||
Active,
|
||||
RetryQueue,
|
||||
SelectedThreads,
|
||||
DidUpdate,
|
||||
ParaInfoResult,
|
||||
PendingSwap,
|
||||
Heads,
|
||||
RelayDispatchQueue
|
||||
];
|
||||
|
||||
function parseActive (id: ParaId, active: Active): DeriveParachainActive | null {
|
||||
const found = active.find(([paraId]) => paraId === id);
|
||||
|
||||
if (found && found[1].isSome) {
|
||||
const [collatorId, retriable] = found[1].unwrap();
|
||||
|
||||
return {
|
||||
collatorId,
|
||||
...(
|
||||
retriable.isWithRetries
|
||||
? {
|
||||
isRetriable: true,
|
||||
retries: retriable.asWithRetries.toNumber()
|
||||
}
|
||||
: {
|
||||
isRetriable: false,
|
||||
retries: 0
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseCollators (id: ParaId, collatorQueue: SelectedThreads | RetryQueue): (CollatorId | null)[] {
|
||||
return collatorQueue.map((queue): CollatorId | null => {
|
||||
const found = queue.find(([paraId]) => paraId === id);
|
||||
|
||||
return found ? found[1] : null;
|
||||
});
|
||||
}
|
||||
|
||||
function parse (id: ParaId, [active, retryQueue, selectedThreads, didUpdate, info, pendingSwap, heads, relayDispatchQueue]: Result): DeriveParachainFull | null {
|
||||
if (info.isNone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
active: parseActive(id, active),
|
||||
didUpdate: didUpdateToBool(didUpdate, id),
|
||||
heads,
|
||||
id,
|
||||
info: { id, ...info.unwrap() } as DeriveParachainInfo,
|
||||
pendingSwapId: pendingSwap.unwrapOr(null),
|
||||
relayDispatchQueue,
|
||||
retryCollators: parseCollators(id, retryQueue),
|
||||
selectedCollators: parseCollators(id, selectedThreads)
|
||||
};
|
||||
}
|
||||
|
||||
export function info (instanceId: string, api: DeriveApi): (id: ParaId | number) => Observable<DeriveParachainFull | null> {
|
||||
return memo(instanceId, (id: ParaId | number): Observable<DeriveParachainFull | null> =>
|
||||
api.query.registrar && api.query.parachains
|
||||
? api.queryMulti<Result>([
|
||||
api.query.registrar.active,
|
||||
api.query.registrar.retryQueue,
|
||||
api.query.registrar.selectedThreads,
|
||||
api.query.parachains.didUpdate,
|
||||
[api.query.registrar.paras, id],
|
||||
[api.query.registrar.pendingSwap, id],
|
||||
[api.query.parachains.heads, id],
|
||||
[api.query.parachains.relayDispatchQueue, id]
|
||||
])
|
||||
.pipe(
|
||||
map((result: Result): DeriveParachainFull | null =>
|
||||
parse(api.registry.createType('ParaId', id), result)
|
||||
)
|
||||
)
|
||||
: of(null)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { ParaId } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveParachain, DeriveParachainInfo } from '../types';
|
||||
import type { DidUpdate, ParaInfoResult, PendingSwap, RelayDispatchQueueSize } from './types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
import { didUpdateToBool } from './util';
|
||||
|
||||
type Result = [
|
||||
ParaId[],
|
||||
DidUpdate,
|
||||
ParaInfoResult[],
|
||||
PendingSwap[],
|
||||
RelayDispatchQueueSize[]
|
||||
];
|
||||
|
||||
function parse ([ids, didUpdate, infos, pendingSwaps, relayDispatchQueueSizes]: Result): DeriveParachain[] {
|
||||
return ids.map((id, index): DeriveParachain => ({
|
||||
didUpdate: didUpdateToBool(didUpdate, id),
|
||||
id,
|
||||
info: { id, ...infos[index].unwrapOr(null) } as DeriveParachainInfo,
|
||||
pendingSwapId: pendingSwaps[index].unwrapOr(null),
|
||||
relayDispatchQueueSize: relayDispatchQueueSizes[index][0].toNumber()
|
||||
}));
|
||||
}
|
||||
|
||||
export function overview (instanceId: string, api: DeriveApi): () => Observable<DeriveParachain[]> {
|
||||
return memo(instanceId, (): Observable<DeriveParachain[]> =>
|
||||
api.query.registrar?.parachains && api.query.parachains
|
||||
? api.query.registrar.parachains<ParaId[]>().pipe(
|
||||
switchMap((paraIds) =>
|
||||
combineLatest([
|
||||
of(paraIds),
|
||||
api.query.parachains.didUpdate<DidUpdate>(),
|
||||
api.query.registrar.paras.multi<ParaInfoResult>(paraIds),
|
||||
api.query.registrar.pendingSwap.multi<PendingSwap>(paraIds),
|
||||
api.query.parachains.relayDispatchQueueSize.multi<RelayDispatchQueueSize>(paraIds)
|
||||
])
|
||||
),
|
||||
map(parse)
|
||||
)
|
||||
: of([] as DeriveParachain[])
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Bytes, Option, u32, Vec } from '@polkadot/types';
|
||||
import type { CollatorId, ParaId, ParaInfo, Retriable, UpwardMessage } from '@polkadot/types/interfaces';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
|
||||
export type ParaInfoResult = Option<ParaInfo>;
|
||||
export type PendingSwap = Option<ParaId>;
|
||||
export type Active = Vec<ITuple<[ParaId, Option<ITuple<[CollatorId, Retriable]>>]>>;
|
||||
export type RetryQueue = Vec<Vec<ITuple<[ParaId, CollatorId]>>>
|
||||
export type SelectedThreads = Vec<Vec<ITuple<[ParaId, CollatorId]>>>
|
||||
export type Code = Bytes;
|
||||
export type Heads = Bytes;
|
||||
export type RelayDispatchQueue = Vec<UpwardMessage>
|
||||
export type RelayDispatchQueueSize = ITuple<[u32, u32]>;
|
||||
export type DidUpdate = Option<Vec<ParaId>>;
|
||||
|
||||
export interface DeriveParachainActive {
|
||||
collatorId: CollatorId;
|
||||
isRetriable: boolean;
|
||||
retries: number;
|
||||
}
|
||||
|
||||
export interface DeriveParachainInfo extends ParaInfo {
|
||||
id: ParaId;
|
||||
icon?: string;
|
||||
name?: string;
|
||||
owner?: string;
|
||||
}
|
||||
|
||||
export interface DeriveParachain {
|
||||
didUpdate: boolean;
|
||||
pendingSwapId: ParaId | null;
|
||||
id: ParaId;
|
||||
info: DeriveParachainInfo | null;
|
||||
relayDispatchQueueSize?: number;
|
||||
}
|
||||
|
||||
export interface DeriveParachainFull extends DeriveParachain {
|
||||
active: DeriveParachainActive | null;
|
||||
heads: Bytes | null;
|
||||
relayDispatchQueue: UpwardMessage[];
|
||||
retryCollators: (CollatorId | null)[];
|
||||
selectedCollators: (CollatorId | null)[];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ParaId } from '@polkadot/types/interfaces';
|
||||
import type { DidUpdate } from './types';
|
||||
|
||||
export function didUpdateToBool (didUpdate: DidUpdate, id: ParaId): boolean {
|
||||
return didUpdate.isSome
|
||||
? didUpdate.unwrap().some((paraId) => paraId.eq(id))
|
||||
: false;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './indexes';
|
||||
export * from './info';
|
||||
export * from './progress';
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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, Moment, SessionIndex } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveSessionIndexes } from '../types';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
// parse into Indexes
|
||||
function parse ([currentIndex, activeEra, activeEraStart, currentEra, validatorCount]: [SessionIndex, EraIndex, Option<Moment>, EraIndex, u32]): DeriveSessionIndexes {
|
||||
return {
|
||||
activeEra,
|
||||
activeEraStart,
|
||||
currentEra,
|
||||
currentIndex,
|
||||
validatorCount
|
||||
};
|
||||
}
|
||||
|
||||
// query based on latest
|
||||
function queryStaking (api: DeriveApi): Observable<DeriveSessionIndexes> {
|
||||
return api.queryMulti<[SessionIndex, Option<ActiveEraInfo>, Option<EraIndex>, u32]>([
|
||||
api.query.session.currentIndex,
|
||||
api.query.staking.activeEra,
|
||||
api.query.staking.currentEra,
|
||||
api.query.staking.validatorCount
|
||||
]).pipe(
|
||||
map(([currentIndex, activeOpt, currentEra, validatorCount]): DeriveSessionIndexes => {
|
||||
const { index, start } = activeOpt.unwrapOrDefault();
|
||||
|
||||
return parse([
|
||||
currentIndex,
|
||||
index,
|
||||
start,
|
||||
currentEra.unwrapOrDefault(),
|
||||
validatorCount
|
||||
]);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// query based on latest
|
||||
function querySession (api: DeriveApi): Observable<DeriveSessionIndexes> {
|
||||
return api.query.session.currentIndex().pipe(
|
||||
map((currentIndex): DeriveSessionIndexes => parse([
|
||||
currentIndex,
|
||||
api.registry.createType('EraIndex'),
|
||||
api.registry.createType('Option<Moment>'),
|
||||
api.registry.createType('EraIndex'),
|
||||
api.registry.createType('u32')
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
// empty set when none is available
|
||||
function empty (api: DeriveApi): Observable<DeriveSessionIndexes> {
|
||||
return of(parse([
|
||||
api.registry.createType('SessionIndex', 1),
|
||||
api.registry.createType('EraIndex'),
|
||||
api.registry.createType('Option<Moment>'),
|
||||
api.registry.createType('EraIndex'),
|
||||
api.registry.createType('u32')
|
||||
]));
|
||||
}
|
||||
|
||||
export function indexes (instanceId: string, api: DeriveApi): () => Observable<DeriveSessionIndexes> {
|
||||
return memo(instanceId, (): Observable<DeriveSessionIndexes> =>
|
||||
api.query.session
|
||||
? api.query.staking
|
||||
? queryStaking(api)
|
||||
: querySession(api)
|
||||
: empty(api)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { DeriveApi, DeriveSessionInfo } from '../types';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @description Retrieves all the session and era query and calculates specific values on it as the length of the session and eras
|
||||
*/
|
||||
export function info (instanceId: string, api: DeriveApi): () => Observable<DeriveSessionInfo> {
|
||||
return memo(instanceId, (): Observable<DeriveSessionInfo> =>
|
||||
api.derive.session.indexes().pipe(
|
||||
map((indexes) => {
|
||||
const sessionLength = api.consts?.babe?.epochDuration || api.registry.createType('u64', 1);
|
||||
const sessionsPerEra = api.consts?.staking?.sessionsPerEra || api.registry.createType('SessionIndex', 1);
|
||||
|
||||
return {
|
||||
...indexes,
|
||||
eraLength: api.registry.createType('BlockNumber', sessionsPerEra.mul(sessionLength)),
|
||||
isEpoch: !!api.query.babe,
|
||||
sessionLength,
|
||||
sessionsPerEra
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, u64 } from '@polkadot/types';
|
||||
import type { BlockNumber, SessionIndex } from '@polkadot/types/interfaces';
|
||||
import type { DeriveApi, DeriveSessionInfo, DeriveSessionProgress } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
type ResultSlotsNoSession = [u64, u64, u64];
|
||||
type ResultSlots = [u64, u64, u64, Option<SessionIndex>];
|
||||
type ResultSlotsFlat = [u64, u64, u64, SessionIndex];
|
||||
|
||||
function withProgressField (field: 'eraLength' | 'eraProgress' | 'sessionProgress'): (instanceId: string, api: DeriveApi) => () => Observable<BlockNumber> {
|
||||
return (instanceId: string, api: DeriveApi) =>
|
||||
memo(instanceId, (): Observable<BlockNumber> =>
|
||||
api.derive.session.progress().pipe(
|
||||
map((info) => info[field])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function createDerive (api: DeriveApi, info: DeriveSessionInfo, [currentSlot, epochIndex, epochOrGenesisStartSlot, activeEraStartSessionIndex]: ResultSlotsFlat): DeriveSessionProgress {
|
||||
const epochStartSlot = epochIndex.mul(info.sessionLength).iadd(epochOrGenesisStartSlot);
|
||||
const sessionProgress = currentSlot.sub(epochStartSlot);
|
||||
const eraProgress = info.currentIndex.sub(activeEraStartSessionIndex).imul(info.sessionLength).iadd(sessionProgress);
|
||||
|
||||
return {
|
||||
...info,
|
||||
eraProgress: api.registry.createType('BlockNumber', eraProgress),
|
||||
sessionProgress: api.registry.createType('BlockNumber', sessionProgress)
|
||||
};
|
||||
}
|
||||
|
||||
function queryAura (api: DeriveApi): Observable<DeriveSessionProgress> {
|
||||
return api.derive.session.info().pipe(
|
||||
map((info): DeriveSessionProgress => ({
|
||||
...info,
|
||||
eraProgress: api.registry.createType('BlockNumber'),
|
||||
sessionProgress: api.registry.createType('BlockNumber')
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
function queryBabe (api: DeriveApi): Observable<[DeriveSessionInfo, ResultSlotsFlat]> {
|
||||
return api.derive.session.info().pipe(
|
||||
switchMap((info): Observable<[DeriveSessionInfo, ResultSlots | ResultSlotsNoSession]> =>
|
||||
combineLatest([
|
||||
of(info),
|
||||
// we may have no staking, but have babe (permissioned)
|
||||
api.query.staking?.erasStartSessionIndex
|
||||
? api.queryMulti<ResultSlots>([
|
||||
api.query.babe.currentSlot,
|
||||
api.query.babe.epochIndex,
|
||||
api.query.babe.genesisSlot,
|
||||
[api.query.staking.erasStartSessionIndex, info.activeEra]
|
||||
])
|
||||
: api.queryMulti<ResultSlotsNoSession>([
|
||||
api.query.babe.currentSlot,
|
||||
api.query.babe.epochIndex,
|
||||
api.query.babe.genesisSlot
|
||||
])
|
||||
])
|
||||
),
|
||||
map(([info, [currentSlot, epochIndex, genesisSlot, optStartIndex]]): [DeriveSessionInfo, ResultSlotsFlat] => [
|
||||
info, [currentSlot, epochIndex, genesisSlot, optStartIndex && optStartIndex.isSome ? optStartIndex.unwrap() : api.registry.createType('SessionIndex', 1)]
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Retrieves all the session and era query and calculates specific values on it as the length of the session and eras
|
||||
*/
|
||||
export function progress (instanceId: string, api: DeriveApi): () => Observable<DeriveSessionProgress> {
|
||||
return memo(instanceId, (): Observable<DeriveSessionProgress> =>
|
||||
api.query.babe
|
||||
? queryBabe(api).pipe(
|
||||
map(([info, slots]: [DeriveSessionInfo, ResultSlotsFlat]): DeriveSessionProgress =>
|
||||
createDerive(api, info, slots)
|
||||
)
|
||||
)
|
||||
: queryAura(api)
|
||||
);
|
||||
}
|
||||
|
||||
export const eraLength = withProgressField('eraLength');
|
||||
export const eraProgress = withProgressField('eraProgress');
|
||||
export const sessionProgress = withProgressField('sessionProgress');
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Option, u32, u64 } from '@polkadot/types';
|
||||
import type { BlockNumber, EraIndex, Moment, SessionIndex } from '@polkadot/types/interfaces';
|
||||
|
||||
export interface DeriveSessionIndexes {
|
||||
activeEra: EraIndex;
|
||||
activeEraStart: Option<Moment>;
|
||||
currentEra: EraIndex;
|
||||
currentIndex: SessionIndex;
|
||||
validatorCount: u32;
|
||||
}
|
||||
|
||||
export interface DeriveSessionInfo extends DeriveSessionIndexes {
|
||||
eraLength: BlockNumber;
|
||||
isEpoch: boolean;
|
||||
sessionLength: u64;
|
||||
sessionsPerEra: SessionIndex;
|
||||
}
|
||||
|
||||
export interface DeriveSessionProgress extends DeriveSessionInfo {
|
||||
eraProgress: BlockNumber;
|
||||
sessionProgress: BlockNumber;
|
||||
}
|
||||
@@ -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 { BalanceOf } from '@polkadot/types/interfaces';
|
||||
import type { PalletSocietyBid, PalletSocietyBidKind } from '@polkadot/types/lookup';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
import type { Option } from '@polkadot/types-codec';
|
||||
import type { DeriveApi, DeriveSocietyCandidate } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
type ResultSuspend = Option<ITuple<[BalanceOf, PalletSocietyBidKind]>>;
|
||||
type Result = [PalletSocietyBid[], ResultSuspend[]]
|
||||
|
||||
/**
|
||||
* @description Get the candidate info for a society
|
||||
*/
|
||||
export function candidates (instanceId: string, api: DeriveApi): () => Observable<DeriveSocietyCandidate[]> {
|
||||
return memo(instanceId, (): Observable<DeriveSocietyCandidate[]> =>
|
||||
api.query.society.candidates().pipe(
|
||||
switchMap((candidates): Observable<Result> =>
|
||||
combineLatest([
|
||||
of(candidates),
|
||||
api.query.society.suspendedCandidates.multi(
|
||||
candidates.map(({ who }) => who)
|
||||
)
|
||||
])
|
||||
),
|
||||
map(([candidates, suspended]: Result): DeriveSocietyCandidate[] =>
|
||||
candidates.map(({ kind, value, who }, index) => ({
|
||||
accountId: who,
|
||||
isSuspended: suspended[index].isSome,
|
||||
kind,
|
||||
value
|
||||
}))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './candidates';
|
||||
export * from './info';
|
||||
export * from './member';
|
||||
export * from './members';
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, u32, Vec } from '@polkadot/types';
|
||||
import type { AccountId, BalanceOf } from '@polkadot/types/interfaces';
|
||||
import type { PalletSocietyBid } from '@polkadot/types/lookup';
|
||||
import type { DeriveApi, DeriveSociety } from '../types';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
type Result = [Vec<PalletSocietyBid>, Option<AccountId>, Option<AccountId>, Option<AccountId>, u32, BalanceOf]
|
||||
|
||||
/**
|
||||
* @description Get the overall info for a society
|
||||
*/
|
||||
export function info (instanceId: string, api: DeriveApi): () => Observable<DeriveSociety> {
|
||||
return memo(instanceId, (): Observable<DeriveSociety> =>
|
||||
api.queryMulti<Result>([
|
||||
api.query.society.bids,
|
||||
api.query.society.defender,
|
||||
api.query.society.founder,
|
||||
api.query.society.head,
|
||||
api.query.society.maxMembers,
|
||||
api.query.society.pot
|
||||
]).pipe(
|
||||
map(([bids, defender, founder, head, maxMembers, pot]: Result): DeriveSociety => ({
|
||||
bids,
|
||||
defender: defender.unwrapOr(undefined),
|
||||
founder: founder.unwrapOr(undefined),
|
||||
hasDefender: (defender.isSome && head.isSome && !head.eq(defender)) || false,
|
||||
head: head.unwrapOr(undefined),
|
||||
maxMembers,
|
||||
pot
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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, DeriveSocietyMember } from '../types';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
/**
|
||||
* @description Get the member info for a society
|
||||
*/
|
||||
export function member (instanceId: string, api: DeriveApi): (accountId: AccountId) => Observable<DeriveSocietyMember> {
|
||||
return memo(instanceId, (accountId: AccountId): Observable<DeriveSocietyMember> =>
|
||||
api.derive.society._members([accountId]).pipe(
|
||||
map(([result]) => result)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -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, DeriveSocietyMember } from '../types';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util';
|
||||
|
||||
export function _members (instanceId: string, api: DeriveApi): (accountIds: AccountId[]) => Observable<DeriveSocietyMember[]> {
|
||||
return memo(instanceId, (accountIds: AccountId[]): Observable<DeriveSocietyMember[]> =>
|
||||
combineLatest([
|
||||
of(accountIds),
|
||||
api.query.society.payouts.multi(accountIds),
|
||||
api.query.society.strikes.multi(accountIds),
|
||||
api.query.society.defenderVotes.multi(accountIds),
|
||||
api.query.society.suspendedMembers.multi(accountIds),
|
||||
api.query.society.vouching.multi(accountIds)
|
||||
]).pipe(
|
||||
map(([accountIds, payouts, strikes, defenderVotes, suspended, vouching]) =>
|
||||
accountIds.map((accountId, index) => ({
|
||||
accountId,
|
||||
isDefenderVoter: defenderVotes[index].isSome,
|
||||
isSuspended: suspended[index].isTrue,
|
||||
payouts: payouts[index],
|
||||
strikes: strikes[index],
|
||||
vote: defenderVotes[index].unwrapOr(undefined),
|
||||
vouching: vouching[index].unwrapOr(undefined)
|
||||
}))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Get the member info for a society
|
||||
*/
|
||||
export function members (instanceId: string, api: DeriveApi): () => Observable<DeriveSocietyMember[]> {
|
||||
return memo(instanceId, (): Observable<DeriveSocietyMember[]> =>
|
||||
api.query.society.members().pipe(
|
||||
switchMap((members) => api.derive.society._members(members))
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { u32 } from '@polkadot/types';
|
||||
import type { AccountId, Balance, BalanceOf, BlockNumber, StrikeCount } from '@polkadot/types/interfaces';
|
||||
import type { PalletSocietyBid, PalletSocietyBidKind, PalletSocietyVote, PalletSocietyVouchingStatus } from '@polkadot/types/lookup';
|
||||
|
||||
export interface DeriveSociety {
|
||||
bids: PalletSocietyBid[];
|
||||
defender?: AccountId;
|
||||
hasDefender: boolean;
|
||||
head?: AccountId;
|
||||
founder?: AccountId;
|
||||
maxMembers: u32;
|
||||
pot: BalanceOf;
|
||||
}
|
||||
|
||||
export interface DeriveSocietyCandidate {
|
||||
accountId: AccountId;
|
||||
kind: PalletSocietyBidKind;
|
||||
value: Balance;
|
||||
isSuspended: boolean;
|
||||
}
|
||||
|
||||
export interface DeriveSocietyMember {
|
||||
accountId: AccountId;
|
||||
isDefenderVoter: boolean;
|
||||
isSuspended: boolean;
|
||||
payouts: [BlockNumber, Balance][];
|
||||
strikes: StrikeCount;
|
||||
vote?: PalletSocietyVote;
|
||||
vouching?: PalletSocietyVouchingStatus;
|
||||
}
|
||||
@@ -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([])
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user