@aptos-labs/cross-chain-core 5.8.2 → 5.9.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 (48) hide show
  1. package/README.md +26 -0
  2. package/dist/CrossChainCore.d.ts +20 -0
  3. package/dist/CrossChainCore.d.ts.map +1 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +580 -275
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +583 -274
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/providers/wormhole/index.d.ts +2 -0
  11. package/dist/providers/wormhole/index.d.ts.map +1 -1
  12. package/dist/providers/wormhole/signers/AptosLocalSigner.d.ts +1 -1
  13. package/dist/providers/wormhole/signers/AptosLocalSigner.d.ts.map +1 -1
  14. package/dist/providers/wormhole/signers/Signer.d.ts +1 -1
  15. package/dist/providers/wormhole/signers/Signer.d.ts.map +1 -1
  16. package/dist/providers/wormhole/signers/SolanaLocalSigner.d.ts +65 -0
  17. package/dist/providers/wormhole/signers/SolanaLocalSigner.d.ts.map +1 -0
  18. package/dist/providers/wormhole/signers/SolanaSigner.d.ts +12 -20
  19. package/dist/providers/wormhole/signers/SolanaSigner.d.ts.map +1 -1
  20. package/dist/providers/wormhole/signers/solanaUtils.d.ts +68 -0
  21. package/dist/providers/wormhole/signers/solanaUtils.d.ts.map +1 -0
  22. package/dist/providers/wormhole/types.d.ts +43 -0
  23. package/dist/providers/wormhole/types.d.ts.map +1 -1
  24. package/dist/providers/wormhole/utils.d.ts +26 -0
  25. package/dist/providers/wormhole/utils.d.ts.map +1 -0
  26. package/dist/providers/wormhole/wormhole.d.ts +36 -6
  27. package/dist/providers/wormhole/wormhole.d.ts.map +1 -1
  28. package/dist/utils/receiptSerialization.d.ts +38 -0
  29. package/dist/utils/receiptSerialization.d.ts.map +1 -0
  30. package/dist/version.d.ts +1 -1
  31. package/package.json +2 -2
  32. package/src/CrossChainCore.ts +20 -0
  33. package/src/config/mainnet/chains.ts +2 -2
  34. package/src/config/testnet/chains.ts +2 -2
  35. package/src/index.ts +1 -0
  36. package/src/providers/wormhole/index.ts +2 -0
  37. package/src/providers/wormhole/signers/AptosLocalSigner.ts +4 -4
  38. package/src/providers/wormhole/signers/AptosSigner.ts +1 -1
  39. package/src/providers/wormhole/signers/EthereumSigner.ts +3 -3
  40. package/src/providers/wormhole/signers/Signer.ts +4 -4
  41. package/src/providers/wormhole/signers/SolanaLocalSigner.ts +243 -0
  42. package/src/providers/wormhole/signers/SolanaSigner.ts +45 -337
  43. package/src/providers/wormhole/signers/solanaUtils.ts +422 -0
  44. package/src/providers/wormhole/types.ts +68 -0
  45. package/src/providers/wormhole/utils.ts +72 -0
  46. package/src/providers/wormhole/wormhole.ts +182 -120
  47. package/src/utils/receiptSerialization.ts +141 -0
  48. package/src/version.ts +1 -1
