@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.
- package/chaintypes/substrate/consts.d.ts +2 -2
- package/chaintypes/substrate/consts.js +1 -0
- package/chaintypes/substrate/errors.d.ts +909 -909
- package/chaintypes/substrate/errors.js +1 -0
- package/chaintypes/substrate/events.d.ts +664 -664
- package/chaintypes/substrate/events.js +1 -0
- package/chaintypes/substrate/index.d.ts +13 -16
- package/chaintypes/substrate/index.js +1 -0
- package/chaintypes/substrate/json-rpc.d.ts +2 -2
- package/chaintypes/substrate/json-rpc.js +1 -0
- package/chaintypes/substrate/query.d.ts +509 -527
- package/chaintypes/substrate/query.js +1 -0
- package/chaintypes/substrate/runtime.d.ts +123 -123
- package/chaintypes/substrate/runtime.js +1 -0
- package/chaintypes/substrate/tx.d.ts +1311 -1311
- package/chaintypes/substrate/tx.js +1 -0
- package/chaintypes/substrate/types.js +1 -0
- package/chaintypes/substrate/view-functions.d.ts +2 -2
- package/chaintypes/substrate/view-functions.js +1 -0
- package/cjs/chaintypes/substrate/consts.js +1 -0
- package/cjs/chaintypes/substrate/errors.js +1 -0
- package/cjs/chaintypes/substrate/events.js +1 -0
- package/cjs/chaintypes/substrate/index.js +1 -0
- package/cjs/chaintypes/substrate/json-rpc.js +1 -0
- package/cjs/chaintypes/substrate/query.js +1 -0
- package/cjs/chaintypes/substrate/runtime.js +1 -0
- package/cjs/chaintypes/substrate/tx.js +1 -0
- package/cjs/chaintypes/substrate/types.js +1 -0
- package/cjs/chaintypes/substrate/view-functions.js +1 -0
- package/cjs/client/BaseSubstrateClient.js +45 -2
- package/cjs/client/DedotClient.js +356 -224
- package/cjs/client/LegacyClient.js +99 -23
- package/cjs/client/V2Client.js +316 -0
- package/cjs/client/explorer/LegacyBlockExplorer.js +263 -0
- package/cjs/client/explorer/V2BlockExplorer.js +74 -0
- package/cjs/client/explorer/index.js +18 -0
- package/cjs/client/utils.js +57 -0
- package/cjs/executor/EventExecutor.js +1 -0
- package/cjs/executor/RuntimeApiExecutor.js +49 -2
- package/cjs/executor/StorageQueryExecutor.js +114 -2
- package/cjs/executor/ViewFunctionExecutor.js +21 -2
- package/cjs/executor/v2/StorageQueryExecutorV2.js +7 -1
- package/cjs/executor/validation-helpers.js +181 -0
- package/cjs/extrinsic/submittable/BaseSubmittableExtrinsic.js +86 -2
- package/cjs/extrinsic/submittable/SubmittableExtrinsic.js +38 -16
- package/cjs/extrinsic/submittable/SubmittableExtrinsicV2.js +12 -2
- package/cjs/json-rpc/group/ChainHead/ChainHead.js +226 -21
- package/cjs/storage/QueryableStorage.js +1 -0
- package/client/BaseSubstrateClient.d.ts +34 -18
- package/client/BaseSubstrateClient.js +47 -4
- package/client/DedotClient.d.ts +346 -49
- package/client/DedotClient.js +356 -224
- package/client/LegacyClient.d.ts +22 -17
- package/client/LegacyClient.js +101 -25
- package/client/V2Client.d.ts +75 -0
- package/client/V2Client.js +312 -0
- package/client/explorer/LegacyBlockExplorer.d.ts +68 -0
- package/client/explorer/LegacyBlockExplorer.js +259 -0
- package/client/explorer/V2BlockExplorer.d.ts +40 -0
- package/client/explorer/V2BlockExplorer.js +70 -0
- package/client/explorer/index.d.ts +2 -0
- package/client/explorer/index.js +2 -0
- package/client/utils.d.ts +18 -0
- package/client/utils.js +52 -0
- package/executor/ConstantExecutor.d.ts +1 -2
- package/executor/ErrorExecutor.d.ts +2 -2
- package/executor/EventExecutor.d.ts +2 -2
- package/executor/EventExecutor.js +1 -0
- package/executor/Executor.d.ts +4 -5
- package/executor/RuntimeApiExecutor.d.ts +2 -2
- package/executor/RuntimeApiExecutor.js +27 -3
- package/executor/StorageQueryExecutor.d.ts +3 -2
- package/executor/StorageQueryExecutor.js +92 -3
- package/executor/TxExecutor.d.ts +2 -2
- package/executor/ViewFunctionExecutor.d.ts +2 -2
- package/executor/ViewFunctionExecutor.js +22 -3
- package/executor/v2/RuntimeApiExecutorV2.d.ts +2 -3
- package/executor/v2/StorageQueryExecutorV2.d.ts +3 -3
- package/executor/v2/StorageQueryExecutorV2.js +8 -2
- package/executor/v2/TxExecutorV2.d.ts +5 -5
- package/executor/v2/ViewFunctionExecutorV2.d.ts +2 -3
- package/executor/validation-helpers.d.ts +36 -0
- package/executor/validation-helpers.js +174 -0
- package/extrinsic/extensions/SignedExtension.d.ts +1 -1
- package/extrinsic/submittable/BaseSubmittableExtrinsic.d.ts +7 -3
- package/extrinsic/submittable/BaseSubmittableExtrinsic.js +64 -3
- package/extrinsic/submittable/SubmittableExtrinsic.d.ts +4 -1
- package/extrinsic/submittable/SubmittableExtrinsic.js +39 -17
- package/extrinsic/submittable/SubmittableExtrinsicV2.d.ts +6 -3
- package/extrinsic/submittable/SubmittableExtrinsicV2.js +13 -3
- package/json-rpc/JsonRpcClient.d.ts +6 -6
- package/json-rpc/group/Archive.d.ts +1 -1
- package/json-rpc/group/ChainHead/ChainHead.d.ts +18 -2
- package/json-rpc/group/ChainHead/ChainHead.js +226 -21
- package/json-rpc/group/ChainSpec.d.ts +3 -3
- package/json-rpc/group/Transaction.d.ts +1 -1
- package/json-rpc/group/TransactionWatch.d.ts +1 -1
- package/package.json +9 -9
- package/proxychain.d.ts +3 -4
- package/storage/NewStorageQuery.d.ts +3 -3
- package/storage/QueryableStorage.js +1 -0
- 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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
148
|
-
this.#
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
17
|
-
"@dedot/providers": "0.
|
|
18
|
-
"@dedot/runtime-specs": "0.
|
|
19
|
-
"@dedot/shape": "0.
|
|
20
|
-
"@dedot/storage": "0.
|
|
21
|
-
"@dedot/types": "0.
|
|
22
|
-
"@dedot/utils": "0.
|
|
16
|
+
"@dedot/codecs": "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": "
|
|
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
|
|
4
|
-
executor: Executor
|
|
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:
|
|
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 {
|
|
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:
|
|
19
|
-
constructor(client:
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 {};
|