@dedot/api 0.18.6 → 1.0.0-next.a0f8d41e.25

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 (102) hide show
  1. package/chaintypes/substrate/consts.d.ts +2 -2
  2. package/chaintypes/substrate/consts.js +1 -0
  3. package/chaintypes/substrate/errors.d.ts +909 -909
  4. package/chaintypes/substrate/errors.js +1 -0
  5. package/chaintypes/substrate/events.d.ts +664 -664
  6. package/chaintypes/substrate/events.js +1 -0
  7. package/chaintypes/substrate/index.d.ts +13 -16
  8. package/chaintypes/substrate/index.js +1 -0
  9. package/chaintypes/substrate/json-rpc.d.ts +2 -2
  10. package/chaintypes/substrate/json-rpc.js +1 -0
  11. package/chaintypes/substrate/query.d.ts +509 -527
  12. package/chaintypes/substrate/query.js +1 -0
  13. package/chaintypes/substrate/runtime.d.ts +123 -123
  14. package/chaintypes/substrate/runtime.js +1 -0
  15. package/chaintypes/substrate/tx.d.ts +1311 -1311
  16. package/chaintypes/substrate/tx.js +1 -0
  17. package/chaintypes/substrate/types.js +1 -0
  18. package/chaintypes/substrate/view-functions.d.ts +2 -2
  19. package/chaintypes/substrate/view-functions.js +1 -0
  20. package/cjs/chaintypes/substrate/consts.js +1 -0
  21. package/cjs/chaintypes/substrate/errors.js +1 -0
  22. package/cjs/chaintypes/substrate/events.js +1 -0
  23. package/cjs/chaintypes/substrate/index.js +1 -0
  24. package/cjs/chaintypes/substrate/json-rpc.js +1 -0
  25. package/cjs/chaintypes/substrate/query.js +1 -0
  26. package/cjs/chaintypes/substrate/runtime.js +1 -0
  27. package/cjs/chaintypes/substrate/tx.js +1 -0
  28. package/cjs/chaintypes/substrate/types.js +1 -0
  29. package/cjs/chaintypes/substrate/view-functions.js +1 -0
  30. package/cjs/client/BaseSubstrateClient.js +45 -2
  31. package/cjs/client/DedotClient.js +356 -224
  32. package/cjs/client/LegacyClient.js +99 -23
  33. package/cjs/client/V2Client.js +316 -0
  34. package/cjs/client/explorer/LegacyBlockExplorer.js +263 -0
  35. package/cjs/client/explorer/V2BlockExplorer.js +74 -0
  36. package/cjs/client/explorer/index.js +18 -0
  37. package/cjs/client/utils.js +57 -0
  38. package/cjs/executor/EventExecutor.js +1 -0
  39. package/cjs/executor/RuntimeApiExecutor.js +49 -2
  40. package/cjs/executor/StorageQueryExecutor.js +114 -2
  41. package/cjs/executor/ViewFunctionExecutor.js +21 -2
  42. package/cjs/executor/v2/StorageQueryExecutorV2.js +7 -1
  43. package/cjs/executor/validation-helpers.js +181 -0
  44. package/cjs/extrinsic/submittable/BaseSubmittableExtrinsic.js +86 -2
  45. package/cjs/extrinsic/submittable/SubmittableExtrinsic.js +38 -16
  46. package/cjs/extrinsic/submittable/SubmittableExtrinsicV2.js +65 -35
  47. package/cjs/json-rpc/group/ChainHead/ChainHead.js +233 -21
  48. package/cjs/storage/QueryableStorage.js +1 -0
  49. package/client/BaseSubstrateClient.d.ts +34 -18
  50. package/client/BaseSubstrateClient.js +47 -4
  51. package/client/DedotClient.d.ts +346 -49
  52. package/client/DedotClient.js +356 -224
  53. package/client/LegacyClient.d.ts +22 -17
  54. package/client/LegacyClient.js +101 -25
  55. package/client/V2Client.d.ts +75 -0
  56. package/client/V2Client.js +312 -0
  57. package/client/explorer/LegacyBlockExplorer.d.ts +68 -0
  58. package/client/explorer/LegacyBlockExplorer.js +259 -0
  59. package/client/explorer/V2BlockExplorer.d.ts +40 -0
  60. package/client/explorer/V2BlockExplorer.js +70 -0
  61. package/client/explorer/index.d.ts +2 -0
  62. package/client/explorer/index.js +2 -0
  63. package/client/utils.d.ts +18 -0
  64. package/client/utils.js +52 -0
  65. package/executor/ConstantExecutor.d.ts +1 -2
  66. package/executor/ErrorExecutor.d.ts +2 -2
  67. package/executor/EventExecutor.d.ts +2 -2
  68. package/executor/EventExecutor.js +1 -0
  69. package/executor/Executor.d.ts +4 -5
  70. package/executor/RuntimeApiExecutor.d.ts +2 -2
  71. package/executor/RuntimeApiExecutor.js +27 -3
  72. package/executor/StorageQueryExecutor.d.ts +3 -2
  73. package/executor/StorageQueryExecutor.js +92 -3
  74. package/executor/TxExecutor.d.ts +2 -2
  75. package/executor/ViewFunctionExecutor.d.ts +2 -2
  76. package/executor/ViewFunctionExecutor.js +22 -3
  77. package/executor/v2/RuntimeApiExecutorV2.d.ts +2 -3
  78. package/executor/v2/StorageQueryExecutorV2.d.ts +3 -3
  79. package/executor/v2/StorageQueryExecutorV2.js +8 -2
  80. package/executor/v2/TxExecutorV2.d.ts +5 -5
  81. package/executor/v2/ViewFunctionExecutorV2.d.ts +2 -3
  82. package/executor/validation-helpers.d.ts +36 -0
  83. package/executor/validation-helpers.js +174 -0
  84. package/extrinsic/extensions/SignedExtension.d.ts +1 -1
  85. package/extrinsic/submittable/BaseSubmittableExtrinsic.d.ts +7 -3
  86. package/extrinsic/submittable/BaseSubmittableExtrinsic.js +64 -3
  87. package/extrinsic/submittable/SubmittableExtrinsic.d.ts +4 -1
  88. package/extrinsic/submittable/SubmittableExtrinsic.js +39 -17
  89. package/extrinsic/submittable/SubmittableExtrinsicV2.d.ts +6 -3
  90. package/extrinsic/submittable/SubmittableExtrinsicV2.js +66 -36
  91. package/json-rpc/JsonRpcClient.d.ts +6 -6
  92. package/json-rpc/group/Archive.d.ts +1 -1
  93. package/json-rpc/group/ChainHead/ChainHead.d.ts +20 -2
  94. package/json-rpc/group/ChainHead/ChainHead.js +233 -21
  95. package/json-rpc/group/ChainSpec.d.ts +3 -3
  96. package/json-rpc/group/Transaction.d.ts +1 -1
  97. package/json-rpc/group/TransactionWatch.d.ts +1 -1
  98. package/package.json +9 -9
  99. package/proxychain.d.ts +3 -4
  100. package/storage/NewStorageQuery.d.ts +3 -3
  101. package/storage/QueryableStorage.js +1 -0
  102. package/types.d.ts +94 -6
