@dedot/api 0.8.1 → 0.8.2-next.6cfafabd.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/cjs/client/BaseSubstrateClient.js +30 -1
  2. package/cjs/executor/StorageQueryExecutor.js +8 -15
  3. package/cjs/executor/v2/StorageQueryExecutorV2.js +7 -33
  4. package/cjs/extrinsic/extensions/ExtraSignedExtension.js +24 -5
  5. package/cjs/extrinsic/extensions/FallbackSignedExtension.js +63 -0
  6. package/cjs/extrinsic/extensions/index.js +1 -0
  7. package/cjs/extrinsic/extensions/known/index.js +11 -8
  8. package/cjs/storage/BaseStorageQuery.js +27 -0
  9. package/cjs/storage/LegacyStorageQuery.js +67 -0
  10. package/cjs/storage/NewStorageQuery.js +85 -0
  11. package/cjs/storage/index.js +3 -0
  12. package/client/BaseSubstrateClient.d.ts +32 -1
  13. package/client/BaseSubstrateClient.js +30 -1
  14. package/client/DedotClient.js +1 -1
  15. package/executor/StorageQueryExecutor.d.ts +3 -2
  16. package/executor/StorageQueryExecutor.js +7 -14
  17. package/executor/v2/StorageQueryExecutorV2.d.ts +4 -6
  18. package/executor/v2/StorageQueryExecutorV2.js +7 -33
  19. package/extrinsic/extensions/ExtraSignedExtension.d.ts +6 -0
  20. package/extrinsic/extensions/ExtraSignedExtension.js +25 -6
  21. package/extrinsic/extensions/FallbackSignedExtension.d.ts +33 -0
  22. package/extrinsic/extensions/FallbackSignedExtension.js +58 -0
  23. package/extrinsic/extensions/index.d.ts +1 -0
  24. package/extrinsic/extensions/index.js +1 -0
  25. package/extrinsic/extensions/known/index.d.ts +11 -0
  26. package/extrinsic/extensions/known/index.js +11 -8
  27. package/package.json +10 -10
  28. package/storage/BaseStorageQuery.d.ts +41 -0
  29. package/storage/BaseStorageQuery.js +23 -0
  30. package/storage/LegacyStorageQuery.d.ts +35 -0
  31. package/storage/LegacyStorageQuery.js +63 -0
  32. package/storage/NewStorageQuery.d.ts +35 -0
  33. package/storage/NewStorageQuery.js +81 -0
  34. package/storage/index.d.ts +3 -0
  35. package/storage/index.js +3 -0
  36. package/types.d.ts +17 -1
  37. package/cjs/extrinsic/extensions/known/CheckNonZeroSender.js +0 -10
  38. package/cjs/extrinsic/extensions/known/CheckWeight.js +0 -10
  39. package/cjs/extrinsic/extensions/known/PrevalidateAttests.js +0 -11
  40. package/cjs/extrinsic/extensions/known/StorageWeightReclaim.js +0 -10
  41. package/extrinsic/extensions/known/CheckNonZeroSender.d.ts +0 -6
  42. package/extrinsic/extensions/known/CheckNonZeroSender.js +0 -6
  43. package/extrinsic/extensions/known/CheckWeight.d.ts +0 -6
  44. package/extrinsic/extensions/known/CheckWeight.js +0 -6
  45. package/extrinsic/extensions/known/PrevalidateAttests.d.ts +0 -7
  46. package/extrinsic/extensions/known/PrevalidateAttests.js +0 -7
  47. package/extrinsic/extensions/known/StorageWeightReclaim.d.ts +0 -6
  48. package/extrinsic/extensions/known/StorageWeightReclaim.js +0 -6
@@ -1,8 +1,7 @@
1
- import { BlockHash, Option, StorageData, StorageKey } from '@dedot/codecs';
2
- import type { AsyncMethod, Callback, GenericSubstrateApi } from '@dedot/types';
3
- import { HexString } from '@dedot/utils';
1
+ import { BlockHash } from '@dedot/codecs';
2
+ import type { AsyncMethod, GenericSubstrateApi, RpcVersion } from '@dedot/types';
4
3
  import { ChainHead } from '../../json-rpc/index.js';
