@hyperbridge/sdk 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 (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/dist/abis/erc6160.d.ts +370 -0
  4. package/dist/abis/erc6160.js +238 -0
  5. package/dist/abis/erc6160.js.map +1 -0
  6. package/dist/abis/evmHost.d.ts +1752 -0
  7. package/dist/abis/evmHost.js +2250 -0
  8. package/dist/abis/evmHost.js.map +1 -0
  9. package/dist/abis/handler.d.ts +580 -0
  10. package/dist/abis/handler.js +750 -0
  11. package/dist/abis/handler.js.map +1 -0
  12. package/dist/abis/pingModule.d.ts +594 -0
  13. package/dist/abis/pingModule.js +765 -0
  14. package/dist/abis/pingModule.js.map +1 -0
  15. package/dist/abis/tokenGateway.d.ts +839 -0
  16. package/dist/abis/tokenGateway.js +471 -0
  17. package/dist/abis/tokenGateway.js.map +1 -0
  18. package/dist/chain.d.ts +83 -0
  19. package/dist/chain.js +34 -0
  20. package/dist/chain.js.map +1 -0
  21. package/dist/chains/evm.d.ts +86 -0
  22. package/dist/chains/evm.js +249 -0
  23. package/dist/chains/evm.js.map +1 -0
  24. package/dist/chains/substrate.d.ts +88 -0
  25. package/dist/chains/substrate.js +287 -0
  26. package/dist/chains/substrate.js.map +1 -0
  27. package/dist/client.d.ts +216 -0
  28. package/dist/client.js +774 -0
  29. package/dist/client.js.map +1 -0
  30. package/dist/index.d.ts +6 -0
  31. package/dist/index.js +7 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/queries.d.ts +3 -0
  34. package/dist/queries.js +78 -0
  35. package/dist/queries.js.map +1 -0
  36. package/dist/tests/hyperbridgeRequests.test.d.ts +1 -0
  37. package/dist/tests/hyperbridgeRequests.test.js +415 -0
  38. package/dist/tests/hyperbridgeRequests.test.js.map +1 -0
  39. package/dist/tests/postRequest.test.d.ts +1 -0
  40. package/dist/tests/postRequest.test.js +293 -0
  41. package/dist/tests/postRequest.test.js.map +1 -0
  42. package/dist/tests/setup.d.ts +1 -0
  43. package/dist/tests/setup.js +6 -0
  44. package/dist/tests/setup.js.map +1 -0
  45. package/dist/tests/tokenGateway.test.d.ts +1 -0
  46. package/dist/tests/tokenGateway.test.js +85 -0
  47. package/dist/tests/tokenGateway.test.js.map +1 -0
  48. package/dist/tests/xcmGateway.test.d.ts +1 -0
  49. package/dist/tests/xcmGateway.test.js +71 -0
  50. package/dist/tests/xcmGateway.test.js.map +1 -0
  51. package/dist/types/index.d.ts +238 -0
  52. package/dist/types/index.js +30 -0
  53. package/dist/types/index.js.map +1 -0
  54. package/dist/utils/mmr.d.ts +13 -0
  55. package/dist/utils/mmr.js +153 -0
  56. package/dist/utils/mmr.js.map +1 -0
  57. package/dist/utils/substrate.d.ts +1913 -0
  58. package/dist/utils/substrate.js +361 -0
  59. package/dist/utils/substrate.js.map +1 -0
  60. package/dist/utils/tokenGateway.d.ts +68 -0
  61. package/dist/utils/tokenGateway.js +151 -0
  62. package/dist/utils/tokenGateway.js.map +1 -0
  63. package/dist/utils/xcmGateway.d.ts +81 -0
  64. package/dist/utils/xcmGateway.js +218 -0
  65. package/dist/utils/xcmGateway.js.map +1 -0
  66. package/dist/utils.d.ts +57 -0
  67. package/dist/utils.js +96 -0
  68. package/dist/utils.js.map +1 -0
  69. package/package.json +74 -0
package/dist/client.js ADDED
@@ -0,0 +1,774 @@
1
+ import { GraphQLClient } from "graphql-request";
2
+ import maxBy from "lodash/maxBy";
3
+ import { pad } from "viem";
4
+ // @ts-ignore
5
+ import mergeRace from "@async-generator/merge-race";
6
+ import { RequestStatus, TimeoutStatus, } from "./types/index.js";
7
+ import { REQUEST_STATUS, STATE_MACHINE_UPDATES_BY_HEIGHT, STATE_MACHINE_UPDATES_BY_TIMESTAMP } from "./queries.js";
8
+ import { COMBINED_STATUS_WEIGHTS, REQUEST_STATUS_WEIGHTS, TIMEOUT_STATUS_WEIGHTS, postRequestCommitment, sleep, } from "./utils.js";
9
+ import { getChain } from "./chain.js";
10
+ /**
11
+ * IndexerClient provides methods for interacting with the Hyperbridge indexer.
12
+ *
13
+ * This client facilitates querying and tracking cross-chain requests and their status
14
+ * through the Hyperbridge protocol. It supports:
15
+ *
16
+ * - Querying state machine updates by block height or timestamp
17
+ * - Retrieving request status information by transaction hash
18
+ * - Monitoring request status changes through streaming interfaces
19
+ * - Handling request timeout flows and related proof generation
20
+ * - Tracking request finalization across source and destination chains
21
+ *
22
+ * The client implements automatic retries with exponential backoff for network
23
+ * resilience and provides both simple query methods and advanced streaming
24
+ * interfaces for real-time status tracking.
25
+ *
26
+ * The URLs provided in the configuration must point to archive nodes to allow the client to query for storage proofs
27
+ * of potentially much older blocks. Regular nodes only store the state for recent blocks and will not be able
28
+ * to provide the necessary proofs for cross-chain verification, especially in timeout scenarios.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const client = new IndexerClient({
33
+ * url: "https://indexer.hyperbridge.xyz/graphql",
34
+ * pollInterval: 2000,
35
+ * source: {
36
+ * stateMachineId: "EVM-1",
37
+ * consensusStateId: "ETH0"
38
+ * rpcUrl: "",
39
+ * host: "0x87ea45..",
40
+ * },
41
+ * dest: {
42
+ * stateMachineId: "EVM-42161",
43
+ * consensusStateId: "ETH0"
44
+ * rpcUrl: "",
45
+ * host: "0x87ea42345..",
46
+ * },
47
+ * hyperbridge: {
48
+ * stateMachineId: "POLKADOT-3367",
49
+ * consensusStateId: "DOT0"
50
+ * wsUrl: "ws://localhost:9944"
51
+ * }
52
+ * });
53
+ *
54
+ * // Query a request status
55
+ * const status = await client.queryRequestWithStatus("0x1234...");
56
+ *
57
+ * // Stream status updates
58
+ * for await (const update of client.postRequestStatusStream("0x1234...")) {
59
+ * console.log(`Request status: ${update.status}`);
60
+ * }
61
+ * ```
62
+ */
63
+ export class IndexerClient {
64
+ /**
65
+ * Creates a new IndexerClient instance
66
+ */
67
+ constructor(config) {
68
+ /**
69
+ * Default configuration for retry behavior when network requests fail
70
+ * - maxRetries: Maximum number of retry attempts before failing
71
+ * - backoffMs: Initial backoff time in milliseconds (doubles with each retry)
72
+ */
73
+ this.defaultRetryConfig = {
74
+ maxRetries: 3,
75
+ backoffMs: 1000,
76
+ };
77
+ this.client = new GraphQLClient(config?.url || "http://localhost:3000/graphql");
78
+ this.config = config;
79
+ }
80
+ /**
81
+ * Query for a single state machine update event greater than or equal to the given height.
82
+ * @params statemachineId - ID of the state machine
83
+ * @params height - Starting block height
84
+ * @params chain - The identifier for the chain where the state machine update should be queried (corresponds to a stateMachineId)
85
+ * @returns Closest state machine update
86
+ */
87
+ async queryStateMachineUpdateByHeight({ statemachineId, height, chain, }) {
88
+ const response = await this.withRetry(() => this.client.request(STATE_MACHINE_UPDATES_BY_HEIGHT, {
89
+ statemachineId,
90
+ height,
91
+ chain,
92
+ }));
93
+ return response.stateMachineUpdateEvents.nodes[0];
94
+ }
95
+ /**
96
+ * Query for a single state machine update event greater than or equal to the given timestamp.
97
+ * @params statemachineId - ID of the state machine
98
+ * @params timestamp - Starting block timestamp
99
+ * @params chain - The identifier for the chain where the state machine update should be queried (corresponds to a stateMachineId)
100
+ * @returns Closest state machine update
101
+ */
102
+ async queryStateMachineUpdateByTimestamp({ statemachineId, commitmentTimestamp, chain, }) {
103
+ const response = await this.withRetry(() => this.client.request(STATE_MACHINE_UPDATES_BY_TIMESTAMP, {
104
+ statemachineId,
105
+ commitmentTimestamp: commitmentTimestamp.toString(),
106
+ chain,
107
+ }));
108
+ return response.stateMachineUpdateEvents.nodes[0];
109
+ }
110
+ /**
111
+ * Queries a request by any of its associated hashes and returns it alongside its statuses
112
+ * Statuses will be one of SOURCE, HYPERBRIDGE_DELIVERED and DESTINATION
113
+ * @param hash - Can be commitment, hyperbridge tx hash, source tx hash, destination tx hash, or timeout tx hash
114
+ * @returns Latest status and block metadata of the request
115
+ */
116
+ async queryRequest(hash) {
117
+ const self = this;
118
+ const response = await self.withRetry(() => self.client.request(REQUEST_STATUS, {
119
+ hash,
120
+ }));
121
+ if (!response.requests.nodes[0])
122
+ return;
123
+ const statuses = response.requests.nodes[0].statusMetadata.nodes.map((item) => ({
124
+ status: item.status,
125
+ metadata: {
126
+ blockHash: item.blockHash,
127
+ blockNumber: parseInt(item.blockNumber),
128
+ transactionHash: item.transactionHash,
129
+ },
130
+ }));
131
+ // sort by ascending order
132
+ const sorted = statuses.sort((a, b) => REQUEST_STATUS_WEIGHTS[a.status] - REQUEST_STATUS_WEIGHTS[b.status]);
133
+ const request = {
134
+ ...response.requests.nodes[0],
135
+ statuses: sorted,
136
+ };
137
+ // @ts-ignore
138
+ delete request.statusMetadata;
139
+ return request;
140
+ }
141
+ /**
142
+ * Enhances a request with finality events by querying state machine updates.
143
+ *
144
+ * This method augments a request object with additional inferred status events
145
+ * that represent chain finality confirmations. It adds:
146
+ * - SOURCE_FINALIZED: When the source chain has finalized the request
147
+ * - HYPERBRIDGE_FINALIZED: When Hyperbridge has finalized the delivery confirmation
148
+ *
149
+ * The method also generates appropriate calldata for submitting cross-chain proofs
150
+ * when applicable.
151
+ *
152
+ * @param request - The request to enhance with finality events
153
+ * @returns The request with finality events added
154
+ * @private
155
+ */
156
+ async addRequestFinalityEvents(request) {
157
+ const self = this;
158
+ let hyperbridgeDelivered;
159
+ if (request.source === self.config.hyperbridge.stateMachineId) {
160
+ // the first status contains the blocknumber of the initial request
161
+ hyperbridgeDelivered = request.statuses[0];
162
+ }
163
+ else {
164
+ // we assume there's always a SOURCE event which contains the blocknumber of the initial request
165
+ const sourceFinality = await self.queryStateMachineUpdateByHeight({
166
+ statemachineId: request.source,
167
+ height: request.statuses[0].metadata.blockNumber,
168
+ chain: self.config.hyperbridge.stateMachineId,
169
+ });
170
+ // no finality event found, return request as is
171
+ if (!sourceFinality)
172
+ return request;
173
+ // Insert finality event into request.statuses at index 1
174
+ request.statuses.push({
175
+ status: RequestStatus.SOURCE_FINALIZED,
176
+ metadata: {
177
+ blockHash: sourceFinality.blockHash,
178
+ blockNumber: sourceFinality.height,
179
+ transactionHash: sourceFinality.transactionHash,
180
+ },
181
+ });
182
+ // check if there's a hyperbridge delivered event
183
+ hyperbridgeDelivered = request.statuses.find((item) => item.status === RequestStatus.HYPERBRIDGE_DELIVERED);
184
+ if (!hyperbridgeDelivered)
185
+ return request;
186
+ }
187
+ // no need to query finality event if destination is hyperbridge
188
+ if (request.dest === self.config.hyperbridge.stateMachineId)
189
+ return request;
190
+ const hyperbridgeFinality = await self.queryStateMachineUpdateByHeight({
191
+ statemachineId: self.config.hyperbridge.stateMachineId,
192
+ height: hyperbridgeDelivered.metadata.blockNumber,
193
+ chain: request.dest,
194
+ });
195
+ if (!hyperbridgeFinality)
196
+ return request;
197
+ // check if request receipt exists on destination chain
198
+ const destChain = await getChain(self.config.dest);
199
+ const hyperbridge = await getChain({
200
+ ...self.config.hyperbridge,
201
+ hasher: "Keccak",
202
+ });
203
+ const proof = await hyperbridge.queryRequestsProof([postRequestCommitment(request)], request.dest, BigInt(hyperbridgeFinality.height));
204
+ const calldata = destChain.encode({
205
+ kind: "PostRequest",
206
+ proof: {
207
+ stateMachine: self.config.hyperbridge.stateMachineId,
208
+ consensusStateId: self.config.hyperbridge.consensusStateId,
209
+ proof,
210
+ height: BigInt(hyperbridgeFinality.height),
211
+ },
212
+ requests: [request],
213
+ signer: pad("0x"),
214
+ });
215
+ request.statuses.push({
216
+ status: RequestStatus.HYPERBRIDGE_FINALIZED,
217
+ metadata: {
218
+ blockHash: hyperbridgeFinality.blockHash,
219
+ blockNumber: hyperbridgeFinality.height,
220
+ transactionHash: hyperbridgeFinality.transactionHash,
221
+ calldata,
222
+ },
223
+ });
224
+ return request;
225
+ }
226
+ /**
227
+ * Adds timeout finality events to a request by querying for relevant timeout proofs and
228
+ * chain state necessary for timeout processing.
229
+ *
230
+ * This method enhances a request object with additional status events related to the
231
+ * timeout flow, including:
232
+ * - PENDING_TIMEOUT: When a request has passed its timeout timestamp
233
+ * - DESTINATION_FINALIZED: When the destination chain has finalized the timeout timestamp
234
+ * - HYPERBRIDGE_FINALIZED_TIMEOUT: When hyperbridge has finalized the timeout state
235
+ *
236
+ * The method also generates appropriate calldata for submitting timeout proofs.
237
+ *
238
+ * @param request - Request to fill timeout events for
239
+ * @returns Request with timeout events filled in, including any proof calldata for timeout submissions
240
+ * @private
241
+ */
242
+ async addTimeoutFinalityEvents(request) {
243
+ const self = this;
244
+ // check if request receipt exists on destination chain
245
+ const destChain = await getChain(self.config.dest);
246
+ const hyperbridge = await getChain({
247
+ ...self.config.hyperbridge,
248
+ hasher: "Keccak",
249
+ });
250
+ const commitment = postRequestCommitment(request);
251
+ const reciept = await destChain.queryRequestReceipt(commitment);
252
+ const destTimestamp = await destChain.timestamp();
253
+ // request not timed out
254
+ if (reciept || request.timeoutTimestamp > destTimestamp)
255
+ return request;
256
+ request.statuses.push({
257
+ status: TimeoutStatus.PENDING_TIMEOUT,
258
+ metadata: { blockHash: "0x", blockNumber: 0, transactionHash: "0x" },
259
+ });
260
+ const delivered = request.statuses.find((item) => item.status === RequestStatus.HYPERBRIDGE_DELIVERED);
261
+ let hyperbridgeFinalized;
262
+ if (!delivered) {
263
+ // either the request was never delivered to hyperbridge
264
+ // or hyperbridge was the destination of the request
265
+ hyperbridgeFinalized = await self.queryStateMachineUpdateByTimestamp({
266
+ statemachineId: self.config.hyperbridge.stateMachineId,
267
+ commitmentTimestamp: request.timeoutTimestamp,
268
+ chain: request.source,
269
+ });
270
+ }
271
+ else {
272
+ let destFinalized = await self.queryStateMachineUpdateByTimestamp({
273
+ statemachineId: request.dest,
274
+ commitmentTimestamp: request.timeoutTimestamp,
275
+ chain: self.config.hyperbridge.stateMachineId,
276
+ });
277
+ if (!destFinalized)
278
+ return request;
279
+ request.statuses.push({
280
+ status: TimeoutStatus.DESTINATION_FINALIZED_TIMEOUT,
281
+ metadata: {
282
+ blockHash: destFinalized.blockHash,
283
+ blockNumber: destFinalized.blockNumber,
284
+ transactionHash: destFinalized.transactionHash,
285
+ },
286
+ });
287
+ // if the source is the hyperbridge state machine, no further action is needed
288
+ // use the timeout stream to timeout on hyperbridge
289
+ if (request.source === self.config.hyperbridge.stateMachineId)
290
+ return request;
291
+ const hyperbridgeTimedOut = request.statuses.find((item) => item.status === TimeoutStatus.HYPERBRIDGE_TIMED_OUT);
292
+ if (!hyperbridgeTimedOut)
293
+ return request;
294
+ hyperbridgeFinalized = await self.queryStateMachineUpdateByHeight({
295
+ statemachineId: self.config.hyperbridge.stateMachineId,
296
+ height: hyperbridgeTimedOut.metadata.blockNumber,
297
+ chain: request.source,
298
+ });
299
+ }
300
+ if (!hyperbridgeFinalized)
301
+ return request;
302
+ const proof = await hyperbridge.queryStateProof(BigInt(hyperbridgeFinalized.height), [
303
+ hyperbridge.requestReceiptKey(commitment),
304
+ ]);
305
+ const sourceChain = await getChain(self.config.source);
306
+ let calldata = sourceChain.encode({
307
+ kind: "TimeoutPostRequest",
308
+ proof: {
309
+ proof,
310
+ height: BigInt(hyperbridgeFinalized.height),
311
+ stateMachine: self.config.hyperbridge.stateMachineId,
312
+ consensusStateId: self.config.hyperbridge.consensusStateId,
313
+ },
314
+ requests: [
315
+ {
316
+ source: request.source,
317
+ dest: request.dest,
318
+ from: request.from,
319
+ to: request.to,
320
+ nonce: request.nonce,
321
+ body: request.body,
322
+ timeoutTimestamp: request.timeoutTimestamp,
323
+ },
324
+ ],
325
+ });
326
+ request.statuses.push({
327
+ status: TimeoutStatus.HYPERBRIDGE_FINALIZED_TIMEOUT,
328
+ metadata: {
329
+ blockHash: hyperbridgeFinalized.blockHash,
330
+ blockNumber: hyperbridgeFinalized.blockNumber,
331
+ transactionHash: hyperbridgeFinalized.transactionHash,
332
+ calldata,
333
+ },
334
+ });
335
+ return request;
336
+ }
337
+ /**
338
+ * Queries a request by any of its associated hashes and returns it alongside its statuses,
339
+ * including any finalization events.
340
+ * @param hash - Can be commitment, hyperbridge tx hash, source tx hash, destination tx hash, or timeout tx hash
341
+ * @returns Full request data with all inferred status events, including SOURCE_FINALIZED and HYPERBRIDGE_FINALIZED
342
+ * @remarks Unlike queryRequest(), this method adds derived finalization status events by querying state machine updates
343
+ */
344
+ async queryRequestWithStatus(hash) {
345
+ let request = await this.queryRequest(hash);
346
+ if (!request)
347
+ return;
348
+ request = await this.addRequestFinalityEvents(request);
349
+ request = await this.addTimeoutFinalityEvents(request);
350
+ // ensure all statuses are sorted by weight
351
+ request.statuses = request.statuses.sort((a, b) => COMBINED_STATUS_WEIGHTS[a.status] - COMBINED_STATUS_WEIGHTS[b.status]);
352
+ return request;
353
+ }
354
+ /**
355
+ * Create a Stream of status updates for a post request.
356
+ * Stream ends when either the request reaches the destination or times out.
357
+ * If the stream yields TimeoutStatus.PENDING_TIMEOUT, use postRequestTimeoutStream() to begin timeout processing.
358
+ * @param hash - Can be commitment, hyperbridge tx hash, source tx hash, destination tx hash, or timeout tx hash
359
+ * @returns AsyncGenerator that emits status updates until a terminal state is reached
360
+ * @example
361
+ *
362
+ * let client = new IndexerClient(config)
363
+ * let stream = client.postRequestStatusStream(hash)
364
+ *
365
+ * // you can use a for-await-of loop
366
+ * for await (const status of stream) {
367
+ * console.log(status)
368
+ * }
369
+ *
370
+ * // you can also use a while loop
371
+ * while (true) {
372
+ * const status = await stream.next()
373
+ * if (status.done) {
374
+ * break
375
+ * }
376
+ * console.log(status.value)
377
+ * }
378
+ *
379
+ */
380
+ async *postRequestStatusStream(hash) {
381
+ const self = this;
382
+ // wait for request to be created
383
+ let request;
384
+ while (!request) {
385
+ await sleep(self.config.pollInterval);
386
+ request = await self.queryRequest(hash);
387
+ continue;
388
+ }
389
+ const chain = await getChain(self.config.dest);
390
+ const timeoutStream = self.timeoutStream(request.timeoutTimestamp, chain);
391
+ const statusStream = self.postRequestStatusStreamInternal(hash);
392
+ const combined = mergeRace(timeoutStream, statusStream);
393
+ let item = await combined.next();
394
+ while (!item.done) {
395
+ yield item.value;
396
+ item = await combined.next();
397
+ }
398
+ return;
399
+ }
400
+ /*
401
+ * Returns a generator that will yield true if the request is timed out
402
+ * If the request does not have a timeout, it will yield never yield
403
+ * @param request - Request to timeout
404
+ */
405
+ async *timeoutStream(timeoutTimestamp, chain) {
406
+ if (timeoutTimestamp > 0) {
407
+ let timestamp = await chain.timestamp();
408
+ while (timestamp < timeoutTimestamp) {
409
+ const diff = BigInt(timeoutTimestamp) - BigInt(timestamp);
410
+ await sleep(Number(diff));
411
+ timestamp = await chain.timestamp();
412
+ }
413
+ yield {
414
+ status: TimeoutStatus.PENDING_TIMEOUT,
415
+ metadata: { blockHash: "0x", blockNumber: 0, transactionHash: "0x" },
416
+ };
417
+ return;
418
+ }
419
+ }
420
+ /**
421
+ * Create a Stream of status updates
422
+ * @param hash - Can be commitment, hyperbridge tx hash, source tx hash, destination tx hash, or timeout tx hash
423
+ * @returns AsyncGenerator that emits status updates until a terminal state is reached
424
+ */
425
+ async *postRequestStatusStreamInternal(hash) {
426
+ const self = this;
427
+ let request;
428
+ while (!request) {
429
+ await sleep(self.config.pollInterval);
430
+ request = await self.queryRequest(hash);
431
+ }
432
+ let status = request.source === self.config.hyperbridge.stateMachineId
433
+ ? RequestStatus.HYPERBRIDGE_DELIVERED
434
+ : RequestStatus.SOURCE;
435
+ const latestMetadata = request.statuses[request.statuses.length - 1];
436
+ // start with the latest status
437
+ status = maxBy([status, latestMetadata.status], (item) => REQUEST_STATUS_WEIGHTS[item]);
438
+ while (true) {
439
+ switch (status) {
440
+ // request has been dispatched from source chain
441
+ case RequestStatus.SOURCE: {
442
+ let sourceUpdate;
443
+ while (!sourceUpdate) {
444
+ await sleep(self.config.pollInterval);
445
+ sourceUpdate = await self.queryStateMachineUpdateByHeight({
446
+ statemachineId: request.source,
447
+ height: request.statuses[0].metadata.blockNumber,
448
+ chain: self.config.hyperbridge.stateMachineId,
449
+ });
450
+ }
451
+ yield {
452
+ status: RequestStatus.SOURCE_FINALIZED,
453
+ metadata: {
454
+ blockHash: sourceUpdate.blockHash,
455
+ blockNumber: sourceUpdate.height,
456
+ transactionHash: sourceUpdate.transactionHash,
457
+ },
458
+ };
459
+ status = RequestStatus.SOURCE_FINALIZED;
460
+ break;
461
+ }
462
+ // finality proofs for request has been verified on Hyperbridge
463
+ case RequestStatus.SOURCE_FINALIZED: {
464
+ // wait for the request to be delivered on Hyperbridge
465
+ while (!request || request.statuses.length < 2) {
466
+ await sleep(self.config.pollInterval);
467
+ request = await self.queryRequest(hash);
468
+ }
469
+ status =
470
+ request.dest === self.config.hyperbridge.stateMachineId
471
+ ? RequestStatus.DESTINATION
472
+ : RequestStatus.HYPERBRIDGE_DELIVERED;
473
+ yield {
474
+ status,
475
+ metadata: {
476
+ blockHash: request.statuses[1].metadata.blockHash,
477
+ blockNumber: request.statuses[1].metadata.blockNumber,
478
+ transactionHash: request.statuses[1].metadata.transactionHash,
479
+ },
480
+ };
481
+ break;
482
+ }
483
+ // the request has been verified and aggregated on Hyperbridge
484
+ case RequestStatus.HYPERBRIDGE_DELIVERED: {
485
+ // Get the latest state machine update for hyperbridge on the destination chain
486
+ let hyperbridgeFinalized;
487
+ let index = request.source === self.config.hyperbridge.stateMachineId ? 0 : 1;
488
+ while (!hyperbridgeFinalized) {
489
+ await sleep(self.config.pollInterval);
490
+ hyperbridgeFinalized = await self.queryStateMachineUpdateByHeight({
491
+ statemachineId: self.config.hyperbridge.stateMachineId,
492
+ height: request.statuses[index].metadata.blockNumber,
493
+ chain: request.dest,
494
+ });
495
+ }
496
+ const destChain = await getChain(self.config.dest);
497
+ const hyperbridge = await getChain({
498
+ ...self.config.hyperbridge,
499
+ hasher: "Keccak",
500
+ });
501
+ const proof = await hyperbridge.queryRequestsProof([postRequestCommitment(request)], request.dest, BigInt(hyperbridgeFinalized.height));
502
+ const calldata = destChain.encode({
503
+ kind: "PostRequest",
504
+ proof: {
505
+ stateMachine: self.config.hyperbridge.stateMachineId,
506
+ consensusStateId: self.config.hyperbridge.consensusStateId,
507
+ proof,
508
+ height: BigInt(hyperbridgeFinalized.height),
509
+ },
510
+ requests: [request],
511
+ signer: pad("0x"),
512
+ });
513
+ yield {
514
+ status: RequestStatus.HYPERBRIDGE_FINALIZED,
515
+ metadata: {
516
+ blockHash: hyperbridgeFinalized.blockHash,
517
+ blockNumber: hyperbridgeFinalized.height,
518
+ transactionHash: hyperbridgeFinalized.transactionHash,
519
+ calldata,
520
+ },
521
+ };
522
+ status = RequestStatus.HYPERBRIDGE_FINALIZED;
523
+ break;
524
+ }
525
+ // request has been finalized by hyperbridge
526
+ case RequestStatus.HYPERBRIDGE_FINALIZED: {
527
+ // wait for the request to be delivered to the destination
528
+ let delivered = request.statuses.find((s) => s.status === RequestStatus.DESTINATION);
529
+ while (!request || !delivered) {
530
+ await sleep(self.config.pollInterval);
531
+ request = await self.queryRequest(hash);
532
+ delivered = request?.statuses.find((s) => s.status === RequestStatus.DESTINATION);
533
+ }
534
+ let index = request.source === self.config.hyperbridge.stateMachineId ? 1 : 2;
535
+ yield {
536
+ status: RequestStatus.DESTINATION,
537
+ metadata: {
538
+ blockHash: request.statuses[index].metadata.blockHash,
539
+ blockNumber: request.statuses[index].metadata.blockNumber,
540
+ transactionHash: request.statuses[index].metadata.transactionHash,
541
+ },
542
+ };
543
+ status = RequestStatus.DESTINATION;
544
+ break;
545
+ }
546
+ case RequestStatus.DESTINATION:
547
+ return;
548
+ }
549
+ }
550
+ }
551
+ /**
552
+ * Create a Stream of status updates for a timed out post request.
553
+ * @param hash - Can be commitment, hyperbridge tx hash, source tx hash, destination tx hash, or timeout tx hash
554
+ * @returns AsyncGenerator that emits status updates until a terminal state is reached
555
+ * @example
556
+ *
557
+ * let client = new IndexerClient(config)
558
+ * let stream = client.postRequestTimeoutStream(hash)
559
+ *
560
+ * // you can use a for-await-of loop
561
+ * for await (const status of stream) {
562
+ * console.log(status)
563
+ * }
564
+ *
565
+ * // you can also use a while loop
566
+ * while (true) {
567
+ * const status = await stream.next()
568
+ * if (status.done) {
569
+ * break
570
+ * }
571
+ * console.log(status.value)
572
+ * }
573
+ */
574
+ async *postRequestTimeoutStream(hash) {
575
+ const self = this;
576
+ let request = await self.queryRequest(hash);
577
+ if (!request)
578
+ throw new Error(`Request not found`);
579
+ const destChain = await getChain(self.config.dest);
580
+ const destTimestamp = await destChain.timestamp();
581
+ if (request.timeoutTimestamp > destTimestamp)
582
+ throw new Error(`Request not timed out`);
583
+ // if the destination is hyperbridge, then just wait for hyperbridge finality
584
+ let status = request.dest === self.config.hyperbridge.stateMachineId
585
+ ? TimeoutStatus.HYPERBRIDGE_TIMED_OUT
586
+ : TimeoutStatus.PENDING_TIMEOUT;
587
+ const commitment = postRequestCommitment(request);
588
+ const hyperbridge = (await getChain({
589
+ ...self.config.hyperbridge,
590
+ hasher: "Keccak",
591
+ }));
592
+ const latest = request.statuses[request.statuses.length - 1];
593
+ // we're always interested in the latest status
594
+ status = maxBy([status, latest.status], (item) => TIMEOUT_STATUS_WEIGHTS[item]);
595
+ while (true) {
596
+ switch (status) {
597
+ case TimeoutStatus.PENDING_TIMEOUT: {
598
+ const receipt = await hyperbridge.queryRequestReceipt(commitment);
599
+ if (!receipt && request.source !== self.config.hyperbridge.stateMachineId) {
600
+ status = TimeoutStatus.HYPERBRIDGE_TIMED_OUT;
601
+ break;
602
+ }
603
+ let update;
604
+ while (!update) {
605
+ await sleep(self.config.pollInterval);
606
+ update = await self.queryStateMachineUpdateByTimestamp({
607
+ statemachineId: request.dest,
608
+ commitmentTimestamp: request.timeoutTimestamp,
609
+ chain: self.config.hyperbridge.stateMachineId,
610
+ });
611
+ }
612
+ yield {
613
+ status: TimeoutStatus.DESTINATION_FINALIZED_TIMEOUT,
614
+ metadata: {
615
+ blockHash: update.blockHash,
616
+ blockNumber: update.height,
617
+ transactionHash: update.transactionHash,
618
+ },
619
+ };
620
+ status = TimeoutStatus.DESTINATION_FINALIZED_TIMEOUT;
621
+ break;
622
+ }
623
+ case TimeoutStatus.DESTINATION_FINALIZED_TIMEOUT: {
624
+ if (request.source !== self.config.hyperbridge.stateMachineId) {
625
+ const receipt = await hyperbridge.queryRequestReceipt(commitment);
626
+ if (!receipt) {
627
+ status = TimeoutStatus.HYPERBRIDGE_TIMED_OUT;
628
+ break;
629
+ }
630
+ }
631
+ const update = (await self.queryStateMachineUpdateByTimestamp({
632
+ statemachineId: request.dest,
633
+ commitmentTimestamp: request.timeoutTimestamp,
634
+ chain: self.config.hyperbridge.stateMachineId,
635
+ }));
636
+ const proof = await destChain.queryStateProof(BigInt(update.height), [
637
+ destChain.requestReceiptKey(commitment),
638
+ ]);
639
+ let { blockHash, transactionHash, blockNumber } = await hyperbridge.submitUnsigned({
640
+ kind: "TimeoutPostRequest",
641
+ proof: {
642
+ proof,
643
+ height: BigInt(update.height),
644
+ stateMachine: request.dest,
645
+ consensusStateId: self.config.dest.consensusStateId,
646
+ },
647
+ requests: [
648
+ {
649
+ source: request.source,
650
+ dest: request.dest,
651
+ from: request.from,
652
+ to: request.to,
653
+ nonce: request.nonce,
654
+ body: request.body,
655
+ timeoutTimestamp: request.timeoutTimestamp,
656
+ },
657
+ ],
658
+ });
659
+ status =
660
+ request.source === self.config.hyperbridge.stateMachineId
661
+ ? TimeoutStatus.TIMED_OUT
662
+ : TimeoutStatus.HYPERBRIDGE_TIMED_OUT;
663
+ yield {
664
+ status,
665
+ metadata: {
666
+ blockHash,
667
+ transactionHash,
668
+ blockNumber,
669
+ },
670
+ };
671
+ break;
672
+ }
673
+ case TimeoutStatus.HYPERBRIDGE_TIMED_OUT: {
674
+ const hasDelivered = request.statuses.some((item) => item.status === RequestStatus.HYPERBRIDGE_DELIVERED);
675
+ let update;
676
+ if (!hasDelivered) {
677
+ // if request was never delivered to Hyperbridge
678
+ // then query for any state machine update > requestTimestamp
679
+ while (!update) {
680
+ await sleep(self.config.pollInterval);
681
+ update = await self.queryStateMachineUpdateByTimestamp({
682
+ statemachineId: self.config.hyperbridge.stateMachineId,
683
+ commitmentTimestamp: request.timeoutTimestamp,
684
+ chain: request.source,
685
+ });
686
+ }
687
+ }
688
+ else {
689
+ let req;
690
+ while (!req) {
691
+ await sleep(self.config.pollInterval);
692
+ req = await self.queryRequest(hash);
693
+ }
694
+ const timeout = req.statuses
695
+ .sort((a, b) => COMBINED_STATUS_WEIGHTS[a.status] - COMBINED_STATUS_WEIGHTS[b.status])
696
+ .pop();
697
+ if (!timeout || timeout.status !== TimeoutStatus.HYPERBRIDGE_TIMED_OUT)
698
+ break;
699
+ while (!update) {
700
+ await sleep(self.config.pollInterval);
701
+ update = await self.queryStateMachineUpdateByHeight({
702
+ statemachineId: self.config.hyperbridge.stateMachineId,
703
+ height: timeout.metadata.blockNumber,
704
+ chain: request.source,
705
+ });
706
+ }
707
+ }
708
+ const proof = await hyperbridge.queryStateProof(BigInt(update.height), [
709
+ hyperbridge.requestReceiptKey(commitment),
710
+ ]);
711
+ const sourceChain = await getChain(self.config.source);
712
+ let calldata = sourceChain.encode({
713
+ kind: "TimeoutPostRequest",
714
+ proof: {
715
+ proof,
716
+ height: BigInt(update.height),
717
+ stateMachine: self.config.hyperbridge.stateMachineId,
718
+ consensusStateId: self.config.hyperbridge.consensusStateId,
719
+ },
720
+ requests: [
721
+ {
722
+ source: request.source,
723
+ dest: request.dest,
724
+ from: request.from,
725
+ to: request.to,
726
+ nonce: request.nonce,
727
+ body: request.body,
728
+ timeoutTimestamp: request.timeoutTimestamp,
729
+ },
730
+ ],
731
+ });
732
+ yield {
733
+ status: TimeoutStatus.HYPERBRIDGE_FINALIZED_TIMEOUT,
734
+ metadata: {
735
+ transactionHash: update.transactionHash,
736
+ blockNumber: update.blockNumber,
737
+ blockHash: update.blockHash,
738
+ calldata,
739
+ },
740
+ };
741
+ status = TimeoutStatus.HYPERBRIDGE_FINALIZED_TIMEOUT;
742
+ break;
743
+ }
744
+ case TimeoutStatus.HYPERBRIDGE_FINALIZED_TIMEOUT:
745
+ case TimeoutStatus.TIMED_OUT:
746
+ return;
747
+ }
748
+ }
749
+ }
750
+ /**
751
+ * Executes an async operation with exponential backoff retry
752
+ * @param operation - Async function to execute
753
+ * @param retryConfig - Optional retry configuration
754
+ * @returns Result of the operation
755
+ * @throws Last encountered error after all retries are exhausted
756
+ *
757
+ * @example
758
+ * const result = await this.withRetry(() => this.queryStatus(hash));
759
+ */
760
+ async withRetry(operation, retryConfig = this.defaultRetryConfig) {
761
+ let lastError;
762
+ for (let i = 0; i < retryConfig.maxRetries; i++) {
763
+ try {
764
+ return await operation();
765
+ }
766
+ catch (error) {
767
+ lastError = error;
768
+ await new Promise((resolve) => setTimeout(resolve, retryConfig.backoffMs * Math.pow(2, i)));
769
+ }
770
+ }
771
+ throw lastError;
772
+ }
773
+ }
774
+ //# sourceMappingURL=client.js.map