@aptos-labs/cross-chain-core 5.8.1 → 5.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/CrossChainCore.d.ts +20 -0
- package/dist/CrossChainCore.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +580 -275
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +583 -274
- package/dist/index.mjs.map +1 -1
- package/dist/providers/wormhole/index.d.ts +2 -0
- package/dist/providers/wormhole/index.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/AptosLocalSigner.d.ts +1 -1
- package/dist/providers/wormhole/signers/AptosLocalSigner.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/Signer.d.ts +1 -1
- package/dist/providers/wormhole/signers/Signer.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/SolanaLocalSigner.d.ts +65 -0
- package/dist/providers/wormhole/signers/SolanaLocalSigner.d.ts.map +1 -0
- package/dist/providers/wormhole/signers/SolanaSigner.d.ts +12 -20
- package/dist/providers/wormhole/signers/SolanaSigner.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/solanaUtils.d.ts +68 -0
- package/dist/providers/wormhole/signers/solanaUtils.d.ts.map +1 -0
- package/dist/providers/wormhole/types.d.ts +43 -0
- package/dist/providers/wormhole/types.d.ts.map +1 -1
- package/dist/providers/wormhole/utils.d.ts +26 -0
- package/dist/providers/wormhole/utils.d.ts.map +1 -0
- package/dist/providers/wormhole/wormhole.d.ts +36 -6
- package/dist/providers/wormhole/wormhole.d.ts.map +1 -1
- package/dist/utils/receiptSerialization.d.ts +38 -0
- package/dist/utils/receiptSerialization.d.ts.map +1 -0
- package/dist/version.d.ts +1 -1
- package/package.json +3 -3
- package/src/CrossChainCore.ts +20 -0
- package/src/config/mainnet/chains.ts +2 -2
- package/src/config/testnet/chains.ts +2 -2
- package/src/index.ts +1 -0
- package/src/providers/wormhole/index.ts +2 -0
- package/src/providers/wormhole/signers/AptosLocalSigner.ts +4 -4
- package/src/providers/wormhole/signers/AptosSigner.ts +1 -1
- package/src/providers/wormhole/signers/EthereumSigner.ts +3 -3
- package/src/providers/wormhole/signers/Signer.ts +4 -4
- package/src/providers/wormhole/signers/SolanaLocalSigner.ts +243 -0
- package/src/providers/wormhole/signers/SolanaSigner.ts +45 -337
- package/src/providers/wormhole/signers/solanaUtils.ts +422 -0
- package/src/providers/wormhole/types.ts +68 -0
- package/src/providers/wormhole/utils.ts +72 -0
- package/src/providers/wormhole/wormhole.ts +182 -120
- package/src/utils/receiptSerialization.ts +141 -0
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -38,9 +38,14 @@ __export(index_exports, {
|
|
|
38
38
|
Network: () => import_ts_sdk7.Network,
|
|
39
39
|
NetworkToChainId: () => import_ts_sdk6.NetworkToChainId,
|
|
40
40
|
NetworkToNodeAPI: () => import_ts_sdk6.NetworkToNodeAPI,
|
|
41
|
+
SolanaLocalSigner: () => SolanaLocalSigner,
|
|
42
|
+
WithdrawError: () => WithdrawError,
|
|
41
43
|
WormholeProvider: () => WormholeProvider,
|
|
44
|
+
createCCTPRoute: () => createCCTPRoute,
|
|
45
|
+
deserializeReceipt: () => deserializeReceipt,
|
|
42
46
|
mainnetChains: () => mainnetChains,
|
|
43
47
|
mainnetTokens: () => mainnetTokens,
|
|
48
|
+
serializeReceipt: () => serializeReceipt,
|
|
44
49
|
signAndSendTransaction: () => signAndSendTransaction,
|
|
45
50
|
testnetChains: () => testnetChains,
|
|
46
51
|
testnetTokens: () => testnetTokens
|
|
@@ -51,7 +56,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
51
56
|
var import_ts_sdk5 = require("@aptos-labs/ts-sdk");
|
|
52
57
|
|
|
53
58
|
// src/providers/wormhole/wormhole.ts
|
|
54
|
-
var
|
|
59
|
+
var import_sdk3 = require("@wormhole-foundation/sdk");
|
|
55
60
|
var import_ts_sdk3 = require("@aptos-labs/ts-sdk");
|
|
56
61
|
var import_aptos = __toESM(require("@wormhole-foundation/sdk/aptos"));
|
|
57
62
|
var import_solana = __toESM(require("@wormhole-foundation/sdk/solana"));
|
|
@@ -77,15 +82,99 @@ var logger = {
|
|
|
77
82
|
}
|
|
78
83
|
};
|
|
79
84
|
|
|
85
|
+
// src/utils/receiptSerialization.ts
|
|
86
|
+
var import_sdk = require("@wormhole-foundation/sdk");
|
|
87
|
+
function uint8ArrayToBase64(bytes) {
|
|
88
|
+
let binary = "";
|
|
89
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
90
|
+
binary += String.fromCharCode(bytes[i]);
|
|
91
|
+
}
|
|
92
|
+
return btoa(binary);
|
|
93
|
+
}
|
|
94
|
+
function base64ToUint8Array(base64) {
|
|
95
|
+
const binary = atob(base64);
|
|
96
|
+
const bytes = new Uint8Array(binary.length);
|
|
97
|
+
for (let i = 0; i < binary.length; i++) {
|
|
98
|
+
bytes[i] = binary.charCodeAt(i);
|
|
99
|
+
}
|
|
100
|
+
return bytes;
|
|
101
|
+
}
|
|
102
|
+
function serializeReceipt(receipt) {
|
|
103
|
+
return JSON.parse(
|
|
104
|
+
JSON.stringify(receipt, (_key, value) => {
|
|
105
|
+
if (typeof value === "bigint") {
|
|
106
|
+
return { __type: "bigint", value: value.toString() };
|
|
107
|
+
}
|
|
108
|
+
if (value instanceof import_sdk.UniversalAddress) {
|
|
109
|
+
return {
|
|
110
|
+
__type: "UniversalAddress",
|
|
111
|
+
value: uint8ArrayToBase64(value.toUint8Array())
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (value instanceof Uint8Array) {
|
|
115
|
+
return {
|
|
116
|
+
__type: "Uint8Array",
|
|
117
|
+
value: uint8ArrayToBase64(value)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return value;
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
function deserializeReceipt(obj) {
|
|
125
|
+
function revive(value, key) {
|
|
126
|
+
if (value && typeof value === "object") {
|
|
127
|
+
const objValue = value;
|
|
128
|
+
if ("__type" in objValue) {
|
|
129
|
+
if (objValue.__type === "bigint") {
|
|
130
|
+
return BigInt(objValue.value);
|
|
131
|
+
}
|
|
132
|
+
if (objValue.__type === "UniversalAddress") {
|
|
133
|
+
return new import_sdk.UniversalAddress(
|
|
134
|
+
base64ToUint8Array(objValue.value)
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (objValue.__type === "Uint8Array") {
|
|
138
|
+
return base64ToUint8Array(objValue.value);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const addressFields = [
|
|
142
|
+
"sender",
|
|
143
|
+
"recipient",
|
|
144
|
+
"destinationCaller",
|
|
145
|
+
"burnToken",
|
|
146
|
+
"mintRecipient",
|
|
147
|
+
"messageSender"
|
|
148
|
+
];
|
|
149
|
+
if (key && addressFields.includes(key) && "address" in objValue) {
|
|
150
|
+
const addressBytes = revive(objValue.address);
|
|
151
|
+
if (addressBytes instanceof Uint8Array) {
|
|
152
|
+
return new import_sdk.UniversalAddress(addressBytes);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (Array.isArray(value)) {
|
|
156
|
+
return value.map((v, i) => revive(v, String(i)));
|
|
157
|
+
}
|
|
158
|
+
const result = {};
|
|
159
|
+
for (const k in objValue) {
|
|
160
|
+
result[k] = revive(objValue[k], k);
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
return revive(obj);
|
|
167
|
+
}
|
|
168
|
+
|
|
80
169
|
// src/providers/wormhole/signers/AptosLocalSigner.ts
|
|
81
170
|
var import_ts_sdk = require("@aptos-labs/ts-sdk");
|
|
82
171
|
var AptosLocalSigner = class {
|
|
83
172
|
constructor(chain, options, wallet, feePayerAccount, dappNetwork) {
|
|
173
|
+
this._claimedTransactionHashes = [];
|
|
84
174
|
this._chain = chain;
|
|
85
175
|
this._options = options;
|
|
86
176
|
this._wallet = wallet;
|
|
87
177
|
this._sponsorAccount = feePayerAccount;
|
|
88
|
-
this._claimedTransactionHashes = "";
|
|
89
178
|
this._dappNetwork = dappNetwork;
|
|
90
179
|
}
|
|
91
180
|
chain() {
|
|
@@ -95,10 +184,11 @@ var AptosLocalSigner = class {
|
|
|
95
184
|
return this._wallet.accountAddress.toString();
|
|
96
185
|
}
|
|
97
186
|
claimedTransactionHashes() {
|
|
98
|
-
return this._claimedTransactionHashes;
|
|
187
|
+
return this._claimedTransactionHashes.join(",");
|
|
99
188
|
}
|
|
100
189
|
async signAndSend(txs) {
|
|
101
190
|
const txHashes = [];
|
|
191
|
+
this._claimedTransactionHashes = [];
|
|
102
192
|
for (const tx of txs) {
|
|
103
193
|
const txId = await signAndSendTransaction(
|
|
104
194
|
tx,
|
|
@@ -107,7 +197,7 @@ var AptosLocalSigner = class {
|
|
|
107
197
|
this._dappNetwork
|
|
108
198
|
);
|
|
109
199
|
txHashes.push(txId);
|
|
110
|
-
this._claimedTransactionHashes
|
|
200
|
+
this._claimedTransactionHashes.push(txId);
|
|
111
201
|
}
|
|
112
202
|
return txHashes;
|
|
113
203
|
}
|
|
@@ -158,119 +248,123 @@ async function signAndSendTransaction(request, wallet, sponsorAccount, dappNetwo
|
|
|
158
248
|
}
|
|
159
249
|
|
|
160
250
|
// src/providers/wormhole/signers/SolanaSigner.ts
|
|
161
|
-
var import_web3 = require("@solana/web3.js");
|
|
162
251
|
var import_web32 = require("@solana/web3.js");
|
|
163
|
-
var import_sdk_solana = require("@wormhole-foundation/sdk-solana");
|
|
164
252
|
var import_web33 = require("@solana/web3.js");
|
|
165
253
|
var import_derived_wallet_solana = require("@aptos-labs/derived-wallet-solana");
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const connection =
|
|
172
|
-
crossChainCore?._dappConfig?.solanaConfig?.rpc ?? crossChainCore?.CHAINS["Solana"]?.defaultRpc ?? "https://api.devnet.solana.com"
|
|
173
|
-
// Last resort fallback
|
|
174
|
-
);
|
|
175
|
-
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
|
|
176
|
-
const unsignedTx = await setPriorityFeeInstructions(
|
|
177
|
-
connection,
|
|
178
|
-
blockhash,
|
|
179
|
-
lastValidBlockHeight,
|
|
180
|
-
request,
|
|
181
|
-
crossChainCore
|
|
182
|
-
);
|
|
183
|
-
let confirmTransactionPromise = null;
|
|
184
|
-
let confirmedTx = null;
|
|
185
|
-
let txSendAttempts = 1;
|
|
186
|
-
let signature = "";
|
|
187
|
-
if (!wallet.solanaWallet.signTransaction) {
|
|
188
|
-
throw new Error("Wallet does not support signing transactions").message;
|
|
189
|
-
}
|
|
190
|
-
const tx = await wallet.solanaWallet.signTransaction(unsignedTx);
|
|
191
|
-
if (!tx) throw new Error("Failed to sign transaction").message;
|
|
192
|
-
if (request.transaction.signers && tx instanceof import_web32.Transaction) {
|
|
193
|
-
tx.partialSign(...request.transaction.signers);
|
|
194
|
-
}
|
|
195
|
-
const serializedTx = tx.serialize();
|
|
254
|
+
|
|
255
|
+
// src/providers/wormhole/signers/solanaUtils.ts
|
|
256
|
+
var import_web3 = require("@solana/web3.js");
|
|
257
|
+
var import_sdk_solana = require("@wormhole-foundation/sdk-solana");
|
|
258
|
+
async function sendAndConfirmTransaction(serializedTx, blockhash, lastValidBlockHeight, config) {
|
|
259
|
+
const { connection, commitment, retryIntervalMs = 5e3, verbose = false } = config;
|
|
196
260
|
const sendOptions = {
|
|
197
261
|
skipPreflight: true,
|
|
198
262
|
maxRetries: 0,
|
|
199
|
-
|
|
200
|
-
// See PR and linked issue for why setting this matters: https://github.com/anza-xyz/agave/pull/483
|
|
263
|
+
preflightCommitment: commitment
|
|
201
264
|
};
|
|
202
|
-
signature = await connection.sendRawTransaction(serializedTx, sendOptions);
|
|
203
|
-
confirmTransactionPromise = connection.confirmTransaction(
|
|
204
|
-
{
|
|
205
|
-
signature,
|
|
206
|
-
blockhash,
|
|
207
|
-
lastValidBlockHeight
|
|
208
|
-
},
|
|
265
|
+
const signature = await connection.sendRawTransaction(serializedTx, sendOptions);
|
|
266
|
+
const confirmTransactionPromise = connection.confirmTransaction(
|
|
267
|
+
{ signature, blockhash, lastValidBlockHeight },
|
|
209
268
|
commitment
|
|
210
269
|
);
|
|
211
|
-
|
|
270
|
+
let confirmedTx = null;
|
|
271
|
+
let txSendAttempts = 1;
|
|
212
272
|
while (!confirmedTx) {
|
|
213
273
|
confirmedTx = await Promise.race([
|
|
214
274
|
confirmTransactionPromise,
|
|
215
275
|
new Promise(
|
|
216
|
-
(resolve) => setTimeout(() =>
|
|
217
|
-
resolve(null);
|
|
218
|
-
}, txRetryInterval)
|
|
276
|
+
(resolve) => setTimeout(() => resolve(null), retryIntervalMs)
|
|
219
277
|
)
|
|
220
278
|
]);
|
|
221
|
-
if (confirmedTx)
|
|
222
|
-
|
|
279
|
+
if (confirmedTx) break;
|
|
280
|
+
if (verbose) {
|
|
281
|
+
console.log(
|
|
282
|
+
`Tx not confirmed after ${retryIntervalMs * txSendAttempts++}ms, resending`
|
|
283
|
+
);
|
|
223
284
|
}
|
|
224
|
-
console.log(
|
|
225
|
-
`Tx not confirmed after ${txRetryInterval * txSendAttempts++}ms, resending`
|
|
226
|
-
);
|
|
227
285
|
try {
|
|
228
286
|
await connection.sendRawTransaction(serializedTx, sendOptions);
|
|
229
287
|
} catch (e) {
|
|
230
|
-
|
|
288
|
+
if (verbose) {
|
|
289
|
+
console.error("Failed to resend transaction:", e);
|
|
290
|
+
}
|
|
231
291
|
}
|
|
232
292
|
}
|
|
233
293
|
if (confirmedTx.value.err) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
errorMessage = `Transaction failed: ${JSON.stringify(
|
|
238
|
-
confirmedTx.value.err,
|
|
239
|
-
(_key, value) => typeof value === "bigint" ? value.toString() : value
|
|
240
|
-
// Handle bigint props
|
|
241
|
-
)}`;
|
|
242
|
-
} catch (e) {
|
|
243
|
-
errorMessage = `Transaction failed: Unknown error`;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
throw new Error(`Transaction failed: ${errorMessage}`).message;
|
|
294
|
+
const errorMessage = formatTransactionError(confirmedTx.value.err);
|
|
295
|
+
throw new Error(errorMessage);
|
|
247
296
|
}
|
|
248
297
|
return signature;
|
|
249
298
|
}
|
|
250
|
-
|
|
251
|
-
|
|
299
|
+
function formatTransactionError(err) {
|
|
300
|
+
if (typeof err === "object" && err !== null) {
|
|
301
|
+
try {
|
|
302
|
+
return `Transaction failed: ${JSON.stringify(
|
|
303
|
+
err,
|
|
304
|
+
(_key, value) => typeof value === "bigint" ? value.toString() : value
|
|
305
|
+
)}`;
|
|
306
|
+
} catch {
|
|
307
|
+
return "Transaction failed: Unknown error";
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return `Transaction failed: ${err}`;
|
|
311
|
+
}
|
|
312
|
+
async function addPriorityFeeInstructions(connection, transaction, priorityFeeConfig, verbose = false) {
|
|
252
313
|
const computeBudgetIxFilter = (ix) => ix.programId.toString() !== "ComputeBudget111111111111111111111111111111";
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
314
|
+
transaction.instructions = transaction.instructions.filter(computeBudgetIxFilter);
|
|
315
|
+
const instructions = await createPriorityFeeInstructions(
|
|
316
|
+
connection,
|
|
317
|
+
transaction,
|
|
318
|
+
priorityFeeConfig,
|
|
319
|
+
verbose
|
|
257
320
|
);
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
321
|
+
transaction.add(...instructions);
|
|
322
|
+
return transaction;
|
|
323
|
+
}
|
|
324
|
+
async function createPriorityFeeInstructions(connection, transaction, priorityFeeConfig, verbose = false) {
|
|
325
|
+
const unitsUsed = await simulateAndGetComputeUnits(connection, transaction);
|
|
326
|
+
const unitBudget = Math.floor(unitsUsed * 1.2);
|
|
327
|
+
const instructions = [];
|
|
328
|
+
instructions.push(
|
|
329
|
+
import_web3.ComputeBudgetProgram.setComputeUnitLimit({
|
|
330
|
+
units: unitBudget
|
|
331
|
+
})
|
|
264
332
|
);
|
|
265
|
-
|
|
333
|
+
const {
|
|
334
|
+
percentile = 0.9,
|
|
335
|
+
percentileMultiple = 1,
|
|
336
|
+
min = 1e5,
|
|
337
|
+
max = 1e8
|
|
338
|
+
} = priorityFeeConfig ?? {};
|
|
339
|
+
const rpcProvider = determineRpcProvider(connection.rpcEndpoint);
|
|
340
|
+
const { fee, methodUsed } = await calculatePriorityFee(
|
|
341
|
+
connection,
|
|
342
|
+
transaction,
|
|
343
|
+
rpcProvider,
|
|
344
|
+
{ percentile, percentileMultiple, min, max }
|
|
345
|
+
);
|
|
346
|
+
if (verbose) {
|
|
347
|
+
const maxFeeInSol = fee / 1e6 / import_web3.LAMPORTS_PER_SOL * unitBudget;
|
|
348
|
+
console.table({
|
|
349
|
+
"RPC Provider": rpcProvider,
|
|
350
|
+
"Method used": methodUsed,
|
|
351
|
+
"Percentile used": percentile,
|
|
352
|
+
"Multiple used": percentileMultiple,
|
|
353
|
+
"Compute budget": unitBudget,
|
|
354
|
+
"Priority fee": fee,
|
|
355
|
+
"Max fee in SOL": maxFeeInSol
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
instructions.push(
|
|
359
|
+
import_web3.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: fee })
|
|
360
|
+
);
|
|
361
|
+
return instructions;
|
|
266
362
|
}
|
|
267
|
-
async function
|
|
363
|
+
async function simulateAndGetComputeUnits(connection, transaction) {
|
|
268
364
|
let unitsUsed = 2e5;
|
|
269
365
|
let simulationAttempts = 0;
|
|
270
366
|
simulationLoop: while (true) {
|
|
271
|
-
const response = await connection.simulateTransaction(
|
|
272
|
-
transaction
|
|
273
|
-
);
|
|
367
|
+
const response = await connection.simulateTransaction(transaction);
|
|
274
368
|
if (response.value.err) {
|
|
275
369
|
if (checkKnownSimulationError(response.value)) {
|
|
276
370
|
if (simulationAttempts < 5) {
|
|
@@ -287,7 +381,7 @@ async function createPriorityFeeInstructions(connection, transaction, crossChain
|
|
|
287
381
|
`Simulation failed: ${JSON.stringify(response.value.err)}
|
|
288
382
|
Logs:
|
|
289
383
|
${(response.value.logs || []).join("\n ")}`
|
|
290
|
-
)
|
|
384
|
+
);
|
|
291
385
|
} else {
|
|
292
386
|
if (response.value.unitsConsumed) {
|
|
293
387
|
unitsUsed = response.value.unitsConsumed;
|
|
@@ -295,41 +389,13 @@ ${(response.value.logs || []).join("\n ")}`
|
|
|
295
389
|
break;
|
|
296
390
|
}
|
|
297
391
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
units: unitBudget
|
|
304
|
-
})
|
|
305
|
-
);
|
|
306
|
-
const {
|
|
307
|
-
percentile = 0.9,
|
|
308
|
-
percentileMultiple = 1,
|
|
309
|
-
min = 1e5,
|
|
310
|
-
max = 1e8
|
|
311
|
-
} = crossChainCore?._dappConfig?.solanaConfig?.priorityFeeConfig ?? {};
|
|
312
|
-
const calculateFee = async (rpcProvider2) => {
|
|
313
|
-
if (rpcProvider2 === "triton") {
|
|
314
|
-
try {
|
|
315
|
-
const fee2 = await (0, import_sdk_solana.determinePriorityFeeTritonOne)(
|
|
316
|
-
connection,
|
|
317
|
-
transaction,
|
|
318
|
-
percentile,
|
|
319
|
-
percentileMultiple,
|
|
320
|
-
min,
|
|
321
|
-
max
|
|
322
|
-
);
|
|
323
|
-
return {
|
|
324
|
-
fee: fee2,
|
|
325
|
-
methodUsed: "triton"
|
|
326
|
-
};
|
|
327
|
-
} catch (e) {
|
|
328
|
-
console.warn(`Failed to determine priority fee using Triton RPC:`, e);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
392
|
+
return unitsUsed;
|
|
393
|
+
}
|
|
394
|
+
async function calculatePriorityFee(connection, transaction, rpcProvider, config) {
|
|
395
|
+
const { percentile, percentileMultiple, min, max } = config;
|
|
396
|
+
if (rpcProvider === "triton") {
|
|
331
397
|
try {
|
|
332
|
-
const
|
|
398
|
+
const fee = await (0, import_sdk_solana.determinePriorityFeeTritonOne)(
|
|
333
399
|
connection,
|
|
334
400
|
transaction,
|
|
335
401
|
percentile,
|
|
@@ -337,37 +403,25 @@ ${(response.value.logs || []).join("\n ")}`
|
|
|
337
403
|
min,
|
|
338
404
|
max
|
|
339
405
|
);
|
|
340
|
-
return {
|
|
341
|
-
fee: fee2,
|
|
342
|
-
methodUsed: "default"
|
|
343
|
-
};
|
|
406
|
+
return { fee, methodUsed: "triton" };
|
|
344
407
|
} catch (e) {
|
|
345
408
|
console.warn(`Failed to determine priority fee using Triton RPC:`, e);
|
|
346
|
-
return {
|
|
347
|
-
fee: min,
|
|
348
|
-
methodUsed: "minimum"
|
|
349
|
-
};
|
|
350
409
|
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
});
|
|
367
|
-
instructions.push(
|
|
368
|
-
import_web3.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: fee })
|
|
369
|
-
);
|
|
370
|
-
return instructions;
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const fee = await (0, import_sdk_solana.determinePriorityFee)(
|
|
413
|
+
connection,
|
|
414
|
+
transaction,
|
|
415
|
+
percentile,
|
|
416
|
+
percentileMultiple,
|
|
417
|
+
min,
|
|
418
|
+
max
|
|
419
|
+
);
|
|
420
|
+
return { fee, methodUsed: "default" };
|
|
421
|
+
} catch (e) {
|
|
422
|
+
console.warn(`Failed to determine priority fee:`, e);
|
|
423
|
+
return { fee: min, methodUsed: "minimum" };
|
|
424
|
+
}
|
|
371
425
|
}
|
|
372
426
|
function checkKnownSimulationError(response) {
|
|
373
427
|
const errors = {};
|
|
@@ -384,61 +438,106 @@ function checkKnownSimulationError(response) {
|
|
|
384
438
|
}
|
|
385
439
|
}
|
|
386
440
|
}
|
|
387
|
-
if (
|
|
441
|
+
if (Object.keys(errors).length === 0) {
|
|
388
442
|
return false;
|
|
389
443
|
}
|
|
390
444
|
console.table(errors);
|
|
391
445
|
return true;
|
|
392
446
|
}
|
|
393
|
-
|
|
394
|
-
return
|
|
447
|
+
function isHostOrSubdomainOf(hostname, base) {
|
|
448
|
+
return hostname === base || hostname.endsWith(`.${base}`);
|
|
395
449
|
}
|
|
396
|
-
var isEmptyObject = (value) => {
|
|
397
|
-
if (value === null || value === void 0) {
|
|
398
|
-
return true;
|
|
399
|
-
}
|
|
400
|
-
for (const key in value) {
|
|
401
|
-
if (value.hasOwnProperty.call(value, key)) {
|
|
402
|
-
return false;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return true;
|
|
406
|
-
};
|
|
407
450
|
function determineRpcProvider(endpoint) {
|
|
408
451
|
try {
|
|
409
452
|
const url = new URL(endpoint);
|
|
410
453
|
const hostname = url.hostname;
|
|
411
|
-
if (hostname
|
|
454
|
+
if (isHostOrSubdomainOf(hostname, "rpcpool.com") || isHostOrSubdomainOf(hostname, "triton.one")) {
|
|
412
455
|
return "triton";
|
|
413
|
-
} else if (hostname
|
|
456
|
+
} else if (isHostOrSubdomainOf(hostname, "helius-rpc.com") || isHostOrSubdomainOf(hostname, "helius.xyz")) {
|
|
414
457
|
return "helius";
|
|
415
|
-
} else if (hostname
|
|
458
|
+
} else if (isHostOrSubdomainOf(hostname, "ankr.com")) {
|
|
416
459
|
return "ankr";
|
|
417
460
|
} else {
|
|
418
461
|
return "unknown";
|
|
419
462
|
}
|
|
420
|
-
} catch
|
|
463
|
+
} catch {
|
|
421
464
|
return "unknown";
|
|
422
465
|
}
|
|
423
466
|
}
|
|
467
|
+
async function sleep(timeout) {
|
|
468
|
+
return new Promise((resolve) => setTimeout(resolve, timeout));
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// src/providers/wormhole/signers/SolanaSigner.ts
|
|
472
|
+
async function signAndSendTransaction2(request, wallet, options, crossChainCore) {
|
|
473
|
+
if (!wallet || !(wallet instanceof import_derived_wallet_solana.SolanaDerivedWallet)) {
|
|
474
|
+
throw new Error("Invalid wallet type or missing Solana wallet");
|
|
475
|
+
}
|
|
476
|
+
const commitment = options?.commitment ?? "finalized";
|
|
477
|
+
const connection = new import_web33.Connection(
|
|
478
|
+
crossChainCore?._dappConfig?.solanaConfig?.rpc ?? crossChainCore?.CHAINS["Solana"]?.defaultRpc ?? "https://api.devnet.solana.com"
|
|
479
|
+
// Last resort fallback
|
|
480
|
+
);
|
|
481
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
|
|
482
|
+
const unsignedTx = await setPriorityFeeInstructions(
|
|
483
|
+
connection,
|
|
484
|
+
blockhash,
|
|
485
|
+
lastValidBlockHeight,
|
|
486
|
+
request,
|
|
487
|
+
crossChainCore?._dappConfig?.solanaConfig?.priorityFeeConfig
|
|
488
|
+
);
|
|
489
|
+
if (!wallet.solanaWallet.signTransaction) {
|
|
490
|
+
throw new Error("Wallet does not support signing transactions");
|
|
491
|
+
}
|
|
492
|
+
const tx = await wallet.solanaWallet.signTransaction(unsignedTx);
|
|
493
|
+
if (!tx) throw new Error("Failed to sign transaction");
|
|
494
|
+
if (request.transaction.signers && tx instanceof import_web32.Transaction) {
|
|
495
|
+
tx.partialSign(...request.transaction.signers);
|
|
496
|
+
}
|
|
497
|
+
const serializedTx = tx.serialize();
|
|
498
|
+
const signature = await sendAndConfirmTransaction(
|
|
499
|
+
serializedTx,
|
|
500
|
+
blockhash,
|
|
501
|
+
lastValidBlockHeight,
|
|
502
|
+
{
|
|
503
|
+
connection,
|
|
504
|
+
commitment,
|
|
505
|
+
retryIntervalMs: 5e3,
|
|
506
|
+
verbose: false
|
|
507
|
+
}
|
|
508
|
+
);
|
|
509
|
+
return signature;
|
|
510
|
+
}
|
|
511
|
+
async function setPriorityFeeInstructions(connection, blockhash, lastValidBlockHeight, request, priorityFeeConfig) {
|
|
512
|
+
const unsignedTx = request.transaction.transaction;
|
|
513
|
+
unsignedTx.recentBlockhash = blockhash;
|
|
514
|
+
unsignedTx.lastValidBlockHeight = lastValidBlockHeight;
|
|
515
|
+
await addPriorityFeeInstructions(
|
|
516
|
+
connection,
|
|
517
|
+
unsignedTx,
|
|
518
|
+
priorityFeeConfig,
|
|
519
|
+
false
|
|
520
|
+
);
|
|
521
|
+
return unsignedTx;
|
|
522
|
+
}
|
|
424
523
|
|
|
425
524
|
// src/providers/wormhole/signers/EthereumSigner.ts
|
|
426
525
|
var import_ethers = require("ethers");
|
|
427
526
|
async function signAndSendTransaction3(request, wallet, chainName, options) {
|
|
428
527
|
if (!wallet) {
|
|
429
|
-
throw new Error("wallet.sendTransaction is undefined")
|
|
528
|
+
throw new Error("wallet.sendTransaction is undefined");
|
|
430
529
|
}
|
|
431
530
|
const chainId = await wallet.eip1193Provider.request({
|
|
432
531
|
method: "eth_chainId"
|
|
433
532
|
});
|
|
434
533
|
const actualChainId = parseInt(chainId, 16);
|
|
435
534
|
if (!actualChainId)
|
|
436
|
-
throw new Error("No signer found for chain" + chainName)
|
|
535
|
+
throw new Error("No signer found for chain" + chainName);
|
|
437
536
|
const expectedChainId = request.transaction.chainId ? (0, import_ethers.getBigInt)(request.transaction.chainId) : void 0;
|
|
438
537
|
if (!actualChainId || !expectedChainId || BigInt(actualChainId) !== expectedChainId) {
|
|
439
538
|
throw new Error(
|
|
440
539
|
`Signer is not connected to the right chain. Expected ${expectedChainId}, got ${actualChainId}`
|
|
441
|
-
)
|
|
540
|
+
);
|
|
442
541
|
}
|
|
443
542
|
const provider = new import_ethers.ethers.BrowserProvider(
|
|
444
543
|
wallet.eip1193Provider
|
|
@@ -455,7 +554,7 @@ var import_wallet_standard = require("@aptos-labs/wallet-standard");
|
|
|
455
554
|
var import_gas_station_client = require("@aptos-labs/gas-station-client");
|
|
456
555
|
async function signAndSendTransaction4(request, wallet, sponsorAccount, dappNetwork) {
|
|
457
556
|
if (!wallet) {
|
|
458
|
-
throw new Error("wallet.sendTransaction is undefined")
|
|
557
|
+
throw new Error("wallet.sendTransaction is undefined");
|
|
459
558
|
}
|
|
460
559
|
const payload = request.transaction;
|
|
461
560
|
payload.functionArguments = payload.functionArguments.map((a) => {
|
|
@@ -555,13 +654,13 @@ async function signAndSendTransaction5(request, wallet) {
|
|
|
555
654
|
// src/providers/wormhole/signers/Signer.ts
|
|
556
655
|
var Signer = class {
|
|
557
656
|
constructor(chain, address, options, wallet, crossChainCore, sponsorAccount) {
|
|
657
|
+
this._claimedTransactionHashes = [];
|
|
558
658
|
this._chain = chain;
|
|
559
659
|
this._address = address;
|
|
560
660
|
this._options = options;
|
|
561
661
|
this._wallet = wallet;
|
|
562
662
|
this._crossChainCore = crossChainCore;
|
|
563
663
|
this._sponsorAccount = sponsorAccount;
|
|
564
|
-
this._claimedTransactionHashes = "";
|
|
565
664
|
}
|
|
566
665
|
chain() {
|
|
567
666
|
return this._chain.key;
|
|
@@ -570,10 +669,11 @@ var Signer = class {
|
|
|
570
669
|
return this._address;
|
|
571
670
|
}
|
|
572
671
|
claimedTransactionHashes() {
|
|
573
|
-
return this._claimedTransactionHashes;
|
|
672
|
+
return this._claimedTransactionHashes.join(",");
|
|
574
673
|
}
|
|
575
674
|
async signAndSend(txs) {
|
|
576
675
|
const txHashes = [];
|
|
676
|
+
this._claimedTransactionHashes = [];
|
|
577
677
|
for (const tx of txs) {
|
|
578
678
|
const txId = await signAndSendTransaction6(
|
|
579
679
|
this._chain,
|
|
@@ -584,7 +684,7 @@ var Signer = class {
|
|
|
584
684
|
this._sponsorAccount
|
|
585
685
|
);
|
|
586
686
|
txHashes.push(txId);
|
|
587
|
-
this._claimedTransactionHashes
|
|
687
|
+
this._claimedTransactionHashes.push(txId);
|
|
588
688
|
}
|
|
589
689
|
return txHashes;
|
|
590
690
|
}
|
|
@@ -629,6 +729,45 @@ var signAndSendTransaction6 = async (chain, request, wallet, options = {}, cross
|
|
|
629
729
|
}
|
|
630
730
|
};
|
|
631
731
|
|
|
732
|
+
// src/providers/wormhole/utils.ts
|
|
733
|
+
var import_sdk2 = require("@wormhole-foundation/sdk");
|
|
734
|
+
async function createCCTPRoute(wh, sourceChain, destChain, tokens) {
|
|
735
|
+
const sourceToken = import_sdk2.Wormhole.tokenId(
|
|
736
|
+
sourceChain,
|
|
737
|
+
tokens[sourceChain].tokenId.address
|
|
738
|
+
);
|
|
739
|
+
const destToken = import_sdk2.Wormhole.tokenId(
|
|
740
|
+
destChain,
|
|
741
|
+
tokens[destChain].tokenId.address
|
|
742
|
+
);
|
|
743
|
+
const destContext = wh.getPlatform((0, import_sdk2.chainToPlatform)(destChain)).getChain(destChain);
|
|
744
|
+
const sourceContext = wh.getPlatform((0, import_sdk2.chainToPlatform)(sourceChain)).getChain(sourceChain);
|
|
745
|
+
const request = await import_sdk2.routes.RouteTransferRequest.create(
|
|
746
|
+
wh,
|
|
747
|
+
{ source: sourceToken, destination: destToken },
|
|
748
|
+
sourceContext,
|
|
749
|
+
destContext
|
|
750
|
+
);
|
|
751
|
+
const resolver = wh.resolver([import_sdk2.routes.CCTPRoute]);
|
|
752
|
+
const foundRoutes = await resolver.findRoutes(request);
|
|
753
|
+
const cctpRoute = foundRoutes[0];
|
|
754
|
+
if (!cctpRoute || !import_sdk2.routes.isManual(cctpRoute)) {
|
|
755
|
+
throw new Error("Expected manual CCTP route");
|
|
756
|
+
}
|
|
757
|
+
return { route: cctpRoute, request };
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// src/providers/wormhole/types.ts
|
|
761
|
+
var WithdrawError = class extends Error {
|
|
762
|
+
constructor(message, originChainTxnId, phase, cause) {
|
|
763
|
+
super(message);
|
|
764
|
+
this.name = "WithdrawError";
|
|
765
|
+
this.originChainTxnId = originChainTxnId;
|
|
766
|
+
this.phase = phase;
|
|
767
|
+
this.cause = cause;
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
|
|
632
771
|
// src/providers/wormhole/wormhole.ts
|
|
633
772
|
var WormholeProvider = class {
|
|
634
773
|
constructor(core) {
|
|
@@ -648,7 +787,7 @@ var WormholeProvider = class {
|
|
|
648
787
|
const isMainnet = dappNetwork === import_ts_sdk3.Network.MAINNET;
|
|
649
788
|
const platforms = [import_aptos.default, import_solana.default, import_evm.default, import_sui.default];
|
|
650
789
|
const solanaRpc = this.crossChainCore._dappConfig?.solanaConfig?.rpc ?? this.crossChainCore.CHAINS["Solana"]?.defaultRpc;
|
|
651
|
-
const wh = await (0,
|
|
790
|
+
const wh = await (0, import_sdk3.wormhole)(isMainnet ? "Mainnet" : "Testnet", platforms, {
|
|
652
791
|
chains: {
|
|
653
792
|
Solana: {
|
|
654
793
|
rpc: solanaRpc
|
|
@@ -661,33 +800,15 @@ var WormholeProvider = class {
|
|
|
661
800
|
if (!this._wormholeContext) {
|
|
662
801
|
throw new Error("Wormhole context not initialized");
|
|
663
802
|
}
|
|
664
|
-
const {
|
|
665
|
-
sourceChain,
|
|
666
|
-
destinationChain
|
|
667
|
-
);
|
|
668
|
-
const destContext = this._wormholeContext.getPlatform((0, import_sdk.chainToPlatform)(destinationChain)).getChain(destinationChain);
|
|
669
|
-
const sourceContext = this._wormholeContext.getPlatform((0, import_sdk.chainToPlatform)(sourceChain)).getChain(sourceChain);
|
|
670
|
-
logger.log("sourceContext", sourceContext);
|
|
671
|
-
logger.log("sourceToken", sourceToken);
|
|
672
|
-
logger.log("destContext", destContext);
|
|
673
|
-
logger.log("destToken", destToken);
|
|
674
|
-
const request = await import_sdk.routes.RouteTransferRequest.create(
|
|
803
|
+
const { route: cctpRoute, request } = await createCCTPRoute(
|
|
675
804
|
this._wormholeContext,
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
},
|
|
680
|
-
sourceContext,
|
|
681
|
-
destContext
|
|
805
|
+
sourceChain,
|
|
806
|
+
destinationChain,
|
|
807
|
+
this.crossChainCore.TOKENS
|
|
682
808
|
);
|
|
683
|
-
const resolver = this._wormholeContext.resolver([
|
|
684
|
-
import_sdk.routes.CCTPRoute
|
|
685
|
-
// manual CCTP
|
|
686
|
-
]);
|
|
687
|
-
const route = await resolver.findRoutes(request);
|
|
688
|
-
const cctpRoute = route[0];
|
|
689
809
|
this.wormholeRoute = cctpRoute;
|
|
690
810
|
this.wormholeRequest = request;
|
|
811
|
+
this.destinationChain = destinationChain;
|
|
691
812
|
return { route: cctpRoute, request };
|
|
692
813
|
}
|
|
693
814
|
async getQuote(input) {
|
|
@@ -709,12 +830,12 @@ var WormholeProvider = class {
|
|
|
709
830
|
const validated = await route.validate(request, transferParams);
|
|
710
831
|
if (!validated.valid) {
|
|
711
832
|
logger.log("invalid", validated.valid);
|
|
712
|
-
throw new Error(`Invalid quote: ${validated.error}`)
|
|
833
|
+
throw new Error(`Invalid quote: ${validated.error}`);
|
|
713
834
|
}
|
|
714
835
|
const quote = await route.quote(request, validated.params);
|
|
715
836
|
if (!quote.success) {
|
|
716
837
|
logger.log("quote failed", quote.success);
|
|
717
|
-
throw new Error(`Invalid quote: ${quote.error}`)
|
|
838
|
+
throw new Error(`Invalid quote: ${quote.error}`);
|
|
718
839
|
}
|
|
719
840
|
this.wormholeQuote = quote;
|
|
720
841
|
logger.log("quote", quote);
|
|
@@ -756,7 +877,7 @@ var WormholeProvider = class {
|
|
|
756
877
|
this.wormholeRequest,
|
|
757
878
|
signer,
|
|
758
879
|
this.wormholeQuote,
|
|
759
|
-
|
|
880
|
+
import_sdk3.Wormhole.chainAddress("Aptos", destinationAddress.toString())
|
|
760
881
|
);
|
|
761
882
|
const originChainTxnId = "originTxs" in receipt ? receipt.originTxs[receipt.originTxs.length - 1].txid : void 0;
|
|
762
883
|
return { originChainTxnId: originChainTxnId || "", receipt };
|
|
@@ -773,7 +894,7 @@ var WormholeProvider = class {
|
|
|
773
894
|
while (retries < maxRetries) {
|
|
774
895
|
try {
|
|
775
896
|
for await (receipt of this.wormholeRoute.track(receipt, 120 * 1e3)) {
|
|
776
|
-
if (receipt.state >=
|
|
897
|
+
if (receipt.state >= import_sdk3.TransferState.SourceInitiated) {
|
|
777
898
|
logger.log("Receipt is on track ", receipt);
|
|
778
899
|
try {
|
|
779
900
|
const signer = new AptosLocalSigner(
|
|
@@ -785,7 +906,7 @@ var WormholeProvider = class {
|
|
|
785
906
|
// the fee payer account
|
|
786
907
|
this.crossChainCore._dappConfig?.aptosNetwork
|
|
787
908
|
);
|
|
788
|
-
if (
|
|
909
|
+
if (import_sdk3.routes.isManual(this.wormholeRoute)) {
|
|
789
910
|
const circleAttestationReceipt = await this.wormholeRoute.complete(signer, receipt);
|
|
790
911
|
logger.log("Claim receipt: ", circleAttestationReceipt);
|
|
791
912
|
const destinationChainTxnId = signer.claimedTransactionHashes();
|
|
@@ -835,15 +956,14 @@ var WormholeProvider = class {
|
|
|
835
956
|
});
|
|
836
957
|
return { originChainTxnId, destinationChainTxnId };
|
|
837
958
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
}
|
|
959
|
+
// --- Split withdraw flow: initiateWithdraw + trackWithdraw + claimWithdraw ---
|
|
960
|
+
/**
|
|
961
|
+
* Phase 1: Initiates a withdraw by burning USDC on Aptos.
|
|
962
|
+
* The user signs the Aptos burn transaction via their wallet.
|
|
963
|
+
* Returns a receipt that can be tracked and later claimed.
|
|
964
|
+
*/
|
|
965
|
+
async initiateWithdraw(input) {
|
|
966
|
+
const { wallet, destinationAddress, sponsorAccount } = input;
|
|
847
967
|
if (!this._wormholeContext) {
|
|
848
968
|
throw new Error("Wormhole context not initialized");
|
|
849
969
|
}
|
|
@@ -852,69 +972,48 @@ var WormholeProvider = class {
|
|
|
852
972
|
}
|
|
853
973
|
const signer = new Signer(
|
|
854
974
|
this.getChainConfig("Aptos"),
|
|
855
|
-
(await
|
|
975
|
+
(await wallet.features["aptos:account"].account()).address.toString(),
|
|
856
976
|
{},
|
|
857
|
-
|
|
977
|
+
wallet,
|
|
858
978
|
this.crossChainCore,
|
|
859
979
|
sponsorAccount
|
|
860
980
|
);
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
logger.log(
|
|
865
|
-
"Wormhole.chainAddress",
|
|
866
|
-
import_sdk.Wormhole.chainAddress(sourceChain, input.destinationAddress.toString())
|
|
981
|
+
const wormholeDestAddress = import_sdk3.Wormhole.chainAddress(
|
|
982
|
+
this.destinationChain,
|
|
983
|
+
destinationAddress.toString()
|
|
867
984
|
);
|
|
868
|
-
|
|
985
|
+
const receipt = await this.wormholeRoute.initiate(
|
|
869
986
|
this.wormholeRequest,
|
|
870
987
|
signer,
|
|
871
988
|
this.wormholeQuote,
|
|
872
|
-
|
|
989
|
+
wormholeDestAddress
|
|
873
990
|
);
|
|
874
|
-
logger.log("receipt", receipt);
|
|
991
|
+
logger.log("initiateWithdraw receipt", receipt);
|
|
875
992
|
const originChainTxnId = "originTxs" in receipt ? receipt.originTxs[receipt.originTxs.length - 1].txid : void 0;
|
|
993
|
+
return { originChainTxnId: originChainTxnId || "", receipt };
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Phase 2: Tracks a withdraw receipt until attestation is ready.
|
|
997
|
+
* This polls Wormhole and returns once the receipt reaches the Attested state.
|
|
998
|
+
*/
|
|
999
|
+
async trackWithdraw(receipt) {
|
|
1000
|
+
if (!this.wormholeRoute) {
|
|
1001
|
+
throw new Error("Wormhole route not initialized");
|
|
1002
|
+
}
|
|
876
1003
|
let retries = 0;
|
|
877
1004
|
const maxRetries = 5;
|
|
878
1005
|
const baseDelay = 1e3;
|
|
879
1006
|
while (retries < maxRetries) {
|
|
880
1007
|
try {
|
|
881
1008
|
for await (receipt of this.wormholeRoute.track(receipt, 120 * 1e3)) {
|
|
882
|
-
if (receipt.state >=
|
|
883
|
-
logger.log("
|
|
884
|
-
|
|
885
|
-
const signer2 = new Signer(
|
|
886
|
-
this.getChainConfig(sourceChain),
|
|
887
|
-
destinationAddress.toString(),
|
|
888
|
-
{},
|
|
889
|
-
wallet,
|
|
890
|
-
this.crossChainCore
|
|
891
|
-
);
|
|
892
|
-
if (import_sdk.routes.isManual(this.wormholeRoute)) {
|
|
893
|
-
const circleAttestationReceipt = await this.wormholeRoute.complete(signer2, receipt);
|
|
894
|
-
logger.log("Claim receipt: ", circleAttestationReceipt);
|
|
895
|
-
const destinationChainTxnId = signer2.claimedTransactionHashes();
|
|
896
|
-
return {
|
|
897
|
-
originChainTxnId: originChainTxnId || "",
|
|
898
|
-
destinationChainTxnId
|
|
899
|
-
};
|
|
900
|
-
} else {
|
|
901
|
-
return {
|
|
902
|
-
originChainTxnId: originChainTxnId || "",
|
|
903
|
-
destinationChainTxnId: ""
|
|
904
|
-
};
|
|
905
|
-
}
|
|
906
|
-
} catch (e) {
|
|
907
|
-
console.error("Failed to claim", e);
|
|
908
|
-
return {
|
|
909
|
-
originChainTxnId: originChainTxnId || "",
|
|
910
|
-
destinationChainTxnId: ""
|
|
911
|
-
};
|
|
912
|
-
}
|
|
1009
|
+
if (receipt.state >= import_sdk3.TransferState.Attested) {
|
|
1010
|
+
logger.log("trackWithdraw: receipt attested", receipt);
|
|
1011
|
+
return receipt;
|
|
913
1012
|
}
|
|
914
1013
|
}
|
|
915
1014
|
} catch (e) {
|
|
916
1015
|
console.error(
|
|
917
|
-
`Error tracking
|
|
1016
|
+
`Error tracking withdraw (attempt ${retries + 1} / ${maxRetries}):`,
|
|
918
1017
|
e
|
|
919
1018
|
);
|
|
920
1019
|
const delay = baseDelay * Math.pow(2, retries);
|
|
@@ -922,10 +1021,108 @@ var WormholeProvider = class {
|
|
|
922
1021
|
retries++;
|
|
923
1022
|
}
|
|
924
1023
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1024
|
+
throw new Error("Failed to track withdraw to attested state");
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Phase 3: Claims the withdraw on the destination chain.
|
|
1028
|
+
*
|
|
1029
|
+
* If the destination is Solana and `solanaConfig.serverClaimUrl` is configured
|
|
1030
|
+
* in the dapp config, the SDK automatically POSTs the attested receipt to that
|
|
1031
|
+
* URL — no wallet popup required. The dapp's server endpoint handles signing
|
|
1032
|
+
* and submitting the claim transaction.
|
|
1033
|
+
*
|
|
1034
|
+
* Otherwise falls back to the wallet-based Signer (triggers wallet popup).
|
|
1035
|
+
*/
|
|
1036
|
+
async claimWithdraw(input) {
|
|
1037
|
+
const { sourceChain, destinationAddress, receipt } = input;
|
|
1038
|
+
const serverClaimUrl = this.crossChainCore._dappConfig?.solanaConfig?.serverClaimUrl;
|
|
1039
|
+
if (sourceChain === "Solana" && serverClaimUrl) {
|
|
1040
|
+
logger.log("claimWithdraw: using server-side claim via", serverClaimUrl);
|
|
1041
|
+
const response = await fetch(serverClaimUrl, {
|
|
1042
|
+
method: "POST",
|
|
1043
|
+
headers: { "Content-Type": "application/json" },
|
|
1044
|
+
body: JSON.stringify({
|
|
1045
|
+
receipt: serializeReceipt(receipt),
|
|
1046
|
+
destinationAddress,
|
|
1047
|
+
sourceChain
|
|
1048
|
+
})
|
|
1049
|
+
});
|
|
1050
|
+
if (!response.ok) {
|
|
1051
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1052
|
+
throw new Error(
|
|
1053
|
+
errorData.error || `Server-side claim failed with status ${response.status}`
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
const result = await response.json();
|
|
1057
|
+
return { destinationChainTxnId: result.destinationChainTxnId };
|
|
1058
|
+
}
|
|
1059
|
+
if (!this.wormholeRoute) {
|
|
1060
|
+
throw new Error("Wormhole route not initialized");
|
|
1061
|
+
}
|
|
1062
|
+
if (!input.wallet) {
|
|
1063
|
+
throw new Error(
|
|
1064
|
+
"Wallet is required for claim when serverClaimUrl is not configured"
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
const claimSigner = new Signer(
|
|
1068
|
+
this.getChainConfig(sourceChain),
|
|
1069
|
+
destinationAddress,
|
|
1070
|
+
{},
|
|
1071
|
+
input.wallet,
|
|
1072
|
+
this.crossChainCore
|
|
1073
|
+
);
|
|
1074
|
+
if (import_sdk3.routes.isManual(this.wormholeRoute)) {
|
|
1075
|
+
const circleAttestationReceipt = await this.wormholeRoute.complete(
|
|
1076
|
+
claimSigner,
|
|
1077
|
+
receipt
|
|
1078
|
+
);
|
|
1079
|
+
logger.log("claimWithdraw receipt:", circleAttestationReceipt);
|
|
1080
|
+
const destinationChainTxnId = claimSigner.claimedTransactionHashes();
|
|
1081
|
+
return { destinationChainTxnId };
|
|
1082
|
+
} else {
|
|
1083
|
+
throw new Error("Automatic route not supported for manual claim");
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Withdraws USDC from Aptos to a destination chain.
|
|
1088
|
+
* Orchestrates all three phases internally:
|
|
1089
|
+
* 1. Initiate — user signs the Aptos burn transaction
|
|
1090
|
+
* 2. Track — wait for Wormhole attestation
|
|
1091
|
+
* 3. Claim — if serverClaimUrl is configured for Solana, delegates to
|
|
1092
|
+
* the server; otherwise uses the wallet-based signer.
|
|
1093
|
+
*
|
|
1094
|
+
* The optional `onPhaseChange` callback lets the dapp update its UI
|
|
1095
|
+
* as the flow progresses.
|
|
1096
|
+
*/
|
|
1097
|
+
async withdraw(input) {
|
|
1098
|
+
const { sourceChain, wallet, destinationAddress, sponsorAccount, onPhaseChange } = input;
|
|
1099
|
+
onPhaseChange?.("initiating");
|
|
1100
|
+
const { originChainTxnId, receipt } = await this.initiateWithdraw({
|
|
1101
|
+
wallet,
|
|
1102
|
+
destinationAddress,
|
|
1103
|
+
sponsorAccount
|
|
1104
|
+
});
|
|
1105
|
+
let currentPhase = "tracking";
|
|
1106
|
+
try {
|
|
1107
|
+
onPhaseChange?.("tracking");
|
|
1108
|
+
const attestedReceipt = await this.trackWithdraw(receipt);
|
|
1109
|
+
currentPhase = "claiming";
|
|
1110
|
+
onPhaseChange?.("claiming");
|
|
1111
|
+
const { destinationChainTxnId } = await this.claimWithdraw({
|
|
1112
|
+
sourceChain,
|
|
1113
|
+
destinationAddress: destinationAddress.toString(),
|
|
1114
|
+
receipt: attestedReceipt,
|
|
1115
|
+
wallet
|
|
1116
|
+
});
|
|
1117
|
+
return { originChainTxnId, destinationChainTxnId };
|
|
1118
|
+
} catch (error) {
|
|
1119
|
+
throw new WithdrawError(
|
|
1120
|
+
error?.message ?? "Withdraw failed after Aptos burn",
|
|
1121
|
+
originChainTxnId,
|
|
1122
|
+
currentPhase,
|
|
1123
|
+
error
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
929
1126
|
}
|
|
930
1127
|
getChainConfig(chain) {
|
|
931
1128
|
const chainConfig = this.crossChainCore.CHAINS[chain];
|
|
@@ -934,16 +1131,119 @@ var WormholeProvider = class {
|
|
|
934
1131
|
}
|
|
935
1132
|
return chainConfig;
|
|
936
1133
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
// src/providers/wormhole/signers/SolanaLocalSigner.ts
|
|
1137
|
+
var import_web34 = require("@solana/web3.js");
|
|
1138
|
+
var SolanaLocalSigner = class {
|
|
1139
|
+
constructor(config) {
|
|
1140
|
+
this._claimedTransactionHashes = [];
|
|
1141
|
+
this.keypair = config.keypair;
|
|
1142
|
+
this.connection = config.connection;
|
|
1143
|
+
this.commitment = config.commitment ?? "finalized";
|
|
1144
|
+
this.retryIntervalMs = config.retryIntervalMs ?? 5e3;
|
|
1145
|
+
this.priorityFeeConfig = config.priorityFeeConfig;
|
|
1146
|
+
this.verbose = config.verbose ?? false;
|
|
1147
|
+
}
|
|
1148
|
+
chain() {
|
|
1149
|
+
return "Solana";
|
|
1150
|
+
}
|
|
1151
|
+
address() {
|
|
1152
|
+
return this.keypair.publicKey.toBase58();
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Returns all transaction hashes from the most recent signAndSend call,
|
|
1156
|
+
* joined by comma. If only one transaction was signed, returns a single hash string.
|
|
1157
|
+
*/
|
|
1158
|
+
claimedTransactionHashes() {
|
|
1159
|
+
return this._claimedTransactionHashes.join(",");
|
|
1160
|
+
}
|
|
1161
|
+
async signAndSend(txs) {
|
|
1162
|
+
const txHashes = [];
|
|
1163
|
+
this._claimedTransactionHashes = [];
|
|
1164
|
+
for (const tx of txs) {
|
|
1165
|
+
const txId = await this.signAndSendTransaction(tx);
|
|
1166
|
+
txHashes.push(txId);
|
|
1167
|
+
this._claimedTransactionHashes.push(txId);
|
|
1168
|
+
}
|
|
1169
|
+
return txHashes;
|
|
1170
|
+
}
|
|
1171
|
+
async signAndSendTransaction(request) {
|
|
1172
|
+
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash(this.commitment);
|
|
1173
|
+
let unsignedTx = request.transaction ?? request;
|
|
1174
|
+
const additionalSigners = request.transaction?.signers;
|
|
1175
|
+
const MAX_UNWRAP_DEPTH = 10;
|
|
1176
|
+
let unwrapDepth = 0;
|
|
1177
|
+
while (unsignedTx && typeof unsignedTx === "object" && "transaction" in unsignedTx && !(unsignedTx instanceof import_web34.Transaction) && !("signatures" in unsignedTx && "message" in unsignedTx)) {
|
|
1178
|
+
if (++unwrapDepth > MAX_UNWRAP_DEPTH) {
|
|
1179
|
+
throw new Error(
|
|
1180
|
+
"Transaction unwrapping exceeded maximum depth \u2014 possible circular nesting"
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
unsignedTx = unsignedTx.transaction;
|
|
1184
|
+
}
|
|
1185
|
+
const isVersioned = unsignedTx.message !== void 0 && unsignedTx.signatures !== void 0 && typeof unsignedTx.message.recentBlockhash !== "undefined";
|
|
1186
|
+
if (isVersioned) {
|
|
1187
|
+
unsignedTx.message.recentBlockhash = blockhash;
|
|
1188
|
+
if (this.verbose || this.priorityFeeConfig) {
|
|
1189
|
+
console.warn(
|
|
1190
|
+
"SolanaLocalSigner: Versioned transaction detected \u2014 priority fees are not applied. Consider using legacy transactions if priority fees are required."
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
unsignedTx.sign([this.keypair]);
|
|
1194
|
+
if (additionalSigners && additionalSigners.length > 0) {
|
|
1195
|
+
unsignedTx.sign(additionalSigners);
|
|
1196
|
+
}
|
|
1197
|
+
} else if (unsignedTx instanceof import_web34.Transaction) {
|
|
1198
|
+
unsignedTx.recentBlockhash = blockhash;
|
|
1199
|
+
unsignedTx.lastValidBlockHeight = lastValidBlockHeight;
|
|
1200
|
+
if (this.priorityFeeConfig) {
|
|
1201
|
+
await addPriorityFeeInstructions(
|
|
1202
|
+
this.connection,
|
|
1203
|
+
unsignedTx,
|
|
1204
|
+
this.priorityFeeConfig,
|
|
1205
|
+
this.verbose
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
if (additionalSigners && additionalSigners.length > 0) {
|
|
1209
|
+
unsignedTx.sign(this.keypair, ...additionalSigners);
|
|
1210
|
+
} else {
|
|
1211
|
+
unsignedTx.sign(this.keypair);
|
|
1212
|
+
}
|
|
1213
|
+
} else if (unsignedTx.recentBlockhash !== void 0 || unsignedTx.feePayer !== void 0) {
|
|
1214
|
+
unsignedTx.recentBlockhash = blockhash;
|
|
1215
|
+
unsignedTx.lastValidBlockHeight = lastValidBlockHeight;
|
|
1216
|
+
if (this.priorityFeeConfig) {
|
|
1217
|
+
await addPriorityFeeInstructions(
|
|
1218
|
+
this.connection,
|
|
1219
|
+
unsignedTx,
|
|
1220
|
+
this.priorityFeeConfig,
|
|
1221
|
+
this.verbose
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
if (additionalSigners && additionalSigners.length > 0) {
|
|
1225
|
+
unsignedTx.sign(this.keypair, ...additionalSigners);
|
|
1226
|
+
} else {
|
|
1227
|
+
unsignedTx.sign(this.keypair);
|
|
1228
|
+
}
|
|
1229
|
+
} else {
|
|
1230
|
+
throw new Error(
|
|
1231
|
+
`Unsupported transaction type: ${unsignedTx?.constructor?.name}`
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
const serializedTx = unsignedTx.serialize();
|
|
1235
|
+
const signature = await sendAndConfirmTransaction(
|
|
1236
|
+
serializedTx,
|
|
1237
|
+
blockhash,
|
|
1238
|
+
lastValidBlockHeight,
|
|
1239
|
+
{
|
|
1240
|
+
connection: this.connection,
|
|
1241
|
+
commitment: this.commitment,
|
|
1242
|
+
retryIntervalMs: this.retryIntervalMs,
|
|
1243
|
+
verbose: this.verbose
|
|
1244
|
+
}
|
|
945
1245
|
);
|
|
946
|
-
return
|
|
1246
|
+
return signature;
|
|
947
1247
|
}
|
|
948
1248
|
};
|
|
949
1249
|
|
|
@@ -1017,8 +1317,8 @@ var testnetChains = {
|
|
|
1017
1317
|
key: "Solana",
|
|
1018
1318
|
context: "Solana" /* SOLANA */,
|
|
1019
1319
|
displayName: "Solana",
|
|
1020
|
-
explorerUrl: "https://
|
|
1021
|
-
explorerName: "
|
|
1320
|
+
explorerUrl: "https://solscan.io",
|
|
1321
|
+
explorerName: "Solscan",
|
|
1022
1322
|
chainId: 0,
|
|
1023
1323
|
icon: "Solana",
|
|
1024
1324
|
symbol: "SOL",
|
|
@@ -1141,8 +1441,8 @@ var mainnetChains = {
|
|
|
1141
1441
|
key: "Solana",
|
|
1142
1442
|
context: "Solana" /* SOLANA */,
|
|
1143
1443
|
displayName: "Solana",
|
|
1144
|
-
explorerUrl: "https://
|
|
1145
|
-
explorerName: "
|
|
1444
|
+
explorerUrl: "https://solscan.io",
|
|
1445
|
+
explorerName: "Solscan",
|
|
1146
1446
|
chainId: 0,
|
|
1147
1447
|
icon: "Solana",
|
|
1148
1448
|
symbol: "SOL",
|
|
@@ -1294,15 +1594,15 @@ var mainnetTokens = {
|
|
|
1294
1594
|
|
|
1295
1595
|
// src/utils/getUsdcBalance.ts
|
|
1296
1596
|
var import_ts_sdk4 = require("@aptos-labs/ts-sdk");
|
|
1297
|
-
var
|
|
1597
|
+
var import_web35 = require("@solana/web3.js");
|
|
1298
1598
|
var import_ethers2 = require("ethers");
|
|
1299
1599
|
var import_client = require("@mysten/sui/client");
|
|
1300
1600
|
var getSolanaWalletUSDCBalance = async (walletAddress, aptosNetwork, rpc) => {
|
|
1301
|
-
const address = new
|
|
1601
|
+
const address = new import_web35.PublicKey(walletAddress);
|
|
1302
1602
|
const tokenAddress = aptosNetwork === import_ts_sdk4.Network.MAINNET ? mainnetTokens["Solana"].tokenId.address : testnetTokens["Solana"].tokenId.address;
|
|
1303
|
-
const connection = new
|
|
1603
|
+
const connection = new import_web35.Connection(rpc);
|
|
1304
1604
|
const splToken = await connection.getTokenAccountsByOwner(address, {
|
|
1305
|
-
mint: new
|
|
1605
|
+
mint: new import_web35.PublicKey(tokenAddress)
|
|
1306
1606
|
});
|
|
1307
1607
|
if (splToken.value.length === 0) {
|
|
1308
1608
|
return "0";
|
|
@@ -1448,9 +1748,14 @@ var import_ts_sdk7 = require("@aptos-labs/ts-sdk");
|
|
|
1448
1748
|
Network,
|
|
1449
1749
|
NetworkToChainId,
|
|
1450
1750
|
NetworkToNodeAPI,
|
|
1751
|
+
SolanaLocalSigner,
|
|
1752
|
+
WithdrawError,
|
|
1451
1753
|
WormholeProvider,
|
|
1754
|
+
createCCTPRoute,
|
|
1755
|
+
deserializeReceipt,
|
|
1452
1756
|
mainnetChains,
|
|
1453
1757
|
mainnetTokens,
|
|
1758
|
+
serializeReceipt,
|
|
1454
1759
|
signAndSendTransaction,
|
|
1455
1760
|
testnetChains,
|
|
1456
1761
|
testnetTokens
|