@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
package/dist/index.mjs
CHANGED
|
@@ -3,9 +3,8 @@ import { Network as Network3 } from "@aptos-labs/ts-sdk";
|
|
|
3
3
|
|
|
4
4
|
// src/providers/wormhole/wormhole.ts
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Wormhole,
|
|
6
|
+
routes as routes3,
|
|
7
|
+
Wormhole as Wormhole2,
|
|
9
8
|
wormhole,
|
|
10
9
|
TransferState
|
|
11
10
|
} from "@wormhole-foundation/sdk";
|
|
@@ -34,19 +33,134 @@ var logger = {
|
|
|
34
33
|
}
|
|
35
34
|
};
|
|
36
35
|
|
|
36
|
+
// src/utils/receiptSerialization.ts
|
|
37
|
+
import {
|
|
38
|
+
UniversalAddress
|
|
39
|
+
} from "@wormhole-foundation/sdk";
|
|
40
|
+
function uint8ArrayToBase64(bytes) {
|
|
41
|
+
let binary = "";
|
|
42
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
43
|
+
binary += String.fromCharCode(bytes[i]);
|
|
44
|
+
}
|
|
45
|
+
return btoa(binary);
|
|
46
|
+
}
|
|
47
|
+
function base64ToUint8Array(base64) {
|
|
48
|
+
const binary = atob(base64);
|
|
49
|
+
const bytes = new Uint8Array(binary.length);
|
|
50
|
+
for (let i = 0; i < binary.length; i++) {
|
|
51
|
+
bytes[i] = binary.charCodeAt(i);
|
|
52
|
+
}
|
|
53
|
+
return bytes;
|
|
54
|
+
}
|
|
55
|
+
function serializeReceipt(receipt) {
|
|
56
|
+
return JSON.parse(
|
|
57
|
+
JSON.stringify(receipt, (_key, value) => {
|
|
58
|
+
if (typeof value === "bigint") {
|
|
59
|
+
return { __type: "bigint", value: value.toString() };
|
|
60
|
+
}
|
|
61
|
+
if (value instanceof UniversalAddress) {
|
|
62
|
+
return {
|
|
63
|
+
__type: "UniversalAddress",
|
|
64
|
+
value: uint8ArrayToBase64(value.toUint8Array())
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (value instanceof Uint8Array) {
|
|
68
|
+
return {
|
|
69
|
+
__type: "Uint8Array",
|
|
70
|
+
value: uint8ArrayToBase64(value)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
function deserializeReceipt(obj) {
|
|
78
|
+
function revive(value, key) {
|
|
79
|
+
if (value && typeof value === "object") {
|
|
80
|
+
const objValue = value;
|
|
81
|
+
if ("__type" in objValue) {
|
|
82
|
+
if (objValue.__type === "bigint") {
|
|
83
|
+
return BigInt(objValue.value);
|
|
84
|
+
}
|
|
85
|
+
if (objValue.__type === "UniversalAddress") {
|
|
86
|
+
return new UniversalAddress(
|
|
87
|
+
base64ToUint8Array(objValue.value)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (objValue.__type === "Uint8Array") {
|
|
91
|
+
return base64ToUint8Array(objValue.value);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const addressFields = [
|
|
95
|
+
"sender",
|
|
96
|
+
"recipient",
|
|
97
|
+
"destinationCaller",
|
|
98
|
+
"burnToken",
|
|
99
|
+
"mintRecipient",
|
|
100
|
+
"messageSender"
|
|
101
|
+
];
|
|
102
|
+
if (key && addressFields.includes(key) && "address" in objValue) {
|
|
103
|
+
const addressBytes = revive(objValue.address);
|
|
104
|
+
if (addressBytes instanceof Uint8Array) {
|
|
105
|
+
return new UniversalAddress(addressBytes);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
return value.map((v, i) => revive(v, String(i)));
|
|
110
|
+
}
|
|
111
|
+
const result = {};
|
|
112
|
+
for (const k in objValue) {
|
|
113
|
+
result[k] = revive(objValue[k], k);
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
return revive(obj);
|
|
120
|
+
}
|
|
121
|
+
|
|
37
122
|
// src/providers/wormhole/signers/AptosLocalSigner.ts
|
|
38
123
|
import {
|
|
39
124
|
Aptos,
|
|
40
125
|
AptosConfig
|
|
41
126
|
} from "@aptos-labs/ts-sdk";
|
|
127
|
+
|
|
128
|
+
// src/providers/wormhole/types.ts
|
|
129
|
+
function validateExpireTimestamp(value) {
|
|
130
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`getExpireTimestamp returned an invalid value (${value}). Expected a non-negative integer (epoch seconds).`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
var TransferError = class extends Error {
|
|
137
|
+
constructor(message, originChainTxnId, cause) {
|
|
138
|
+
super(message);
|
|
139
|
+
this.name = "TransferError";
|
|
140
|
+
this.originChainTxnId = originChainTxnId;
|
|
141
|
+
this.cause = cause;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var WithdrawError = class extends Error {
|
|
145
|
+
constructor(message, originChainTxnId, phase, cause) {
|
|
146
|
+
super(message);
|
|
147
|
+
this.name = "WithdrawError";
|
|
148
|
+
this.originChainTxnId = originChainTxnId;
|
|
149
|
+
this.phase = phase;
|
|
150
|
+
this.cause = cause;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// src/providers/wormhole/signers/AptosLocalSigner.ts
|
|
42
155
|
var AptosLocalSigner = class {
|
|
43
|
-
constructor(chain, options, wallet, feePayerAccount,
|
|
156
|
+
constructor(chain, options, wallet, feePayerAccount, crossChainCore, onTransactionSigned) {
|
|
157
|
+
this._claimedTransactionHashes = [];
|
|
44
158
|
this._chain = chain;
|
|
45
159
|
this._options = options;
|
|
46
160
|
this._wallet = wallet;
|
|
47
161
|
this._sponsorAccount = feePayerAccount;
|
|
48
|
-
this.
|
|
49
|
-
this.
|
|
162
|
+
this._crossChainCore = crossChainCore;
|
|
163
|
+
this._onTransactionSigned = onTransactionSigned;
|
|
50
164
|
}
|
|
51
165
|
chain() {
|
|
52
166
|
return this._chain;
|
|
@@ -55,24 +169,27 @@ var AptosLocalSigner = class {
|
|
|
55
169
|
return this._wallet.accountAddress.toString();
|
|
56
170
|
}
|
|
57
171
|
claimedTransactionHashes() {
|
|
58
|
-
return this._claimedTransactionHashes;
|
|
172
|
+
return this._claimedTransactionHashes.join(",");
|
|
59
173
|
}
|
|
60
174
|
async signAndSend(txs) {
|
|
61
175
|
const txHashes = [];
|
|
176
|
+
this._claimedTransactionHashes = [];
|
|
62
177
|
for (const tx of txs) {
|
|
178
|
+
this._onTransactionSigned?.(tx.description, null);
|
|
63
179
|
const txId = await signAndSendTransaction(
|
|
64
180
|
tx,
|
|
65
181
|
this._wallet,
|
|
66
182
|
this._sponsorAccount,
|
|
67
|
-
this.
|
|
183
|
+
this._crossChainCore
|
|
68
184
|
);
|
|
185
|
+
this._onTransactionSigned?.(tx.description, txId);
|
|
69
186
|
txHashes.push(txId);
|
|
70
|
-
this._claimedTransactionHashes
|
|
187
|
+
this._claimedTransactionHashes.push(txId);
|
|
71
188
|
}
|
|
72
189
|
return txHashes;
|
|
73
190
|
}
|
|
74
191
|
};
|
|
75
|
-
async function signAndSendTransaction(request, wallet, sponsorAccount,
|
|
192
|
+
async function signAndSendTransaction(request, wallet, sponsorAccount, crossChainCore) {
|
|
76
193
|
if (!wallet) {
|
|
77
194
|
throw new Error("Wallet is undefined");
|
|
78
195
|
}
|
|
@@ -86,14 +203,20 @@ async function signAndSendTransaction(request, wallet, sponsorAccount, dappNetwo
|
|
|
86
203
|
return a;
|
|
87
204
|
}
|
|
88
205
|
});
|
|
206
|
+
const dappNetwork = crossChainCore._dappConfig.aptosNetwork;
|
|
89
207
|
const aptosConfig = new AptosConfig({
|
|
90
208
|
network: dappNetwork
|
|
91
209
|
});
|
|
92
210
|
const aptos2 = new Aptos(aptosConfig);
|
|
211
|
+
const expireTimestamp = crossChainCore._dappConfig.getExpireTimestamp?.();
|
|
212
|
+
if (typeof expireTimestamp !== "undefined") {
|
|
213
|
+
validateExpireTimestamp(expireTimestamp);
|
|
214
|
+
}
|
|
93
215
|
const txnToSign = await aptos2.transaction.build.simple({
|
|
94
216
|
data: payload,
|
|
95
217
|
sender: wallet.accountAddress.toString(),
|
|
96
|
-
withFeePayer: sponsorAccount ? true : false
|
|
218
|
+
withFeePayer: sponsorAccount ? true : false,
|
|
219
|
+
...typeof expireTimestamp !== "undefined" ? { options: { expireTimestamp } } : {}
|
|
97
220
|
});
|
|
98
221
|
const senderAuthenticator = await aptos2.transaction.sign({
|
|
99
222
|
signer: wallet,
|
|
@@ -117,126 +240,241 @@ async function signAndSendTransaction(request, wallet, sponsorAccount, dappNetwo
|
|
|
117
240
|
return tx.hash;
|
|
118
241
|
}
|
|
119
242
|
|
|
243
|
+
// src/providers/wormhole/signers/AptosSigner.ts
|
|
244
|
+
import {
|
|
245
|
+
AccountAddress,
|
|
246
|
+
Aptos as Aptos2,
|
|
247
|
+
AptosConfig as AptosConfig2,
|
|
248
|
+
Deserializer
|
|
249
|
+
} from "@aptos-labs/ts-sdk";
|
|
250
|
+
import { UserResponseStatus } from "@aptos-labs/wallet-standard";
|
|
251
|
+
import { GasStationClient, GasStationTransactionSubmitter } from "@aptos-labs/gas-station-client";
|
|
252
|
+
async function signAndSendTransaction2(request, wallet, sponsorAccount, dappNetwork, crossChainCore) {
|
|
253
|
+
if (!wallet) {
|
|
254
|
+
throw new Error("wallet.sendTransaction is undefined");
|
|
255
|
+
}
|
|
256
|
+
const payload = request.transaction;
|
|
257
|
+
payload.functionArguments = payload.functionArguments.map((a) => {
|
|
258
|
+
if (a instanceof Uint8Array) {
|
|
259
|
+
return Array.from(a);
|
|
260
|
+
} else if (typeof a === "bigint") {
|
|
261
|
+
return a.toString();
|
|
262
|
+
} else {
|
|
263
|
+
return a;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
let aptosConfig;
|
|
267
|
+
const useGasStation = sponsorAccount && !isAccount(sponsorAccount);
|
|
268
|
+
if (useGasStation) {
|
|
269
|
+
const gasStationClient = new GasStationClient({
|
|
270
|
+
network: dappNetwork,
|
|
271
|
+
apiKey: sponsorAccount[dappNetwork]
|
|
272
|
+
});
|
|
273
|
+
const transactionSubmitter = new GasStationTransactionSubmitter(gasStationClient);
|
|
274
|
+
aptosConfig = new AptosConfig2({
|
|
275
|
+
network: dappNetwork,
|
|
276
|
+
pluginSettings: {
|
|
277
|
+
TRANSACTION_SUBMITTER: transactionSubmitter
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
} else {
|
|
281
|
+
aptosConfig = new AptosConfig2({
|
|
282
|
+
network: dappNetwork
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
const aptos2 = new Aptos2(aptosConfig);
|
|
286
|
+
const functionArguments = extractFunctionArguments(
|
|
287
|
+
payload.functionArguments
|
|
288
|
+
);
|
|
289
|
+
const withdrawFunction = "0x5e2d961f06cd27aa07554a39d55f5ce1e58dff35d803c3529b1cd5c4fa3ab584::withdraw::deposit_for_burn";
|
|
290
|
+
const transactionData = {
|
|
291
|
+
function: withdrawFunction,
|
|
292
|
+
functionArguments
|
|
293
|
+
};
|
|
294
|
+
const expireTimestamp = crossChainCore?._dappConfig?.getExpireTimestamp?.();
|
|
295
|
+
if (typeof expireTimestamp !== "undefined") {
|
|
296
|
+
validateExpireTimestamp(expireTimestamp);
|
|
297
|
+
}
|
|
298
|
+
const txnToSign = await aptos2.transaction.build.simple({
|
|
299
|
+
data: transactionData,
|
|
300
|
+
sender: (await wallet.features["aptos:account"]?.account()).address.toString(),
|
|
301
|
+
withFeePayer: sponsorAccount ? true : false,
|
|
302
|
+
...typeof expireTimestamp !== "undefined" ? { options: { expireTimestamp } } : {}
|
|
303
|
+
});
|
|
304
|
+
const response = await wallet.features["aptos:signTransaction"]?.signTransaction(txnToSign);
|
|
305
|
+
if (response?.status === UserResponseStatus.REJECTED) {
|
|
306
|
+
throw new Error("User has rejected the request");
|
|
307
|
+
}
|
|
308
|
+
const txnToSubmit = {
|
|
309
|
+
transaction: txnToSign,
|
|
310
|
+
senderAuthenticator: response.args
|
|
311
|
+
};
|
|
312
|
+
if (sponsorAccount && isAccount(sponsorAccount)) {
|
|
313
|
+
const feePayerSignerAuthenticator = aptos2.transaction.signAsFeePayer({
|
|
314
|
+
signer: sponsorAccount,
|
|
315
|
+
transaction: txnToSign
|
|
316
|
+
});
|
|
317
|
+
txnToSubmit.feePayerAuthenticator = feePayerSignerAuthenticator;
|
|
318
|
+
}
|
|
319
|
+
const txnSubmitted = await aptos2.transaction.submit.simple(txnToSubmit);
|
|
320
|
+
const tx = await aptos2.waitForTransaction({
|
|
321
|
+
transactionHash: txnSubmitted.hash
|
|
322
|
+
});
|
|
323
|
+
return tx.hash;
|
|
324
|
+
}
|
|
325
|
+
function extractFunctionArguments(functionArguments) {
|
|
326
|
+
const deserializer1 = new Deserializer(functionArguments[0].bcsToBytes());
|
|
327
|
+
const amount = deserializer1.deserializeU64();
|
|
328
|
+
const deserializer2 = new Deserializer(functionArguments[1].bcsToBytes());
|
|
329
|
+
const destination_domain = deserializer2.deserializeU32();
|
|
330
|
+
const mint_recipient = new AccountAddress(functionArguments[2].bcsToBytes());
|
|
331
|
+
const burn_token = new AccountAddress(functionArguments[3].bcsToBytes());
|
|
332
|
+
return [amount, destination_domain, mint_recipient, burn_token];
|
|
333
|
+
}
|
|
334
|
+
function isAccount(obj) {
|
|
335
|
+
return "accountAddress" in obj;
|
|
336
|
+
}
|
|
337
|
+
|
|
120
338
|
// src/providers/wormhole/signers/SolanaSigner.ts
|
|
339
|
+
import {
|
|
340
|
+
Transaction as Transaction2
|
|
341
|
+
} from "@solana/web3.js";
|
|
342
|
+
import { Connection as Connection2 } from "@solana/web3.js";
|
|
343
|
+
import { SolanaDerivedWallet } from "@aptos-labs/derived-wallet-solana";
|
|
344
|
+
|
|
345
|
+
// src/providers/wormhole/signers/solanaUtils.ts
|
|
121
346
|
import {
|
|
122
347
|
ComputeBudgetProgram,
|
|
123
348
|
LAMPORTS_PER_SOL
|
|
124
349
|
} from "@solana/web3.js";
|
|
125
|
-
import { Transaction } from "@solana/web3.js";
|
|
126
350
|
import {
|
|
127
351
|
determinePriorityFee,
|
|
128
352
|
determinePriorityFeeTritonOne
|
|
129
353
|
} from "@wormhole-foundation/sdk-solana";
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
async function signAndSendTransaction2(request, wallet, options, crossChainCore) {
|
|
133
|
-
if (!wallet || !(wallet instanceof SolanaDerivedWallet)) {
|
|
134
|
-
throw new Error("Invalid wallet type or missing Solana wallet").message;
|
|
135
|
-
}
|
|
136
|
-
const commitment = options?.commitment ?? "finalized";
|
|
137
|
-
const connection = new Connection(
|
|
138
|
-
crossChainCore?._dappConfig?.solanaConfig?.rpc ?? crossChainCore?.CHAINS["Solana"]?.defaultRpc ?? "https://api.devnet.solana.com"
|
|
139
|
-
// Last resort fallback
|
|
140
|
-
);
|
|
141
|
-
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
|
|
142
|
-
const unsignedTx = await setPriorityFeeInstructions(
|
|
143
|
-
connection,
|
|
144
|
-
blockhash,
|
|
145
|
-
lastValidBlockHeight,
|
|
146
|
-
request,
|
|
147
|
-
crossChainCore
|
|
148
|
-
);
|
|
149
|
-
let confirmTransactionPromise = null;
|
|
150
|
-
let confirmedTx = null;
|
|
151
|
-
let txSendAttempts = 1;
|
|
152
|
-
let signature = "";
|
|
153
|
-
if (!wallet.solanaWallet.signTransaction) {
|
|
154
|
-
throw new Error("Wallet does not support signing transactions").message;
|
|
155
|
-
}
|
|
156
|
-
const tx = await wallet.solanaWallet.signTransaction(unsignedTx);
|
|
157
|
-
if (!tx) throw new Error("Failed to sign transaction").message;
|
|
158
|
-
if (request.transaction.signers && tx instanceof Transaction) {
|
|
159
|
-
tx.partialSign(...request.transaction.signers);
|
|
160
|
-
}
|
|
161
|
-
const serializedTx = tx.serialize();
|
|
354
|
+
async function sendAndConfirmTransaction(serializedTx, blockhash, lastValidBlockHeight, config) {
|
|
355
|
+
const { connection, commitment, retryIntervalMs = 5e3, verbose = false } = config;
|
|
162
356
|
const sendOptions = {
|
|
163
357
|
skipPreflight: true,
|
|
164
358
|
maxRetries: 0,
|
|
165
|
-
|
|
166
|
-
// See PR and linked issue for why setting this matters: https://github.com/anza-xyz/agave/pull/483
|
|
359
|
+
preflightCommitment: commitment
|
|
167
360
|
};
|
|
168
|
-
signature = await connection.sendRawTransaction(serializedTx, sendOptions);
|
|
169
|
-
confirmTransactionPromise = connection.confirmTransaction(
|
|
170
|
-
{
|
|
171
|
-
signature,
|
|
172
|
-
blockhash,
|
|
173
|
-
lastValidBlockHeight
|
|
174
|
-
},
|
|
361
|
+
const signature = await connection.sendRawTransaction(serializedTx, sendOptions);
|
|
362
|
+
const confirmTransactionPromise = connection.confirmTransaction(
|
|
363
|
+
{ signature, blockhash, lastValidBlockHeight },
|
|
175
364
|
commitment
|
|
176
365
|
);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
await connection.sendRawTransaction(serializedTx, sendOptions);
|
|
195
|
-
} catch (e) {
|
|
196
|
-
console.error("Failed to resend transaction:", e);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (confirmedTx.value.err) {
|
|
200
|
-
let errorMessage = `Transaction failed: ${confirmedTx.value.err}`;
|
|
201
|
-
if (typeof confirmedTx.value.err === "object") {
|
|
366
|
+
let confirmedTx = null;
|
|
367
|
+
let txSendAttempts = 1;
|
|
368
|
+
try {
|
|
369
|
+
while (!confirmedTx) {
|
|
370
|
+
confirmedTx = await Promise.race([
|
|
371
|
+
confirmTransactionPromise,
|
|
372
|
+
new Promise(
|
|
373
|
+
(resolve) => setTimeout(() => resolve(null), retryIntervalMs)
|
|
374
|
+
)
|
|
375
|
+
]);
|
|
376
|
+
if (confirmedTx) break;
|
|
377
|
+
if (verbose) {
|
|
378
|
+
console.log(
|
|
379
|
+
`Tx not confirmed after ${retryIntervalMs * txSendAttempts++}ms, resending`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
202
382
|
try {
|
|
203
|
-
|
|
204
|
-
confirmedTx.value.err,
|
|
205
|
-
(_key, value) => typeof value === "bigint" ? value.toString() : value
|
|
206
|
-
// Handle bigint props
|
|
207
|
-
)}`;
|
|
383
|
+
await connection.sendRawTransaction(serializedTx, sendOptions);
|
|
208
384
|
} catch (e) {
|
|
209
|
-
|
|
385
|
+
if (verbose) {
|
|
386
|
+
console.error("Failed to resend transaction:", e);
|
|
387
|
+
}
|
|
210
388
|
}
|
|
211
389
|
}
|
|
212
|
-
|
|
390
|
+
} catch (e) {
|
|
391
|
+
const message = e instanceof Error ? e.message.toLowerCase() : "";
|
|
392
|
+
if (message.includes("block height exceeded") || message.includes("blockheightexceeded")) {
|
|
393
|
+
if (verbose) {
|
|
394
|
+
console.warn(
|
|
395
|
+
"Block height exceeded but tx was already sent, returning signature:",
|
|
396
|
+
signature
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
return signature;
|
|
400
|
+
}
|
|
401
|
+
throw e;
|
|
402
|
+
}
|
|
403
|
+
if (confirmedTx.value.err) {
|
|
404
|
+
const errorMessage = formatTransactionError(confirmedTx.value.err);
|
|
405
|
+
throw new Error(errorMessage);
|
|
213
406
|
}
|
|
214
407
|
return signature;
|
|
215
408
|
}
|
|
216
|
-
|
|
217
|
-
|
|
409
|
+
function formatTransactionError(err) {
|
|
410
|
+
if (typeof err === "object" && err !== null) {
|
|
411
|
+
try {
|
|
412
|
+
return `Transaction failed: ${JSON.stringify(
|
|
413
|
+
err,
|
|
414
|
+
(_key, value) => typeof value === "bigint" ? value.toString() : value
|
|
415
|
+
)}`;
|
|
416
|
+
} catch {
|
|
417
|
+
return "Transaction failed: Unknown error";
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return `Transaction failed: ${err}`;
|
|
421
|
+
}
|
|
422
|
+
async function addPriorityFeeInstructions(connection, transaction, priorityFeeConfig, verbose = false) {
|
|
218
423
|
const computeBudgetIxFilter = (ix) => ix.programId.toString() !== "ComputeBudget111111111111111111111111111111";
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
424
|
+
transaction.instructions = transaction.instructions.filter(computeBudgetIxFilter);
|
|
425
|
+
const instructions = await createPriorityFeeInstructions(
|
|
426
|
+
connection,
|
|
427
|
+
transaction,
|
|
428
|
+
priorityFeeConfig,
|
|
429
|
+
verbose
|
|
223
430
|
);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
431
|
+
transaction.add(...instructions);
|
|
432
|
+
return transaction;
|
|
433
|
+
}
|
|
434
|
+
async function createPriorityFeeInstructions(connection, transaction, priorityFeeConfig, verbose = false) {
|
|
435
|
+
const unitsUsed = await simulateAndGetComputeUnits(connection, transaction);
|
|
436
|
+
const unitBudget = Math.floor(unitsUsed * 1.2);
|
|
437
|
+
const instructions = [];
|
|
438
|
+
instructions.push(
|
|
439
|
+
ComputeBudgetProgram.setComputeUnitLimit({
|
|
440
|
+
units: unitBudget
|
|
441
|
+
})
|
|
230
442
|
);
|
|
231
|
-
|
|
443
|
+
const {
|
|
444
|
+
percentile = 0.9,
|
|
445
|
+
percentileMultiple = 1,
|
|
446
|
+
min = 1e5,
|
|
447
|
+
max = 1e8
|
|
448
|
+
} = priorityFeeConfig ?? {};
|
|
449
|
+
const rpcProvider = determineRpcProvider(connection.rpcEndpoint);
|
|
450
|
+
const { fee, methodUsed } = await calculatePriorityFee(
|
|
451
|
+
connection,
|
|
452
|
+
transaction,
|
|
453
|
+
rpcProvider,
|
|
454
|
+
{ percentile, percentileMultiple, min, max }
|
|
455
|
+
);
|
|
456
|
+
if (verbose) {
|
|
457
|
+
const maxFeeInSol = fee / 1e6 / LAMPORTS_PER_SOL * unitBudget;
|
|
458
|
+
console.table({
|
|
459
|
+
"RPC Provider": rpcProvider,
|
|
460
|
+
"Method used": methodUsed,
|
|
461
|
+
"Percentile used": percentile,
|
|
462
|
+
"Multiple used": percentileMultiple,
|
|
463
|
+
"Compute budget": unitBudget,
|
|
464
|
+
"Priority fee": fee,
|
|
465
|
+
"Max fee in SOL": maxFeeInSol
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
instructions.push(
|
|
469
|
+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: fee })
|
|
470
|
+
);
|
|
471
|
+
return instructions;
|
|
232
472
|
}
|
|
233
|
-
async function
|
|
473
|
+
async function simulateAndGetComputeUnits(connection, transaction) {
|
|
234
474
|
let unitsUsed = 2e5;
|
|
235
475
|
let simulationAttempts = 0;
|
|
236
476
|
simulationLoop: while (true) {
|
|
237
|
-
const response = await connection.simulateTransaction(
|
|
238
|
-
transaction
|
|
239
|
-
);
|
|
477
|
+
const response = await connection.simulateTransaction(transaction);
|
|
240
478
|
if (response.value.err) {
|
|
241
479
|
if (checkKnownSimulationError(response.value)) {
|
|
242
480
|
if (simulationAttempts < 5) {
|
|
@@ -253,7 +491,7 @@ async function createPriorityFeeInstructions(connection, transaction, crossChain
|
|
|
253
491
|
`Simulation failed: ${JSON.stringify(response.value.err)}
|
|
254
492
|
Logs:
|
|
255
493
|
${(response.value.logs || []).join("\n ")}`
|
|
256
|
-
)
|
|
494
|
+
);
|
|
257
495
|
} else {
|
|
258
496
|
if (response.value.unitsConsumed) {
|
|
259
497
|
unitsUsed = response.value.unitsConsumed;
|
|
@@ -261,41 +499,13 @@ ${(response.value.logs || []).join("\n ")}`
|
|
|
261
499
|
break;
|
|
262
500
|
}
|
|
263
501
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
units: unitBudget
|
|
270
|
-
})
|
|
271
|
-
);
|
|
272
|
-
const {
|
|
273
|
-
percentile = 0.9,
|
|
274
|
-
percentileMultiple = 1,
|
|
275
|
-
min = 1e5,
|
|
276
|
-
max = 1e8
|
|
277
|
-
} = crossChainCore?._dappConfig?.solanaConfig?.priorityFeeConfig ?? {};
|
|
278
|
-
const calculateFee = async (rpcProvider2) => {
|
|
279
|
-
if (rpcProvider2 === "triton") {
|
|
280
|
-
try {
|
|
281
|
-
const fee2 = await determinePriorityFeeTritonOne(
|
|
282
|
-
connection,
|
|
283
|
-
transaction,
|
|
284
|
-
percentile,
|
|
285
|
-
percentileMultiple,
|
|
286
|
-
min,
|
|
287
|
-
max
|
|
288
|
-
);
|
|
289
|
-
return {
|
|
290
|
-
fee: fee2,
|
|
291
|
-
methodUsed: "triton"
|
|
292
|
-
};
|
|
293
|
-
} catch (e) {
|
|
294
|
-
console.warn(`Failed to determine priority fee using Triton RPC:`, e);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
502
|
+
return unitsUsed;
|
|
503
|
+
}
|
|
504
|
+
async function calculatePriorityFee(connection, transaction, rpcProvider, config) {
|
|
505
|
+
const { percentile, percentileMultiple, min, max } = config;
|
|
506
|
+
if (rpcProvider === "triton") {
|
|
297
507
|
try {
|
|
298
|
-
const
|
|
508
|
+
const fee = await determinePriorityFeeTritonOne(
|
|
299
509
|
connection,
|
|
300
510
|
transaction,
|
|
301
511
|
percentile,
|
|
@@ -303,37 +513,25 @@ ${(response.value.logs || []).join("\n ")}`
|
|
|
303
513
|
min,
|
|
304
514
|
max
|
|
305
515
|
);
|
|
306
|
-
return {
|
|
307
|
-
fee: fee2,
|
|
308
|
-
methodUsed: "default"
|
|
309
|
-
};
|
|
516
|
+
return { fee, methodUsed: "triton" };
|
|
310
517
|
} catch (e) {
|
|
311
518
|
console.warn(`Failed to determine priority fee using Triton RPC:`, e);
|
|
312
|
-
return {
|
|
313
|
-
fee: min,
|
|
314
|
-
methodUsed: "minimum"
|
|
315
|
-
};
|
|
316
519
|
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
});
|
|
333
|
-
instructions.push(
|
|
334
|
-
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: fee })
|
|
335
|
-
);
|
|
336
|
-
return instructions;
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
const fee = await determinePriorityFee(
|
|
523
|
+
connection,
|
|
524
|
+
transaction,
|
|
525
|
+
percentile,
|
|
526
|
+
percentileMultiple,
|
|
527
|
+
min,
|
|
528
|
+
max
|
|
529
|
+
);
|
|
530
|
+
return { fee, methodUsed: "default" };
|
|
531
|
+
} catch (e) {
|
|
532
|
+
console.warn(`Failed to determine priority fee:`, e);
|
|
533
|
+
return { fee: min, methodUsed: "minimum" };
|
|
534
|
+
}
|
|
337
535
|
}
|
|
338
536
|
function checkKnownSimulationError(response) {
|
|
339
537
|
const errors = {};
|
|
@@ -350,159 +548,148 @@ function checkKnownSimulationError(response) {
|
|
|
350
548
|
}
|
|
351
549
|
}
|
|
352
550
|
}
|
|
353
|
-
if (
|
|
551
|
+
if (Object.keys(errors).length === 0) {
|
|
354
552
|
return false;
|
|
355
553
|
}
|
|
356
554
|
console.table(errors);
|
|
357
555
|
return true;
|
|
358
556
|
}
|
|
359
|
-
|
|
360
|
-
return
|
|
557
|
+
function isHostOrSubdomainOf(hostname, base) {
|
|
558
|
+
return hostname === base || hostname.endsWith(`.${base}`);
|
|
361
559
|
}
|
|
362
|
-
var isEmptyObject = (value) => {
|
|
363
|
-
if (value === null || value === void 0) {
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
|
-
for (const key in value) {
|
|
367
|
-
if (value.hasOwnProperty.call(value, key)) {
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
return true;
|
|
372
|
-
};
|
|
373
560
|
function determineRpcProvider(endpoint) {
|
|
374
561
|
try {
|
|
375
562
|
const url = new URL(endpoint);
|
|
376
563
|
const hostname = url.hostname;
|
|
377
|
-
if (hostname
|
|
564
|
+
if (isHostOrSubdomainOf(hostname, "rpcpool.com") || isHostOrSubdomainOf(hostname, "triton.one")) {
|
|
378
565
|
return "triton";
|
|
379
|
-
} else if (hostname
|
|
566
|
+
} else if (isHostOrSubdomainOf(hostname, "helius-rpc.com") || isHostOrSubdomainOf(hostname, "helius.xyz")) {
|
|
380
567
|
return "helius";
|
|
381
|
-
} else if (hostname
|
|
568
|
+
} else if (isHostOrSubdomainOf(hostname, "ankr.com")) {
|
|
382
569
|
return "ankr";
|
|
383
570
|
} else {
|
|
384
571
|
return "unknown";
|
|
385
572
|
}
|
|
386
|
-
} catch
|
|
387
|
-
return "unknown";
|
|
388
|
-
}
|
|
573
|
+
} catch {
|
|
574
|
+
return "unknown";
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
async function sleep(timeout) {
|
|
578
|
+
return new Promise((resolve) => setTimeout(resolve, timeout));
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/providers/wormhole/signers/SolanaSigner.ts
|
|
582
|
+
async function signAndSendTransaction3(request, wallet, options, crossChainCore) {
|
|
583
|
+
if (!wallet || !(wallet instanceof SolanaDerivedWallet)) {
|
|
584
|
+
throw new Error("Invalid wallet type or missing Solana wallet");
|
|
585
|
+
}
|
|
586
|
+
const commitment = options?.commitment ?? crossChainCore?._dappConfig?.solanaConfig?.commitment ?? "finalized";
|
|
587
|
+
const connection = new Connection2(
|
|
588
|
+
crossChainCore?._dappConfig?.solanaConfig?.rpc ?? crossChainCore?.CHAINS["Solana"]?.defaultRpc ?? "https://api.devnet.solana.com"
|
|
589
|
+
// Last resort fallback
|
|
590
|
+
);
|
|
591
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
|
|
592
|
+
const unsignedTx = await setPriorityFeeInstructions(
|
|
593
|
+
connection,
|
|
594
|
+
blockhash,
|
|
595
|
+
lastValidBlockHeight,
|
|
596
|
+
request,
|
|
597
|
+
crossChainCore?._dappConfig?.solanaConfig?.priorityFeeConfig
|
|
598
|
+
);
|
|
599
|
+
if (!wallet.solanaWallet.signTransaction) {
|
|
600
|
+
throw new Error("Wallet does not support signing transactions");
|
|
601
|
+
}
|
|
602
|
+
const tx = await wallet.solanaWallet.signTransaction(unsignedTx);
|
|
603
|
+
if (!tx) throw new Error("Failed to sign transaction");
|
|
604
|
+
if (request.transaction.signers && tx instanceof Transaction2) {
|
|
605
|
+
tx.partialSign(...request.transaction.signers);
|
|
606
|
+
}
|
|
607
|
+
const serializedTx = tx.serialize();
|
|
608
|
+
const signature = await sendAndConfirmTransaction(
|
|
609
|
+
serializedTx,
|
|
610
|
+
blockhash,
|
|
611
|
+
lastValidBlockHeight,
|
|
612
|
+
{
|
|
613
|
+
connection,
|
|
614
|
+
commitment,
|
|
615
|
+
retryIntervalMs: 5e3,
|
|
616
|
+
verbose: false
|
|
617
|
+
}
|
|
618
|
+
);
|
|
619
|
+
return signature;
|
|
620
|
+
}
|
|
621
|
+
async function setPriorityFeeInstructions(connection, blockhash, lastValidBlockHeight, request, priorityFeeConfig) {
|
|
622
|
+
const unsignedTx = request.transaction.transaction;
|
|
623
|
+
unsignedTx.recentBlockhash = blockhash;
|
|
624
|
+
unsignedTx.lastValidBlockHeight = lastValidBlockHeight;
|
|
625
|
+
await addPriorityFeeInstructions(
|
|
626
|
+
connection,
|
|
627
|
+
unsignedTx,
|
|
628
|
+
priorityFeeConfig,
|
|
629
|
+
false
|
|
630
|
+
);
|
|
631
|
+
return unsignedTx;
|
|
389
632
|
}
|
|
390
633
|
|
|
391
634
|
// src/providers/wormhole/signers/EthereumSigner.ts
|
|
392
635
|
import { ethers, getBigInt } from "ethers";
|
|
393
|
-
async function
|
|
636
|
+
async function signAndSendTransaction4(request, wallet, chainName) {
|
|
394
637
|
if (!wallet) {
|
|
395
|
-
throw new Error("wallet.sendTransaction is undefined")
|
|
638
|
+
throw new Error("wallet.sendTransaction is undefined");
|
|
396
639
|
}
|
|
397
640
|
const chainId = await wallet.eip1193Provider.request({
|
|
398
641
|
method: "eth_chainId"
|
|
399
642
|
});
|
|
400
643
|
const actualChainId = parseInt(chainId, 16);
|
|
401
|
-
if (!actualChainId)
|
|
402
|
-
throw new Error("No signer found for chain" + chainName).message;
|
|
644
|
+
if (!actualChainId) throw new Error("No signer found for chain" + chainName);
|
|
403
645
|
const expectedChainId = request.transaction.chainId ? getBigInt(request.transaction.chainId) : void 0;
|
|
404
646
|
if (!actualChainId || !expectedChainId || BigInt(actualChainId) !== expectedChainId) {
|
|
405
647
|
throw new Error(
|
|
406
648
|
`Signer is not connected to the right chain. Expected ${expectedChainId}, got ${actualChainId}`
|
|
407
|
-
)
|
|
649
|
+
);
|
|
408
650
|
}
|
|
409
651
|
const provider = new ethers.BrowserProvider(
|
|
410
652
|
wallet.eip1193Provider
|
|
411
653
|
);
|
|
412
654
|
const signer = await provider.getSigner();
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
AptosConfig as AptosConfig2,
|
|
423
|
-
Deserializer
|
|
424
|
-
} from "@aptos-labs/ts-sdk";
|
|
425
|
-
import { UserResponseStatus } from "@aptos-labs/wallet-standard";
|
|
426
|
-
import { GasStationClient, GasStationTransactionSubmitter } from "@aptos-labs/gas-station-client";
|
|
427
|
-
async function signAndSendTransaction4(request, wallet, sponsorAccount, dappNetwork) {
|
|
428
|
-
if (!wallet) {
|
|
429
|
-
throw new Error("wallet.sendTransaction is undefined").message;
|
|
430
|
-
}
|
|
431
|
-
const payload = request.transaction;
|
|
432
|
-
payload.functionArguments = payload.functionArguments.map((a) => {
|
|
433
|
-
if (a instanceof Uint8Array) {
|
|
434
|
-
return Array.from(a);
|
|
435
|
-
} else if (typeof a === "bigint") {
|
|
436
|
-
return a.toString();
|
|
437
|
-
} else {
|
|
438
|
-
return a;
|
|
655
|
+
let response;
|
|
656
|
+
try {
|
|
657
|
+
response = await signer.sendTransaction(request.transaction);
|
|
658
|
+
} catch (e) {
|
|
659
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
660
|
+
const hashMatch = message.match(/"hash":\s*"(0x[a-fA-F0-9]{64})"/);
|
|
661
|
+
if (hashMatch) {
|
|
662
|
+
console.warn("Extracted EVM tx hash from error:", hashMatch[1]);
|
|
663
|
+
return hashMatch[1];
|
|
439
664
|
}
|
|
440
|
-
|
|
441
|
-
let aptosConfig;
|
|
442
|
-
const useGasStation = sponsorAccount && !isAccount(sponsorAccount);
|
|
443
|
-
if (useGasStation) {
|
|
444
|
-
const gasStationClient = new GasStationClient({
|
|
445
|
-
network: dappNetwork,
|
|
446
|
-
apiKey: sponsorAccount[dappNetwork]
|
|
447
|
-
});
|
|
448
|
-
const transactionSubmitter = new GasStationTransactionSubmitter(gasStationClient);
|
|
449
|
-
aptosConfig = new AptosConfig2({
|
|
450
|
-
network: dappNetwork,
|
|
451
|
-
pluginSettings: {
|
|
452
|
-
TRANSACTION_SUBMITTER: transactionSubmitter
|
|
453
|
-
}
|
|
454
|
-
});
|
|
455
|
-
} else {
|
|
456
|
-
aptosConfig = new AptosConfig2({
|
|
457
|
-
network: dappNetwork
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
const aptos2 = new Aptos2(aptosConfig);
|
|
461
|
-
const functionArguments = extractFunctionArguments(
|
|
462
|
-
payload.functionArguments
|
|
463
|
-
);
|
|
464
|
-
const withdrawFunction = "0x5e2d961f06cd27aa07554a39d55f5ce1e58dff35d803c3529b1cd5c4fa3ab584::withdraw::deposit_for_burn";
|
|
465
|
-
const transactionData = {
|
|
466
|
-
function: withdrawFunction,
|
|
467
|
-
functionArguments
|
|
468
|
-
};
|
|
469
|
-
const txnToSign = await aptos2.transaction.build.simple({
|
|
470
|
-
data: transactionData,
|
|
471
|
-
sender: (await wallet.features["aptos:account"]?.account()).address.toString(),
|
|
472
|
-
withFeePayer: sponsorAccount ? true : false
|
|
473
|
-
});
|
|
474
|
-
const response = await wallet.features["aptos:signTransaction"]?.signTransaction(txnToSign);
|
|
475
|
-
if (response?.status === UserResponseStatus.REJECTED) {
|
|
476
|
-
throw new Error("User has rejected the request");
|
|
665
|
+
throw e;
|
|
477
666
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
667
|
+
try {
|
|
668
|
+
const receipt = await response.wait();
|
|
669
|
+
return receipt?.hash || response.hash || "";
|
|
670
|
+
} catch (e) {
|
|
671
|
+
if (e?.code === "TRANSACTION_REPLACED") {
|
|
672
|
+
if (e.reason === "repriced") {
|
|
673
|
+
const replacementHash = e.receipt?.hash || e.replacement?.hash;
|
|
674
|
+
if (replacementHash) {
|
|
675
|
+
console.warn(
|
|
676
|
+
"EVM transaction was repriced. Using replacement hash:",
|
|
677
|
+
replacementHash
|
|
678
|
+
);
|
|
679
|
+
return replacementHash;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
throw e;
|
|
683
|
+
}
|
|
684
|
+
if (response.hash) {
|
|
685
|
+
console.warn(
|
|
686
|
+
"EVM transaction wait failed but tx was submitted:",
|
|
687
|
+
response.hash
|
|
688
|
+
);
|
|
689
|
+
return response.hash;
|
|
690
|
+
}
|
|
691
|
+
throw e;
|
|
488
692
|
}
|
|
489
|
-
const txnSubmitted = await aptos2.transaction.submit.simple(txnToSubmit);
|
|
490
|
-
const tx = await aptos2.waitForTransaction({
|
|
491
|
-
transactionHash: txnSubmitted.hash
|
|
492
|
-
});
|
|
493
|
-
return tx.hash;
|
|
494
|
-
}
|
|
495
|
-
function extractFunctionArguments(functionArguments) {
|
|
496
|
-
const deserializer1 = new Deserializer(functionArguments[0].bcsToBytes());
|
|
497
|
-
const amount = deserializer1.deserializeU64();
|
|
498
|
-
const deserializer2 = new Deserializer(functionArguments[1].bcsToBytes());
|
|
499
|
-
const destination_domain = deserializer2.deserializeU32();
|
|
500
|
-
const mint_recipient = new AccountAddress(functionArguments[2].bcsToBytes());
|
|
501
|
-
const burn_token = new AccountAddress(functionArguments[3].bcsToBytes());
|
|
502
|
-
return [amount, destination_domain, mint_recipient, burn_token];
|
|
503
|
-
}
|
|
504
|
-
function isAccount(obj) {
|
|
505
|
-
return "accountAddress" in obj;
|
|
506
693
|
}
|
|
507
694
|
|
|
508
695
|
// src/providers/wormhole/signers/SuiSigner.ts
|
|
@@ -525,14 +712,16 @@ async function signAndSendTransaction5(request, wallet) {
|
|
|
525
712
|
|
|
526
713
|
// src/providers/wormhole/signers/Signer.ts
|
|
527
714
|
var Signer = class {
|
|
528
|
-
constructor(chain, address, options, wallet, crossChainCore, sponsorAccount) {
|
|
715
|
+
constructor(chain, address, options, wallet, crossChainCore, sponsorAccount, onTransactionSigned, trackAsSourceChain = true) {
|
|
716
|
+
this._claimedTransactionHashes = [];
|
|
529
717
|
this._chain = chain;
|
|
530
718
|
this._address = address;
|
|
531
719
|
this._options = options;
|
|
532
720
|
this._wallet = wallet;
|
|
533
721
|
this._crossChainCore = crossChainCore;
|
|
534
722
|
this._sponsorAccount = sponsorAccount;
|
|
535
|
-
this.
|
|
723
|
+
this._onTransactionSigned = onTransactionSigned;
|
|
724
|
+
this._trackAsSourceChain = trackAsSourceChain;
|
|
536
725
|
}
|
|
537
726
|
chain() {
|
|
538
727
|
return this._chain.key;
|
|
@@ -541,11 +730,13 @@ var Signer = class {
|
|
|
541
730
|
return this._address;
|
|
542
731
|
}
|
|
543
732
|
claimedTransactionHashes() {
|
|
544
|
-
return this._claimedTransactionHashes;
|
|
733
|
+
return this._claimedTransactionHashes.join(",");
|
|
545
734
|
}
|
|
546
735
|
async signAndSend(txs) {
|
|
547
736
|
const txHashes = [];
|
|
737
|
+
this._claimedTransactionHashes = [];
|
|
548
738
|
for (const tx of txs) {
|
|
739
|
+
this._onTransactionSigned?.(tx.description, null);
|
|
549
740
|
const txId = await signAndSendTransaction6(
|
|
550
741
|
this._chain,
|
|
551
742
|
tx,
|
|
@@ -554,8 +745,12 @@ var Signer = class {
|
|
|
554
745
|
this._crossChainCore,
|
|
555
746
|
this._sponsorAccount
|
|
556
747
|
);
|
|
748
|
+
if (this._trackAsSourceChain) {
|
|
749
|
+
this._crossChainCore._lastSourceChainTxId = txId;
|
|
750
|
+
}
|
|
751
|
+
this._onTransactionSigned?.(tx.description, txId);
|
|
557
752
|
txHashes.push(txId);
|
|
558
|
-
this._claimedTransactionHashes
|
|
753
|
+
this._claimedTransactionHashes.push(txId);
|
|
559
754
|
}
|
|
560
755
|
return txHashes;
|
|
561
756
|
}
|
|
@@ -566,7 +761,7 @@ var signAndSendTransaction6 = async (chain, request, wallet, options = {}, cross
|
|
|
566
761
|
}
|
|
567
762
|
const dappNetwork = crossChainCore._dappConfig.aptosNetwork;
|
|
568
763
|
if (chain.context === "Solana") {
|
|
569
|
-
const signature = await
|
|
764
|
+
const signature = await signAndSendTransaction3(
|
|
570
765
|
request,
|
|
571
766
|
wallet,
|
|
572
767
|
options,
|
|
@@ -574,11 +769,10 @@ var signAndSendTransaction6 = async (chain, request, wallet, options = {}, cross
|
|
|
574
769
|
);
|
|
575
770
|
return signature;
|
|
576
771
|
} else if (chain.context === "Ethereum") {
|
|
577
|
-
const tx = await
|
|
772
|
+
const tx = await signAndSendTransaction4(
|
|
578
773
|
request,
|
|
579
774
|
wallet,
|
|
580
|
-
chain.displayName
|
|
581
|
-
options
|
|
775
|
+
chain.displayName
|
|
582
776
|
);
|
|
583
777
|
return tx;
|
|
584
778
|
} else if (chain.context === "Sui") {
|
|
@@ -588,11 +782,12 @@ var signAndSendTransaction6 = async (chain, request, wallet, options = {}, cross
|
|
|
588
782
|
);
|
|
589
783
|
return tx;
|
|
590
784
|
} else if (chain.context === "Aptos") {
|
|
591
|
-
const tx = await
|
|
785
|
+
const tx = await signAndSendTransaction2(
|
|
592
786
|
request,
|
|
593
787
|
wallet,
|
|
594
788
|
sponsorAccount,
|
|
595
|
-
dappNetwork
|
|
789
|
+
dappNetwork,
|
|
790
|
+
crossChainCore
|
|
596
791
|
);
|
|
597
792
|
return tx;
|
|
598
793
|
} else {
|
|
@@ -600,6 +795,38 @@ var signAndSendTransaction6 = async (chain, request, wallet, options = {}, cross
|
|
|
600
795
|
}
|
|
601
796
|
};
|
|
602
797
|
|
|
798
|
+
// src/providers/wormhole/utils.ts
|
|
799
|
+
import {
|
|
800
|
+
chainToPlatform,
|
|
801
|
+
routes as routes2,
|
|
802
|
+
Wormhole
|
|
803
|
+
} from "@wormhole-foundation/sdk";
|
|
804
|
+
async function createCCTPRoute(wh, sourceChain, destChain, tokens) {
|
|
805
|
+
const sourceToken = Wormhole.tokenId(
|
|
806
|
+
sourceChain,
|
|
807
|
+
tokens[sourceChain].tokenId.address
|
|
808
|
+
);
|
|
809
|
+
const destToken = Wormhole.tokenId(
|
|
810
|
+
destChain,
|
|
811
|
+
tokens[destChain].tokenId.address
|
|
812
|
+
);
|
|
813
|
+
const destContext = wh.getPlatform(chainToPlatform(destChain)).getChain(destChain);
|
|
814
|
+
const sourceContext = wh.getPlatform(chainToPlatform(sourceChain)).getChain(sourceChain);
|
|
815
|
+
const request = await routes2.RouteTransferRequest.create(
|
|
816
|
+
wh,
|
|
817
|
+
{ source: sourceToken, destination: destToken },
|
|
818
|
+
sourceContext,
|
|
819
|
+
destContext
|
|
820
|
+
);
|
|
821
|
+
const resolver = wh.resolver([routes2.CCTPRoute]);
|
|
822
|
+
const foundRoutes = await resolver.findRoutes(request);
|
|
823
|
+
const cctpRoute = foundRoutes[0];
|
|
824
|
+
if (!cctpRoute || !routes2.isManual(cctpRoute)) {
|
|
825
|
+
throw new Error("Expected manual CCTP route");
|
|
826
|
+
}
|
|
827
|
+
return { route: cctpRoute, request };
|
|
828
|
+
}
|
|
829
|
+
|
|
603
830
|
// src/providers/wormhole/wormhole.ts
|
|
604
831
|
var WormholeProvider = class {
|
|
605
832
|
constructor(core) {
|
|
@@ -618,12 +845,22 @@ var WormholeProvider = class {
|
|
|
618
845
|
}
|
|
619
846
|
const isMainnet = dappNetwork === Network.MAINNET;
|
|
620
847
|
const platforms = [aptos, solana, evm, sui];
|
|
621
|
-
const
|
|
848
|
+
const dappConfig = this.crossChainCore._dappConfig;
|
|
849
|
+
const chains = this.crossChainCore.CHAINS;
|
|
850
|
+
const solanaRpc = dappConfig?.solanaConfig?.rpc ?? chains["Solana"]?.defaultRpc;
|
|
851
|
+
const suiRpc = dappConfig?.suiConfig?.rpc ?? chains["Sui"]?.defaultRpc;
|
|
852
|
+
const evmChainsConfig = {};
|
|
853
|
+
for (const name of EVM_CHAIN_NAMES) {
|
|
854
|
+
const rpc = dappConfig?.evmConfig?.[name]?.rpc ?? chains[name]?.defaultRpc;
|
|
855
|
+
if (rpc) {
|
|
856
|
+
evmChainsConfig[name] = { rpc };
|
|
857
|
+
}
|
|
858
|
+
}
|
|
622
859
|
const wh = await wormhole(isMainnet ? "Mainnet" : "Testnet", platforms, {
|
|
623
860
|
chains: {
|
|
624
|
-
Solana: {
|
|
625
|
-
|
|
626
|
-
|
|
861
|
+
...solanaRpc ? { Solana: { rpc: solanaRpc } } : {},
|
|
862
|
+
...suiRpc ? { Sui: { rpc: suiRpc } } : {},
|
|
863
|
+
...evmChainsConfig
|
|
627
864
|
}
|
|
628
865
|
});
|
|
629
866
|
this._wormholeContext = wh;
|
|
@@ -632,33 +869,15 @@ var WormholeProvider = class {
|
|
|
632
869
|
if (!this._wormholeContext) {
|
|
633
870
|
throw new Error("Wormhole context not initialized");
|
|
634
871
|
}
|
|
635
|
-
const {
|
|
636
|
-
sourceChain,
|
|
637
|
-
destinationChain
|
|
638
|
-
);
|
|
639
|
-
const destContext = this._wormholeContext.getPlatform(chainToPlatform(destinationChain)).getChain(destinationChain);
|
|
640
|
-
const sourceContext = this._wormholeContext.getPlatform(chainToPlatform(sourceChain)).getChain(sourceChain);
|
|
641
|
-
logger.log("sourceContext", sourceContext);
|
|
642
|
-
logger.log("sourceToken", sourceToken);
|
|
643
|
-
logger.log("destContext", destContext);
|
|
644
|
-
logger.log("destToken", destToken);
|
|
645
|
-
const request = await routes.RouteTransferRequest.create(
|
|
872
|
+
const { route: cctpRoute, request } = await createCCTPRoute(
|
|
646
873
|
this._wormholeContext,
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
},
|
|
651
|
-
sourceContext,
|
|
652
|
-
destContext
|
|
874
|
+
sourceChain,
|
|
875
|
+
destinationChain,
|
|
876
|
+
this.crossChainCore.TOKENS
|
|
653
877
|
);
|
|
654
|
-
const resolver = this._wormholeContext.resolver([
|
|
655
|
-
routes.CCTPRoute
|
|
656
|
-
// manual CCTP
|
|
657
|
-
]);
|
|
658
|
-
const route = await resolver.findRoutes(request);
|
|
659
|
-
const cctpRoute = route[0];
|
|
660
878
|
this.wormholeRoute = cctpRoute;
|
|
661
879
|
this.wormholeRequest = request;
|
|
880
|
+
this.destinationChain = destinationChain;
|
|
662
881
|
return { route: cctpRoute, request };
|
|
663
882
|
}
|
|
664
883
|
async getQuote(input) {
|
|
@@ -680,19 +899,19 @@ var WormholeProvider = class {
|
|
|
680
899
|
const validated = await route.validate(request, transferParams);
|
|
681
900
|
if (!validated.valid) {
|
|
682
901
|
logger.log("invalid", validated.valid);
|
|
683
|
-
throw new Error(`Invalid quote: ${validated.error}`)
|
|
902
|
+
throw new Error(`Invalid quote: ${validated.error}`);
|
|
684
903
|
}
|
|
685
904
|
const quote = await route.quote(request, validated.params);
|
|
686
905
|
if (!quote.success) {
|
|
687
906
|
logger.log("quote failed", quote.success);
|
|
688
|
-
throw new Error(`Invalid quote: ${quote.error}`)
|
|
907
|
+
throw new Error(`Invalid quote: ${quote.error}`);
|
|
689
908
|
}
|
|
690
909
|
this.wormholeQuote = quote;
|
|
691
910
|
logger.log("quote", quote);
|
|
692
911
|
return quote;
|
|
693
912
|
}
|
|
694
913
|
async submitCCTPTransfer(input) {
|
|
695
|
-
const { sourceChain, wallet, destinationAddress } = input;
|
|
914
|
+
const { sourceChain, wallet, destinationAddress, onTransactionSigned } = input;
|
|
696
915
|
if (!this._wormholeContext) {
|
|
697
916
|
await this.setWormholeContext(sourceChain);
|
|
698
917
|
}
|
|
@@ -718,7 +937,9 @@ var WormholeProvider = class {
|
|
|
718
937
|
signerAddress,
|
|
719
938
|
{},
|
|
720
939
|
wallet,
|
|
721
|
-
this.crossChainCore
|
|
940
|
+
this.crossChainCore,
|
|
941
|
+
void 0,
|
|
942
|
+
onTransactionSigned
|
|
722
943
|
);
|
|
723
944
|
logger.log("signer", signer);
|
|
724
945
|
logger.log("wormholeRequest", this.wormholeRequest);
|
|
@@ -727,16 +948,21 @@ var WormholeProvider = class {
|
|
|
727
948
|
this.wormholeRequest,
|
|
728
949
|
signer,
|
|
729
950
|
this.wormholeQuote,
|
|
730
|
-
|
|
951
|
+
Wormhole2.chainAddress("Aptos", destinationAddress.toString())
|
|
731
952
|
);
|
|
732
953
|
const originChainTxnId = "originTxs" in receipt ? receipt.originTxs[receipt.originTxs.length - 1].txid : void 0;
|
|
733
954
|
return { originChainTxnId: originChainTxnId || "", receipt };
|
|
734
955
|
}
|
|
735
956
|
async claimCCTPTransfer(input) {
|
|
736
|
-
let { receipt, mainSigner, sponsorAccount } = input;
|
|
957
|
+
let { receipt, mainSigner, sponsorAccount, onTransactionSigned } = input;
|
|
737
958
|
if (!this.wormholeRoute) {
|
|
738
959
|
throw new Error("Wormhole route not initialized");
|
|
739
960
|
}
|
|
961
|
+
if (sponsorAccount && !isAccount(sponsorAccount)) {
|
|
962
|
+
throw new Error(
|
|
963
|
+
"AptosLocalSigner does not support GasStationApiKey as a sponsor account. Wormhole claim transactions are script-based and cannot be submitted via the gas station. Please provide an Account instance as the sponsor, or omit the sponsor account."
|
|
964
|
+
);
|
|
965
|
+
}
|
|
740
966
|
logger.log("mainSigner", mainSigner.accountAddress.toString());
|
|
741
967
|
let retries = 0;
|
|
742
968
|
const maxRetries = 5;
|
|
@@ -754,9 +980,10 @@ var WormholeProvider = class {
|
|
|
754
980
|
// the account that signs the "claim" transaction
|
|
755
981
|
sponsorAccount,
|
|
756
982
|
// the fee payer account
|
|
757
|
-
this.crossChainCore
|
|
983
|
+
this.crossChainCore,
|
|
984
|
+
onTransactionSigned
|
|
758
985
|
);
|
|
759
|
-
if (
|
|
986
|
+
if (routes3.isManual(this.wormholeRoute)) {
|
|
760
987
|
const circleAttestationReceipt = await this.wormholeRoute.complete(signer, receipt);
|
|
761
988
|
logger.log("Claim receipt: ", circleAttestationReceipt);
|
|
762
989
|
const destinationChainTxnId = signer.claimedTransactionHashes();
|
|
@@ -799,22 +1026,30 @@ var WormholeProvider = class {
|
|
|
799
1026
|
});
|
|
800
1027
|
}
|
|
801
1028
|
let { originChainTxnId, receipt } = await this.submitCCTPTransfer(input);
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
await this.setWormholeContext(sourceChain);
|
|
1029
|
+
try {
|
|
1030
|
+
const { destinationChainTxnId } = await this.claimCCTPTransfer({
|
|
1031
|
+
receipt,
|
|
1032
|
+
mainSigner: input.mainSigner,
|
|
1033
|
+
sponsorAccount: input.sponsorAccount,
|
|
1034
|
+
onTransactionSigned: input.onTransactionSigned
|
|
1035
|
+
});
|
|
1036
|
+
return { originChainTxnId, destinationChainTxnId };
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
throw new TransferError(
|
|
1039
|
+
error?.message ?? "Transfer claim failed after source-chain burn",
|
|
1040
|
+
originChainTxnId,
|
|
1041
|
+
error
|
|
1042
|
+
);
|
|
817
1043
|
}
|
|
1044
|
+
}
|
|
1045
|
+
// --- Split withdraw flow: initiateWithdraw + trackWithdraw + claimWithdraw ---
|
|
1046
|
+
/**
|
|
1047
|
+
* Phase 1: Initiates a withdraw by burning USDC on Aptos.
|
|
1048
|
+
* The user signs the Aptos burn transaction via their wallet.
|
|
1049
|
+
* Returns a receipt that can be tracked and later claimed.
|
|
1050
|
+
*/
|
|
1051
|
+
async initiateWithdraw(input) {
|
|
1052
|
+
const { wallet, destinationAddress, sponsorAccount, onTransactionSigned } = input;
|
|
818
1053
|
if (!this._wormholeContext) {
|
|
819
1054
|
throw new Error("Wormhole context not initialized");
|
|
820
1055
|
}
|
|
@@ -823,69 +1058,49 @@ var WormholeProvider = class {
|
|
|
823
1058
|
}
|
|
824
1059
|
const signer = new Signer(
|
|
825
1060
|
this.getChainConfig("Aptos"),
|
|
826
|
-
(await
|
|
1061
|
+
(await wallet.features["aptos:account"].account()).address.toString(),
|
|
827
1062
|
{},
|
|
828
|
-
|
|
1063
|
+
wallet,
|
|
829
1064
|
this.crossChainCore,
|
|
830
|
-
sponsorAccount
|
|
1065
|
+
sponsorAccount,
|
|
1066
|
+
onTransactionSigned
|
|
831
1067
|
);
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
logger.log(
|
|
836
|
-
"Wormhole.chainAddress",
|
|
837
|
-
Wormhole.chainAddress(sourceChain, input.destinationAddress.toString())
|
|
1068
|
+
const wormholeDestAddress = Wormhole2.chainAddress(
|
|
1069
|
+
this.destinationChain,
|
|
1070
|
+
destinationAddress.toString()
|
|
838
1071
|
);
|
|
839
|
-
|
|
1072
|
+
const receipt = await this.wormholeRoute.initiate(
|
|
840
1073
|
this.wormholeRequest,
|
|
841
1074
|
signer,
|
|
842
1075
|
this.wormholeQuote,
|
|
843
|
-
|
|
1076
|
+
wormholeDestAddress
|
|
844
1077
|
);
|
|
845
|
-
logger.log("receipt", receipt);
|
|
1078
|
+
logger.log("initiateWithdraw receipt", receipt);
|
|
846
1079
|
const originChainTxnId = "originTxs" in receipt ? receipt.originTxs[receipt.originTxs.length - 1].txid : void 0;
|
|
1080
|
+
return { originChainTxnId: originChainTxnId || "", receipt };
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Phase 2: Tracks a withdraw receipt until attestation is ready.
|
|
1084
|
+
* This polls Wormhole and returns once the receipt reaches the Attested state.
|
|
1085
|
+
*/
|
|
1086
|
+
async trackWithdraw(receipt) {
|
|
1087
|
+
if (!this.wormholeRoute) {
|
|
1088
|
+
throw new Error("Wormhole route not initialized");
|
|
1089
|
+
}
|
|
847
1090
|
let retries = 0;
|
|
848
1091
|
const maxRetries = 5;
|
|
849
1092
|
const baseDelay = 1e3;
|
|
850
1093
|
while (retries < maxRetries) {
|
|
851
1094
|
try {
|
|
852
1095
|
for await (receipt of this.wormholeRoute.track(receipt, 120 * 1e3)) {
|
|
853
|
-
if (receipt.state >= TransferState.
|
|
854
|
-
logger.log("
|
|
855
|
-
|
|
856
|
-
const signer2 = new Signer(
|
|
857
|
-
this.getChainConfig(sourceChain),
|
|
858
|
-
destinationAddress.toString(),
|
|
859
|
-
{},
|
|
860
|
-
wallet,
|
|
861
|
-
this.crossChainCore
|
|
862
|
-
);
|
|
863
|
-
if (routes.isManual(this.wormholeRoute)) {
|
|
864
|
-
const circleAttestationReceipt = await this.wormholeRoute.complete(signer2, receipt);
|
|
865
|
-
logger.log("Claim receipt: ", circleAttestationReceipt);
|
|
866
|
-
const destinationChainTxnId = signer2.claimedTransactionHashes();
|
|
867
|
-
return {
|
|
868
|
-
originChainTxnId: originChainTxnId || "",
|
|
869
|
-
destinationChainTxnId
|
|
870
|
-
};
|
|
871
|
-
} else {
|
|
872
|
-
return {
|
|
873
|
-
originChainTxnId: originChainTxnId || "",
|
|
874
|
-
destinationChainTxnId: ""
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
} catch (e) {
|
|
878
|
-
console.error("Failed to claim", e);
|
|
879
|
-
return {
|
|
880
|
-
originChainTxnId: originChainTxnId || "",
|
|
881
|
-
destinationChainTxnId: ""
|
|
882
|
-
};
|
|
883
|
-
}
|
|
1096
|
+
if (receipt.state >= TransferState.Attested) {
|
|
1097
|
+
logger.log("trackWithdraw: receipt attested", receipt);
|
|
1098
|
+
return receipt;
|
|
884
1099
|
}
|
|
885
1100
|
}
|
|
886
1101
|
} catch (e) {
|
|
887
1102
|
console.error(
|
|
888
|
-
`Error tracking
|
|
1103
|
+
`Error tracking withdraw (attempt ${retries + 1} / ${maxRetries}):`,
|
|
889
1104
|
e
|
|
890
1105
|
);
|
|
891
1106
|
const delay = baseDelay * Math.pow(2, retries);
|
|
@@ -893,10 +1108,173 @@ var WormholeProvider = class {
|
|
|
893
1108
|
retries++;
|
|
894
1109
|
}
|
|
895
1110
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1111
|
+
throw new Error("Failed to track withdraw to attested state");
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Phase 3: Claims the withdraw on the destination chain.
|
|
1115
|
+
*
|
|
1116
|
+
* If the destination is Solana and `solanaConfig.serverClaimUrl` is configured
|
|
1117
|
+
* in the dapp config, the SDK automatically POSTs the attested receipt to that
|
|
1118
|
+
* URL — no wallet popup required. The dapp's server endpoint handles signing
|
|
1119
|
+
* and submitting the claim transaction.
|
|
1120
|
+
*
|
|
1121
|
+
* Otherwise falls back to the wallet-based Signer (triggers wallet popup).
|
|
1122
|
+
*/
|
|
1123
|
+
async claimWithdraw(input) {
|
|
1124
|
+
const { claimChain, destinationAddress, receipt } = input;
|
|
1125
|
+
const serverClaimUrl = this.crossChainCore._dappConfig?.solanaConfig?.serverClaimUrl;
|
|
1126
|
+
if (claimChain === "Solana" && serverClaimUrl) {
|
|
1127
|
+
logger.log("claimWithdraw: using server-side claim via", serverClaimUrl);
|
|
1128
|
+
const response = await fetch(serverClaimUrl, {
|
|
1129
|
+
method: "POST",
|
|
1130
|
+
headers: { "Content-Type": "application/json" },
|
|
1131
|
+
body: JSON.stringify({
|
|
1132
|
+
receipt: serializeReceipt(receipt),
|
|
1133
|
+
destinationAddress,
|
|
1134
|
+
claimChain
|
|
1135
|
+
})
|
|
1136
|
+
});
|
|
1137
|
+
if (!response.ok) {
|
|
1138
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1139
|
+
throw new Error(
|
|
1140
|
+
errorData.error || `Server-side claim failed with status ${response.status}`
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
const result = await response.json();
|
|
1144
|
+
return { destinationChainTxnId: result.destinationChainTxnId };
|
|
1145
|
+
}
|
|
1146
|
+
if (!this.wormholeRoute) {
|
|
1147
|
+
throw new Error("Wormhole route not initialized");
|
|
1148
|
+
}
|
|
1149
|
+
if (!input.wallet) {
|
|
1150
|
+
throw new Error(
|
|
1151
|
+
"Wallet is required for claim when serverClaimUrl is not configured"
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
const claimSigner = new Signer(
|
|
1155
|
+
this.getChainConfig(claimChain),
|
|
1156
|
+
destinationAddress,
|
|
1157
|
+
{},
|
|
1158
|
+
input.wallet,
|
|
1159
|
+
this.crossChainCore,
|
|
1160
|
+
void 0,
|
|
1161
|
+
input.onTransactionSigned,
|
|
1162
|
+
false
|
|
1163
|
+
);
|
|
1164
|
+
if (routes3.isManual(this.wormholeRoute)) {
|
|
1165
|
+
const circleAttestationReceipt = await this.wormholeRoute.complete(
|
|
1166
|
+
claimSigner,
|
|
1167
|
+
receipt
|
|
1168
|
+
);
|
|
1169
|
+
logger.log("claimWithdraw receipt:", circleAttestationReceipt);
|
|
1170
|
+
const destinationChainTxnId = claimSigner.claimedTransactionHashes();
|
|
1171
|
+
return { destinationChainTxnId };
|
|
1172
|
+
} else {
|
|
1173
|
+
throw new Error("Automatic route not supported for manual claim");
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Withdraws USDC from Aptos to a destination chain.
|
|
1178
|
+
* Orchestrates all three phases internally:
|
|
1179
|
+
* 1. Initiate — user signs the Aptos burn transaction
|
|
1180
|
+
* 2. Track — wait for Wormhole attestation
|
|
1181
|
+
* 3. Claim — if serverClaimUrl is configured for Solana, delegates to
|
|
1182
|
+
* the server; otherwise uses the wallet-based signer.
|
|
1183
|
+
*
|
|
1184
|
+
* The optional `onPhaseChange` callback lets the dapp update its UI
|
|
1185
|
+
* as the flow progresses.
|
|
1186
|
+
*/
|
|
1187
|
+
async withdraw(input) {
|
|
1188
|
+
const {
|
|
1189
|
+
sourceChain,
|
|
1190
|
+
wallet,
|
|
1191
|
+
destinationAddress,
|
|
1192
|
+
sponsorAccount,
|
|
1193
|
+
onPhaseChange,
|
|
1194
|
+
onTransactionSigned
|
|
1195
|
+
} = input;
|
|
1196
|
+
onPhaseChange?.("initiating");
|
|
1197
|
+
const { originChainTxnId, receipt } = await this.initiateWithdraw({
|
|
1198
|
+
wallet,
|
|
1199
|
+
destinationAddress,
|
|
1200
|
+
sponsorAccount,
|
|
1201
|
+
onTransactionSigned
|
|
1202
|
+
});
|
|
1203
|
+
let currentPhase = "tracking";
|
|
1204
|
+
try {
|
|
1205
|
+
onPhaseChange?.("tracking");
|
|
1206
|
+
const attestedReceipt = await this.trackWithdraw(receipt);
|
|
1207
|
+
currentPhase = "claiming";
|
|
1208
|
+
onPhaseChange?.("claiming");
|
|
1209
|
+
const { destinationChainTxnId } = await this.claimWithdraw({
|
|
1210
|
+
claimChain: sourceChain,
|
|
1211
|
+
destinationAddress: destinationAddress.toString(),
|
|
1212
|
+
receipt: attestedReceipt,
|
|
1213
|
+
wallet,
|
|
1214
|
+
onTransactionSigned
|
|
1215
|
+
});
|
|
1216
|
+
return { originChainTxnId, destinationChainTxnId };
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
throw new WithdrawError(
|
|
1219
|
+
error?.message ?? "Withdraw failed after Aptos burn",
|
|
1220
|
+
originChainTxnId,
|
|
1221
|
+
currentPhase,
|
|
1222
|
+
error
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Retries a failed `claimWithdraw` call with configurable exponential backoff.
|
|
1228
|
+
*
|
|
1229
|
+
* Use this when the claim phase of a withdrawal fails (e.g., RPC instability,
|
|
1230
|
+
* network congestion) but the Aptos burn transaction has already been submitted.
|
|
1231
|
+
* The attested receipt can be recovered from the `WithdrawError` thrown by
|
|
1232
|
+
* `withdraw()` and passed directly to this method.
|
|
1233
|
+
*
|
|
1234
|
+
* @example
|
|
1235
|
+
* ```ts
|
|
1236
|
+
* try {
|
|
1237
|
+
* await provider.withdraw({ ... });
|
|
1238
|
+
* } catch (error) {
|
|
1239
|
+
* if (error instanceof WithdrawError && error.phase === "claiming") {
|
|
1240
|
+
* const result = await provider.retryWithdrawClaim({
|
|
1241
|
+
* sourceChain,
|
|
1242
|
+
* destinationAddress,
|
|
1243
|
+
* receipt: attestedReceipt,
|
|
1244
|
+
* wallet,
|
|
1245
|
+
* maxRetries: 5,
|
|
1246
|
+
* });
|
|
1247
|
+
* }
|
|
1248
|
+
* }
|
|
1249
|
+
* ```
|
|
1250
|
+
*/
|
|
1251
|
+
async retryWithdrawClaim(input) {
|
|
1252
|
+
const {
|
|
1253
|
+
maxRetries = 5,
|
|
1254
|
+
initialDelayMs = 2e3,
|
|
1255
|
+
backoffMultiplier = 2,
|
|
1256
|
+
...claimInput
|
|
1257
|
+
} = input;
|
|
1258
|
+
let lastError;
|
|
1259
|
+
let delay = initialDelayMs;
|
|
1260
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1261
|
+
try {
|
|
1262
|
+
const result = await this.claimWithdraw(claimInput);
|
|
1263
|
+
return { ...result, retriesUsed: attempt };
|
|
1264
|
+
} catch (error) {
|
|
1265
|
+
lastError = error;
|
|
1266
|
+
logger.log(
|
|
1267
|
+
`retryWithdrawClaim: attempt ${attempt + 1}/${maxRetries + 1} failed: ${lastError.message}`
|
|
1268
|
+
);
|
|
1269
|
+
if (attempt < maxRetries) {
|
|
1270
|
+
await sleep2(delay);
|
|
1271
|
+
delay *= backoffMultiplier;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
throw new Error(
|
|
1276
|
+
`Claim failed after ${maxRetries + 1} attempts: ${lastError?.message}`
|
|
1277
|
+
);
|
|
900
1278
|
}
|
|
901
1279
|
getChainConfig(chain) {
|
|
902
1280
|
const chainConfig = this.crossChainCore.CHAINS[chain];
|
|
@@ -905,16 +1283,124 @@ var WormholeProvider = class {
|
|
|
905
1283
|
}
|
|
906
1284
|
return chainConfig;
|
|
907
1285
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1286
|
+
};
|
|
1287
|
+
|
|
1288
|
+
// src/providers/wormhole/signers/SolanaLocalSigner.ts
|
|
1289
|
+
import {
|
|
1290
|
+
Transaction as Transaction3
|
|
1291
|
+
} from "@solana/web3.js";
|
|
1292
|
+
var SolanaLocalSigner = class {
|
|
1293
|
+
constructor(config) {
|
|
1294
|
+
this._claimedTransactionHashes = [];
|
|
1295
|
+
this.keypair = config.keypair;
|
|
1296
|
+
this.connection = config.connection;
|
|
1297
|
+
this.commitment = config.commitment ?? "finalized";
|
|
1298
|
+
this.retryIntervalMs = config.retryIntervalMs ?? 5e3;
|
|
1299
|
+
this.priorityFeeConfig = config.priorityFeeConfig;
|
|
1300
|
+
this.verbose = config.verbose ?? false;
|
|
1301
|
+
this._onTransactionSigned = config.onTransactionSigned;
|
|
1302
|
+
}
|
|
1303
|
+
chain() {
|
|
1304
|
+
return "Solana";
|
|
1305
|
+
}
|
|
1306
|
+
address() {
|
|
1307
|
+
return this.keypair.publicKey.toBase58();
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Returns all transaction hashes from the most recent signAndSend call,
|
|
1311
|
+
* joined by comma. If only one transaction was signed, returns a single hash string.
|
|
1312
|
+
*/
|
|
1313
|
+
claimedTransactionHashes() {
|
|
1314
|
+
return this._claimedTransactionHashes.join(",");
|
|
1315
|
+
}
|
|
1316
|
+
async signAndSend(txs) {
|
|
1317
|
+
const txHashes = [];
|
|
1318
|
+
this._claimedTransactionHashes = [];
|
|
1319
|
+
for (const tx of txs) {
|
|
1320
|
+
this._onTransactionSigned?.(tx.description, null);
|
|
1321
|
+
const txId = await this.signAndSendTransaction(tx);
|
|
1322
|
+
this._onTransactionSigned?.(tx.description, txId);
|
|
1323
|
+
txHashes.push(txId);
|
|
1324
|
+
this._claimedTransactionHashes.push(txId);
|
|
1325
|
+
}
|
|
1326
|
+
return txHashes;
|
|
1327
|
+
}
|
|
1328
|
+
async signAndSendTransaction(request) {
|
|
1329
|
+
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash(this.commitment);
|
|
1330
|
+
let unsignedTx = request.transaction ?? request;
|
|
1331
|
+
const additionalSigners = request.transaction?.signers;
|
|
1332
|
+
const MAX_UNWRAP_DEPTH = 10;
|
|
1333
|
+
let unwrapDepth = 0;
|
|
1334
|
+
while (unsignedTx && typeof unsignedTx === "object" && "transaction" in unsignedTx && !(unsignedTx instanceof Transaction3) && !("signatures" in unsignedTx && "message" in unsignedTx)) {
|
|
1335
|
+
if (++unwrapDepth > MAX_UNWRAP_DEPTH) {
|
|
1336
|
+
throw new Error(
|
|
1337
|
+
"Transaction unwrapping exceeded maximum depth \u2014 possible circular nesting"
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
unsignedTx = unsignedTx.transaction;
|
|
1341
|
+
}
|
|
1342
|
+
const isVersioned = unsignedTx.message !== void 0 && unsignedTx.signatures !== void 0 && typeof unsignedTx.message.recentBlockhash !== "undefined";
|
|
1343
|
+
if (isVersioned) {
|
|
1344
|
+
unsignedTx.message.recentBlockhash = blockhash;
|
|
1345
|
+
if (this.verbose || this.priorityFeeConfig) {
|
|
1346
|
+
console.warn(
|
|
1347
|
+
"SolanaLocalSigner: Versioned transaction detected \u2014 priority fees are not applied. Consider using legacy transactions if priority fees are required."
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
unsignedTx.sign([this.keypair]);
|
|
1351
|
+
if (additionalSigners && additionalSigners.length > 0) {
|
|
1352
|
+
unsignedTx.sign(additionalSigners);
|
|
1353
|
+
}
|
|
1354
|
+
} else if (unsignedTx instanceof Transaction3) {
|
|
1355
|
+
unsignedTx.recentBlockhash = blockhash;
|
|
1356
|
+
unsignedTx.lastValidBlockHeight = lastValidBlockHeight;
|
|
1357
|
+
if (this.priorityFeeConfig) {
|
|
1358
|
+
await addPriorityFeeInstructions(
|
|
1359
|
+
this.connection,
|
|
1360
|
+
unsignedTx,
|
|
1361
|
+
this.priorityFeeConfig,
|
|
1362
|
+
this.verbose
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
if (additionalSigners && additionalSigners.length > 0) {
|
|
1366
|
+
unsignedTx.sign(this.keypair, ...additionalSigners);
|
|
1367
|
+
} else {
|
|
1368
|
+
unsignedTx.sign(this.keypair);
|
|
1369
|
+
}
|
|
1370
|
+
} else if (unsignedTx.recentBlockhash !== void 0 || unsignedTx.feePayer !== void 0) {
|
|
1371
|
+
unsignedTx.recentBlockhash = blockhash;
|
|
1372
|
+
unsignedTx.lastValidBlockHeight = lastValidBlockHeight;
|
|
1373
|
+
if (this.priorityFeeConfig) {
|
|
1374
|
+
await addPriorityFeeInstructions(
|
|
1375
|
+
this.connection,
|
|
1376
|
+
unsignedTx,
|
|
1377
|
+
this.priorityFeeConfig,
|
|
1378
|
+
this.verbose
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
if (additionalSigners && additionalSigners.length > 0) {
|
|
1382
|
+
unsignedTx.sign(this.keypair, ...additionalSigners);
|
|
1383
|
+
} else {
|
|
1384
|
+
unsignedTx.sign(this.keypair);
|
|
1385
|
+
}
|
|
1386
|
+
} else {
|
|
1387
|
+
throw new Error(
|
|
1388
|
+
`Unsupported transaction type: ${unsignedTx?.constructor?.name}`
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
const serializedTx = unsignedTx.serialize();
|
|
1392
|
+
const signature = await sendAndConfirmTransaction(
|
|
1393
|
+
serializedTx,
|
|
1394
|
+
blockhash,
|
|
1395
|
+
lastValidBlockHeight,
|
|
1396
|
+
{
|
|
1397
|
+
connection: this.connection,
|
|
1398
|
+
commitment: this.commitment,
|
|
1399
|
+
retryIntervalMs: this.retryIntervalMs,
|
|
1400
|
+
verbose: this.verbose
|
|
1401
|
+
}
|
|
916
1402
|
);
|
|
917
|
-
return
|
|
1403
|
+
return signature;
|
|
918
1404
|
}
|
|
919
1405
|
};
|
|
920
1406
|
|
|
@@ -988,8 +1474,8 @@ var testnetChains = {
|
|
|
988
1474
|
key: "Solana",
|
|
989
1475
|
context: "Solana" /* SOLANA */,
|
|
990
1476
|
displayName: "Solana",
|
|
991
|
-
explorerUrl: "https://
|
|
992
|
-
explorerName: "
|
|
1477
|
+
explorerUrl: "https://solscan.io",
|
|
1478
|
+
explorerName: "Solscan",
|
|
993
1479
|
chainId: 0,
|
|
994
1480
|
icon: "Solana",
|
|
995
1481
|
symbol: "SOL",
|
|
@@ -1112,8 +1598,8 @@ var mainnetChains = {
|
|
|
1112
1598
|
key: "Solana",
|
|
1113
1599
|
context: "Solana" /* SOLANA */,
|
|
1114
1600
|
displayName: "Solana",
|
|
1115
|
-
explorerUrl: "https://
|
|
1116
|
-
explorerName: "
|
|
1601
|
+
explorerUrl: "https://solscan.io",
|
|
1602
|
+
explorerName: "Solscan",
|
|
1117
1603
|
chainId: 0,
|
|
1118
1604
|
icon: "Solana",
|
|
1119
1605
|
symbol: "SOL",
|
|
@@ -1265,13 +1751,13 @@ var mainnetTokens = {
|
|
|
1265
1751
|
|
|
1266
1752
|
// src/utils/getUsdcBalance.ts
|
|
1267
1753
|
import { Aptos as Aptos3, AptosConfig as AptosConfig3, Network as Network2 } from "@aptos-labs/ts-sdk";
|
|
1268
|
-
import { Connection as
|
|
1754
|
+
import { Connection as Connection4, PublicKey } from "@solana/web3.js";
|
|
1269
1755
|
import { ethers as ethers2, JsonRpcProvider } from "ethers";
|
|
1270
1756
|
import { SuiClient } from "@mysten/sui/client";
|
|
1271
1757
|
var getSolanaWalletUSDCBalance = async (walletAddress, aptosNetwork, rpc) => {
|
|
1272
1758
|
const address = new PublicKey(walletAddress);
|
|
1273
1759
|
const tokenAddress = aptosNetwork === Network2.MAINNET ? mainnetTokens["Solana"].tokenId.address : testnetTokens["Solana"].tokenId.address;
|
|
1274
|
-
const connection = new
|
|
1760
|
+
const connection = new Connection4(rpc);
|
|
1275
1761
|
const splToken = await connection.getTokenAccountsByOwner(address, {
|
|
1276
1762
|
mint: new PublicKey(tokenAddress)
|
|
1277
1763
|
});
|
|
@@ -1324,6 +1810,18 @@ var getSuiWalletUSDCBalance = async (walletAddress, aptosNetwork, rpc) => {
|
|
|
1324
1810
|
|
|
1325
1811
|
// src/CrossChainCore.ts
|
|
1326
1812
|
import { NetworkToChainId, NetworkToNodeAPI } from "@aptos-labs/ts-sdk";
|
|
1813
|
+
var _evmChainRecord = {
|
|
1814
|
+
Ethereum: true,
|
|
1815
|
+
Sepolia: true,
|
|
1816
|
+
BaseSepolia: true,
|
|
1817
|
+
ArbitrumSepolia: true,
|
|
1818
|
+
Avalanche: true,
|
|
1819
|
+
Base: true,
|
|
1820
|
+
Arbitrum: true,
|
|
1821
|
+
PolygonSepolia: true,
|
|
1822
|
+
Polygon: true
|
|
1823
|
+
};
|
|
1824
|
+
var EVM_CHAIN_NAMES = Object.keys(_evmChainRecord);
|
|
1327
1825
|
var EthereumChainIdToTestnetChain = {
|
|
1328
1826
|
11155111: testnetChains.Sepolia,
|
|
1329
1827
|
84532: testnetChains.BaseSepolia,
|
|
@@ -1338,7 +1836,7 @@ var EthereumChainIdToMainnetChain = {
|
|
|
1338
1836
|
43114: mainnetChains.Avalanche,
|
|
1339
1837
|
137: mainnetChains.Polygon
|
|
1340
1838
|
};
|
|
1341
|
-
var
|
|
1839
|
+
var CrossChainCore2 = class {
|
|
1342
1840
|
constructor(args) {
|
|
1343
1841
|
this._dappConfig = {
|
|
1344
1842
|
aptosNetwork: Network3.TESTNET
|
|
@@ -1392,14 +1890,13 @@ var CrossChainCore = class {
|
|
|
1392
1890
|
walletAddress,
|
|
1393
1891
|
this._dappConfig.aptosNetwork,
|
|
1394
1892
|
sourceChain,
|
|
1395
|
-
|
|
1396
|
-
this.CHAINS[sourceChain].defaultRpc
|
|
1893
|
+
this._dappConfig?.evmConfig?.[sourceChain]?.rpc ?? this.CHAINS[sourceChain].defaultRpc
|
|
1397
1894
|
);
|
|
1398
1895
|
case "Sui":
|
|
1399
1896
|
return await getSuiWalletUSDCBalance(
|
|
1400
1897
|
walletAddress,
|
|
1401
1898
|
this._dappConfig.aptosNetwork,
|
|
1402
|
-
this.CHAINS[sourceChain].defaultRpc
|
|
1899
|
+
this._dappConfig?.suiConfig?.rpc ?? this.CHAINS[sourceChain].defaultRpc
|
|
1403
1900
|
);
|
|
1404
1901
|
default:
|
|
1405
1902
|
throw new Error(`Unsupported chain: ${sourceChain}`);
|
|
@@ -1412,17 +1909,25 @@ import { Network as Network4 } from "@aptos-labs/ts-sdk";
|
|
|
1412
1909
|
export {
|
|
1413
1910
|
AptosLocalSigner,
|
|
1414
1911
|
Context,
|
|
1415
|
-
CrossChainCore,
|
|
1912
|
+
CrossChainCore2 as CrossChainCore,
|
|
1913
|
+
EVM_CHAIN_NAMES,
|
|
1416
1914
|
EthereumChainIdToMainnetChain,
|
|
1417
1915
|
EthereumChainIdToTestnetChain,
|
|
1418
1916
|
Network4 as Network,
|
|
1419
1917
|
NetworkToChainId,
|
|
1420
1918
|
NetworkToNodeAPI,
|
|
1919
|
+
SolanaLocalSigner,
|
|
1920
|
+
TransferError,
|
|
1921
|
+
WithdrawError,
|
|
1421
1922
|
WormholeProvider,
|
|
1923
|
+
createCCTPRoute,
|
|
1924
|
+
deserializeReceipt,
|
|
1422
1925
|
mainnetChains,
|
|
1423
1926
|
mainnetTokens,
|
|
1927
|
+
serializeReceipt,
|
|
1424
1928
|
signAndSendTransaction,
|
|
1425
1929
|
testnetChains,
|
|
1426
|
-
testnetTokens
|
|
1930
|
+
testnetTokens,
|
|
1931
|
+
validateExpireTimestamp
|
|
1427
1932
|
};
|
|
1428
1933
|
//# sourceMappingURL=index.mjs.map
|