diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a27deb07..24d5e0450f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Contributed: Changes: +- Add basic `derive.bagsList` support - Optimize JSONRPC requests/response handling - Add latest Polkadot 9180 upgrade block diff --git a/packages/api-derive/src/bagsList/get.ts b/packages/api-derive/src/bagsList/get.ts new file mode 100644 index 0000000000..a9d5940ea3 --- /dev/null +++ b/packages/api-derive/src/bagsList/get.ts @@ -0,0 +1,41 @@ +// Copyright 2017-2022 @polkadot/api-derive authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { Observable } from 'rxjs'; +import type { Option, StorageKey, u64 } from '@polkadot/types'; +import type { PalletBagsListListBag } from '@polkadot/types/lookup'; +import type { BN } from '@polkadot/util'; +import type { DeriveApi } from '../types'; +import type { BagBase } from './types'; + +import { map } from 'rxjs'; + +import { bnToBn } from '@polkadot/util'; + +import { memo } from '../util'; + +export function all (instanceId: string, api: DeriveApi): () => Observable { + return memo(instanceId, (): Observable => + api.query.bagsList.listBags.entries().pipe( + map((entries: [StorageKey<[u64]>, Option][]) => + entries.map(([{ args: [id] }, opt]) => ({ + bag: opt.unwrapOr(null), + id + })) + ) + ) + ); +} + +export function get (instanceId: string, api: DeriveApi): (id: BN | number) => Observable { + return memo(instanceId, (_id: BN | number): Observable => { + const id = bnToBn(_id); + + return api.query.bagsList.listBags(id).pipe( + map((opt: Option) => ({ + bag: opt.unwrapOr(null), + id + })) + ); + }); +} diff --git a/packages/api-derive/src/bagsList/getExpanded.ts b/packages/api-derive/src/bagsList/getExpanded.ts new file mode 100644 index 0000000000..1a3abcc73a --- /dev/null +++ b/packages/api-derive/src/bagsList/getExpanded.ts @@ -0,0 +1,23 @@ +// Copyright 2017-2022 @polkadot/api-derive authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { Observable } from 'rxjs'; +import type { BN } from '@polkadot/util'; +import type { DeriveApi } from '../types'; +import type { Bag } from './types'; + +import { map, switchMap } from 'rxjs'; + +import { memo } from '../util'; + +export function getExpanded (instanceId: string, api: DeriveApi): (id: BN | number) => Observable { + return memo(instanceId, (id: BN | number): Observable => + api.derive.bagsList.get(id).pipe( + switchMap((base) => + api.derive.bagsList.listNodes(base.bag).pipe( + map((nodes) => ({ ...base, nodes })) + ) + ) + ) + ); +} diff --git a/packages/api-derive/src/bagsList/index.ts b/packages/api-derive/src/bagsList/index.ts new file mode 100644 index 0000000000..49b4b02510 --- /dev/null +++ b/packages/api-derive/src/bagsList/index.ts @@ -0,0 +1,6 @@ +// Copyright 2017-2022 @polkadot/api-derive authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +export * from './get'; +export * from './getExpanded'; +export * from './listNodes'; diff --git a/packages/api-derive/src/bagsList/listNodes.ts b/packages/api-derive/src/bagsList/listNodes.ts new file mode 100644 index 0000000000..47a9463877 --- /dev/null +++ b/packages/api-derive/src/bagsList/listNodes.ts @@ -0,0 +1,41 @@ +// Copyright 2017-2022 @polkadot/api-derive authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { Observable } from 'rxjs'; +import type { Option } from '@polkadot/types'; +import type { AccountId32 } from '@polkadot/types/interfaces'; +import type { PalletBagsListListBag, PalletBagsListListNode } from '@polkadot/types/lookup'; +import type { DeriveApi } from '../types'; + +import { BehaviorSubject, map, of, switchMap, tap, toArray } from 'rxjs'; + +import { memo } from '../util'; + +function traverseLinks (api: DeriveApi, head: AccountId32 | string): Observable { + const subject = new BehaviorSubject(head); + + return subject.pipe( + switchMap((account) => + api.query.bagsList.listNodes(account) + ), + tap((node: Option): void => { + setTimeout((): void => { + node.isSome && (node.value as PalletBagsListListNode).next.isSome + ? subject.next(node.unwrap().next.unwrap()) + : subject.complete(); + }, 0); + }), + toArray(), // toArray since we want to startSubject to be completed + map((all: Option[]) => + all.map((o) => o.unwrap()) + ) + ); +} + +export function listNodes (instanceId: string, api: DeriveApi): (bag: PalletBagsListListBag | null) => Observable { + return memo(instanceId, (bag: PalletBagsListListBag | null): Observable => + bag && bag.head.isSome + ? traverseLinks(api, bag.head.unwrap()) + : of([]) + ); +} diff --git a/packages/api-derive/src/bagsList/types.ts b/packages/api-derive/src/bagsList/types.ts new file mode 100644 index 0000000000..ae3e667778 --- /dev/null +++ b/packages/api-derive/src/bagsList/types.ts @@ -0,0 +1,15 @@ +// Copyright 2017-2022 @polkadot/api-derive authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { PalletBagsListListBag, PalletBagsListListNode } from '@polkadot/types/lookup'; +import type { BN } from '@polkadot/util'; + +export interface BagBase { + bag: PalletBagsListListBag | null; + id: BN; +} + +export interface Bag extends BagBase { + + nodes: PalletBagsListListNode[]; +} diff --git a/packages/api-derive/src/bundle.ts b/packages/api-derive/src/bundle.ts index 49995cf6c0..6fb150cbcd 100644 --- a/packages/api-derive/src/bundle.ts +++ b/packages/api-derive/src/bundle.ts @@ -22,6 +22,10 @@ export { lazyDeriveSection }; // Enable derive only if some of these modules are available const checks: Record = { + bagsList: { + instances: ['bagsList'], + methods: [] + }, contracts: { instances: ['contracts'], methods: [] diff --git a/packages/api-derive/src/crowdloan/contributions.ts b/packages/api-derive/src/crowdloan/contributions.ts index 95cb43caa2..2ed6afa1e5 100644 --- a/packages/api-derive/src/crowdloan/contributions.ts +++ b/packages/api-derive/src/crowdloan/contributions.ts @@ -60,17 +60,17 @@ function _eventTriggerAll (api: DeriveApi, paraId: string | number | BN): Observ } function _getKeysPaged (api: DeriveApi, childKey: string): Observable { - const startSubject = new BehaviorSubject(undefined); + const subject = new BehaviorSubject(undefined); - return startSubject.pipe( + return subject.pipe( switchMap((startKey) => api.rpc.childstate.getKeysPaged(childKey, '0x', PAGE_SIZE_K, startKey) ), tap((keys): void => { setTimeout((): void => { keys.length === PAGE_SIZE_K - ? startSubject.next(keys[PAGE_SIZE_K - 1].toHex()) - : startSubject.complete(); + ? subject.next(keys[PAGE_SIZE_K - 1].toHex()) + : subject.complete(); }, 0); }), toArray(), // toArray since we want to startSubject to be completed diff --git a/packages/api-derive/src/derive.ts b/packages/api-derive/src/derive.ts index db2f3e4eb6..8f44c826ff 100644 --- a/packages/api-derive/src/derive.ts +++ b/packages/api-derive/src/derive.ts @@ -4,6 +4,7 @@ import type { AnyFunction } from '@polkadot/types/types'; import * as accounts from './accounts'; +import * as bagsList from './bagsList'; import * as balances from './balances'; import * as bounties from './bounties'; import * as chain from './chain'; @@ -22,7 +23,7 @@ import * as technicalCommittee from './technicalCommittee'; import * as treasury from './treasury'; import * as tx from './tx'; -export const derive = { accounts, balances, bounties, chain, contracts, council, crowdloan, democracy, elections, imOnline, membership, parachains, session, society, staking, technicalCommittee, treasury, tx }; +export const derive = { accounts, bagsList, balances, bounties, chain, contracts, council, crowdloan, democracy, elections, imOnline, membership, parachains, session, society, staking, technicalCommittee, treasury, tx }; type DeriveSection
= { [M in keyof Section]: Section[M] extends AnyFunction diff --git a/packages/api-derive/src/staking/util.ts b/packages/api-derive/src/staking/util.ts index 8e589bdb8c..f7bec6ff70 100644 --- a/packages/api-derive/src/staking/util.ts +++ b/packages/api-derive/src/staking/util.ts @@ -23,17 +23,17 @@ const ERA_CHUNK_SIZE = 14; function chunkEras (eras: EraIndex[], fn: (eras: EraIndex[]) => Observable): Observable { const chunked = arrayChunk(eras, ERA_CHUNK_SIZE); let index = 0; - const startSubject = new BehaviorSubject(chunked[index]); + const subject = new BehaviorSubject(chunked[index]); - return startSubject.pipe( + return subject.pipe( switchMap(fn), tap((): void => { setTimeout((): void => { index++; index === chunked.length - ? startSubject.complete() - : startSubject.next(chunked[index]); + ? subject.complete() + : subject.next(chunked[index]); }, 0); }), toArray(),