@dedot/api 0.18.8 → 1.0.0

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 +12 -2
  47. package/cjs/json-rpc/group/ChainHead/ChainHead.js +226 -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 +13 -3
  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 +18 -2
  94. package/json-rpc/group/ChainHead/ChainHead.js +226 -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
@@ -2,11 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ChainHead = void 0;
4
4
  const codecs_1 = require("@dedot/codecs");
5
+ const providers_1 = require("@dedot/providers");
5
6
  const utils_1 = require("@dedot/utils");
7
+ const utils_js_1 = require("../../../client/utils.js");
6
8
  const JsonRpcGroup_js_1 = require("../JsonRpcGroup.js");
7
9
  const BlockUsage_js_1 = require("./BlockUsage.js");
8
10
  const error_js_1 = require("./error.js");
9
- const MIN_FINALIZED_QUEUE_SIZE = 16; // finalized queue size
11
+ const MIN_FINALIZED_QUEUE_SIZE = 10; // finalized queue size
10
12
  const CHAINHEAD_CACHE_CAPACITY = 256;
11
13
  const CHAINHEAD_CACHE_TTL = 30_000; // 30 seconds
12
14
  class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
@@ -14,11 +16,15 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
14
16
  #subscriptionId;
15
17
  #handlers;
16
18
  #pendingOperations;
19
+ #pendingRequests = new Map();
20
+ #requestIdCounter = 0;
17
21
  #finalizedQueue;
18
22
  #pinnedBlocks;
19
23
  #bestHash;
24
+ #lastBestBlockNumber; // Track last emitted best block number for gap detection
20
25
  #finalizedHash; // best finalized hash
21
26
  #finalizedRuntime;
27
+ #hasher; // detected hasher from block header
22
28
  #followResponseQueue;
23
29
  #retryQueue;
24
30
  #recovering;
@@ -63,6 +69,34 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
63
69
  this.#archive = archive;
64
70
  return this;
65
71
  }
