mirror of
https://github.com/pezkuwichain/pezkuwi-api.git
synced 2026-04-22 09:07:56 +00:00
Rebrand: polkadot → pezkuwi, substrate → bizinikiwi, kusama → dicle
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId, AccountIndex, Address } from '@pezkuwi/types/interfaces';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { assertReturn, isU8a } from '@pezkuwi/util';
|
||||
import { decodeAddress } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name accountId
|
||||
* @param {(Address | AccountId | AccountIndex | string | null)} address An accounts address in various formats.
|
||||
* @description Resolves an address (in different formats) to its corresponding `AccountId`.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const ALICE = "F7Hs";
|
||||
*
|
||||
* api.derive.accounts.accountId(ALICE, (accountId) => {
|
||||
* console.log(`Resolved AccountId: ${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(decoded.length === 20 ? 'AccountId20' : '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,90 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId, Address, Balance } from '@pezkuwi/types/interfaces';
|
||||
import type{ PezpalletElectionsPhragmenSeatHolder } from '@pezkuwi/types/lookup';
|
||||
import type { Codec } from '@pezkuwi/types/types';
|
||||
import type { Option } from '@pezkuwi/types-codec';
|
||||
import type { DeriveAccountFlags, DeriveApi } from '../types.js';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { isFunction } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
type FlagsIntermediate = [
|
||||
PezpalletElectionsPhragmenSeatHolder[] | [AccountId, Balance][] | undefined,
|
||||
AccountId[],
|
||||
AccountId[],
|
||||
AccountId[],
|
||||
Option<AccountId> | AccountId | undefined
|
||||
];
|
||||
|
||||
function parseFlags (address: AccountId | Address | string | null | undefined, [electionsMembers, councilMembers, technicalCommitteeMembers, societyMembers, sudoKey]: FlagsIntermediate): DeriveAccountFlags {
|
||||
const addrStr = address?.toString();
|
||||
const isIncluded = (id: AccountId | Address | string) =>
|
||||
id.toString() === addrStr;
|
||||
|
||||
return {
|
||||
isCouncil: (electionsMembers?.map((r) => Array.isArray(r) ? r[0] : r.who) || councilMembers || []).some(isIncluded),
|
||||
isSociety: (societyMembers || []).some(isIncluded),
|
||||
isSudo: sudoKey?.toString() === addrStr,
|
||||
isTechCommittee: (technicalCommitteeMembers || []).some(isIncluded)
|
||||
};
|
||||
}
|
||||
|
||||
export function _flags (instanceId: string, api: DeriveApi): () => Observable<FlagsIntermediate> {
|
||||
return memo(instanceId, (): Observable<FlagsIntermediate> => {
|
||||
const results: unknown[] = [undefined, [], [], [], undefined];
|
||||
const calls = [
|
||||
(api.query.elections || api.query['phragmenElection'] || api.query['electionsPhragmen'])?.members,
|
||||
api.query.council?.members,
|
||||
api.query.technicalCommittee?.members,
|
||||
api.query.society?.members,
|
||||
api.query.sudo?.key
|
||||
];
|
||||
const filtered = calls.filter((c) => c);
|
||||
|
||||
if (!filtered.length) {
|
||||
return of(results as FlagsIntermediate);
|
||||
}
|
||||
|
||||
return api.queryMulti(filtered).pipe(
|
||||
map((values: Codec[]): FlagsIntermediate => {
|
||||
let resultIndex = -1;
|
||||
|
||||
for (let i = 0, count = calls.length; i < count; i++) {
|
||||
if (isFunction(calls[i])) {
|
||||
results[i] = values[++resultIndex];
|
||||
}
|
||||
}
|
||||
|
||||
return results as FlagsIntermediate;
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name flags
|
||||
* @param {(AccountId | Address | string | null)} address The account identifier.
|
||||
* @description Retrieves the membership flags for a given account.
|
||||
* @example
|
||||
* const ALICE = "F7Hs";
|
||||
*
|
||||
* api.derive.accounts.flags(ALICE, (flags) => {
|
||||
* console.log(
|
||||
* `Account Flags:`,
|
||||
* Object.keys(flags).map((flag) => `${flag}: ${flags[flag]}`)
|
||||
* );
|
||||
* });
|
||||
*/
|
||||
export function flags (instanceId: string, api: DeriveApi): (address?: AccountId | Address | string | null) => Observable<DeriveAccountFlags> {
|
||||
return memo(instanceId, (address?: AccountId | Address | string | null): Observable<DeriveAccountFlags> =>
|
||||
api.derive.accounts._flags().pipe(
|
||||
map((r) => parseFlags(address, r))
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId, AccountIndex, Address } from '@pezkuwi/types/interfaces';
|
||||
import type { AccountIdAndIndex, DeriveApi } from '../types.js';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { isU8a } from '@pezkuwi/util';
|
||||
import { decodeAddress } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @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
|
||||
* ```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(decoded.length === 20 ? 'AccountId20' : '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 {
|
||||
return of([undefined, undefined]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId, AccountIndex } from '@pezkuwi/types/interfaces';
|
||||
import type { AccountIndexes, DeriveApi } from '../types.js';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name idToIndex
|
||||
* @description Retrieves the corresponding AccountIndex.
|
||||
* @param {( AccountId | string )} accountId An accounts Id in different formats.
|
||||
* @example
|
||||
* ```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,235 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Bytes, Data, Struct } from '@pezkuwi/types';
|
||||
import type { AccountId, H160 } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletIdentityLegacyIdentityInfo, PezpalletIdentityRegistration } from '@pezkuwi/types/lookup';
|
||||
import type { Option } from '@pezkuwi/types-codec';
|
||||
import type { ITuple } from '@pezkuwi/types-codec/types';
|
||||
import type { DeriveAccountRegistration, DeriveApi, DeriveHasIdentity } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { isHex, u8aToString } from '@pezkuwi/util';
|
||||
|
||||
import { firstMemo, memo } from '../util/index.js';
|
||||
|
||||
type IdentityInfoAdditional = PezpalletIdentityLegacyIdentityInfo['additional'][0];
|
||||
|
||||
interface PeopleIdentityInfo extends Struct {
|
||||
display: Data;
|
||||
legal: Data;
|
||||
web: Data;
|
||||
matrix: Data;
|
||||
email: Data;
|
||||
pgpFingerprint: Option<H160>;
|
||||
image: Data;
|
||||
twitter: Data;
|
||||
github: Data;
|
||||
discord: Data;
|
||||
}
|
||||
|
||||
const UNDEF_HEX = { toHex: () => undefined };
|
||||
|
||||
function dataAsString (data: Data): string | undefined {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
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;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// handle compatibility between generations of structures
|
||||
function identityCompat (identityOfOpt: Option<ITuple<[PezpalletIdentityRegistration, Option<Bytes>]>> | Option<PezpalletIdentityRegistration>): PezpalletIdentityRegistration {
|
||||
const identity = identityOfOpt.unwrap();
|
||||
|
||||
return Array.isArray(identity)
|
||||
? identity[0]
|
||||
: identity;
|
||||
}
|
||||
|
||||
function extractIdentity (identityOfOpt?: Option<ITuple<[PezpalletIdentityRegistration, Option<Bytes>]>> | Option<PezpalletIdentityRegistration>, superOf?: [AccountId, Data]): DeriveAccountRegistration {
|
||||
if (!identityOfOpt?.isSome) {
|
||||
return { judgements: [] };
|
||||
}
|
||||
|
||||
const { info, judgements } = identityCompat(identityOfOpt);
|
||||
const topDisplay = dataAsString(info.display);
|
||||
|
||||
return {
|
||||
discord: dataAsString((info as unknown as PeopleIdentityInfo).discord),
|
||||
display: (superOf && dataAsString(superOf[1])) || topDisplay,
|
||||
displayParent: superOf && topDisplay,
|
||||
email: dataAsString(info.email),
|
||||
github: dataAsString((info as unknown as PeopleIdentityInfo).github),
|
||||
image: dataAsString(info.image),
|
||||
judgements,
|
||||
legal: dataAsString(info.legal),
|
||||
matrix: dataAsString((info as unknown as PeopleIdentityInfo).matrix),
|
||||
other: info.additional ? extractOther(info.additional) : {},
|
||||
parent: 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<ITuple<[PezpalletIdentityRegistration, Option<Bytes>]>> | Option<PezpalletIdentityRegistration> | undefined, superOfOpt: Option<ITuple<[AccountId, Data]>> | undefined): Observable<[Option<ITuple<[PezpalletIdentityRegistration, Option<Bytes>]>> | Option<PezpalletIdentityRegistration> | undefined, [AccountId, Data] | undefined]> {
|
||||
if (identityOfOpt?.isSome) {
|
||||
// this identity has something set
|
||||
return of([identityOfOpt, undefined]);
|
||||
} else if (superOfOpt?.isSome) {
|
||||
const superOf = superOfOpt.unwrap();
|
||||
|
||||
return combineLatest([
|
||||
api.derive.accounts._identity(superOf[0]).pipe(
|
||||
map(([info]) => info)
|
||||
),
|
||||
of(superOf)
|
||||
]);
|
||||
}
|
||||
|
||||
// nothing of value returned
|
||||
return of([undefined, undefined]);
|
||||
}
|
||||
|
||||
export function _identity (instanceId: string, api: DeriveApi): (accountId?: AccountId | Uint8Array | string) => Observable<[Option<ITuple<[PezpalletIdentityRegistration, Option<Bytes>]>> | Option<PezpalletIdentityRegistration> | undefined, Option<ITuple<[AccountId, Data]>> | undefined]> {
|
||||
return memo(instanceId, (accountId?: AccountId | Uint8Array | string): Observable<[Option<ITuple<[PezpalletIdentityRegistration, Option<Bytes>]>> | Option<PezpalletIdentityRegistration> | undefined, Option<ITuple<[AccountId, Data]>> | undefined]> =>
|
||||
accountId && api.query.identity?.identityOf
|
||||
? combineLatest([
|
||||
api.query.identity.identityOf(accountId),
|
||||
api.query.identity.superOf(accountId)
|
||||
])
|
||||
: of([undefined, undefined])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name identity
|
||||
* @description Retrieves the on chain identity information for a given account.
|
||||
* @param {(AccountId | Uint8Array | string)} accoutId The account identifier to query the identity for.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const ALICE = "13xAUH";
|
||||
*
|
||||
* api.derive.accounts.identity(ALICE, (identity) => {
|
||||
* console.log(
|
||||
* "Account Identity:",
|
||||
* Object.keys(identity).map((key) => `${key}: ${identity[key]}`)
|
||||
* );
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function identity (instanceId: string, api: DeriveApi): (accountId?: AccountId | Uint8Array | string) => Observable<DeriveAccountRegistration> {
|
||||
return memo(instanceId, (accountId?: AccountId | Uint8Array | string): Observable<DeriveAccountRegistration> =>
|
||||
api.derive.accounts._identity(accountId).pipe(
|
||||
switchMap(([identityOfOpt, superOfOpt]) =>
|
||||
getParent(api, identityOfOpt, superOfOpt)
|
||||
),
|
||||
map(([identityOfOpt, superOf]) =>
|
||||
extractIdentity(identityOfOpt, superOf)
|
||||
),
|
||||
switchMap((identity) =>
|
||||
getSubIdentities(identity, api, accountId)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// if an account has no parents it will extract its subidentities
|
||||
// otherwise if the account is a subidentity, obtain all subidentities of its parent.
|
||||
function getSubIdentities (identity: DeriveAccountRegistration, api: DeriveApi, accountId?: AccountId | Uint8Array | string): Observable<DeriveAccountRegistration> {
|
||||
const targetAccount = identity.parent || accountId;
|
||||
|
||||
if (!targetAccount || !api.query.identity) {
|
||||
// No valid accountId return the identity as-is
|
||||
return of(identity);
|
||||
}
|
||||
|
||||
return api.query.identity.subsOf(targetAccount).pipe(
|
||||
map((subsResponse) => {
|
||||
const subs = subsResponse[1];
|
||||
|
||||
return {
|
||||
...identity,
|
||||
subs
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name hasIdentity
|
||||
* @description Checks if a specific account has an identity registered on chain.
|
||||
* @param {(AccountId | Uint8Array | string)} accoutId The account identifier to query.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const ALICE = "13AU";
|
||||
* console.log(await api.derive.accounts.hasIdentity(ALICE));
|
||||
* ```
|
||||
*/
|
||||
export const hasIdentity = /*#__PURE__*/ firstMemo(
|
||||
(api: DeriveApi, accountId: AccountId | Uint8Array | string) =>
|
||||
api.derive.accounts.hasIdentityMulti([accountId])
|
||||
);
|
||||
|
||||
/**
|
||||
* @name hasIdentityMulti
|
||||
* @description Checks whether multiple accounts have on chain identities registered.
|
||||
* @param {(AccountId | Uint8Array | string)[]} accountIds Array of account identifiers to query.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const ALICE = "13AU";
|
||||
* const BOB = "16WW";
|
||||
* console.log(await api.derive.accounts.hasIdentityMulti([ALICE, BOB]));
|
||||
* ```
|
||||
*/
|
||||
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(identityCompat(identityOfOpt).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-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './accountId.js';
|
||||
export * from './flags.js';
|
||||
export * from './idAndIndex.js';
|
||||
export * from './identity.js';
|
||||
export * from './idToIndex.js';
|
||||
export * from './indexes.js';
|
||||
export * from './indexToId.js';
|
||||
export * from './info.js';
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId, AccountIndex } from '@pezkuwi/types/interfaces';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name indexToId
|
||||
* @description Resolves an AccountIndex (short address) to the full AccountId.
|
||||
* @param {( AccountIndex | string )} accountIndex An accounts index in different formats.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const ALICE = "13AU";
|
||||
* const id = await api.derive.accounts.indexToId(ALICE);
|
||||
* console.log(id);
|
||||
* ```
|
||||
*/
|
||||
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,56 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountIndexes, DeriveApi } from '../types.js';
|
||||
|
||||
import { map, of, startWith } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
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
|
||||
* ```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,58 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Bytes, Option, u32 } from '@pezkuwi/types';
|
||||
import type { AccountId, AccountIndex, Address, Balance } from '@pezkuwi/types/interfaces';
|
||||
import type { ITuple } from '@pezkuwi/types/types';
|
||||
import type { DeriveAccountInfo, DeriveAccountRegistration, DeriveApi } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { u8aToString } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
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]).substring(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, identity and nickname
|
||||
* @param {(AccountIndex | AccountId | Address | Uint8Array | string | null)} address An accounts in different formats.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const ALICE = "13AU";
|
||||
* const info = await api.derive.accounts.info(ALICE);
|
||||
* console.log(
|
||||
* "Account Info: ",
|
||||
* Object.keys(info).map((key) => `${key}: ${info[key]}`)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
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 | undefined]> =>
|
||||
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,48 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AccountId, AccountIndex, RegistrationJudgement } from '@pezkuwi/types/interfaces';
|
||||
import type { Vec } from '@pezkuwi/types-codec';
|
||||
|
||||
export type AccountIdAndIndex = [AccountId | undefined, AccountIndex | undefined];
|
||||
|
||||
export type AccountIndexes = Record<string, AccountIndex>;
|
||||
|
||||
export interface DeriveAccountRegistration {
|
||||
discord?: string | undefined;
|
||||
display?: string | undefined;
|
||||
displayParent?: string | undefined;
|
||||
email?: string | undefined;
|
||||
github?: string | undefined;
|
||||
image?: string | undefined;
|
||||
legal?: string | undefined;
|
||||
matrix?: string | undefined;
|
||||
other?: Record<string, string> | undefined;
|
||||
parent?: AccountId | undefined;
|
||||
pgp?: string | undefined;
|
||||
riot?: string | undefined;
|
||||
subs?: Vec<AccountId> | undefined;
|
||||
twitter?: string | undefined;
|
||||
web?: string | undefined;
|
||||
judgements: RegistrationJudgement[];
|
||||
}
|
||||
|
||||
export interface DeriveAccountFlags {
|
||||
isCouncil: boolean;
|
||||
isSociety: boolean;
|
||||
isSudo: boolean;
|
||||
isTechCommittee: boolean;
|
||||
}
|
||||
|
||||
export interface DeriveAccountInfo {
|
||||
accountId?: AccountId | undefined;
|
||||
accountIndex?: AccountIndex | undefined;
|
||||
identity: DeriveAccountRegistration;
|
||||
nickname?: string | undefined;
|
||||
}
|
||||
|
||||
export interface DeriveHasIdentity {
|
||||
display?: string | undefined;
|
||||
hasIdentity: boolean;
|
||||
parentId?: string | undefined;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2017-2025 @pezkuwi/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/index.js';
|
||||
|
||||
/**
|
||||
* @name members
|
||||
* @description Retrieves the list of members in the "allianceMotion" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const members = await api.derive.alliance.members();
|
||||
* console.log(`Members: ${JSON.stringify(members)});
|
||||
* ```
|
||||
*/
|
||||
export const members = /*#__PURE__*/ collectiveMembers('allianceMotion');
|
||||
|
||||
/**
|
||||
* @name hasProposals
|
||||
* @description Checks if there are any active proposals in the "allianceMotion" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const exists = await api.derive.alliance.hasProposals();
|
||||
* console.log(exists);
|
||||
* ```
|
||||
*/
|
||||
export const hasProposals = /*#__PURE__*/ collectiveHasProposals('allianceMotion');
|
||||
/**
|
||||
* @name proposal
|
||||
* @description Retrieves details of a specific proposal in the "allianceMotion" collective by its hash.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const proposalDetails = await api.derive.alliance.proposal(PROPOSAL_HASH);
|
||||
* console.log(proposalDetails);
|
||||
* ```
|
||||
*/
|
||||
export const proposal = /*#__PURE__*/ collectiveProposal('allianceMotion');
|
||||
/**
|
||||
* @name proposalCount
|
||||
* @description Retrieves the total number of proposals in the "allianceMotion" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const count = await api.derive.alliance.proposalCount();
|
||||
* console.log(`Amount of proposals: ${count}`);
|
||||
* ```
|
||||
*/
|
||||
export const proposalCount = /*#__PURE__*/ collectiveProposalCount('allianceMotion');
|
||||
/**
|
||||
* @name proposalHashes
|
||||
* @description Retrieves an array of hashes for all active proposals in the "allianceMotion" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const hashes = await api.derive.alliance.proposalHashes();
|
||||
* console.log(`Proposals ${JSON.stringify(hashes)}`);
|
||||
* ```
|
||||
*/
|
||||
export const proposalHashes = /*#__PURE__*/ collectiveProposalHashes('allianceMotion');
|
||||
/**
|
||||
* @name proposals
|
||||
* @description Retrieves a list of all active proposals in the "allianceMotion" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const proposals = await api.derive.alliance.proposals();
|
||||
* console.log(proposals);
|
||||
* ```
|
||||
*/
|
||||
export const proposals = /*#__PURE__*/ collectiveProposals('allianceMotion');
|
||||
/**
|
||||
* @name prime
|
||||
* @description Retrieves the prime member of the "allianceMotion" collective, if one exists.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const primeMember = await api.derive.alliance.prime();
|
||||
* console.log(primeMember);
|
||||
* ```
|
||||
*/
|
||||
export const prime = /*#__PURE__*/ collectivePrime('allianceMotion');
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import '@pezkuwi/api-augment';
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, u64 } from '@pezkuwi/types';
|
||||
import type { PezpalletBagsListListBag } from '@pezkuwi/types/lookup';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
import type { Bag } from './types.js';
|
||||
|
||||
import { map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { BN_ZERO, bnToBn, objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
import { getQueryInterface } from './util.js';
|
||||
|
||||
function orderBags (ids: BN[], bags: Option<PezpalletBagsListListBag>[]): Bag[] {
|
||||
const sorted = ids
|
||||
.map((id, index) => ({
|
||||
bag: bags[index].unwrapOr(null),
|
||||
id,
|
||||
key: id.toString()
|
||||
}))
|
||||
.sort((a, b) => b.id.cmp(a.id));
|
||||
const max = sorted.length - 1;
|
||||
|
||||
return sorted.map((entry, index): Bag =>
|
||||
objectSpread(entry, {
|
||||
bagLower: index === max
|
||||
? BN_ZERO
|
||||
: sorted[index + 1].id,
|
||||
bagUpper: entry.id,
|
||||
index
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function _getIds (instanceId: string, api: DeriveApi): (ids: (BN | number)[]) => Observable<Bag[]> {
|
||||
const query = getQueryInterface(api);
|
||||
|
||||
return memo(instanceId, (_ids: (BN | number)[]): Observable<Bag[]> => {
|
||||
const ids = _ids.map((id) => bnToBn(id));
|
||||
|
||||
return ids.length
|
||||
? query.listBags.multi<Option<PezpalletBagsListListBag>>(ids).pipe(
|
||||
map((bags) => orderBags(ids, bags))
|
||||
)
|
||||
: of([]);
|
||||
});
|
||||
}
|
||||
|
||||
export function all (instanceId: string, api: DeriveApi): () => Observable<Bag[]> {
|
||||
const query = getQueryInterface(api);
|
||||
|
||||
return memo(instanceId, (): Observable<Bag[]> =>
|
||||
query.listBags.keys<[u64]>().pipe(
|
||||
switchMap((keys) =>
|
||||
api.derive.bagsList._getIds(keys.map(({ args: [id] }) => id))
|
||||
),
|
||||
map((list) =>
|
||||
list.filter(({ bag }) => bag)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name get
|
||||
* @param {(BN | number)} id The id of the bag to retrieve.
|
||||
* @description Retrieves a specific bag from the BagsList pallet by its id.
|
||||
*/
|
||||
export function get (instanceId: string, api: DeriveApi): (id: BN | number) => Observable<Bag> {
|
||||
return memo(instanceId, (id: BN | number): Observable<Bag> =>
|
||||
api.derive.bagsList._getIds([bnToBn(id)]).pipe(
|
||||
map((bags) => bags[0])
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
import type { Bag, BagExpanded } from './types.js';
|
||||
|
||||
import { map, switchMap } from 'rxjs';
|
||||
|
||||
import { objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name expand
|
||||
* @description Expands a given bag by retrieving all its nodes (accounts contained within the bag).
|
||||
* @param {Bag} bag The bag to be expanded.
|
||||
*/
|
||||
export function expand (instanceId: string, api: DeriveApi): (bag: Bag) => Observable<BagExpanded> {
|
||||
return memo(instanceId, (bag: Bag): Observable<BagExpanded> =>
|
||||
api.derive.bagsList.listNodes(bag.bag).pipe(
|
||||
map((nodes) => objectSpread({ nodes }, bag))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name getExpanded
|
||||
* @description Retrieves and expands a specific bag from the BagsList pallet.
|
||||
* @param {BN | number} id The id of the bag to expand.
|
||||
*/
|
||||
export function getExpanded (instanceId: string, api: DeriveApi): (id: BN | number) => Observable<BagExpanded> {
|
||||
return memo(instanceId, (id: BN | number): Observable<BagExpanded> =>
|
||||
api.derive.bagsList.get(id).pipe(
|
||||
switchMap((bag) =>
|
||||
api.derive.bagsList.expand(bag)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './get.js';
|
||||
export * from './getExpanded.js';
|
||||
export * from './listNodes.js';
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option } from '@pezkuwi/types';
|
||||
import type { AccountId32 } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletBagsListListBag, PezpalletBagsListListNode } from '@pezkuwi/types/lookup';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { BehaviorSubject, map, of, switchMap, tap, toArray } from 'rxjs';
|
||||
|
||||
import { nextTick } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
import { getQueryInterface } from './util.js';
|
||||
|
||||
function traverseLinks (api: DeriveApi, head: AccountId32 | string): Observable<PezpalletBagsListListNode[]> {
|
||||
const subject = new BehaviorSubject<AccountId32 | string>(head);
|
||||
const query = getQueryInterface(api);
|
||||
|
||||
return subject.pipe(
|
||||
switchMap((account) =>
|
||||
query.listNodes<Option<PezpalletBagsListListNode>>(account)
|
||||
),
|
||||
tap((node: Option<PezpalletBagsListListNode>): void => {
|
||||
nextTick((): void => {
|
||||
node.isSome && node.value.next.isSome
|
||||
? subject.next(node.unwrap().next.unwrap())
|
||||
: subject.complete();
|
||||
});
|
||||
}),
|
||||
toArray(), // toArray since we want to startSubject to be completed
|
||||
map((all: Option<PezpalletBagsListListNode>[]) =>
|
||||
all.map((o) => o.unwrap())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name listNodes
|
||||
* @param {(PezpalletBagsListListBag | null)} bag A reference to a specific bag in the BagsList pallet.
|
||||
* @description Retrieves the list of nodes (accounts) contained in a specific bag within the BagsList pallet.
|
||||
*/
|
||||
export function listNodes (instanceId: string, api: DeriveApi): (bag: PezpalletBagsListListBag | null) => Observable<PezpalletBagsListListNode[]> {
|
||||
return memo(instanceId, (bag: PezpalletBagsListListBag | null): Observable<PezpalletBagsListListNode[]> =>
|
||||
bag && bag.head.isSome
|
||||
? traverseLinks(api, bag.head.unwrap())
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PezpalletBagsListListBag, PezpalletBagsListListNode } from '@pezkuwi/types/lookup';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
|
||||
export interface Bag {
|
||||
bag: PezpalletBagsListListBag | null;
|
||||
bagUpper: BN;
|
||||
bagLower: BN;
|
||||
id: BN;
|
||||
index: number;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface BagExpanded extends Bag {
|
||||
nodes: PezpalletBagsListListNode[];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
export function getQueryInterface (api: DeriveApi): DeriveApi['query']['voterList'] {
|
||||
return (
|
||||
// latest bizinikiwi & pezkuwi
|
||||
api.query.voterList ||
|
||||
// previous bizinikiwi
|
||||
api.query['voterBagsList'] ||
|
||||
api.query['bagsList']
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { QueryableStorageEntry } from '@pezkuwi/api-base/types';
|
||||
import type { AccountData, AccountId, AccountIndex, AccountInfo, Address, Balance, Index } from '@pezkuwi/types/interfaces';
|
||||
import type { PezframeSystemAccountInfo, PezpalletBalancesAccountData } from '@pezkuwi/types/lookup';
|
||||
import type { ITuple } from '@pezkuwi/types/types';
|
||||
import type { DeriveApi, DeriveBalancesAccount, DeriveBalancesAccountData } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { isFunction, objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
type BalanceResult = [Balance, Balance, Balance, Balance];
|
||||
|
||||
type Result = [Index, BalanceResult[], AccountType];
|
||||
|
||||
interface AccountType { isFrameAccountData: boolean }
|
||||
|
||||
type DeriveCustomAccount = DeriveApi['derive'] & Record<string, {
|
||||
customAccount?: DeriveApi['query']['balances']['account']
|
||||
}>
|
||||
|
||||
function zeroBalance (api: DeriveApi) {
|
||||
return api.registry.createType('Balance');
|
||||
}
|
||||
|
||||
function getBalance (api: DeriveApi, [freeBalance, reservedBalance, frozenFeeOrFrozen, frozenMiscOrFlags]: BalanceResult, accType: AccountType): DeriveBalancesAccountData {
|
||||
const votingBalance = api.registry.createType('Balance', freeBalance.toBn());
|
||||
|
||||
if (accType.isFrameAccountData) {
|
||||
return {
|
||||
frameSystemAccountInfo: {
|
||||
flags: frozenMiscOrFlags,
|
||||
frozen: frozenFeeOrFrozen
|
||||
},
|
||||
freeBalance,
|
||||
frozenFee: api.registry.createType('Balance', 0),
|
||||
frozenMisc: api.registry.createType('Balance', 0),
|
||||
reservedBalance,
|
||||
votingBalance
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
freeBalance,
|
||||
frozenFee: frozenFeeOrFrozen,
|
||||
frozenMisc: frozenMiscOrFlags,
|
||||
reservedBalance,
|
||||
votingBalance
|
||||
};
|
||||
}
|
||||
|
||||
function calcBalances (api: DeriveApi, [accountId, [accountNonce, [primary, ...additional], accType]]: [AccountId, Result]): DeriveBalancesAccount {
|
||||
return objectSpread({
|
||||
accountId,
|
||||
accountNonce,
|
||||
additional: additional.map((b) => getBalance(api, b, accType))
|
||||
}, getBalance(api, primary, accType));
|
||||
}
|
||||
|
||||
// old
|
||||
function queryBalancesFree (api: DeriveApi, accountId: AccountId): Observable<Result> {
|
||||
return combineLatest([
|
||||
api.query.balances['freeBalance']<Balance>(accountId),
|
||||
api.query.balances['reservedBalance']<Balance>(accountId),
|
||||
api.query.system['accountNonce']<Index>(accountId)
|
||||
]).pipe(
|
||||
map(([freeBalance, reservedBalance, accountNonce]): Result => [
|
||||
accountNonce,
|
||||
[[freeBalance, reservedBalance, zeroBalance(api), zeroBalance(api)]],
|
||||
{ isFrameAccountData: false }
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
function queryNonceOnly (api: DeriveApi, accountId: AccountId): Observable<Result> {
|
||||
const fill = (nonce: Index): Result => [
|
||||
nonce,
|
||||
[[zeroBalance(api), zeroBalance(api), zeroBalance(api), zeroBalance(api)]],
|
||||
{ isFrameAccountData: false }
|
||||
];
|
||||
|
||||
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 as 'balances']?.account)
|
||||
.filter((q) => isFunction(q));
|
||||
|
||||
const extract = (nonce: Index, data: AccountData[]): Result => [
|
||||
nonce,
|
||||
data.map(({ feeFrozen, free, miscFrozen, reserved }): BalanceResult => [free, reserved, feeFrozen, miscFrozen]),
|
||||
{ isFrameAccountData: false }
|
||||
];
|
||||
|
||||
// NOTE this is for the first case where we do have instances specified
|
||||
return balances.length
|
||||
? isFunction(api.query.system.account)
|
||||
? combineLatest([
|
||||
api.query.system.account(accountId),
|
||||
...balances.map((c) => c(accountId))
|
||||
]).pipe(
|
||||
map(([{ nonce }, ...balances]) => extract(nonce, balances as unknown as AccountData[]))
|
||||
)
|
||||
: combineLatest([
|
||||
api.query.system['accountNonce']<Index>(accountId),
|
||||
...balances.map((c) => c(accountId))
|
||||
]).pipe(
|
||||
map(([nonce, ...balances]) => extract(nonce, balances as unknown as AccountData[]))
|
||||
)
|
||||
: queryNonceOnly(api, accountId);
|
||||
}
|
||||
|
||||
function querySystemAccount (api: DeriveApi, accountId: AccountId): Observable<Result> {
|
||||
// AccountInfo is current, support old, eg. Edgeware
|
||||
return api.query.system.account<AccountInfo | PezframeSystemAccountInfo | 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)]],
|
||||
{ isFrameAccountData: false }
|
||||
];
|
||||
}
|
||||
|
||||
const isFrameType = !!(infoOrTuple as PezframeSystemAccountInfo).data.frozen;
|
||||
|
||||
if (isFrameType) {
|
||||
const { flags, free, frozen, reserved } = (data as unknown as PezpalletBalancesAccountData);
|
||||
|
||||
return [
|
||||
nonce,
|
||||
[[free, reserved, frozen, flags]],
|
||||
{ isFrameAccountData: true }
|
||||
];
|
||||
} else {
|
||||
const { feeFrozen, free, miscFrozen, reserved } = data;
|
||||
|
||||
return [
|
||||
nonce,
|
||||
[[free, reserved, feeFrozen, miscFrozen]],
|
||||
{ isFrameAccountData: false }
|
||||
];
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name account
|
||||
* @description Retrieves the essential balance details for an account, such as free balance and account nonce.
|
||||
* @param {( AccountIndex | AccountId | Address | string )} address An accountsId in different formats.
|
||||
* @example
|
||||
* ```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, 'balances');
|
||||
const nonDefaultBalances = balanceInstances && balanceInstances[0] !== '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),
|
||||
nonDefaultBalances
|
||||
? 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)]],
|
||||
{ isFrameAccountData: false }
|
||||
]])
|
||||
)
|
||||
),
|
||||
map((result): DeriveBalancesAccount => calcBalances(api, result))
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, Vec } from '@pezkuwi/types';
|
||||
import type { AccountId, Balance, BalanceLockTo212, BlockNumber, VestingSchedule } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletBalancesBalanceLock, PezpalletBalancesReserveData, PezpalletVestingVestingInfo } from '@pezkuwi/types/lookup';
|
||||
import type { DeriveApi, DeriveBalancesAccount, DeriveBalancesAccountData, DeriveBalancesAll, DeriveBalancesAllAccountData, DeriveBalancesAllVesting } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { BN, BN_ZERO, bnMax, bnMin, isFunction, objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
type ResultBalance = [PezpalletVestingVestingInfo[] | null, ((PezpalletBalancesBalanceLock | BalanceLockTo212)[])[], PezpalletBalancesReserveData[][]];
|
||||
type Result = [DeriveBalancesAccount, ResultBalance, BlockNumber];
|
||||
|
||||
interface AllLocked {
|
||||
allLocked: boolean,
|
||||
lockedBalance: Balance,
|
||||
lockedBreakdown: (PezpalletBalancesBalanceLock | BalanceLockTo212)[],
|
||||
vestingLocked: Balance
|
||||
}
|
||||
|
||||
type DeriveCustomLocks = DeriveApi['derive'] & Record<string, {
|
||||
customLocks?: DeriveApi['query']['balances']['locks']
|
||||
}>
|
||||
|
||||
const VESTING_ID = '0x76657374696e6720';
|
||||
|
||||
function calcLocked (api: DeriveApi, bestNumber: BlockNumber, locks: (PezpalletBalancesBalanceLock | BalanceLockTo212)[]): AllLocked {
|
||||
let lockedBalance = api.registry.createType('Balance');
|
||||
let lockedBreakdown: (PezpalletBalancesBalanceLock | 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/pezkuwichain/bizinikiwi/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: (PezpalletBalancesBalanceLock | BalanceLockTo212)[]): DeriveBalancesAllAccountData {
|
||||
const { allLocked, lockedBalance, lockedBreakdown, vestingLocked } = calcLocked(api, bestNumber, locks);
|
||||
let transferable = null;
|
||||
|
||||
if (data?.frameSystemAccountInfo?.frozen) {
|
||||
const { frameSystemAccountInfo, freeBalance, reservedBalance } = data;
|
||||
const noFrozenReserved = frameSystemAccountInfo.frozen.isZero() && reservedBalance.isZero();
|
||||
const ED = api.consts.balances.existentialDeposit;
|
||||
const maybeED = noFrozenReserved ? new BN(0) : ED;
|
||||
const frozenReserveDif = frameSystemAccountInfo.frozen.sub(reservedBalance);
|
||||
|
||||
transferable = api.registry.createType(
|
||||
'Balance',
|
||||
allLocked
|
||||
? 0
|
||||
: bnMax(new BN(0), freeBalance.sub(bnMax(maybeED, frozenReserveDif)))
|
||||
);
|
||||
}
|
||||
|
||||
return objectSpread({}, data, {
|
||||
availableBalance: api.registry.createType('Balance', allLocked ? 0 : bnMax(new BN(0), data?.freeBalance ? data.freeBalance.sub(lockedBalance) : new BN(0))),
|
||||
lockedBalance,
|
||||
lockedBreakdown,
|
||||
transferable,
|
||||
vestingLocked
|
||||
});
|
||||
}
|
||||
|
||||
function calcVesting (bestNumber: BlockNumber, shared: DeriveBalancesAllAccountData, _vesting: PezpalletVestingVestingInfo[] | 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, result: Result): DeriveBalancesAll {
|
||||
const [data, [vesting, allLocks, namedReserves], bestNumber] = result;
|
||||
const shared = calcShared(api, bestNumber, data, allLocks[0]);
|
||||
|
||||
return objectSpread(shared, calcVesting(bestNumber, shared, vesting), {
|
||||
accountId: data.accountId,
|
||||
accountNonce: data.accountNonce,
|
||||
additional: allLocks
|
||||
.slice(1)
|
||||
.map((l, index) => calcShared(api, bestNumber, data.additional[index], l)),
|
||||
namedReserves
|
||||
});
|
||||
}
|
||||
|
||||
// old
|
||||
function queryOld (api: DeriveApi, accountId: AccountId | string): Observable<ResultBalance> {
|
||||
return combineLatest([
|
||||
api.query.balances.locks(accountId),
|
||||
api.query.balances['vesting']<Option<VestingSchedule>>(accountId)
|
||||
]).pipe(
|
||||
map(([locks, optVesting]): ResultBalance => {
|
||||
let vestingNew = null;
|
||||
|
||||
if (optVesting.isSome) {
|
||||
const { offset: locked, perBlock, startingBlock } = optVesting.unwrap();
|
||||
|
||||
vestingNew = api.registry.createType<PezpalletVestingVestingInfo>('VestingInfo', { locked, perBlock, startingBlock });
|
||||
}
|
||||
|
||||
return [
|
||||
vestingNew
|
||||
? [vestingNew]
|
||||
: null,
|
||||
[locks],
|
||||
[]
|
||||
];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const isNonNullable = <T>(nullable: T): nullable is NonNullable<T> => !!nullable;
|
||||
|
||||
function createCalls <T> (calls: (((a: unknown) => Observable<T>) | null | undefined)[]): [boolean[], ((a: unknown) => Observable<T>)[]] {
|
||||
return [
|
||||
calls.map((c) => !c),
|
||||
calls.filter(isNonNullable)
|
||||
];
|
||||
}
|
||||
|
||||
// current (balances, vesting)
|
||||
function queryCurrent (api: DeriveApi, accountId: AccountId | string, balanceInstances: string[] = ['balances']): Observable<ResultBalance> {
|
||||
const [lockEmpty, lockQueries] = createCalls<Vec<PezpalletBalancesBalanceLock>>(
|
||||
balanceInstances.map((m) =>
|
||||
(api.derive as DeriveCustomLocks)[m]?.customLocks || api.query[m as 'balances']?.locks
|
||||
)
|
||||
);
|
||||
const [reserveEmpty, reserveQueries] = createCalls<Vec<PezpalletBalancesReserveData>>(
|
||||
balanceInstances.map((m) =>
|
||||
api.query[m as 'balances']?.reserves
|
||||
)
|
||||
);
|
||||
|
||||
return combineLatest([
|
||||
api.query.vesting?.vesting
|
||||
? api.query.vesting.vesting(accountId)
|
||||
: of(api.registry.createType('Option<VestingInfo>')),
|
||||
lockQueries.length
|
||||
? combineLatest(lockQueries.map((c) => c(accountId)))
|
||||
: of([] as Vec<PezpalletBalancesBalanceLock>[]),
|
||||
reserveQueries.length
|
||||
? combineLatest(reserveQueries.map((c) => c(accountId)))
|
||||
: of([] as Vec<PezpalletBalancesReserveData>[])
|
||||
]).pipe(
|
||||
map(([opt, locks, reserves]): ResultBalance => {
|
||||
let offsetLock = -1;
|
||||
let offsetReserve = -1;
|
||||
const vesting = opt.unwrapOr(null);
|
||||
|
||||
return [
|
||||
vesting
|
||||
? Array.isArray(vesting)
|
||||
? vesting
|
||||
: [vesting as PezpalletVestingVestingInfo]
|
||||
: null,
|
||||
lockEmpty.map((e) =>
|
||||
e ? api.registry.createType<Vec<PezpalletBalancesBalanceLock>>('Vec<BalanceLock>') : locks[++offsetLock]
|
||||
),
|
||||
reserveEmpty.map((e) =>
|
||||
e ? api.registry.createType<Vec<PezpalletBalancesReserveData>>('Vec<PezpalletBalancesReserveData>') : reserves[++offsetReserve]
|
||||
)
|
||||
];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name all
|
||||
* @description Retrieves the complete balance information for an account, including free balance, locked balance, reserved balance, and more.
|
||||
* @param {( AccountId | string )} address An accountsId in different formats.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const ALICE = 'F7Hs';
|
||||
*
|
||||
* api.derive.balances.account(ALICE, (accountInfo) => {
|
||||
* console.log(
|
||||
* `${accountInfo.accountId} info:`,
|
||||
* Object.keys(accountInfo).map((key) => `${key}: ${accountInfo[key]}`)
|
||||
* );
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function all (instanceId: string, api: DeriveApi): (address: AccountId | string) => Observable<DeriveBalancesAll> {
|
||||
const balanceInstances = api.registry.getModuleInstances(api.runtimeVersion.specName, 'balances');
|
||||
|
||||
return memo(instanceId, (address: AccountId | string): Observable<DeriveBalancesAll> =>
|
||||
combineLatest([
|
||||
api.derive.balances.account(address),
|
||||
isFunction(api.query.system?.account) || isFunction(api.query.balances?.account)
|
||||
? queryCurrent(api, address, balanceInstances)
|
||||
: queryOld(api, address)
|
||||
]).pipe(
|
||||
switchMap(([account, locks]) =>
|
||||
combineLatest([
|
||||
of(account),
|
||||
of(locks),
|
||||
api.derive.chain.bestNumber()
|
||||
])
|
||||
),
|
||||
map((result) => calcBalances(api, result))
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { all } from './all.js';
|
||||
|
||||
export * from './account.js';
|
||||
export * from './votingBalances.js';
|
||||
|
||||
/**
|
||||
* @name votingBalance
|
||||
* @param {( AccountId | 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.votingBalance(ALICE, ({ accountId, lockedBalance }) => {
|
||||
* console.log(`The account ${accountId} has a locked balance ${lockedBalance} units.`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
const votingBalance = all;
|
||||
|
||||
export { all, votingBalance };
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AccountId, Balance, BalanceLockTo212, Index } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletBalancesBalanceLock, PezpalletBalancesReserveData } from '@pezkuwi/types/lookup';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
|
||||
export interface DeriveBalancesAccountData {
|
||||
frameSystemAccountInfo?: {
|
||||
frozen: Balance;
|
||||
flags: Balance;
|
||||
}
|
||||
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 {
|
||||
/**
|
||||
* Calculated available balance. This uses the formula: max(0, free - locked)
|
||||
* This is only correct when the return type of `api.query.system.account` is `AccountInfo` which was replaced by `PezframeSystemAccountInfo`.
|
||||
* See `transferable` for the correct balance calculation.
|
||||
*
|
||||
* ref: https://github.com/pezkuwichain/bizinikiwi/pull/12951
|
||||
*/
|
||||
availableBalance: Balance;
|
||||
/**
|
||||
* The amount of balance locked away.
|
||||
*/
|
||||
lockedBalance: Balance;
|
||||
/**
|
||||
* The breakdown of locked balances.
|
||||
*/
|
||||
lockedBreakdown: (PezpalletBalancesBalanceLock | BalanceLockTo212)[];
|
||||
/**
|
||||
* Calculated transferable balance. This uses the formula: free - max(maybeEd, frozen - reserve)
|
||||
* Where `maybeEd` means if there is no frozen and reserves it will be zero, else it will be the existential deposit.
|
||||
* This is only correct when the return type of `api.query.system.account` is `PezframeSystemAccountInfo`.
|
||||
* Which is the most up to date calulcation for transferrable balances.
|
||||
*
|
||||
* ref: https://github.com/pezkuwichain/pezkuwi-sdk/issues/1833
|
||||
*/
|
||||
transferable: Balance | null;
|
||||
/**
|
||||
* Amount locked in vesting.
|
||||
*/
|
||||
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[];
|
||||
namedReserves: PezpalletBalancesReserveData[][];
|
||||
}
|
||||
|
||||
export type DeriveBalancesMap = Record<string, DeriveBalancesAll>;
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId, AccountIndex, Address } from '@pezkuwi/types/interfaces';
|
||||
import type { DeriveApi, DeriveBalancesAccount } from '../types.js';
|
||||
|
||||
import { combineLatest, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name votingBalances
|
||||
* @description Retrieves the balance information for multiple accounts, typically used in governance-related contexts to check voting power.
|
||||
* @param {(AccountId | AccountIndex | Address | string)[]} addresses An array of account identifiers.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const addresses = ["5D4b...Zf1", "5HGj...yrV"];
|
||||
* const balances = await api.derive.balances.votingBalances(addresses);
|
||||
* console.log("Voting Balances:", balances);
|
||||
* ```
|
||||
*/
|
||||
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?.length
|
||||
? of([] as DeriveBalancesAccount[])
|
||||
: combineLatest(
|
||||
addresses.map((accountId): Observable<DeriveBalancesAccount> =>
|
||||
api.derive.balances.account(accountId)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import type { ApiPromise } from '@pezkuwi/api';
|
||||
import type { SubmittableExtrinsic } from '@pezkuwi/api-base/types';
|
||||
import type { Bytes, Option, StorageKey } from '@pezkuwi/types';
|
||||
import type { Bounty, BountyIndex, Proposal, ProposalIndex } from '@pezkuwi/types/interfaces';
|
||||
import type { Codec, InterfaceTypes } from '@pezkuwi/types/types';
|
||||
import type { DeriveApi, DeriveCollectiveProposal } from '../types.js';
|
||||
|
||||
import { firstValueFrom, of } from 'rxjs';
|
||||
|
||||
import { BountyFactory } from '../test/bountyFactory.js';
|
||||
import { BytesFactory } from '../test/bytesFactory.js';
|
||||
import { createApiWithAugmentations } from '../test/helpers.js';
|
||||
import { ProposalFactory } from '../test/proposalFactory.js';
|
||||
import { bounties } from './index.js';
|
||||
|
||||
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 pezkuwi 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.transferAllowDeath('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 pezkuwi 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 pezkuwi 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 pezkuwi 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: api.tx.treasury['approveProposal'] && 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,76 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Bytes, Option } from '@pezkuwi/types';
|
||||
import type { BountyIndex } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletBountiesBounty } from '@pezkuwi/types/lookup';
|
||||
import type { DeriveApi, DeriveBounties, DeriveCollectiveProposal } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
import { filterBountiesProposals } from './helpers/filterBountyProposals.js';
|
||||
|
||||
type Result = [Option<PezpalletBountiesBounty>[], 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) =>
|
||||
bountyProposal.proposal && ids[index].eq(bountyProposal.proposal.args[0])
|
||||
)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return bounties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name bounties
|
||||
* @descrive Retrieves all active bounties, their descriptions, and associated proposals.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const bounties = await api.derive.bounties();
|
||||
* console.log("Active bounties:", bounties);
|
||||
* ```
|
||||
*/
|
||||
export function bounties (instanceId: string, api: DeriveApi): () => Observable<DeriveBounties> {
|
||||
const bountyBase = api.query.bounties || api.query.treasury;
|
||||
|
||||
return memo(instanceId, (): Observable<DeriveBounties> =>
|
||||
bountyBase.bounties
|
||||
? 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)
|
||||
)
|
||||
: of(parseResult([[], [], [], []]))
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveApi, DeriveCollectiveProposal } from '../../types.js';
|
||||
|
||||
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) =>
|
||||
proposal.proposal && bountyCall.is(proposal.proposal))
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './bounties.js';
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveCustom } from '@pezkuwi/api-base/types';
|
||||
import type { AnyFunction, AnyString } from '@pezkuwi/types/types';
|
||||
import type { ExactDerive } from './derive.js';
|
||||
import type { DeriveApi } from './types.js';
|
||||
|
||||
import { lazyDeriveSection } from './util/index.js';
|
||||
import { derive } from './derive.js';
|
||||
|
||||
export * from './derive.js';
|
||||
export * from './type/index.js';
|
||||
|
||||
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> = {
|
||||
allianceMotion: {
|
||||
instances: ['allianceMotion'],
|
||||
methods: []
|
||||
},
|
||||
bagsList: {
|
||||
instances: ['voterBagsList', 'voterList', 'bagsList'],
|
||||
methods: [],
|
||||
withDetect: true
|
||||
},
|
||||
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: []
|
||||
},
|
||||
teyrchains: {
|
||||
instances: ['teyrchains', '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: AnyString, 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;
|
||||
|
||||
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, count = names.length; i < count; 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,21 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { createBlockNumberDerive } from './util.js';
|
||||
|
||||
/**
|
||||
* @name bestNumber
|
||||
* @descrive Retrieves the latest block number.
|
||||
* @example
|
||||
* ```javascript
|
||||
* api.derive.chain.bestNumber((blockNumber) => {
|
||||
* console.log(`the current best block is #${blockNumber}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const bestNumber = /*#__PURE__*/ createBlockNumberDerive(
|
||||
(api: DeriveApi) =>
|
||||
api.rpc.chain.subscribeNewHeads()
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { createBlockNumberDerive } from './util.js';
|
||||
|
||||
/**
|
||||
* @name bestNumberFinalized
|
||||
* @returns A BlockNumber
|
||||
* @description Get the latest finalized block number.
|
||||
* @example
|
||||
* ```javascript
|
||||
* api.derive.chain.bestNumberFinalized((blockNumber) => {
|
||||
* console.log(`the current finalized block is #${blockNumber}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const bestNumberFinalized = /*#__PURE__*/ createBlockNumberDerive(
|
||||
(api: DeriveApi) =>
|
||||
api.rpc.chain.subscribeFinalizedHeads()
|
||||
);
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { BlockNumber } from '@pezkuwi/types/interfaces';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { combineLatest, map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name bestNumberLag
|
||||
* @returns A number of blocks
|
||||
* @description Calculates the lag between finalized head and best head
|
||||
* @examplew
|
||||
* ```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,43 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { SignedBlockExtended } from '../type/types.js';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { createSignedBlockExtended } from '../type/index.js';
|
||||
import { memo } from '../util/index.js';
|
||||
import { getAuthorDetails } from './util.js';
|
||||
|
||||
/**
|
||||
* @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
|
||||
* ```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> {
|
||||
return memo(instanceId, (blockHash: Uint8Array | string): Observable<SignedBlockExtended> =>
|
||||
combineLatest([
|
||||
api.rpc.chain.getBlock(blockHash),
|
||||
api.queryAt(blockHash)
|
||||
]).pipe(
|
||||
switchMap(([signedBlock, queryAt]) =>
|
||||
combineLatest([
|
||||
of(signedBlock),
|
||||
queryAt.system.events(),
|
||||
getAuthorDetails(api, signedBlock.block.header, blockHash)
|
||||
])
|
||||
),
|
||||
map(([signedBlock, events, [, validators, author]]) =>
|
||||
createSignedBlockExtended(events.registry, signedBlock, events, validators, author)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AnyNumber } from '@pezkuwi/types/types';
|
||||
import type { SignedBlockExtended } from '../type/types.js';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name getBlockByNumber
|
||||
* @param {( BN | bigint | Uint8Array | number | string )} blockNumber
|
||||
* @description Get a specific block (e.g. rpc.chain.getBlock) and extend it with the author by block number
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { author, block } = await api.derive.chain.getBlockByNumber(123);
|
||||
*
|
||||
* console.log(`block #${block.header.number} was authored by ${author}`);
|
||||
* ```
|
||||
*/
|
||||
export function getBlockByNumber (instanceId: string, api: DeriveApi): (blockNumber: AnyNumber) => Observable<SignedBlockExtended> {
|
||||
return memo(instanceId, (blockNumber: AnyNumber): Observable<SignedBlockExtended> =>
|
||||
api.rpc.chain.getBlockHash(blockNumber).pipe(
|
||||
switchMap((h) => api.derive.chain.getBlock(h))
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { HeaderExtended } from '../type/types.js';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { map, switchMap } from 'rxjs';
|
||||
|
||||
import { createHeaderExtended } from '../type/index.js';
|
||||
import { memo } from '../util/index.js';
|
||||
import { getAuthorDetails } from './util.js';
|
||||
|
||||
/**
|
||||
* @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
|
||||
* ```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> {
|
||||
return memo(instanceId, (blockHash: Uint8Array | string): Observable<HeaderExtended> =>
|
||||
api.rpc.chain.getHeader(blockHash).pipe(
|
||||
switchMap((header) =>
|
||||
getAuthorDetails(api, header, blockHash)
|
||||
),
|
||||
map(([header, validators, author]) =>
|
||||
createHeaderExtended((validators || header).registry, header, validators, author)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './bestNumber.js';
|
||||
export * from './bestNumberFinalized.js';
|
||||
export * from './bestNumberLag.js';
|
||||
export * from './getBlock.js';
|
||||
export * from './getBlockByNumber.js';
|
||||
export * from './getHeader.js';
|
||||
export * from './subscribeFinalizedBlocks.js';
|
||||
export * from './subscribeFinalizedHeads.js';
|
||||
export * from './subscribeNewBlocks.js';
|
||||
export * from './subscribeNewHeads.js';
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { SignedBlockExtended } from '../type/types.js';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name subscribeFinalizedBlocks
|
||||
* @description Retrieves the finalized block & events for that block
|
||||
* @example
|
||||
* ```javascript
|
||||
* const unsub = await api.derive.chain.subscribeFinalizedBlocks((finalizedBlock) => {
|
||||
* console.log(`# Finalized block ${finalizedBlock.block.hash}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function subscribeFinalizedBlocks (instanceId: string, api: DeriveApi): () => Observable<SignedBlockExtended> {
|
||||
return memo(instanceId, (): Observable<SignedBlockExtended> =>
|
||||
api.derive.chain.subscribeFinalizedHeads().pipe(
|
||||
switchMap((header) =>
|
||||
api.derive.chain.getBlock(header.createdAtHash || header.hash)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Hash, Header } from '@pezkuwi/types/interfaces';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { from, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* Returns a header range from startHash to to (not including) endHash, i.e. lastBlock.parentHash === endHash
|
||||
*/
|
||||
export function _getHeaderRange (instanceId: string, api: DeriveApi): (startHash: Hash, endHash: Hash, prev?: Header[]) => Observable<Header[]> {
|
||||
return memo(instanceId, (startHash: Hash, endHash: Hash, prev: Header[] = []): Observable<Header[]> =>
|
||||
api.rpc.chain.getHeader(startHash).pipe(
|
||||
switchMap((header) =>
|
||||
header.parentHash.eq(endHash)
|
||||
? of([header, ...prev])
|
||||
: api.derive.chain._getHeaderRange(header.parentHash, endHash, [header, ...prev])
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name subscribeFinalizedHeads
|
||||
* @description An observable of the finalized block headers. Unlike the base
|
||||
* chain.subscribeFinalizedHeads this does not skip any headers. Since finalization
|
||||
* may skip specific blocks (finalization happens in terms of chains), this version
|
||||
* of the derive tracks missing headers (since last retrieved) and provides them
|
||||
* to the caller.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const unsub = await api.derive.chain.subscribeFinalizedHeads((finalizedHead) => {
|
||||
* console.log(`${finalizedHead.hash}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function subscribeFinalizedHeads (instanceId: string, api: DeriveApi): () => Observable<Header> {
|
||||
return memo(instanceId, (): Observable<Header> => {
|
||||
let prevHash: Hash | null = null;
|
||||
|
||||
return api.rpc.chain.subscribeFinalizedHeads().pipe(
|
||||
switchMap((header) => {
|
||||
const endHash = prevHash;
|
||||
const startHash = header.parentHash;
|
||||
|
||||
prevHash = header.createdAtHash = header.hash;
|
||||
|
||||
return endHash === null || startHash.eq(endHash)
|
||||
? of(header)
|
||||
: api.derive.chain._getHeaderRange(startHash, endHash, [header]).pipe(
|
||||
switchMap((headers) =>
|
||||
from(headers)
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { SignedBlockExtended } from '../type/types.js';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name subscribeNewBlocks
|
||||
* @returns The latest block & events for that block
|
||||
* @example
|
||||
* ```javascript
|
||||
* const unsub = await api.derive.chain.subscribeNewBlocks((newBlock) => {
|
||||
* console.log(`Block Hash: ${newBlock.hash}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function subscribeNewBlocks (instanceId: string, api: DeriveApi): () => Observable<SignedBlockExtended> {
|
||||
return memo(instanceId, (): Observable<SignedBlockExtended> =>
|
||||
api.derive.chain.subscribeNewHeads().pipe(
|
||||
switchMap((header) =>
|
||||
api.derive.chain.getBlock(header.createdAtHash || header.hash)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { HeaderExtended } from '../type/types.js';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { map, switchMap } from 'rxjs';
|
||||
|
||||
import { createHeaderExtended } from '../type/index.js';
|
||||
import { memo } from '../util/index.js';
|
||||
import { getAuthorDetails } from './util.js';
|
||||
|
||||
/**
|
||||
* @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
|
||||
* ```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> =>
|
||||
api.rpc.chain.subscribeNewHeads().pipe(
|
||||
switchMap((header) =>
|
||||
getAuthorDetails(api, header)
|
||||
),
|
||||
map(([header, validators, author]): HeaderExtended => {
|
||||
header.createdAtHash = header.hash;
|
||||
|
||||
return createHeaderExtended(header.registry, header, validators, author);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { QueryableStorage } from '@pezkuwi/api-base/types';
|
||||
import type { Compact, Vec } from '@pezkuwi/types';
|
||||
import type { AccountId, BlockNumber, Header } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletImOnlineSr25519AppSr25519Public } from '@pezkuwi/types/lookup';
|
||||
import type { Codec, IOption } from '@pezkuwi/types/types';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { combineLatest, map, mergeMap, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo, unwrapBlockNumber } from '../util/index.js';
|
||||
|
||||
export type BlockNumberDerive = (instanceId: string, api: DeriveApi) => () => Observable<BlockNumber>;
|
||||
|
||||
type OptionMapping = IOption<{ account: AccountId } & Codec>;
|
||||
type OptionNimbus = IOption<{ nimbus: PezpalletImOnlineSr25519AppSr25519Public } & Codec>;
|
||||
|
||||
export function createBlockNumberDerive <T extends { number: Compact<BlockNumber> | BlockNumber }> (fn: (api: DeriveApi) => Observable<T>): BlockNumberDerive {
|
||||
return (instanceId: string, api: DeriveApi) =>
|
||||
memo(instanceId, () =>
|
||||
fn(api).pipe(
|
||||
map(unwrapBlockNumber)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function getAuthorDetailsWithAt (header: Header, queryAt: QueryableStorage<'rxjs'>): Observable<[Header, Vec<AccountId> | null, AccountId | null]> {
|
||||
const validators = queryAt.session?.validators
|
||||
? queryAt.session.validators()
|
||||
: of(null);
|
||||
|
||||
// nimbus consensus stores the session key of the block author in header logs
|
||||
const { logs: [log] } = header.digest;
|
||||
const loggedAuthor = (log && (
|
||||
(log.isConsensus && log.asConsensus[0].isNimbus && log.asConsensus[1]) ||
|
||||
(log.isPreRuntime && log.asPreRuntime[0].isNimbus && log.asPreRuntime[1])
|
||||
));
|
||||
|
||||
if (loggedAuthor) {
|
||||
// use the author mapping pallet, if available (ie: moonbeam, moonriver), to map session (nimbus) key to author (collator/validator) key
|
||||
if (queryAt['authorMapping']?.['mappingWithDeposit']) {
|
||||
return combineLatest([
|
||||
of(header),
|
||||
validators,
|
||||
queryAt['authorMapping']['mappingWithDeposit']<OptionMapping>(loggedAuthor).pipe(
|
||||
map((o) =>
|
||||
o.unwrapOr({ account: null }).account
|
||||
)
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
// fall back to session and teyrchain staking pallets, if available (ie: manta, calamari), to map session (nimbus) key to author (collator) key
|
||||
if (queryAt['teyrchainStaking']?.['selectedCandidates'] && queryAt.session?.nextKeys) {
|
||||
const loggedHex = loggedAuthor.toHex();
|
||||
|
||||
return combineLatest([
|
||||
of(header),
|
||||
validators,
|
||||
queryAt['teyrchainStaking']['selectedCandidates']<Vec<AccountId>>().pipe(
|
||||
mergeMap((selectedCandidates) =>
|
||||
combineLatest([
|
||||
of(selectedCandidates),
|
||||
queryAt.session.nextKeys.multi<OptionNimbus>(selectedCandidates).pipe(
|
||||
map((nextKeys) =>
|
||||
nextKeys.findIndex((o) =>
|
||||
o.unwrapOrDefault().nimbus.toHex() === loggedHex
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
),
|
||||
map(([selectedCandidates, index]) =>
|
||||
index === -1
|
||||
? null
|
||||
: selectedCandidates[index]
|
||||
)
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// normal operation, non-mapping
|
||||
return combineLatest([
|
||||
of(header),
|
||||
validators,
|
||||
of(null)
|
||||
]);
|
||||
}
|
||||
|
||||
export function getAuthorDetails (api: DeriveApi, header: Header, blockHash?: Uint8Array | string): Observable<[Header, Vec<AccountId> | null, AccountId | null]> {
|
||||
// For on-chain state, we need to retrieve it as per the start
|
||||
// of the block being constructed, i.e. session validators would
|
||||
// be at the point of the block construction, not when all operations
|
||||
// has been supplied.
|
||||
//
|
||||
// However for the first block (no parentHash available), we would
|
||||
// just use the as-is
|
||||
return api.queryAt(
|
||||
header.parentHash.isEmpty
|
||||
? blockHash || header.hash
|
||||
: header.parentHash
|
||||
).pipe(
|
||||
switchMap((queryAt) =>
|
||||
getAuthorDetailsWithAt(header, queryAt)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import '@pezkuwi/api-augment';
|
||||
|
||||
import { derive } from './index.js';
|
||||
|
||||
console.log(derive.chain.bestNumber);
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
import type { Collective } from './types.js';
|
||||
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { isFunction } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
export function getInstance (api: DeriveApi, section: string): DeriveApi['query']['council'] {
|
||||
const instances = api.registry.getModuleInstances(api.runtimeVersion.specName, section);
|
||||
const name = instances?.length
|
||||
? instances[0]
|
||||
: section;
|
||||
|
||||
return api.query[name as 'council'];
|
||||
}
|
||||
|
||||
export function withSection <T, F extends (...args: any[]) => Observable<T>> (section: Collective, fn: (query: DeriveApi['query']['council'], 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, (query) =>
|
||||
(): Observable<T> =>
|
||||
isFunction(query?.[method])
|
||||
? query[method]() as unknown as Observable<T>
|
||||
: of(empty)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './members.js';
|
||||
export * from './prime.js';
|
||||
export * from './proposals.js';
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { MembersFn } from './types.js';
|
||||
|
||||
import { callMethod } from './helpers.js';
|
||||
|
||||
export const members: MembersFn = /*#__PURE__*/ callMethod('members', []);
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId } from '@pezkuwi/types/interfaces';
|
||||
import type { Collective, PrimeFnRet } from './types.js';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { isFunction } from '@pezkuwi/util';
|
||||
|
||||
import { withSection } from './helpers.js';
|
||||
|
||||
export function prime (section: Collective): PrimeFnRet {
|
||||
return withSection(section, (query) =>
|
||||
(): Observable<AccountId | null> =>
|
||||
isFunction(query?.prime)
|
||||
? query.prime().pipe(
|
||||
map((o): AccountId | null =>
|
||||
o.unwrapOr(null)
|
||||
)
|
||||
)
|
||||
: of(null)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option } from '@pezkuwi/types';
|
||||
import type { Hash, Proposal, Votes } from '@pezkuwi/types/interfaces';
|
||||
import type { DeriveApi, DeriveCollectiveProposal } from '../types.js';
|
||||
import type { Collective, HasProposalsFnRet, ProposalCountFn, ProposalFnRet, ProposalHashesFn, ProposalsFnRet } from './types.js';
|
||||
|
||||
import { catchError, combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { isFunction } from '@pezkuwi/util';
|
||||
|
||||
import { firstObservable } from '../util/index.js';
|
||||
import { callMethod, withSection } from './helpers.js';
|
||||
|
||||
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 => ({
|
||||
hash: api.registry.createType('Hash', hashes[index]),
|
||||
proposal: o && o.isSome
|
||||
? o.unwrap()
|
||||
: null,
|
||||
votes: votes[index].unwrapOr(null)
|
||||
}));
|
||||
}
|
||||
|
||||
function _proposalsFrom (api: DeriveApi, query: DeriveApi['query']['council'], hashes: (Hash | Uint8Array | string)[]): Observable<DeriveCollectiveProposal[]> {
|
||||
return (isFunction(query?.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
|
||||
query.proposalOf.multi<Option<Proposal>>(hashes).pipe(
|
||||
catchError(() => of(hashes.map(() => null)))
|
||||
),
|
||||
query.voting.multi<Option<Votes>>(hashes)
|
||||
])
|
||||
: of<Result>([[], [], []])
|
||||
).pipe(
|
||||
map((r) => parse(api, r))
|
||||
);
|
||||
}
|
||||
|
||||
export function hasProposals (section: Collective): HasProposalsFnRet {
|
||||
return withSection(section, (query) =>
|
||||
(): Observable<boolean> =>
|
||||
of(isFunction(query?.proposals))
|
||||
);
|
||||
}
|
||||
|
||||
export function proposals (section: Collective): ProposalsFnRet {
|
||||
return withSection(section, (query, api) =>
|
||||
(): Observable<DeriveCollectiveProposal[]> =>
|
||||
api.derive[section as 'council'].proposalHashes().pipe(
|
||||
switchMap((all) => _proposalsFrom(api, query, all))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function proposal (section: Collective): ProposalFnRet {
|
||||
return withSection(section, (query, api) =>
|
||||
(hash: Hash | Uint8Array | string): Observable<DeriveCollectiveProposal | null> =>
|
||||
isFunction(query?.proposals)
|
||||
? firstObservable(_proposalsFrom(api, query, [hash]))
|
||||
: of(null)
|
||||
);
|
||||
}
|
||||
|
||||
export const proposalCount: ProposalCountFn = /*#__PURE__*/ callMethod('proposalCount', null);
|
||||
export const proposalHashes: ProposalHashesFn = /*#__PURE__*/ callMethod('proposals', []);
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { u32 } from '@pezkuwi/types';
|
||||
import type { AccountId, Hash } from '@pezkuwi/types/interfaces';
|
||||
import type { DeriveApi, DeriveCollectiveProposal } from '../types.js';
|
||||
|
||||
export type Collective = 'allianceMotion' | 'council' | 'membership' | 'technicalCommittee';
|
||||
|
||||
export type HasProposalsFnRet = (instanceId: string, api: DeriveApi) => () => Observable<boolean>;
|
||||
export type HasProposalsFn = (section: Collective) => HasProposalsFnRet;
|
||||
|
||||
export type MembersFnRet = (instanceId: string, api: DeriveApi) => () => Observable<AccountId[]>;
|
||||
export type MembersFn = (section: Collective) => MembersFnRet;
|
||||
|
||||
export type PrimeFnRet = (instanceId: string, api: DeriveApi) => () => Observable<AccountId | null>;
|
||||
export type PrimeFn = (section: Collective) => PrimeFnRet;
|
||||
|
||||
export type ProposalFnRet = (instanceId: string, api: DeriveApi) => (hash: Hash | Uint8Array | string) => Observable<DeriveCollectiveProposal | null>;
|
||||
export type ProposalFn = (section: Collective) => ProposalFnRet;
|
||||
|
||||
export type ProposalsFnRet = (instanceId: string, api: DeriveApi) => () => Observable<DeriveCollectiveProposal[]>;
|
||||
export type ProposalsFn = (section: Collective) => ProposalsFnRet;
|
||||
|
||||
export type ProposalCountFnRet = (instanceId: string, api: DeriveApi) => () => Observable<u32 | null>;
|
||||
export type ProposalCountFn = (section: Collective) => ProposalCountFnRet;
|
||||
|
||||
export type ProposalHashesFnRet = (instanceId: string, api: DeriveApi) => () => Observable<Hash[]>;
|
||||
export type ProposalHashesFn = (section: Collective) => ProposalHashesFnRet;
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { DeriveApi, DeriveContractFees } from '../types.js';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
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.registry.createType('Balance'),
|
||||
api.consts.contracts['rentDepositOffset'] || api.registry.createType('Balance'),
|
||||
api.consts.contracts['surchargeReward'] || api.registry.createType('Balance'),
|
||||
api.consts.contracts['tombstoneDeposit'] || api.registry.createType('Balance')
|
||||
]) 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 bizinikiwi chain spec.
|
||||
* @example
|
||||
* ```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-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './fees.js';
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2017-2025 @pezkuwi/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/index.js';
|
||||
|
||||
export * from './votes.js';
|
||||
export * from './votesOf.js';
|
||||
|
||||
/**
|
||||
* @name members
|
||||
* @description Retrieves the list of members in the "council" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const members = await api.derive.council.members();
|
||||
* console.log(`Members: ${JSON.stringify(members)});
|
||||
* ```
|
||||
*/
|
||||
export const members = /*#__PURE__*/ collectiveMembers('council');
|
||||
|
||||
/**
|
||||
* @name hasProposals
|
||||
* @description Checks if there are any active proposals in the "council" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const exists = await api.derive.council.hasProposals();
|
||||
* console.log(exists);
|
||||
* ```
|
||||
*/
|
||||
export const hasProposals = /*#__PURE__*/ collectiveHasProposals('council');
|
||||
/**
|
||||
* @name proposal
|
||||
* @description Retrieves details of a specific proposal in the "councilMotion" collective by its hash.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const proposalDetails = await api.derive.council.proposal(PROPOSAL_HASH);
|
||||
* console.log(proposalDetails);
|
||||
* ```
|
||||
*/
|
||||
export const proposal = /*#__PURE__*/ collectiveProposal('council');
|
||||
/**
|
||||
* @name proposalCount
|
||||
* @description Retrieves the total number of proposals in the "council" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const count = await api.derive.council.proposalCount();
|
||||
* console.log(`Amount of proposals: ${count}`);
|
||||
* ```
|
||||
*/
|
||||
export const proposalCount = /*#__PURE__*/ collectiveProposalCount('council');
|
||||
/**
|
||||
* @name proposalHashes
|
||||
* @description Retrieves an array of hashes for all active proposals in the "council" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const hashes = await api.derive.council.proposalHashes();
|
||||
* console.log(`Proposals ${JSON.stringify(hashes)}`);
|
||||
* ```
|
||||
*/
|
||||
export const proposalHashes = /*#__PURE__*/ collectiveProposalHashes('council');
|
||||
/**
|
||||
* @name proposals
|
||||
* @description Retrieves a list of all active proposals in the "council" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const proposals = await api.derive.council.proposals();
|
||||
* console.log(proposals);
|
||||
* ```
|
||||
*/
|
||||
export const proposals = /*#__PURE__*/ collectiveProposals('council');
|
||||
/**
|
||||
* @name prime
|
||||
* @description Retrieves the prime member of the "council" collective, if one exists.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const primeMember = await api.derive.council.prime();
|
||||
* console.log(primeMember);
|
||||
* ```
|
||||
*/
|
||||
export const prime = /*#__PURE__*/ collectivePrime('council');
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AccountId, Balance } from '@pezkuwi/types/interfaces';
|
||||
|
||||
export interface DeriveCouncilVote {
|
||||
stake: Balance;
|
||||
votes: AccountId[];
|
||||
}
|
||||
|
||||
export type DeriveCouncilVotes = [AccountId, DeriveCouncilVote][];
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { QueryableModuleStorage } from '@pezkuwi/api-base/types';
|
||||
import type { Vec } from '@pezkuwi/types';
|
||||
import type { AccountId, Balance } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletElectionsPhragmenVoter } from '@pezkuwi/types/lookup';
|
||||
import type { ITuple } from '@pezkuwi/types/types';
|
||||
import type { DeriveApi, DeriveCouncilVote, DeriveCouncilVotes } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
// Voter is current tuple is 2.x-era
|
||||
type VoteEntry = PezpalletElectionsPhragmenVoter | ITuple<[Balance, Vec<AccountId>]>;
|
||||
|
||||
function isVoter (value: VoteEntry): value is PezpalletElectionsPhragmenVoter {
|
||||
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: DeriveApi['query']['elections']): 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: DeriveApi['query']['elections']): 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: DeriveApi['query']['elections']): 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] }
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name votes
|
||||
* @description Retrieves the council election votes for all participants.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const votes = await api.derive.council.votes();
|
||||
* ```
|
||||
*/
|
||||
export function votes (instanceId: string, api: DeriveApi): () => Observable<DeriveCouncilVotes> {
|
||||
const elections = api.query.elections || api.query['phragmenElection'] || api.query['electionsPhragmen'];
|
||||
|
||||
return memo(instanceId, (): Observable<DeriveCouncilVotes> =>
|
||||
elections
|
||||
? elections['stakeOf']
|
||||
? retrievePrev(api, elections)
|
||||
: retrieveCurrent(elections)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId } from '@pezkuwi/types/interfaces';
|
||||
import type { DeriveApi, DeriveCouncilVote } from '../types.js';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name votesOf
|
||||
* @description Retrieves the council votes associated with a given account.
|
||||
* @returns The stake and the list of candidates the account has voted for.
|
||||
* @param {string | Uint8Array | AccountId} accountId The accountId to retrieve votes for.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const accountId = "5Gw3s7qQ9Z..."; // Replace with a valid account ID
|
||||
* const votes = await api.derive.council.votesOf(accountId);
|
||||
* console.log("Account votes:", votes);
|
||||
* ```
|
||||
*/
|
||||
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,57 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, u32 } from '@pezkuwi/types';
|
||||
import type { PezkuwiRuntimeCommonCrowdloanFundInfo } from '@pezkuwi/types/lookup';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { u8aConcat, u8aToHex } from '@pezkuwi/util';
|
||||
import { blake2AsU8a } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
interface AllInfo extends PezkuwiRuntimeCommonCrowdloanFundInfo {
|
||||
// previously it was named trieIndex
|
||||
trieIndex?: u32;
|
||||
}
|
||||
|
||||
function createChildKey (info: AllInfo): string {
|
||||
return u8aToHex(
|
||||
u8aConcat(
|
||||
':child_storage:default:',
|
||||
blake2AsU8a(
|
||||
u8aConcat(
|
||||
'crowdloan',
|
||||
(info.fundIndex || info.trieIndex).toU8a()
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name childKey
|
||||
* @description Retrieves the child storage key for a given teyrchain’s crowdloan contributions.
|
||||
* This key is used to access contribution data stored in a separate child trie of the blockchain’s state.
|
||||
* @param {string | number | BN} paraId The teyrchain ID for which contributions are being queried.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const childKey = await api.derive.crowdloan.childKey(3369);
|
||||
* console.log("Child Key:", childKey);
|
||||
* ```
|
||||
*/
|
||||
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<PezkuwiRuntimeCommonCrowdloanFundInfo>>(paraId).pipe(
|
||||
map((optInfo) =>
|
||||
optInfo.isSome
|
||||
? createChildKey(optInfo.unwrap())
|
||||
: null
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { StorageKey } from '@pezkuwi/types';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { DeriveApi, DeriveContributions } from '../types.js';
|
||||
|
||||
import { BehaviorSubject, combineLatest, EMPTY, map, of, startWith, switchMap, tap, toArray } from 'rxjs';
|
||||
|
||||
import { arrayFlatten, isFunction, nextTick } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
import { extractContributed } from './util.js';
|
||||
|
||||
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 subject = new BehaviorSubject<string | undefined>(undefined);
|
||||
|
||||
return subject.pipe(
|
||||
switchMap((startKey) =>
|
||||
api.rpc.childstate.getKeysPaged(childKey, '0x', PAGE_SIZE_K, startKey)
|
||||
),
|
||||
tap((keys): void => {
|
||||
nextTick((): void => {
|
||||
keys.length === PAGE_SIZE_K
|
||||
? subject.next(keys[PAGE_SIZE_K - 1].toHex())
|
||||
: subject.complete();
|
||||
});
|
||||
}),
|
||||
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)
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name contributions
|
||||
* @description Retrieves all contributions for a given teyrchain crowdloan.
|
||||
* @param {string | number | BN} paraId The teyrchain ID for which contributions are being queried.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contributions = await api.derive.crowdloan.contributions(3369);
|
||||
* console.log("Contributions:", contributions);
|
||||
* ```
|
||||
*/
|
||||
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-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './childKey.js';
|
||||
export * from './contributions.js';
|
||||
export * from './ownContributions.js';
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { DeriveApi, DeriveOwnContributions } from '../types.js';
|
||||
|
||||
import { combineLatest, EMPTY, map, of, startWith, switchMap } from 'rxjs';
|
||||
|
||||
import { objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
import { extractContributed } from './util.js';
|
||||
|
||||
function _getValues (api: DeriveApi, childKey: string, keys: string[]): Observable<DeriveOwnContributions> {
|
||||
// We actually would love to use multi-keys https://github.com/pezkuwichain/bizinikiwi/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 =>
|
||||
objectSpread(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]) =>
|
||||
objectSpread({}, all, latest)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name ownContributions
|
||||
* @description Retrieves the contribution amounts made by specific accounts (`keys`) to a given teyrchain crowdloan (`paraId`).
|
||||
* @param {string | number | BN} paraId The teyrchain ID for which contributions are being queried.
|
||||
* @param {string[]} keys An array of account addresses whose contributions are to be fetched.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contributions = await api.derive.crowdloan.ownContributions(2000, ['5Ff...PqV', '5Gg...XyZ']);
|
||||
* console.log("Own Contributions:", contributions);
|
||||
* ```
|
||||
*/
|
||||
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-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Balance } from '@pezkuwi/types/interfaces';
|
||||
|
||||
export interface DeriveContributions {
|
||||
blockHash: string;
|
||||
contributorsHex: string[];
|
||||
}
|
||||
|
||||
export type DeriveOwnContributions = Record<string, Balance>;
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PezframeSystemEventRecord } from '@pezkuwi/types/lookup';
|
||||
import type { Vec } from '@pezkuwi/types-codec';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
|
||||
interface Changes {
|
||||
added: string[];
|
||||
blockHash: string;
|
||||
removed: string[];
|
||||
}
|
||||
|
||||
export function extractContributed (paraId: string | number | BN, events: Vec<PezframeSystemEventRecord>): 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,158 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Bytes, Option, u8, u32, Vec } from '@pezkuwi/types';
|
||||
import type { BlockNumber, Call, Hash, ReferendumIndex, Scheduled } from '@pezkuwi/types/interfaces';
|
||||
import type { PezframeSupportPreimagesBounded, PezpalletSchedulerScheduled } from '@pezkuwi/types/lookup';
|
||||
import type { Codec, ITuple } from '@pezkuwi/types/types';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { DeriveApi, DeriveDispatch, DeriveProposalImage } from '../types.js';
|
||||
|
||||
import { catchError, combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { Enum } from '@pezkuwi/types';
|
||||
import { isFunction, objectSpread, stringToHex } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
import { getImageHashBounded } from './util.js';
|
||||
|
||||
const DEMOCRACY_ID = stringToHex('democrac');
|
||||
|
||||
// included here for backwards compat
|
||||
interface PezpalletSchedulerScheduledV3 extends Codec {
|
||||
maybeId: Option<Bytes>;
|
||||
priority: u8;
|
||||
call: PezframeSupportScheduleMaybeHashed;
|
||||
maybePeriodic: Option<ITuple<[u32, u32]>>;
|
||||
origin: Codec;
|
||||
}
|
||||
|
||||
// included here for backwards compat
|
||||
interface PezframeSupportScheduleMaybeHashed extends Codec {
|
||||
isHash: boolean;
|
||||
isValue: boolean;
|
||||
asValue: Call;
|
||||
asHash: Hash;
|
||||
}
|
||||
|
||||
interface SchedulerInfo {
|
||||
at: BlockNumber;
|
||||
imageHash: HexString;
|
||||
index: ReferendumIndex;
|
||||
}
|
||||
|
||||
function isMaybeHashedOrBounded (call: PezframeSupportPreimagesBounded | PezframeSupportScheduleMaybeHashed | Call): call is PezframeSupportScheduleMaybeHashed | PezframeSupportPreimagesBounded {
|
||||
// check for enum
|
||||
return call instanceof Enum;
|
||||
}
|
||||
|
||||
function isBounded (call: PezframeSupportPreimagesBounded | PezframeSupportScheduleMaybeHashed): call is PezframeSupportPreimagesBounded {
|
||||
// check for type
|
||||
return (call as PezframeSupportPreimagesBounded).isInline || (call as PezframeSupportPreimagesBounded).isLegacy || (call as PezframeSupportPreimagesBounded).isLookup;
|
||||
}
|
||||
|
||||
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: getImageHashBounded(imageHash),
|
||||
index
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function schedulerEntries (api: DeriveApi): Observable<[BlockNumber[], Option<PezpalletSchedulerScheduled | PezpalletSchedulerScheduledV3 | Scheduled>[][]]> {
|
||||
// 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
|
||||
api.query.scheduler.agenda.multi(blockNumbers).pipe(
|
||||
catchError(() => of(blockNumbers.map(() => [])))
|
||||
)
|
||||
])
|
||||
: of<[BlockNumber[], Option<PezpalletSchedulerScheduledV3 | Scheduled>[][]]>([[], []]);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (id.startsWith(DEMOCRACY_ID)) {
|
||||
const imageHash = isMaybeHashedOrBounded(scheduled.call)
|
||||
? isBounded(scheduled.call)
|
||||
? getImageHashBounded(scheduled.call)
|
||||
: scheduled.call.isHash
|
||||
? scheduled.call.asHash.toHex()
|
||||
: scheduled.call.asValue.args[0].toHex()
|
||||
: scheduled.call.args[0].toHex();
|
||||
|
||||
result.push({ at, imageHash, 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) => objectSpread({ image: images[index] }, info))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name dispatchQueue
|
||||
* @description Retrieves the list of scheduled or pending dispatches in the governance system.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const queue = await api.derive.democracy.dispatchQueue();
|
||||
* console.log("Dispatch Queue:", queue);
|
||||
* ```
|
||||
*/
|
||||
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,14 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './dispatchQueue.js';
|
||||
export * from './locks.js';
|
||||
export * from './nextExternal.js';
|
||||
export * from './preimages.js';
|
||||
export * from './proposals.js';
|
||||
export * from './referendumIds.js';
|
||||
export * from './referendums.js';
|
||||
export * from './referendumsActive.js';
|
||||
export * from './referendumsFinished.js';
|
||||
export * from './referendumsInfo.js';
|
||||
export * from './sqrtElectorate.js';
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId, ReferendumInfoTo239, Vote } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletDemocracyReferendumInfo, PezpalletDemocracyVoteVoting } from '@pezkuwi/types/lookup';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { DeriveApi, DeriveDemocracyLock } from '../types.js';
|
||||
|
||||
import { map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { BN_ZERO, isUndefined } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
type ReferendumInfoFinished = PezpalletDemocracyReferendumInfo['asFinished'];
|
||||
type VotingDelegating = PezpalletDemocracyVoteVoting['asDelegating'];
|
||||
type VotingDirect = PezpalletDemocracyVoteVoting['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: PezpalletDemocracyReferendumInfo): 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, PezpalletDemocracyReferendumInfo | ReferendumInfoTo239 | null] =>
|
||||
[vote, referendums[index].unwrapOr(null)]
|
||||
)
|
||||
.filter((item): item is [VotingDirectVote, PezpalletDemocracyReferendumInfo] =>
|
||||
!!item[1] && isUndefined((item[1] as ReferendumInfoTo239).end) && item[0][1].isStandard
|
||||
)
|
||||
.map(([directVote, referendum]) =>
|
||||
parseLock(api, directVote, referendum)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name locks
|
||||
* @description Retrieves the democracy voting locks for a given account.
|
||||
* @param { string | AccountId } accountId The accountId for which to retrieve democracy voting locks.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const locks = await api.derive.democracy.locks('5FfFjX...'); // Replace with an actual accountId
|
||||
* console.log("Democracy Locks:", locks);
|
||||
* ```
|
||||
*/
|
||||
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,49 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option } from '@pezkuwi/types';
|
||||
import type { H256 } from '@pezkuwi/types/interfaces';
|
||||
import type { PezframeSupportPreimagesBounded, PezpalletDemocracyVoteThreshold } from '@pezkuwi/types/lookup';
|
||||
import type { ITuple } from '@pezkuwi/types/types';
|
||||
import type { DeriveApi, DeriveProposalExternal } from '../types.js';
|
||||
|
||||
import { map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
import { getImageHashBounded } from './util.js';
|
||||
|
||||
function withImage (api: DeriveApi, nextOpt: Option<ITuple<[H256 | PezframeSupportPreimagesBounded, PezpalletDemocracyVoteThreshold]>>): Observable<DeriveProposalExternal | null> {
|
||||
if (nextOpt.isNone) {
|
||||
return of(null);
|
||||
}
|
||||
|
||||
const [hash, threshold] = nextOpt.unwrap();
|
||||
|
||||
return api.derive.democracy.preimage(hash).pipe(
|
||||
map((image): DeriveProposalExternal => ({
|
||||
image,
|
||||
imageHash: getImageHashBounded(hash),
|
||||
threshold
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name nextExternal
|
||||
* @description Retrieves the next external proposal that is scheduled for a referendum.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const nextExternal = await api.derive.democracy.nextExternal();
|
||||
* console.log("Next external proposal:", nextExternal);
|
||||
* ```
|
||||
*/
|
||||
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,171 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { u128 } from '@pezkuwi/types';
|
||||
import type { AccountId, AccountId32, Balance, BlockNumber, Call, Hash, PreimageStatus } from '@pezkuwi/types/interfaces';
|
||||
import type { PezframeSupportPreimagesBounded, PezpalletPreimageOldRequestStatus, PezpalletPreimageRequestStatus } from '@pezkuwi/types/lookup';
|
||||
import type { Bytes, Option } from '@pezkuwi/types-codec';
|
||||
import type { ITuple } from '@pezkuwi/types-codec/types';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { DeriveApi, DeriveProposalImage } from '../types.js';
|
||||
|
||||
import { map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { BN_ZERO, isFunction } from '@pezkuwi/util';
|
||||
|
||||
import { firstMemo, memo } from '../util/index.js';
|
||||
import { getImageHashBounded } from './util.js';
|
||||
|
||||
type PreimageInfo = [Bytes, AccountId, Balance, BlockNumber];
|
||||
type OldPreimage = ITuple<PreimageInfo>;
|
||||
type CompatStatusU = PezpalletPreimageRequestStatus['asUnrequested'] & { deposit: ITuple<[AccountId32, u128]> };
|
||||
type CompatStatusR = PezpalletPreimageRequestStatus['asRequested'] & { deposit: Option<ITuple<[AccountId32, u128]>> };
|
||||
|
||||
function getUnrequestedTicket (status: PezpalletPreimageRequestStatus['asUnrequested']): [AccountId32, u128] {
|
||||
return status.ticket || (status as CompatStatusU).deposit;
|
||||
}
|
||||
|
||||
function getRequestedTicket (status: PezpalletPreimageRequestStatus['asRequested']): [AccountId32, u128] {
|
||||
return (status.maybeTicket || (status as CompatStatusR).deposit).unwrapOrDefault();
|
||||
}
|
||||
|
||||
function isDemocracyPreimage (api: DeriveApi, imageOpt: Option<OldPreimage> | Option<PreimageStatus>): imageOpt is Option<PreimageStatus> {
|
||||
return !!imageOpt && !api.query.democracy['dispatchQueue'];
|
||||
}
|
||||
|
||||
function constructProposal (api: DeriveApi, [bytes, proposer, balance, at]: PreimageInfo): DeriveProposalImage {
|
||||
let proposal: Call | undefined;
|
||||
|
||||
try {
|
||||
proposal = api.registry.createType('Call', bytes.toU8a(true));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return { at, balance, proposal, proposer };
|
||||
}
|
||||
|
||||
function parseDemocracy (api: DeriveApi, imageOpt: Option<OldPreimage> | Option<PreimageStatus>): DeriveProposalImage | undefined {
|
||||
if (imageOpt.isNone) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDemocracyPreimage(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());
|
||||
}
|
||||
|
||||
function parseImage (api: DeriveApi, [proposalHash, status, bytes]: [HexString, PezpalletPreimageRequestStatus | PezpalletPreimageOldRequestStatus | null, Bytes | null]): DeriveProposalImage | undefined {
|
||||
if (!status) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const [proposer, balance] = status.isUnrequested
|
||||
? getUnrequestedTicket((status as PezpalletPreimageRequestStatus).asUnrequested)
|
||||
: getRequestedTicket((status as PezpalletPreimageRequestStatus).asRequested);
|
||||
let proposal: Call | undefined;
|
||||
|
||||
if (bytes) {
|
||||
try {
|
||||
proposal = api.registry.createType('Call', bytes.toU8a(true));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return { at: BN_ZERO, balance, proposal, proposalHash, proposer };
|
||||
}
|
||||
|
||||
function getDemocracyImages (api: DeriveApi, bounded: (Hash | Uint8Array | string | PezframeSupportPreimagesBounded)[]): Observable<(DeriveProposalImage | undefined)[]> {
|
||||
const hashes = bounded.map((b) => getImageHashBounded(b));
|
||||
|
||||
return api.query.democracy['preimages'].multi<Option<PreimageStatus>>(hashes).pipe(
|
||||
map((images): (DeriveProposalImage | undefined)[] =>
|
||||
images.map((imageOpt) => parseDemocracy(api, imageOpt))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function getImages (api: DeriveApi, bounded: (PezframeSupportPreimagesBounded | Uint8Array | string)[]): Observable<(DeriveProposalImage | undefined)[]> {
|
||||
const hashes = bounded.map((b) => getImageHashBounded(b));
|
||||
const bytesType = api.registry.lookup.getTypeDef(api.query.preimage.preimageFor.creator.meta.type.asMap.key).type;
|
||||
|
||||
return api.query.preimage.statusFor.multi(hashes).pipe(
|
||||
switchMap((optStatus) => {
|
||||
const statuses = optStatus.map((o) => o.unwrapOr(null));
|
||||
const keys = statuses
|
||||
.map((s, i) =>
|
||||
s
|
||||
? bytesType === 'H256'
|
||||
// first generation
|
||||
? hashes[i]
|
||||
// current generation (H256,u32)
|
||||
: s.isRequested
|
||||
? [hashes[i], s.asRequested.len.unwrapOr(0)]
|
||||
: [hashes[i], s.asUnrequested.len]
|
||||
: null
|
||||
)
|
||||
.filter((p) => !!p);
|
||||
|
||||
return api.query.preimage.preimageFor.multi(keys).pipe(
|
||||
map((optBytes) => {
|
||||
let ptr = -1;
|
||||
|
||||
return statuses
|
||||
.map((s, i): [HexString, PezpalletPreimageRequestStatus | PezpalletPreimageOldRequestStatus | null, Bytes | null] =>
|
||||
s
|
||||
? [hashes[i], s, optBytes[++ptr].unwrapOr(null)]
|
||||
: [hashes[i], null, null]
|
||||
)
|
||||
.map((v) => parseImage(api, v));
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name preimages
|
||||
* @description Retrieves the full details (preimages) of governance proposals using their on-chain hashes.
|
||||
* @param { (Hash | Uint8Array | string | PezframeSupportPreimagesBounded)[] } hashes An array of hashes representing governance proposals.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const preimages = await api.derive.democracy.preimages([HASH1, HASH2]);
|
||||
* ```
|
||||
*/
|
||||
export function preimages (instanceId: string, api: DeriveApi): (hashes: (Hash | Uint8Array | string | PezframeSupportPreimagesBounded)[]) => Observable<(DeriveProposalImage | undefined)[]> {
|
||||
return memo(instanceId, (hashes: (Hash | Uint8Array | string | PezframeSupportPreimagesBounded)[]): Observable<(DeriveProposalImage | undefined)[]> =>
|
||||
hashes.length
|
||||
? isFunction(api.query.democracy['preimages'])
|
||||
? getDemocracyImages(api, hashes)
|
||||
: isFunction(api.query.preimage.preimageFor)
|
||||
? getImages(api, hashes)
|
||||
: of([])
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name preimage
|
||||
* @description Retrieves the full details (preimage) of a governance proposal using its on-chain hash.
|
||||
* @param { Hash | Uint8Array | string | PezframeSupportPreimagesBounded } hash Hash that represents governance proposals.
|
||||
* * @example
|
||||
* ```javascript
|
||||
* const preimage = await api.derive.democracy.preimage(HASH);
|
||||
* ```
|
||||
*/
|
||||
export const preimage = /*#__PURE__*/ firstMemo(
|
||||
(api: DeriveApi, hash: Hash | Uint8Array | string | PezframeSupportPreimagesBounded) =>
|
||||
api.derive.democracy.preimages([hash])
|
||||
);
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, Vec } from '@pezkuwi/types';
|
||||
import type { AccountId, Balance, Hash, PropIndex } from '@pezkuwi/types/interfaces';
|
||||
import type { PezframeSupportPreimagesBounded } from '@pezkuwi/types/lookup';
|
||||
import type { ITuple } from '@pezkuwi/types/types';
|
||||
import type { DeriveApi, DeriveProposal, DeriveProposalImage } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { isFunction, objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
import { getImageHashBounded } from './util.js';
|
||||
|
||||
type DepositorsNew = Option<ITuple<[Vec<AccountId>, Balance]>>;
|
||||
type DepositorsOld = Option<ITuple<[Balance, Vec<AccountId>]>>;
|
||||
type Depositors = DepositorsNew | DepositorsOld;
|
||||
type Proposals = ITuple<[PropIndex, Hash | PezframeSupportPreimagesBounded, 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...
|
||||
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, hash, proposer], proposalIndex): DeriveProposal => {
|
||||
const depositors = optDepositors[proposalIndex].unwrap();
|
||||
|
||||
return objectSpread(
|
||||
{
|
||||
image: images[proposalIndex],
|
||||
imageHash: getImageHashBounded(hash),
|
||||
index,
|
||||
proposer
|
||||
},
|
||||
isNewDepositors(depositors)
|
||||
? { balance: depositors[1], seconds: depositors[0] }
|
||||
: { balance: depositors[0], seconds: depositors[1] }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name proposals
|
||||
* @description Retrieves the list of active public proposals in the democracy module, along with their associated preimage data and deposit information.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const proposals = await api.derive.democracy.proposals();
|
||||
* console.log("proposals:", proposals);
|
||||
* ```
|
||||
*/
|
||||
export function proposals (instanceId: string, api: DeriveApi): () => Observable<DeriveProposal[]> {
|
||||
return memo(instanceId, (): Observable<DeriveProposal[]> =>
|
||||
isFunction(api.query.democracy?.publicProps)
|
||||
? 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,37 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { ReferendumIndex } from '@pezkuwi/types/interfaces';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name referendumIds
|
||||
* @description Retrieves an array of active referendum IDs.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const referendums = await api.derive.democracy.referendumIds();
|
||||
* ```
|
||||
*/
|
||||
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,39 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { DeriveApi, DeriveReferendumExt } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name referendums
|
||||
* @description Retrieves information about all active referendums, including their details and associated votes.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const referendums = await api.derive.democracy.referendums();
|
||||
* ```
|
||||
*/
|
||||
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 =>
|
||||
objectSpread({}, referendum, votes[index])
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { DeriveApi, DeriveReferendum } from '../types.js';
|
||||
|
||||
import { of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name referendumsActive
|
||||
* @description Retrieves information about active referendums.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const referendums = await api.derive.democracy.referendumsActive();
|
||||
* console.log("Active Referendums:", referendums);
|
||||
* ```
|
||||
*/
|
||||
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,37 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { PezpalletDemocracyReferendumInfo } from '@pezkuwi/types/lookup';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { map, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
type ReferendumInfoFinished = PezpalletDemocracyReferendumInfo['asFinished'];
|
||||
|
||||
/**
|
||||
* @name referendumsFinished
|
||||
* @description Retrieves information about finished referendums.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const referendums = await api.derive.democracy.referendumsFinished();
|
||||
* console.log("Finished Referendums:", referendums);
|
||||
* ```
|
||||
*/
|
||||
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 PezpalletDemocracyReferendumInfo => !!info && info.isFinished)
|
||||
.map((info) => info.asFinished)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, Vec } from '@pezkuwi/types';
|
||||
import type { AccountId, Hash, ReferendumInfoTo239, Vote } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletDemocracyReferendumInfo, PezpalletDemocracyReferendumStatus, PezpalletDemocracyVoteVoting } from '@pezkuwi/types/lookup';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { DeriveApi, DeriveBalancesAccount, DeriveReferendum, DeriveReferendumVote, DeriveReferendumVotes } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { isFunction, objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
import { calcVotes, getImageHash, getStatus } from './util.js';
|
||||
|
||||
type VotingDelegating = PezpalletDemocracyVoteVoting['asDelegating'];
|
||||
type VotingDirect = PezpalletDemocracyVoteVoting['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, PezpalletDemocracyVoteVoting][], 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(
|
||||
objectSpread({
|
||||
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, PezpalletDemocracyVoteVoting] => [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<PezpalletDemocracyReferendumInfo | ReferendumInfoTo239>) => Observable<DeriveReferendum | null> {
|
||||
return memo(instanceId, (index: BN, info: Option<PezpalletDemocracyReferendumInfo | ReferendumInfoTo239>): Observable<DeriveReferendum | null> => {
|
||||
const status = getStatus(info);
|
||||
|
||||
return status
|
||||
? api.derive.democracy.preimage(
|
||||
(status as PezpalletDemocracyReferendumStatus).proposal ||
|
||||
(status as unknown as { proposalHash: Hash }).proposalHash
|
||||
).pipe(
|
||||
map((image): DeriveReferendum => ({
|
||||
image,
|
||||
imageHash: getImageHash(status),
|
||||
index: api.registry.createType('ReferendumIndex', index),
|
||||
status
|
||||
}))
|
||||
)
|
||||
: of(null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name referendumsInfo
|
||||
* @description Retrieves information about multiple referendums by their IDs.
|
||||
* @param {BN[]} ids An array of referendum IDs to query.
|
||||
* @example
|
||||
* ```javascript
|
||||
* import { BN } from "@pezkuwi/util";
|
||||
*
|
||||
* const referendumIds = [new BN(1)];
|
||||
* const referendums = await api.derive.democracy.referendumsInfo(referendumIds);
|
||||
* console.log("Referendums Info:", referendums);
|
||||
* ```
|
||||
*/
|
||||
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((r): r is DeriveReferendum => !!r)
|
||||
)
|
||||
)
|
||||
: of([])
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { bnSqrt } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name sqrtElectorate
|
||||
* @description Computes the square root of the total token issuance in the network.
|
||||
* @example
|
||||
* ```javascript
|
||||
* let sqrtElectorate = await api.derive.democracy.sqrtElectorate();
|
||||
* console.log("Square root of token issuance:", sqrtElectorate);
|
||||
* ```
|
||||
*/
|
||||
export function sqrtElectorate (instanceId: string, api: DeriveApi): () => Observable<BN> {
|
||||
return memo(instanceId, (): Observable<BN> =>
|
||||
api.query.balances.totalIssuance().pipe(
|
||||
map(bnSqrt)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Vec } from '@pezkuwi/types';
|
||||
import type { AccountId, Balance, Call, Hash, PropIndex, ReferendumIndex, ReferendumInfoTo239, Vote } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletDemocracyReferendumStatus, PezpalletDemocracyVoteThreshold } from '@pezkuwi/types/lookup';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
|
||||
export interface AtBlock {
|
||||
at: BN;
|
||||
}
|
||||
|
||||
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?: Call | undefined;
|
||||
proposalHash?: HexString;
|
||||
proposalLen?: number;
|
||||
proposer: AccountId;
|
||||
}
|
||||
|
||||
export interface DeriveDispatch extends AtBlock {
|
||||
index: ReferendumIndex;
|
||||
imageHash: HexString;
|
||||
image?: DeriveProposalImage | undefined;
|
||||
}
|
||||
|
||||
export interface DeriveProposal {
|
||||
balance?: Balance;
|
||||
index: PropIndex;
|
||||
image?: DeriveProposalImage | undefined;
|
||||
imageHash: Hash;
|
||||
proposer: AccountId;
|
||||
seconds: Vec<AccountId>;
|
||||
}
|
||||
|
||||
export interface DeriveProposalExternal {
|
||||
image?: DeriveProposalImage | undefined;
|
||||
imageHash: HexString;
|
||||
threshold: PezpalletDemocracyVoteThreshold;
|
||||
}
|
||||
|
||||
export interface DeriveReferendum {
|
||||
index: ReferendumIndex;
|
||||
image?: DeriveProposalImage | undefined;
|
||||
imageHash: HexString;
|
||||
status: PezpalletDemocracyReferendumStatus | 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,166 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Hash, ReferendumInfoTo239, Tally } from '@pezkuwi/types/interfaces';
|
||||
import type { PezframeSupportPreimagesBounded, PezpalletDemocracyReferendumInfo, PezpalletDemocracyReferendumStatus, PezpalletDemocracyVoteThreshold } from '@pezkuwi/types/lookup';
|
||||
import type { Option } from '@pezkuwi/types-codec';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { DeriveReferendum, DeriveReferendumVote, DeriveReferendumVotes, DeriveReferendumVoteState } from '../types.js';
|
||||
|
||||
import { BN, bnSqrt, isHex, isString, isU8a, objectSpread, stringToHex, u8aToHex } from '@pezkuwi/util';
|
||||
|
||||
interface ApproxState {
|
||||
votedAye: BN;
|
||||
votedNay: BN;
|
||||
votedTotal: BN;
|
||||
}
|
||||
|
||||
function isOldInfo (info: PezpalletDemocracyReferendumInfo | ReferendumInfoTo239): info is ReferendumInfoTo239 {
|
||||
return !!(info as ReferendumInfoTo239).proposalHash;
|
||||
}
|
||||
|
||||
function isCurrentStatus (status: PezpalletDemocracyReferendumStatus | ReferendumInfoTo239): status is PezpalletDemocracyReferendumStatus {
|
||||
return !!(status as PezpalletDemocracyReferendumStatus).tally;
|
||||
}
|
||||
|
||||
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: PezpalletDemocracyVoteThreshold, 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: PezpalletDemocracyVoteThreshold, 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 objectSpread({}, state, {
|
||||
isPassing: calcPassing(referendum.status.threshold, sqrtElectorate, state),
|
||||
votes
|
||||
});
|
||||
}
|
||||
|
||||
export function getStatus (info: Option<PezpalletDemocracyReferendumInfo | ReferendumInfoTo239>): PezpalletDemocracyReferendumStatus | ReferendumInfoTo239 | null {
|
||||
if (info.isNone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const unwrapped = info.unwrap();
|
||||
|
||||
return isOldInfo(unwrapped)
|
||||
? unwrapped
|
||||
: unwrapped.isOngoing
|
||||
? unwrapped.asOngoing
|
||||
// done, we don't include it here... only currently active
|
||||
: null;
|
||||
}
|
||||
|
||||
export function getImageHashBounded (hash: Uint8Array | string | Hash | PezframeSupportPreimagesBounded): HexString {
|
||||
return (hash as PezframeSupportPreimagesBounded).isLegacy
|
||||
? (hash as PezframeSupportPreimagesBounded).asLegacy.hash_.toHex()
|
||||
: (hash as PezframeSupportPreimagesBounded).isLookup
|
||||
? (hash as PezframeSupportPreimagesBounded).asLookup.hash_.toHex()
|
||||
// for inline, use the actual Bytes hash
|
||||
: (hash as PezframeSupportPreimagesBounded).isInline
|
||||
? (hash as PezframeSupportPreimagesBounded).asInline.hash.toHex()
|
||||
: isString(hash)
|
||||
? isHex(hash)
|
||||
? hash
|
||||
: stringToHex(hash)
|
||||
: isU8a(hash)
|
||||
? u8aToHex(hash)
|
||||
: hash.toHex();
|
||||
}
|
||||
|
||||
export function getImageHash (status: PezpalletDemocracyReferendumStatus | ReferendumInfoTo239): HexString {
|
||||
return getImageHashBounded(
|
||||
(status as PezpalletDemocracyReferendumStatus).proposal ||
|
||||
(status as ReferendumInfoTo239).proposalHash
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AnyFunction } from '@pezkuwi/types/types';
|
||||
|
||||
import * as accounts from './accounts/index.js';
|
||||
import * as alliance from './alliance/index.js';
|
||||
import * as bagsList from './bagsList/index.js';
|
||||
import * as balances from './balances/index.js';
|
||||
import * as bounties from './bounties/index.js';
|
||||
import * as chain from './chain/index.js';
|
||||
import * as contracts from './contracts/index.js';
|
||||
import * as council from './council/index.js';
|
||||
import * as crowdloan from './crowdloan/index.js';
|
||||
import * as democracy from './democracy/index.js';
|
||||
import * as elections from './elections/index.js';
|
||||
import * as imOnline from './imOnline/index.js';
|
||||
import * as membership from './membership/index.js';
|
||||
import * as session from './session/index.js';
|
||||
import * as society from './society/index.js';
|
||||
import * as staking from './staking/index.js';
|
||||
import * as technicalCommittee from './technicalCommittee/index.js';
|
||||
import * as teyrchains from './teyrchains/index.js';
|
||||
import * as treasury from './treasury/index.js';
|
||||
import * as tx from './tx/index.js';
|
||||
|
||||
export const derive = { accounts, alliance, bagsList, balances, bounties, chain, contracts, council, crowdloan, democracy, elections, imOnline, membership, teyrchains, 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]>
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ExactDerive extends DeriveAllSections<typeof derive> {
|
||||
// keep empty, allows for augmentation
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './info.js';
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { u32, Vec } from '@pezkuwi/types';
|
||||
import type { AccountId32, Balance, BlockNumber } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletElectionsPhragmenSeatHolder } from '@pezkuwi/types/lookup';
|
||||
import type { ITuple } from '@pezkuwi/types/types';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
import type { DeriveElectionsInfo } from './types.js';
|
||||
|
||||
import { combineLatest, map, of } from 'rxjs';
|
||||
|
||||
import { objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
// SeatHolder is current tuple is 2.x-era Bizinikiwi
|
||||
type Member = PezpalletElectionsPhragmenSeatHolder | ITuple<[AccountId32, Balance]>;
|
||||
|
||||
type Candidate = AccountId32 | ITuple<[AccountId32, Balance]>;
|
||||
|
||||
function isSeatHolder (value: Member): value is PezpalletElectionsPhragmenSeatHolder {
|
||||
return !Array.isArray(value);
|
||||
}
|
||||
|
||||
function isCandidateTuple (value: Candidate): value is ITuple<[AccountId32, Balance]> {
|
||||
return Array.isArray(value);
|
||||
}
|
||||
|
||||
function getAccountTuple (value: Member): [AccountId32, Balance] {
|
||||
return isSeatHolder(value)
|
||||
? [value.who, value.stake]
|
||||
: value;
|
||||
}
|
||||
|
||||
function getCandidate (value: Candidate): AccountId32 {
|
||||
return isCandidateTuple(value)
|
||||
? value[0]
|
||||
: value;
|
||||
}
|
||||
|
||||
function sortAccounts ([, balanceA]: [AccountId32, Balance], [, balanceB]: [AccountId32, Balance]): number {
|
||||
return balanceB.cmp(balanceA);
|
||||
}
|
||||
|
||||
function getConstants (api: DeriveApi, elections: string | null): Partial<DeriveElectionsInfo> {
|
||||
return elections
|
||||
? {
|
||||
candidacyBond: api.consts[elections as 'elections'].candidacyBond as Balance,
|
||||
desiredRunnersUp: api.consts[elections as 'elections'].desiredRunnersUp as u32,
|
||||
desiredSeats: api.consts[elections as 'elections'].desiredMembers as u32,
|
||||
termDuration: api.consts[elections as 'elections'].termDuration as BlockNumber,
|
||||
votingBond: api.consts[elections as 'elections']['votingBond'] as Balance,
|
||||
votingBondBase: api.consts[elections as 'elections'].votingBondBase as Balance,
|
||||
votingBondFactor: api.consts[elections as 'elections'].votingBondFactor as Balance
|
||||
}
|
||||
: {};
|
||||
}
|
||||
|
||||
function getModules (api: DeriveApi): [string, string | null] {
|
||||
const [council] = api.registry.getModuleInstances(api.runtimeVersion.specName, 'council') || ['council'];
|
||||
const elections = api.query['phragmenElection']
|
||||
? 'phragmenElection'
|
||||
: api.query['electionsPhragmen']
|
||||
? 'electionsPhragmen'
|
||||
: api.query.elections
|
||||
? 'elections'
|
||||
: null;
|
||||
// In some cases council here can refer to `generalCouncil` depending on what the chain specific override is.
|
||||
// Therefore, we check to see if it exists in the query field. If it does not we default to `council`.
|
||||
const resolvedCouncil = api.query[council] ? council : 'council';
|
||||
|
||||
return [resolvedCouncil, elections];
|
||||
}
|
||||
|
||||
function queryAll (api: DeriveApi, council: string, elections: string): Observable<[AccountId32[], Candidate[], Member[], Member[]]> {
|
||||
return api.queryMulti<[Vec<AccountId32>, Vec<Candidate>, Vec<Member>, Vec<Member>]>([
|
||||
api.query[council as 'council'].members,
|
||||
api.query[elections as 'elections'].candidates,
|
||||
api.query[elections as 'elections'].members,
|
||||
api.query[elections as 'elections'].runnersUp
|
||||
]);
|
||||
}
|
||||
|
||||
function queryCouncil (api: DeriveApi, council: string): Observable<[AccountId32[], Candidate[], Member[], Member[]]> {
|
||||
return combineLatest([
|
||||
api.query[council as 'council'].members(),
|
||||
of<Candidate[]>([]),
|
||||
of<Member[]>([]),
|
||||
of<Member[]>([])
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name info
|
||||
* @description An object containing the combined results of the storage queries for all relevant election module properties.
|
||||
* @example
|
||||
* ```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
|
||||
? queryAll(api, council, elections)
|
||||
: queryCouncil(api, council)
|
||||
).pipe(
|
||||
map(([councilMembers, candidates, members, runnersUp]): DeriveElectionsInfo =>
|
||||
objectSpread({}, 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): [AccountId32, Balance] => [a, api.registry.createType('Balance')]),
|
||||
runnersUp: runnersUp.map(getAccountTuple).sort(sortAccounts)
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { u32 } from '@pezkuwi/types';
|
||||
import type { AccountId, Balance, BlockNumber, SetIndex, VoteIndex } from '@pezkuwi/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;
|
||||
votingBondBase?: Balance;
|
||||
votingBondFactor?: Balance;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './receivedHeartbeats.js';
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, u32 } from '@pezkuwi/types';
|
||||
import type { AccountId } from '@pezkuwi/types/interfaces';
|
||||
import type { Codec } from '@pezkuwi/types/types';
|
||||
import type { DeriveApi, DeriveHeartbeats } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { BN_ZERO } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
type Result = [DeriveHeartbeats, AccountId[], Option<Codec>[], u32[]];
|
||||
|
||||
function mapResult ([result, validators, heartbeats, numBlocks]: Result): 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name receivedHeartbeats
|
||||
* @description Return a boolean array indicating whether the passed accounts had received heartbeats in the current session.
|
||||
* @example
|
||||
* ```javascript
|
||||
* let unsub = await api.derive.imOnline.receivedHeartbeats((heartbeat) => {
|
||||
* console.log(heartbeat);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
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<Result> =>
|
||||
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,103 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
|
||||
import type { ExactDerive } from './index.js';
|
||||
|
||||
import { from, Observable } from 'rxjs';
|
||||
|
||||
import { ApiRx } from '@pezkuwi/api';
|
||||
import { MockProvider } from '@pezkuwi/rpc-provider/mock';
|
||||
import { TypeRegistry } from '@pezkuwi/types/create';
|
||||
|
||||
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 as string}`, (): 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);
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
function waitReady (api: ApiRx): Promise<ApiRx> {
|
||||
return new Promise<ApiRx>((resolve) =>
|
||||
api.isReady.subscribe((api) => resolve(api))
|
||||
);
|
||||
}
|
||||
|
||||
describe('derive', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
describe('builtin', (): void => {
|
||||
const api = new ApiRx({ provider: new MockProvider(registry), registry });
|
||||
|
||||
beforeAll(async () => {
|
||||
await waitReady(api);
|
||||
});
|
||||
afterAll(() => 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', '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(async () => {
|
||||
await waitReady(api);
|
||||
});
|
||||
afterAll(() => 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,6 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import './packageDetect.js';
|
||||
|
||||
export * from './bundle.js';
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2017-2025 @pezkuwi/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/index.js';
|
||||
|
||||
/**
|
||||
* @name members
|
||||
* @description Retrieves the list of members in the "membership" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const members = await api.derive.membership.members();
|
||||
* console.log(`Members: ${JSON.stringify(members)});
|
||||
* ```
|
||||
*/
|
||||
export const members = /*#__PURE__*/ collectiveMembers('membership');
|
||||
|
||||
/**
|
||||
* @name hasProposals
|
||||
* @description Checks if there are any active proposals in the "membership" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const exists = await api.derive.membership.hasProposals();
|
||||
* console.log(exists);
|
||||
* ```
|
||||
*/
|
||||
export const hasProposals = /*#__PURE__*/ collectiveHasProposals('membership');
|
||||
/**
|
||||
* @name proposal
|
||||
* @description Retrieves details of a specific proposal in the "membership" collective by its hash.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const proposalDetails = await api.derive.membership.proposal(PROPOSAL_HASH);
|
||||
* console.log(proposalDetails);
|
||||
* ```
|
||||
*/
|
||||
export const proposal = /*#__PURE__*/ collectiveProposal('membership');
|
||||
/**
|
||||
* @name proposalCount
|
||||
* @description Retrieves the total number of proposals in the "membership" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const count = await api.derive.membership.proposalCount();
|
||||
* console.log(`Amount of proposals: ${count}`);
|
||||
* ```
|
||||
*/
|
||||
export const proposalCount = /*#__PURE__*/ collectiveProposalCount('membership');
|
||||
/**
|
||||
* @name proposalHashes
|
||||
* @description Retrieves an array of hashes for all active proposals in the "membership" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const hashes = await api.derive.membership.proposalHashes();
|
||||
* console.log(`Proposals ${JSON.stringify(hashes)}`);
|
||||
* ```
|
||||
*/
|
||||
export const proposalHashes = /*#__PURE__*/ collectiveProposalHashes('membership');
|
||||
/**
|
||||
* @name proposals
|
||||
* @description Retrieves a list of all active proposals in the "membership" collective.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const proposals = await api.derive.membership.proposals();
|
||||
* console.log(proposals);
|
||||
* ```
|
||||
*/
|
||||
export const proposals = /*#__PURE__*/ collectiveProposals('membership');
|
||||
/**
|
||||
* @name prime
|
||||
* @description Retrieves the prime member of the "membership" collective, if one exists.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const primeMember = await api.derive.membership.prime();
|
||||
* console.log(primeMember);
|
||||
* ```
|
||||
*/
|
||||
export const prime = /*#__PURE__*/ collectivePrime('membership');
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './index.js';
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2026 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Do not edit, auto-generated by @pezkuwi/dev
|
||||
// (packageInfo imports will be kept as-is, user-editable)
|
||||
|
||||
import { detectPackage } from '@pezkuwi/util';
|
||||
|
||||
import { packageInfo } from './packageInfo.js';
|
||||
|
||||
detectPackage(packageInfo, null, []);
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2026 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Do not edit, auto-generated by @pezkuwi/dev
|
||||
|
||||
export const packageInfo = { name: '@pezkuwi/api-derive', path: 'auto', type: 'auto', version: '16.5.4' };
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './indexes.js';
|
||||
export * from './info.js';
|
||||
export * from './progress.js';
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, u32 } from '@pezkuwi/types';
|
||||
import type { ActiveEraInfo, EraIndex, Moment, SessionIndex } from '@pezkuwi/types/interfaces';
|
||||
import type { DeriveApi, DeriveSessionIndexes } from '../types.js';
|
||||
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
// 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')
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @name indexes
|
||||
* @description Retrieves session-related index data, adapting to whether
|
||||
* the chain has staking enabled.
|
||||
* @example
|
||||
* ```javascript
|
||||
* api.derive.session.indexes((indexes) => {
|
||||
* console.log(`Current session index: ${indexes.currentIndex}`);
|
||||
* console.log(`Validator count: ${indexes.validatorCount}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
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,39 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { DeriveApi, DeriveSessionInfo } from '../types.js';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name info
|
||||
* @description Retrieves all the session and era query and calculates specific values on it as the length of the session and eras.
|
||||
* @example
|
||||
* ```javascript
|
||||
* api.derive.session.info((info) => {
|
||||
* console.log(`Session info ${JSON.stringify(info)}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
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 objectSpread({
|
||||
eraLength: api.registry.createType('BlockNumber', sessionsPerEra.mul(sessionLength)),
|
||||
isEpoch: !!api.query.babe,
|
||||
sessionLength,
|
||||
sessionsPerEra
|
||||
}, indexes);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, u64 } from '@pezkuwi/types';
|
||||
import type { BlockNumber, SessionIndex } from '@pezkuwi/types/interfaces';
|
||||
import type { DeriveApi, DeriveSessionInfo, DeriveSessionProgress } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
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 objectSpread({
|
||||
eraProgress: api.registry.createType('BlockNumber', eraProgress),
|
||||
sessionProgress: api.registry.createType('BlockNumber', sessionProgress)
|
||||
}, info);
|
||||
}
|
||||
|
||||
function queryAura (api: DeriveApi): Observable<DeriveSessionProgress> {
|
||||
return api.derive.session.info().pipe(
|
||||
map((info): DeriveSessionProgress =>
|
||||
objectSpread({
|
||||
eraProgress: api.registry.createType('BlockNumber'),
|
||||
sessionProgress: api.registry.createType('BlockNumber')
|
||||
}, info)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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)]
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name progress
|
||||
* @description Retrieves session information and progress.
|
||||
* @example
|
||||
* ```javascript
|
||||
* api.derive.session.progress((progress) => {
|
||||
* console.log(`Session progress ${JSON.stringify(progress)}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name eraLenght
|
||||
* @description Retrieves the total length of the current era.
|
||||
* @example
|
||||
* ```javascript
|
||||
* api.derive.session.eraLength((length) => {
|
||||
* console.log(`Current era length: ${length} sessions`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const eraLength = /*#__PURE__*/ withProgressField('eraLength');
|
||||
/**
|
||||
* @name eraProgress
|
||||
* @description Retrieves the progress of the current era.
|
||||
* @example
|
||||
* ```javascript
|
||||
* api.derive.session.eraProgress((progress) => {
|
||||
* console.log(`Current era progress: ${progress} sessions`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const eraProgress = /*#__PURE__*/ withProgressField('eraProgress');
|
||||
/**
|
||||
* @name sessionProgress
|
||||
* @description Retrieves the progress of the current session.
|
||||
* @example
|
||||
* ```javascript
|
||||
* api.derive.session.sessionProgress((progress) => {
|
||||
* console.log(`Current session progress: ${progress} slots`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const sessionProgress = /*#__PURE__*/ withProgressField('sessionProgress');
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Option, u32, u64 } from '@pezkuwi/types';
|
||||
import type { BlockNumber, EraIndex, Moment, SessionIndex } from '@pezkuwi/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,71 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId32, BalanceOf } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletSocietyBid, PezpalletSocietyBidKind, PezpalletSocietyCandidacy } from '@pezkuwi/types/lookup';
|
||||
import type { ITuple } from '@pezkuwi/types/types';
|
||||
import type { Option, Vec } from '@pezkuwi/types-codec';
|
||||
import type { DeriveApi, DeriveSocietyCandidate } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
type ResultSuspend = Option<ITuple<[BalanceOf, PezpalletSocietyBidKind]>>;
|
||||
type Result = [PezpalletSocietyBid[], ResultSuspend[]]
|
||||
|
||||
function getPrev (api: DeriveApi): Observable<DeriveSocietyCandidate[]> {
|
||||
return api.query.society.candidates<Vec<PezpalletSocietyBid>>().pipe(
|
||||
switchMap((candidates): Observable<Result> =>
|
||||
combineLatest([
|
||||
of(candidates),
|
||||
api.query.society['suspendedCandidates'].multi<Option<ITuple<[BalanceOf, PezpalletSocietyBidKind]>>>(
|
||||
candidates.map(({ who }) => who)
|
||||
)
|
||||
])
|
||||
),
|
||||
map(([candidates, suspended]: Result): DeriveSocietyCandidate[] =>
|
||||
candidates.map(({ kind, value, who }, index) => ({
|
||||
accountId: who,
|
||||
isSuspended: suspended[index].isSome,
|
||||
kind,
|
||||
value
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function getCurr (api: DeriveApi) {
|
||||
return api.query.society.candidates.entries().pipe(
|
||||
map((entries) =>
|
||||
entries
|
||||
.filter(([, opt]) => opt.isSome)
|
||||
.map(([{ args: [accountId] }, opt]): [AccountId32, PezpalletSocietyCandidacy] => [accountId, opt.unwrap()])
|
||||
// FIXME We are missing the new fields from the candidate record
|
||||
.map(([accountId, { bid, kind }]) => ({
|
||||
accountId,
|
||||
isSuspended: false,
|
||||
kind,
|
||||
value: bid
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name candidate
|
||||
* @description Retrieves the list of candidates for the society module.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const societyCandidates = await api.derive.society.candidates();
|
||||
* console.log(societyCandidates);
|
||||
* ```
|
||||
*/
|
||||
export function candidates (instanceId: string, api: DeriveApi): () => Observable<DeriveSocietyCandidate[]> {
|
||||
return memo(instanceId, (): Observable<DeriveSocietyCandidate[]> =>
|
||||
api.query.society['suspendedCandidates'] && api.query.society.candidates.creator.meta.type.isPlain
|
||||
? getPrev(api)
|
||||
: getCurr(api)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './candidates.js';
|
||||
export * from './info.js';
|
||||
export * from './member.js';
|
||||
export * from './members.js';
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Option, u32, Vec } from '@pezkuwi/types';
|
||||
import type { AccountId, BalanceOf } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletSocietyBid } from '@pezkuwi/types/lookup';
|
||||
import type { DeriveApi, DeriveSociety } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
type Result = [Vec<PezpalletSocietyBid>, Option<AccountId> | undefined, Option<AccountId>, Option<AccountId>, u32 | undefined, BalanceOf]
|
||||
|
||||
/**
|
||||
* @name info
|
||||
* @description Get the overall info for a society.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const societyInfo = await api.derive.society.candidates();
|
||||
* console.log(societyInfo);
|
||||
* ```
|
||||
*/
|
||||
export function info (instanceId: string, api: DeriveApi): () => Observable<DeriveSociety> {
|
||||
return memo(instanceId, (): Observable<DeriveSociety> =>
|
||||
combineLatest<Result>([
|
||||
api.query.society.bids(),
|
||||
api.query.society['defender']
|
||||
? api.query.society['defender']<Option<AccountId>>()
|
||||
: of(undefined),
|
||||
api.query.society.founder(),
|
||||
api.query.society.head(),
|
||||
api.query.society['maxMembers']
|
||||
? api.query.society['maxMembers']<u32>()
|
||||
: of(undefined),
|
||||
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,28 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId } from '@pezkuwi/types/interfaces';
|
||||
import type { DeriveApi, DeriveSocietyMember } from '../types.js';
|
||||
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name member
|
||||
* @description Get the member info for a society.
|
||||
* @param { AccountId } accountId
|
||||
* @example
|
||||
* ```javascript
|
||||
* const member = await api.derive.society.member(ALICE);
|
||||
* console.log(member);
|
||||
* ```
|
||||
*/
|
||||
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,107 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletSocietyVote, PezpalletSocietyVouchingStatus } from '@pezkuwi/types/lookup';
|
||||
import type { ITuple } from '@pezkuwi/types/types';
|
||||
import type { bool, Option, u32, u128, Vec } from '@pezkuwi/types-codec';
|
||||
import type { DeriveApi, DeriveSocietyMember } from '../types.js';
|
||||
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
function _membersPrev (api: DeriveApi, accountIds: AccountId[]): Observable<DeriveSocietyMember[]> {
|
||||
return combineLatest([
|
||||
of(accountIds),
|
||||
api.query.society.payouts.multi<Vec<ITuple<[u32, u128]>>>(accountIds),
|
||||
api.query.society['strikes'].multi<u32>(accountIds),
|
||||
api.query.society.defenderVotes.multi<Option<PezpalletSocietyVote>>(accountIds),
|
||||
api.query.society.suspendedMembers.multi<bool>(accountIds),
|
||||
api.query.society['vouching'].multi<Option<PezpalletSocietyVouchingStatus>>(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)
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function _membersCurr (api: DeriveApi, accountIds: AccountId[]): Observable<DeriveSocietyMember[]> {
|
||||
return combineLatest([
|
||||
of(accountIds),
|
||||
api.query.society.members.multi(accountIds),
|
||||
api.query.society.payouts.multi(accountIds),
|
||||
api.query.society.challengeRoundCount().pipe(
|
||||
switchMap((round) =>
|
||||
api.query.society.defenderVotes.multi(accountIds.map((accountId) => [round, accountId]))
|
||||
)
|
||||
),
|
||||
api.query.society.suspendedMembers.multi(accountIds)
|
||||
]).pipe(
|
||||
map(([accountIds, members, payouts, defenderVotes, suspendedMembers]) =>
|
||||
accountIds
|
||||
.map((accountId, index) =>
|
||||
members[index].isSome
|
||||
? {
|
||||
accountId,
|
||||
isDefenderVoter: defenderVotes[index].isSome,
|
||||
isSuspended: suspendedMembers[index].isSome,
|
||||
member: members[index].unwrap(),
|
||||
payouts: payouts[index].payouts
|
||||
}
|
||||
: null
|
||||
)
|
||||
.filter((m): m is NonNullable<typeof m> => !!m)
|
||||
.map(({ accountId, isDefenderVoter, isSuspended, member, payouts }) => ({
|
||||
accountId,
|
||||
isDefenderVoter,
|
||||
isSuspended,
|
||||
payouts,
|
||||
strikes: member.strikes,
|
||||
vouching: member.vouching.unwrapOr(undefined)
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function _members (instanceId: string, api: DeriveApi): (accountIds: AccountId[]) => Observable<DeriveSocietyMember[]> {
|
||||
return memo(instanceId, (accountIds: AccountId[]): Observable<DeriveSocietyMember[]> =>
|
||||
api.query.society.members.creator.meta.type.isMap
|
||||
? _membersCurr(api, accountIds)
|
||||
: _membersPrev(api, accountIds)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name members
|
||||
* @description Get the society members.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const members = await api.derive.society.members();
|
||||
* console.log(members);
|
||||
* ```
|
||||
*/
|
||||
export function members (instanceId: string, api: DeriveApi): () => Observable<DeriveSocietyMember[]> {
|
||||
return memo(instanceId, (): Observable<DeriveSocietyMember[]> =>
|
||||
api.query.society.members.creator.meta.type.isMap
|
||||
? api.query.society.members.keys().pipe(
|
||||
switchMap((keys) =>
|
||||
api.derive.society._members(
|
||||
keys.map(({ args: [accountId] }) => accountId)
|
||||
)
|
||||
)
|
||||
)
|
||||
: api.query.society.members<Vec<AccountId>>().pipe(
|
||||
switchMap((members) => api.derive.society._members(members))
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { u32 } from '@pezkuwi/types';
|
||||
import type { AccountId, Balance, BalanceOf, BlockNumber, StrikeCount } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletSocietyBid, PezpalletSocietyBidKind, PezpalletSocietyVote, PezpalletSocietyVouchingStatus } from '@pezkuwi/types/lookup';
|
||||
|
||||
export interface DeriveSociety {
|
||||
bids: PezpalletSocietyBid[];
|
||||
defender?: AccountId | undefined;
|
||||
hasDefender: boolean;
|
||||
head?: AccountId | undefined;
|
||||
founder?: AccountId | undefined;
|
||||
maxMembers?: u32 | undefined;
|
||||
pot: BalanceOf;
|
||||
}
|
||||
|
||||
export interface DeriveSocietyCandidate {
|
||||
accountId: AccountId;
|
||||
kind: PezpalletSocietyBidKind;
|
||||
value: Balance;
|
||||
isSuspended: boolean;
|
||||
}
|
||||
|
||||
export interface DeriveSocietyMember {
|
||||
accountId: AccountId;
|
||||
isDefenderVoter: boolean;
|
||||
isSuspended: boolean;
|
||||
payouts: [BlockNumber, Balance][];
|
||||
strikes: StrikeCount;
|
||||
vote?: PezpalletSocietyVote | undefined;
|
||||
vouching?: PezpalletSocietyVouchingStatus | undefined;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Balance } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletStakingStakingLedger, PezpalletStakingUnlockChunk } from '@pezkuwi/types/lookup';
|
||||
import type { DeriveApi, DeriveSessionInfo, DeriveStakingAccount, DeriveStakingKeys, DeriveStakingQuery, DeriveUnlocking } from '../types.js';
|
||||
import type { StakingQueryFlags } from './types.js';
|
||||
|
||||
import { combineLatest, map, switchMap } from 'rxjs';
|
||||
|
||||
import { BN, BN_ZERO, objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { firstMemo, memo } from '../util/index.js';
|
||||
|
||||
const QUERY_OPTS = {
|
||||
withDestination: true,
|
||||
withLedger: true,
|
||||
withNominations: true,
|
||||
withPrefs: true
|
||||
};
|
||||
|
||||
function groupByEra (list: PezpalletStakingUnlockChunk[]): 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: PezpalletStakingStakingLedger | 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: PezpalletStakingStakingLedger | undefined, sessionInfo: DeriveSessionInfo): Balance {
|
||||
return api.registry.createType('Balance', (stakingLedger?.unlocking || [] as PezpalletStakingUnlockChunk[]).reduce((total, { era, value }): BN => {
|
||||
// aligns with https://github.com/pezkuwichain/bizinikiwi/blob/fdfdc73f9e64dc47934b72eb9af3e1989e4ba699/frame/staking/src/pallet/mod.rs#L973-L975
|
||||
// (ensure currentEra >= era passed, as per https://github.com/pezkuwichain/bizinikiwi/blob/fdfdc73f9e64dc47934b72eb9af3e1989e4ba699/frame/staking/src/lib.rs#L477-L494)
|
||||
// NOTE: Previously we used activeEra >= era, which is incorrect for the last session
|
||||
return era.unwrap().gt(sessionInfo.currentEra)
|
||||
? total
|
||||
: total.iadd(value.unwrap());
|
||||
}, new BN(0)));
|
||||
}
|
||||
|
||||
function parseResult (api: DeriveApi, sessionInfo: DeriveSessionInfo, keys: DeriveStakingKeys, query: DeriveStakingQuery): DeriveStakingAccount {
|
||||
return objectSpread({}, keys, query, {
|
||||
redeemable: redeemableSum(api, query.stakingLedger, sessionInfo),
|
||||
unlocking: calculateUnlocking(api, query.stakingLedger, sessionInfo)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name accounts
|
||||
* @param {(Uint8Array | string)[]} accountIds List of account stashes
|
||||
* @param {StakingQueryFlags} opts optional filtering flag
|
||||
* @description From a list of stashes, fill in all the relevant staking details
|
||||
* @example
|
||||
* ```javascript
|
||||
* const accounts = await api.derive.staking.accounts([
|
||||
* "149B17nn7zVL4SkLSNmANupEkGexUBAxVrdk4bbWFZYibkFc",
|
||||
* ]);
|
||||
* console.log("First account staking info:", accounts[0]);
|
||||
* ```
|
||||
*/
|
||||
export function accounts (instanceId: string, api: DeriveApi): (accountIds: (Uint8Array | string)[], opts?: StakingQueryFlags) => Observable<DeriveStakingAccount[]> {
|
||||
return memo(instanceId, (accountIds: (Uint8Array | string)[], opts: StakingQueryFlags = QUERY_OPTS): Observable<DeriveStakingAccount[]> =>
|
||||
api.derive.session.info().pipe(
|
||||
switchMap((sessionInfo) =>
|
||||
combineLatest([
|
||||
api.derive.staking.keysMulti(accountIds),
|
||||
api.derive.staking.queryMulti(accountIds, opts)
|
||||
]).pipe(
|
||||
map(([keys, queries]) =>
|
||||
queries.map((q, index) => parseResult(api, sessionInfo, keys[index], q))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name account
|
||||
* @param {(Uint8Array | string)} accountId AccountId of the stash.
|
||||
* @param {StakingQueryFlags} opts (Optional) filtering flag.
|
||||
* @description From a stash, retrieve the controllerId and fill in all the relevant staking details.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const accountStakingData = await api.derive.staking.account(
|
||||
* "149B17nn7zVL4SkLSNmANupEkGexUBAxVrdk4bbWFZYibkFc"
|
||||
* );
|
||||
* console.log(accountStakingData);
|
||||
* ```
|
||||
*/
|
||||
export const account = /*#__PURE__*/ firstMemo(
|
||||
(api: DeriveApi, accountId: Uint8Array | string, opts?: StakingQueryFlags) =>
|
||||
api.derive.staking.accounts([accountId], opts)
|
||||
);
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { EraIndex } from '@pezkuwi/types/interfaces';
|
||||
|
||||
import { deriveCache } from '../util/index.js';
|
||||
|
||||
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))
|
||||
)
|
||||
.filter((e): e is T => !!e);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { PezpalletStakingEraRewardPoints } from '@pezkuwi/types/lookup';
|
||||
import type { DeriveApi } from '../types.js';
|
||||
|
||||
import { switchMap } from 'rxjs';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
/**
|
||||
* @name currentPoints
|
||||
* @description Retrieve the staking overview, including elected and points earned.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const currentPoints = await api.derive.staking.currentPoints();
|
||||
* console.log(currentPoints.toHuman());
|
||||
* ```
|
||||
*/
|
||||
export function currentPoints (instanceId: string, api: DeriveApi): () => Observable<PezpalletStakingEraRewardPoints> {
|
||||
return memo(instanceId, (): Observable<PezpalletStakingEraRewardPoints> =>
|
||||
api.derive.session.indexes().pipe(
|
||||
switchMap(({ activeEra }) =>
|
||||
api.query.staking.erasRewardPoints(activeEra)
|
||||
)
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AccountId } from '@pezkuwi/types/interfaces';
|
||||
import type { DeriveApi, DeriveStakingElected, StakingQueryFlags } from '../types.js';
|
||||
|
||||
import { map, switchMap } from 'rxjs';
|
||||
|
||||
import { arrayFlatten } from '@pezkuwi/util';
|
||||
|
||||
import { memo } from '../util/index.js';
|
||||
|
||||
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)))]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name electedInfo
|
||||
* @param {StakingQueryFlags} flags? (Optional) Query flags to filter the staking data.
|
||||
* @param {number} page? (Optional) The page index for paginated results.
|
||||
* @description Retrieves detailed staking information about the next elected validators and their associated staking data.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { nextElected, validators, info } =
|
||||
* await api.derive.staking.electedInfo();
|
||||
* console.log(
|
||||
* "Next Elected Validators:",
|
||||
* nextElected.map((acc) => acc.toString())
|
||||
* );
|
||||
* console.log(
|
||||
* "Current Validators:",
|
||||
* validators.map((acc) => acc.toString())
|
||||
* );
|
||||
* console.log("Validator Staking Info:", info);
|
||||
* ```
|
||||
*/
|
||||
export function electedInfo (instanceId: string, api: DeriveApi): (flags?: StakingQueryFlags, page?: number) => Observable<DeriveStakingElected> {
|
||||
return memo(instanceId, (flags: StakingQueryFlags = DEFAULT_FLAGS, page = 0): Observable<DeriveStakingElected> =>
|
||||
api.derive.staking.validators().pipe(
|
||||
switchMap(({ nextElected, validators }): Observable<DeriveStakingElected> =>
|
||||
api.derive.staking.queryMulti(combineAccounts(nextElected, validators), flags, page).pipe(
|
||||
map((info): DeriveStakingElected => ({
|
||||
info,
|
||||
nextElected,
|
||||
validators
|
||||
}))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user