@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.
- package/cjs/client/BaseSubstrateClient.js +30 -1
- package/cjs/executor/StorageQueryExecutor.js +8 -15
- package/cjs/executor/v2/StorageQueryExecutorV2.js +7 -33
- package/cjs/extrinsic/extensions/ExtraSignedExtension.js +24 -5
- package/cjs/extrinsic/extensions/FallbackSignedExtension.js +63 -0
- package/cjs/extrinsic/extensions/index.js +1 -0
- package/cjs/extrinsic/extensions/known/index.js +11 -8
- package/cjs/storage/BaseStorageQuery.js +27 -0
- package/cjs/storage/LegacyStorageQuery.js +67 -0
- package/cjs/storage/NewStorageQuery.js +85 -0
- package/cjs/storage/index.js +3 -0
- package/client/BaseSubstrateClient.d.ts +32 -1
- package/client/BaseSubstrateClient.js +30 -1
- package/client/DedotClient.js +1 -1
- package/executor/StorageQueryExecutor.d.ts +3 -2
- package/executor/StorageQueryExecutor.js +7 -14
- package/executor/v2/StorageQueryExecutorV2.d.ts +4 -6
- package/executor/v2/StorageQueryExecutorV2.js +7 -33
- package/extrinsic/extensions/ExtraSignedExtension.d.ts +6 -0
- package/extrinsic/extensions/ExtraSignedExtension.js +25 -6
- package/extrinsic/extensions/FallbackSignedExtension.d.ts +33 -0
- package/extrinsic/extensions/FallbackSignedExtension.js +58 -0
- package/extrinsic/extensions/index.d.ts +1 -0
- package/extrinsic/extensions/index.js +1 -0
- package/extrinsic/extensions/known/index.d.ts +11 -0
- package/extrinsic/extensions/known/index.js +11 -8
- package/package.json +10 -10
- package/storage/BaseStorageQuery.d.ts +41 -0
- package/storage/BaseStorageQuery.js +23 -0
- package/storage/LegacyStorageQuery.d.ts +35 -0
- package/storage/LegacyStorageQuery.js +63 -0
- package/storage/NewStorageQuery.d.ts +35 -0
- package/storage/NewStorageQuery.js +81 -0
- package/storage/index.d.ts +3 -0
- package/storage/index.js +3 -0
- package/types.d.ts +17 -1
- package/cjs/extrinsic/extensions/known/CheckNonZeroSender.js +0 -10
- package/cjs/extrinsic/extensions/known/CheckWeight.js +0 -10
- package/cjs/extrinsic/extensions/known/PrevalidateAttests.js +0 -11
- package/cjs/extrinsic/extensions/known/StorageWeightReclaim.js +0 -10
- package/extrinsic/extensions/known/CheckNonZeroSender.d.ts +0 -6
- package/extrinsic/extensions/known/CheckNonZeroSender.js +0 -6
- package/extrinsic/extensions/known/CheckWeight.d.ts +0 -6
- package/extrinsic/extensions/known/CheckWeight.js +0 -6
- package/extrinsic/extensions/known/PrevalidateAttests.d.ts +0 -7
- package/extrinsic/extensions/known/PrevalidateAttests.js +0 -7
- package/extrinsic/extensions/known/StorageWeightReclaim.d.ts +0 -6
- 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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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;
|
package/cjs/storage/index.js
CHANGED
|
@@ -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
|
}
|
package/client/DedotClient.js
CHANGED
|
@@ -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
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
}
|