@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
@@ -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) {
@@ -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.8",
3
+ "version": "1.0.0",
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.8",
17
- "@dedot/providers": "0.18.8",
18
- "@dedot/runtime-specs": "0.18.8",
19
- "@dedot/shape": "0.18.8",
20
- "@dedot/storage": "0.18.8",
21
- "@dedot/types": "0.18.8",
22
- "@dedot/utils": "0.18.8"
16
+ "@dedot/codecs": "1.0.0",
17
+ "@dedot/providers": "1.0.0",
18
+ "@dedot/runtime-specs": "1.0.0",
19
+ "@dedot/shape": "1.0.0",
20
+ "@dedot/storage": "1.0.0",
21
+ "@dedot/types": "1.0.0",
22
+ "@dedot/utils": "1.0.0"
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": "5f87f515d4ce4183038681dd02b20bb682a5fdce",
51
+ "gitHead": "7767062a2188b4ac90561c5ad3cc3f06ac608a24",
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));
package/types.d.ts CHANGED
@@ -1,9 +1,11 @@
1
- import { BlockHash, Hash, Metadata, PortableRegistry } from '@dedot/codecs';
1
+ import { BlockHash, Extrinsic, Hash, Header, Metadata, PortableRegistry } from '@dedot/codecs';
2
2
  import type { ConnectionStatus, JsonRpcProvider, ProviderEvent } from '@dedot/providers';
3
3
  import type { AnyShape } from '@dedot/shape';
4
4
  import type { IStorage } from '@dedot/storage';
5
- import type { Callback, GenericStorageQuery, GenericSubstrateApi, InjectedSigner, Query, QueryFnResult, RpcVersion, RuntimeApiName, RuntimeApiSpec, Unsub } from '@dedot/types';
5
+ import { Callback, GenericStorageQuery, GenericSubstrateApi, InjectedSigner, Query, QueryFnResult, RpcVersion, RuntimeApiName, RuntimeApiSpec, TxUnsub, Unsub } from '@dedot/types';
6
+ import { Properties } from '@dedot/types/json-rpc';
6
7
  import type { HashFn, HexString, IEventEmitter } from '@dedot/utils';
8
+ import type { SubstrateApi } from './chaintypes/index.js';
7
9
  import type { AnySignedExtension } from './extrinsic/index.js';
8
10
  import type { ChainHeadEvent } from './json-rpc/index.js';
9
11
  export type MetadataKey = `RAW_META/${string}`;
@@ -60,6 +62,20 @@ export interface ApiOptions extends JsonRpcClientOptions {
60
62
  * A signer instance to use for signing transactions
61
63
  */
62
64
  signer?: InjectedSigner;
65
+ /**
66
+ * Timeout in milliseconds for detecting stale WebSocket connections.
67
+ * When set, monitors block subscription activity and triggers reconnection
68
+ * to switch to a different endpoint if no blocks/events received within the timeout period.
69
+ *
70
+ * Note: This option is only supported when used with WsProvider.
71
+ *
72
+ * - Default: 30000 (30 seconds) - staling detection enabled
73
+ * - 0: Disabled - no staling detection
74
+ * - Any positive number: Custom timeout in milliseconds
75
+ *
76
+ * @default 30000
77
+ */
78
+ stalingDetectionTimeout?: number;
63
79
  }
64
80
  export type ApiEvent = ProviderEvent | 'ready' | 'runtimeUpgraded';
65
81
  export type DedotClientEvent = ApiEvent | ChainHeadEvent;
