@dedot/api 0.17.1-next.db98ff20.1 → 0.18.1
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 +11 -2
- package/cjs/client/DedotClient.js +32 -10
- package/cjs/client/LegacyClient.js +18 -2
- package/cjs/extrinsic/submittable/SubmittableExtrinsicV2.js +9 -0
- package/cjs/json-rpc/group/ChainHead/ChainHead.js +13 -5
- package/client/BaseSubstrateClient.d.ts +4 -0
- package/client/BaseSubstrateClient.js +11 -2
- package/client/DedotClient.js +35 -13
- package/client/LegacyClient.js +18 -2
- package/extrinsic/submittable/SubmittableExtrinsicV2.js +10 -1
- package/json-rpc/group/ChainHead/ChainHead.js +13 -5
- package/package.json +9 -9
- package/types.d.ts +46 -4
|
@@ -325,7 +325,7 @@ class BaseSubstrateClient extends index_js_2.JsonRpcClient {
|
|
|
325
325
|
setSigner(signer) {
|
|
326
326
|
this._options.signer = signer;
|
|
327
327
|
}
|
|
328
|
-
async
|
|
328
|
+
async internalQueryMulti(queries, callback, blockHash) {
|
|
329
329
|
// Extract keys from queries
|
|
330
330
|
const keys = queries.map((q) => q.fn.rawKey(...(q.args || [])));
|
|
331
331
|
const decodeValue = (query, rawValue) => {
|
|
@@ -341,10 +341,19 @@ class BaseSubstrateClient extends index_js_2.JsonRpcClient {
|
|
|
341
341
|
});
|
|
342
342
|
}
|
|
343
343
|
else {
|
|
344
|
-
const results = await this.getStorageQuery().query(keys);
|
|
344
|
+
const results = await this.getStorageQuery().query(keys, blockHash);
|
|
345
345
|
return queries.map((q, i) => decodeValue(q.fn, results[keys[i]]));
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
|
+
async queryMulti(queries, callback) {
|
|
349
|
+
// If a callback is provided, set up a subscription
|
|
350
|
+
if (callback) {
|
|
351
|
+
return this.internalQueryMulti(queries, callback);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
return this.internalQueryMulti(queries);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
348
357
|
getStorageQuery() {
|
|
349
358
|
throw new Error('Unimplemented!');
|
|
350
359
|
}
|
|
@@ -186,24 +186,42 @@ class DedotClient// prettier-end-here
|
|
|
186
186
|
const cached = this._apiAtCache.get(hash);
|
|
187
187
|
if (cached)
|
|
188
188
|
return cached;
|
|
189
|
-
let
|
|
189
|
+
let parentVersion;
|
|
190
|
+
let parentHash;
|
|
190
191
|
// Try to get block info from ChainHead first (for pinned blocks)
|
|
191
192
|
const targetBlock = this.chainHead.findBlock(hash);
|
|
192
193
|
if (targetBlock) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
194
|
+
if (hash === this.genesisHash) {
|
|
195
|
+
parentHash = hash;
|
|
196
|
+
parentVersion = targetBlock.runtime;
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
parentHash = targetBlock.parent;
|
|
200
|
+
const parentBlock = this.chainHead.findBlock(parentHash);
|
|
201
|
+
parentVersion = parentBlock?.runtime;
|
|
202
|
+
}
|
|
203
|
+
// fallback to fetching on-chain runtime if we can't find it in the block
|
|
204
|
+
if (!parentVersion) {
|
|
205
|
+
parentVersion = this.toSubstrateRuntimeVersion(await this.callAt(parentHash).core.version());
|
|
197
206
|
}
|
|
198
207
|
}
|
|
199
208
|
else {
|
|
200
209
|
// Block not pinned, try via Archive fallback if supported
|
|
201
210
|
if (this._archive && (await this._archive.supported())) {
|
|
202
211
|
try {
|
|
212
|
+
if (hash === this.genesisHash) {
|
|
213
|
+
parentHash = hash;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
const rawHeader = await this._archive.header(hash);
|
|
217
|
+
(0, utils_1.assert)(rawHeader, `Header for block ${hash} not found`);
|
|
218
|
+
const header = codecs_1.$Header.tryDecode(rawHeader);
|
|
219
|
+
parentHash = header.parentHash;
|
|
220
|
+
}
|
|
203
221
|
// Fetch runtime version via Archive
|
|
204
|
-
const runtimeRaw = await this._archive.call('Core_version', '0x',
|
|
222
|
+
const runtimeRaw = await this._archive.call('Core_version', '0x', parentHash);
|
|
205
223
|
(0, utils_1.assert)(runtimeRaw, 'Runtime Version Not Found');
|
|
206
|
-
|
|
224
|
+
parentVersion = this.toSubstrateRuntimeVersion(codecs_1.$RuntimeVersion.tryDecode(runtimeRaw));
|
|
207
225
|
}
|
|
208
226
|
catch (error) {
|
|
209
227
|
throw new utils_1.DedotError(`Unable to fetch runtime version for block ${hash}: ${error}`);
|
|
@@ -215,8 +233,8 @@ class DedotClient// prettier-end-here
|
|
|
215
233
|
}
|
|
216
234
|
let metadata = this.metadata;
|
|
217
235
|
let registry = this.registry;
|
|
218
|
-
if (
|
|
219
|
-
metadata = await this.fetchMetadata(
|
|
236
|
+
if (parentVersion && parentVersion.specVersion !== this.runtimeVersion.specVersion) {
|
|
237
|
+
metadata = await this.fetchMetadata(parentHash, parentVersion);
|
|
220
238
|
registry = new codecs_1.PortableRegistry(metadata.latest, this.options.hasher);
|
|
221
239
|
}
|
|
222
240
|
const api = {
|
|
@@ -224,7 +242,7 @@ class DedotClient// prettier-end-here
|
|
|
224
242
|
atBlockHash: hash,
|
|
225
243
|
options: this.options,
|
|
226
244
|
genesisHash: this.genesisHash,
|
|
227
|
-
runtimeVersion:
|
|
245
|
+
runtimeVersion: parentVersion,
|
|
228
246
|
metadata,
|
|
229
247
|
registry,
|
|
230
248
|
rpc: this.rpc,
|
|
@@ -235,6 +253,10 @@ class DedotClient// prettier-end-here
|
|
|
235
253
|
api.query = (0, proxychain_js_1.newProxyChain)({ executor: new index_js_1.StorageQueryExecutorV2(api, this.chainHead) });
|
|
236
254
|
api.call = (0, proxychain_js_1.newProxyChain)({ executor: new index_js_1.RuntimeApiExecutorV2(api, this.chainHead) });
|
|
237
255
|
api.view = (0, proxychain_js_1.newProxyChain)({ executor: new index_js_1.ViewFunctionExecutorV2(api, this.chainHead) });
|
|
256
|
+
// @ts-ignore Add queryMulti implementation for at-block queries
|
|
257
|
+
api.queryMulti = (queries) => {
|
|
258
|
+
return this.internalQueryMulti(queries, undefined, hash);
|
|
259
|
+
};
|
|
238
260
|
this._apiAtCache.set(hash, api);
|
|
239
261
|
return api;
|
|
240
262
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LegacyClient = void 0;
|
|
4
4
|
const codecs_1 = require("@dedot/codecs");
|
|
5
|
+
const utils_1 = require("@dedot/utils");
|
|
5
6
|
const index_js_1 = require("../executor/index.js");
|
|
6
7
|
const proxychain_js_1 = require("../proxychain.js");
|
|
7
8
|
const index_js_2 = require("../storage/index.js");
|
|
@@ -233,11 +234,12 @@ class LegacyClient// prettier-end-here
|
|
|
233
234
|
const cached = this._apiAtCache.get(hash);
|
|
234
235
|
if (cached)
|
|
235
236
|
return cached;
|
|
236
|
-
const
|
|
237
|
+
const parentHash = await this.#findParentHash(hash);
|
|
238
|
+
const targetVersion = await this.#getRuntimeVersion(parentHash);
|
|
237
239
|
let metadata = this.metadata;
|
|
238
240
|
let registry = this.registry;
|
|
239
241
|
if (targetVersion.specVersion !== this.runtimeVersion.specVersion) {
|
|
240
|
-
metadata = await this.fetchMetadata(
|
|
242
|
+
metadata = await this.fetchMetadata(parentHash, targetVersion);
|
|
241
243
|
registry = new codecs_1.PortableRegistry(metadata.latest, this.options.hasher);
|
|
242
244
|
}
|
|
243
245
|
const api = {
|
|
@@ -255,11 +257,25 @@ class LegacyClient// prettier-end-here
|
|
|
255
257
|
api.call = (0, proxychain_js_1.newProxyChain)({ executor: new index_js_1.RuntimeApiExecutor(api) });
|
|
256
258
|
api.events = (0, proxychain_js_1.newProxyChain)({ executor: new index_js_1.EventExecutor(api) });
|
|
257
259
|
api.errors = (0, proxychain_js_1.newProxyChain)({ executor: new index_js_1.ErrorExecutor(api) });
|
|
260
|
+
// @ts-ignore Add queryMulti implementation for at-block queries
|
|
261
|
+
api.queryMulti = (queries) => {
|
|
262
|
+
return this.internalQueryMulti(queries, undefined, hash);
|
|
263
|
+
};
|
|
258
264
|
this._apiAtCache.set(hash, api);
|
|
259
265
|
return api;
|
|
260
266
|
}
|
|
261
267
|
getStorageQuery() {
|
|
262
268
|
return new index_js_2.LegacyStorageQuery(this);
|
|
263
269
|
}
|
|
270
|
+
async #findParentHash(hash) {
|
|
271
|
+
if (hash === this.genesisHash) {
|
|
272
|
+
return this.genesisHash;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
const header = await this.rpc.chain_getHeader(hash);
|
|
276
|
+
(0, utils_1.assert)(header, `Header for ${hash} not found`);
|
|
277
|
+
return header.parentHash;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
264
280
|
}
|
|
265
281
|
exports.LegacyClient = LegacyClient;
|
|
@@ -22,6 +22,15 @@ class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic_js_1.BaseSubmittab
|
|
|
22
22
|
const txHash = this.hash;
|
|
23
23
|
// validate the transaction
|
|
24
24
|
// https://github.com/paritytech/json-rpc-interface-spec/issues/55#issuecomment-1609011150
|
|
25
|
+
// ensure at least 2 finalized blocks, this is to make sure the api.at can find the parent hash & block to fetch runtime
|
|
26
|
+
while (true) {
|
|
27
|
+
const block = await this.client.chainHead.finalizedBlock();
|
|
28
|
+
const parentBlock = this.client.chainHead.findBlock(block.parent);
|
|
29
|
+
if (parentBlock) {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
await (0, utils_1.waitFor)(100);
|
|
33
|
+
}
|
|
25
34
|
const finalizedHash = await this.client.chainHead.finalizedHash();
|
|
26
35
|
const validateTx = async (hash) => {
|
|
27
36
|
const apiAt = await api.at(hash);
|
|
@@ -192,10 +192,19 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
192
192
|
}
|
|
193
193
|
case 'finalized': {
|
|
194
194
|
const { finalizedBlockHashes, prunedBlockHashes } = result;
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
const currentFinalizedNumber = this.findBlock(this.#finalizedHash).number;
|
|
196
|
+
for (const hash of finalizedBlockHashes) {
|
|
197
|
+
const block = this.findBlock(hash);
|
|
198
|
+
// Skip if block not found OR blocks that are already finalized (block number <= previous finalized number)
|
|
199
|
+
if (!block || block.number <= currentFinalizedNumber) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
this.#finalizedHash = hash;
|
|
203
|
+
const finalizedRuntime = this.#findRuntimeAt(hash);
|
|
204
|
+
if (finalizedRuntime) {
|
|
205
|
+
this.#finalizedRuntime = finalizedRuntime;
|
|
206
|
+
}
|
|
207
|
+
this.emit('finalizedBlock', block);
|
|
199
208
|
}
|
|
200
209
|
// push new finalized hashes into the queue, we'll adjust the size later if needed
|
|
201
210
|
finalizedBlockHashes.forEach((hash) => {
|
|
@@ -204,7 +213,6 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
|
|
|
204
213
|
this.#finalizedQueue.push(hash);
|
|
205
214
|
});
|
|
206
215
|
const currentFinalizedBlock = this.findBlock(this.#finalizedHash);
|
|
207
|
-
this.emit('finalizedBlock', currentFinalizedBlock);
|
|
208
216
|
// TODO account for operations that haven't received its operationId yet
|
|
209
217
|
Object.values(this.#handlers).forEach(({ defer, hash, operationId }) => {
|
|
210
218
|
if (prunedBlockHashes.includes(hash)) {
|
|
@@ -77,6 +77,10 @@ export declare abstract class BaseSubstrateClient<Rv extends RpcVersion, ChainAp
|
|
|
77
77
|
get tx(): ChainApi[Rv]['tx'];
|
|
78
78
|
at<ChainApiAt extends GenericSubstrateApi = ChainApi[Rv]>(hash: BlockHash): Promise<ISubstrateClientAt<ChainApiAt>>;
|
|
79
79
|
setSigner(signer?: InjectedSigner): void;
|
|
80
|
+
protected internalQueryMulti(queries: {
|
|
81
|
+
fn: GenericStorageQuery;
|
|
82
|
+
args?: any[];
|
|
83
|
+
}[], callback?: Callback<any[]>, blockHash?: BlockHash): Promise<any[] | Unsub>;
|
|
80
84
|
/**
|
|
81
85
|
* Query multiple storage items in a single call
|
|
82
86
|
*
|
|
@@ -321,7 +321,7 @@ export class BaseSubstrateClient extends JsonRpcClient {
|
|
|
321
321
|
setSigner(signer) {
|
|
322
322
|
this._options.signer = signer;
|
|
323
323
|
}
|
|
324
|
-
async
|
|
324
|
+
async internalQueryMulti(queries, callback, blockHash) {
|
|
325
325
|
// Extract keys from queries
|
|
326
326
|
const keys = queries.map((q) => q.fn.rawKey(...(q.args || [])));
|
|
327
327
|
const decodeValue = (query, rawValue) => {
|
|
@@ -337,10 +337,19 @@ export class BaseSubstrateClient extends JsonRpcClient {
|
|
|
337
337
|
});
|
|
338
338
|
}
|
|
339
339
|
else {
|
|
340
|
-
const results = await this.getStorageQuery().query(keys);
|
|
340
|
+
const results = await this.getStorageQuery().query(keys, blockHash);
|
|
341
341
|
return queries.map((q, i) => decodeValue(q.fn, results[keys[i]]));
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
|
+
async queryMulti(queries, callback) {
|
|
345
|
+
// If a callback is provided, set up a subscription
|
|
346
|
+
if (callback) {
|
|
347
|
+
return this.internalQueryMulti(queries, callback);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
return this.internalQueryMulti(queries);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
344
353
|
getStorageQuery() {
|
|
345
354
|
throw new Error('Unimplemented!');
|
|
346
355
|
}
|
package/client/DedotClient.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { $H256, $RuntimeVersion, PortableRegistry } from '@dedot/codecs';
|
|
1
|
+
import { $H256, $Header, $RuntimeVersion, PortableRegistry } from '@dedot/codecs';
|
|
2
2
|
import { u32 } from '@dedot/shape';
|
|
3
|
-
import { assert, concatU8a, noop, twox64Concat, u8aToHex, xxhashAsU8a
|
|
4
|
-
import { ConstantExecutor, ErrorExecutor, EventExecutor, RuntimeApiExecutorV2, StorageQueryExecutorV2,
|
|
3
|
+
import { assert, concatU8a, DedotError, noop, twox64Concat, u8aToHex, xxhashAsU8a } from '@dedot/utils';
|
|
4
|
+
import { ConstantExecutor, ErrorExecutor, EventExecutor, RuntimeApiExecutorV2, StorageQueryExecutorV2, TxExecutorV2, ViewFunctionExecutorV2, } from '../executor/index.js';
|
|
5
5
|
import { Archive, ChainHead, ChainSpec, Transaction, TransactionWatch } from '../json-rpc/index.js';
|
|
6
6
|
import { newProxyChain } from '../proxychain.js';
|
|
7
7
|
import { NewStorageQuery } from '../storage/index.js';
|
|
@@ -183,24 +183,42 @@ export class DedotClient// prettier-end-here
|
|
|
183
183
|
const cached = this._apiAtCache.get(hash);
|
|
184
184
|
if (cached)
|
|
185
185
|
return cached;
|
|
186
|
-
let
|
|
186
|
+
let parentVersion;
|
|
187
|
+
let parentHash;
|
|
187
188
|
// Try to get block info from ChainHead first (for pinned blocks)
|
|
188
189
|
const targetBlock = this.chainHead.findBlock(hash);
|
|
189
190
|
if (targetBlock) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
191
|
+
if (hash === this.genesisHash) {
|
|
192
|
+
parentHash = hash;
|
|
193
|
+
parentVersion = targetBlock.runtime;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
parentHash = targetBlock.parent;
|
|
197
|
+
const parentBlock = this.chainHead.findBlock(parentHash);
|
|
198
|
+
parentVersion = parentBlock?.runtime;
|
|
199
|
+
}
|
|
200
|
+
// fallback to fetching on-chain runtime if we can't find it in the block
|
|
201
|
+
if (!parentVersion) {
|
|
202
|
+
parentVersion = this.toSubstrateRuntimeVersion(await this.callAt(parentHash).core.version());
|
|
194
203
|
}
|
|
195
204
|
}
|
|
196
205
|
else {
|
|
197
206
|
// Block not pinned, try via Archive fallback if supported
|
|
198
207
|
if (this._archive && (await this._archive.supported())) {
|
|
199
208
|
try {
|
|
209
|
+
if (hash === this.genesisHash) {
|
|
210
|
+
parentHash = hash;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
const rawHeader = await this._archive.header(hash);
|
|
214
|
+
assert(rawHeader, `Header for block ${hash} not found`);
|
|
215
|
+
const header = $Header.tryDecode(rawHeader);
|
|
216
|
+
parentHash = header.parentHash;
|
|
217
|
+
}
|
|
200
218
|
// Fetch runtime version via Archive
|
|
201
|
-
const runtimeRaw = await this._archive.call('Core_version', '0x',
|
|
219
|
+
const runtimeRaw = await this._archive.call('Core_version', '0x', parentHash);
|
|
202
220
|
assert(runtimeRaw, 'Runtime Version Not Found');
|
|
203
|
-
|
|
221
|
+
parentVersion = this.toSubstrateRuntimeVersion($RuntimeVersion.tryDecode(runtimeRaw));
|
|
204
222
|
}
|
|
205
223
|
catch (error) {
|
|
206
224
|
throw new DedotError(`Unable to fetch runtime version for block ${hash}: ${error}`);
|
|
@@ -212,8 +230,8 @@ export class DedotClient// prettier-end-here
|
|
|
212
230
|
}
|
|
213
231
|
let metadata = this.metadata;
|
|
214
232
|
let registry = this.registry;
|
|
215
|
-
if (
|
|
216
|
-
metadata = await this.fetchMetadata(
|
|
233
|
+
if (parentVersion && parentVersion.specVersion !== this.runtimeVersion.specVersion) {
|
|
234
|
+
metadata = await this.fetchMetadata(parentHash, parentVersion);
|
|
217
235
|
registry = new PortableRegistry(metadata.latest, this.options.hasher);
|
|
218
236
|
}
|
|
219
237
|
const api = {
|
|
@@ -221,7 +239,7 @@ export class DedotClient// prettier-end-here
|
|
|
221
239
|
atBlockHash: hash,
|
|
222
240
|
options: this.options,
|
|
223
241
|
genesisHash: this.genesisHash,
|
|
224
|
-
runtimeVersion:
|
|
242
|
+
runtimeVersion: parentVersion,
|
|
225
243
|
metadata,
|
|
226
244
|
registry,
|
|
227
245
|
rpc: this.rpc,
|
|
@@ -232,6 +250,10 @@ export class DedotClient// prettier-end-here
|
|
|
232
250
|
api.query = newProxyChain({ executor: new StorageQueryExecutorV2(api, this.chainHead) });
|
|
233
251
|
api.call = newProxyChain({ executor: new RuntimeApiExecutorV2(api, this.chainHead) });
|
|
234
252
|
api.view = newProxyChain({ executor: new ViewFunctionExecutorV2(api, this.chainHead) });
|
|
253
|
+
// @ts-ignore Add queryMulti implementation for at-block queries
|
|
254
|
+
api.queryMulti = (queries) => {
|
|
255
|
+
return this.internalQueryMulti(queries, undefined, hash);
|
|
256
|
+
};
|
|
235
257
|
this._apiAtCache.set(hash, api);
|
|
236
258
|
return api;
|
|
237
259
|
}
|
package/client/LegacyClient.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PortableRegistry } from '@dedot/codecs';
|
|
2
|
+
import { assert } from '@dedot/utils';
|
|
2
3
|
import { ConstantExecutor, ErrorExecutor, EventExecutor, RuntimeApiExecutor, StorageQueryExecutor, TxExecutor, ViewFunctionExecutor, } from '../executor/index.js';
|
|
3
4
|
import { newProxyChain } from '../proxychain.js';
|
|
4
5
|
import { LegacyStorageQuery } from '../storage/index.js';
|
|
@@ -230,11 +231,12 @@ export class LegacyClient// prettier-end-here
|
|
|
230
231
|
const cached = this._apiAtCache.get(hash);
|
|
231
232
|
if (cached)
|
|
232
233
|
return cached;
|
|
233
|
-
const
|
|
234
|
+
const parentHash = await this.#findParentHash(hash);
|
|
235
|
+
const targetVersion = await this.#getRuntimeVersion(parentHash);
|
|
234
236
|
let metadata = this.metadata;
|
|
235
237
|
let registry = this.registry;
|
|
236
238
|
if (targetVersion.specVersion !== this.runtimeVersion.specVersion) {
|
|
237
|
-
metadata = await this.fetchMetadata(
|
|
239
|
+
metadata = await this.fetchMetadata(parentHash, targetVersion);
|
|
238
240
|
registry = new PortableRegistry(metadata.latest, this.options.hasher);
|
|
239
241
|
}
|
|
240
242
|
const api = {
|
|
@@ -252,10 +254,24 @@ export class LegacyClient// prettier-end-here
|
|
|
252
254
|
api.call = newProxyChain({ executor: new RuntimeApiExecutor(api) });
|
|
253
255
|
api.events = newProxyChain({ executor: new EventExecutor(api) });
|
|
254
256
|
api.errors = newProxyChain({ executor: new ErrorExecutor(api) });
|
|
257
|
+
// @ts-ignore Add queryMulti implementation for at-block queries
|
|
258
|
+
api.queryMulti = (queries) => {
|
|
259
|
+
return this.internalQueryMulti(queries, undefined, hash);
|
|
260
|
+
};
|
|
255
261
|
this._apiAtCache.set(hash, api);
|
|
256
262
|
return api;
|
|
257
263
|
}
|
|
258
264
|
getStorageQuery() {
|
|
259
265
|
return new LegacyStorageQuery(this);
|
|
260
266
|
}
|
|
267
|
+
async #findParentHash(hash) {
|
|
268
|
+
if (hash === this.genesisHash) {
|
|
269
|
+
return this.genesisHash;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
const header = await this.rpc.chain_getHeader(hash);
|
|
273
|
+
assert(header, `Header for ${hash} not found`);
|
|
274
|
+
return header.parentHash;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
261
277
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AsyncQueue, noop } from '@dedot/utils';
|
|
1
|
+
import { AsyncQueue, noop, waitFor } from '@dedot/utils';
|
|
2
2
|
import { BaseSubmittableExtrinsic } from './BaseSubmittableExtrinsic.js';
|
|
3
3
|
import { SubmittableResult } from './SubmittableResult.js';
|
|
4
4
|
import { InvalidTxError } from './errors.js';
|
|
@@ -19,6 +19,15 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
|
19
19
|
const txHash = this.hash;
|
|
20
20
|
// validate the transaction
|
|
21
21
|
// https://github.com/paritytech/json-rpc-interface-spec/issues/55#issuecomment-1609011150
|
|
22
|
+
// ensure at least 2 finalized blocks, this is to make sure the api.at can find the parent hash & block to fetch runtime
|
|
23
|
+
while (true) {
|
|
24
|
+
const block = await this.client.chainHead.finalizedBlock();
|
|
25
|
+
const parentBlock = this.client.chainHead.findBlock(block.parent);
|
|
26
|
+
if (parentBlock) {
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
await waitFor(100);
|
|
30
|
+
}
|
|
22
31
|
const finalizedHash = await this.client.chainHead.finalizedHash();
|
|
23
32
|
const validateTx = async (hash) => {
|
|
24
33
|
const apiAt = await api.at(hash);
|
|
@@ -189,10 +189,19 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
189
189
|
}
|
|
190
190
|
case 'finalized': {
|
|
191
191
|
const { finalizedBlockHashes, prunedBlockHashes } = result;
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
const currentFinalizedNumber = this.findBlock(this.#finalizedHash).number;
|
|
193
|
+
for (const hash of finalizedBlockHashes) {
|
|
194
|
+
const block = this.findBlock(hash);
|
|
195
|
+
// Skip if block not found OR blocks that are already finalized (block number <= previous finalized number)
|
|
196
|
+
if (!block || block.number <= currentFinalizedNumber) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
this.#finalizedHash = hash;
|
|
200
|
+
const finalizedRuntime = this.#findRuntimeAt(hash);
|
|
201
|
+
if (finalizedRuntime) {
|
|
202
|
+
this.#finalizedRuntime = finalizedRuntime;
|
|
203
|
+
}
|
|
204
|
+
this.emit('finalizedBlock', block);
|
|
196
205
|
}
|
|
197
206
|
// push new finalized hashes into the queue, we'll adjust the size later if needed
|
|
198
207
|
finalizedBlockHashes.forEach((hash) => {
|
|
@@ -201,7 +210,6 @@ export class ChainHead extends JsonRpcGroup {
|
|
|
201
210
|
this.#finalizedQueue.push(hash);
|
|
202
211
|
});
|
|
203
212
|
const currentFinalizedBlock = this.findBlock(this.#finalizedHash);
|
|
204
|
-
this.emit('finalizedBlock', currentFinalizedBlock);
|
|
205
213
|
// TODO account for operations that haven't received its operationId yet
|
|
206
214
|
Object.values(this.#handlers).forEach(({ defer, hash, operationId }) => {
|
|
207
215
|
if (prunedBlockHashes.includes(hash)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dedot/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"description": "A delightful JavaScript/TypeScript client for Polkadot & Substrate",
|
|
5
5
|
"author": "Thang X. Vu <thang@dedot.dev>",
|
|
6
6
|
"homepage": "https://dedot.dev",
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"type": "module",
|
|
14
14
|
"sideEffects": false,
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@dedot/codecs": "0.
|
|
17
|
-
"@dedot/providers": "0.
|
|
18
|
-
"@dedot/runtime-specs": "0.
|
|
19
|
-
"@dedot/shape": "0.
|
|
20
|
-
"@dedot/storage": "0.
|
|
21
|
-
"@dedot/types": "0.
|
|
22
|
-
"@dedot/utils": "0.
|
|
16
|
+
"@dedot/codecs": "0.18.1",
|
|
17
|
+
"@dedot/providers": "0.18.1",
|
|
18
|
+
"@dedot/runtime-specs": "0.18.1",
|
|
19
|
+
"@dedot/shape": "0.18.1",
|
|
20
|
+
"@dedot/storage": "0.18.1",
|
|
21
|
+
"@dedot/types": "0.18.1",
|
|
22
|
+
"@dedot/utils": "0.18.1"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"node": ">=18"
|
|
49
49
|
},
|
|
50
50
|
"license": "Apache-2.0",
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "3ffda568232b50a3993d13c6ccac2894b0759dd7",
|
|
52
52
|
"module": "./index.js",
|
|
53
53
|
"types": "./index.d.ts"
|
|
54
54
|
}
|
package/types.d.ts
CHANGED
|
@@ -99,6 +99,28 @@ export interface IGenericSubstrateClient<ChainApi extends GenericSubstrateApi =
|
|
|
99
99
|
events: ChainApi['events'];
|
|
100
100
|
errors: ChainApi['errors'];
|
|
101
101
|
view: ChainApi['view'];
|
|
102
|
+
/**
|
|
103
|
+
* Query multiple storage items in a single call
|
|
104
|
+
*
|
|
105
|
+
* This method allows you to query multiple storage items in a single call with type safety
|
|
106
|
+
* for both the query functions and their results.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* // One-time query with type-safe results
|
|
110
|
+
* const [balance, blockNumber] = await client.queryMulti([
|
|
111
|
+
* { fn: client.query.system.account, args: [ALICE] },
|
|
112
|
+
* { fn: client.query.system.number, args: [] }
|
|
113
|
+
* ]);
|
|
114
|
+
*
|
|
115
|
+
* @template Fns Array of storage query functions
|
|
116
|
+
* @param queries - Array of query specifications, each with a function and optional arguments
|
|
117
|
+
* @returns Array of decoded values with proper types
|
|
118
|
+
*/
|
|
119
|
+
queryMulti<Fns extends GenericStorageQuery[]>(queries: {
|
|
120
|
+
[K in keyof Fns]: Query<Fns[K]>;
|
|
121
|
+
}): Promise<{
|
|
122
|
+
[K in keyof Fns]: QueryFnResult<Fns[K]>;
|
|
123
|
+
}>;
|
|
102
124
|
}
|
|
103
125
|
/**
|
|
104
126
|
* A generic interface for Substrate clients at a specific block
|
|
@@ -128,10 +150,30 @@ export interface ISubstrateClient<ChainApi extends GenericSubstrateApi = Generic
|
|
|
128
150
|
*/
|
|
129
151
|
setSigner(signer?: InjectedSigner): void;
|
|
130
152
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
153
|
+
* Query multiple storage items in a single call or subscribe to multiple storage items
|
|
154
|
+
*
|
|
155
|
+
* This method allows you to query multiple storage items in a single call or set up a subscription
|
|
156
|
+
* to multiple storage items. It provides type safety for both the query functions and their results.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* // One-time query with type-safe results
|
|
160
|
+
* const [balance, blockNumber] = await client.queryMulti([
|
|
161
|
+
* { fn: client.query.system.account, args: [ALICE] },
|
|
162
|
+
* { fn: client.query.system.number, args: [] }
|
|
163
|
+
* ]);
|
|
164
|
+
*
|
|
165
|
+
* // Subscription with callback
|
|
166
|
+
* const unsub = await client.queryMulti([
|
|
167
|
+
* { fn: client.query.system.account, args: [ALICE] },
|
|
168
|
+
* { fn: client.query.system.number, args: [] }
|
|
169
|
+
* ], (results) => {
|
|
170
|
+
* console.log('Balance:', results[0], 'Block number:', results[1]);
|
|
171
|
+
* });
|
|
172
|
+
*
|
|
173
|
+
* @template Fns Array of storage query functions
|
|
174
|
+
* @param queries - Array of query specifications, each with a function and optional arguments
|
|
175
|
+
* @param callback - Optional callback for subscription-based queries
|
|
176
|
+
* @returns For one-time queries: Array of decoded values with proper types; For subscriptions: Unsubscribe function
|
|
135
177
|
*/
|
|
136
178
|
queryMulti<Fns extends GenericStorageQuery[]>(queries: {
|
|
137
179
|
[K in keyof Fns]: Query<Fns[K]>;
|