72
+ /**
73
+ * Override send() to track pending requests for retry on network disconnection
74
+ */
75
+ async send(method, ...params) {
76
+ const requestId = `req_${++this.#requestIdCounter}`;
77
+ const defer = (0, utils_1.deferred)();
78
+ this.#pendingRequests.set(requestId, {
79
+ method,
80
+ params,
81
+ defer,
82
+ });
83
+ try {
84
+ const result = await super.send(method, ...params);
85
+ this.#pendingRequests.delete(requestId);
86
+ defer.resolve(result);
87
+ return result;
88
+ }
89
+ catch (error) {
90
+ if (error instanceof providers_1.NetworkDisconnectedError) {
91
+ // Keep in pending for retry on re-follow - don't delete
92
+ // Return the deferred promise that will be resolved on retry
93
+ return defer.promise;
94
+ }
95
+ // Other errors: clean up and throw
96
+ this.#pendingRequests.delete(requestId);
97
+ throw error;
98
+ }
99
+ }
66
100
  async runtimeVersion() {
67
101
  await this.#ensureFollowed();
68
102
  return this.#finalizedRuntime;
@@ -85,9 +119,20 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
85
119
  async finalizedBlock() {
86
120
  return this.findBlock(await this.finalizedHash());
87
121
  }
122
+ /**
123
+ * Returns the detected hasher for the chain
124
+ * Returns undefined if detection failed (client should use default)
125
+ */
126
+ async hasher() {
127
+ await this.#ensureFollowed();
128
+ return this.#hasher;
129
+ }
88
130
  findBlock(hash) {
89
131
  return this.#pinnedBlocks[hash];
90
132
  }
133
+ #findBlocksByNumber(blockNumber) {
134
+ return Object.values(this.#pinnedBlocks).filter((block) => block.number === blockNumber);
135
+ }
91
136
  isPinned(hash) {
92
137
  return !!this.findBlock(hash);
93
138
  }
@@ -102,8 +147,10 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
102
147
  /**
103
148
  * chainHead_follow
104
149
  */
105
- async follow() {
106
- (0, utils_1.assert)(!this.#subscriptionId, 'Already followed chain head. Please unfollow first.');
150
+ async follow(force) {
151
+ if (!force) {
152
+ (0, utils_1.assert)(!this.#subscriptionId, 'Already followed chain head. Please unfollow first.');
153
+ }
107
154
  return this.#doFollow();
108
155
  }
109
156
  async #doFollow() {
@@ -132,7 +179,41 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
132
179
  catch (e) {
133
180
  defer.reject(e);
134
181
  }
135
- return defer.promise;
182
+ // Wait for follow to complete, then retry pending requests
183
+ await defer.promise;
184
+ // Retry pending requests if any (from previous disconnection)
185
+ if (this.#pendingRequests.size > 0) {
186
+ this.#retryPendingRequests();
187
+ }
188
+ }
189
+ /**
190
+ * Retry pending requests that were queued during network disconnection
191
+ */
192
+ #retryPendingRequests() {
193
+ const pending = Array.from(this.#pendingRequests.entries());
194
+ for (const [requestId, request] of pending) {
195
+ const { method, params, defer } = request;
196
+ // Update subscription ID with the new one (first param for all ChainHead RPC methods)
197
+ const updatedParams = [...params];
198
+ if (updatedParams.length > 0) {
199
+ updatedParams[0] = this.#subscriptionId;
200
+ }
201
+ // Retry the request with new subscriptionId
202
+ super
203
+ .send(method, ...updatedParams)
204
+ .then((result) => {
205
+ defer.resolve(result);
206
+ this.#pendingRequests.delete(requestId);
207
+ })
208
+ .catch((error) => {
209
+ if (!(error instanceof providers_1.NetworkDisconnectedError)) {
210
+ // Real error, reject and clean up
211
+ defer.reject(error);
212
+ this.#pendingRequests.delete(requestId);
213
+ }
214
+ // If NetworkDisconnectedError again, keep in pending for next retry
215
+ });
216
+ }
136
217
  }
137
218
  #onFollowEvent = async (result, subscription) => {
138
219
  switch (result.event) {
@@ -140,6 +221,9 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
140
221
  const { finalizedBlockHashes = [], finalizedBlockHash, finalizedBlockRuntime } = result;
141
222
  if (finalizedBlockHash)
142
223
  finalizedBlockHashes.push(finalizedBlockHash);
224
+ const lastFinalizedHash = this.#finalizedHash;
225
+ const prevPinnedBlocks = this.#pinnedBlocks;
226
+ // Initialize subscription state
143
227
  this.#subscriptionId = subscription.subscriptionId;
144
228
  this.#finalizedQueue = finalizedBlockHashes;
145
229
  if (finalizedBlockHashes.length > MIN_FINALIZED_QUEUE_SIZE) {
@@ -147,35 +231,37 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
147
231
  }
148
232
  this.#finalizedRuntime = this.#extractRuntime(finalizedBlockRuntime);
149
233
  (0, utils_1.assert)(this.#finalizedRuntime, 'Invalid finalized runtime');
150
- this.#bestHash = this.#finalizedHash = finalizedBlockHashes.at(-1);
151
- this.#pinnedBlocks = finalizedBlockHashes.reduce((o, hash, idx, arr) => {
152
- o[hash] = { hash, parent: arr[idx - 1], number: idx };
153
- // assign finalized runtime to the current finalized block
154
- if (idx === finalizedBlockHashes.length - 1) {
155
- o[hash]['runtime'] = this.#finalizedRuntime;
156
- }
157
- return o;
158
- }, {});
159
- const header = codecs_1.$Header.tryDecode(await this.#getHeader(finalizedBlockHashes[0]));
160
- Object.values(this.#pinnedBlocks).forEach((b, idx) => {
161
- b.number += header.number;
162
- if (idx === 0) {
163
- b.parent = header.parentHash;
164
- }
165
- });
234
+ // Build initial pinned blocks
235
+ this.#finalizedHash = finalizedBlockHashes.at(-1);
236
+ if (!this.#bestHash) {
237
+ this.#bestHash = this.#finalizedHash;
238
+ }
239
+ this.#pinnedBlocks = this.#buildInitialPinnedBlocks(finalizedBlockHashes, this.#finalizedRuntime);
240
+ await this.#updateBlockNumbersAndParents(finalizedBlockHashes, prevPinnedBlocks);
241
+ // Handle first initialization or reconnection
242
+ if (!lastFinalizedHash) {
243
+ this.emit('finalizedBlock', await this.finalizedBlock());
244
+ }
245
+ else {
246
+ await this.#handleMissingBlocks(finalizedBlockHashes, lastFinalizedHash, prevPinnedBlocks);
247
+ }
166
248
  break;
167
249
  }
168
250
  case 'newBlock': {
169
251
  const { blockHash: hash, parentBlockHash: parent, newRuntime } = result;
170
252
  const runtime = this.#extractRuntime(newRuntime);
171
253
  const parentBlock = this.findBlock(parent);
172
- (0, utils_1.assert)(parentBlock, `Parent block not found for new block ${hash}`);
254
+ if (!parentBlock) {
255
+ console.warn(`Parent block not found for new block ${hash}`);
256
+ return;
257
+ }
173
258
  this.#pinnedBlocks[hash] = {
174
259
  hash,
175
260
  parent,
176
261
  // if the runtime is not provided, we'll find it from the parent block
177
262
  runtime: runtime || this.#findRuntimeAt(parent),
178
263
  number: parentBlock.number + 1,
264
+ runtimeUpgraded: !!runtime,
179
265
  };
180
266
  this.emit('newBlock', this.#pinnedBlocks[hash]);
181
267
  break;
@@ -188,8 +274,13 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
188
274
  return;
189
275
  if (currentBestBlock.hash === newBestBlock.hash)
190
276
  return;
277
+ // Detect and fill gaps before updating best hash
278
+ if (this.#lastBestBlockNumber !== undefined) {
279
+ this.#fillBestBlockGaps(this.#lastBestBlockNumber, newBestBlock.number);
280
+ }
191
281
  const bestChainChanged = !this.#onTheSameChain(currentBestBlock, newBestBlock);
192
282
  this.#bestHash = bestBlockHash;
283
+ this.#lastBestBlockNumber = newBestBlock.number; // Update tracking
193
284
  this.emit('bestBlock', newBestBlock, bestChainChanged);
194
285
  if (bestChainChanged)
195
286
  this.emit('bestChainChanged', newBestBlock);
@@ -365,6 +456,100 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
365
456
  return;
366
457
  return this.#findRuntimeAt(block.parent);
367
458
  }
459
+ #hasRuntimeUpgrade(header) {
460
+ return header.digest.logs.some((log) => log.type === 'RuntimeEnvironmentUpdated');
461
+ }
462
+ #buildInitialPinnedBlocks(finalizedBlockHashes, runtime) {
463
+ return finalizedBlockHashes.reduce((o, hash, idx, arr) => {
464
+ o[hash] = { hash, parent: arr[idx - 1], number: idx };
465
+ // assign finalized runtime to the current finalized block
466
+ if (idx === finalizedBlockHashes.length - 1) {
467
+ o[hash]['runtime'] = runtime;
468
+ }
469
+ return o;
470
+ }, {});
471
+ }
472
+ async #fetchInitializationHeaders(finalizedBlockHashes, prevPinnedBlocks) {
473
+ const fetchStartingHeader = async () => {
474
+ const startingBlockHash = finalizedBlockHashes[0];
475
+ const existing = prevPinnedBlocks[startingBlockHash];
476
+ if (existing) {
477
+ return {
478
+ number: existing.number,
479
+ parent: existing.parent,
480
+ };
481
+ }
482
+ else {
483
+ const header = codecs_1.$Header.tryDecode(await this.#getHeader(startingBlockHash));
484
+ return {
485
+ number: header.number,
486
+ parent: header.parentHash,
487
+ };
488
+ }
489
+ };
490
+ const [startingBlock, finalizedHeader] = await Promise.all([
491
+ fetchStartingHeader(),
492
+ codecs_1.$Header.tryDecode(await this.#getHeader(this.#finalizedHash)),
493
+ ]);
494
+ return { startingHeader: startingBlock, finalizedHeader };
495
+ }
496
+ async #updateBlockNumbersAndParents(finalizedBlockHashes, prevPinnedBlocks) {
497
+ // Fetch headers and update block metadata
498
+ const { startingHeader, finalizedHeader } = await this.#fetchInitializationHeaders(finalizedBlockHashes, prevPinnedBlocks);
499
+ // Detect hasher using the finalized header we just fetched
500
+ this.#hasher = (0, utils_js_1.detectHasherFromHeader)(finalizedHeader, this.#finalizedHash);
501
+ Object.values(this.#pinnedBlocks).forEach((b, idx) => {
502
+ b.number += startingHeader.number;
503
+ if (idx === 0) {
504
+ b.parent = startingHeader.parent;
505
+ }
506
+ });
507
+ this.#pinnedBlocks[this.#finalizedHash].runtimeUpgraded = this.#hasRuntimeUpgrade(finalizedHeader);
508
+ }
509
+ async #fetchAndApplyRuntimeUpgrades(missingHashes, prevPinnedBlocks) {
510
+ const blocksNeedingHeaders = [];
511
+ for (const hash of missingHashes) {
512
+ const prevBlock = prevPinnedBlocks[hash];
513
+ const currentBlock = this.#pinnedBlocks[hash];
514
+ if (prevBlock?.runtimeUpgraded !== undefined) {
515
+ // Reuse existing runtime info from prevPinnedBlocks
516
+ currentBlock.runtimeUpgraded = prevBlock.runtimeUpgraded;
517
+ }
518
+ else {
519
+ // Need to fetch header for this block
520
+ blocksNeedingHeaders.push(hash);
521
+ }
522
+ }
523
+ if (blocksNeedingHeaders.length === 0)
524
+ return;
525
+ const headers = await Promise.all(blocksNeedingHeaders.map(async (hash) => ({
526
+ hash,
527
+ header: codecs_1.$Header.tryDecode(await this.#getHeader(hash)),
528
+ })));
529
+ // Set runtimeUpgraded flag based on digest check
530
+ for (const { hash, header } of headers) {
531
+ const block = this.#pinnedBlocks[hash];
532
+ if (block) {
533
+ block.runtimeUpgraded = this.#hasRuntimeUpgrade(header);
534
+ }
535
+ }
536
+ }
537
+ async #handleMissingBlocks(finalizedBlockHashes, lastFinalizedHash, prevPinnedBlocks) {
538
+ const lastFinalizedIndex = finalizedBlockHashes.indexOf(lastFinalizedHash);
539
+ if (lastFinalizedIndex !== -1 && lastFinalizedIndex < finalizedBlockHashes.length - 1) {
540
+ // Get missing block hashes that occurred during disconnection
541
+ const missingHashes = finalizedBlockHashes.slice(lastFinalizedIndex + 1);
542
+ // Identify blocks needing header fetch vs those with cached runtime info
543
+ await this.#fetchAndApplyRuntimeUpgrades(missingHashes, prevPinnedBlocks);
544
+ // Emit all missing finalized blocks with proper runtime info
545
+ for (const hash of missingHashes) {
546
+ const block = this.#pinnedBlocks[hash];
547
+ if (block) {
548
+ this.emit('finalizedBlock', block);
549
+ }
550
+ }
551
+ }
552
+ }
368
553
  #onTheSameChain(b1, b2) {