@@ -74,7 +90,7 @@ export interface SubstrateRuntimeVersion {
74
90
  /**
75
91
  * A generic interface for JSON-RPC clients
76
92
  */
77
- export interface IJsonRpcClient<ChainApi extends GenericSubstrateApi = GenericSubstrateApi, Events extends string = ProviderEvent> extends IEventEmitter<Events> {
93
+ export interface IJsonRpcClient<ChainApi extends GenericSubstrateApi = SubstrateApi, Events extends string = ProviderEvent> extends IEventEmitter<Events> {
78
94
  options: JsonRpcClientOptions;
79
95
  status: ConnectionStatus;
80
96
  provider: JsonRpcProvider;
@@ -85,7 +101,7 @@ export interface IJsonRpcClient<ChainApi extends GenericSubstrateApi = GenericSu
85
101
  /**
86
102
  * @internal
87
103
  */
88
- export interface IGenericSubstrateClient<ChainApi extends GenericSubstrateApi = GenericSubstrateApi> {
104
+ export interface IGenericSubstrateClient<ChainApi extends GenericSubstrateApi = SubstrateApi> {
89
105
  rpcVersion: RpcVersion;
90
106
  options: ApiOptions;
91
107
  genesisHash: Hash;
@@ -125,15 +141,37 @@ export interface IGenericSubstrateClient<ChainApi extends GenericSubstrateApi =
125
141
  /**
126
142
  * A generic interface for Substrate clients at a specific block
127
143
  */
128
- export interface ISubstrateClientAt<ChainApi extends GenericSubstrateApi = GenericSubstrateApi> extends IGenericSubstrateClient<ChainApi> {
144
+ export interface ISubstrateClientAt<ChainApi extends GenericSubstrateApi = SubstrateApi> extends IGenericSubstrateClient<ChainApi> {
129
145
  atBlockHash: BlockHash;
130
146
  }
147
+ export interface BlockInfo {
148
+ hash: BlockHash;
149
+ number: number;
150
+ parent: BlockHash;
151
+ runtimeUpgraded: boolean;
152
+ }
153
+ export interface BlockExplorer {
154
+ best(): Promise<BlockInfo>;
155
+ best(callback: (block: BlockInfo) => void): () => void;
156
+ finalized(): Promise<BlockInfo>;
157
+ finalized(callback: (block: BlockInfo) => void): () => void;
158
+ header(hash: BlockHash): Promise<Header>;
159
+ body(hash: BlockHash): Promise<HexString[]>;
160
+ }
161
+ export interface IChainSpec {
162
+ chainName(): Promise<string>;
163
+ genesisHash(): Promise<HexString>;
164
+ properties(): Promise<Properties>;
165
+ }
131
166
  /**
132
167
  * A generic interface for Substrate clients
133
168
  */
134
- export interface ISubstrateClient<ChainApi extends GenericSubstrateApi = GenericSubstrateApi, Events extends string = ApiEvent> extends IJsonRpcClient<ChainApi, Events>, IGenericSubstrateClient<ChainApi> {
169
+ export interface ISubstrateClient<ChainApi extends GenericSubstrateApi = SubstrateApi, // --
170
+ Events extends string = ApiEvent> extends IJsonRpcClient<ChainApi, Events>, IGenericSubstrateClient<ChainApi> {
135
171
  options: ApiOptions;
136
172
  tx: ChainApi['tx'];
173
+ chainSpec: IChainSpec;
174
+ block: BlockExplorer;
137
175
  at<ChainApiAt extends GenericSubstrateApi = ChainApi>(hash: BlockHash): Promise<ISubstrateClientAt<ChainApiAt>>;
138
176
  /**
139
177
  * Get current version of the runtime
@@ -149,6 +187,44 @@ export interface ISubstrateClient<ChainApi extends GenericSubstrateApi = Generic
149
187
  * @param signer
150
188
  */
151
189
  setSigner(signer?: InjectedSigner): void;
190
+ /**
191
+ * Broadcasts a transaction to the network and monitors its status.
192
+ *
193
+ * This method accepts a transaction (either as a hex-encoded string or an Extrinsic instance)
194
+ * and submits it to the network. It returns a TxUnsub object that allows you to:
195
+ * - Subscribe to transaction status updates via an optional callback
196
+ * - Wait for specific transaction states (finalized, included in best chain block)
197
+ *
198
+ * @param tx - The signed transaction to broadcast, either as a hex-encoded string or Extrinsic instance
199
+ * @param callback - Optional callback function to receive transaction status updates. The callback
200
+ * receives an ISubmittableResult object containing status, events, errors, tx hash, etc...
201
+ *
202
+ * @returns A TxUnsub object that is both a Promise<Unsub> and provides utility methods:
203
+ * - `.untilFinalized()` - Resolves when transaction is finalized
204
+ * - `.untilBestChainBlockIncluded()` - Resolves when transaction is included in best chain block
205
+ * - When awaited directly, resolves to an unsubscribe function
206
+ *
207
+ * @example
208
+ * // Basic usage with callback for status updates
209
+ * const unsub = await client.sendTx(rawTxHex, (result) => {
210
+ * console.log('Transaction status:', result.status);
211
+ * console.log('Transaction hash:', result.txHash);
212
+ * if (result.dispatchError) {
213
+ * console.error('Transaction failed:', result.dispatchError);
214
+ * }
215
+ * });
216
+ *
217
+ * @example
218
+ * // Wait for transaction to be included in best chain block
219
+ * const result = await client.sendTx(rawTxHex, console.log).untilBestChainBlockIncluded();
220
+ * console.log('Transaction included in block:', result);
221
+ *
222
+ * @example
223
+ * // Wait for transaction finalization
224
+ * const result = await client.sendTx(rawTxHex).untilFinalized();
225
+ * console.log('Transaction finalized:', result);
226
+ */
227
+ sendTx(tx: HexString | Extrinsic, callback?: Callback): TxUnsub;
152
228
  /**
153
229
  * Query multiple storage items in a single call or subscribe to multiple storage items
154
230
  *
@@ -185,6 +261,17 @@ export interface ISubstrateClient<ChainApi extends GenericSubstrateApi = Generic
185
261
  }, callback: Callback<{
186
262
  [K in keyof Fns]: QueryFnResult<Fns[K]>;
187
263
  }>): Promise<Unsub>;
264
+ on<Event extends Events = Events>(event: Event, handler: EventHandlerFn<Event>): () => void;
265
+ }
266
+ export type EventHandlerFn<Event extends string> = EventTypes[Event];
267
+ interface EventTypes {
268
+ ready: () => void;
269
+ connected: (connectedEndpoint: string) => void;
270
+ disconnected: () => void;
271
+ reconnecting: () => void;
272
+ runtimeUpgraded: (newRuntimeVersion: SubstrateRuntimeVersion, at: BlockInfo) => void;
273
+ error: (error?: Error) => void;
274
+ [event: string]: (...args: any[]) => void;
188
275
  }
189
276
  /**
190
277
  * A generic interface for broadcasting transactions
@@ -202,3 +289,4 @@ export interface TxBroadcaster {
202
289
  */
203
290
  supported(): Promise<boolean>;
204
291
  }
292
+ export {};