@aptos-labs/cross-chain-core 5.8.2 → 6.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/README.md +26 -0
- package/dist/CrossChainCore.d.ts +95 -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 +908 -404
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +914 -409
- 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 +9 -7
- package/dist/providers/wormhole/signers/AptosLocalSigner.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/AptosSigner.d.ts +2 -1
- package/dist/providers/wormhole/signers/AptosSigner.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/EthereumSigner.d.ts +1 -1
- package/dist/providers/wormhole/signers/EthereumSigner.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/Signer.d.ts +11 -3
- package/dist/providers/wormhole/signers/Signer.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/SolanaLocalSigner.d.ts +69 -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 +120 -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 +62 -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 +3 -3
- package/src/CrossChainCore.ts +110 -3
- 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 +31 -18
- package/src/providers/wormhole/signers/AptosSigner.ts +11 -2
- package/src/providers/wormhole/signers/EthereumSigner.ts +59 -8
- package/src/providers/wormhole/signers/Signer.ts +23 -6
- package/src/providers/wormhole/signers/SolanaLocalSigner.ts +250 -0
- package/src/providers/wormhole/signers/SolanaSigner.ts +49 -338
- package/src/providers/wormhole/signers/solanaUtils.ts +446 -0
- package/src/providers/wormhole/types.ts +167 -0
- package/src/providers/wormhole/utils.ts +72 -0
- package/src/providers/wormhole/wormhole.ts +309 -137
- package/src/utils/receiptSerialization.ts +141 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { routes, AttestationReceipt } from "@wormhole-foundation/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Serializes a Wormhole receipt for JSON transport.
|
|
4
|
+
*
|
|
5
|
+
* JSON doesn't natively support BigInt, Uint8Array, or class instances.
|
|
6
|
+
* This function converts these types to a serializable format with type markers
|
|
7
|
+
* that can be reconstructed by `deserializeReceipt`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { serializeReceipt } from "@aptos-labs/cross-chain-core";
|
|
12
|
+
*
|
|
13
|
+
* // On the client side, before sending to server
|
|
14
|
+
* const serialized = serializeReceipt(receipt);
|
|
15
|
+
* await fetch("/api/claim", {
|
|
16
|
+
* method: "POST",
|
|
17
|
+
* body: JSON.stringify({ receipt: serialized }),
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function serializeReceipt(receipt: routes.Receipt<AttestationReceipt>): unknown;
|
|
22
|
+
/**
|
|
23
|
+
* Deserializes a Wormhole receipt from JSON transport format.
|
|
24
|
+
*
|
|
25
|
+
* Reconstructs BigInt, Uint8Array, and UniversalAddress instances from
|
|
26
|
+
* the serialized format produced by `serializeReceipt`.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { deserializeReceipt, SolanaLocalSigner } from "@aptos-labs/cross-chain-core";
|
|
31
|
+
*
|
|
32
|
+
* // On the server side, after receiving from client
|
|
33
|
+
* const receipt = deserializeReceipt(body.receipt);
|
|
34
|
+
* await cctpRoute.complete(signer, receipt);
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function deserializeReceipt(obj: unknown): routes.Receipt<AttestationReceipt>;
|
|
38
|
+
//# sourceMappingURL=receiptSerialization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receiptSerialization.d.ts","sourceRoot":"","sources":["../../src/utils/receiptSerialization.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,kBAAkB,EAEnB,MAAM,0BAA0B,CAAC;AAoBlC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,GAC1C,OAAO,CAuBT;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,OAAO,GACX,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAoDpC"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CROSS_CHAIN_CORE_VERSION = "
|
|
1
|
+
export declare const CROSS_CHAIN_CORE_VERSION = "6.0.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aptos-labs/cross-chain-core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Aptos Cross Chain Core",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
"eventemitter3": "^4.0.7",
|
|
58
58
|
"tweetnacl": "^1.0.3",
|
|
59
59
|
"@aptos-labs/derived-wallet-ethereum": "0.9.0",
|
|
60
|
-
"@aptos-labs/derived-wallet-solana": "0.12.0",
|
|
61
60
|
"@aptos-labs/derived-wallet-sui": "0.2.0",
|
|
62
|
-
"@aptos-labs/wallet-adapter-core": "8.
|
|
61
|
+
"@aptos-labs/wallet-adapter-core": "8.4.0",
|
|
62
|
+
"@aptos-labs/derived-wallet-solana": "0.12.1"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"@aptos-labs/ts-sdk": "^5.1.1"
|
package/src/CrossChainCore.ts
CHANGED
|
@@ -29,6 +29,17 @@ import {
|
|
|
29
29
|
export interface CrossChainDappConfig {
|
|
30
30
|
aptosNetwork: Network;
|
|
31
31
|
disableTelemetry?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Returns an epoch-second timestamp used as `expireTimestamp` when building
|
|
34
|
+
* Aptos transactions. Called at transaction-build time so that each
|
|
35
|
+
* transaction in a multi-step bridge flow gets a fresh expiration window.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* getExpireTimestamp: () => Math.floor(Date.now() / 1000) + 120 // 2 minutes
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
getExpireTimestamp?: () => number;
|
|
32
43
|
solanaConfig?: {
|
|
33
44
|
rpc?: string;
|
|
34
45
|
priorityFeeConfig?: {
|
|
@@ -37,7 +48,73 @@ export interface CrossChainDappConfig {
|
|
|
37
48
|
min?: number;
|
|
38
49
|
max?: number;
|
|
39
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* URL of a server-side API endpoint that claims withdraw transactions on Solana.
|
|
53
|
+
* When set, the SDK will POST the attested receipt to this URL instead of
|
|
54
|
+
* asking the user's wallet to sign the claim transaction.
|
|
55
|
+
*
|
|
56
|
+
* Expected request body: { serializedReceipt: string, destinationAddress: string, sourceChain: string }
|
|
57
|
+
* Expected response: { destinationChainTxnId: string }
|
|
58
|
+
* Check out the SERVERSIDE_SOLANA_SIGNER.md file for more details.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* const crossChainCore = new CrossChainCore({
|
|
62
|
+
* dappConfig: {
|
|
63
|
+
* aptosNetwork: Network.TESTNET,
|
|
64
|
+
* solanaConfig: {
|
|
65
|
+
* serverClaimUrl: "/api/claim-withdraw",
|
|
66
|
+
* },
|
|
67
|
+
* },
|
|
68
|
+
* });
|
|
69
|
+
*/
|
|
70
|
+
serverClaimUrl?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Solana transaction confirmation commitment level.
|
|
73
|
+
*
|
|
74
|
+
* - `"finalized"` (default) — waits for supermajority finalization (~30 s).
|
|
75
|
+
* - `"confirmed"` — waits for supermajority confirmation (~0.5 s).
|
|
76
|
+
*
|
|
77
|
+
* For bridge flows `"confirmed"` is usually sufficient because Wormhole
|
|
78
|
+
* guardians independently verify finality before issuing attestations.
|
|
79
|
+
*
|
|
80
|
+
* @default "finalized"
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* const crossChainCore = new CrossChainCore({
|
|
84
|
+
* dappConfig: {
|
|
85
|
+
* aptosNetwork: Network.MAINNET,
|
|
86
|
+
* solanaConfig: {
|
|
87
|
+
* commitment: "confirmed", // ~0.5 s vs ~30 s
|
|
88
|
+
* },
|
|
89
|
+
* },
|
|
90
|
+
* });
|
|
91
|
+
*/
|
|
92
|
+
commitment?: "confirmed" | "finalized";
|
|
40
93
|
};
|
|
94
|
+
/**
|
|
95
|
+
* Custom RPC endpoints for EVM chains. When provided, these override the
|
|
96
|
+
* built-in `defaultRpc` values for balance lookups and Wormhole SDK
|
|
97
|
+
* initialization.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* evmConfig: {
|
|
102
|
+
* Ethereum: { rpc: "https://rpc.ankr.com/eth/MY_KEY" },
|
|
103
|
+
* Base: { rpc: "https://rpc.ankr.com/base/MY_KEY" },
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
evmConfig?: Partial<Record<EvmChainName, { rpc: string }>>;
|
|
108
|
+
/**
|
|
109
|
+
* Custom RPC endpoint for the Sui chain. When provided, overrides the
|
|
110
|
+
* built-in `defaultRpc` for balance lookups and Wormhole SDK initialization.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* suiConfig: { rpc: "https://fullnode.mainnet.sui.io" }
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
suiConfig?: { rpc?: string };
|
|
41
118
|
}
|
|
42
119
|
export type { AccountAddressInput } from "@aptos-labs/ts-sdk";
|
|
43
120
|
export { NetworkToChainId, NetworkToNodeAPI } from "@aptos-labs/ts-sdk";
|
|
@@ -58,6 +135,27 @@ export type Chain =
|
|
|
58
135
|
| "Polygon"
|
|
59
136
|
| "Sui";
|
|
60
137
|
|
|
138
|
+
/**
|
|
139
|
+
* EVM chain names supported by the SDK — derived from {@link Chain} by
|
|
140
|
+
* excluding the non-EVM ecosystems. Adding a new EVM chain to `Chain`
|
|
141
|
+
* automatically makes it a valid key in `evmConfig`.
|
|
142
|
+
*/
|
|
143
|
+
export type EvmChainName = Exclude<Chain, "Solana" | "Aptos" | "Sui">;
|
|
144
|
+
|
|
145
|
+
// Record ensures every EvmChainName key is present at compile time.
|
|
146
|
+
const _evmChainRecord: Record<EvmChainName, true> = {
|
|
147
|
+
Ethereum: true,
|
|
148
|
+
Sepolia: true,
|
|
149
|
+
BaseSepolia: true,
|
|
150
|
+
ArbitrumSepolia: true,
|
|
151
|
+
Avalanche: true,
|
|
152
|
+
Base: true,
|
|
153
|
+
Arbitrum: true,
|
|
154
|
+
PolygonSepolia: true,
|
|
155
|
+
Polygon: true,
|
|
156
|
+
};
|
|
157
|
+
export const EVM_CHAIN_NAMES = Object.keys(_evmChainRecord) as EvmChainName[];
|
|
158
|
+
|
|
61
159
|
// Map of Ethereum chain id to testnet chain config
|
|
62
160
|
export const EthereumChainIdToTestnetChain: Record<string, ChainConfig> = {
|
|
63
161
|
11155111: testnetChains.Sepolia!,
|
|
@@ -99,6 +197,14 @@ export class CrossChainCore {
|
|
|
99
197
|
readonly CHAINS: ChainsConfig = testnetChains;
|
|
100
198
|
readonly TOKENS: Record<string, TokenConfig> = testnetTokens;
|
|
101
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Last known source-chain transaction ID, set by signers immediately after
|
|
202
|
+
* a transaction is submitted. Acts as a recovery side-channel so that
|
|
203
|
+
* callers can retrieve the tx hash even when the orchestration layer throws
|
|
204
|
+
* before returning it (e.g. claim failure after a successful burn).
|
|
205
|
+
*/
|
|
206
|
+
_lastSourceChainTxId: string | undefined;
|
|
207
|
+
|
|
102
208
|
constructor(args: { dappConfig: CrossChainDappConfig }) {
|
|
103
209
|
this._dappConfig = args.dappConfig;
|
|
104
210
|
if (args.dappConfig?.aptosNetwork === Network.MAINNET) {
|
|
@@ -161,14 +267,15 @@ export class CrossChainCore {
|
|
|
161
267
|
walletAddress,
|
|
162
268
|
this._dappConfig.aptosNetwork,
|
|
163
269
|
sourceChain,
|
|
164
|
-
|
|
165
|
-
|
|
270
|
+
this._dappConfig?.evmConfig?.[sourceChain]?.rpc ??
|
|
271
|
+
this.CHAINS[sourceChain].defaultRpc,
|
|
166
272
|
);
|
|
167
273
|
case "Sui":
|
|
168
274
|
return await getSuiWalletUSDCBalance(
|
|
169
275
|
walletAddress,
|
|
170
276
|
this._dappConfig.aptosNetwork,
|
|
171
|
-
this.
|
|
277
|
+
this._dappConfig?.suiConfig?.rpc ??
|
|
278
|
+
this.CHAINS[sourceChain].defaultRpc,
|
|
172
279
|
);
|
|
173
280
|
default:
|
|
174
281
|
throw new Error(`Unsupported chain: ${sourceChain}`);
|
|
@@ -16,8 +16,8 @@ export const mainnetChains: ChainsConfig = {
|
|
|
16
16
|
key: "Solana",
|
|
17
17
|
context: Context.SOLANA,
|
|
18
18
|
displayName: "Solana",
|
|
19
|
-
explorerUrl: "https://
|
|
20
|
-
explorerName: "
|
|
19
|
+
explorerUrl: "https://solscan.io",
|
|
20
|
+
explorerName: "Solscan",
|
|
21
21
|
chainId: 0,
|
|
22
22
|
icon: "Solana",
|
|
23
23
|
symbol: "SOL",
|
|
@@ -61,8 +61,8 @@ export const testnetChains: ChainsConfig = {
|
|
|
61
61
|
key: "Solana",
|
|
62
62
|
context: Context.SOLANA,
|
|
63
63
|
displayName: "Solana",
|
|
64
|
-
explorerUrl: "https://
|
|
65
|
-
explorerName: "
|
|
64
|
+
explorerUrl: "https://solscan.io",
|
|
65
|
+
explorerName: "Solscan",
|
|
66
66
|
chainId: 0,
|
|
67
67
|
icon: "Solana",
|
|
68
68
|
symbol: "SOL",
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
AnyRawTransaction,
|
|
4
4
|
Aptos,
|
|
5
5
|
AptosConfig,
|
|
6
|
-
Network as AptosNetwork,
|
|
7
6
|
Account,
|
|
8
7
|
} from "@aptos-labs/ts-sdk";
|
|
9
8
|
|
|
@@ -18,8 +17,11 @@ import {
|
|
|
18
17
|
AptosUnsignedTransaction,
|
|
19
18
|
AptosChains,
|
|
20
19
|
} from "@wormhole-foundation/sdk-aptos";
|
|
21
|
-
import {
|
|
22
|
-
|
|
20
|
+
import {
|
|
21
|
+
OnTransactionSigned,
|
|
22
|
+
validateExpireTimestamp,
|
|
23
|
+
} from "../types";
|
|
24
|
+
import { CrossChainCore } from "../../../CrossChainCore";
|
|
23
25
|
|
|
24
26
|
export class AptosLocalSigner<
|
|
25
27
|
N extends Network,
|
|
@@ -28,22 +30,24 @@ export class AptosLocalSigner<
|
|
|
28
30
|
_chain: C;
|
|
29
31
|
_options: any;
|
|
30
32
|
_wallet: Account;
|
|
31
|
-
_sponsorAccount: Account |
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
_sponsorAccount: Account | undefined;
|
|
34
|
+
_onTransactionSigned: OnTransactionSigned | undefined;
|
|
35
|
+
_crossChainCore: CrossChainCore;
|
|
36
|
+
_claimedTransactionHashes: string[] = [];
|
|
34
37
|
constructor(
|
|
35
38
|
chain: C,
|
|
36
39
|
options: any,
|
|
37
40
|
wallet: Account,
|
|
38
|
-
feePayerAccount: Account |
|
|
39
|
-
|
|
41
|
+
feePayerAccount: Account | undefined,
|
|
42
|
+
crossChainCore: CrossChainCore,
|
|
43
|
+
onTransactionSigned?: OnTransactionSigned,
|
|
40
44
|
) {
|
|
41
45
|
this._chain = chain;
|
|
42
46
|
this._options = options;
|
|
43
47
|
this._wallet = wallet;
|
|
44
48
|
this._sponsorAccount = feePayerAccount;
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
49
|
+
this._crossChainCore = crossChainCore;
|
|
50
|
+
this._onTransactionSigned = onTransactionSigned;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
chain(): C {
|
|
@@ -54,21 +58,24 @@ export class AptosLocalSigner<
|
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
claimedTransactionHashes(): string {
|
|
57
|
-
return this._claimedTransactionHashes;
|
|
61
|
+
return this._claimedTransactionHashes.join(",");
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
async signAndSend(txs: UnsignedTransaction<N, C>[]): Promise<TxHash[]> {
|
|
61
65
|
const txHashes: TxHash[] = [];
|
|
66
|
+
this._claimedTransactionHashes = [];
|
|
62
67
|
|
|
63
68
|
for (const tx of txs) {
|
|
69
|
+
this._onTransactionSigned?.(tx.description, null);
|
|
64
70
|
const txId = await signAndSendTransaction(
|
|
65
71
|
tx as AptosUnsignedTransaction<Network, AptosChains>,
|
|
66
72
|
this._wallet,
|
|
67
73
|
this._sponsorAccount,
|
|
68
|
-
this.
|
|
74
|
+
this._crossChainCore,
|
|
69
75
|
);
|
|
76
|
+
this._onTransactionSigned?.(tx.description, txId);
|
|
70
77
|
txHashes.push(txId);
|
|
71
|
-
this._claimedTransactionHashes
|
|
78
|
+
this._claimedTransactionHashes.push(txId);
|
|
72
79
|
}
|
|
73
80
|
return txHashes;
|
|
74
81
|
}
|
|
@@ -77,8 +84,8 @@ export class AptosLocalSigner<
|
|
|
77
84
|
export async function signAndSendTransaction(
|
|
78
85
|
request: UnsignedTransaction<Network, AptosChains>,
|
|
79
86
|
wallet: Account,
|
|
80
|
-
sponsorAccount: Account |
|
|
81
|
-
|
|
87
|
+
sponsorAccount: Account | undefined,
|
|
88
|
+
crossChainCore: CrossChainCore,
|
|
82
89
|
) {
|
|
83
90
|
if (!wallet) {
|
|
84
91
|
throw new Error("Wallet is undefined");
|
|
@@ -96,15 +103,23 @@ export async function signAndSendTransaction(
|
|
|
96
103
|
}
|
|
97
104
|
});
|
|
98
105
|
|
|
106
|
+
const dappNetwork = crossChainCore._dappConfig.aptosNetwork;
|
|
99
107
|
const aptosConfig = new AptosConfig({
|
|
100
108
|
network: dappNetwork,
|
|
101
109
|
});
|
|
102
110
|
const aptos = new Aptos(aptosConfig);
|
|
103
111
|
|
|
112
|
+
const expireTimestamp = crossChainCore._dappConfig.getExpireTimestamp?.();
|
|
113
|
+
if (typeof expireTimestamp !== "undefined") {
|
|
114
|
+
validateExpireTimestamp(expireTimestamp);
|
|
115
|
+
}
|
|
104
116
|
const txnToSign = await aptos.transaction.build.simple({
|
|
105
117
|
data: payload,
|
|
106
118
|
sender: wallet.accountAddress.toString(),
|
|
107
119
|
withFeePayer: sponsorAccount ? true : false,
|
|
120
|
+
...(typeof expireTimestamp !== "undefined"
|
|
121
|
+
? { options: { expireTimestamp } }
|
|
122
|
+
: {}),
|
|
108
123
|
});
|
|
109
124
|
|
|
110
125
|
const senderAuthenticator = await aptos.transaction.sign({
|
|
@@ -122,10 +137,8 @@ export async function signAndSendTransaction(
|
|
|
122
137
|
};
|
|
123
138
|
|
|
124
139
|
if (sponsorAccount) {
|
|
125
|
-
// Gas station is currently impossible to use, since the transactino we get from
|
|
126
|
-
// Wormhole is a script transaction and gas station only supports entry function transactions
|
|
127
140
|
const feePayerSignerAuthenticator = aptos.transaction.signAsFeePayer({
|
|
128
|
-
signer: sponsorAccount
|
|
141
|
+
signer: sponsorAccount,
|
|
129
142
|
transaction: txnToSign,
|
|
130
143
|
});
|
|
131
144
|
txnToSubmit.feePayerAuthenticator = feePayerSignerAuthenticator;
|
|
@@ -16,18 +16,20 @@ import {
|
|
|
16
16
|
AptosChains,
|
|
17
17
|
AptosUnsignedTransaction,
|
|
18
18
|
} from "@wormhole-foundation/sdk-aptos";
|
|
19
|
-
import { GasStationApiKey } from "..";
|
|
19
|
+
import { GasStationApiKey, validateExpireTimestamp } from "..";
|
|
20
20
|
import { UserResponseStatus } from "@aptos-labs/wallet-standard";
|
|
21
21
|
import { GasStationClient, GasStationTransactionSubmitter } from "@aptos-labs/gas-station-client";
|
|
22
|
+
import { CrossChainCore } from "../../../CrossChainCore";
|
|
22
23
|
|
|
23
24
|
export async function signAndSendTransaction(
|
|
24
25
|
request: AptosUnsignedTransaction<Network, AptosChains>,
|
|
25
26
|
wallet: AdapterWallet,
|
|
26
27
|
sponsorAccount: Account | GasStationApiKey | undefined,
|
|
27
28
|
dappNetwork: AptosNetwork,
|
|
29
|
+
crossChainCore?: CrossChainCore,
|
|
28
30
|
) {
|
|
29
31
|
if (!wallet) {
|
|
30
|
-
throw new Error("wallet.sendTransaction is undefined")
|
|
32
|
+
throw new Error("wallet.sendTransaction is undefined");
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
const payload = request.transaction;
|
|
@@ -90,12 +92,19 @@ export async function signAndSendTransaction(
|
|
|
90
92
|
functionArguments,
|
|
91
93
|
};
|
|
92
94
|
|
|
95
|
+
const expireTimestamp = crossChainCore?._dappConfig?.getExpireTimestamp?.();
|
|
96
|
+
if (typeof expireTimestamp !== "undefined") {
|
|
97
|
+
validateExpireTimestamp(expireTimestamp);
|
|
98
|
+
}
|
|
93
99
|
const txnToSign = await aptos.transaction.build.simple({
|
|
94
100
|
data: transactionData,
|
|
95
101
|
sender: (
|
|
96
102
|
await wallet.features["aptos:account"]?.account()
|
|
97
103
|
).address.toString(),
|
|
98
104
|
withFeePayer: sponsorAccount ? true : false,
|
|
105
|
+
...(typeof expireTimestamp !== "undefined"
|
|
106
|
+
? { options: { expireTimestamp } }
|
|
107
|
+
: {}),
|
|
99
108
|
});
|
|
100
109
|
|
|
101
110
|
const response =
|
|
@@ -6,14 +6,14 @@ import { Network } from "@wormhole-foundation/sdk";
|
|
|
6
6
|
import { ethers, getBigInt } from "ethers";
|
|
7
7
|
import { AdapterWallet } from "@aptos-labs/wallet-adapter-core";
|
|
8
8
|
import { EIP1193DerivedWallet } from "@aptos-labs/derived-wallet-ethereum";
|
|
9
|
+
|
|
9
10
|
export async function signAndSendTransaction(
|
|
10
11
|
request: EvmUnsignedTransaction<Network, EvmChains>,
|
|
11
12
|
wallet: AdapterWallet,
|
|
12
13
|
chainName: string,
|
|
13
|
-
options: any,
|
|
14
14
|
): Promise<string> {
|
|
15
15
|
if (!wallet) {
|
|
16
|
-
throw new Error("wallet.sendTransaction is undefined")
|
|
16
|
+
throw new Error("wallet.sendTransaction is undefined");
|
|
17
17
|
}
|
|
18
18
|
// Ensure the signer is connected to the correct chain
|
|
19
19
|
const chainId = await (
|
|
@@ -23,8 +23,7 @@ export async function signAndSendTransaction(
|
|
|
23
23
|
});
|
|
24
24
|
const actualChainId = parseInt(chainId, 16);
|
|
25
25
|
|
|
26
|
-
if (!actualChainId)
|
|
27
|
-
throw new Error("No signer found for chain" + chainName).message;
|
|
26
|
+
if (!actualChainId) throw new Error("No signer found for chain" + chainName);
|
|
28
27
|
const expectedChainId = request.transaction.chainId
|
|
29
28
|
? getBigInt(request.transaction.chainId)
|
|
30
29
|
: undefined;
|
|
@@ -35,15 +34,67 @@ export async function signAndSendTransaction(
|
|
|
35
34
|
) {
|
|
36
35
|
throw new Error(
|
|
37
36
|
`Signer is not connected to the right chain. Expected ${expectedChainId}, got ${actualChainId}`,
|
|
38
|
-
)
|
|
37
|
+
);
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
const provider = new ethers.BrowserProvider(
|
|
42
41
|
(wallet as EIP1193DerivedWallet).eip1193Provider,
|
|
43
42
|
);
|
|
44
43
|
const signer = await provider.getSigner();
|
|
45
|
-
const response = await signer.sendTransaction(request.transaction);
|
|
46
|
-
const receipt = await response.wait();
|
|
47
44
|
|
|
48
|
-
|
|
45
|
+
let response: ethers.TransactionResponse;
|
|
46
|
+
try {
|
|
47
|
+
response = await signer.sendTransaction(request.transaction);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
// Some wallet providers (e.g. MetaMask via injected provider) can throw
|
|
50
|
+
// after the transaction is already broadcast. Try to extract the hash from
|
|
51
|
+
// the error so the caller can track the pending transaction.
|
|
52
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
53
|
+
const hashMatch = message.match(/"hash":\s*"(0x[a-fA-F0-9]{64})"/);
|
|
54
|
+
if (hashMatch) {
|
|
55
|
+
console.warn("Extracted EVM tx hash from error:", hashMatch[1]);
|
|
56
|
+
return hashMatch[1];
|
|
57
|
+
}
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const receipt = await response.wait();
|
|
63
|
+
return receipt?.hash || response.hash || "";
|
|
64
|
+
} catch (e: any) {
|
|
65
|
+
// When a user speeds up or cancels a transaction in their wallet, ethers
|
|
66
|
+
// throws a TRANSACTION_REPLACED error. We must handle this specifically
|
|
67
|
+
// to avoid returning the old (now-invalid) hash.
|
|
68
|
+
if (e?.code === "TRANSACTION_REPLACED") {
|
|
69
|
+
// "repriced" means the same transaction data was re-sent with higher
|
|
70
|
+
// gas — the bridge burn still went through with the replacement tx.
|
|
71
|
+
if (e.reason === "repriced") {
|
|
72
|
+
const replacementHash = e.receipt?.hash || e.replacement?.hash;
|
|
73
|
+
if (replacementHash) {
|
|
74
|
+
console.warn(
|
|
75
|
+
"EVM transaction was repriced. Using replacement hash:",
|
|
76
|
+
replacementHash,
|
|
77
|
+
);
|
|
78
|
+
return replacementHash;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// "cancelled" or "replaced" means the original burn was superseded by
|
|
82
|
+
// a different transaction (e.g. a 0-value self-transfer or an entirely
|
|
83
|
+
// different call). The bridge burn did not happen, so we must not
|
|
84
|
+
// return a hash that implies success.
|
|
85
|
+
throw e;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// wait() can fail due to network timeouts or RPC instability, but the
|
|
89
|
+
// transaction was already submitted (sendTransaction returned successfully).
|
|
90
|
+
// Return the hash so the caller can track confirmation asynchronously.
|
|
91
|
+
if (response.hash) {
|
|
92
|
+
console.warn(
|
|
93
|
+
"EVM transaction wait failed but tx was submitted:",
|
|
94
|
+
response.hash,
|
|
95
|
+
);
|
|
96
|
+
return response.hash;
|
|
97
|
+
}
|
|
98
|
+
throw e;
|
|
99
|
+
}
|
|
49
100
|
}
|
|
@@ -25,7 +25,7 @@ import { ChainConfig } from "../../../config";
|
|
|
25
25
|
import { CrossChainCore } from "../../../CrossChainCore";
|
|
26
26
|
import { AptosChains } from "@wormhole-foundation/sdk-aptos/dist/cjs/types";
|
|
27
27
|
import { AptosUnsignedTransaction } from "@wormhole-foundation/sdk-aptos/dist/cjs/unsignedTransaction";
|
|
28
|
-
import { GasStationApiKey } from "../types";
|
|
28
|
+
import { GasStationApiKey, OnTransactionSigned } from "../types";
|
|
29
29
|
import { Account } from "@aptos-labs/ts-sdk";
|
|
30
30
|
export class Signer<
|
|
31
31
|
N extends Network,
|
|
@@ -37,7 +37,15 @@ export class Signer<
|
|
|
37
37
|
_wallet: AdapterWallet;
|
|
38
38
|
_crossChainCore: CrossChainCore;
|
|
39
39
|
_sponsorAccount: Account | GasStationApiKey | undefined;
|
|
40
|
-
|
|
40
|
+
_onTransactionSigned: OnTransactionSigned | undefined;
|
|
41
|
+
_claimedTransactionHashes: string[] = [];
|
|
42
|
+
/**
|
|
43
|
+
* When true, signed tx hashes are written to
|
|
44
|
+
* `_crossChainCore._lastSourceChainTxId` as a recovery side-channel.
|
|
45
|
+
* Set to false for destination-chain claim signers so they don't
|
|
46
|
+
* overwrite the source-chain burn hash.
|
|
47
|
+
*/
|
|
48
|
+
_trackAsSourceChain: boolean;
|
|
41
49
|
|
|
42
50
|
constructor(
|
|
43
51
|
chain: ChainConfig,
|
|
@@ -46,6 +54,8 @@ export class Signer<
|
|
|
46
54
|
wallet: AdapterWallet,
|
|
47
55
|
crossChainCore: CrossChainCore,
|
|
48
56
|
sponsorAccount?: Account | GasStationApiKey | undefined,
|
|
57
|
+
onTransactionSigned?: OnTransactionSigned,
|
|
58
|
+
trackAsSourceChain: boolean = true,
|
|
49
59
|
) {
|
|
50
60
|
this._chain = chain;
|
|
51
61
|
this._address = address;
|
|
@@ -53,7 +63,8 @@ export class Signer<
|
|
|
53
63
|
this._wallet = wallet;
|
|
54
64
|
this._crossChainCore = crossChainCore;
|
|
55
65
|
this._sponsorAccount = sponsorAccount;
|
|
56
|
-
this.
|
|
66
|
+
this._onTransactionSigned = onTransactionSigned;
|
|
67
|
+
this._trackAsSourceChain = trackAsSourceChain;
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
chain(): C {
|
|
@@ -64,13 +75,15 @@ export class Signer<
|
|
|
64
75
|
}
|
|
65
76
|
|
|
66
77
|
claimedTransactionHashes(): string {
|
|
67
|
-
return this._claimedTransactionHashes;
|
|
78
|
+
return this._claimedTransactionHashes.join(",");
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
async signAndSend(txs: UnsignedTransaction<N, C>[]): Promise<TxHash[]> {
|
|
71
82
|
const txHashes: TxHash[] = [];
|
|
83
|
+
this._claimedTransactionHashes = [];
|
|
72
84
|
|
|
73
85
|
for (const tx of txs) {
|
|
86
|
+
this._onTransactionSigned?.(tx.description, null);
|
|
74
87
|
const txId = await signAndSendTransaction(
|
|
75
88
|
this._chain,
|
|
76
89
|
tx,
|
|
@@ -79,8 +92,12 @@ export class Signer<
|
|
|
79
92
|
this._crossChainCore,
|
|
80
93
|
this._sponsorAccount,
|
|
81
94
|
);
|
|
95
|
+
if (this._trackAsSourceChain) {
|
|
96
|
+
this._crossChainCore._lastSourceChainTxId = txId;
|
|
97
|
+
}
|
|
98
|
+
this._onTransactionSigned?.(tx.description, txId);
|
|
82
99
|
txHashes.push(txId);
|
|
83
|
-
this._claimedTransactionHashes
|
|
100
|
+
this._claimedTransactionHashes.push(txId);
|
|
84
101
|
}
|
|
85
102
|
return txHashes;
|
|
86
103
|
}
|
|
@@ -113,7 +130,6 @@ export const signAndSendTransaction = async (
|
|
|
113
130
|
request as EvmUnsignedTransaction<Network, EvmChains>,
|
|
114
131
|
wallet,
|
|
115
132
|
chain.displayName,
|
|
116
|
-
options,
|
|
117
133
|
);
|
|
118
134
|
return tx;
|
|
119
135
|
} else if (chain.context === "Sui") {
|
|
@@ -128,6 +144,7 @@ export const signAndSendTransaction = async (
|
|
|
128
144
|
wallet,
|
|
129
145
|
sponsorAccount,
|
|
130
146
|
dappNetwork,
|
|
147
|
+
crossChainCore,
|
|
131
148
|
);
|
|
132
149
|
return tx;
|
|
133
150
|
} else {
|