@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.
- package/README.md +26 -0
- package/dist/CrossChainCore.d.ts +20 -0
- package/dist/CrossChainCore.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +580 -275
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +583 -274
- package/dist/index.mjs.map +1 -1
- package/dist/providers/wormhole/index.d.ts +2 -0
- package/dist/providers/wormhole/index.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/AptosLocalSigner.d.ts +1 -1
- package/dist/providers/wormhole/signers/AptosLocalSigner.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/Signer.d.ts +1 -1
- package/dist/providers/wormhole/signers/Signer.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/SolanaLocalSigner.d.ts +65 -0
- package/dist/providers/wormhole/signers/SolanaLocalSigner.d.ts.map +1 -0
- package/dist/providers/wormhole/signers/SolanaSigner.d.ts +12 -20
- package/dist/providers/wormhole/signers/SolanaSigner.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/solanaUtils.d.ts +68 -0
- package/dist/providers/wormhole/signers/solanaUtils.d.ts.map +1 -0
- package/dist/providers/wormhole/types.d.ts +43 -0
- package/dist/providers/wormhole/types.d.ts.map +1 -1
- package/dist/providers/wormhole/utils.d.ts +26 -0
- package/dist/providers/wormhole/utils.d.ts.map +1 -0
- package/dist/providers/wormhole/wormhole.d.ts +36 -6
- package/dist/providers/wormhole/wormhole.d.ts.map +1 -1
- package/dist/utils/receiptSerialization.d.ts +38 -0
- package/dist/utils/receiptSerialization.d.ts.map +1 -0
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/CrossChainCore.ts +20 -0
- package/src/config/mainnet/chains.ts +2 -2
- package/src/config/testnet/chains.ts +2 -2
- package/src/index.ts +1 -0
- package/src/providers/wormhole/index.ts +2 -0
- package/src/providers/wormhole/signers/AptosLocalSigner.ts +4 -4
- package/src/providers/wormhole/signers/AptosSigner.ts +1 -1
- package/src/providers/wormhole/signers/EthereumSigner.ts +3 -3
- package/src/providers/wormhole/signers/Signer.ts +4 -4
- package/src/providers/wormhole/signers/SolanaLocalSigner.ts +243 -0
- package/src/providers/wormhole/signers/SolanaSigner.ts +45 -337
- package/src/providers/wormhole/signers/solanaUtils.ts +422 -0
- package/src/providers/wormhole/types.ts +68 -0
- package/src/providers/wormhole/utils.ts +72 -0
- package/src/providers/wormhole/wormhole.ts +182 -120
- package/src/utils/receiptSerialization.ts +141 -0
- 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 {
|
|
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}`)
|
|
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}`)
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
|
337
|
+
await wallet.features["aptos:account"].account()
|
|
359
338
|
).address.toString(),
|
|
360
339
|
{},
|
|
361
|
-
|
|
340
|
+
wallet,
|
|
362
341
|
this.crossChainCore,
|
|
363
342
|
sponsorAccount,
|
|
364
343
|
);
|
|
365
344
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
350
|
+
const receipt = await this.wormholeRoute.initiate(
|
|
375
351
|
this.wormholeRequest,
|
|
376
352
|
signer,
|
|
377
353
|
this.wormholeQuote,
|
|
378
|
-
|
|
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;
|
|
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.
|
|
395
|
-
logger.log("
|
|
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
|
|
391
|
+
`Error tracking withdraw (attempt ${retries + 1} / ${maxRetries}):`,
|
|
435
392
|
e,
|
|
436
393
|
);
|
|
437
|
-
const delay = baseDelay * Math.pow(2, retries);
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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.
|
|
1
|
+
export const CROSS_CHAIN_CORE_VERSION = "5.9.0";
|