5
- import { QueryableStorage } from '../../storage/QueryableStorage.js';
4
+ import { type BaseStorageQuery, QueryableStorage } from '../../storage/index.js';
6
5
  import { ISubstrateClientAt } from '../../types.js';
7
6
  import { StorageQueryExecutor } from '../StorageQueryExecutor.js';
8
7
  /**
@@ -12,6 +11,5 @@ export declare class StorageQueryExecutorV2<ChainApi extends GenericSubstrateApi
12
11
  chainHead: ChainHead;
13
12
  constructor(client: ISubstrateClientAt<ChainApi>, chainHead: ChainHead, atBlockHash?: BlockHash);
14
13
  protected exposeStorageMapMethods(entry: QueryableStorage): Record<string, AsyncMethod>;
15
- protected queryStorage(keys: StorageKey[], at?: BlockHash): Promise<Record<StorageKey, Option<StorageData>>>;
16
- protected subscribeStorage(keys: HexString[], callback: Callback<Array<StorageData | undefined>>): Promise<() => Promise<void>>;
14
+ protected getStorageQuery(): BaseStorageQuery<RpcVersion>;
17
15
  }
@@ -1,4 +1,5 @@
1
1
  import { assert } from '@dedot/utils';
2
+ import { NewStorageQuery } from '../../storage/index.js';
2
3
  import { StorageQueryExecutor } from '../StorageQueryExecutor.js';
3
4
  /**
4
5
  * @name StorageQueryExecutorV2
@@ -28,38 +29,11 @@ export class StorageQueryExecutorV2 extends StorageQueryExecutor {
28
29
  };
29
30
  return { entries };
30
31
  }
31
- async queryStorage(keys, at) {
32
- const results = await this.chainHead.storage(keys.map((key) => ({ type: 'value', key })), undefined, at);
33
- return results.reduce((o, r) => {
34
- o[r.key] = (r.value ?? undefined);
35
- return o;
36
- }, {});
37
- }
38
- async subscribeStorage(keys, callback) {
39
- let best = await this.chainHead.bestBlock();
40
- let eventToListen = 'bestBlock';
41
- // TODO subscribe to finalized data source
42
- // initialHash = this.chainHead.finalizedHash;
43
- // eventToListen = 'finalizedBlock';
44
- const latestChanges = new Map();
45
- const pull = async ({ hash }) => {
46
- const results = await this.queryStorage(keys, hash);
47
- let changed = false;
48
- keys.forEach((key) => {
49
- const newValue = results[key];
50
- if (latestChanges.size > 0 && latestChanges.get(key) === newValue)
51
- return;
52
- changed = true;
53
- latestChanges.set(key, newValue);
54
- });
55
- if (!changed)
56
- return;
57
- callback(keys.map((key) => latestChanges.get(key)));
58
- };
59
- await pull(best);
60
- const unsub = this.chainHead.on(eventToListen, pull);
61
- return async () => {
62
- unsub();
63
- };
32
+ getStorageQuery() {
33
+ // @ts-ignore little trick to make querying data client.at instance works here,
34
+ // TODO need to rethink about this
35
+ if (!this.client['chainHead'])
36
+ this.client['chainHead'] = this.chainHead;
37
+ return new NewStorageQuery(this.client);
64
38
  }
65
39
  }
@@ -9,6 +9,12 @@ export declare class ExtraSignedExtension extends SignedExtension<any[], any[]>
9
9
  get $Data(): $.AnyShape;
10
10
  get $AdditionalSigned(): $.AnyShape;
11
11
  get $Payload(): $.AnyShape;
12
+ /**
13
+ * Check if the extension requires no external inputs (e.g: struct or tuple with empty types like `()` or `[]`)
14
+ * @param extDef - The definition of the signed extension
15
+ * @returns boolean
16
+ */
17
+ private isRequireNoExternalInputs;
12
18
  toPayload(call?: HexString): SignerPayloadJSON;
13
19
  toRawPayload(call?: HexString): SignerPayloadRaw;
14
20
  }
@@ -1,6 +1,7 @@
1
1
  import * as $ from '@dedot/shape';
2
- import { assert, ensurePresence, u8aToHex } from '@dedot/utils';
2
+ import { ensurePresence, u8aToHex } from '@dedot/utils';
3
3
  import { SignedExtension } from './SignedExtension.js';
