@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
@@ -7,6 +7,7 @@ const utils_1 = require("@dedot/utils");
7
7
  const index_js_1 = require("../executor/index.js");
8
8
  const index_js_2 = require("../json-rpc/index.js");
9
9
  const proxychain_js_1 = require("../proxychain.js");
10
+ const index_js_3 = require("../storage/index.js");
10
11
  const SUPPORTED_METADATA_VERSIONS = [15, 14];
11
12
  const MetadataApiHash = (0, utils_1.calcRuntimeApiHash)('Metadata'); // 0x37e397fc7c91f5e4
12
13
  const MESSAGE = 'Make sure to call `.connect()` method first before using the API interfaces.';
@@ -110,7 +111,7 @@ class BaseSubstrateClient extends index_js_2.JsonRpcClient {
110
111
  try {
111
112
  // Get all keys that start with RAW_META/
112
113
  const allKeys = await this._localCache.keys();
113
- const metadataKeys = allKeys.filter(k => k.startsWith('RAW_META/') && k !== key);
114
+ const metadataKeys = allKeys.filter((k) => k.startsWith('RAW_META/') && k !== key);
114
115
  // Remove all other metadata entries
115
116
  for (const metaKey of metadataKeys) {
116
117
  await this._localCache.remove(metaKey);
@@ -308,5 +309,33 @@ class BaseSubstrateClient extends index_js_2.JsonRpcClient {
308
309
  setSigner(signer) {
309
310
  this._options.signer = signer;
310
311
  }
312
+ async queryMulti(queries, callback) {
313
+ // Extract keys from queries
314
+ const keys = queries.map((q) => q.fn.rawKey(...(q.args || [])));
315
+ const decodeValue = (query, rawValue) => {
316
+ // Get the QueryableStorage instance from the query function
317
+ const entry = new index_js_3.QueryableStorage(this.registry, query.meta.pallet, query.meta.name);
318
+ // Decode the value
319
+ return entry.decodeValue(rawValue);
320
+ };
321
+ // Create service directly when needed
322
+ let service;
323
+ if (this.rpcVersion === 'v2') {
324
+ service = new index_js_3.NewStorageQuery(this);
325
+ }
326
+ else {
327
+ service = new index_js_3.LegacyStorageQuery(this);
328
+ }
329
+ // If a callback is provided, set up a subscription
330
+ if (callback) {
331
+ return service.subscribe(keys, (results) => {
332
+ callback(queries.map((q, i) => decodeValue(q.fn, results[keys[i]])));
333
+ });
334
+ }
335
+ else {
336
+ const results = await service.query(keys);
337
+ return queries.map((q, i) => decodeValue(q.fn, results[keys[i]]));
338
+ }
339
+ }
311
340
  }
312
341
  exports.BaseSubstrateClient = BaseSubstrateClient;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StorageQueryExecutor = void 0;
4
4
  const utils_1 = require("@dedot/utils");
5
- const QueryableStorage_js_1 = require("../storage/QueryableStorage.js");
5
+ const index_js_1 = require("../storage/index.js");
6
6
  const Executor_js_1 = require("./Executor.js");
7
7
  const DEFAULT_KEYS_PAGE_SIZE = 1000;
8
8
  const DEFAULT_ENTRIES_PAGE_SIZE = 250;
@@ -12,7 +12,7 @@ const DEFAULT_ENTRIES_PAGE_SIZE = 250;
12
12
  */
13
13
  class StorageQueryExecutor extends Executor_js_1.Executor {
14
14
  doExecute(pallet, storage) {
15
- const entry = new QueryableStorage_js_1.QueryableStorage(this.registry, pallet, storage);
15
+ const entry = new index_js_1.QueryableStorage(this.registry, pallet, storage);
16
16
  const extractArgs = (args) => {
17
17
  const inArgs = args.slice();
18
18
  const lastArg = args.at(-1);
@@ -101,22 +101,15 @@ class StorageQueryExecutor extends Executor_js_1.Executor {
101
101
  };
102
102
  return { pagedKeys, pagedEntries };
103
103
  }
104
+ getStorageQuery() {
105
+ return new index_js_1.LegacyStorageQuery(this.client);
106
+ }
104
107
  async queryStorage(keys, hash) {
105
- const changeSets = await this.client.rpc.state_queryStorageAt(keys, hash);
106
- return changeSets[0].changes.reduce((o, [key, value]) => {
107
- o[key] = value ?? undefined;
108
- return o;
109
- }, {});
108
+ return this.getStorageQuery().query(keys, hash);
110
109
  }
111
110
  subscribeStorage(keys, callback) {
112
- const lastChanges = {};
113
- return this.client.rpc.state_subscribeStorage(keys, (changeSet) => {
114
- changeSet.changes.forEach(([key, value]) => {
115
- if (lastChanges[key] !== value) {
116
- lastChanges[key] = value ?? undefined;
117
- }
118
- });
119
- return callback(keys.map((key) => lastChanges[key]));
111
+ return this.getStorageQuery().subscribe(keys, (results) => {
112
+ callback(keys.map((key) => results[key]));
120
113
  });
121
114
  }
122
115
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StorageQueryExecutorV2 = void 0;
4
4
  const utils_1 = require("@dedot/utils");
5
+ const index_js_1 = require("../../storage/index.js");
5
6
  const StorageQueryExecutor_js_1 = require("../StorageQueryExecutor.js");
6
7
  /**
7
8
  * @name StorageQueryExecutorV2
@@ -31,39 +32,12 @@ class StorageQueryExecutorV2 extends StorageQueryExecutor_js_1.StorageQueryExecu
31
32
  };
32
33
  return { entries };
33
34
  }
34
- async queryStorage(keys, at) {
35
- const results = await this.chainHead.storage(keys.map((key) => ({ type: 'value', key })), undefined, at);
36
- return results.reduce((o, r) => {
37
- o[r.key] = (r.value ?? undefined);
38
- return o;
39
- }, {});
40
- }
41
- async subscribeStorage(keys, callback) {
42
- let best = await this.chainHead.bestBlock();
43
- let eventToListen = 'bestBlock';
44
- // TODO subscribe to finalized data source
45
- // initialHash = this.chainHead.finalizedHash;
46
- // eventToListen = 'finalizedBlock';
47
- const latestChanges = new Map();
48
- const pull = async ({ hash }) => {
49
- const results = await this.queryStorage(keys, hash);
50
- let changed = false;
51
- keys.forEach((key) => {
52
- const newValue = results[key];
53
- if (latestChanges.size > 0 && latestChanges.get(key) === newValue)
54
- return;
55
- changed = true;
56
- latestChanges.set(key, newValue);
57
- });
58
- if (!changed)
59
- return;
60
- callback(keys.map((key) => latestChanges.get(key)));
61
- };
62
- await pull(best);
63
- const unsub = this.chainHead.on(eventToListen, pull);
64
- return async () => {
65
- unsub();
66
- };
35
+ getStorageQuery() {
36
+ // @ts-ignore little trick to make querying data client.at instance works here,
37
+ // TODO need to rethink about this
38
+ if (!this.client['chainHead'])
39
+ this.client['chainHead'] = this.chainHead;
40
+ return new index_js_1.NewStorageQuery(this.client);
67
41
  }
68
42
  }
69
43
  exports.StorageQueryExecutorV2 = StorageQueryExecutorV2;
@@ -27,6 +27,7 @@ exports.ExtraSignedExtension = void 0;
27
27
  const $ = __importStar(require("@dedot/shape"));
28
28
  const utils_1 = require("@dedot/utils");
29
29
  const SignedExtension_js_1 = require("./SignedExtension.js");
30
+ const FallbackSignedExtension_js_1 = require("./FallbackSignedExtension.js");
30
31
  const index_js_1 = require("./known/index.js");
31
32
  class ExtraSignedExtension extends SignedExtension_js_1.SignedExtension {
32
33
  #signedExtensions;
@@ -60,13 +61,31 @@ class ExtraSignedExtension extends SignedExtension_js_1.SignedExtension {
60
61
  const { signedExtensions: userSignedExtensions = {} } = this.client.options;
61
62
  const Extension = userSignedExtensions[extDef.ident] ||
62
63
  index_js_1.knownSignedExtensions[extDef.ident];
63
- (0, utils_1.assert)(Extension, `SignedExtension for ${extDef.ident} not found`);
64
- return new Extension(this.client, {
65
- ...(0, utils_1.ensurePresence)(this.options),
66
- def: extDef,
67
- });
64
+ if (Extension) {
65
+ return new Extension(this.client, {
66
+ ...(0, utils_1.ensurePresence)(this.options),
67
+ def: extDef,
68
+ });
69
+ }
70
+ else if (this.isRequireNoExternalInputs(extDef)) {
71
+ return new FallbackSignedExtension_js_1.FallbackSignedExtension(this.client, {
72
+ ...(0, utils_1.ensurePresence)(this.options),
73
+ def: extDef,
74
+ }, extDef.ident);
75
+ }
76
+ // For extensions that require input but aren't implemented, throw an error
77
+ throw new Error(`SignedExtension for ${extDef.ident} requires input but is not implemented`);
68
78
  });
69
79
  }
80
+ /**
81
+ * Check if the extension requires no external inputs (e.g: struct or tuple with empty types like `()` or `[]`)
82
+ * @param extDef - The definition of the signed extension
83
+ * @returns boolean
84
+ */
85
+ isRequireNoExternalInputs(extDef) {
86
+ return ((0, FallbackSignedExtension_js_1.isEmptyStructOrTuple)(this.registry, extDef.typeId) &&
87
+ (0, FallbackSignedExtension_js_1.isEmptyStructOrTuple)(this.registry, extDef.additionalSigned));
88
+ }
70
89
  toPayload(call = '0x') {
71
90
  const signedExtensions = this.#signedExtensions.map((se) => se.identifier);
72
91
  const { version } = this.registry.metadata.extrinsic;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isEmptyStructOrTuple = exports.FallbackSignedExtension = void 0;
4
+ const SignedExtension_js_1 = require("./SignedExtension.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
+ class FallbackSignedExtension extends SignedExtension_js_1.SignedExtension {
20
+ extensionIdent;
21
+ constructor(client, options, extensionIdent) {
22
+ super(client, options);
23
+ this.extensionIdent = extensionIdent;
24
+ }
25
+ get identifier() {
26
+ return this.extensionIdent;
27
+ }
28
+ async init() {
29
+ // Initialize with empty data and additionalSigned
30
+ this.data = {};
31
+ this.additionalSigned = [];
32
+ }
33
+ toPayload() {
34
+ return {}; // No payload contribution
35
+ }
36
+ }
37
+ exports.FallbackSignedExtension = FallbackSignedExtension;
38
+ /**
39
+ * Checks if a type is an empty struct or tuple (doesn't require external input).
40
+ *
41
+ * @param registry The portable registry
42
+ * @param typeId The type ID to check
43
+ * @returns True if the type is an empty struct or tuple, false otherwise
44
+ */
45
+ function isEmptyStructOrTuple(registry, typeId) {
46
+ try {
47
+ const type = registry.findType(typeId);
48
+ // Check if it's an empty struct
49
+ if (type.typeDef.type === 'Struct' && type.typeDef.value.fields.length === 0) {
50
+ return true;
51
+ }
52
+ // Check if it's an empty tuple
53
+ if (type.typeDef.type === 'Tuple' && type.typeDef.value.fields.length === 0) {
54
+ return true;
55
+ }
56
+ }
57
+ catch (error) {
58
+ // Ignore errors
59
+ }
60
+ // All other cases (including errors) require input
61
+ return false;
62
+ }
63
+ exports.isEmptyStructOrTuple = isEmptyStructOrTuple;
@@ -17,3 +17,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./SignedExtension.js"), exports);
18
18
  __exportStar(require("./known/index.js"), exports);
19
19
  __exportStar(require("./ExtraSignedExtension.js"), exports);
20
+ __exportStar(require("./FallbackSignedExtension.js"), exports);
@@ -1,4 +1,15 @@
1
1
  "use strict";
2
+ /**
3
+ * Known signed extensions that require external input.
4
+ *
5
+ * Note: Extensions that don't require external input are automatically handled by FallbackSignedExtension:
6
+ * - CheckNonZeroSender
7
+ * - CheckWeight
8
+ * - PrevalidateAttests
9
+ * - StorageWeightReclaim
10
+ *
11
+ * These extensions have empty struct or tuple types and don't need explicit implementation.
12
+ */
2
13
  Object.defineProperty(exports, "__esModule", { value: true });
3
14
  exports.knownSignedExtensions = void 0;
4
15
  const ChargeAssetTxPayment_js_1 = require("./ChargeAssetTxPayment.js");
@@ -6,24 +17,16 @@ const ChargeTransactionPayment_js_1 = require("./ChargeTransactionPayment.js");
6
17
  const CheckGenesis_js_1 = require("./CheckGenesis.js");
7
18
  const CheckMetadataHash_js_1 = require("./CheckMetadataHash.js");
8
19
  const CheckMortality_js_1 = require("./CheckMortality.js");
9
- const CheckNonZeroSender_js_1 = require("./CheckNonZeroSender.js");
10
20
  const CheckNonce_js_1 = require("./CheckNonce.js");
11
21
  const CheckSpecVersion_js_1 = require("./CheckSpecVersion.js");
12
22
  const CheckTxVersion_js_1 = require("./CheckTxVersion.js");
13
- const CheckWeight_js_1 = require("./CheckWeight.js");
14
- const PrevalidateAttests_js_1 = require("./PrevalidateAttests.js");
15
- const StorageWeightReclaim_js_1 = require("./StorageWeightReclaim.js");
16
23
  exports.knownSignedExtensions = {
17
- CheckNonZeroSender: CheckNonZeroSender_js_1.CheckNonZeroSender,
18
24
  CheckSpecVersion: CheckSpecVersion_js_1.CheckSpecVersion,
19
25
  CheckTxVersion: CheckTxVersion_js_1.CheckTxVersion,
20
26
  CheckGenesis: CheckGenesis_js_1.CheckGenesis,
21
27
  CheckMortality: CheckMortality_js_1.CheckMortality,
22
28
  CheckNonce: CheckNonce_js_1.CheckNonce,
23
- CheckWeight: CheckWeight_js_1.CheckWeight,
24
29
  ChargeTransactionPayment: ChargeTransactionPayment_js_1.ChargeTransactionPayment,
25
- PrevalidateAttests: PrevalidateAttests_js_1.PrevalidateAttests,
26
30
  ChargeAssetTxPayment: ChargeAssetTxPayment_js_1.ChargeAssetTxPayment,
27
31
  CheckMetadataHash: CheckMetadataHash_js_1.CheckMetadataHash,
28
- StorageWeightReclaim: StorageWeightReclaim_js_1.StorageWeightReclaim,
29
32
  };
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseStorageQuery = void 0;
4
+ /**
5
+ * @name BaseStorageQuery
6
+ * @description
7
+ * An abstract service that provides functionality for querying and subscribing to
8
+ * multiple storage items. This service is designed to be extended by version-specific
9
+ * implementations that handle the details of interacting with different JSON-RPC APIs.
10
+ *
11
+ * The service provides a simple interface for:
12
+ * - Querying multiple storage keys in a single call
13
+ * - Subscribing to changes in multiple storage keys
14
+ *
15
+ * This abstraction eliminates code duplication between different client implementations
16
+ * and provides a consistent interface for storage operations.
17
+ */
18
+ class BaseStorageQuery {
19
+ client;
20
+ /**
21
+ * @param client - The substrate client instance
22
+ */
23
+ constructor(client) {
24
+ this.client = client;
25
+ }
26
+ }
27
+ exports.BaseStorageQuery = BaseStorageQuery;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LegacyStorageQuery = void 0;
4
+ const BaseStorageQuery_js_1 = require("./BaseStorageQuery.js");
5
+ /**
6
+ * @name LegacyStorageQuery
7
+ * @description
8
+ * Implementation of BaseStorageQuery for the legacy JSON-RPC API (v1).
9
+ * This service handles storage queries using the state_queryStorageAt and
10
+ * state_subscribeStorage RPC methods.
11
+ *
12
+ * It provides:
13
+ * - One-time queries using state_queryStorageAt
14
+ * - Subscriptions using state_subscribeStorage
15
+ * - Efficient change tracking for subscriptions
16
+ */
17
+ class LegacyStorageQuery extends BaseStorageQuery_js_1.BaseStorageQuery {
18
+ /**
19
+ * Query multiple storage items in a single call using state_queryStorageAt
20
+ *
21
+ * @param keys - Array of storage keys to query
22
+ * @param at - Optional block hash to query at (defaults to current/best block)
23
+ * @returns Promise resolving to a record mapping storage keys to their values
24
+ */
25
+ async query(keys, at) {
26
+ // Query storage at the specified block or current block
27
+ const changeSets = at
28
+ ? await this.client.rpc.state_queryStorageAt(keys, at)
29
+ : await this.client.rpc.state_queryStorageAt(keys);
30
+ // Create a map of key -> value for easy lookup
31
+ const results = {};
32
+ // Initialize all keys with undefined
33
+ keys.forEach(key => results[key] = undefined);
34
+ // Update with actual values from the response
35
+ if (changeSets && changeSets.length > 0) {
36
+ changeSets[0].changes.forEach(([key, value]) => {
37
+ results[key] = value ?? undefined;
38
+ });
39
+ }
40
+ return results;
41
+ }
42
+ /**
43
+ * Subscribe to multiple storage items using state_subscribeStorage
44
+ *
45
+ * @param keys - Array of storage keys to subscribe to
46
+ * @param callback - Function to call when storage values change
47
+ * @returns Promise resolving to an unsubscribe function
48
+ */
49
+ async subscribe(keys, callback) {
50
+ // Track the latest changes for each key
51
+ const lastChanges = {};
52
+ // Initialize all keys with undefined
53
+ keys.forEach(key => lastChanges[key] = undefined);
54
+ // Subscribe to storage changes
55
+ return this.client.rpc.state_subscribeStorage(keys, (changeSet) => {
56
+ // Update the latest changes
57
+ changeSet.changes.forEach(([key, value]) => {
58
+ if (lastChanges[key] !== value) {
59
+ lastChanges[key] = value ?? undefined;
60
+ }
61
+ });
62
+ // Call the callback with the updated map
63
+ callback({ ...lastChanges });
64
+ });
65
+ }
66
+ }
67
+ exports.LegacyStorageQuery = LegacyStorageQuery;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NewStorageQuery = void 0;
4
+ const BaseStorageQuery_js_1 = require("./BaseStorageQuery.js");
5
+ /**
6
+ * @name NewStorageQuery
7
+ * @description
8
+ * Implementation of BaseStorageQuery for the new JSON-RPC API (v2).
9
+ * This service handles storage queries using the chainHead_storage RPC method
10
+ * and chainHead events for subscriptions.
11
+ *
12
+ * It provides:
13
+ * - One-time queries using chainHead_storage
14
+ * - Subscriptions using chainHead 'bestBlock' events
15
+ * - Efficient change detection for subscriptions
16
+ */
17
+ class NewStorageQuery extends BaseStorageQuery_js_1.BaseStorageQuery {
18
+ /**
19
+ * Query multiple storage items in a single call using chainHead_storage
20
+ *
21
+ * @param keys - Array of storage keys to query
22
+ * @param at - Optional block hash to query at (defaults to best block)
23
+ * @returns Promise resolving to a record mapping storage keys to their values
24
+ */
25
+ async query(keys, at) {
26
+ // Query storage using ChainHead API
27
+ const storageQueries = keys.map(key => ({ type: 'value', key }));
28
+ // Use the provided block hash or skip it in tests
29
+ const rawResults = at
30
+ ? await this.client.chainHead.storage(storageQueries, undefined, at)
31
+ : await this.client.chainHead.storage(storageQueries);
32
+ // Create a map of key -> value for easy lookup
33
+ const results = {};
34
+ // Initialize all keys with undefined
35
+ keys.forEach(key => results[key] = undefined);
36
+ // Update with actual values from the response
37
+ rawResults.forEach((result) => {
38
+ results[result.key] = result.value ?? undefined;
39
+ });
40
+ return results;
41
+ }
42
+ /**
43
+ * Subscribe to multiple storage items using chainHead 'bestBlock' events
44
+ *
45
+ * @param keys - Array of storage keys to subscribe to
46
+ * @param callback - Function to call when storage values change
47
+ * @returns Promise resolving to an unsubscribe function
48
+ */
49
+ async subscribe(keys, callback) {
50
+ // Get the best block
51
+ const best = await this.client.chainHead.bestBlock();
52
+ // Track the latest changes for each key
53
+ const latestChanges = {};
54
+ // Function to pull storage values and call the callback if there are changes
55
+ const pull = async ({ hash }) => {
56
+ // Query storage using ChainHead API
57
+ const storageQueries = keys.map(key => ({ type: 'value', key }));
58
+ const rawResults = await this.client.chainHead.storage(storageQueries, undefined, hash);
59
+ let changed = false;
60
+ // Create a map for easy lookup
61
+ const results = {};
62
+ rawResults.forEach((result) => {
63
+ results[result.key] = result.value ?? undefined;
64
+ });
65
+ keys.forEach((key) => {
66
+ const newValue = results[key];
67
+ if (Object.keys(latestChanges).length > 0 && latestChanges[key] === newValue)
68
+ return;
69
+ changed = true;
70
+ latestChanges[key] = newValue;
71
+ });
72
+ if (!changed)
73
+ return;
74
+ callback({ ...latestChanges });
75
+ };
76
+ // Initial pull
77
+ await pull(best);
78
+ // Subscribe to best block events
79
+ const unsub = this.client.chainHead.on('bestBlock', pull);
80
+ return async () => {
81
+ unsub();
82
+ };
83
+ }
84
+ }
85
+ exports.NewStorageQuery = NewStorageQuery;
@@ -15,3 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./QueryableStorage.js"), exports);
18
+ __exportStar(require("./BaseStorageQuery.js"), exports);
19
+ __exportStar(require("./LegacyStorageQuery.js"), exports);
20
+ __exportStar(require("./NewStorageQuery.js"), exports);
@@ -1,7 +1,7 @@
1
1
  import { BlockHash, Hash, Metadata, PortableRegistry, RuntimeVersion } from '@dedot/codecs';
2
2
  import type { JsonRpcProvider } from '@dedot/providers';
3
3
  import { type IStorage } from '@dedot/storage';
4
- import { GenericSubstrateApi, InjectedSigner, RpcVersion, VersionedGenericSubstrateApi } from '@dedot/types';
4
+ import { Callback, GenericStorageQuery, GenericSubstrateApi, InjectedSigner, Query, QueryFnResult, RpcVersion, Unsub, VersionedGenericSubstrateApi } from '@dedot/types';
5
5
  import { Deferred } from '@dedot/utils';
6
6
  import type { SubstrateApi } from '../chaintypes/index.js';
7
7
  import { JsonRpcClient } from '../json-rpc/index.js';
@@ -73,4 +73,35 @@ export declare abstract class BaseSubstrateClient<Rv extends RpcVersion, ChainAp
73
73
  get tx(): ChainApi[Rv]['tx'];
74
74
  at<ChainApiAt extends GenericSubstrateApi = ChainApi[Rv]>(hash: BlockHash): Promise<ISubstrateClientAt<ChainApiAt>>;
75
75
  setSigner(signer?: InjectedSigner): void;
76
+ /**
77
+ * Query multiple storage items in a single call
78
+ *
79
+ * This method allows you to query multiple storage items in a single call or set up a subscription
80
+ * to multiple storage items. It provides type safety for both the query functions and their results.
81
+ *
82
+ * @example
83
+ * // One-time query with type-safe results
84
+ * const [balance, blockNumber] = await client.queryMulti([
85
+ * { fn: client.query.system.account, args: [ALICE] },
86
+ * { fn: client.query.system.number, args: [] }
87
+ * ]);
88
+ * // balance will be typed as AccountInfo
89
+ * // blockNumber will be typed as number
90
+ *
91
+ *
92
+ * @template Fns Array of storage query functions
93
+ * @param queries - Array of query specifications, each with a function and optional arguments
94
+ * @param callback - Optional callback for subscription-based queries
95
+ * @returns For one-time queries: Array of decoded values with proper types; For subscriptions: Unsubscribe function
96
+ */
97
+ queryMulti<Fns extends GenericStorageQuery[]>(queries: {
98
+ [K in keyof Fns]: Query<Fns[K]>;
99
+ }): Promise<{
100
+ [K in keyof Fns]: QueryFnResult<Fns[K]>;
101
+ }>;
102
+ queryMulti<Fns extends GenericStorageQuery[]>(queries: {
103
+ [K in keyof Fns]: Query<Fns[K]>;
104
+ }, callback: Callback<{
105
+ [K in keyof Fns]: QueryFnResult<Fns[K]>;
106
+ }>): Promise<Unsub>;
76
107
  }
@@ -4,6 +4,7 @@ import { calcRuntimeApiHash, deferred, ensurePresence as _ensurePresence, u8aToH
4
4
  import { ConstantExecutor, ErrorExecutor, EventExecutor } from '../executor/index.js';
5
5
  import { isJsonRpcProvider, JsonRpcClient } from '../json-rpc/index.js';
6
6
  import { newProxyChain } from '../proxychain.js';
7
+ import { LegacyStorageQuery, NewStorageQuery, QueryableStorage } from '../storage/index.js';
7
8
  const SUPPORTED_METADATA_VERSIONS = [15, 14];
8
9
  const MetadataApiHash = calcRuntimeApiHash('Metadata'); // 0x37e397fc7c91f5e4
9
10
  const MESSAGE = 'Make sure to call `.connect()` method first before using the API interfaces.';
@@ -106,7 +107,7 @@ export class BaseSubstrateClient extends JsonRpcClient {
106
107
  try {
107
108
  // Get all keys that start with RAW_META/
108
109
  const allKeys = await this._localCache.keys();
109
- const metadataKeys = allKeys.filter(k => k.startsWith('RAW_META/') && k !== key);
110
+ const metadataKeys = allKeys.filter((k) => k.startsWith('RAW_META/') && k !== key);
110
111
  // Remove all other metadata entries
111
112
  for (const metaKey of metadataKeys) {
112
113
  await this._localCache.remove(metaKey);
@@ -304,4 +305,32 @@ export class BaseSubstrateClient extends JsonRpcClient {
304
305
  setSigner(signer) {
305
306
  this._options.signer = signer;
306
307
  }
308
+ async queryMulti(queries, callback) {
309
+ // Extract keys from queries
310
+ const keys = queries.map((q) => q.fn.rawKey(...(q.args || [])));
311
+ const decodeValue = (query, rawValue) => {
312
+ // Get the QueryableStorage instance from the query function
313
+ const entry = new QueryableStorage(this.registry, query.meta.pallet, query.meta.name);
314
+ // Decode the value
315
+ return entry.decodeValue(rawValue);
316
+ };
317
+ // Create service directly when needed
318
+ let service;
319
+ if (this.rpcVersion === 'v2') {
320
+ service = new NewStorageQuery(this);
321
+ }
322
+ else {
323
+ service = new LegacyStorageQuery(this);
324
+ }
325
+ // If a callback is provided, set up a subscription
326
+ if (callback) {
327
+ return service.subscribe(keys, (results) => {
328
+ callback(queries.map((q, i) => decodeValue(q.fn, results[keys[i]])));
329
+ });
330
+ }
331
+ else {
332
+ const results = await service.query(keys);
333
+ return queries.map((q, i) => decodeValue(q.fn, results[keys[i]]));
334
+ }
335
+ }
307
336
  }
@@ -1,7 +1,7 @@
1
1
  import { $H256, PortableRegistry } from '@dedot/codecs';
2
2
  import { u32 } from '@dedot/shape';
3
3
  import { assert, concatU8a, noop, twox64Concat, u8aToHex, xxhashAsU8a } from '@dedot/utils';
4
- import { ConstantExecutor, ErrorExecutor, EventExecutor, RuntimeApiExecutorV2, StorageQueryExecutorV2, TxExecutorV2, } from '../executor/index.js';
4
+ import { ConstantExecutor, ErrorExecutor, EventExecutor, RuntimeApiExecutorV2, StorageQueryExecutorV2, TxExecutorV2 } from '../executor/index.js';
5
5
  import { ChainHead, ChainSpec, Transaction, TransactionWatch } from '../json-rpc/index.js';
6
6
  import { newProxyChain } from '../proxychain.js';
7
7
  import { BaseSubstrateClient, ensurePresence } from './BaseSubstrateClient.js';
@@ -1,6 +1,6 @@
1
1
  import { BlockHash, Option, StorageData, StorageKey } from '@dedot/codecs';
2
- import type { AsyncMethod, Callback, GenericStorageQuery, GenericSubstrateApi, Unsub } from '@dedot/types';
3
- import { QueryableStorage } from '../storage/QueryableStorage.js';
2
+ import type { AsyncMethod, Callback, GenericStorageQuery, GenericSubstrateApi, RpcVersion, Unsub } from '@dedot/types';
3
+ import { type BaseStorageQuery, QueryableStorage } from '../storage/index.js';
4
4
  import { Executor } from './Executor.js';
5
5
  /**
6
6
  * @name StorageQueryExecutor
@@ -9,6 +9,7 @@ import { Executor } from './Executor.js';
9
9
  export declare class StorageQueryExecutor<ChainApi extends GenericSubstrateApi = GenericSubstrateApi> extends Executor<ChainApi> {
10
10
  doExecute(pallet: string, storage: string): GenericStorageQuery;
11
11
  protected exposeStorageMapMethods(entry: QueryableStorage): Record<string, AsyncMethod>;
12
+ protected getStorageQuery(): BaseStorageQuery<RpcVersion>;
12
13
  protected queryStorage(keys: StorageKey[], hash?: BlockHash): Promise<Record<StorageKey, Option<StorageData>>>;
13
14
  protected subscribeStorage(keys: StorageKey[], callback: Callback<Array<StorageData | undefined>>): Promise<Unsub>;
14
15
  }
@@ -1,5 +1,5 @@
1
1
  import { assert, isFunction, isObject } from '@dedot/utils';
2
- import { QueryableStorage } from '../storage/QueryableStorage.js';
2
+ import { LegacyStorageQuery, QueryableStorage } from '../storage/index.js';
3
3
  import { Executor } from './Executor.js';
4
4
  const DEFAULT_KEYS_PAGE_SIZE = 1000;
5
5
  const DEFAULT_ENTRIES_PAGE_SIZE = 250;
@@ -98,22 +98,15 @@ export class StorageQueryExecutor extends Executor {
98
98
  };
99
99
  return { pagedKeys, pagedEntries };
100
100
  }
101
+ getStorageQuery() {
102
+ return new LegacyStorageQuery(this.client);
103
+ }
101
104
  async queryStorage(keys, hash) {
102
- const changeSets = await this.client.rpc.state_queryStorageAt(keys, hash);
103
- return changeSets[0].changes.reduce((o, [key, value]) => {
104
- o[key] = value ?? undefined;
105
- return o;
106
- }, {});
105
+ return this.getStorageQuery().query(keys, hash);
107
106
  }
108
107
  subscribeStorage(keys, callback) {
109
- const lastChanges = {};
110
- return this.client.rpc.state_subscribeStorage(keys, (changeSet) => {
111
- changeSet.changes.forEach(([key, value]) => {
112
- if (lastChanges[key] !== value) {
113
- lastChanges[key] = value ?? undefined;
114
- }
115
- });
116
- return callback(keys.map((key) => lastChanges[key]));
108
+ return this.getStorageQuery().subscribe(keys, (results) => {
109
+ callback(keys.map((key) => results[key]));
117
110
  });
118
111
  }
119
112
  }