@aptos-labs/cross-chain-core 5.8.2 → 5.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/CrossChainCore.d.ts +20 -0
- package/dist/CrossChainCore.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +580 -275
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +583 -274
- package/dist/index.mjs.map +1 -1
- package/dist/providers/wormhole/index.d.ts +2 -0
- package/dist/providers/wormhole/index.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/AptosLocalSigner.d.ts +1 -1
- package/dist/providers/wormhole/signers/AptosLocalSigner.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/Signer.d.ts +1 -1
- package/dist/providers/wormhole/signers/Signer.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/SolanaLocalSigner.d.ts +65 -0
- package/dist/providers/wormhole/signers/SolanaLocalSigner.d.ts.map +1 -0
- package/dist/providers/wormhole/signers/SolanaSigner.d.ts +12 -20
- package/dist/providers/wormhole/signers/SolanaSigner.d.ts.map +1 -1
- package/dist/providers/wormhole/signers/solanaUtils.d.ts +68 -0
- package/dist/providers/wormhole/signers/solanaUtils.d.ts.map +1 -0
- package/dist/providers/wormhole/types.d.ts +43 -0
- package/dist/providers/wormhole/types.d.ts.map +1 -1
- package/dist/providers/wormhole/utils.d.ts +26 -0
- package/dist/providers/wormhole/utils.d.ts.map +1 -0
- package/dist/providers/wormhole/wormhole.d.ts +36 -6
- package/dist/providers/wormhole/wormhole.d.ts.map +1 -1
- package/dist/utils/receiptSerialization.d.ts +38 -0
- package/dist/utils/receiptSerialization.d.ts.map +1 -0
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/CrossChainCore.ts +20 -0
- package/src/config/mainnet/chains.ts +2 -2
- package/src/config/testnet/chains.ts +2 -2
- package/src/index.ts +1 -0
- package/src/providers/wormhole/index.ts +2 -0
- package/src/providers/wormhole/signers/AptosLocalSigner.ts +4 -4
- package/src/providers/wormhole/signers/AptosSigner.ts +1 -1
- package/src/providers/wormhole/signers/EthereumSigner.ts +3 -3
- package/src/providers/wormhole/signers/Signer.ts +4 -4
- package/src/providers/wormhole/signers/SolanaLocalSigner.ts +243 -0
- package/src/providers/wormhole/signers/SolanaSigner.ts +45 -337
- package/src/providers/wormhole/signers/solanaUtils.ts +422 -0
- package/src/providers/wormhole/types.ts +68 -0
- package/src/providers/wormhole/utils.ts +72 -0
- package/src/providers/wormhole/wormhole.ts +182 -120
- package/src/utils/receiptSerialization.ts +141 -0
- package/src/version.ts +1 -1
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,6 +33,92 @@ 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,
|
|
@@ -41,11 +126,11 @@ import {
|
|
|
41
126
|
} from "@aptos-labs/ts-sdk";
|
|
42
127
|
var AptosLocalSigner = class {
|
|
43
128
|
constructor(chain, options, wallet, feePayerAccount, dappNetwork) {
|
|
129
|
+
this._claimedTransactionHashes = [];
|
|
44
130
|
this._chain = chain;
|
|
45
131
|
this._options = options;
|
|
46
132
|
this._wallet = wallet;
|
|
47
133
|
this._sponsorAccount = feePayerAccount;
|
|
48
|
-
this._claimedTransactionHashes = "";
|
|
49
134
|
this._dappNetwork = dappNetwork;
|
|
50
135
|
}
|
|
51
136
|
chain() {
|
|
@@ -55,10 +140,11 @@ var AptosLocalSigner = class {
|
|
|
55
140
|
return this._wallet.accountAddress.toString();
|
|
56
141
|
}
|
|
57
142
|
claimedTransactionHashes() {
|
|
58
|
-
return this._claimedTransactionHashes;
|
|
143
|
+
return this._claimedTransactionHashes.join(",");
|
|
59
144
|
}
|
|
60
145
|
async signAndSend(txs) {
|
|
61
146
|
const txHashes = [];
|
|
147
|
+
this._claimedTransactionHashes = [];
|
|
62
148
|
for (const tx of txs) {
|
|
63
149
|
const txId = await signAndSendTransaction(
|
|
64
150
|
tx,
|
|
@@ -67,7 +153,7 @@ var AptosLocalSigner = class {
|
|
|
67
153
|
this._dappNetwork
|
|
68
154
|
);
|
|
69
155
|
txHashes.push(txId);
|
|
70
|
-
this._claimedTransactionHashes
|
|
156
|
+
this._claimedTransactionHashes.push(txId);
|
|
71
157
|
}
|
|
72
158
|
return txHashes;
|
|
73
159
|
}
|
|
@@ -118,125 +204,131 @@ async function signAndSendTransaction(request, wallet, sponsorAccount, dappNetwo
|
|
|
118
204
|
}
|
|
119
205
|
|
|
120
206
|
// src/providers/wormhole/signers/SolanaSigner.ts
|
|
207
|
+
import {
|
|
208
|
+
Transaction as Transaction2
|
|
209
|
+
} from "@solana/web3.js";
|
|
210
|
+
import { Connection as Connection2 } from "@solana/web3.js";
|
|
211
|
+
import { SolanaDerivedWallet } from "@aptos-labs/derived-wallet-solana";
|
|
212
|
+
|
|
213
|
+
// src/providers/wormhole/signers/solanaUtils.ts
|
|
121
214
|
import {
|
|
122
215
|
ComputeBudgetProgram,
|
|
123
216
|
LAMPORTS_PER_SOL
|
|
124
217
|
} from "@solana/web3.js";
|
|
125
|
-
import { Transaction } from "@solana/web3.js";
|
|
126
218
|
import {
|
|
127
219
|
determinePriorityFee,
|
|
128
220
|
determinePriorityFeeTritonOne
|
|
129
221
|
} 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();
|
|
222
|
+
async function sendAndConfirmTransaction(serializedTx, blockhash, lastValidBlockHeight, config) {
|
|
223
|
+
const { connection, commitment, retryIntervalMs = 5e3, verbose = false } = config;
|
|
162
224
|
const sendOptions = {
|
|
163
225
|
skipPreflight: true,
|
|
164
226
|
maxRetries: 0,
|
|
165
|
-
|
|
166
|
-
// See PR and linked issue for why setting this matters: https://github.com/anza-xyz/agave/pull/483
|
|
227
|
+
preflightCommitment: commitment
|
|
167
228
|
};
|
|
168
|
-
signature = await connection.sendRawTransaction(serializedTx, sendOptions);
|
|
169
|
-
confirmTransactionPromise = connection.confirmTransaction(
|
|
170
|
-
{
|
|
171
|
-
signature,
|
|
172
|
-
blockhash,
|
|
173
|
-
lastValidBlockHeight
|
|
174
|
-
},
|
|
229
|
+
const signature = await connection.sendRawTransaction(serializedTx, sendOptions);
|
|
230
|
+
const confirmTransactionPromise = connection.confirmTransaction(
|
|
231
|
+
{ signature, blockhash, lastValidBlockHeight },
|
|
175
232
|
commitment
|
|
176
233
|
);
|
|
177
|
-
|
|
234
|
+
let confirmedTx = null;
|
|
235
|
+
let txSendAttempts = 1;
|
|
178
236
|
while (!confirmedTx) {
|
|
179
237
|
confirmedTx = await Promise.race([
|
|
180
238
|
confirmTransactionPromise,
|
|
181
239
|
new Promise(
|
|
182
|
-
(resolve) => setTimeout(() =>
|
|
183
|
-
resolve(null);
|
|
184
|
-
}, txRetryInterval)
|
|
240
|
+
(resolve) => setTimeout(() => resolve(null), retryIntervalMs)
|
|
185
241
|
)
|
|
186
242
|
]);
|
|
187
|
-
if (confirmedTx)
|
|
188
|
-
|
|
243
|
+
if (confirmedTx) break;
|
|
244
|
+
if (verbose) {
|
|
245
|
+
console.log(
|
|
246
|
+
`Tx not confirmed after ${retryIntervalMs * txSendAttempts++}ms, resending`
|
|
247
|
+
);
|
|
189
248
|
}
|
|
190
|
-
console.log(
|
|
191
|
-
`Tx not confirmed after ${txRetryInterval * txSendAttempts++}ms, resending`
|
|
192
|
-
);
|
|
193
249
|
try {
|
|
194
250
|
await connection.sendRawTransaction(serializedTx, sendOptions);
|
|
195
251
|
} catch (e) {
|
|
196
|
-
|
|
252
|
+
if (verbose) {
|
|
253
|
+
console.error("Failed to resend transaction:", e);
|
|
254
|
+
}
|
|
197
255
|
}
|
|
198
256
|
}
|
|
199
257
|
if (confirmedTx.value.err) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
try {
|
|
203
|
-
errorMessage = `Transaction failed: ${JSON.stringify(
|
|
204
|
-
confirmedTx.value.err,
|
|
205
|
-
(_key, value) => typeof value === "bigint" ? value.toString() : value
|
|
206
|
-
// Handle bigint props
|
|
207
|
-
)}`;
|
|
208
|
-
} catch (e) {
|
|
209
|
-
errorMessage = `Transaction failed: Unknown error`;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
throw new Error(`Transaction failed: ${errorMessage}`).message;
|
|
258
|
+
const errorMessage = formatTransactionError(confirmedTx.value.err);
|
|
259
|
+
throw new Error(errorMessage);
|
|
213
260
|
}
|
|
214
261
|
return signature;
|
|
215
262
|
}
|
|
216
|
-
|
|
217
|
-
|
|
263
|
+
function formatTransactionError(err) {
|
|
264
|
+
if (typeof err === "object" && err !== null) {
|
|
265
|
+
try {
|
|
266
|
+
return `Transaction failed: ${JSON.stringify(
|
|
267
|
+
err,
|
|
268
|
+
(_key, value) => typeof value === "bigint" ? value.toString() : value
|
|
269
|
+
)}`;
|
|
270
|
+
} catch {
|
|
271
|
+
return "Transaction failed: Unknown error";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return `Transaction failed: ${err}`;
|
|
275
|
+
}
|
|
276
|
+
async function addPriorityFeeInstructions(connection, transaction, priorityFeeConfig, verbose = false) {
|
|
218
277
|
const computeBudgetIxFilter = (ix) => ix.programId.toString() !== "ComputeBudget111111111111111111111111111111";
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
278
|
+
transaction.instructions = transaction.instructions.filter(computeBudgetIxFilter);
|
|
279
|
+
const instructions = await createPriorityFeeInstructions(
|
|
280
|
+
connection,
|
|
281
|
+
transaction,
|
|
282
|
+
priorityFeeConfig,
|
|
283
|
+
verbose
|
|
223
284
|
);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
285
|
+
transaction.add(...instructions);
|
|
286
|
+
return transaction;
|
|
287
|
+
}
|
|
288
|
+
async function createPriorityFeeInstructions(connection, transaction, priorityFeeConfig, verbose = false) {
|
|
289
|
+
const unitsUsed = await simulateAndGetComputeUnits(connection, transaction);
|
|
290
|
+
const unitBudget = Math.floor(unitsUsed * 1.2);
|
|
291
|
+
const instructions = [];
|
|
292
|
+
instructions.push(
|
|
293
|
+
ComputeBudgetProgram.setComputeUnitLimit({
|
|
294
|
+
units: unitBudget
|
|
295
|
+
})
|
|
230
296
|
);
|
|
231
|
-
|
|
297
|
+
const {
|
|
298
|
+
percentile = 0.9,
|
|
299
|
+
percentileMultiple = 1,
|
|
300
|
+
min = 1e5,
|
|
301
|
+
max = 1e8
|
|
302
|
+
} = priorityFeeConfig ?? {};
|
|
303
|
+
const rpcProvider = determineRpcProvider(connection.rpcEndpoint);
|
|
304
|
+
const { fee, methodUsed } = await calculatePriorityFee(
|
|
305
|
+
connection,
|
|
306
|
+
transaction,
|
|
307
|
+
rpcProvider,
|
|
308
|
+
{ percentile, percentileMultiple, min, max }
|
|
309
|
+
);
|
|
310
|
+
if (verbose) {
|
|
311
|
+
const maxFeeInSol = fee / 1e6 / LAMPORTS_PER_SOL * unitBudget;
|
|
312
|
+
console.table({
|
|
313
|
+
"RPC Provider": rpcProvider,
|
|
314
|
+
"Method used": methodUsed,
|
|
315
|
+
"Percentile used": percentile,
|
|
316
|
+
"Multiple used": percentileMultiple,
|
|
317
|
+
"Compute budget": unitBudget,
|
|
318
|
+
"Priority fee": fee,
|
|
319
|
+
"Max fee in SOL": maxFeeInSol
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
instructions.push(
|
|
323
|
+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: fee })
|
|
324
|
+
);
|
|
325
|
+
return instructions;
|
|
232
326
|
}
|
|
233
|
-
async function
|
|
327
|
+
async function simulateAndGetComputeUnits(connection, transaction) {
|
|
234
328
|
let unitsUsed = 2e5;
|
|
235
329
|
let simulationAttempts = 0;
|
|
236
330
|
simulationLoop: while (true) {
|
|
237
|
-
const response = await connection.simulateTransaction(
|
|
238
|
-
transaction
|
|
239
|
-
);
|
|
331
|
+
const response = await connection.simulateTransaction(transaction);
|
|
240
332
|
if (response.value.err) {
|
|
241
333
|
if (checkKnownSimulationError(response.value)) {
|
|
242
334
|
if (simulationAttempts < 5) {
|
|
@@ -253,7 +345,7 @@ async function createPriorityFeeInstructions(connection, transaction, crossChain
|
|
|
253
345
|
`Simulation failed: ${JSON.stringify(response.value.err)}
|
|
254
346
|
Logs:
|
|
255
347
|
${(response.value.logs || []).join("\n ")}`
|
|
256
|
-
)
|
|
348
|
+
);
|
|
257
349
|
} else {
|
|
258
350
|
if (response.value.unitsConsumed) {
|
|
259
351
|
unitsUsed = response.value.unitsConsumed;
|
|
@@ -261,41 +353,13 @@ ${(response.value.logs || []).join("\n ")}`
|
|
|
261
353
|
break;
|
|
262
354
|
}
|
|
263
355
|
}
|
|
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
|
-
}
|
|
356
|
+
return unitsUsed;
|
|
357
|
+
}
|
|
358
|
+
async function calculatePriorityFee(connection, transaction, rpcProvider, config) {
|
|
359
|
+
const { percentile, percentileMultiple, min, max } = config;
|
|
360
|
+
if (rpcProvider === "triton") {
|
|
297
361
|
try {
|
|
298
|
-
const
|
|
362
|
+
const fee = await determinePriorityFeeTritonOne(
|
|
299
363
|
connection,
|
|
300
364
|
transaction,
|
|
301
365
|
percentile,
|
|
@@ -303,37 +367,25 @@ ${(response.value.logs || []).join("\n ")}`
|
|
|
303
367
|
min,
|
|
304
368
|
max
|
|
305
369
|
);
|
|
306
|
-
return {
|
|
307
|
-
fee: fee2,
|
|
308
|
-
methodUsed: "default"
|
|
309
|
-
};
|
|
370
|
+
return { fee, methodUsed: "triton" };
|
|
310
371
|
} catch (e) {
|
|
311
372
|
console.warn(`Failed to determine priority fee using Triton RPC:`, e);
|
|
312
|
-
return {
|
|
313
|
-
fee: min,
|
|
314
|
-
methodUsed: "minimum"
|
|
315
|
-
};
|
|
316
373
|
}
|
|
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;
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
const fee = await determinePriorityFee(
|
|
377
|
+
connection,
|
|
378
|
+
transaction,
|
|
379
|
+
percentile,
|
|
380
|
+
percentileMultiple,
|
|
381
|
+
min,
|
|
382
|
+
max
|
|
383
|
+
);
|
|
384
|
+
return { fee, methodUsed: "default" };
|
|
385
|
+
} catch (e) {
|
|
386
|
+
console.warn(`Failed to determine priority fee:`, e);
|
|
387
|
+
return { fee: min, methodUsed: "minimum" };
|
|
388
|
+
}
|
|
337
389
|
}
|
|
338
390
|
function checkKnownSimulationError(response) {
|
|
339
391
|
const errors = {};
|
|
@@ -350,61 +402,106 @@ function checkKnownSimulationError(response) {
|
|
|
350
402
|
}
|
|
351
403
|
}
|
|
352
404
|
}
|
|
353
|
-
if (
|
|
405
|
+
if (Object.keys(errors).length === 0) {
|
|
354
406
|
return false;
|
|
355
407
|
}
|
|
356
408
|
console.table(errors);
|
|
357
409
|
return true;
|
|
358
410
|
}
|
|
359
|
-
|
|
360
|
-
return
|
|
411
|
+
function isHostOrSubdomainOf(hostname, base) {
|
|
412
|
+
return hostname === base || hostname.endsWith(`.${base}`);
|
|
361
413
|
}
|
|
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
414
|
function determineRpcProvider(endpoint) {
|
|
374
415
|
try {
|
|
375
416
|
const url = new URL(endpoint);
|
|
376
417
|
const hostname = url.hostname;
|
|
377
|
-
if (hostname
|
|
418
|
+
if (isHostOrSubdomainOf(hostname, "rpcpool.com") || isHostOrSubdomainOf(hostname, "triton.one")) {
|
|
378
419
|
return "triton";
|
|
379
|
-
} else if (hostname
|
|
420
|
+
} else if (isHostOrSubdomainOf(hostname, "helius-rpc.com") || isHostOrSubdomainOf(hostname, "helius.xyz")) {
|
|
380
421
|
return "helius";
|
|
381
|
-
} else if (hostname
|
|
422
|
+
} else if (isHostOrSubdomainOf(hostname, "ankr.com")) {
|
|
382
423
|
return "ankr";
|
|
383
424
|
} else {
|
|
384
425
|
return "unknown";
|
|
385
426
|
}
|
|
386
|
-
} catch
|
|
427
|
+
} catch {
|
|
387
428
|
return "unknown";
|
|
388
429
|
}
|
|
389
430
|
}
|
|
431
|
+
async function sleep(timeout) {
|
|
432
|
+
return new Promise((resolve) => setTimeout(resolve, timeout));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/providers/wormhole/signers/SolanaSigner.ts
|
|
436
|
+
async function signAndSendTransaction2(request, wallet, options, crossChainCore) {
|
|
437
|
+
if (!wallet || !(wallet instanceof SolanaDerivedWallet)) {
|
|
438
|
+
throw new Error("Invalid wallet type or missing Solana wallet");
|
|
439
|
+
}
|
|
440
|
+
const commitment = options?.commitment ?? "finalized";
|
|
441
|
+
const connection = new Connection2(
|
|
442
|
+
crossChainCore?._dappConfig?.solanaConfig?.rpc ?? crossChainCore?.CHAINS["Solana"]?.defaultRpc ?? "https://api.devnet.solana.com"
|
|
443
|
+
// Last resort fallback
|
|
444
|
+
);
|
|
445
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
|
|
446
|
+
const unsignedTx = await setPriorityFeeInstructions(
|
|
447
|
+
connection,
|
|
448
|
+
blockhash,
|
|
449
|
+
lastValidBlockHeight,
|
|
450
|
+
request,
|
|
451
|
+
crossChainCore?._dappConfig?.solanaConfig?.priorityFeeConfig
|
|
452
|
+
);
|
|
453
|
+
if (!wallet.solanaWallet.signTransaction) {
|
|
454
|
+
throw new Error("Wallet does not support signing transactions");
|
|
455
|
+
}
|
|
456
|
+
const tx = await wallet.solanaWallet.signTransaction(unsignedTx);
|
|
457
|
+
if (!tx) throw new Error("Failed to sign transaction");
|
|
458
|
+
if (request.transaction.signers && tx instanceof Transaction2) {
|
|
459
|
+
tx.partialSign(...request.transaction.signers);
|
|
460
|
+
}
|
|
461
|
+
const serializedTx = tx.serialize();
|
|
462
|
+
const signature = await sendAndConfirmTransaction(
|
|
463
|
+
serializedTx,
|
|
464
|
+
blockhash,
|
|
465
|
+
lastValidBlockHeight,
|
|
466
|
+
{
|
|
467
|
+
connection,
|
|
468
|
+
commitment,
|
|
469
|
+
retryIntervalMs: 5e3,
|
|
470
|
+
verbose: false
|
|
471
|
+
}
|
|
472
|
+
);
|
|
473
|
+
return signature;
|
|
474
|
+
}
|
|
475
|
+
async function setPriorityFeeInstructions(connection, blockhash, lastValidBlockHeight, request, priorityFeeConfig) {
|
|
476
|
+
const unsignedTx = request.transaction.transaction;
|
|
477
|
+
unsignedTx.recentBlockhash = blockhash;
|
|
478
|
+
unsignedTx.lastValidBlockHeight = lastValidBlockHeight;
|
|
479
|
+
await addPriorityFeeInstructions(
|
|
480
|
+
connection,
|
|
481
|
+
unsignedTx,
|
|
482
|
+
priorityFeeConfig,
|
|
483
|
+
false
|
|
484
|
+
);
|
|
485
|
+
return unsignedTx;
|
|
486
|
+
}
|
|
390
487
|
|
|
391
488
|
// src/providers/wormhole/signers/EthereumSigner.ts
|
|
392
489
|
import { ethers, getBigInt } from "ethers";
|
|
393
490
|
async function signAndSendTransaction3(request, wallet, chainName, options) {
|
|
394
491
|
if (!wallet) {
|
|
395
|
-
throw new Error("wallet.sendTransaction is undefined")
|
|
492
|
+
throw new Error("wallet.sendTransaction is undefined");
|
|
396
493
|
}
|
|
397
494
|
const chainId = await wallet.eip1193Provider.request({
|
|
398
495
|
method: "eth_chainId"
|
|
399
496
|
});
|
|
400
497
|
const actualChainId = parseInt(chainId, 16);
|
|
401
498
|
if (!actualChainId)
|
|
402
|
-
throw new Error("No signer found for chain" + chainName)
|
|
499
|
+
throw new Error("No signer found for chain" + chainName);
|
|
403
500
|
const expectedChainId = request.transaction.chainId ? getBigInt(request.transaction.chainId) : void 0;
|
|
404
501
|
if (!actualChainId || !expectedChainId || BigInt(actualChainId) !== expectedChainId) {
|
|
405
502
|
throw new Error(
|
|
406
503
|
`Signer is not connected to the right chain. Expected ${expectedChainId}, got ${actualChainId}`
|
|
407
|
-
)
|
|
504
|
+
);
|
|
408
505
|
}
|
|
409
506
|
const provider = new ethers.BrowserProvider(
|
|
410
507
|
wallet.eip1193Provider
|
|
@@ -426,7 +523,7 @@ import { UserResponseStatus } from "@aptos-labs/wallet-standard";
|
|
|
426
523
|
import { GasStationClient, GasStationTransactionSubmitter } from "@aptos-labs/gas-station-client";
|
|
427
524
|
async function signAndSendTransaction4(request, wallet, sponsorAccount, dappNetwork) {
|
|
428
525
|
if (!wallet) {
|
|
429
|
-
throw new Error("wallet.sendTransaction is undefined")
|
|
526
|
+
throw new Error("wallet.sendTransaction is undefined");
|
|
430
527
|
}
|
|
431
528
|
const payload = request.transaction;
|
|
432
529
|
payload.functionArguments = payload.functionArguments.map((a) => {
|
|
@@ -526,13 +623,13 @@ async function signAndSendTransaction5(request, wallet) {
|
|
|
526
623
|
// src/providers/wormhole/signers/Signer.ts
|
|
527
624
|
var Signer = class {
|
|
528
625
|
constructor(chain, address, options, wallet, crossChainCore, sponsorAccount) {
|
|
626
|
+
this._claimedTransactionHashes = [];
|
|
529
627
|
this._chain = chain;
|
|
530
628
|
this._address = address;
|
|
531
629
|
this._options = options;
|
|
532
630
|
this._wallet = wallet;
|
|
533
631
|
this._crossChainCore = crossChainCore;
|
|
534
632
|
this._sponsorAccount = sponsorAccount;
|
|
535
|
-
this._claimedTransactionHashes = "";
|
|
536
633
|
}
|
|
537
634
|
chain() {
|
|
538
635
|
return this._chain.key;
|
|
@@ -541,10 +638,11 @@ var Signer = class {
|
|
|
541
638
|
return this._address;
|
|
542
639
|
}
|
|
543
640
|
claimedTransactionHashes() {
|
|
544
|
-
return this._claimedTransactionHashes;
|
|
641
|
+
return this._claimedTransactionHashes.join(",");
|
|
545
642
|
}
|
|
546
643
|
async signAndSend(txs) {
|
|
547
644
|
const txHashes = [];
|
|
645
|
+
this._claimedTransactionHashes = [];
|
|
548
646
|
for (const tx of txs) {
|
|
549
647
|
const txId = await signAndSendTransaction6(
|
|
550
648
|
this._chain,
|
|
@@ -555,7 +653,7 @@ var Signer = class {
|
|
|
555
653
|
this._sponsorAccount
|
|
556
654
|
);
|
|
557
655
|
txHashes.push(txId);
|
|
558
|
-
this._claimedTransactionHashes
|
|
656
|
+
this._claimedTransactionHashes.push(txId);
|
|
559
657
|
}
|
|
560
658
|
return txHashes;
|
|
561
659
|
}
|
|
@@ -600,6 +698,49 @@ var signAndSendTransaction6 = async (chain, request, wallet, options = {}, cross
|
|
|
600
698
|
}
|
|
601
699
|
};
|
|
602
700
|
|
|
701
|
+
// src/providers/wormhole/utils.ts
|
|
702
|
+
import {
|
|
703
|
+
chainToPlatform,
|
|
704
|
+
routes as routes2,
|
|
705
|
+
Wormhole
|
|
706
|
+
} from "@wormhole-foundation/sdk";
|
|
707
|
+
async function createCCTPRoute(wh, sourceChain, destChain, tokens) {
|
|
708
|
+
const sourceToken = Wormhole.tokenId(
|
|
709
|
+
sourceChain,
|
|
710
|
+
tokens[sourceChain].tokenId.address
|
|
711
|
+
);
|
|
712
|
+
const destToken = Wormhole.tokenId(
|
|
713
|
+
destChain,
|
|
714
|
+
tokens[destChain].tokenId.address
|
|
715
|
+
);
|
|
716
|
+
const destContext = wh.getPlatform(chainToPlatform(destChain)).getChain(destChain);
|
|
717
|
+
const sourceContext = wh.getPlatform(chainToPlatform(sourceChain)).getChain(sourceChain);
|
|
718
|
+
const request = await routes2.RouteTransferRequest.create(
|
|
719
|
+
wh,
|
|
720
|
+
{ source: sourceToken, destination: destToken },
|
|
721
|
+
sourceContext,
|
|
722
|
+
destContext
|
|
723
|
+
);
|
|
724
|
+
const resolver = wh.resolver([routes2.CCTPRoute]);
|
|
725
|
+
const foundRoutes = await resolver.findRoutes(request);
|
|
726
|
+
const cctpRoute = foundRoutes[0];
|
|
727
|
+
if (!cctpRoute || !routes2.isManual(cctpRoute)) {
|
|
728
|
+
throw new Error("Expected manual CCTP route");
|
|
729
|
+
}
|
|
730
|
+
return { route: cctpRoute, request };
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// src/providers/wormhole/types.ts
|
|
734
|
+
var WithdrawError = class extends Error {
|
|
735
|
+
constructor(message, originChainTxnId, phase, cause) {
|
|
736
|
+
super(message);
|
|
737
|
+
this.name = "WithdrawError";
|
|
738
|
+
this.originChainTxnId = originChainTxnId;
|
|
739
|
+
this.phase = phase;
|
|
740
|
+
this.cause = cause;
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
|
|
603
744
|
// src/providers/wormhole/wormhole.ts
|
|
604
745
|
var WormholeProvider = class {
|
|
605
746
|
constructor(core) {
|
|
@@ -632,33 +773,15 @@ var WormholeProvider = class {
|
|
|
632
773
|
if (!this._wormholeContext) {
|
|
633
774
|
throw new Error("Wormhole context not initialized");
|
|
634
775
|
}
|
|
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(
|
|
776
|
+
const { route: cctpRoute, request } = await createCCTPRoute(
|
|
646
777
|
this._wormholeContext,
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
},
|
|
651
|
-
sourceContext,
|
|
652
|
-
destContext
|
|
778
|
+
sourceChain,
|
|
779
|
+
destinationChain,
|
|
780
|
+
this.crossChainCore.TOKENS
|
|
653
781
|
);
|
|
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
782
|
this.wormholeRoute = cctpRoute;
|
|
661
783
|
this.wormholeRequest = request;
|
|
784
|
+
this.destinationChain = destinationChain;
|
|
662
785
|
return { route: cctpRoute, request };
|
|
663
786
|
}
|
|
664
787
|
async getQuote(input) {
|
|
@@ -680,12 +803,12 @@ var WormholeProvider = class {
|
|
|
680
803
|
const validated = await route.validate(request, transferParams);
|
|
681
804
|
if (!validated.valid) {
|
|
682
805
|
logger.log("invalid", validated.valid);
|
|
683
|
-
throw new Error(`Invalid quote: ${validated.error}`)
|
|
806
|
+
throw new Error(`Invalid quote: ${validated.error}`);
|
|
684
807
|
}
|
|
685
808
|
const quote = await route.quote(request, validated.params);
|
|
686
809
|
if (!quote.success) {
|
|
687
810
|
logger.log("quote failed", quote.success);
|
|
688
|
-
throw new Error(`Invalid quote: ${quote.error}`)
|
|
811
|
+
throw new Error(`Invalid quote: ${quote.error}`);
|
|
689
812
|
}
|
|
690
813
|
this.wormholeQuote = quote;
|
|
691
814
|
logger.log("quote", quote);
|
|
@@ -727,7 +850,7 @@ var WormholeProvider = class {
|
|
|
727
850
|
this.wormholeRequest,
|
|
728
851
|
signer,
|
|
729
852
|
this.wormholeQuote,
|
|
730
|
-
|
|
853
|
+
Wormhole2.chainAddress("Aptos", destinationAddress.toString())
|
|
731
854
|
);
|
|
732
855
|
const originChainTxnId = "originTxs" in receipt ? receipt.originTxs[receipt.originTxs.length - 1].txid : void 0;
|
|
733
856
|
return { originChainTxnId: originChainTxnId || "", receipt };
|
|
@@ -756,7 +879,7 @@ var WormholeProvider = class {
|
|
|
756
879
|
// the fee payer account
|
|
757
880
|
this.crossChainCore._dappConfig?.aptosNetwork
|
|
758
881
|
);
|
|
759
|
-
if (
|
|
882
|
+
if (routes3.isManual(this.wormholeRoute)) {
|
|
760
883
|
const circleAttestationReceipt = await this.wormholeRoute.complete(signer, receipt);
|
|
761
884
|
logger.log("Claim receipt: ", circleAttestationReceipt);
|
|
762
885
|
const destinationChainTxnId = signer.claimedTransactionHashes();
|
|
@@ -806,15 +929,14 @@ var WormholeProvider = class {
|
|
|
806
929
|
});
|
|
807
930
|
return { originChainTxnId, destinationChainTxnId };
|
|
808
931
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
}
|
|
932
|
+
// --- Split withdraw flow: initiateWithdraw + trackWithdraw + claimWithdraw ---
|
|
933
|
+
/**
|
|
934
|
+
* Phase 1: Initiates a withdraw by burning USDC on Aptos.
|
|
935
|
+
* The user signs the Aptos burn transaction via their wallet.
|
|
936
|
+
* Returns a receipt that can be tracked and later claimed.
|
|
937
|
+
*/
|
|
938
|
+
async initiateWithdraw(input) {
|
|
939
|
+
const { wallet, destinationAddress, sponsorAccount } = input;
|
|
818
940
|
if (!this._wormholeContext) {
|
|
819
941
|
throw new Error("Wormhole context not initialized");
|
|
820
942
|
}
|
|
@@ -823,69 +945,48 @@ var WormholeProvider = class {
|
|
|
823
945
|
}
|
|
824
946
|
const signer = new Signer(
|
|
825
947
|
this.getChainConfig("Aptos"),
|
|
826
|
-
(await
|
|
948
|
+
(await wallet.features["aptos:account"].account()).address.toString(),
|
|
827
949
|
{},
|
|
828
|
-
|
|
950
|
+
wallet,
|
|
829
951
|
this.crossChainCore,
|
|
830
952
|
sponsorAccount
|
|
831
953
|
);
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
logger.log(
|
|
836
|
-
"Wormhole.chainAddress",
|
|
837
|
-
Wormhole.chainAddress(sourceChain, input.destinationAddress.toString())
|
|
954
|
+
const wormholeDestAddress = Wormhole2.chainAddress(
|
|
955
|
+
this.destinationChain,
|
|
956
|
+
destinationAddress.toString()
|
|
838
957
|
);
|
|
839
|
-
|
|
958
|
+
const receipt = await this.wormholeRoute.initiate(
|
|
840
959
|
this.wormholeRequest,
|
|
841
960
|
signer,
|
|
842
961
|
this.wormholeQuote,
|
|
843
|
-
|
|
962
|
+
wormholeDestAddress
|
|
844
963
|
);
|
|
845
|
-
logger.log("receipt", receipt);
|
|
964
|
+
logger.log("initiateWithdraw receipt", receipt);
|
|
846
965
|
const originChainTxnId = "originTxs" in receipt ? receipt.originTxs[receipt.originTxs.length - 1].txid : void 0;
|
|
966
|
+
return { originChainTxnId: originChainTxnId || "", receipt };
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Phase 2: Tracks a withdraw receipt until attestation is ready.
|
|
970
|
+
* This polls Wormhole and returns once the receipt reaches the Attested state.
|
|
971
|
+
*/
|
|
972
|
+
async trackWithdraw(receipt) {
|
|
973
|
+
if (!this.wormholeRoute) {
|
|
974
|
+
throw new Error("Wormhole route not initialized");
|
|
975
|
+
}
|
|
847
976
|
let retries = 0;
|
|
848
977
|
const maxRetries = 5;
|
|
849
978
|
const baseDelay = 1e3;
|
|
850
979
|
while (retries < maxRetries) {
|
|
851
980
|
try {
|
|
852
981
|
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
|
-
}
|
|
982
|
+
if (receipt.state >= TransferState.Attested) {
|
|
983
|
+
logger.log("trackWithdraw: receipt attested", receipt);
|
|
984
|
+
return receipt;
|
|
884
985
|
}
|
|
885
986
|
}
|
|
886
987
|
} catch (e) {
|
|
887
988
|
console.error(
|
|
888
|
-
`Error tracking
|
|
989
|
+
`Error tracking withdraw (attempt ${retries + 1} / ${maxRetries}):`,
|
|
889
990
|
e
|
|
890
991
|
);
|
|
891
992
|
const delay = baseDelay * Math.pow(2, retries);
|
|
@@ -893,10 +994,108 @@ var WormholeProvider = class {
|
|
|
893
994
|
retries++;
|
|
894
995
|
}
|
|
895
996
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
997
|
+
throw new Error("Failed to track withdraw to attested state");
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Phase 3: Claims the withdraw on the destination chain.
|
|
1001
|
+
*
|
|
1002
|
+
* If the destination is Solana and `solanaConfig.serverClaimUrl` is configured
|
|
1003
|
+
* in the dapp config, the SDK automatically POSTs the attested receipt to that
|
|
1004
|
+
* URL — no wallet popup required. The dapp's server endpoint handles signing
|
|
1005
|
+
* and submitting the claim transaction.
|
|
1006
|
+
*
|
|
1007
|
+
* Otherwise falls back to the wallet-based Signer (triggers wallet popup).
|
|
1008
|
+
*/
|
|
1009
|
+
async claimWithdraw(input) {
|
|
1010
|
+
const { sourceChain, destinationAddress, receipt } = input;
|
|
1011
|
+
const serverClaimUrl = this.crossChainCore._dappConfig?.solanaConfig?.serverClaimUrl;
|
|
1012
|
+
if (sourceChain === "Solana" && serverClaimUrl) {
|
|
1013
|
+
logger.log("claimWithdraw: using server-side claim via", serverClaimUrl);
|
|
1014
|
+
const response = await fetch(serverClaimUrl, {
|
|
1015
|
+
method: "POST",
|
|
1016
|
+
headers: { "Content-Type": "application/json" },
|
|
1017
|
+
body: JSON.stringify({
|
|
1018
|
+
receipt: serializeReceipt(receipt),
|
|
1019
|
+
destinationAddress,
|
|
1020
|
+
sourceChain
|
|
1021
|
+
})
|
|
1022
|
+
});
|
|
1023
|
+
if (!response.ok) {
|
|
1024
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1025
|
+
throw new Error(
|
|
1026
|
+
errorData.error || `Server-side claim failed with status ${response.status}`
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
const result = await response.json();
|
|
1030
|
+
return { destinationChainTxnId: result.destinationChainTxnId };
|
|
1031
|
+
}
|
|
1032
|
+
if (!this.wormholeRoute) {
|
|
1033
|
+
throw new Error("Wormhole route not initialized");
|
|
1034
|
+
}
|
|
1035
|
+
if (!input.wallet) {
|
|
1036
|
+
throw new Error(
|
|
1037
|
+
"Wallet is required for claim when serverClaimUrl is not configured"
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
const claimSigner = new Signer(
|
|
1041
|
+
this.getChainConfig(sourceChain),
|
|
1042
|
+
destinationAddress,
|
|
1043
|
+
{},
|
|
1044
|
+
input.wallet,
|
|
1045
|
+
this.crossChainCore
|
|
1046
|
+
);
|
|
1047
|
+
if (routes3.isManual(this.wormholeRoute)) {
|
|
1048
|
+
const circleAttestationReceipt = await this.wormholeRoute.complete(
|
|
1049
|
+
claimSigner,
|
|
1050
|
+
receipt
|
|
1051
|
+
);
|
|
1052
|
+
logger.log("claimWithdraw receipt:", circleAttestationReceipt);
|
|
1053
|
+
const destinationChainTxnId = claimSigner.claimedTransactionHashes();
|
|
1054
|
+
return { destinationChainTxnId };
|
|
1055
|
+
} else {
|
|
1056
|
+
throw new Error("Automatic route not supported for manual claim");
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Withdraws USDC from Aptos to a destination chain.
|
|
1061
|
+
* Orchestrates all three phases internally:
|
|
1062
|
+
* 1. Initiate — user signs the Aptos burn transaction
|
|
1063
|
+
* 2. Track — wait for Wormhole attestation
|
|
1064
|
+
* 3. Claim — if serverClaimUrl is configured for Solana, delegates to
|
|
1065
|
+
* the server; otherwise uses the wallet-based signer.
|
|
1066
|
+
*
|
|
1067
|
+
* The optional `onPhaseChange` callback lets the dapp update its UI
|
|
1068
|
+
* as the flow progresses.
|
|
1069
|
+
*/
|
|
1070
|
+
async withdraw(input) {
|
|
1071
|
+
const { sourceChain, wallet, destinationAddress, sponsorAccount, onPhaseChange } = input;
|
|
1072
|
+
onPhaseChange?.("initiating");
|
|
1073
|
+
const { originChainTxnId, receipt } = await this.initiateWithdraw({
|
|
1074
|
+
wallet,
|
|
1075
|
+
destinationAddress,
|
|
1076
|
+
sponsorAccount
|
|
1077
|
+
});
|
|
1078
|
+
let currentPhase = "tracking";
|
|
1079
|
+
try {
|
|
1080
|
+
onPhaseChange?.("tracking");
|
|
1081
|
+
const attestedReceipt = await this.trackWithdraw(receipt);
|
|
1082
|
+
currentPhase = "claiming";
|
|
1083
|
+
onPhaseChange?.("claiming");
|
|
1084
|
+
const { destinationChainTxnId } = await this.claimWithdraw({
|
|
1085
|
+
sourceChain,
|
|
1086
|
+
destinationAddress: destinationAddress.toString(),
|
|
1087
|
+
receipt: attestedReceipt,
|
|
1088
|
+
wallet
|
|
1089
|
+
});
|
|
1090
|
+
return { originChainTxnId, destinationChainTxnId };
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
throw new WithdrawError(
|
|
1093
|
+
error?.message ?? "Withdraw failed after Aptos burn",
|
|
1094
|
+
originChainTxnId,
|
|
1095
|
+
currentPhase,
|
|
1096
|
+
error
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
900
1099
|
}
|
|
901
1100
|
getChainConfig(chain) {
|
|
902
1101
|
const chainConfig = this.crossChainCore.CHAINS[chain];
|
|
@@ -905,16 +1104,121 @@ var WormholeProvider = class {
|
|
|
905
1104
|
}
|
|
906
1105
|
return chainConfig;
|
|
907
1106
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
// src/providers/wormhole/signers/SolanaLocalSigner.ts
|
|
1110
|
+
import {
|
|
1111
|
+
Transaction as Transaction3
|
|
1112
|
+
} from "@solana/web3.js";
|
|
1113
|
+
var SolanaLocalSigner = class {
|
|
1114
|
+
constructor(config) {
|
|
1115
|
+
this._claimedTransactionHashes = [];
|
|
1116
|
+
this.keypair = config.keypair;
|
|
1117
|
+
this.connection = config.connection;
|
|
1118
|
+
this.commitment = config.commitment ?? "finalized";
|
|
1119
|
+
this.retryIntervalMs = config.retryIntervalMs ?? 5e3;
|
|
1120
|
+
this.priorityFeeConfig = config.priorityFeeConfig;
|
|
1121
|
+
this.verbose = config.verbose ?? false;
|
|
1122
|
+
}
|
|
1123
|
+
chain() {
|
|
1124
|
+
return "Solana";
|
|
1125
|
+
}
|
|
1126
|
+
address() {
|
|
1127
|
+
return this.keypair.publicKey.toBase58();
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Returns all transaction hashes from the most recent signAndSend call,
|
|
1131
|
+
* joined by comma. If only one transaction was signed, returns a single hash string.
|
|
1132
|
+
*/
|
|
1133
|
+
claimedTransactionHashes() {
|
|
1134
|
+
return this._claimedTransactionHashes.join(",");
|
|
1135
|
+
}
|
|
1136
|
+
async signAndSend(txs) {
|
|
1137
|
+
const txHashes = [];
|
|
1138
|
+
this._claimedTransactionHashes = [];
|
|
1139
|
+
for (const tx of txs) {
|
|
1140
|
+
const txId = await this.signAndSendTransaction(tx);
|
|
1141
|
+
txHashes.push(txId);
|
|
1142
|
+
this._claimedTransactionHashes.push(txId);
|
|
1143
|
+
}
|
|
1144
|
+
return txHashes;
|
|
1145
|
+
}
|
|
1146
|
+
async signAndSendTransaction(request) {
|
|
1147
|
+
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash(this.commitment);
|
|
1148
|
+
let unsignedTx = request.transaction ?? request;
|
|
1149
|
+
const additionalSigners = request.transaction?.signers;
|
|
1150
|
+
const MAX_UNWRAP_DEPTH = 10;
|
|
1151
|
+
let unwrapDepth = 0;
|
|
1152
|
+
while (unsignedTx && typeof unsignedTx === "object" && "transaction" in unsignedTx && !(unsignedTx instanceof Transaction3) && !("signatures" in unsignedTx && "message" in unsignedTx)) {
|
|
1153
|
+
if (++unwrapDepth > MAX_UNWRAP_DEPTH) {
|
|
1154
|
+
throw new Error(
|
|
1155
|
+
"Transaction unwrapping exceeded maximum depth \u2014 possible circular nesting"
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
unsignedTx = unsignedTx.transaction;
|
|
1159
|
+
}
|
|
1160
|
+
const isVersioned = unsignedTx.message !== void 0 && unsignedTx.signatures !== void 0 && typeof unsignedTx.message.recentBlockhash !== "undefined";
|
|
1161
|
+
if (isVersioned) {
|
|
1162
|
+
unsignedTx.message.recentBlockhash = blockhash;
|
|
1163
|
+
if (this.verbose || this.priorityFeeConfig) {
|
|
1164
|
+
console.warn(
|
|
1165
|
+
"SolanaLocalSigner: Versioned transaction detected \u2014 priority fees are not applied. Consider using legacy transactions if priority fees are required."
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
unsignedTx.sign([this.keypair]);
|
|
1169
|
+
if (additionalSigners && additionalSigners.length > 0) {
|
|
1170
|
+
unsignedTx.sign(additionalSigners);
|
|
1171
|
+
}
|
|
1172
|
+
} else if (unsignedTx instanceof Transaction3) {
|
|
1173
|
+
unsignedTx.recentBlockhash = blockhash;
|
|
1174
|
+
unsignedTx.lastValidBlockHeight = lastValidBlockHeight;
|
|
1175
|
+
if (this.priorityFeeConfig) {
|
|
1176
|
+
await addPriorityFeeInstructions(
|
|
1177
|
+
this.connection,
|
|
1178
|
+
unsignedTx,
|
|
1179
|
+
this.priorityFeeConfig,
|
|
1180
|
+
this.verbose
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
if (additionalSigners && additionalSigners.length > 0) {
|
|
1184
|
+
unsignedTx.sign(this.keypair, ...additionalSigners);
|
|
1185
|
+
} else {
|
|
1186
|
+
unsignedTx.sign(this.keypair);
|
|
1187
|
+
}
|
|
1188
|
+
} else if (unsignedTx.recentBlockhash !== void 0 || unsignedTx.feePayer !== void 0) {
|
|
1189
|
+
unsignedTx.recentBlockhash = blockhash;
|
|
1190
|
+
unsignedTx.lastValidBlockHeight = lastValidBlockHeight;
|
|
1191
|
+
if (this.priorityFeeConfig) {
|
|
1192
|
+
await addPriorityFeeInstructions(
|
|
1193
|
+
this.connection,
|
|
1194
|
+
unsignedTx,
|
|
1195
|
+
this.priorityFeeConfig,
|
|
1196
|
+
this.verbose
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
if (additionalSigners && additionalSigners.length > 0) {
|
|
1200
|
+
unsignedTx.sign(this.keypair, ...additionalSigners);
|
|
1201
|
+
} else {
|
|
1202
|
+
unsignedTx.sign(this.keypair);
|
|
1203
|
+
}
|
|
1204
|
+
} else {
|
|
1205
|
+
throw new Error(
|
|
1206
|
+
`Unsupported transaction type: ${unsignedTx?.constructor?.name}`
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
const serializedTx = unsignedTx.serialize();
|
|
1210
|
+
const signature = await sendAndConfirmTransaction(
|
|
1211
|
+
serializedTx,
|
|
1212
|
+
blockhash,
|
|
1213
|
+
lastValidBlockHeight,
|
|
1214
|
+
{
|
|
1215
|
+
connection: this.connection,
|
|
1216
|
+
commitment: this.commitment,
|
|
1217
|
+
retryIntervalMs: this.retryIntervalMs,
|
|
1218
|
+
verbose: this.verbose
|
|
1219
|
+
}
|
|
916
1220
|
);
|
|
917
|
-
return
|
|
1221
|
+
return signature;
|
|
918
1222
|
}
|
|
919
1223
|
};
|
|
920
1224
|
|
|
@@ -988,8 +1292,8 @@ var testnetChains = {
|
|
|
988
1292
|
key: "Solana",
|
|
989
1293
|
context: "Solana" /* SOLANA */,
|
|
990
1294
|
displayName: "Solana",
|
|
991
|
-
explorerUrl: "https://
|
|
992
|
-
explorerName: "
|
|
1295
|
+
explorerUrl: "https://solscan.io",
|
|
1296
|
+
explorerName: "Solscan",
|
|
993
1297
|
chainId: 0,
|
|
994
1298
|
icon: "Solana",
|
|
995
1299
|
symbol: "SOL",
|
|
@@ -1112,8 +1416,8 @@ var mainnetChains = {
|
|
|
1112
1416
|
key: "Solana",
|
|
1113
1417
|
context: "Solana" /* SOLANA */,
|
|
1114
1418
|
displayName: "Solana",
|
|
1115
|
-
explorerUrl: "https://
|
|
1116
|
-
explorerName: "
|
|
1419
|
+
explorerUrl: "https://solscan.io",
|
|
1420
|
+
explorerName: "Solscan",
|
|
1117
1421
|
chainId: 0,
|
|
1118
1422
|
icon: "Solana",
|
|
1119
1423
|
symbol: "SOL",
|
|
@@ -1265,13 +1569,13 @@ var mainnetTokens = {
|
|
|
1265
1569
|
|
|
1266
1570
|
// src/utils/getUsdcBalance.ts
|
|
1267
1571
|
import { Aptos as Aptos3, AptosConfig as AptosConfig3, Network as Network2 } from "@aptos-labs/ts-sdk";
|
|
1268
|
-
import { Connection as
|
|
1572
|
+
import { Connection as Connection4, PublicKey } from "@solana/web3.js";
|
|
1269
1573
|
import { ethers as ethers2, JsonRpcProvider } from "ethers";
|
|
1270
1574
|
import { SuiClient } from "@mysten/sui/client";
|
|
1271
1575
|
var getSolanaWalletUSDCBalance = async (walletAddress, aptosNetwork, rpc) => {
|
|
1272
1576
|
const address = new PublicKey(walletAddress);
|
|
1273
1577
|
const tokenAddress = aptosNetwork === Network2.MAINNET ? mainnetTokens["Solana"].tokenId.address : testnetTokens["Solana"].tokenId.address;
|
|
1274
|
-
const connection = new
|
|
1578
|
+
const connection = new Connection4(rpc);
|
|
1275
1579
|
const splToken = await connection.getTokenAccountsByOwner(address, {
|
|
1276
1580
|
mint: new PublicKey(tokenAddress)
|
|
1277
1581
|
});
|
|
@@ -1418,9 +1722,14 @@ export {
|
|
|
1418
1722
|
Network4 as Network,
|
|
1419
1723
|
NetworkToChainId,
|
|
1420
1724
|
NetworkToNodeAPI,
|
|
1725
|
+
SolanaLocalSigner,
|
|
1726
|
+
WithdrawError,
|
|
1421
1727
|
WormholeProvider,
|
|
1728
|
+
createCCTPRoute,
|
|
1729
|
+
deserializeReceipt,
|
|
1422
1730
|
mainnetChains,
|
|
1423
1731
|
mainnetTokens,
|
|
1732
|
+
serializeReceipt,
|
|
1424
1733
|
signAndSendTransaction,
|
|
1425
1734
|
testnetChains,
|
|
1426
1735
|
testnetTokens
|