4
+ import { FallbackSignedExtension, isEmptyStructOrTuple } from './FallbackSignedExtension.js';
4
5
  import { knownSignedExtensions } from './known/index.js';
5
6
  export class ExtraSignedExtension extends SignedExtension {
6
7
  #signedExtensions;
@@ -34,13 +35,31 @@ export class ExtraSignedExtension extends SignedExtension {
34
35
  const { signedExtensions: userSignedExtensions = {} } = this.client.options;
35
36
  const Extension = userSignedExtensions[extDef.ident] ||
36
37
  knownSignedExtensions[extDef.ident];
37
- assert(Extension, `SignedExtension for ${extDef.ident} not found`);
38
- return new Extension(this.client, {
39
- ...ensurePresence(this.options),
40
- def: extDef,
41
- });
38
+ if (Extension) {
39
+ return new Extension(this.client, {
40
+ ...ensurePresence(this.options),
41
+ def: extDef,
42
+ });
43
+ }
44
+ else if (this.isRequireNoExternalInputs(extDef)) {
45
+ return new FallbackSignedExtension(this.client, {
46
+ ...ensurePresence(this.options),
47
+ def: extDef,
48
+ }, extDef.ident);
49
+ }
50
+ // For extensions that require input but aren't implemented, throw an error
51
+ throw new Error(`SignedExtension for ${extDef.ident} requires input but is not implemented`);
42
52
  });
43
53
  }
