@aptos-labs/cross-chain-core 5.9.0 → 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/dist/CrossChainCore.d.ts +75 -0
- package/dist/CrossChainCore.d.ts.map +1 -1
- package/dist/index.js +360 -161
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +361 -165
- package/dist/index.mjs.map +1 -1
- package/dist/providers/wormhole/signers/AptosLocalSigner.d.ts +8 -6
- 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 +10 -2
- package/dist/providers/wormhole/signers/Signer.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/SolanaLocalSigner.d.ts +4 -0
- package/dist/providers/wormhole/signers/SolanaLocalSigner.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/SolanaSigner.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/solanaUtils.d.ts.map +1 -1
- package/dist/providers/wormhole/types.d.ts +78 -1
- package/dist/providers/wormhole/types.d.ts.map +1 -1
- package/dist/providers/wormhole/wormhole.d.ts +27 -1
- package/dist/providers/wormhole/wormhole.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +3 -3
- package/src/CrossChainCore.ts +91 -4
- package/src/providers/wormhole/signers/AptosLocalSigner.ts +27 -14
- package/src/providers/wormhole/signers/AptosSigner.ts +10 -1
- package/src/providers/wormhole/signers/EthereumSigner.ts +57 -6
- package/src/providers/wormhole/signers/Signer.ts +19 -2
- package/src/providers/wormhole/signers/SolanaLocalSigner.ts +7 -0
- package/src/providers/wormhole/signers/SolanaSigner.ts +4 -1
- package/src/providers/wormhole/signers/solanaUtils.ts +43 -19
- package/src/providers/wormhole/types.ts +100 -1
- package/src/providers/wormhole/wormhole.ts +138 -28
- package/src/version.ts +1 -1
|
@@ -16,15 +16,17 @@ 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
32
|
throw new Error("wallet.sendTransaction is undefined");
|
|
@@ -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,11 +6,11 @@ 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
16
|
throw new Error("wallet.sendTransaction is undefined");
|
|
@@ -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);
|
|
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;
|
|
@@ -42,8 +41,60 @@ export async function signAndSendTransaction(
|
|
|
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
|
+
_onTransactionSigned: OnTransactionSigned | undefined;
|
|
40
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,6 +63,8 @@ export class Signer<
|
|
|
53
63
|
this._wallet = wallet;
|
|
54
64
|
this._crossChainCore = crossChainCore;
|
|
55
65
|
this._sponsorAccount = sponsorAccount;
|
|
66
|
+
this._onTransactionSigned = onTransactionSigned;
|
|
67
|
+
this._trackAsSourceChain = trackAsSourceChain;
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
chain(): C {
|
|
@@ -71,6 +83,7 @@ export class Signer<
|
|
|
71
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,6 +92,10 @@ 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
100
|
this._claimedTransactionHashes.push(txId);
|
|
84
101
|
}
|
|
@@ -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 {
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
PriorityFeeConfig,
|
|
17
17
|
sendAndConfirmTransaction,
|
|
18
18
|
} from "./solanaUtils";
|
|
19
|
+
import { OnTransactionSigned } from "../types";
|
|
19
20
|
|
|
20
21
|
export interface SolanaLocalSignerConfig {
|
|
21
22
|
/** The Solana keypair to sign transactions with */
|
|
@@ -30,6 +31,8 @@ export interface SolanaLocalSignerConfig {
|
|
|
30
31
|
priorityFeeConfig?: PriorityFeeConfig;
|
|
31
32
|
/** Enable verbose logging (default: false) */
|
|
32
33
|
verbose?: boolean;
|
|
34
|
+
/** Optional callback fired before and after each individual transaction is signed. */
|
|
35
|
+
onTransactionSigned?: OnTransactionSigned;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|
|
@@ -68,6 +71,7 @@ export class SolanaLocalSigner<N extends Network, C extends Chain>
|
|
|
68
71
|
private retryIntervalMs: number;
|
|
69
72
|
private priorityFeeConfig?: PriorityFeeConfig;
|
|
70
73
|
private verbose: boolean;
|
|
74
|
+
private _onTransactionSigned?: OnTransactionSigned;
|
|
71
75
|
private _claimedTransactionHashes: string[] = [];
|
|
72
76
|
|
|
73
77
|
constructor(config: SolanaLocalSignerConfig) {
|
|
@@ -77,6 +81,7 @@ export class SolanaLocalSigner<N extends Network, C extends Chain>
|
|
|
77
81
|
this.retryIntervalMs = config.retryIntervalMs ?? 5000;
|
|
78
82
|
this.priorityFeeConfig = config.priorityFeeConfig;
|
|
79
83
|
this.verbose = config.verbose ?? false;
|
|
84
|
+
this._onTransactionSigned = config.onTransactionSigned;
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
chain(): C {
|
|
@@ -100,7 +105,9 @@ export class SolanaLocalSigner<N extends Network, C extends Chain>
|
|
|
100
105
|
this._claimedTransactionHashes = [];
|
|
101
106
|
|
|
102
107
|
for (const tx of txs) {
|
|
108
|
+
this._onTransactionSigned?.(tx.description, null);
|
|
103
109
|
const txId = await this.signAndSendTransaction(tx);
|
|
110
|
+
this._onTransactionSigned?.(tx.description, txId);
|
|
104
111
|
txHashes.push(txId);
|
|
105
112
|
this._claimedTransactionHashes.push(txId);
|
|
106
113
|
}
|
|
@@ -41,7 +41,10 @@ export async function signAndSendTransaction(
|
|
|
41
41
|
throw new Error("Invalid wallet type or missing Solana wallet");
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const commitment =
|
|
44
|
+
const commitment =
|
|
45
|
+
options?.commitment ??
|
|
46
|
+
crossChainCore?._dappConfig?.solanaConfig?.commitment ??
|
|
47
|
+
"finalized";
|
|
45
48
|
// Solana rpc should come from dapp config
|
|
46
49
|
const connection = new Connection(
|
|
47
50
|
crossChainCore?._dappConfig?.solanaConfig?.rpc ??
|
|
@@ -82,34 +82,58 @@ export async function sendAndConfirmTransaction(
|
|
|
82
82
|
commitment,
|
|
83
83
|
);
|
|
84
84
|
|
|
85
|
-
// Retry loop: resend if not confirmed after interval
|
|
85
|
+
// Retry loop: resend if not confirmed after interval.
|
|
86
|
+
// The confirmation promise can reject with "block height exceeded" when the
|
|
87
|
+
// blockhash expires before confirmation completes. Because the transaction was
|
|
88
|
+
// already sent (sendRawTransaction succeeded), it may still land on-chain.
|
|
89
|
+
// In that case we return the signature so the caller can track it, rather than
|
|
90
|
+
// throwing and losing the transaction reference.
|
|
86
91
|
let confirmedTx: RpcResponseAndContext<SignatureResult> | null = null;
|
|
87
92
|
let txSendAttempts = 1;
|
|
88
93
|
|
|
89
|
-
|
|
90
|
-
confirmedTx
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
try {
|
|
95
|
+
while (!confirmedTx) {
|
|
96
|
+
confirmedTx = await Promise.race([
|
|
97
|
+
confirmTransactionPromise,
|
|
98
|
+
new Promise<null>((resolve) =>
|
|
99
|
+
setTimeout(() => resolve(null), retryIntervalMs),
|
|
100
|
+
),
|
|
101
|
+
]);
|
|
96
102
|
|
|
97
|
-
|
|
103
|
+
if (confirmedTx) break;
|
|
98
104
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
if (verbose) {
|
|
106
|
+
console.log(
|
|
107
|
+
`Tx not confirmed after ${retryIntervalMs * txSendAttempts++}ms, resending`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
try {
|
|
112
|
+
await connection.sendRawTransaction(serializedTx, sendOptions);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
if (verbose) {
|
|
115
|
+
console.error("Failed to resend transaction:", e);
|
|
116
|
+
}
|
|
117
|
+
// Ignore resend errors, confirmation will handle success/failure
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch (e) {
|
|
121
|
+
const message = e instanceof Error ? e.message.toLowerCase() : "";
|
|
122
|
+
if (
|
|
123
|
+
message.includes("block height exceeded") ||
|
|
124
|
+
message.includes("blockheightexceeded")
|
|
125
|
+
) {
|
|
108
126
|
if (verbose) {
|
|
109
|
-
console.
|
|
127
|
+
console.warn(
|
|
128
|
+
"Block height exceeded but tx was already sent, returning signature:",
|
|
129
|
+
signature,
|
|
130
|
+
);
|
|
110
131
|
}
|
|
111
|
-
//
|
|
132
|
+
// Transaction was already sent — return the signature so the caller can
|
|
133
|
+
// track confirmation asynchronously instead of losing the tx reference.
|
|
134
|
+
return signature;
|
|
112
135
|
}
|
|
136
|
+
throw e;
|
|
113
137
|
}
|
|
114
138
|
|
|
115
139
|
if (confirmedTx.value.err) {
|
|
@@ -35,6 +35,8 @@ export interface WormholeTransferRequest {
|
|
|
35
35
|
mainSigner: Account;
|
|
36
36
|
amount?: string;
|
|
37
37
|
sponsorAccount?: Account;
|
|
38
|
+
/** Optional callback fired before and after each individual transaction is signed. */
|
|
39
|
+
onTransactionSigned?: OnTransactionSigned;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export type WithdrawPhase =
|
|
@@ -42,25 +44,51 @@ export type WithdrawPhase =
|
|
|
42
44
|
| "tracking" // Waiting for Wormhole attestation (~60s)
|
|
43
45
|
| "claiming"; // Claiming on destination chain
|
|
44
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Callback fired before and after each individual transaction is signed
|
|
49
|
+
* and submitted during a bridge flow.
|
|
50
|
+
*
|
|
51
|
+
* @param description - A human-readable description of the transaction
|
|
52
|
+
* (e.g. "Approving USDC transfer"). Comes from the Wormhole SDK's
|
|
53
|
+
* `UnsignedTransaction.description`.
|
|
54
|
+
* @param txId - `null` when called *before* signing; the on-chain
|
|
55
|
+
* transaction hash when called *after* signing.
|
|
56
|
+
*/
|
|
57
|
+
export type OnTransactionSigned = (description: string, txId: string | null) => void;
|
|
58
|
+
|
|
45
59
|
export interface WormholeWithdrawRequest {
|
|
60
|
+
/**
|
|
61
|
+
* The non-Aptos chain involved in the withdrawal. For a withdrawal from
|
|
62
|
+
* Aptos → Solana, this is `"Solana"`.
|
|
63
|
+
*
|
|
64
|
+
* Note: despite the name, this is the *destination* of the bridge transfer
|
|
65
|
+
* (where USDC will be claimed), not the chain that burns USDC (which is
|
|
66
|
+
* always Aptos for withdrawals).
|
|
67
|
+
*/
|
|
46
68
|
sourceChain: Chain;
|
|
47
69
|
wallet: AdapterWallet;
|
|
48
70
|
destinationAddress: AccountAddressInput;
|
|
49
71
|
sponsorAccount?: Account | GasStationApiKey;
|
|
50
72
|
/** Optional callback fired when the withdraw progresses to a new phase. */
|
|
51
73
|
onPhaseChange?: (phase: WithdrawPhase) => void;
|
|
74
|
+
/** Optional callback fired before and after each individual transaction is signed. */
|
|
75
|
+
onTransactionSigned?: OnTransactionSigned;
|
|
52
76
|
}
|
|
53
77
|
|
|
54
78
|
export interface WormholeSubmitTransferRequest {
|
|
55
79
|
sourceChain: Chain;
|
|
56
80
|
wallet: AdapterWallet;
|
|
57
81
|
destinationAddress: AccountAddressInput;
|
|
82
|
+
/** Optional callback fired before and after each individual transaction is signed. */
|
|
83
|
+
onTransactionSigned?: OnTransactionSigned;
|
|
58
84
|
}
|
|
59
85
|
|
|
60
86
|
export interface WormholeClaimTransferRequest {
|
|
61
87
|
receipt: routes.Receipt<AttestationReceipt>;
|
|
62
88
|
mainSigner: AptosAccount;
|
|
63
89
|
sponsorAccount?: AptosAccount | GasStationApiKey;
|
|
90
|
+
/** Optional callback fired before and after each individual transaction is signed. */
|
|
91
|
+
onTransactionSigned?: OnTransactionSigned;
|
|
64
92
|
}
|
|
65
93
|
|
|
66
94
|
export interface WormholeTransferResponse {
|
|
@@ -84,6 +112,8 @@ export interface WormholeInitiateWithdrawRequest {
|
|
|
84
112
|
wallet: AdapterWallet;
|
|
85
113
|
destinationAddress: AccountAddressInput;
|
|
86
114
|
sponsorAccount?: Account | GasStationApiKey;
|
|
115
|
+
/** Optional callback fired before and after each individual transaction is signed. */
|
|
116
|
+
onTransactionSigned?: OnTransactionSigned;
|
|
87
117
|
}
|
|
88
118
|
|
|
89
119
|
export interface WormholeInitiateWithdrawResponse {
|
|
@@ -92,18 +122,87 @@ export interface WormholeInitiateWithdrawResponse {
|
|
|
92
122
|
}
|
|
93
123
|
|
|
94
124
|
export interface WormholeClaimWithdrawRequest {
|
|
95
|
-
|
|
125
|
+
/**
|
|
126
|
+
* The chain on which the claim transaction will be executed (the destination
|
|
127
|
+
* chain of the withdrawal).
|
|
128
|
+
*
|
|
129
|
+
* For example, when withdrawing from Aptos → Solana, `claimChain` is
|
|
130
|
+
* `"Solana"` because that's where the USDC is minted/claimed.
|
|
131
|
+
*/
|
|
132
|
+
claimChain: Chain;
|
|
96
133
|
destinationAddress: string;
|
|
97
134
|
receipt: routes.Receipt<AttestationReceipt>;
|
|
98
135
|
// Required for wallet-based claim (non-Solana chains, or Solana without serverClaimUrl).
|
|
99
136
|
// Not needed when the SDK uses the configured serverClaimUrl for Solana claims.
|
|
100
137
|
wallet?: AdapterWallet;
|
|
138
|
+
/** Optional callback fired before and after each individual transaction is signed. */
|
|
139
|
+
onTransactionSigned?: OnTransactionSigned;
|
|
101
140
|
}
|
|
102
141
|
|
|
103
142
|
export interface WormholeClaimWithdrawResponse {
|
|
104
143
|
destinationChainTxnId: string;
|
|
105
144
|
}
|
|
106
145
|
|
|
146
|
+
export interface RetryWithdrawClaimRequest extends WormholeClaimWithdrawRequest {
|
|
147
|
+
/** Maximum number of retry attempts (default: 5). */
|
|
148
|
+
maxRetries?: number;
|
|
149
|
+
/** Initial delay in ms before the first retry (default: 2000). */
|
|
150
|
+
initialDelayMs?: number;
|
|
151
|
+
/** Multiplier applied to the delay after each failed attempt (default: 2). */
|
|
152
|
+
backoffMultiplier?: number;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface RetryWithdrawClaimResponse extends WormholeClaimWithdrawResponse {
|
|
156
|
+
/** Number of retry attempts that were needed (0 means first attempt succeeded). */
|
|
157
|
+
retriesUsed: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Validates that a value returned by `getExpireTimestamp` is a non-negative
|
|
162
|
+
* integer suitable for use as an epoch-second expiration timestamp.
|
|
163
|
+
* Throws immediately for NaN, Infinity, negative values, or floats so that
|
|
164
|
+
* misconfigured callbacks fail fast instead of producing silent misbehaviour.
|
|
165
|
+
*/
|
|
166
|
+
export function validateExpireTimestamp(value: number): void {
|
|
167
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`getExpireTimestamp returned an invalid value (${value}). ` +
|
|
170
|
+
"Expected a non-negative integer (epoch seconds).",
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Error thrown when the transfer (deposit) flow fails *after* the source-chain
|
|
177
|
+
* burn transaction has already been submitted (i.e. during attestation tracking
|
|
178
|
+
* or Aptos claiming).
|
|
179
|
+
*
|
|
180
|
+
* Consumers should check `instanceof TransferError` in their catch block
|
|
181
|
+
* to recover the `originChainTxnId` and display an explorer link so the
|
|
182
|
+
* user can verify their burn on-chain.
|
|
183
|
+
*/
|
|
184
|
+
export class TransferError extends Error {
|
|
185
|
+
/** Source-chain burn transaction hash — available when the burn succeeded before the failure. */
|
|
186
|
+
readonly originChainTxnId: string;
|
|
187
|
+
/**
|
|
188
|
+
* The underlying error that caused this failure.
|
|
189
|
+
* Mirrors ES2022 Error.cause — declared explicitly because the project's
|
|
190
|
+
* TypeScript lib target does not include ES2022 ErrorOptions.
|
|
191
|
+
*/
|
|
192
|
+
readonly cause?: unknown;
|
|
193
|
+
|
|
194
|
+
constructor(
|
|
195
|
+
message: string,
|
|
196
|
+
originChainTxnId: string,
|
|
197
|
+
cause?: unknown,
|
|
198
|
+
) {
|
|
199
|
+
super(message);
|
|
200
|
+
this.name = "TransferError";
|
|
201
|
+
this.originChainTxnId = originChainTxnId;
|
|
202
|
+
this.cause = cause;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
107
206
|
/**
|
|
108
207
|
* Error thrown when the withdraw flow fails *after* the Aptos burn
|
|
109
208
|
* transaction has already been submitted (i.e. during attestation tracking
|