369
554
  if (!b1 || !b2)
370
555
  return false;
@@ -378,6 +563,23 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
378
563
  return this.#onTheSameChain(this.findBlock(b1.parent), b2);
379
564
  }
380
565
  }
566
+ /**
567
+ * Detect and emit missing best blocks from pinned blocks
568
+ * Called when bestBlockChanged event detects a gap
569
+ */
570
+ #fillBestBlockGaps(lastNumber, currentNumber) {
571
+ // No gap if current is next sequential block
572
+ if (currentNumber <= lastNumber + 1) {
573
+ return;
574
+ }
575
+ // Emit missing blocks from pinned blocks
576
+ for (let num = lastNumber + 1; num < currentNumber; num++) {
577
+ const blocks = this.#findBlocksByNumber(num);
578
+ for (let idx = 0; idx < blocks.length; idx++) {
579
+ this.emit('bestBlock', blocks[idx], idx >= 1);
580
+ }
581
+ }
582
+ }
381
583
  #ensurePinnedHash(hash) {
382
584
  if (hash) {
383
585
  if (this.isPinned(hash)) {
@@ -463,8 +665,10 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
463
665
  this.#unsub = undefined;
464
666
  this.#handlers = {};
465
667
  this.#pendingOperations = {};
668
+ this.#pendingRequests.clear();
466
669
  this.#pinnedBlocks = {};
467
670
  this.#bestHash = undefined;
671
+ this.#lastBestBlockNumber = undefined;
468
672
  this.#finalizedHash = undefined;
469
673
  this.#finalizedRuntime = undefined;
470
674
  this.#followResponseQueue.clear();
@@ -472,6 +676,7 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
472
676
  this.#blockUsage.clear();
473
677
  this.clearCache();
474
678
  this.#operationQueue.cancel();
679
+ this.#hasher = undefined;
475
680
  }
476
681
  async #ensureFollowed() {
477
682
  if (this.#recovering) {
@@ -68,6 +68,7 @@ class QueryableStorage {
68
68
  const keyId = keyTypeIds[index];
69
69
  const hasher = utils_1.HASHERS[hashers[index]];
70
70
  const $keyCodec = this.registry.findCodec(keyId);
71
+ $keyCodec.assert?.(input);
71
72
  return hasher($keyCodec.tryEncode(input));
72
73
  });
73
74
  return (0, utils_2.u8aToHex)((0, utils_2.concatU8a)(this.prefixKeyAsU8a, ...keyParts));
@@ -1,21 +1,22 @@
1
- import { BlockHash, Hash, Metadata, PortableRegistry, RuntimeVersion } from '@dedot/codecs';
2
- import type { JsonRpcProvider } from '@dedot/providers';
1
+ import { BlockHash, Extrinsic, Hash, Metadata, PortableRegistry, RuntimeVersion } from '@dedot/codecs';
2
+ import { type JsonRpcProvider } from '@dedot/providers';
3
3
  import { type IStorage } from '@dedot/storage';
4
- import { Callback, GenericStorageQuery, GenericSubstrateApi, InjectedSigner, Query, QueryFnResult, RpcVersion, Unsub, VersionedGenericSubstrateApi } from '@dedot/types';
5
- import { Deferred, LRUCache } from '@dedot/utils';
4
+ import { Callback, GenericStorageQuery, GenericSubstrateApi, InjectedSigner, Query, QueryFnResult, RpcVersion, TxUnsub, Unsub } from '@dedot/types';
5
+ import { Deferred, HexString, LRUCache } from '@dedot/utils';
6
6
  import type { SubstrateApi } from '../chaintypes/index.js';
7
7
  import { JsonRpcClient } from '../json-rpc/index.js';
8
8
  import { BaseStorageQuery } from '../storage/index.js';
9
- import type { ApiEvent, ApiOptions, ISubstrateClient, ISubstrateClientAt, JsonRpcClientOptions, MetadataKey, SubstrateRuntimeVersion } from '../types.js';
9
+ import type { ApiEvent, ApiOptions, BlockExplorer, EventHandlerFn, IChainSpec, ISubstrateClient, ISubstrateClientAt, JsonRpcClientOptions, MetadataKey, SubstrateRuntimeVersion } from '../types.js';
10
10
  export declare function ensurePresence<T>(value: T): NonNullable<T>;
11
11
  /**
12
12
  * @name BaseSubstrateClient
13
13
  * @description Base & shared abstraction for Substrate API Clients
14
14
  */
15
- export declare abstract class BaseSubstrateClient<Rv extends RpcVersion, ChainApi extends VersionedGenericSubstrateApi = SubstrateApi, Events extends string = ApiEvent> extends JsonRpcClient<ChainApi, Events> implements ISubstrateClient<ChainApi[Rv], Events> {
15
+ export declare abstract class BaseSubstrateClient<ChainApi extends GenericSubstrateApi = SubstrateApi, Events extends string = ApiEvent> extends JsonRpcClient<ChainApi, Events> implements ISubstrateClient<ChainApi, Events> {
16
+ #private;
16
17
  rpcVersion: RpcVersion;
17
18
  protected _options: ApiOptions;
18
- protected _registry?: PortableRegistry<ChainApi[Rv]['types']>;
19
+ protected _registry?: PortableRegistry<ChainApi['types']>;
19
20
  protected _metadata?: Metadata;
20
21
  protected _genesisHash?: Hash;
21
22
  protected _runtimeVersion?: SubstrateRuntimeVersion;
@@ -30,7 +31,7 @@ export declare abstract class BaseSubstrateClient<Rv extends RpcVersion, ChainAp
30
31
  * Safely set metadata to cache with fallback cleanup if storage limit is exceeded
31
32
  */
32
33
  protected safeSetMetadataToCache(key: string, value: string): Promise<void>;
33
- protected setMetadata(metadata: Metadata): void;
34
+ protected setMetadata(metadata: Metadata): Promise<void>;
34
35
  protected getMetadataKey(runtime?: SubstrateRuntimeVersion): MetadataKey;
35
36
  get currentMetadataKey(): string;
36
37
  protected shouldPreloadMetadata(): Promise<boolean>;
@@ -42,6 +43,17 @@ export declare abstract class BaseSubstrateClient<Rv extends RpcVersion, ChainAp
42
43
  */
43
44
  protected findMetadataInCache(specVersion: number): [Metadata, PortableRegistry] | undefined;
44
45
  protected fetchMetadata(hash?: BlockHash, runtime?: SubstrateRuntimeVersion): Promise<Metadata>;
46
+ /**
47
+ * Set up staling detection with timeout reset logic.
48
+ * Returns a callback function that should be called on each activity (block/event).
49
+ *
50
+ * @returns A function to call on each activity to reset the timeout
51
+ */
52
+ protected getStalingDetectionFn(): (() => void) | undefined;
53
+ /**
54
+ * Clean up staling detection timer
55
+ */
56
+ protected cleanupStalingDetection(): void;
45
57
  protected cleanUp(): void;
46
58
  /**
47
59
  * @description Clear local cache and API at-block cache
@@ -69,19 +81,20 @@ export declare abstract class BaseSubstrateClient<Rv extends RpcVersion, ChainAp
69
81
  disconnect(): Promise<void>;
70
82
  get options(): ApiOptions;
71
83
  get metadata(): Metadata;
72
- get registry(): PortableRegistry<ChainApi[Rv]['types']>;
84
+ get registry(): PortableRegistry<ChainApi['types']>;
73
85
  get genesisHash(): Hash;
74
86
  get runtimeVersion(): SubstrateRuntimeVersion;
75
87
  getRuntimeVersion(): Promise<SubstrateRuntimeVersion>;
76
- get consts(): ChainApi[Rv]['consts'];
77
- get errors(): ChainApi[Rv]['errors'];
78
- get events(): ChainApi[Rv]['events'];
79
- get query(): ChainApi[Rv]['query'];
80
- get view(): ChainApi[Rv]['view'];
81
- get call(): ChainApi[Rv]['call'];
82
- protected callAt(hash?: BlockHash): ChainApi[Rv]['call'];
83
- get tx(): ChainApi[Rv]['tx'];
84
- at<ChainApiAt extends GenericSubstrateApi = ChainApi[Rv]>(hash: BlockHash): Promise<ISubstrateClientAt<ChainApiAt>>;
88
+ get block(): BlockExplorer;
89
+ get consts(): ChainApi['consts'];
90
+ get errors(): ChainApi['errors'];
91
+ get events(): ChainApi['events'];
92
+ get query(): ChainApi['query'];
93
+ get view(): ChainApi['view'];
94
+ get call(): ChainApi['call'];
95
+ protected callAt(hash?: BlockHash): ChainApi['call'];
96
+ get tx(): ChainApi['tx'];
97
+ at<ChainApiAt extends GenericSubstrateApi = ChainApi>(hash: BlockHash): Promise<ISubstrateClientAt<ChainApiAt>>;
85
98
  setSigner(signer?: InjectedSigner): void;
86
99
  protected internalQueryMulti(queries: {
87
100
  fn: GenericStorageQuery;
@@ -119,4 +132,7 @@ export declare abstract class BaseSubstrateClient<Rv extends RpcVersion, ChainAp
119
132
  [K in keyof Fns]: QueryFnResult<Fns[K]>;
120
133
  }>): Promise<Unsub>;
121
134
  protected getStorageQuery(): BaseStorageQuery;
135
+ sendTx(tx: HexString | Extrinsic, callback?: Callback): TxUnsub;
136
+ get chainSpec(): IChainSpec;
137
+ on<Event extends Events = Events>(event: Event, handler: EventHandlerFn<Event>): () => void;
122
138
  }
@@ -1,6 +1,7 @@
1
- import { $Metadata, PortableRegistry } from '@dedot/codecs';
1
+ import { $Metadata, PortableRegistry, } from '@dedot/codecs';
2
+ import { WsProvider } from '@dedot/providers';
2
3
  import { LocalStorage } from '@dedot/storage';
3
- import { calcRuntimeApiHash, deferred, ensurePresence as _ensurePresence, u8aToHex, LRUCache, } from '@dedot/utils';
4
+ import { calcRuntimeApiHash, deferred, ensurePresence as _ensurePresence, LRUCache, noop, u8aToHex, } from '@dedot/utils';
4
5
  import { ConstantExecutor, ErrorExecutor, EventExecutor } from '../executor/index.js';
5
6
  import { isJsonRpcProvider, JsonRpcClient } from '../json-rpc/index.js';
6
7
  import { newProxyChain } from '../proxychain.js';
@@ -27,6 +28,7 @@ export class BaseSubstrateClient extends JsonRpcClient {
27
28
  _localCache;
28
29
  _runtimeUpgrading;
29
30
  _apiAtCache;
31
+ #stalingWatchdogTimer;
30
32
  constructor(rpcVersion, options) {
31
33
  super(options);
32
34
  this.rpcVersion = rpcVersion;
@@ -94,7 +96,7 @@ export class BaseSubstrateClient extends JsonRpcClient {
94
96
  if (!metadata) {
95
97
  throw new Error('Cannot load metadata');
96
98
  }
97
- this.setMetadata(metadata);
99
+ await this.setMetadata(metadata);
98
100
  }
99
101
  /**
100
102
  * Safely set metadata to cache with fallback cleanup if storage limit is exceeded
@@ -126,7 +128,7 @@ export class BaseSubstrateClient extends JsonRpcClient {
126
128
  }
127
129
  }
128
130
  }
129
- setMetadata(metadata) {
131
+ async setMetadata(metadata) {
130
132
  this._metadata = metadata;
131
133
  this._registry = new PortableRegistry(metadata.latest, this.options.hasher);
132
134
  }
@@ -199,6 +201,34 @@ export class BaseSubstrateClient extends JsonRpcClient {
199
201
  return await this.rpc.state_getMetadata();
200
202
  }
201
203
  }
204
+ /**
205
+ * Set up staling detection with timeout reset logic.
206
+ * Returns a callback function that should be called on each activity (block/event).
207
+ *
208
+ * @returns A function to call on each activity to reset the timeout
209
+ */
210
+ getStalingDetectionFn() {
211
+ const timeout = this._options.stalingDetectionTimeout ?? 30_000; // default to 30 seconds
212
+ if (timeout === 0 || !(this.provider instanceof WsProvider)) {
213
+ return;
214
+ }
215
+ return () => {
216
+ this.cleanupStalingDetection();
217
+ this.#stalingWatchdogTimer = setTimeout(() => {
218
+ console.warn(`No new blocks received for over ${timeout}ms, triggering reconnection with endpoint switch...`);
219
+ this.provider.disconnect(true).catch(noop);
220
+ }, timeout);
221
+ };
222
+ }
223
+ /**
224
+ * Clean up staling detection timer
225
+ */
226
+ cleanupStalingDetection() {
227
+ if (this.#stalingWatchdogTimer) {
228
+ clearTimeout(this.#stalingWatchdogTimer);
229
+ this.#stalingWatchdogTimer = undefined;
230
+ }
231
+ }
202
232
  cleanUp() {
203
233
  this._registry = undefined;
204
234
  this._metadata = undefined;
@@ -206,6 +236,7 @@ export class BaseSubstrateClient extends JsonRpcClient {
206
236
  this._runtimeVersion = undefined;
207
237
  this._localCache = undefined;
208
238
  this._apiAtCache.clear();
239
+ this.cleanupStalingDetection();
209
240
  }
210
241
  /**
211
242
  * @description Clear local cache and API at-block cache
@@ -306,6 +337,9 @@ export class BaseSubstrateClient extends JsonRpcClient {
306
337
  await this.ensureRuntimeUpgraded();
307
338
  return this.runtimeVersion;
308
339
  }
340
+ get block() {
341
+ throw new Error('Unimplemented!');
342
+ }
309
343
  get consts() {
310
344
  return newProxyChain({ executor: new ConstantExecutor(this) });
311
345
  }
@@ -369,4 +403,13 @@ export class BaseSubstrateClient extends JsonRpcClient {
369
403
  getStorageQuery() {
370
404
  throw new Error('Unimplemented!');
371
405
  }
406
+ sendTx(tx, callback) {
407
+ throw new Error('Unimplemented!');
408
+ }
409
+ get chainSpec() {
410
+ throw new Error('Unimplemented!');
411
+ }
412
+ on(event, handler) {
413
+ return super.on(event, handler);
414
+ }
372
415
  }