54
+ /**
55
+ * Check if the extension requires no external inputs (e.g: struct or tuple with empty types like `()` or `[]`)
56
+ * @param extDef - The definition of the signed extension
57
+ * @returns boolean
58
+ */
59
+ isRequireNoExternalInputs(extDef) {
60
+ return (isEmptyStructOrTuple(this.registry, extDef.typeId) &&
61
+ isEmptyStructOrTuple(this.registry, extDef.additionalSigned));
62
+ }
44
63
  toPayload(call = '0x') {
45
64
  const signedExtensions = this.#signedExtensions.map((se) => se.identifier);
46
65
  const { version } = this.registry.metadata.extrinsic;
@@ -0,0 +1,33 @@
1
+ import { PortableRegistry } from '@dedot/codecs';
2
+ import { SignerPayloadJSON } from '@dedot/types';
3
+ import { SignedExtension } from './SignedExtension.js';
4
+ import { ISubstrateClient } from '../../types.js';
5
+ /**
6
+ * A fallback signed extension that can be used for extensions
7
+ * that don't require external input.
8
+ *
9
+ * This extension is automatically used for:
10
+ * - Unknown extensions with empty struct or tuple types
11
+ * - Known extensions that don't require input, such as:
12
+ * - CheckNonZeroSender: Ensures sender is not the zero address
13
+ * - CheckWeight: Block resource (weight) limit check
14
+ * - PrevalidateAttests: Validates `attest` calls prior to execution
15
+ * - StorageWeightReclaim: Storage weight reclaim mechanism
16
+ *
17
+ * These extensions have empty struct or tuple types and don't need explicit implementation.
18
+ */
19
+ export declare class FallbackSignedExtension extends SignedExtension {
20
+ private readonly extensionIdent;
21
+ constructor(client: ISubstrateClient, options: any, extensionIdent: string);
22
+ get identifier(): string;
23
+ init(): Promise<void>;
24
+ toPayload(): Partial<SignerPayloadJSON>;
25
+ }
26
+ /**
27
+ * Checks if a type is an empty struct or tuple (doesn't require external input).
28
+ *
29
+ * @param registry The portable registry
30
+ * @param typeId The type ID to check
31
+ * @returns True if the type is an empty struct or tuple, false otherwise
32
+ */
33
+ export declare function isEmptyStructOrTuple(registry: PortableRegistry, typeId: number): boolean;
@@ -0,0 +1,58 @@
1
+ import { SignedExtension } from './SignedExtension.js';
2
+ /**
3
+ * A fallback signed extension that can be used for extensions
4
+ * that don't require external input.
5
+ *
6
+ * This extension is automatically used for:
7
+ * - Unknown extensions with empty struct or tuple types
8
+ * - Known extensions that don't require input, such as:
9
+ * - CheckNonZeroSender: Ensures sender is not the zero address
10
+ * - CheckWeight: Block resource (weight) limit check
11
+ * - PrevalidateAttests: Validates `attest` calls prior to execution
12
+ * - StorageWeightReclaim: Storage weight reclaim mechanism
13
+ *
14
+ * These extensions have empty struct or tuple types and don't need explicit implementation.
15
+ */
16
+ export class FallbackSignedExtension extends SignedExtension {
17
+ extensionIdent;
18
+ constructor(client, options, extensionIdent) {
19
+ super(client, options);
20
+ this.extensionIdent = extensionIdent;
21
+ }
22
+ get identifier() {
23
+ return this.extensionIdent;
24
+ }
25
+ async init() {
26
+ // Initialize with empty data and additionalSigned
27
+ this.data = {};
28
+ this.additionalSigned = [];
29
+ }
30
+ toPayload() {
31
+ return {}; // No payload contribution
32
+ }
33
+ }
34
+ /**
35
+ * Checks if a type is an empty struct or tuple (doesn't require external input).
36
+ *
37
+ * @param registry The portable registry
38
+ * @param typeId The type ID to check
39
+ * @returns True if the type is an empty struct or tuple, false otherwise
40
+ */
41
+ export function isEmptyStructOrTuple(registry, typeId) {
42
+ try {
43
+ const type = registry.findType(typeId);
44
+ // Check if it's an empty struct
45
+ if (type.typeDef.type === 'Struct' && type.typeDef.value.fields.length === 0) {
46
+ return true;
47
+ }
48
+ // Check if it's an empty tuple
49
+ if (type.typeDef.type === 'Tuple' && type.typeDef.value.fields.length === 0) {
50
+ return true;
51
+ }
52
+ }
53
+ catch (error) {
54
+ // Ignore errors
55
+ }
56
+ // All other cases (including errors) require input
57
+ return false;
58
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './SignedExtension.js';
2
2
  export * from './known/index.js';
3
3
  export * from './ExtraSignedExtension.js';
4
+ export * from './FallbackSignedExtension.js';
@@ -1,3 +1,4 @@
1
1
  export * from './SignedExtension.js';
2
2
  export * from './known/index.js';
3
3
  export * from './ExtraSignedExtension.js';
4
+ export * from './FallbackSignedExtension.js';
@@ -1,3 +1,14 @@
1
+ /**
2
+ * Known signed extensions that require external input.
3
+ *
4
+ * Note: Extensions that don't require external input are automatically handled by FallbackSignedExtension:
5
+ * - CheckNonZeroSender
6
+ * - CheckWeight
7
+ * - PrevalidateAttests
8
+ * - StorageWeightReclaim
9
+ *
10
+ * These extensions have empty struct or tuple types and don't need explicit implementation.
11
+ */
1
12
  import { ISignedExtension } from '../SignedExtension.js';
2
13
  export type AnySignedExtension = new (...args: any[]) => ISignedExtension;
3
14
  export declare const knownSignedExtensions: Record<string, AnySignedExtension>;
@@ -1,26 +1,29 @@
1
+ /**
2
+ * Known signed extensions that require external input.
3
+ *
4
+ * Note: Extensions that don't require external input are automatically handled by FallbackSignedExtension:
5
+ * - CheckNonZeroSender
6
+ * - CheckWeight
7
+ * - PrevalidateAttests
8
+ * - StorageWeightReclaim
9
+ *
10
+ * These extensions have empty struct or tuple types and don't need explicit implementation.
11
+ */
1
12
  import { ChargeAssetTxPayment } from './ChargeAssetTxPayment.js';
2
13
  import { ChargeTransactionPayment } from './ChargeTransactionPayment.js';
3
14
  import { CheckGenesis } from './CheckGenesis.js';
4
15
  import { CheckMetadataHash } from './CheckMetadataHash.js';
5
16
  import { CheckMortality } from './CheckMortality.js';
6
- import { CheckNonZeroSender } from './CheckNonZeroSender.js';
7
17
  import { CheckNonce } from './CheckNonce.js';
8
18
  import { CheckSpecVersion } from './CheckSpecVersion.js';
9
19
  import { CheckTxVersion } from './CheckTxVersion.js';
10
- import { CheckWeight } from './CheckWeight.js';
11
- import { PrevalidateAttests } from './PrevalidateAttests.js';
12
- import { StorageWeightReclaim } from './StorageWeightReclaim.js';
13
20
  export const knownSignedExtensions = {
14
- CheckNonZeroSender,
15
21
  CheckSpecVersion,
16
22
  CheckTxVersion,
17
23
  CheckGenesis,
18
24
  CheckMortality,
19
25
  CheckNonce,
20
- CheckWeight,
21
26
  ChargeTransactionPayment,
22
- PrevalidateAttests,
23
27
  ChargeAssetTxPayment,
24
28
  CheckMetadataHash,
25
- StorageWeightReclaim,
26
29
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dedot/api",
3
- "version": "0.8.1",
3
+ "version": "0.8.2-next.6cfafabd.24+6cfafab",
4
4
  "description": "A delightful JavaScript/TypeScript client for Polkadot & Substrate",
5
5
  "author": "Thang X. Vu <thang@coongcrafts.io>",
6
6
  "homepage": "https://github.com/dedotdev/dedot/tree/main/packages/api",
@@ -13,18 +13,18 @@
13
13
  "type": "module",
14
14
  "sideEffects": false,
15
15
  "dependencies": {
16
- "@dedot/codecs": "0.8.1",
17
- "@dedot/providers": "0.8.1",
18
- "@dedot/runtime-specs": "0.8.1",
19
- "@dedot/shape": "0.8.1",
20
- "@dedot/storage": "0.8.1",
21
- "@dedot/types": "0.8.1",
22
- "@dedot/utils": "0.8.1"
16
+ "@dedot/codecs": "0.8.2-next.6cfafabd.24+6cfafab",
17
+ "@dedot/providers": "0.8.2-next.6cfafabd.24+6cfafab",
18
+ "@dedot/runtime-specs": "0.8.2-next.6cfafabd.24+6cfafab",
19
+ "@dedot/shape": "0.8.2-next.6cfafabd.24+6cfafab",
20
+ "@dedot/storage": "0.8.2-next.6cfafabd.24+6cfafab",
21
+ "@dedot/types": "0.8.2-next.6cfafabd.24+6cfafab",
22
+ "@dedot/utils": "0.8.2-next.6cfafabd.24+6cfafab"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json",
26
26
  "clean": "rm -rf ./dist && rm -rf ./tsconfig.tsbuildinfo ./tsconfig.build.tsbuildinfo",
27
- "test": "vitest --watch=false"
27
+ "test": "npx vitest --watch=false"
28
28
  },
29
29
  "exports": {
30
30
  ".": {
@@ -48,7 +48,7 @@
48
48
  "node": ">=18"
49
49
  },
50
50
  "license": "Apache-2.0",
51
- "gitHead": "03ad1a7c81a90b78b39206b30fc96a65537faaee",
51
+ "gitHead": "6cfafabd52dbc7e5bd3ded135e9617fc412bba2b",
52
52
  "module": "./index.js",
53
53
  "types": "./index.d.ts"
54
54
  }
@@ -0,0 +1,41 @@
1
+ import { BlockHash, StorageData, StorageKey } from '@dedot/codecs';
2
+ import type { Callback, RpcVersion, Unsub, VersionedGenericSubstrateApi } from '@dedot/types';
3
+ import type { SubstrateApi } from '../chaintypes/index.js';
4
+ import { BaseSubstrateClient } from '../client/BaseSubstrateClient.js';
5
+ /**
6
+ * @name BaseStorageQuery
7
+ * @description
8
+ * An abstract service that provides functionality for querying and subscribing to
9
+ * multiple storage items. This service is designed to be extended by version-specific
10
+ * implementations that handle the details of interacting with different JSON-RPC APIs.
11
+ *
12
+ * The service provides a simple interface for:
13
+ * - Querying multiple storage keys in a single call
14
+ * - Subscribing to changes in multiple storage keys
15
+ *
16
+ * This abstraction eliminates code duplication between different client implementations
17
+ * and provides a consistent interface for storage operations.
18
+ */
19
+ export declare abstract class BaseStorageQuery<Rv extends RpcVersion, ChainApi extends VersionedGenericSubstrateApi = SubstrateApi, T extends BaseSubstrateClient<Rv, ChainApi> = BaseSubstrateClient<Rv, ChainApi>> {
20
+ protected client: T;
21
+ /**
22
+ * @param client - The substrate client instance
23
+ */
24
+ constructor(client: T);
25
+ /**
26
+ * Query multiple storage items in a single call
27
+ *
28
+ * @param keys - Array of storage keys to query
29
+ * @param at - Optional block hash to query at
30
+ * @returns Promise resolving to a record mapping storage keys to their values
31
+ */
32
+ abstract query(keys: StorageKey[], at?: BlockHash): Promise<Record<StorageKey, StorageData | undefined>>;
33
+ /**
34
+ * Subscribe to multiple storage items
35
+ *
36
+ * @param keys - Array of storage keys to subscribe to
37
+ * @param callback - Function to call when storage values change
38
+ * @returns Promise resolving to an unsubscribe function
39
+ */
40
+ abstract subscribe(keys: StorageKey[], callback: Callback<Record<StorageKey, StorageData | undefined>>): Promise<Unsub>;
41
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @name BaseStorageQuery
3
+ * @description
4
+ * An abstract service that provides functionality for querying and subscribing to
5
+ * multiple storage items. This service is designed to be extended by version-specific
6
+ * implementations that handle the details of interacting with different JSON-RPC APIs.
7
+ *
8
+ * The service provides a simple interface for:
9
+ * - Querying multiple storage keys in a single call
10
+ * - Subscribing to changes in multiple storage keys
11
+ *
12
+ * This abstraction eliminates code duplication between different client implementations
13
+ * and provides a consistent interface for storage operations.
14
+ */
15
+ export class BaseStorageQuery {
16
+ client;
17
+ /**
18
+ * @param client - The substrate client instance
19
+ */
20
+ constructor(client) {
21
+ this.client = client;
22
+ }
23
+ }
@@ -0,0 +1,35 @@
1
+ import { BlockHash, StorageData, StorageKey } from '@dedot/codecs';
2
+ import type { Callback, RpcLegacy, Unsub, VersionedGenericSubstrateApi } from '@dedot/types';
3
+ import type { SubstrateApi } from '../chaintypes/index.js';
4
+ import { LegacyClient } from '../client/LegacyClient.js';
5
+ import { BaseStorageQuery } from './BaseStorageQuery.js';
6
+ /**
7
+ * @name LegacyStorageQuery
8
+ * @description
9
+ * Implementation of BaseStorageQuery for the legacy JSON-RPC API (v1).
10
+ * This service handles storage queries using the state_queryStorageAt and
11
+ * state_subscribeStorage RPC methods.
12
+ *
13
+ * It provides:
14
+ * - One-time queries using state_queryStorageAt
15
+ * - Subscriptions using state_subscribeStorage
16
+ * - Efficient change tracking for subscriptions
17
+ */
18
+ export declare class LegacyStorageQuery<ChainApi extends VersionedGenericSubstrateApi = SubstrateApi> extends BaseStorageQuery<RpcLegacy, ChainApi, LegacyClient<ChainApi>> {
19
+ /**
20
+ * Query multiple storage items in a single call using state_queryStorageAt
21
+ *
22
+ * @param keys - Array of storage keys to query
23
+ * @param at - Optional block hash to query at (defaults to current/best block)
24
+ * @returns Promise resolving to a record mapping storage keys to their values
25
+ */
26
+ query(keys: StorageKey[], at?: BlockHash): Promise<Record<StorageKey, StorageData | undefined>>;
27
+ /**
28
+ * Subscribe to multiple storage items using state_subscribeStorage
29
+ *
30
+ * @param keys - Array of storage keys to subscribe to
31
+ * @param callback - Function to call when storage values change
32
+ * @returns Promise resolving to an unsubscribe function
33
+ */
34
+ subscribe(keys: StorageKey[], callback: Callback<Record<StorageKey, StorageData | undefined>>): Promise<Unsub>;
35
+ }
@@ -0,0 +1,63 @@
1
+ import { BaseStorageQuery } from './BaseStorageQuery.js';
2
+ /**
3
+ * @name LegacyStorageQuery
4
+ * @description
5
+ * Implementation of BaseStorageQuery for the legacy JSON-RPC API (v1).
6
+ * This service handles storage queries using the state_queryStorageAt and
7
+ * state_subscribeStorage RPC methods.
8
+ *
9
+ * It provides:
10
+ * - One-time queries using state_queryStorageAt
11
+ * - Subscriptions using state_subscribeStorage
12
+ * - Efficient change tracking for subscriptions
13
+ */
14
+ export class LegacyStorageQuery extends BaseStorageQuery {
15
+ /**
16
+ * Query multiple storage items in a single call using state_queryStorageAt
17
+ *
18
+ * @param keys - Array of storage keys to query
19
+ * @param at - Optional block hash to query at (defaults to current/best block)
20
+ * @returns Promise resolving to a record mapping storage keys to their values
21
+ */
22
+ async query(keys, at) {
23
+ // Query storage at the specified block or current block
24
+ const changeSets = at
25
+ ? await this.client.rpc.state_queryStorageAt(keys, at)
26
+ : await this.client.rpc.state_queryStorageAt(keys);
27
+ // Create a map of key -> value for easy lookup
28
+ const results = {};
29
+ // Initialize all keys with undefined
30
+ keys.forEach(key => results[key] = undefined);
31
+ // Update with actual values from the response
32
+ if (changeSets && changeSets.length > 0) {
33
+ changeSets[0].changes.forEach(([key, value]) => {
34
+ results[key] = value ?? undefined;
35
+ });
36
+ }
37
+ return results;
38
+ }
39
+ /**
40
+ * Subscribe to multiple storage items using state_subscribeStorage
41
+ *
42
+ * @param keys - Array of storage keys to subscribe to
43
+ * @param callback - Function to call when storage values change
44
+ * @returns Promise resolving to an unsubscribe function
45
+ */
46
+ async subscribe(keys, callback) {
47
+ // Track the latest changes for each key
48
+ const lastChanges = {};
49
+ // Initialize all keys with undefined
50
+ keys.forEach(key => lastChanges[key] = undefined);
51
+ // Subscribe to storage changes
52
+ return this.client.rpc.state_subscribeStorage(keys, (changeSet) => {
53
+ // Update the latest changes
54
+ changeSet.changes.forEach(([key, value]) => {
55
+ if (lastChanges[key] !== value) {
56
+ lastChanges[key] = value ?? undefined;
57
+ }
58
+ });
59
+ // Call the callback with the updated map
60
+ callback({ ...lastChanges });
61
+ });
62
+ }
63
+ }
@@ -0,0 +1,35 @@
1
+ import { BlockHash, StorageData, StorageKey } from '@dedot/codecs';
2
+ import type { Callback, RpcV2, Unsub, VersionedGenericSubstrateApi } from '@dedot/types';
3
+ import type { SubstrateApi } from '../chaintypes/index.js';
4
+ import { DedotClient } from '../client/DedotClient.js';
5
+ import { BaseStorageQuery } from './BaseStorageQuery.js';
6
+ /**
7
+ * @name NewStorageQuery
8
+ * @description
9
+ * Implementation of BaseStorageQuery for the new JSON-RPC API (v2).
10
+ * This service handles storage queries using the chainHead_storage RPC method
11
+ * and chainHead events for subscriptions.
12
+ *
13
+ * It provides:
14
+ * - One-time queries using chainHead_storage
15
+ * - Subscriptions using chainHead 'bestBlock' events
16
+ * - Efficient change detection for subscriptions
17
+ */
18
+ export declare class NewStorageQuery<ChainApi extends VersionedGenericSubstrateApi = SubstrateApi> extends BaseStorageQuery<RpcV2, ChainApi, DedotClient<ChainApi>> {
19
+ /**
20
+ * Query multiple storage items in a single call using chainHead_storage
21
+ *
22
+ * @param keys - Array of storage keys to query
23
+ * @param at - Optional block hash to query at (defaults to best block)
24
+ * @returns Promise resolving to a record mapping storage keys to their values
25
+ */
26
+ query(keys: StorageKey[], at?: BlockHash): Promise<Record<StorageKey, StorageData | undefined>>;
27
+ /**
28
+ * Subscribe to multiple storage items using chainHead 'bestBlock' events
29
+ *
30
+ * @param keys - Array of storage keys to subscribe to
31
+ * @param callback - Function to call when storage values change
32
+ * @returns Promise resolving to an unsubscribe function
33
+ */
34
+ subscribe(keys: StorageKey[], callback: Callback<Record<StorageKey, StorageData | undefined>>): Promise<Unsub>;
35
+ }
@@ -0,0 +1,81 @@
1
+ import { BaseStorageQuery } from './BaseStorageQuery.js';
2
+ /**
3
+ * @name NewStorageQuery
4
+ * @description
5
+ * Implementation of BaseStorageQuery for the new JSON-RPC API (v2).
6
+ * This service handles storage queries using the chainHead_storage RPC method
7
+ * and chainHead events for subscriptions.
8
+ *
9
+ * It provides:
10
+ * - One-time queries using chainHead_storage
11
+ * - Subscriptions using chainHead 'bestBlock' events
12
+ * - Efficient change detection for subscriptions
13
+ */
14
+ export class NewStorageQuery extends BaseStorageQuery {
15
+ /**
16
+ * Query multiple storage items in a single call using chainHead_storage
17
+ *
18
+ * @param keys - Array of storage keys to query
19
+ * @param at - Optional block hash to query at (defaults to best block)
20
+ * @returns Promise resolving to a record mapping storage keys to their values
21
+ */
22
+ async query(keys, at) {
23
+ // Query storage using ChainHead API
24
+ const storageQueries = keys.map(key => ({ type: 'value', key }));
25
+ // Use the provided block hash or skip it in tests
26
+ const rawResults = at
27
+ ? await this.client.chainHead.storage(storageQueries, undefined, at)
28
+ : await this.client.chainHead.storage(storageQueries);
29
+ // Create a map of key -> value for easy lookup
30
+ const results = {};
31
+ // Initialize all keys with undefined
32
+ keys.forEach(key => results[key] = undefined);
33
+ // Update with actual values from the response
34
+ rawResults.forEach((result) => {
35
+ results[result.key] = result.value ?? undefined;
36
+ });
37
+ return results;
38
+ }
39
+ /**
40
+ * Subscribe to multiple storage items using chainHead 'bestBlock' events
41
+ *
42
+ * @param keys - Array of storage keys to subscribe to
43
+ * @param callback - Function to call when storage values change
44
+ * @returns Promise resolving to an unsubscribe function
45
+ */
46
+ async subscribe(keys, callback) {
47
+ // Get the best block
48
+ const best = await this.client.chainHead.bestBlock();
49
+ // Track the latest changes for each key
50
+ const latestChanges = {};
51
+ // Function to pull storage values and call the callback if there are changes
52
+ const pull = async ({ hash }) => {
53
+ // Query storage using ChainHead API
54
+ const storageQueries = keys.map(key => ({ type: 'value', key }));
55
+ const rawResults = await this.client.chainHead.storage(storageQueries, undefined, hash);
56
+ let changed = false;
57
+ // Create a map for easy lookup
58
+ const results = {};
59
+ rawResults.forEach((result) => {
60
+ results[result.key] = result.value ?? undefined;
61
+ });
62
+ keys.forEach((key) => {
63
+ const newValue = results[key];
64
+ if (Object.keys(latestChanges).length > 0 && latestChanges[key] === newValue)
65
+ return;
66
+ changed = true;
67
+ latestChanges[key] = newValue;
68
+ });
69
+ if (!changed)
70
+ return;
71
+ callback({ ...latestChanges });
72
+ };
73
+ // Initial pull
74
+ await pull(best);
75
+ // Subscribe to best block events
76
+ const unsub = this.client.chainHead.on('bestBlock', pull);
77
+ return async () => {
78
+ unsub();
79
+ };
80
+ }
81
+ }
@@ -1 +1,4 @@
1
1
  export * from './QueryableStorage.js';
2
+ export * from './BaseStorageQuery.js';
3
+ export * from './LegacyStorageQuery.js';
4
+ export * from './NewStorageQuery.js';
package/storage/index.js CHANGED
@@ -1 +1,4 @@
1
1
  export * from './QueryableStorage.js';
2
+ export * from './BaseStorageQuery.js';
3
+ export * from './LegacyStorageQuery.js';
4
+ export * from './NewStorageQuery.js';