@@ -1,5 +1,6 @@
1
1
  import { BlockHash, Option } from '@dedot/codecs';
2
2
  import type { ChainHeadRuntimeVersion, OperationId, StorageQuery, StorageResult } from '@dedot/types/json-rpc';
3
+ import { HashFn } from '@dedot/utils';
3
4
  import { Deferred, HexString } from '@dedot/utils';
4
5
  import type { IJsonRpcClient } from '../../../types.js';
5
6
  import { Archive } from '../Archive.js';
@@ -10,16 +11,22 @@ export type OperationHandler<T = any> = {
10
11
  storageResults?: Array<StorageResult>;
11
12
  hash: BlockHash;
12
13
  };
14
+ export type PendingRequest<T = any> = {
15
+ method: string;
16
+ params: any[];
17
+ defer: Deferred<T>;
18
+ };
13
19
  export type PinnedBlock = {
14
20
  hash: BlockHash;
15
21
  number: number;
16
22
  parent: BlockHash;
17
23
  runtime?: ChainHeadRuntimeVersion;
24
+ runtimeUpgraded?: boolean;
18
25
  };
19
26
  export type ChainHeadEvent = 'newBlock' | 'bestBlock' | 'finalizedBlock' | 'bestChainChanged';
20
27
  export declare class ChainHead extends JsonRpcGroup<ChainHeadEvent> {
21
28
  #private;
22
- constructor(client: IJsonRpcClient, options?: Partial<JsonRpcGroupOptions>);
29
+ constructor(client: IJsonRpcClient<any>, options?: Partial<JsonRpcGroupOptions>);
23
30
  /**
24
31
  * Attach an Archive instance as fallback for operations that fail due to unpinned blocks.
25
32
  * When a ChainHeadBlockNotPinnedError occurs, the operation will automatically fallback
@@ -29,12 +36,21 @@ export declare class ChainHead extends JsonRpcGroup<ChainHeadEvent> {
29
36
  * @returns this ChainHead instance for method chaining
30
37
  */
31
38
  withArchive(archive: Archive): this;
39
+ /**
40
+ * Override send() to track pending requests for retry on network disconnection
41
+ */
42
+ send<T = any>(method: string, ...params: any[]): Promise<T>;
32
43
  runtimeVersion(): Promise<ChainHeadRuntimeVersion>;
33
44
  bestRuntimeVersion(): Promise<ChainHeadRuntimeVersion>;
34
45
  finalizedHash(): Promise<BlockHash>;
35
46
  bestHash(): Promise<BlockHash>;
36
47
  bestBlock(): Promise<PinnedBlock>;
37
48
  finalizedBlock(): Promise<PinnedBlock>;
49
+ /**
50
+ * Returns the detected hasher for the chain
51
+ * Returns undefined if detection failed (client should use default)
52
+ */
53
+ hasher(): Promise<HashFn | undefined>;
38
54
  findBlock(hash: BlockHash): PinnedBlock | undefined;
39
55
  isPinned(hash: BlockHash): boolean;
40
56
  /**
@@ -44,7 +60,7 @@ export declare class ChainHead extends JsonRpcGroup<ChainHeadEvent> {
44
60
  /**
45
61
  * chainHead_follow
46
62
  */
47
- follow(): Promise<void>;
63
+ follow(force?: boolean): Promise<void>;
48
64
  /**
49
65
  * chainHead_body
50
66
  */
@@ -87,4 +103,6 @@ export declare class ChainHead extends JsonRpcGroup<ChainHeadEvent> {
87
103
  * ```
88
104
  */
89
105
  clearCache(): void;
106
+ holdBlock(hash: BlockHash): void;
107
+ releaseBlock(hash: BlockHash): void;
90
108
  }
@@ -1,9 +1,11 @@
1
1
  import { $Header } from '@dedot/codecs';
2
+ import { NetworkDisconnectedError } from '@dedot/providers';
2
3
  import { assert, AsyncQueue, deferred, ensurePresence, noop, ThrottleQueue, waitFor, LRUCache, } from '@dedot/utils';
4
+ import { detectHasherFromHeader } from '../../../client/utils.js';
3
5
  import { JsonRpcGroup } from '../JsonRpcGroup.js';
4
6
  import { BlockUsage } from './BlockUsage.js';
5
7
  import { ChainHeadBlockNotPinnedError, ChainHeadBlockPrunedError, ChainHeadError, ChainHeadLimitReachedError, ChainHeadOperationError, ChainHeadOperationInaccessibleError, ChainHeadStopError, RetryStrategy, } from './error.js';
6
- const MIN_FINALIZED_QUEUE_SIZE = 16; // finalized queue size
8
+ const MIN_FINALIZED_QUEUE_SIZE = 10; // finalized queue size
7
9
  const CHAINHEAD_CACHE_CAPACITY = 256;
8
10
  const CHAINHEAD_CACHE_TTL = 30_000; // 30 seconds
9
11
  export class ChainHead extends JsonRpcGroup {
@@ -11,11 +13,15 @@ export class ChainHead extends JsonRpcGroup {
11
13
  #subscriptionId;
12
14
  #handlers;
13
15
  #pendingOperations;
16
+ #pendingRequests = new Map();
17
+ #requestIdCounter = 0;
14
18
  #finalizedQueue;
15
19
  #pinnedBlocks;
16
20
  #bestHash;
21
+ #lastBestBlockNumber; // Track last emitted best block number for gap detection
17
22
  #finalizedHash; // best finalized hash
18
23
  #finalizedRuntime;
24
+ #hasher; // detected hasher from block header
19
25
  #followResponseQueue;
20
26
  #retryQueue;
21
27
  #recovering;
@@ -60,6 +66,34 @@ export class ChainHead extends JsonRpcGroup {
60
66
  this.#archive = archive;
61
67
  return this;
62
68
  }
69
+ /**
70
+ * Override send() to track pending requests for retry on network disconnection
71
+ */
72
+ async send(method, ...params) {
73
+ const requestId = `req_${++this.#requestIdCounter}`;
74
+ const defer = deferred();
75
+ this.#pendingRequests.set(requestId, {
76
+ method,
77
+ params,
78
+ defer,
79
+ });
80
+ try {
81
+ const result = await super.send(method, ...params);
82
+ this.#pendingRequests.delete(requestId);
83
+ defer.resolve(result);
84
+ return result;
85
+ }
86
+ catch (error) {
87
+ if (error instanceof NetworkDisconnectedError) {
88
+ // Keep in pending for retry on re-follow - don't delete
89
+ // Return the deferred promise that will be resolved on retry
90
+ return defer.promise;
91
+ }
92
+ // Other errors: clean up and throw
93
+ this.#pendingRequests.delete(requestId);
94
+ throw error;
95
+ }
96
+ }
63
97
  async runtimeVersion() {
64
98
  await this.#ensureFollowed();
65
99
  return this.#finalizedRuntime;
@@ -82,9 +116,20 @@ export class ChainHead extends JsonRpcGroup {
82
116
  async finalizedBlock() {
83
117
  return this.findBlock(await this.finalizedHash());
84
118
  }
119
+ /**
120
+ * Returns the detected hasher for the chain
121
+ * Returns undefined if detection failed (client should use default)
122
+ */
123
+ async hasher() {
124
+ await this.#ensureFollowed();
125
+ return this.#hasher;
126
+ }
85
127
  findBlock(hash) {
86
128
  return this.#pinnedBlocks[hash];
87
129
  }
130
+ #findBlocksByNumber(blockNumber) {
131
+ return Object.values(this.#pinnedBlocks).filter((block) => block.number === blockNumber);
132
+ }
88
133
  isPinned(hash) {
89
134
  return !!this.findBlock(hash);
90
135
  }
@@ -99,8 +144,10 @@ export class ChainHead extends JsonRpcGroup {
99
144
  /**
100
145
  * chainHead_follow
101
146
  */
102
- async follow() {
103
- assert(!this.#subscriptionId, 'Already followed chain head. Please unfollow first.');
147
+ async follow(force) {
148
+ if (!force) {
149
+ assert(!this.#subscriptionId, 'Already followed chain head. Please unfollow first.');
150
+ }
104
151
  return this.#doFollow();
105
152
  }
106
153
  async #doFollow() {
@@ -129,7 +176,41 @@ export class ChainHead extends JsonRpcGroup {
129
176
  catch (e) {
130
177
  defer.reject(e);
131
178
  }
132
- return defer.promise;
179
+ // Wait for follow to complete, then retry pending requests
180
+ await defer.promise;
181
+ // Retry pending requests if any (from previous disconnection)
182
+ if (this.#pendingRequests.size > 0) {
183
+ this.#retryPendingRequests();
184
+ }
185
+ }
186
+ /**
187
+ * Retry pending requests that were queued during network disconnection
188
+ */
189
+ #retryPendingRequests() {
190
+ const pending = Array.from(this.#pendingRequests.entries());
191
+ for (const [requestId, request] of pending) {
192
+ const { method, params, defer } = request;
193
+ // Update subscription ID with the new one (first param for all ChainHead RPC methods)
194
+ const updatedParams = [...params];
195
+ if (updatedParams.length > 0) {
196
+ updatedParams[0] = this.#subscriptionId;
197
+ }
198
+ // Retry the request with new subscriptionId
199
+ super
200
+ .send(method, ...updatedParams)
201
+ .then((result) => {
202
+ defer.resolve(result);
203
+ this.#pendingRequests.delete(requestId);
204
+ })
205
+ .catch((error) => {
206
+ if (!(error instanceof NetworkDisconnectedError)) {
207
+ // Real error, reject and clean up
208
+ defer.reject(error);
209
+ this.#pendingRequests.delete(requestId);
210
+ }
211
+ // If NetworkDisconnectedError again, keep in pending for next retry
212
+ });
213
+ }
133
214
  }
134
215
  #onFollowEvent = async (result, subscription) => {
135
216
  switch (result.event) {
@@ -137,6 +218,9 @@ export class ChainHead extends JsonRpcGroup {
137
218
  const { finalizedBlockHashes = [], finalizedBlockHash, finalizedBlockRuntime } = result;
138
219
  if (finalizedBlockHash)
139
220
  finalizedBlockHashes.push(finalizedBlockHash);
221
+ const lastFinalizedHash = this.#finalizedHash;
222
+ const prevPinnedBlocks = this.#pinnedBlocks;
223
+ // Initialize subscription state
140
224
  this.#subscriptionId = subscription.subscriptionId;
141
225
  this.#finalizedQueue = finalizedBlockHashes;
142
226
  if (finalizedBlockHashes.length > MIN_FINALIZED_QUEUE_SIZE) {
@@ -144,35 +228,37 @@ export class ChainHead extends JsonRpcGroup {
144
228
  }
145
229
  this.#finalizedRuntime = this.#extractRuntime(finalizedBlockRuntime);
146
230
  assert(this.#finalizedRuntime, 'Invalid finalized runtime');
147
- this.#bestHash = this.#finalizedHash = finalizedBlockHashes.at(-1);
148
- this.#pinnedBlocks = finalizedBlockHashes.reduce((o, hash, idx, arr) => {
149
- o[hash] = { hash, parent: arr[idx - 1], number: idx };
150
- // assign finalized runtime to the current finalized block
151
- if (idx === finalizedBlockHashes.length - 1) {
152
- o[hash]['runtime'] = this.#finalizedRuntime;
153
- }
154
- return o;
155
- }, {});
156
- const header = $Header.tryDecode(await this.#getHeader(finalizedBlockHashes[0]));
157
- Object.values(this.#pinnedBlocks).forEach((b, idx) => {
158
- b.number += header.number;
159
- if (idx === 0) {
160
- b.parent = header.parentHash;
161
- }
162
- });
231
+ // Build initial pinned blocks
232
+ this.#finalizedHash = finalizedBlockHashes.at(-1);
233
+ if (!this.#bestHash) {
234
+ this.#bestHash = this.#finalizedHash;
235
+ }
236
+ this.#pinnedBlocks = this.#buildInitialPinnedBlocks(finalizedBlockHashes, this.#finalizedRuntime);
237
+ await this.#updateBlockNumbersAndParents(finalizedBlockHashes, prevPinnedBlocks);
238
+ // Handle first initialization or reconnection
239
+ if (!lastFinalizedHash) {
240
+ this.emit('finalizedBlock', await this.finalizedBlock());
241
+ }
242
+ else {
243
+ await this.#handleMissingBlocks(finalizedBlockHashes, lastFinalizedHash, prevPinnedBlocks);
244
+ }
163
245
  break;
164
246
  }
165
247
  case 'newBlock': {
166
248
  const { blockHash: hash, parentBlockHash: parent, newRuntime } = result;
167
249
  const runtime = this.#extractRuntime(newRuntime);
168
250
  const parentBlock = this.findBlock(parent);
169
- assert(parentBlock, `Parent block not found for new block ${hash}`);
251
+ if (!parentBlock) {
252
+ console.warn(`Parent block not found for new block ${hash}`);
253
+ return;
254
+ }
170
255
  this.#pinnedBlocks[hash] = {
171
256
  hash,
172
257
  parent,
173
258
  // if the runtime is not provided, we'll find it from the parent block
174
259
  runtime: runtime || this.#findRuntimeAt(parent),
175
260
  number: parentBlock.number + 1,
261
+ runtimeUpgraded: !!runtime,
176
262
  };
177
263
  this.emit('newBlock', this.#pinnedBlocks[hash]);
178
264
  break;
@@ -185,8 +271,13 @@ export class ChainHead extends JsonRpcGroup {
185
271
  return;
186
272
  if (currentBestBlock.hash === newBestBlock.hash)
187
273
  return;
274
+ // Detect and fill gaps before updating best hash
275
+ if (this.#lastBestBlockNumber !== undefined) {
276
+ this.#fillBestBlockGaps(this.#lastBestBlockNumber, newBestBlock.number);
277
+ }
188
278
  const bestChainChanged = !this.#onTheSameChain(currentBestBlock, newBestBlock);
189
279
  this.#bestHash = bestBlockHash;
280
+ this.#lastBestBlockNumber = newBestBlock.number; // Update tracking
190
281
  this.emit('bestBlock', newBestBlock, bestChainChanged);
191
282
  if (bestChainChanged)
192
283
  this.emit('bestChainChanged', newBestBlock);
@@ -362,6 +453,100 @@ export class ChainHead extends JsonRpcGroup {
362
453
  return;
363
454
  return this.#findRuntimeAt(block.parent);
364
455
  }
456
+ #hasRuntimeUpgrade(header) {
457
+ return header.digest.logs.some((log) => log.type === 'RuntimeEnvironmentUpdated');
458
+ }
459
+ #buildInitialPinnedBlocks(finalizedBlockHashes, runtime) {
460
+ return finalizedBlockHashes.reduce((o, hash, idx, arr) => {
461
+ o[hash] = { hash, parent: arr[idx - 1], number: idx };
462
+ // assign finalized runtime to the current finalized block
463
+ if (idx === finalizedBlockHashes.length - 1) {
464
+ o[hash]['runtime'] = runtime;
465
+ }
466
+ return o;
467
+ }, {});
468
+ }
469
+ async #fetchInitializationHeaders(finalizedBlockHashes, prevPinnedBlocks) {
470
+ const fetchStartingHeader = async () => {
471
+ const startingBlockHash = finalizedBlockHashes[0];
472
+ const existing = prevPinnedBlocks[startingBlockHash];
473
+ if (existing) {
474
+ return {
475
+ number: existing.number,
476
+ parent: existing.parent,
477
+ };
478
+ }
479
+ else {
480
+ const header = $Header.tryDecode(await this.#getHeader(startingBlockHash));
481
+ return {
482
+ number: header.number,
483
+ parent: header.parentHash,
484
+ };
485
+ }
486
+ };
487
+ const [startingBlock, finalizedHeader] = await Promise.all([
488
+ fetchStartingHeader(),
489
+ $Header.tryDecode(await this.#getHeader(this.#finalizedHash)),
490
+ ]);
491
+ return { startingHeader: startingBlock, finalizedHeader };
492
+ }
493
+ async #updateBlockNumbersAndParents(finalizedBlockHashes, prevPinnedBlocks) {
494
+ // Fetch headers and update block metadata
495
+ const { startingHeader, finalizedHeader } = await this.#fetchInitializationHeaders(finalizedBlockHashes, prevPinnedBlocks);
496
+ // Detect hasher using the finalized header we just fetched
497
+ this.#hasher = detectHasherFromHeader(finalizedHeader, this.#finalizedHash);
498
+ Object.values(this.#pinnedBlocks).forEach((b, idx) => {
499
+ b.number += startingHeader.number;
500
+ if (idx === 0) {
501
+ b.parent = startingHeader.parent;
502
+ }
503
+ });
504
+ this.#pinnedBlocks[this.#finalizedHash].runtimeUpgraded = this.#hasRuntimeUpgrade(finalizedHeader);
505
+ }
506
+ async #fetchAndApplyRuntimeUpgrades(missingHashes, prevPinnedBlocks) {
507
+ const blocksNeedingHeaders = [];
508
+ for (const hash of missingHashes) {
509
+ const prevBlock = prevPinnedBlocks[hash];
510
+ const currentBlock = this.#pinnedBlocks[hash];
511
+ if (prevBlock?.runtimeUpgraded !== undefined) {
512
+ // Reuse existing runtime info from prevPinnedBlocks
513
+ currentBlock.runtimeUpgraded = prevBlock.runtimeUpgraded;
514
+ }
515
+ else {
516
+ // Need to fetch header for this block
517
+ blocksNeedingHeaders.push(hash);
518
+ }
519
+ }
520
+ if (blocksNeedingHeaders.length === 0)
521
+ return;
522
+ const headers = await Promise.all(blocksNeedingHeaders.map(async (hash) => ({
523
+ hash,
524
+ header: $Header.tryDecode(await this.#getHeader(hash)),
525
+ })));
526
+ // Set runtimeUpgraded flag based on digest check
527
+ for (const { hash, header } of headers) {
528
+ const block = this.#pinnedBlocks[hash];
529
+ if (block) {
530
+ block.runtimeUpgraded = this.#hasRuntimeUpgrade(header);
531
+ }
532
+ }
533
+ }
534
+ async #handleMissingBlocks(finalizedBlockHashes, lastFinalizedHash, prevPinnedBlocks) {
535
+ const lastFinalizedIndex = finalizedBlockHashes.indexOf(lastFinalizedHash);
536
+ if (lastFinalizedIndex !== -1 && lastFinalizedIndex < finalizedBlockHashes.length - 1) {
537
+ // Get missing block hashes that occurred during disconnection
538
+ const missingHashes = finalizedBlockHashes.slice(lastFinalizedIndex + 1);
539
+ // Identify blocks needing header fetch vs those with cached runtime info
540
+ await this.#fetchAndApplyRuntimeUpgrades(missingHashes, prevPinnedBlocks);
541
+ // Emit all missing finalized blocks with proper runtime info
542
+ for (const hash of missingHashes) {
543
+ const block = this.#pinnedBlocks[hash];
544
+ if (block) {
545
+ this.emit('finalizedBlock', block);
546
+ }
547
+ }
548
+ }
549
+ }
365
550
  #onTheSameChain(b1, b2) {
366
551
  if (!b1 || !b2)
367
552
  return false;
@@ -375,6 +560,23 @@ export class ChainHead extends JsonRpcGroup {
375
560
  return this.#onTheSameChain(this.findBlock(b1.parent), b2);
376
561
  }
377
562
  }
563
+ /**
564
+ * Detect and emit missing best blocks from pinned blocks
565
+ * Called when bestBlockChanged event detects a gap
566
+ */
567
+ #fillBestBlockGaps(lastNumber, currentNumber) {
568
+ // No gap if current is next sequential block
569
+ if (currentNumber <= lastNumber + 1) {
570
+ return;
571
+ }
572
+ // Emit missing blocks from pinned blocks
573
+ for (let num = lastNumber + 1; num < currentNumber; num++) {
574
+ const blocks = this.#findBlocksByNumber(num);
575
+ for (let idx = 0; idx < blocks.length; idx++) {
576
+ this.emit('bestBlock', blocks[idx], idx >= 1);
577
+ }
578
+ }
579
+ }
378
580
  #ensurePinnedHash(hash) {
379
581
  if (hash) {
380
582
  if (this.isPinned(hash)) {
@@ -460,8 +662,10 @@ export class ChainHead extends JsonRpcGroup {
460
662
  this.#unsub = undefined;
461
663
  this.#handlers = {};
462
664
  this.#pendingOperations = {};
665
+ this.#pendingRequests.clear();
463
666
  this.#pinnedBlocks = {};
464
667
  this.#bestHash = undefined;
668
+ this.#lastBestBlockNumber = undefined;
465
669
  this.#finalizedHash = undefined;
466
670
  this.#finalizedRuntime = undefined;
467
671
  this.#followResponseQueue.clear();
@@ -469,6 +673,7 @@ export class ChainHead extends JsonRpcGroup {
469
673
  this.#blockUsage.clear();
470
674
  this.clearCache();
471
675
  this.#operationQueue.cancel();
676
+ this.#hasher = undefined;
472
677
  }
473
678
  async #ensureFollowed() {
474
679
  if (this.#recovering) {
@@ -762,4 +967,11 @@ export class ChainHead extends JsonRpcGroup {
762
967
  this.#cache.clear();
763
968
  this.#archive?.clearCache();
764
969
  }
970
+ holdBlock(hash) {
971
+ this.#ensurePinnedHash(hash);
972
+ this.#blockUsage.use(hash);
973
+ }
974
+ releaseBlock(hash) {
975
+ this.#blockUsage.release(hash);
976
+ }
765
977
  }
@@ -1,9 +1,9 @@
1
1
  import { Properties } from '@dedot/types/json-rpc';
2
2
  import { HexString } from '@dedot/utils';
3
- import { IJsonRpcClient } from '../../types.js';
3
+ import { IJsonRpcClient, IChainSpec } from '../../types.js';
4
4
  import { JsonRpcGroup, JsonRpcGroupOptions } from './JsonRpcGroup.js';
5
- export declare class ChainSpec extends JsonRpcGroup {
6
- constructor(client: IJsonRpcClient, options?: Partial<JsonRpcGroupOptions>);
5
+ export declare class ChainSpec extends JsonRpcGroup implements IChainSpec {
6
+ constructor(client: IJsonRpcClient<any>, options?: Partial<JsonRpcGroupOptions>);
7
7
  chainName(): Promise<string>;
8
8
  genesisHash(): Promise<HexString>;
9
9
  properties(): Promise<Properties>;
@@ -4,7 +4,7 @@ import { HexString } from '@dedot/utils';
4
4
  import { IJsonRpcClient, TxBroadcaster } from '../../types.js';
5
5
  import { JsonRpcGroup, JsonRpcGroupOptions } from './JsonRpcGroup.js';
6
6
  export declare class Transaction extends JsonRpcGroup implements TxBroadcaster {
7
- constructor(client: IJsonRpcClient, options?: Partial<JsonRpcGroupOptions>);
7
+ constructor(client: IJsonRpcClient<any>, options?: Partial<JsonRpcGroupOptions>);
8
8
  broadcastTx(tx: HexString): Promise<Unsub>;
9
9
  broadcast(tx: string): Promise<OperationId>;
10
10
  stop(operationId: OperationId): Promise<void>;
@@ -4,7 +4,7 @@ import { HexString } from '@dedot/utils';
4
4
  import { IJsonRpcClient, TxBroadcaster } from '../../types.js';
5
5
  import { JsonRpcGroup, JsonRpcGroupOptions } from './JsonRpcGroup.js';
6
6
  export declare class TransactionWatch extends JsonRpcGroup implements TxBroadcaster {
7
- constructor(client: IJsonRpcClient, options?: Partial<JsonRpcGroupOptions>);
7
+ constructor(client: IJsonRpcClient<any>, options?: Partial<JsonRpcGroupOptions>);
8
8
  broadcastTx(tx: HexString): Promise<Unsub>;
9
9
  submitAndWatch(tx: HexString, callback: Callback<TransactionWatchEvent>): Promise<Unsub>;
10
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dedot/api",
3
- "version": "0.18.6",
3
+ "version": "1.0.0-next.a0f8d41e.25+a0f8d41e",
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.18.6",
17
- "@dedot/providers": "0.18.6",
18
- "@dedot/runtime-specs": "0.18.6",
19
- "@dedot/shape": "0.18.6",
20
- "@dedot/storage": "0.18.6",
21
- "@dedot/types": "0.18.6",
22
- "@dedot/utils": "0.18.6"
16
+ "@dedot/codecs": "1.0.0-next.a0f8d41e.25+a0f8d41e",
17
+ "@dedot/providers": "1.0.0-next.a0f8d41e.25+a0f8d41e",
18
+ "@dedot/runtime-specs": "1.0.0-next.a0f8d41e.25+a0f8d41e",
19
+ "@dedot/shape": "1.0.0-next.a0f8d41e.25+a0f8d41e",
20
+ "@dedot/storage": "1.0.0-next.a0f8d41e.25+a0f8d41e",
21
+ "@dedot/types": "1.0.0-next.a0f8d41e.25+a0f8d41e",
22
+ "@dedot/utils": "1.0.0-next.a0f8d41e.25+a0f8d41e"
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": "2ff0200696aa2e42505d30369fe84b2416838791",
51
+ "gitHead": "a0f8d41e632a44c03ccc31ee77bcb7d19df936e9",
52
52
  "module": "./index.js",
53
53
  "types": "./index.d.ts"
54
54
  }
package/proxychain.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- import { GenericSubstrateApi } from '@dedot/types';
2
1
  import { Executor } from './executor/index.js';
3
- export interface Carrier<ChainApi extends GenericSubstrateApi> {
4
- executor: Executor<ChainApi>;
2
+ export interface Carrier {
3
+ executor: Executor;
5
4
  chain?: string[];
6
5
  }
7
6
  /**
@@ -11,4 +10,4 @@ export interface Carrier<ChainApi extends GenericSubstrateApi> {
11
10
  * @param currentLevel
12
11
  * @param maxLevel
13
12
  */
14
- export declare const newProxyChain: <ChainApi extends GenericSubstrateApi<import("@dedot/types").RpcVersion>>(carrier: Carrier<ChainApi>, currentLevel?: number, maxLevel?: number) => unknown;
13
+ export declare const newProxyChain: (carrier: Carrier, currentLevel?: number, maxLevel?: number) => unknown;
@@ -1,6 +1,6 @@
1
1
  import { BlockHash, StorageData, StorageKey } from '@dedot/codecs';
2
2
  import type { Callback, Unsub } from '@dedot/types';
3
- import { DedotClient } from '../client/DedotClient.js';
3
+ import { V2Client } from '../client/V2Client.js';
4
4
  import { BaseStorageQuery } from './BaseStorageQuery.js';
5
5
  /**
6
6
  * @name NewStorageQuery
@@ -15,8 +15,8 @@ import { BaseStorageQuery } from './BaseStorageQuery.js';
15
15
  * - Efficient change detection for subscriptions
16
16
  */
17
17
  export declare class NewStorageQuery extends BaseStorageQuery {
18
- protected client: DedotClient<any>;
19
- constructor(client: DedotClient<any>);
18
+ protected client: V2Client<any>;
19
+ constructor(client: V2Client<any>);
20
20
  /**
21
21
  * Query multiple storage items in a single call using chainHead_storage
22
22
  *
@@ -65,6 +65,7 @@ export class QueryableStorage {
65
65
  const keyId = keyTypeIds[index];
66
66
  const hasher = HASHERS[hashers[index]];
67
67
  const $keyCodec = this.registry.findCodec(keyId);
68
+ $keyCodec.assert?.(input);
68
69
  return hasher($keyCodec.tryEncode(input));
69
70
  });
70
71
  return u8aToHex(concatU8a(this.prefixKeyAsU8a, ...keyParts));