@@ -1,7 +1,5 @@
1
1
  import {
2
- chainToPlatform,
3
2
  routes,
4
- TokenId,
5
3
  Wormhole,
6
4
  wormhole,
7
5
  PlatformLoader,
@@ -19,9 +17,11 @@ import {
19
17
  CrossChainCore,
20
18
  } from "../../CrossChainCore";
21
19
  import { logger } from "../../utils/logger";
20
+ import { serializeReceipt } from "../../utils/receiptSerialization";
22
21
  import { AptosLocalSigner } from "./signers/AptosLocalSigner";
23
22
  import { Signer } from "./signers/Signer";
24
23
  import { ChainConfig } from "../../config";
24
+ import { createCCTPRoute } from "./utils";
25
25
  import {
26
26
  WormholeQuoteRequest,
27
27
  WormholeQuoteResponse,
@@ -34,6 +34,11 @@ import {
34
34
  WormholeClaimTransferRequest,
35
35
  WormholeWithdrawRequest,
36
36
  WormholeWithdrawResponse,
37
+ WormholeInitiateWithdrawRequest,
38
+ WormholeInitiateWithdrawResponse,
39
+ WormholeClaimWithdrawRequest,
40
+ WormholeClaimWithdrawResponse,
41
+ WithdrawError,
37
42
  } from "./types";
38
43
  import { SolanaDerivedWallet } from "@aptos-labs/derived-wallet-solana";
39
44
  import { EIP1193DerivedWallet } from "@aptos-labs/derived-wallet-ethereum";
@@ -54,6 +59,7 @@ export class WormholeProvider implements CrossChainProvider<
54
59
  private wormholeRoute: WormholeRouteResponse | undefined;
55
60
  private wormholeRequest: WormholeRequest | undefined;
56
61
  private wormholeQuote: WormholeQuoteResponse | undefined;
62
+ private destinationChain?: Chain;
57
63
 
58
64
  constructor(core: CrossChainCore) {
59
65
  this.crossChainCore = core;
@@ -100,43 +106,16 @@ export class WormholeProvider implements CrossChainProvider<
100
106
  throw new Error("Wormhole context not initialized");
101
107
  }
102
108
 
103
- const { sourceToken, destToken } = this.getTokenInfo(
109
+ const { route: cctpRoute, request } = await createCCTPRoute(
110
+ this._wormholeContext,
104
111
  sourceChain,
105
112
  destinationChain,
113
+ this.crossChainCore.TOKENS,
106
114
  );
107
115
 
108
- const destContext = this._wormholeContext
109
- .getPlatform(chainToPlatform(destinationChain))
110
- .getChain(destinationChain);
111
- const sourceContext = this._wormholeContext
112
- .getPlatform(chainToPlatform(sourceChain))
113
- .getChain(sourceChain);
114
-
115
- logger.log("sourceContext", sourceContext);
116
- logger.log("sourceToken", sourceToken);
117
-
118
- logger.log("destContext", destContext);
119
- logger.log("destToken", destToken);
120
-
121
- const request = await routes.RouteTransferRequest.create(
122
- this._wormholeContext,
123
- {
124
- source: sourceToken,
125
- destination: destToken,
126
- },
127
- sourceContext,
128
- destContext,
129
- );
130
-
131
- const resolver = this._wormholeContext.resolver([
132
- routes.CCTPRoute, // manual CCTP
133
- ]);
134
-
135
- const route = await resolver.findRoutes(request);
136
- const cctpRoute = route[0];
137
-
138
116
  this.wormholeRoute = cctpRoute;
139
117
  this.wormholeRequest = request;
118
+ this.destinationChain = destinationChain;
140
119
 
141
120
  return { route: cctpRoute, request };
142
121
  }
@@ -168,12 +147,12 @@ export class WormholeProvider implements CrossChainProvider<
168
147
  const validated = await route.validate(request, transferParams);
169
148
  if (!validated.valid) {
170
149
  logger.log("invalid", validated.valid);
171
- throw new Error(`Invalid quote: ${validated.error}`).message;
150
+ throw new Error(`Invalid quote: ${validated.error}`);
172
151
  }
173
152
  const quote = await route.quote(request, validated.params);
174
153
  if (!quote.success) {
175
154
  logger.log("quote failed", quote.success);
176
- throw new Error(`Invalid quote: ${quote.error}`).message;
155
+ throw new Error(`Invalid quote: ${quote.error}`);
177
156
  }
178
157
  this.wormholeQuote = quote;
179
158
  logger.log("quote", quote);
@@ -333,18 +312,18 @@ export class WormholeProvider implements CrossChainProvider<
333
312
  return { originChainTxnId, destinationChainTxnId };
334
313
  }
335
314
 
336
- async withdraw(
337
- input: WormholeWithdrawRequest,
338
- ): Promise<WormholeWithdrawResponse> {
339
- const { sourceChain, wallet, destinationAddress, sponsorAccount } = input;
340
- logger.log("sourceChain", sourceChain);
341
- logger.log("wallet", wallet);
342
- logger.log("destinationAddress", destinationAddress);
343
- logger.log("sponsorAccount", sponsorAccount);
315
+ // --- Split withdraw flow: initiateWithdraw + trackWithdraw + claimWithdraw ---
316
+
317
+ /**
318
+ * Phase 1: Initiates a withdraw by burning USDC on Aptos.
319
+ * The user signs the Aptos burn transaction via their wallet.
320
+ * Returns a receipt that can be tracked and later claimed.
321
+ */
322
+ async initiateWithdraw(
323
+ input: WormholeInitiateWithdrawRequest,
324
+ ): Promise<WormholeInitiateWithdrawResponse> {
325
+ const { wallet, destinationAddress, sponsorAccount } = input;
344
326
 
345
- if (!this._wormholeContext) {
346
- await this.setWormholeContext(sourceChain);
347
- }
348
327
  if (!this._wormholeContext) {
349
328
  throw new Error("Wormhole context not initialized");
350
329
  }
@@ -355,95 +334,198 @@ export class WormholeProvider implements CrossChainProvider<
355
334
  const signer = new Signer(
356
335
  this.getChainConfig("Aptos"),
357
336
  (
358
- await input.wallet.features["aptos:account"].account()
337
+ await wallet.features["aptos:account"].account()
359
338
  ).address.toString(),
360
339
  {},
361
- input.wallet,
340
+ wallet,
362
341
  this.crossChainCore,
363
342
  sponsorAccount,
364
343
  );
365
344
 
366
- logger.log("signer", signer);
367
- logger.log("wormholeRequest", this.wormholeRequest);
368
- logger.log("wormholeQuote", this.wormholeQuote);
369
- logger.log(
370
- "Wormhole.chainAddress",
371
- Wormhole.chainAddress(sourceChain, input.destinationAddress.toString()),
345
+ const wormholeDestAddress = Wormhole.chainAddress(
346
+ this.destinationChain!,
347
+ destinationAddress.toString(),
372
348
  );
373
349
 
374
- let receipt = await this.wormholeRoute.initiate(
350
+ const receipt = await this.wormholeRoute.initiate(
375
351
  this.wormholeRequest,
376
352
  signer,
377
353
  this.wormholeQuote,
378
- Wormhole.chainAddress(sourceChain, input.destinationAddress.toString()),
354
+ wormholeDestAddress,
379
355
  );
380
- logger.log("receipt", receipt);
356
+ logger.log("initiateWithdraw receipt", receipt);
381
357
 
382
358
  const originChainTxnId =
383
359
  "originTxs" in receipt
384
360
  ? receipt.originTxs[receipt.originTxs.length - 1].txid
385
361
  : undefined;
386
362
 
363
+ return { originChainTxnId: originChainTxnId || "", receipt };
364
+ }
365
+
366
+ /**
367
+ * Phase 2: Tracks a withdraw receipt until attestation is ready.
368
+ * This polls Wormhole and returns once the receipt reaches the Attested state.
369
+ */
370
+ async trackWithdraw(
371
+ receipt: routes.Receipt,
372
+ ): Promise<routes.Receipt> {
373
+ if (!this.wormholeRoute) {
374
+ throw new Error("Wormhole route not initialized");
375
+ }
376
+
387
377
  let retries = 0;
388
378
  const maxRetries = 5;
389
- const baseDelay = 1000; // Initial delay of 1 second
379
+ const baseDelay = 1000;
390
380
 
391
381
  while (retries < maxRetries) {
392
382
  try {
393
383
  for await (receipt of this.wormholeRoute.track(receipt, 120 * 1000)) {
394
- if (receipt.state >= TransferState.SourceInitiated) {
395
- logger.log("Receipt is on track ", receipt);
396
-
397
- try {
398
- const signer = new Signer(
399
- this.getChainConfig(sourceChain),
400
- destinationAddress.toString(),
401
- {},
402
- wallet,
403
- this.crossChainCore,
404
- );
405
-
406
- if (routes.isManual(this.wormholeRoute)) {
407
- const circleAttestationReceipt =
408
- await this.wormholeRoute.complete(signer, receipt);
409
- logger.log("Claim receipt: ", circleAttestationReceipt);
410
-
411
- const destinationChainTxnId = signer.claimedTransactionHashes();
412
- return {
413
- originChainTxnId: originChainTxnId || "",
414
- destinationChainTxnId,
415
- };
416
- } else {
417
- // Should be unreachable
418
- return {
419
- originChainTxnId: originChainTxnId || "",
420
- destinationChainTxnId: "",
421
- };
422
- }
423
- } catch (e) {
424
- console.error("Failed to claim", e);
425
- return {
426
- originChainTxnId: originChainTxnId || "",
427
- destinationChainTxnId: "",
428
- };
429
- }
384
+ if (receipt.state >= TransferState.Attested) {
385
+ logger.log("trackWithdraw: receipt attested", receipt);
386
+ return receipt;
430
387
  }
431
388
  }
432
389
  } catch (e) {
433
390
  console.error(
434
- `Error tracking transfer (attempt ${retries + 1} / ${maxRetries}):`,
391
+ `Error tracking withdraw (attempt ${retries + 1} / ${maxRetries}):`,
435
392
  e,
436
393
  );
437
- const delay = baseDelay * Math.pow(2, retries); // Exponential backoff
394
+ const delay = baseDelay * Math.pow(2, retries);
438
395
  await sleep(delay);
439
396
  retries++;
440
397
  }
441
398
  }
399
+ throw new Error("Failed to track withdraw to attested state");
400
+ }
442
401
 
443
- return {
444
- originChainTxnId: originChainTxnId || "",
445
- destinationChainTxnId: "",
446
- };
402
+ /**
403
+ * Phase 3: Claims the withdraw on the destination chain.
404
+ *
405
+ * If the destination is Solana and `solanaConfig.serverClaimUrl` is configured
406
+ * in the dapp config, the SDK automatically POSTs the attested receipt to that
407
+ * URL — no wallet popup required. The dapp's server endpoint handles signing
408
+ * and submitting the claim transaction.
409
+ *
410
+ * Otherwise falls back to the wallet-based Signer (triggers wallet popup).
411
+ */
412
+ async claimWithdraw(
413
+ input: WormholeClaimWithdrawRequest,
414
+ ): Promise<WormholeClaimWithdrawResponse> {
415
+ const { sourceChain, destinationAddress, receipt } = input;
416
+
417
+ // Server-side claim path: Solana destination with configured serverClaimUrl
418
+ const serverClaimUrl =
419
+ this.crossChainCore._dappConfig?.solanaConfig?.serverClaimUrl;
420
+
421
+ if (sourceChain === "Solana" && serverClaimUrl) {
422
+ logger.log("claimWithdraw: using server-side claim via", serverClaimUrl);
423
+
424
+ const response = await fetch(serverClaimUrl, {
425
+ method: "POST",
426
+ headers: { "Content-Type": "application/json" },
427
+ body: JSON.stringify({
428
+ receipt: serializeReceipt(receipt),
429
+ destinationAddress,
430
+ sourceChain,
431
+ }),
432
+ });
433
+
434
+ if (!response.ok) {
435
+ const errorData = await response.json().catch(() => ({}));
436
+ throw new Error(
437
+ errorData.error ||
438
+ `Server-side claim failed with status ${response.status}`,
439
+ );
440
+ }
441
+
442
+ const result = await response.json();
443
+ return { destinationChainTxnId: result.destinationChainTxnId };
444
+ }
445
+
446
+ // Wallet-based claim path
447
+ if (!this.wormholeRoute) {
448
+ throw new Error("Wormhole route not initialized");
449
+ }
450
+
451
+ if (!input.wallet) {
452
+ throw new Error(
453
+ "Wallet is required for claim when serverClaimUrl is not configured",
454
+ );
455
+ }
456
+
457
+ const claimSigner = new Signer(
458
+ this.getChainConfig(sourceChain),
459
+ destinationAddress,
460
+ {},
461
+ input.wallet,
462
+ this.crossChainCore,
463
+ );
464
+
465
+ if (routes.isManual(this.wormholeRoute)) {
466
+ const circleAttestationReceipt = await this.wormholeRoute.complete(
467
+ claimSigner,
468
+ receipt,
469
+ );
470
+ logger.log("claimWithdraw receipt:", circleAttestationReceipt);
471
+ const destinationChainTxnId = claimSigner.claimedTransactionHashes();
472
+ return { destinationChainTxnId };
473
+ } else {
474
+ throw new Error("Automatic route not supported for manual claim");
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Withdraws USDC from Aptos to a destination chain.
480
+ * Orchestrates all three phases internally:
481
+ * 1. Initiate — user signs the Aptos burn transaction
482
+ * 2. Track — wait for Wormhole attestation
483
+ * 3. Claim — if serverClaimUrl is configured for Solana, delegates to
484
+ * the server; otherwise uses the wallet-based signer.
485
+ *
486
+ * The optional `onPhaseChange` callback lets the dapp update its UI
487
+ * as the flow progresses.
488
+ */
489
+ async withdraw(
490
+ input: WormholeWithdrawRequest,
491
+ ): Promise<WormholeWithdrawResponse> {
492
+ const { sourceChain, wallet, destinationAddress, sponsorAccount, onPhaseChange } = input;
493
+
494
+ // Phase 1: Initiate — user signs Aptos burn
495
+ onPhaseChange?.("initiating");
496
+ const { originChainTxnId, receipt } = await this.initiateWithdraw({
497
+ wallet,
498
+ destinationAddress,
499
+ sponsorAccount,
500
+ });
501
+
502
+ // Phases 2 & 3 are wrapped so that, if they fail, the caller still
503
+ // receives the originChainTxnId (the irreversible Aptos burn).
504
+ let currentPhase: "tracking" | "claiming" = "tracking";
505
+ try {
506
+ // Phase 2: Track — wait for attestation
507
+ onPhaseChange?.("tracking");
508
+ const attestedReceipt = await this.trackWithdraw(receipt);
509
+
510
+ // Phase 3: Claim — server-side or wallet-based
511
+ currentPhase = "claiming";
512
+ onPhaseChange?.("claiming");
513
+ const { destinationChainTxnId } = await this.claimWithdraw({
514
+ sourceChain,
515
+ destinationAddress: destinationAddress.toString(),
516
+ receipt: attestedReceipt,
517
+ wallet,
518
+ });
519
+
520
+ return { originChainTxnId, destinationChainTxnId };
521
+ } catch (error: any) {
522
+ throw new WithdrawError(
523
+ error?.message ?? "Withdraw failed after Aptos burn",
524
+ originChainTxnId,
525
+ currentPhase,
526
+ error,
527
+ );
528
+ }
447
529
  }
448
530
 
449
531
  getChainConfig(chain: Chain): ChainConfig {
@@ -456,24 +538,4 @@ export class WormholeProvider implements CrossChainProvider<
456
538
  }
457
539
  return chainConfig;
458
540
  }
459
-
460
- getTokenInfo(
461
- sourceChain: Chain,
462
- destinationChain: Chain,
463
- ): {
464
- sourceToken: TokenId;
465
- destToken: TokenId;
466
- } {
467
- const sourceToken: TokenId = Wormhole.tokenId(
468
- this.crossChainCore.TOKENS[sourceChain].tokenId.chain as Chain,
469
- this.crossChainCore.TOKENS[sourceChain].tokenId.address,
470
- );
471
-
472
- const destToken: TokenId = Wormhole.tokenId(
473
- this.crossChainCore.TOKENS[destinationChain].tokenId.chain as Chain,
474
- this.crossChainCore.TOKENS[destinationChain].tokenId.address,
475
- );
476
-
477
- return { sourceToken, destToken };
478
- }
479
541
  }
@@ -0,0 +1,141 @@
1
+ import {
2
+ routes,
3
+ AttestationReceipt,
4
+ UniversalAddress,
5
+ } from "@wormhole-foundation/sdk";
6
+
7
+ // Cross-platform base64 helpers (no Node.js Buffer dependency)
8
+ function uint8ArrayToBase64(bytes: Uint8Array): string {
9
+ let binary = "";
10
+ for (let i = 0; i < bytes.length; i++) {
11
+ binary += String.fromCharCode(bytes[i]);
12
+ }
13
+ return btoa(binary);
14
+ }
15
+
16
+ function base64ToUint8Array(base64: string): Uint8Array {
17
+ const binary = atob(base64);
18
+ const bytes = new Uint8Array(binary.length);
19
+ for (let i = 0; i < binary.length; i++) {
20
+ bytes[i] = binary.charCodeAt(i);
21
+ }
22
+ return bytes;
23
+ }
24
+
25
+ /**
26
+ * Serializes a Wormhole receipt for JSON transport.
27
+ *
28
+ * JSON doesn't natively support BigInt, Uint8Array, or class instances.
29
+ * This function converts these types to a serializable format with type markers
30
+ * that can be reconstructed by `deserializeReceipt`.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * import { serializeReceipt } from "@aptos-labs/cross-chain-core";
35
+ *
36
+ * // On the client side, before sending to server
37
+ * const serialized = serializeReceipt(receipt);
38
+ * await fetch("/api/claim", {
39
+ * method: "POST",
40
+ * body: JSON.stringify({ receipt: serialized }),
41
+ * });
42
+ * ```
43
+ */
44
+ export function serializeReceipt(
45
+ receipt: routes.Receipt<AttestationReceipt>,
46
+ ): unknown {
47
+ return JSON.parse(
48
+ JSON.stringify(receipt, (_key, value) => {
49
+ if (typeof value === "bigint") {
50
+ return { __type: "bigint", value: value.toString() };
51
+ }
52
+ // Check UniversalAddress before Uint8Array — if the SDK ever makes
53
+ // UniversalAddress extend Uint8Array the order matters.
54
+ if (value instanceof UniversalAddress) {
55
+ return {
56
+ __type: "UniversalAddress",
57
+ value: uint8ArrayToBase64(value.toUint8Array()),
58
+ };
59
+ }
60
+ if (value instanceof Uint8Array) {
61
+ return {
62
+ __type: "Uint8Array",
63
+ value: uint8ArrayToBase64(value),
64
+ };
65
+ }
66
+ return value;
67
+ }),
68
+ );
69
+ }
70
+
71
+ /**
72
+ * Deserializes a Wormhole receipt from JSON transport format.
73
+ *
74
+ * Reconstructs BigInt, Uint8Array, and UniversalAddress instances from
75
+ * the serialized format produced by `serializeReceipt`.
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * import { deserializeReceipt, SolanaLocalSigner } from "@aptos-labs/cross-chain-core";
80
+ *
81
+ * // On the server side, after receiving from client
82
+ * const receipt = deserializeReceipt(body.receipt);
83
+ * await cctpRoute.complete(signer, receipt);
84
+ * ```
85
+ */
86
+ export function deserializeReceipt(
87
+ obj: unknown,
88
+ ): routes.Receipt<AttestationReceipt> {
89
+ function revive(value: unknown, key?: string): unknown {
90
+ if (value && typeof value === "object") {
91
+ const objValue = value as Record<string, unknown>;
92
+
93
+ // Handle serialized BigInt, Uint8Array, and UniversalAddress
94
+ if ("__type" in objValue) {
95
+ if (objValue.__type === "bigint") {
96
+ return BigInt(objValue.value as string);
97
+ }
98
+ if (objValue.__type === "UniversalAddress") {
99
+ return new UniversalAddress(
100
+ base64ToUint8Array(objValue.value as string),
101
+ );
102
+ }
103
+ if (objValue.__type === "Uint8Array") {
104
+ return base64ToUint8Array(objValue.value as string);
105
+ }
106
+ }
107
+
108
+ // Backwards-compatible fallback: reconstruct UniversalAddress for known
109
+ // CCTP message address fields that were serialized without __type markers
110
+ // (i.e. data produced before UniversalAddress-aware serialization).
111
+ const addressFields = [
112
+ "sender",
113
+ "recipient",
114
+ "destinationCaller",
115
+ "burnToken",
116
+ "mintRecipient",
117
+ "messageSender",
118
+ ];
119
+ if (key && addressFields.includes(key) && "address" in objValue) {
120
+ const addressBytes = revive(objValue.address);
121
+ if (addressBytes instanceof Uint8Array) {
122
+ return new UniversalAddress(addressBytes);
123
+ }
124
+ }
125
+
126
+ // Recursively process nested objects and arrays
127
+ if (Array.isArray(value)) {
128
+ return value.map((v, i) => revive(v, String(i)));
129
+ }
130
+ const result: Record<string, unknown> = {};
131
+ for (const k in objValue) {
132
+ result[k] = revive(objValue[k], k);
133
+ }
134
+ return result;
135
+ }
136
+ return value;
137
+ }
138
+
139
+ return revive(obj) as routes.Receipt<AttestationReceipt>;
140
+ }
141
+
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const CROSS_CHAIN_CORE_VERSION = "5.8.2";
1
+ export const CROSS_CHAIN_CORE_VERSION = "5.9.0";