Rebrand: polkadot → pezkuwi, substrate → bizinikiwi, kusama → dicle

This commit is contained in:
2026-01-07 02:29:40 +03:00
commit d5f038faea
1383 changed files with 1088018 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
# @pezkuwi/api-derive
Collection of high-level utility functions built on top of the @pezkuwi/api library. Designed to simplify the process of querying complex on-chain data by combining multiple RPC calls, storage queries, and runtime logic into a single, callable function.
Instead of manually fetching and processing blockchain data, developers can use `api.derive` methods to retrieve information.
## Available Derive Namespaces
The derive functions are categorized into namespaces based on different common Bizinikiwi modules. Accesible by calling `api.derive.NAMESPACE` (e.g. `api.derive.balances`). The available modules are as follows:
- [accounts](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/accounts)
- [alliance](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/alliance)
- [bagsList](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/bagsList)
- [balances](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/balances)
- [bounties](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/bounties)
- [chain](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/chain)
- [contracts](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/contracts)
- [council](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/council)
- [crowdloan](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/crowdloan)
- [democracy](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/democracy)
- [elections](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/elections)
- [imOnline](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/imOnline)
- [membership](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/membership)
- [teyrchains](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/teyrchains)
- [session](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/session)
- [society](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/society)
- [staking](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/staking)
- [technicalCommittee](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/technicalCommittee)
- [treasury](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/treasury)
- [tx](https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive/src/tx)
+42
View File
@@ -0,0 +1,42 @@
{
"author": "Jaco Greeff <jacogr@gmail.com>",
"bugs": "https://github.com/pezkuwichain/pezkuwi-api/issues",
"description": "Common functions used across Pezkuwi, derived from RPC calls and storage queries.",
"engines": {
"node": ">=18"
},
"homepage": "https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/api-derive#readme",
"license": "Apache-2.0",
"name": "@pezkuwi/api-derive",
"repository": {
"directory": "packages/api-derive",
"type": "git",
"url": "https://github.com/pezkuwichain/pezkuwi-api.git"
},
"sideEffects": [
"./packageDetect.js",
"./packageDetect.cjs"
],
"type": "module",
"version": "16.5.6",
"main": "index.js",
"dependencies": {
"@pezkuwi/api": "16.5.4",
"@pezkuwi/api-augment": "16.5.4",
"@pezkuwi/api-base": "16.5.4",
"@pezkuwi/rpc-core": "16.5.4",
"@pezkuwi/types": "16.5.4",
"@pezkuwi/types-codec": "16.5.4",
"@pezkuwi/util": "^14.0.1",
"@pezkuwi/util-crypto": "^14.0.1",
"rxjs": "^7.8.1",
"tslib": "^2.8.1"
},
"devDependencies": {
"@pezkuwi/api": "16.5.4",
"@pezkuwi/api-augment": "16.5.4",
"@pezkuwi/rpc-augment": "16.5.4",
"@pezkuwi/rpc-provider": "16.5.4",
"@pezkuwi/types-support": "16.5.4"
}
}
@@ -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'))
);
});
}
+90
View File
@@ -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 })))
);
}
+11
View File
@@ -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;
})
)
);
}
+58
View File
@@ -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
}))
));
}
+48
View File
@@ -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;
}
+76
View File
@@ -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');
+4
View File
@@ -0,0 +1,4 @@
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
// SPDX-License-Identifier: Apache-2.0
import '@pezkuwi/api-augment';
+79
View File
@@ -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([])
);
}
+18
View File
@@ -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[];
}
+14
View File
@@ -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']
);
}
+210
View File
@@ -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))
));
}
+246
View File
@@ -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))
));
}
+26
View File
@@ -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 };
+79
View File
@@ -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';
+140
View File
@@ -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))
)
));
}
+43
View File
@@ -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)
)
)
);
}
+13
View File
@@ -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);
})
)
);
}
+112
View File
@@ -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;
+61
View File
@@ -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';
+79
View File
@@ -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');
+11
View File
@@ -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][];
+97
View File
@@ -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 teyrchains crowdloan contributions.
* This key is used to access contribution data stored in a separate child trie of the blockchains 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>;
+33
View File
@@ -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';
+114
View File
@@ -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 {
}
+166
View File
@@ -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
);
}
+41
View File
@@ -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';
+126
View File
@@ -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({})
);
}
+103
View File
@@ -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', []);
});
});
+6
View File
@@ -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');
+4
View File
@@ -0,0 +1,4 @@
// Copyright 2017-2025 @pezkuwi/api-derive authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './index.js';
+11
View File
@@ -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, []);
+6
View File
@@ -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' };
+6
View File
@@ -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)
);
}
+39
View File
@@ -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);
})
)
);
}
+130
View File
@@ -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');
+25
View File
@@ -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)
);
}
+7
View File
@@ -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';
+50
View File
@@ -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
}))
)
);
}
+28
View File
@@ -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)
)
);
}
+107
View File
@@ -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))
)
);
}
+33
View File
@@ -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;
}
+112
View File
@@ -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)
);
+48
View File
@@ -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);
}

Some files were not shown because too many files have changed in this diff Show More