@stableflow/wallet-evm 3.0.0-beta-02a59f37a0-20260515

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1540 @@
1
+ import { Csl, OpenAPI, erc20Abi, numberRemoveEndZero, getPrice, ExecTime, addressToBytes32, Service, SendType, quoteSignature, usdtTokens } from '@stableflow/core';
2
+ import { LZ_RECEIVE_VALUE, getHopMsgFee, USDT0_LEGACY_MESH_TRANSFTER_FEE, FRAXZERO_MIDDLE_TOKEN_USDC, FRAXZERO_MIDDLE_TOKEN_FRXUSD, USDT0_CONFIG, OFT_ABI } from '@stableflow/bridges';
3
+ import { evmRpcFallbackProvider, NATIVE_MSG_FEE_BUFFER, buildEndpointV2LzComposePayload } from '@stableflow/utils-evm';
4
+ import { getDestinationAssociatedTokenAddress } from '@stableflow/utils-solana';
5
+ import Big from 'big.js';
6
+ import { ethers } from 'ethers';
7
+
8
+ // src/evm.ts
9
+ var DEFAULT_GAS_LIMIT = 100000n;
10
+ var DEFAULT_GAS_LIMIT_FAILED = 4000000n;
11
+ var EVMWallet = class {
12
+ constructor(_provider, _signer) {
13
+ this.provider = _provider;
14
+ this.signer = _signer;
15
+ const cs = new Csl(OpenAPI.DEBUG);
16
+ this.csl = cs.log;
17
+ }
18
+ async transfer(data) {
19
+ const { originAsset, depositAddress, amount } = data;
20
+ if (originAsset === "eth") {
21
+ const tx2 = await this.signer.sendTransaction({
22
+ to: depositAddress,
23
+ value: ethers.parseEther(amount)
24
+ });
25
+ await tx2.wait();
26
+ return tx2;
27
+ }
28
+ const contract = new ethers.Contract(originAsset, erc20Abi, this.signer);
29
+ const tx = await contract.transfer(depositAddress, amount);
30
+ const result = await tx.wait();
31
+ return result.hash;
32
+ }
33
+ async getBalance(token, account, options) {
34
+ const { isCatchError = false } = options || {};
35
+ try {
36
+ let provider = this.provider;
37
+ if (token.rpcUrls) {
38
+ provider = evmRpcFallbackProvider(token);
39
+ }
40
+ if (token.symbol === "eth" || token.symbol === "ETH" || token.symbol === "native") {
41
+ const balance2 = await provider.getBalance(account);
42
+ return balance2.toString();
43
+ }
44
+ const contract = new ethers.Contract(token.contractAddress, erc20Abi, provider);
45
+ const balance = await contract.balanceOf(account);
46
+ this.csl("EVM getBalance", "green-500", "Success getting %s token balance: %o", token.contractAddress, balance);
47
+ return balance.toString();
48
+ } catch (err) {
49
+ this.csl("EVM getBalance", "red-500", "Get balance failed: %o", err);
50
+ if (isCatchError) {
51
+ throw err;
52
+ }
53
+ return "0";
54
+ }
55
+ }
56
+ async balanceOf(token, account, options) {
57
+ return await this.getBalance(token, account, options);
58
+ }
59
+ /**
60
+ * Estimate gas limit for transfer transaction
61
+ * @param data Transfer data
62
+ * @returns Gas limit estimate, gas price, and estimated gas cost
63
+ */
64
+ async estimateTransferGas(data) {
65
+ const { fromToken, depositAddress, amount, account } = data;
66
+ const originAsset = fromToken.contractAddress;
67
+ const provider = evmRpcFallbackProvider(fromToken);
68
+ let gasLimit;
69
+ if (originAsset === "eth") {
70
+ const tx = {
71
+ from: account,
72
+ to: depositAddress,
73
+ value: ethers.parseEther(amount)
74
+ };
75
+ gasLimit = await provider.estimateGas(tx);
76
+ } else {
77
+ const contract = new ethers.Contract(originAsset, erc20Abi, provider);
78
+ gasLimit = await contract.transfer.estimateGas(depositAddress, amount);
79
+ }
80
+ gasLimit = gasLimit * 120n / 100n;
81
+ const feeData = await provider.getFeeData();
82
+ const gasPrice = feeData.gasPrice || 0n;
83
+ const estimateGas = gasLimit * gasPrice;
84
+ return {
85
+ gasLimit,
86
+ gasPrice,
87
+ estimateGas
88
+ };
89
+ }
90
+ getContract(params) {
91
+ const {
92
+ contractAddress,
93
+ abi
94
+ } = params;
95
+ return new ethers.Contract(contractAddress, abi, this.signer);
96
+ }
97
+ async allowance(params) {
98
+ const {
99
+ dry,
100
+ contractAddress,
101
+ spender,
102
+ address,
103
+ amountWei,
104
+ provider,
105
+ blockTag,
106
+ strict = false
107
+ } = params;
108
+ const runner = provider || this.provider;
109
+ const contract = new ethers.Contract(contractAddress, erc20Abi, runner);
110
+ if (dry) {
111
+ return {
112
+ contract,
113
+ allowance: "0",
114
+ needApprove: false
115
+ };
116
+ }
117
+ let allowance = "0";
118
+ try {
119
+ allowance = blockTag ? await contract.allowance(address, spender, { blockTag }) : await contract.allowance(address, spender);
120
+ allowance = allowance.toString();
121
+ } catch (error) {
122
+ this.csl("EVM allowance", "red-500", "Error getting allowance: %o", error);
123
+ if (strict) {
124
+ throw error;
125
+ }
126
+ }
127
+ return {
128
+ contract,
129
+ allowance,
130
+ needApprove: Big(amountWei || 0).gt(allowance || 0)
131
+ };
132
+ }
133
+ async approve(params) {
134
+ const {
135
+ contractAddress,
136
+ spender,
137
+ amountWei,
138
+ isApproveMax = false,
139
+ isDetails = false,
140
+ isWaitTxReceipt = true
141
+ } = params;
142
+ const contract = new ethers.Contract(contractAddress, erc20Abi, this.signer);
143
+ let _amountWei = amountWei;
144
+ if (isApproveMax) {
145
+ _amountWei = ethers.MaxUint256;
146
+ }
147
+ const detailResult = {
148
+ success: false,
149
+ data: {},
150
+ message: null
151
+ };
152
+ try {
153
+ const tx = await contract.approve(spender, _amountWei);
154
+ const txReceipt = await tx.wait();
155
+ if (txReceipt.status === 1) {
156
+ if (isDetails) {
157
+ const confirmations = typeof txReceipt.confirmations === "function" ? await txReceipt.confirmations() : txReceipt.confirmations;
158
+ detailResult.success = true;
159
+ detailResult.data = {
160
+ txHash: txReceipt.hash,
161
+ blockNumber: txReceipt.blockNumber,
162
+ confirmations
163
+ };
164
+ return detailResult;
165
+ }
166
+ return true;
167
+ }
168
+ if (isDetails) {
169
+ detailResult.message = "Arrove failed";
170
+ return detailResult;
171
+ }
172
+ return false;
173
+ } catch (error) {
174
+ this.csl("EVM approve", "red-500", "Error approve: %o", error);
175
+ if (isDetails) {
176
+ detailResult.message = error.message;
177
+ }
178
+ }
179
+ if (isDetails) {
180
+ return detailResult;
181
+ }
182
+ return false;
183
+ }
184
+ async getEstimateGas(params) {
185
+ const { gasLimit, price, nativeToken, provider, gasPrice } = params;
186
+ let finalGasPrice = gasPrice;
187
+ if (!finalGasPrice) {
188
+ const feeData = await provider.getFeeData();
189
+ finalGasPrice = feeData.maxFeePerGas || feeData.gasPrice || BigInt("20000000000");
190
+ }
191
+ const estimateGas = BigInt(gasLimit) * BigInt(finalGasPrice);
192
+ const estimateGasAmount = Big(estimateGas.toString()).div(10 ** nativeToken.decimals);
193
+ const estimateGasUsd = Big(estimateGasAmount).times(price || 1);
194
+ return {
195
+ gasPrice: finalGasPrice,
196
+ usd: numberRemoveEndZero(Big(estimateGasUsd).toFixed(20)),
197
+ wei: estimateGas,
198
+ amount: numberRemoveEndZero(Big(estimateGasAmount).toFixed(nativeToken.decimals))
199
+ };
200
+ }
201
+ async estimateTransaction(params) {
202
+ const {
203
+ dry,
204
+ contract,
205
+ method,
206
+ param,
207
+ fromToken,
208
+ prices,
209
+ evmGasFees,
210
+ defaultGasLimit = DEFAULT_GAS_LIMIT,
211
+ refundTo,
212
+ txRequest
213
+ } = params;
214
+ const cs = new Csl(OpenAPI.DEBUG);
215
+ const csl = cs.log;
216
+ const provider = evmRpcFallbackProvider(fromToken);
217
+ const nativeTokenPrice = getPrice(prices, fromToken.nativeToken.symbol);
218
+ const result = {
219
+ estimateSourceGasLimit: dry ? DEFAULT_GAS_LIMIT_FAILED : DEFAULT_GAS_LIMIT,
220
+ estimateSourceGas: 0n,
221
+ estimateSourceGasUsd: "0"
222
+ };
223
+ const setDefaultGasLimit = async () => {
224
+ const { usd, wei } = await this.getEstimateGas({
225
+ gasLimit: DEFAULT_GAS_LIMIT,
226
+ price: nativeTokenPrice,
227
+ nativeToken: fromToken.nativeToken,
228
+ provider,
229
+ gasPrice: dry ? evmGasFees[fromToken.chainId].gasPrice : void 0
230
+ });
231
+ result.estimateSourceGas = wei;
232
+ result.estimateSourceGasUsd = usd;
233
+ };
234
+ let finalGasLimit = defaultGasLimit;
235
+ if (dry) {
236
+ await setDefaultGasLimit();
237
+ return result;
238
+ }
239
+ if (txRequest) {
240
+ try {
241
+ const gasLimit = await provider.estimateGas({
242
+ to: txRequest.target,
243
+ data: txRequest.calldata,
244
+ from: refundTo || this.signer?.address
245
+ });
246
+ finalGasLimit = gasLimit * 120n / 100n;
247
+ const { usd, wei } = await this.getEstimateGas({
248
+ gasLimit: finalGasLimit,
249
+ price: nativeTokenPrice,
250
+ nativeToken: fromToken.nativeToken,
251
+ provider
252
+ });
253
+ result.estimateSourceGasLimit = finalGasLimit;
254
+ result.estimateSourceGas = wei;
255
+ result.estimateSourceGasUsd = usd;
256
+ } catch (error) {
257
+ csl("EVM estimateTransaction", "red-500", "%s estimateGas failed: %o", method, error);
258
+ await setDefaultGasLimit();
259
+ result.estimateSourceGasLimit = DEFAULT_GAS_LIMIT_FAILED / 2n;
260
+ }
261
+ return result;
262
+ }
263
+ try {
264
+ const gasLimit = await contract[method].estimateGas(...param);
265
+ finalGasLimit = gasLimit * 120n / 100n;
266
+ const { usd, wei } = await this.getEstimateGas({
267
+ gasLimit: finalGasLimit,
268
+ price: nativeTokenPrice,
269
+ nativeToken: fromToken.nativeToken,
270
+ provider
271
+ });
272
+ result.estimateSourceGasLimit = finalGasLimit;
273
+ result.estimateSourceGas = wei;
274
+ result.estimateSourceGasUsd = usd;
275
+ } catch (error) {
276
+ csl("EVM estimateTransaction", "red-500", "%s estimateGas failed: %o", method, error);
277
+ await setDefaultGasLimit();
278
+ result.estimateSourceGasLimit = DEFAULT_GAS_LIMIT_FAILED / 2n;
279
+ }
280
+ return result;
281
+ }
282
+ async quoteOFT(params) {
283
+ const { Options } = await import('@layerzerolabs/lz-v2-utilities');
284
+ const {
285
+ dry,
286
+ abi,
287
+ dstEid,
288
+ recipient,
289
+ amountWei,
290
+ slippageTolerance,
291
+ payInLzToken,
292
+ fromToken,
293
+ toToken,
294
+ prices,
295
+ evmGasFees,
296
+ originLayerzeroAddress,
297
+ destinationLayerzeroAddress,
298
+ excludeFees,
299
+ refundTo,
300
+ multiHopComposer,
301
+ isMultiHopComposer,
302
+ isOriginLegacy,
303
+ isDestinationLegacy,
304
+ originLayerzero,
305
+ destinationLayerzero
306
+ } = params;
307
+ const result = {
308
+ needApprove: false,
309
+ approveSpender: originLayerzeroAddress,
310
+ sendParam: void 0,
311
+ quoteParam: {
312
+ ...params,
313
+ originLayerzeroAddress,
314
+ destinationLayerzeroAddress
315
+ },
316
+ fees: {},
317
+ totalFeesUsd: "0",
318
+ estimateSourceGas: 0n,
319
+ totalEstimateSourceGas: 0n,
320
+ estimateSourceGasUsd: "0",
321
+ estimateTime: 0,
322
+ // seconds - dynamically calculated using LayerZero formula
323
+ outputAmount: numberRemoveEndZero(Big(amountWei || 0).div(10 ** params.fromToken.decimals).toFixed(params.fromToken.decimals, 0))
324
+ };
325
+ const execTime = new ExecTime({ type: `Usdt0 EVM ${fromToken.chainName}->${toToken.chainName}`, logStyle: "stone-100", isDebug: OpenAPI.DEBUG });
326
+ const provider = evmRpcFallbackProvider(fromToken);
327
+ const oftContract = new ethers.Contract(originLayerzeroAddress, abi, this.signer);
328
+ const oftContractRead = new ethers.Contract(originLayerzeroAddress, abi, provider);
329
+ execTime.breakpoint();
330
+ const approvalRequired = isOriginLegacy ? originLayerzero.oftLegacyApprovalRequired : originLayerzero.oftApprovalRequired;
331
+ this.csl("EVM quoteOFT", "blue-900", "approvalRequired: %o", approvalRequired);
332
+ execTime.log("approvalRequired");
333
+ const lzReceiveOptionGas = isDestinationLegacy ? destinationLayerzero.lzReceiveOptionGasLegacy : destinationLayerzero.lzReceiveOptionGas || 2e5;
334
+ let lzReceiveOptionValue = 0;
335
+ execTime.breakpoint();
336
+ const destATA = await getDestinationAssociatedTokenAddress({
337
+ recipient,
338
+ toToken
339
+ });
340
+ execTime.log("getDestinationAssociatedTokenAddress");
341
+ if (destATA.needCreateTokenAccount) {
342
+ lzReceiveOptionValue = LZ_RECEIVE_VALUE[toToken.chainName] || 0;
343
+ }
344
+ let unMultiHopExtraOptions = Options.newOptions().toHex();
345
+ if (!isMultiHopComposer && lzReceiveOptionValue) {
346
+ unMultiHopExtraOptions = Options.newOptions().addExecutorLzReceiveOption(lzReceiveOptionGas, lzReceiveOptionValue).toHex();
347
+ }
348
+ const sendParam = {
349
+ dstEid,
350
+ to: addressToBytes32(toToken.chainType, recipient),
351
+ amountLD: amountWei,
352
+ minAmountLD: 0n,
353
+ extraOptions: unMultiHopExtraOptions,
354
+ composeMsg: "0x",
355
+ oftCmd: "0x"
356
+ };
357
+ if (isMultiHopComposer) {
358
+ sendParam.dstEid = multiHopComposer.eid;
359
+ sendParam.to = addressToBytes32("evm", multiHopComposer.oftMultiHopComposer);
360
+ let multiHopExtraOptions = Options.newOptions().toHex();
361
+ if (lzReceiveOptionValue) {
362
+ multiHopExtraOptions = Options.newOptions().addExecutorLzReceiveOption(lzReceiveOptionGas, lzReceiveOptionValue).toHex();
363
+ }
364
+ const composeMsgSendParam = {
365
+ dstEid,
366
+ to: addressToBytes32(toToken.chainType, recipient),
367
+ amountLD: sendParam.amountLD,
368
+ minAmountLD: sendParam.minAmountLD,
369
+ extraOptions: multiHopExtraOptions,
370
+ composeMsg: "0x",
371
+ oftCmd: "0x"
372
+ };
373
+ execTime.breakpoint();
374
+ const hopMsgFee = await getHopMsgFee({
375
+ sendParam: composeMsgSendParam,
376
+ toToken
377
+ });
378
+ execTime.log("getHopMsgFee");
379
+ sendParam.extraOptions = Options.newOptions().addExecutorComposeOption(0, originLayerzero.composeOptionGas || 8e5, hopMsgFee).toHex();
380
+ const abiCoder = ethers.AbiCoder.defaultAbiCoder();
381
+ sendParam.composeMsg = abiCoder.encode(
382
+ ["tuple(uint32 dstEid, bytes32 to, uint256 amountLD, uint256 minAmountLD, bytes extraOptions, bytes composeMsg, bytes oftCmd)"],
383
+ [Object.values(composeMsgSendParam)]
384
+ );
385
+ }
386
+ if (!dry) {
387
+ execTime.breakpoint();
388
+ const oftData = await oftContractRead.quoteOFT.staticCall(sendParam);
389
+ const [, , oftReceipt] = oftData;
390
+ sendParam.minAmountLD = oftReceipt[1] * (1000000n - BigInt(slippageTolerance * 1e4)) / 1000000n;
391
+ execTime.log("quoteOFT.staticCall");
392
+ }
393
+ execTime.breakpoint();
394
+ const mergedCall = [
395
+ oftContractRead.quoteSend.staticCall(sendParam, payInLzToken)
396
+ ];
397
+ if (approvalRequired) {
398
+ mergedCall.push(
399
+ this.allowance({
400
+ dry,
401
+ contractAddress: fromToken.contractAddress,
402
+ spender: originLayerzeroAddress,
403
+ address: refundTo,
404
+ amountWei,
405
+ provider
406
+ })
407
+ );
408
+ }
409
+ const [msgFee, allowanceResult] = await Promise.all(mergedCall);
410
+ if (approvalRequired) {
411
+ result.needApprove = allowanceResult?.needApprove;
412
+ }
413
+ execTime.log("quoteSend.staticCall and allowance");
414
+ let nativeMsgFee = msgFee[0];
415
+ const lzMsgFee = msgFee[1];
416
+ result.totalEstimateSourceGas = nativeMsgFee;
417
+ nativeMsgFee = nativeMsgFee * NATIVE_MSG_FEE_BUFFER / 100n;
418
+ const nativeFeeUsd = Big(nativeMsgFee?.toString() || 0).div(10 ** fromToken.nativeToken.decimals).times(getPrice(prices, fromToken.nativeToken.symbol));
419
+ result.fees.nativeFee = numberRemoveEndZero(Big(nativeMsgFee?.toString() || 0).div(10 ** fromToken.nativeToken.decimals).toFixed(fromToken.nativeToken.decimals));
420
+ result.fees.nativeFeeUsd = numberRemoveEndZero(Big(nativeFeeUsd).toFixed(20));
421
+ result.fees.lzTokenFeeUsd = numberRemoveEndZero(Big(lzMsgFee?.toString() || 0).div(10 ** fromToken.decimals).toFixed(20));
422
+ if (isOriginLegacy || isDestinationLegacy) {
423
+ result.fees.legacyMeshFeeUsd = numberRemoveEndZero(Big(amountWei || 0).div(10 ** params.fromToken.decimals).times(USDT0_LEGACY_MESH_TRANSFTER_FEE).toFixed(params.fromToken.decimals));
424
+ result.outputAmount = numberRemoveEndZero(Big(Big(amountWei || 0).div(10 ** params.fromToken.decimals)).minus(result.fees.legacyMeshFeeUsd || 0).toFixed(params.fromToken.decimals, 0));
425
+ }
426
+ const finalSendParam = [
427
+ sendParam,
428
+ {
429
+ nativeFee: nativeMsgFee,
430
+ lzTokenFee: lzMsgFee
431
+ },
432
+ refundTo,
433
+ { value: nativeMsgFee }
434
+ ];
435
+ execTime.breakpoint();
436
+ const ett = await this.estimateTransaction({
437
+ dry,
438
+ contract: oftContract,
439
+ method: "send",
440
+ param: finalSendParam,
441
+ fromToken,
442
+ prices,
443
+ evmGasFees
444
+ });
445
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
446
+ result.estimateSourceGas = ett.estimateSourceGas;
447
+ result.totalEstimateSourceGas += ett.estimateSourceGas;
448
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
449
+ finalSendParam[3].gasLimit = ett.estimateSourceGasLimit;
450
+ execTime.log("send.estimateGas");
451
+ result.sendParam = {
452
+ contract: oftContract,
453
+ method: "send",
454
+ param: finalSendParam
455
+ };
456
+ for (const feeKey in result.fees) {
457
+ if (excludeFees.includes(feeKey) || !/Usd$/.test(feeKey)) {
458
+ continue;
459
+ }
460
+ result.totalFeesUsd = Big(result.totalFeesUsd || 0).plus(result.fees[feeKey] || 0);
461
+ }
462
+ result.totalFeesUsd = numberRemoveEndZero(Big(result.totalFeesUsd).toFixed(20));
463
+ execTime.logTotal("quoteOFT");
464
+ return result;
465
+ }
466
+ async sendTransaction(params) {
467
+ const {
468
+ method,
469
+ contract,
470
+ param
471
+ } = params;
472
+ const overridesIndex = param.length - 1;
473
+ const overrides = param[overridesIndex] && typeof param[overridesIndex] === "object" && !Array.isArray(param[overridesIndex]) ? { ...param[overridesIndex] } : {};
474
+ if (!overrides.maxFeePerGas) {
475
+ try {
476
+ const feeData = await this.provider.getFeeData();
477
+ if (feeData.maxFeePerGas) {
478
+ overrides.maxFeePerGas = feeData.maxFeePerGas * 120n / 100n;
479
+ }
480
+ if (feeData.maxPriorityFeePerGas && !overrides.maxPriorityFeePerGas) {
481
+ overrides.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
482
+ }
483
+ } catch (error) {
484
+ this.csl("EVM sendTransaction", "red-500", "Failed to get fee data for gas buffer: %o", error);
485
+ }
486
+ }
487
+ const finalParam = [...param];
488
+ if (param[overridesIndex] && typeof param[overridesIndex] === "object" && !Array.isArray(param[overridesIndex])) {
489
+ finalParam[overridesIndex] = overrides;
490
+ } else {
491
+ finalParam.push(overrides);
492
+ }
493
+ try {
494
+ const tx = await contract[method](...finalParam);
495
+ return tx.hash;
496
+ } catch (error) {
497
+ this.csl("EVM sendTransaction", "red-500", "Error sending transaction: %o, message: %o", error, error.message);
498
+ let _finalErrorMessage = `Transaction failed: ${error.message}`;
499
+ if (error?.message?.includes("user rejected action")) {
500
+ _finalErrorMessage = error.message;
501
+ }
502
+ throw new Error(_finalErrorMessage);
503
+ }
504
+ }
505
+ /**
506
+ * Unified quote method that routes to specific quote methods based on type
507
+ * @param type Service type from Service
508
+ * @param params Parameters for the quote
509
+ */
510
+ async quote(type, params) {
511
+ switch (type) {
512
+ case Service.CCTP:
513
+ return await this.quoteCCTP(params);
514
+ case Service.Usdt0:
515
+ return await this.quoteOFT(params);
516
+ case Service.OneClick:
517
+ return await this.quoteOneClickProxy(params);
518
+ case Service.Native:
519
+ return await this.quoteNative(params);
520
+ case Service.FraxZero:
521
+ return await this.quoteFraxZero(params);
522
+ default:
523
+ throw new Error(`Unsupported quote type: ${type}`);
524
+ }
525
+ }
526
+ /**
527
+ * Unified send method that routes to specific send methods based on type
528
+ * @param type Send type from SendType enum
529
+ * @param params Parameters for the send transaction
530
+ */
531
+ async send(type, params) {
532
+ switch (type) {
533
+ case SendType.SEND:
534
+ return await this.sendTransaction(params);
535
+ case SendType.TRANSFER:
536
+ return await this.transfer(params);
537
+ default:
538
+ throw new Error(`Unsupported send type: ${type}`);
539
+ }
540
+ }
541
+ async quoteCCTP(params) {
542
+ const {
543
+ dry,
544
+ proxyAddress,
545
+ abi,
546
+ refundTo,
547
+ recipient,
548
+ amountWei,
549
+ // slippageTolerance,
550
+ fromToken,
551
+ toToken,
552
+ prices,
553
+ evmGasFees,
554
+ excludeFees,
555
+ destinationDomain,
556
+ sourceDomain
557
+ } = params;
558
+ const result = {
559
+ needApprove: false,
560
+ approveSpender: proxyAddress,
561
+ sendParam: void 0,
562
+ quoteParam: {
563
+ sourceDomain,
564
+ destinationDomain,
565
+ proxyAddress,
566
+ ...params
567
+ },
568
+ fees: {},
569
+ totalFeesUsd: void 0,
570
+ estimateSourceGas: void 0,
571
+ totalEstimateSourceGas: 0n,
572
+ estimateSourceGasUsd: void 0,
573
+ estimateTime: Math.floor(Math.random() * 8) + 3,
574
+ outputAmount: numberRemoveEndZero(Big(amountWei || 0).div(10 ** fromToken.decimals).toFixed(fromToken.decimals, 0))
575
+ };
576
+ const provider = evmRpcFallbackProvider(fromToken);
577
+ const execTime = new ExecTime({ type: `CCTP EVM ${fromToken.chainName}->${toToken.chainName}`, logStyle: "stone-200", isDebug: OpenAPI.DEBUG });
578
+ const proxyContract = new ethers.Contract(proxyAddress, abi, this.signer);
579
+ new ethers.Contract(proxyAddress, abi, provider);
580
+ let realRecipient = recipient;
581
+ execTime.breakpoint();
582
+ const getUsrNonce = async () => {
583
+ let userNonce2 = 0n;
584
+ try {
585
+ userNonce2 = await proxyContract.userNonces(refundTo);
586
+ } catch (error) {
587
+ }
588
+ return userNonce2;
589
+ };
590
+ const mergedCalls = [
591
+ getDestinationAssociatedTokenAddress({
592
+ recipient,
593
+ toToken
594
+ }),
595
+ getUsrNonce(),
596
+ this.allowance({
597
+ dry,
598
+ contractAddress: fromToken.contractAddress,
599
+ address: refundTo,
600
+ spender: proxyAddress,
601
+ amountWei,
602
+ provider
603
+ })
604
+ ];
605
+ const [destATA, userNonce, allowance] = await Promise.all(mergedCalls);
606
+ execTime.log("getDestinationATA & getUsrNonce & get allowance", "destATA: %o, userNonce: %o, allowance: %o", destATA, userNonce, allowance);
607
+ result.needCreateTokenAccount = destATA.needCreateTokenAccount;
608
+ if (destATA.associatedTokenAddress) {
609
+ realRecipient = destATA.associatedTokenAddress;
610
+ }
611
+ result.needApprove = allowance.needApprove;
612
+ execTime.breakpoint();
613
+ const signatureRes = await quoteSignature({
614
+ address: refundTo,
615
+ amount: numberRemoveEndZero(Big(amountWei || 0).div(10 ** fromToken.decimals).toFixed(fromToken.decimals, 0)),
616
+ destination_domain_id: destinationDomain,
617
+ receipt_address: realRecipient,
618
+ source_domain_id: sourceDomain,
619
+ user_nonce: Number(userNonce)
620
+ });
621
+ const {
622
+ bridge_fee,
623
+ finality_threshold,
624
+ max_fee,
625
+ mint_fee,
626
+ receipt_amount,
627
+ signature,
628
+ destination_caller
629
+ } = signatureRes;
630
+ execTime.log("quoteSignature");
631
+ result.fees.estimateMintGasUsd = numberRemoveEndZero(Big(mint_fee || 0).div(10 ** fromToken.decimals).toFixed(fromToken.decimals));
632
+ result.fees.bridgeFeeUsd = numberRemoveEndZero(Big(bridge_fee || 0).div(10 ** fromToken.decimals).toFixed(fromToken.decimals));
633
+ const chargedAmount = BigInt(amountWei) - BigInt(mint_fee);
634
+ result.outputAmount = numberRemoveEndZero(Big(receipt_amount || 0).div(10 ** fromToken.decimals).toFixed(fromToken.decimals, 0));
635
+ const depositParam = [
636
+ // originalAmount
637
+ amountWei,
638
+ // chargedAmount = originalAmount - gas fee
639
+ chargedAmount,
640
+ // destinationDomain
641
+ destinationDomain,
642
+ // mintRecipient
643
+ addressToBytes32(toToken.chainType, realRecipient),
644
+ // burnToken
645
+ fromToken.contractAddress,
646
+ // destinationCaller
647
+ destination_caller ? addressToBytes32(toToken.chainType, destination_caller) : "0x0000000000000000000000000000000000000000000000000000000000000000",
648
+ // maxFee
649
+ max_fee,
650
+ // minFinalityThreshold
651
+ finality_threshold,
652
+ // signature
653
+ signature
654
+ ];
655
+ execTime.breakpoint();
656
+ const ett = await this.estimateTransaction({
657
+ dry,
658
+ contract: proxyContract,
659
+ method: "depositWithFee",
660
+ param: depositParam,
661
+ fromToken,
662
+ prices,
663
+ evmGasFees
664
+ });
665
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
666
+ result.estimateSourceGas = ett.estimateSourceGas;
667
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
668
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
669
+ depositParam.push({ gasLimit: ett.estimateSourceGasLimit });
670
+ execTime.log("estimateTransaction");
671
+ result.sendParam = {
672
+ method: "depositWithFee",
673
+ contract: proxyContract,
674
+ param: depositParam
675
+ };
676
+ for (const feeKey in result.fees) {
677
+ if (excludeFees.includes(feeKey) || !/Usd$/.test(feeKey)) {
678
+ continue;
679
+ }
680
+ result.totalFeesUsd = Big(result.totalFeesUsd || 0).plus(result.fees[feeKey] || 0);
681
+ }
682
+ result.totalFeesUsd = numberRemoveEndZero(Big(result.totalFeesUsd).toFixed(20));
683
+ execTime.logTotal("quoteCCTP");
684
+ return result;
685
+ }
686
+ async quoteOneClickProxy(params) {
687
+ const {
688
+ dry,
689
+ proxyAddress,
690
+ abi,
691
+ fromToken,
692
+ refundTo,
693
+ depositAddress,
694
+ amountWei,
695
+ prices,
696
+ evmGasFees
697
+ } = params;
698
+ const execTime = new ExecTime({ type: `OneClick EVM ${fromToken.chainName}`, logStyle: "stone-300", isDebug: OpenAPI.DEBUG });
699
+ const result = { fees: {} };
700
+ const provider = evmRpcFallbackProvider(fromToken);
701
+ const proxyContract = new ethers.Contract(proxyAddress, abi, this.signer);
702
+ const proxyParam = [
703
+ // tokenAddress
704
+ fromToken.contractAddress,
705
+ // recipient
706
+ depositAddress,
707
+ // amount
708
+ amountWei
709
+ ];
710
+ execTime.breakpoint();
711
+ const mergedCalls = [
712
+ this.allowance({
713
+ dry,
714
+ contractAddress: fromToken.contractAddress,
715
+ address: refundTo,
716
+ spender: proxyAddress,
717
+ amountWei,
718
+ provider
719
+ }),
720
+ this.estimateTransaction({
721
+ dry,
722
+ contract: proxyContract,
723
+ method: "proxyTransfer",
724
+ param: proxyParam,
725
+ fromToken,
726
+ prices,
727
+ evmGasFees
728
+ })
729
+ ];
730
+ const [allowance, ett] = await Promise.all(mergedCalls);
731
+ result.needApprove = allowance.needApprove;
732
+ result.approveSpender = proxyAddress;
733
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
734
+ result.estimateSourceGas = ett.estimateSourceGas;
735
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
736
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
737
+ proxyParam.push({ gasLimit: ett.estimateSourceGasLimit });
738
+ execTime.log("allowance & estimateTransaction");
739
+ result.sendParam = {
740
+ method: "proxyTransfer",
741
+ contract: proxyContract,
742
+ param: proxyParam
743
+ };
744
+ execTime.logTotal("quoteOneClickPorxy");
745
+ return result;
746
+ }
747
+ async quoteNative(params) {
748
+ const {
749
+ quoteResponse,
750
+ bridgeRouterAddress,
751
+ ...restParams
752
+ } = params;
753
+ const {
754
+ dry,
755
+ amountWei,
756
+ refundTo,
757
+ fromToken,
758
+ toToken,
759
+ prices,
760
+ evmGasFees
761
+ } = restParams;
762
+ const result = {
763
+ ...quoteResponse,
764
+ fees: {},
765
+ needApprove: false,
766
+ approveSpender: bridgeRouterAddress,
767
+ quoteParam: {
768
+ ...restParams
769
+ },
770
+ sendParam: {
771
+ txRequest: quoteResponse.txRequest
772
+ },
773
+ totalFeesUsd: void 0,
774
+ estimateSourceGas: void 0,
775
+ totalEstimateSourceGas: 0n,
776
+ estimateSourceGasUsd: void 0,
777
+ estimateTime: quoteResponse.priority === "fast" ? Math.floor(Math.random() * 10) + 50 : Math.floor(Math.random() * 60) + 300,
778
+ outputAmount: 0
779
+ };
780
+ const execTime = new ExecTime({ type: `Native EVM ${fromToken.chainName}->${toToken.chainName}`, logStyle: "stone-400", isDebug: OpenAPI.DEBUG });
781
+ evmRpcFallbackProvider(fromToken);
782
+ const estimateNativeGas = async () => {
783
+ if (dry) {
784
+ const ett = await this.estimateTransaction({
785
+ dry,
786
+ fromToken,
787
+ prices,
788
+ evmGasFees
789
+ });
790
+ result.outputAmount = quoteResponse.buyerTokenAmount + "";
791
+ result.fees = {
792
+ estimateGasUsd: ett.estimateSourceGasUsd,
793
+ widgetFeeUsd: quoteResponse.widgetFeeUsd,
794
+ liquidityProviderFeeUsd: quoteResponse.liquidityProviderFeeUsd
795
+ };
796
+ result.estimateSourceGas = ett.estimateSourceGas;
797
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
798
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
799
+ result.totalFeesUsd = numberRemoveEndZero(Big(quoteResponse.totalFeeUsd).toFixed(20));
800
+ } else {
801
+ const ett = await this.estimateTransaction({
802
+ dry,
803
+ fromToken,
804
+ prices,
805
+ evmGasFees,
806
+ txRequest: quoteResponse.txRequest,
807
+ refundTo
808
+ });
809
+ result.outputAmount = Big(quoteResponse.amountOut || 0).div(10 ** (toToken.decimals || 6)).toFixed(toToken.decimals || 6, 0);
810
+ result.fees = {
811
+ estimateGasUsd: ett.estimateSourceGasUsd,
812
+ // maybe 100 = 1%
813
+ widgetFeeUsd: numberRemoveEndZero(Big(amountWei || 0).div(10 ** (fromToken.decimals || 6)).times(quoteResponse.widgetFee.feeRate || 0).toFixed(20, 0)),
814
+ // not return by api
815
+ liquidityProviderFeeUsd: "0"
816
+ };
817
+ result.estimateSourceGas = ett.estimateSourceGas;
818
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
819
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
820
+ result.totalFeesUsd = numberRemoveEndZero(Big(quoteResponse.amountOutBeforeFee || 0).minus(quoteResponse.amountOut).div(10 ** (fromToken.decimals || 6)).toFixed(20, 0));
821
+ result.txRequest.gasLimit = ett.estimateSourceGasLimit;
822
+ }
823
+ };
824
+ execTime.breakpoint();
825
+ const mergedCalls = [
826
+ this.allowance({
827
+ dry,
828
+ contractAddress: fromToken.contractAddress,
829
+ spender: bridgeRouterAddress,
830
+ address: refundTo,
831
+ amountWei
832
+ }),
833
+ estimateNativeGas()
834
+ ];
835
+ const [allowanceResult] = await Promise.all(mergedCalls);
836
+ result.needApprove = allowanceResult.needApprove;
837
+ execTime.log("allowance & estimateNativeGas");
838
+ execTime.logTotal("quoteNative");
839
+ return result;
840
+ }
841
+ async quoteFraxZero(params) {
842
+ const {
843
+ dry,
844
+ abi,
845
+ recipient,
846
+ amountWei,
847
+ slippageTolerance,
848
+ fromToken,
849
+ toToken,
850
+ prices,
851
+ evmGasFees,
852
+ excludeFees,
853
+ refundTo,
854
+ originLayerzero,
855
+ destinationLayerzero
856
+ } = params;
857
+ const {
858
+ eid: srcEid,
859
+ remoteHop,
860
+ lockbox
861
+ } = originLayerzero;
862
+ const {
863
+ eid: dstEid
864
+ } = destinationLayerzero;
865
+ const isFromEthereum = fromToken.chainId === 1;
866
+ const result = {
867
+ needApprove: false,
868
+ approveSpender: remoteHop,
869
+ sendParam: void 0,
870
+ quoteParam: {
871
+ ...params
872
+ },
873
+ fees: {},
874
+ totalFeesUsd: 0,
875
+ estimateSourceGas: 0n,
876
+ totalEstimateSourceGas: 0n,
877
+ estimateSourceGasUsd: 0,
878
+ estimateTime: 0,
879
+ outputAmount: numberRemoveEndZero(Big(amountWei || 0).div(10 ** params.fromToken.decimals).toFixed(params.fromToken.decimals, 0))
880
+ };
881
+ const provider = evmRpcFallbackProvider(fromToken);
882
+ const execTime = new ExecTime({ type: `FraxZero EVM ${fromToken.chainName}->${toToken.chainName}`, logStyle: "stone-500", isDebug: OpenAPI.DEBUG });
883
+ const remoteHopContract = new ethers.Contract(remoteHop, abi, this.signer);
884
+ const remoteHopContractRead = new ethers.Contract(remoteHop, abi, provider);
885
+ const sendParams = [
886
+ // _oft
887
+ isFromEthereum ? lockbox : fromToken.contractAddress,
888
+ // _dstEid
889
+ dstEid,
890
+ // _to
891
+ addressToBytes32(toToken.chainType, recipient),
892
+ // _amountLD
893
+ amountWei
894
+ ];
895
+ execTime.breakpoint();
896
+ const mergedCalls = [
897
+ this.allowance({
898
+ dry,
899
+ contractAddress: fromToken.contractAddress,
900
+ spender: remoteHop,
901
+ address: refundTo,
902
+ amountWei,
903
+ provider
904
+ }),
905
+ remoteHopContractRead.quote.staticCall(...sendParams)
906
+ ];
907
+ const [allowanceResult, msgFee] = await Promise.all(mergedCalls);
908
+ execTime.log("allowance & quote.staticCall");
909
+ result.needApprove = allowanceResult.needApprove;
910
+ let nativeMsgFee = msgFee[0];
911
+ this.csl("EVM quoteFraxZero", "blue-700", "nativeMsgFee: %o", nativeMsgFee);
912
+ nativeMsgFee = nativeMsgFee * NATIVE_MSG_FEE_BUFFER / 100n;
913
+ this.csl("EVM quoteFraxZero", "blue-700", "nativeMsgFee after buffer: %o", nativeMsgFee);
914
+ result.totalEstimateSourceGas = nativeMsgFee;
915
+ const nativeFeeUsd = Big(nativeMsgFee?.toString() || 0).div(10 ** fromToken.nativeToken.decimals).times(getPrice(prices, fromToken.nativeToken.symbol));
916
+ result.fees.nativeFee = numberRemoveEndZero(Big(nativeMsgFee?.toString() || 0).div(10 ** fromToken.nativeToken.decimals).toFixed(fromToken.nativeToken.decimals));
917
+ result.fees.nativeFeeUsd = numberRemoveEndZero(Big(nativeFeeUsd).toFixed(20));
918
+ result.fees.lzTokenFeeUsd = numberRemoveEndZero(Big(msgFee[1]?.toString() || 0).div(10 ** fromToken.decimals).toFixed(20));
919
+ this.csl("EVM quoteFraxZero", "blue-700", "msgFee: %o", msgFee);
920
+ execTime.breakpoint();
921
+ const finalSendParam = [...sendParams, { value: nativeMsgFee }];
922
+ const ett = await this.estimateTransaction({
923
+ dry,
924
+ contract: remoteHopContract,
925
+ method: "sendOFT",
926
+ param: finalSendParam,
927
+ fromToken,
928
+ prices,
929
+ evmGasFees
930
+ });
931
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
932
+ result.estimateSourceGas = ett.estimateSourceGas;
933
+ result.totalEstimateSourceGas += ett.estimateSourceGas;
934
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
935
+ finalSendParam[finalSendParam.length - 1].gasLimit = ett.estimateSourceGasLimit;
936
+ execTime.log("sendOFT.estimateTransaction");
937
+ result.sendParam = {
938
+ contract: remoteHopContract,
939
+ method: "sendOFT",
940
+ param: finalSendParam
941
+ };
942
+ for (const feeKey in result.fees) {
943
+ if (excludeFees.includes(feeKey) || !/Usd$/.test(feeKey)) {
944
+ continue;
945
+ }
946
+ result.totalFeesUsd = Big(result.totalFeesUsd || 0).plus(result.fees[feeKey] || 0);
947
+ }
948
+ result.totalFeesUsd = numberRemoveEndZero(Big(result.totalFeesUsd).toFixed(20));
949
+ execTime.logTotal("quoteFraxZero");
950
+ return result;
951
+ }
952
+ async preivewRedeemFrxUSD(params) {
953
+ const {
954
+ dry,
955
+ amountWei,
956
+ fromToken,
957
+ abi,
958
+ usdcCustodianAddress,
959
+ rwaCustodianAddress,
960
+ redemptionAddress
961
+ } = params;
962
+ if (dry) {
963
+ return {
964
+ maxUsdc: 0n,
965
+ maxRwa: 0n,
966
+ amountWeiBigInt: 0n,
967
+ // The token obtained from redeem is Ethereum USDC
968
+ totalAssetsOut: BigInt(Big(amountWei || 0).div(10 ** fromToken.decimals).times(10 ** FRAXZERO_MIDDLE_TOKEN_USDC.decimals).toFixed(0, 0)),
969
+ isInsufficientLiquidity: false
970
+ };
971
+ }
972
+ const execTime = new ExecTime({ type: `FraxZero EVM preivewRedeemFrxUSD ${fromToken.chainName}`, logStyle: "stone-600", isDebug: OpenAPI.DEBUG });
973
+ const provider = evmRpcFallbackProvider(fromToken);
974
+ const usdcCustodian = new ethers.Contract(usdcCustodianAddress, abi, provider);
975
+ const rwaCustodian = new ethers.Contract(rwaCustodianAddress, abi, provider);
976
+ const redemption = new ethers.Contract(redemptionAddress, abi, provider);
977
+ execTime.breakpoint();
978
+ const [usdcView, redemptionView] = await Promise.all([
979
+ usdcCustodian.mdwrComboView.staticCall(),
980
+ redemption.maxUstbRedemptionAmount.staticCall()
981
+ ]);
982
+ execTime.log("usdcCustodian & redemption");
983
+ const maxUsdc = usdcView[3];
984
+ const superstateTokenAmount = redemptionView[0];
985
+ const [maxRwa] = await redemption.calculateUsdcOut.staticCall(superstateTokenAmount);
986
+ const amountWeiBigInt = BigInt(amountWei || 0);
987
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "usdcCustodian maxSharesRedeemable(frxUSD wei amount): %o", maxUsdc);
988
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "rwaCustodian maxSharesRedeemable(USDC wei amount): %o", maxRwa);
989
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "amountWei: %o", amountWeiBigInt);
990
+ let totalAssetsOut = 0n;
991
+ let isInsufficientLiquidity = false;
992
+ if (amountWeiBigInt <= maxUsdc) {
993
+ execTime.breakpoint();
994
+ const assetsOut = await usdcCustodian.previewRedeem.staticCall(amountWeiBigInt);
995
+ execTime.log("usdcCustodian.previewRedeem (USDC only)");
996
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "USDC path only, usdcCustodian previewRedeem input: %o, value: %o", amountWeiBigInt, assetsOut);
997
+ totalAssetsOut = assetsOut;
998
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "USDC path only, totalAssetsOut: %o", totalAssetsOut);
999
+ } else {
1000
+ if (maxUsdc > 0n) {
1001
+ execTime.breakpoint();
1002
+ const usdcAssetsOut = await usdcCustodian.previewRedeem.staticCall(maxUsdc);
1003
+ execTime.log("usdcCustodian.previewRedeem (mixed)");
1004
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "USDC first (maxUsdc), usdcCustodian previewRedeem input: %o, value: %o", maxUsdc, usdcAssetsOut);
1005
+ totalAssetsOut += usdcAssetsOut;
1006
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "USDC first (maxUsdc), totalAssetsOut: %o", totalAssetsOut);
1007
+ }
1008
+ const rwaAmount = amountWeiBigInt - maxUsdc;
1009
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "RWA for remainder, rwaAmount: %o", rwaAmount);
1010
+ if (rwaAmount > 0n) {
1011
+ execTime.breakpoint();
1012
+ const superstateTokenInAmount = await rwaCustodian.previewRedeem.staticCall(rwaAmount);
1013
+ execTime.log("rwaCustodian.previewRedeem (mixed)");
1014
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "USDC first (maxUsdc), rwaCustodian previewRedeem input: %o, superstateTokenInAmount: %o", rwaAmount, superstateTokenInAmount);
1015
+ execTime.breakpoint();
1016
+ const [rwaAssetsOut] = await redemption.calculateUsdcOut.staticCall(superstateTokenInAmount);
1017
+ execTime.log("redemption.calculateUsdcOut (mixed)");
1018
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "USDC first (maxUsdc), rwaCustodian previewRedeem input: %o, value: %o", rwaAmount, rwaAssetsOut);
1019
+ totalAssetsOut += rwaAssetsOut;
1020
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "RWA for remainder, totalAssetsOut: %o", totalAssetsOut);
1021
+ if (rwaAssetsOut > maxRwa) {
1022
+ isInsufficientLiquidity = true;
1023
+ }
1024
+ }
1025
+ }
1026
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "final totalAssetsOut: %o", totalAssetsOut);
1027
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "final insufficient liquidity: %o", isInsufficientLiquidity);
1028
+ execTime.logTotal("preivewRedeemFrxUSD");
1029
+ return {
1030
+ maxUsdc,
1031
+ maxRwa,
1032
+ amountWeiBigInt,
1033
+ totalAssetsOut,
1034
+ isInsufficientLiquidity
1035
+ };
1036
+ }
1037
+ async previewMintFrxUSD(params) {
1038
+ const {
1039
+ dry,
1040
+ amountWei,
1041
+ fromToken,
1042
+ abi,
1043
+ usdcCustodianAddress
1044
+ } = params;
1045
+ this.csl("EVM previewMintFrxUSD", "blue-700", "params: %o", params);
1046
+ if (dry) {
1047
+ return {
1048
+ maxUsdc: 0n,
1049
+ amountWeiBigInt: 0n,
1050
+ totalMax: 0n,
1051
+ // Minting means converting Ethereum USDC into Ethereum frxUSD
1052
+ totalAssetsOut: BigInt(Big(amountWei || 0).div(10 ** fromToken.decimals).times(10 ** FRAXZERO_MIDDLE_TOKEN_FRXUSD.decimals).toFixed(0, 0))
1053
+ };
1054
+ }
1055
+ const execTime = new ExecTime({ type: `FraxZero EVM previewMintFrxUSD ${fromToken.chainName}`, logStyle: "stone-700", isDebug: OpenAPI.DEBUG });
1056
+ const provider = evmRpcFallbackProvider(fromToken);
1057
+ const usdcCustodian = new ethers.Contract(usdcCustodianAddress, abi, provider);
1058
+ execTime.breakpoint();
1059
+ const usdcView = await usdcCustodian.mdwrComboView.staticCall();
1060
+ execTime.log("usdcCustodian.mdwrComboView");
1061
+ const maxUsdc = usdcView[0];
1062
+ const amountWeiBigInt = BigInt(amountWei || 0);
1063
+ const totalMax = maxUsdc;
1064
+ execTime.breakpoint();
1065
+ const totalAssetsOut = await usdcCustodian.previewDeposit.staticCall(amountWeiBigInt);
1066
+ execTime.log("usdcCustodian.previewDeposit");
1067
+ execTime.logTotal("previewMintFrxUSD");
1068
+ return {
1069
+ maxUsdc,
1070
+ amountWeiBigInt,
1071
+ totalMax,
1072
+ totalAssetsOut
1073
+ };
1074
+ }
1075
+ /**
1076
+ * Redeem frxUSD to USDC via USDC and/or RWA custodian contracts.
1077
+ * Uses mdwrComboView to get max redeemable liquidity and splits redemption across USDC/RWA paths.
1078
+ * Uses previewRedeem to get accurate output amount (USDC).
1079
+ */
1080
+ async redeemFrxUSD(params) {
1081
+ const {
1082
+ dry,
1083
+ recipient,
1084
+ amountWei,
1085
+ fromToken,
1086
+ toToken,
1087
+ prices,
1088
+ evmGasFees,
1089
+ refundTo,
1090
+ abi,
1091
+ usdcCustodianAddress,
1092
+ rwaCustodianAddress,
1093
+ redeemAndMintContractAddress,
1094
+ preivewRedeemResult
1095
+ } = params;
1096
+ this.csl("EVM redeemFrxUSD", "blue-700", "params: %o", params);
1097
+ const ETH_AVG_BLOCK_TIME = 12;
1098
+ const estimateTime = ETH_AVG_BLOCK_TIME * 2 + Math.floor(Math.random() * 11) - 5;
1099
+ const result = {
1100
+ needApprove: false,
1101
+ approveSpender: redeemAndMintContractAddress,
1102
+ sendParam: void 0,
1103
+ quoteParam: {
1104
+ ...params
1105
+ },
1106
+ fees: {},
1107
+ totalFeesUsd: 0,
1108
+ estimateSourceGas: 0n,
1109
+ totalEstimateSourceGas: 0n,
1110
+ estimateSourceGasUsd: 0,
1111
+ estimateTime,
1112
+ outputAmount: "0"
1113
+ };
1114
+ const provider = evmRpcFallbackProvider(fromToken);
1115
+ const execTime = new ExecTime({ type: `FraxZero EVM redeemFrxUSD ${fromToken.chainName}->${toToken.chainName}`, logStyle: "stone-800", isDebug: OpenAPI.DEBUG });
1116
+ new ethers.Contract(redeemAndMintContractAddress, abi, provider);
1117
+ const redeemContract = new ethers.Contract(redeemAndMintContractAddress, abi, this.signer);
1118
+ execTime.breakpoint();
1119
+ let finalPreivewRedeemResult = preivewRedeemResult;
1120
+ if (!finalPreivewRedeemResult) {
1121
+ finalPreivewRedeemResult = await this.preivewRedeemFrxUSD(params);
1122
+ }
1123
+ const {
1124
+ isInsufficientLiquidity,
1125
+ totalAssetsOut
1126
+ } = finalPreivewRedeemResult;
1127
+ execTime.log("preivewRedeemFrxUSD");
1128
+ if (isInsufficientLiquidity) {
1129
+ throw new Error("Insufficient liquidity");
1130
+ }
1131
+ result.outputAmount = numberRemoveEndZero(
1132
+ Big(totalAssetsOut.toString()).div(10 ** toToken.decimals).toFixed(toToken.decimals, 0)
1133
+ );
1134
+ execTime.breakpoint();
1135
+ const allowanceResult = await this.allowance({
1136
+ dry,
1137
+ contractAddress: fromToken.contractAddress,
1138
+ spender: redeemAndMintContractAddress,
1139
+ address: refundTo,
1140
+ amountWei,
1141
+ provider
1142
+ });
1143
+ result.needApprove = allowanceResult.needApprove;
1144
+ execTime.log("allowance");
1145
+ result.sendParam = {
1146
+ contract: redeemContract,
1147
+ method: "redeemToUsdcAndTransfer",
1148
+ param: [
1149
+ amountWei,
1150
+ recipient
1151
+ ]
1152
+ };
1153
+ execTime.breakpoint();
1154
+ const ett = await this.estimateTransaction({
1155
+ dry,
1156
+ contract: redeemContract,
1157
+ method: result.sendParam.method,
1158
+ param: [...result.sendParam.param, { gasLimit: DEFAULT_GAS_LIMIT_FAILED }],
1159
+ fromToken,
1160
+ prices,
1161
+ evmGasFees
1162
+ });
1163
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
1164
+ result.estimateSourceGas = ett.estimateSourceGas;
1165
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
1166
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
1167
+ result.totalFeesUsd = ett.estimateSourceGasUsd;
1168
+ result.sendParam.param[2] = { gasLimit: ett.estimateSourceGasLimit };
1169
+ execTime.log("estimateTransaction");
1170
+ execTime.logTotal("redeemFrxUSD");
1171
+ return result;
1172
+ }
1173
+ /**
1174
+ * Mint frxUSD by depositing USDC into the custodian contract.
1175
+ * Calls deposit(assetsIn, receiver) on usdcCustodianAddress.
1176
+ * assetsIn = USDC amount (6 decimals), sharesOut = frxUSD amount (18 decimals).
1177
+ */
1178
+ async mintFrxUSD(params) {
1179
+ const {
1180
+ dry,
1181
+ recipient,
1182
+ amountWei,
1183
+ fromToken,
1184
+ toToken,
1185
+ prices,
1186
+ evmGasFees,
1187
+ refundTo,
1188
+ abi,
1189
+ usdcCustodianAddress,
1190
+ previewMintResult
1191
+ } = params;
1192
+ this.csl("EVM mintFrxUSD", "blue-700", "params: %o", params);
1193
+ const ETH_AVG_BLOCK_TIME = 12;
1194
+ const estimateTime = ETH_AVG_BLOCK_TIME * 2 + Math.floor(Math.random() * 11) - 5;
1195
+ const result = {
1196
+ needApprove: false,
1197
+ approveSpender: usdcCustodianAddress,
1198
+ sendParam: void 0,
1199
+ quoteParam: {
1200
+ ...params
1201
+ },
1202
+ fees: {},
1203
+ totalFeesUsd: 0,
1204
+ estimateSourceGas: 0n,
1205
+ totalEstimateSourceGas: 0n,
1206
+ estimateSourceGasUsd: 0,
1207
+ estimateTime,
1208
+ outputAmount: "0"
1209
+ };
1210
+ const execTime = new ExecTime({ type: `FraxZero EVM mintFrxUSD ${fromToken.chainName}->${toToken.chainName}`, logStyle: "stone-900", isDebug: OpenAPI.DEBUG });
1211
+ const provider = evmRpcFallbackProvider(fromToken);
1212
+ execTime.breakpoint();
1213
+ const allowanceResult = await this.allowance({
1214
+ dry,
1215
+ contractAddress: fromToken.contractAddress,
1216
+ spender: usdcCustodianAddress,
1217
+ address: refundTo,
1218
+ amountWei,
1219
+ provider
1220
+ });
1221
+ result.needApprove = allowanceResult.needApprove;
1222
+ execTime.log("allowance");
1223
+ new ethers.Contract(usdcCustodianAddress, abi, provider);
1224
+ const usdcCustodianWithSigner = new ethers.Contract(usdcCustodianAddress, abi, this.signer);
1225
+ execTime.breakpoint();
1226
+ let finalPreviewMintResult = previewMintResult;
1227
+ if (!finalPreviewMintResult) {
1228
+ finalPreviewMintResult = await this.previewMintFrxUSD(params);
1229
+ }
1230
+ const {
1231
+ maxUsdc,
1232
+ amountWeiBigInt,
1233
+ totalMax,
1234
+ totalAssetsOut
1235
+ } = finalPreviewMintResult;
1236
+ execTime.log("previewMintFrxUSD");
1237
+ result.outputAmount = numberRemoveEndZero(
1238
+ Big(totalAssetsOut.toString()).div(10 ** toToken.decimals).toFixed(toToken.decimals, 0)
1239
+ );
1240
+ result.sendParam = {
1241
+ contract: usdcCustodianWithSigner,
1242
+ method: "deposit",
1243
+ param: [
1244
+ amountWeiBigInt,
1245
+ recipient
1246
+ ]
1247
+ };
1248
+ execTime.breakpoint();
1249
+ const ett = await this.estimateTransaction({
1250
+ dry,
1251
+ contract: usdcCustodianWithSigner,
1252
+ method: "deposit",
1253
+ param: [...result.sendParam.param, { gasLimit: DEFAULT_GAS_LIMIT_FAILED }],
1254
+ fromToken,
1255
+ prices,
1256
+ evmGasFees
1257
+ });
1258
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
1259
+ result.estimateSourceGas = ett.estimateSourceGas;
1260
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
1261
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
1262
+ result.totalFeesUsd = ett.estimateSourceGasUsd;
1263
+ result.sendParam.param[2] = { gasLimit: ett.estimateSourceGasLimit };
1264
+ execTime.log("estimateTransaction");
1265
+ execTime.logTotal("mintFrxUSD");
1266
+ return result;
1267
+ }
1268
+ async mintAndSendFrxUSD(params) {
1269
+ const {
1270
+ dry,
1271
+ recipient,
1272
+ amountWei,
1273
+ fromToken,
1274
+ toToken,
1275
+ prices,
1276
+ evmGasFees,
1277
+ refundTo,
1278
+ abi,
1279
+ usdcCustodianAddress,
1280
+ redeemAndMintContractAddress,
1281
+ originLayerzero,
1282
+ destinationLayerzero,
1283
+ previewMintResult
1284
+ } = params;
1285
+ this.csl("EVM mintAndSendFrxUSD", "blue-700", "params: %o", params);
1286
+ const {
1287
+ eid: dstEid
1288
+ } = destinationLayerzero;
1289
+ const ETH_AVG_BLOCK_TIME = 12;
1290
+ const estimateTime = ETH_AVG_BLOCK_TIME * 2 + Math.floor(Math.random() * 11) - 5;
1291
+ const result = {
1292
+ needApprove: false,
1293
+ approveSpender: redeemAndMintContractAddress,
1294
+ sendParam: void 0,
1295
+ quoteParam: {
1296
+ ...params
1297
+ },
1298
+ fees: {},
1299
+ totalFeesUsd: 0,
1300
+ estimateSourceGas: 0n,
1301
+ totalEstimateSourceGas: 0n,
1302
+ estimateSourceGasUsd: 0,
1303
+ estimateTime,
1304
+ outputAmount: "0"
1305
+ };
1306
+ const execTime = new ExecTime({ type: `FraxZero EVM mintAndSendFrxUSD ${fromToken.chainName}->${toToken.chainName}`, logStyle: "stone-950", isDebug: OpenAPI.DEBUG });
1307
+ const provider = evmRpcFallbackProvider(fromToken);
1308
+ execTime.breakpoint();
1309
+ const allowanceResult = await this.allowance({
1310
+ dry,
1311
+ contractAddress: fromToken.contractAddress,
1312
+ spender: redeemAndMintContractAddress,
1313
+ address: refundTo,
1314
+ amountWei,
1315
+ provider
1316
+ });
1317
+ result.needApprove = allowanceResult.needApprove;
1318
+ execTime.log("allowance");
1319
+ const redeemAndMintContractWithSigner = new ethers.Contract(redeemAndMintContractAddress, abi, this.signer);
1320
+ execTime.breakpoint();
1321
+ let finalPreviewMintResult = previewMintResult;
1322
+ if (!finalPreviewMintResult) {
1323
+ finalPreviewMintResult = await this.previewMintFrxUSD(params);
1324
+ }
1325
+ const {
1326
+ totalAssetsOut
1327
+ } = finalPreviewMintResult;
1328
+ execTime.log("previewMintFrxUSD");
1329
+ this.csl("EVM mintAndSendFrxUSD", "gray-600", "previewMintFrxUSD totalAssetsOut: %o", totalAssetsOut);
1330
+ result.outputAmount = numberRemoveEndZero(
1331
+ Big(totalAssetsOut.toString()).div(10 ** 18).toFixed(18, 0)
1332
+ );
1333
+ const mintAndSendParam = [
1334
+ // assetsIn
1335
+ amountWei,
1336
+ // dstEid
1337
+ dstEid,
1338
+ // receiver
1339
+ addressToBytes32(toToken.chainType, recipient)
1340
+ ];
1341
+ result.sendParam = {
1342
+ contract: redeemAndMintContractWithSigner,
1343
+ method: "mintAndSend",
1344
+ param: mintAndSendParam
1345
+ };
1346
+ execTime.breakpoint();
1347
+ const ett = await this.estimateTransaction({
1348
+ dry,
1349
+ contract: redeemAndMintContractWithSigner,
1350
+ method: "mintAndSend",
1351
+ param: [...mintAndSendParam, { gasLimit: DEFAULT_GAS_LIMIT_FAILED }],
1352
+ fromToken,
1353
+ prices,
1354
+ evmGasFees
1355
+ });
1356
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
1357
+ result.estimateSourceGas = ett.estimateSourceGas;
1358
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
1359
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
1360
+ result.totalFeesUsd = ett.estimateSourceGasUsd;
1361
+ result.sendParam.param.push({ gasLimit: ett.estimateSourceGasLimit });
1362
+ execTime.log("estimateTransaction");
1363
+ execTime.logTotal("mintAndSendFrxUSD");
1364
+ return result;
1365
+ }
1366
+ async retryLayerzeroLzComponse(params) {
1367
+ const { Options } = await import('@layerzerolabs/lz-v2-utilities');
1368
+ const {
1369
+ layerzeroData,
1370
+ history
1371
+ } = params;
1372
+ const LayerZeroEndpointV2 = "0x1a44076050125825900e736c501f859c50fe728c";
1373
+ const LayerZeroEndpointV2ABI = [
1374
+ {
1375
+ "inputs": [
1376
+ {
1377
+ "internalType": "address",
1378
+ "name": "_from",
1379
+ "type": "address"
1380
+ },
1381
+ {
1382
+ "internalType": "address",
1383
+ "name": "_to",
1384
+ "type": "address"
1385
+ },
1386
+ {
1387
+ "internalType": "bytes32",
1388
+ "name": "_guid",
1389
+ "type": "bytes32"
1390
+ },
1391
+ {
1392
+ "internalType": "uint16",
1393
+ "name": "_index",
1394
+ "type": "uint16"
1395
+ },
1396
+ {
1397
+ "internalType": "bytes",
1398
+ "name": "_message",
1399
+ "type": "bytes"
1400
+ },
1401
+ {
1402
+ "internalType": "bytes",
1403
+ "name": "_extraData",
1404
+ "type": "bytes"
1405
+ }
1406
+ ],
1407
+ "name": "lzCompose",
1408
+ "outputs": [],
1409
+ "stateMutability": "payable",
1410
+ "type": "function"
1411
+ }
1412
+ ];
1413
+ const LayerZeroEndpointV2Contract = new ethers.Contract(LayerZeroEndpointV2, LayerZeroEndpointV2ABI, this.signer);
1414
+ const toToken = usdtTokens[history.destination_chain.blockchain];
1415
+ const amountWei = Big(history.token_in_amount).times(10 ** (toToken.decimals || 6)).toFixed(0);
1416
+ const originLayerzero = USDT0_CONFIG["Arbitrum"];
1417
+ const isOriginLegacy = layerzeroData.destination.lzCompose.failedTx[0].from.toLowerCase() === originLayerzero.oftLegacy?.toLowerCase();
1418
+ isOriginLegacy ? originLayerzero.lzReceiveOptionGasLegacy : originLayerzero.lzReceiveOptionGas;
1419
+ const destATA = await getDestinationAssociatedTokenAddress({
1420
+ recipient: history.receive_address,
1421
+ toToken
1422
+ });
1423
+ if (destATA.needCreateTokenAccount) {
1424
+ LZ_RECEIVE_VALUE[toToken.chainName] || 0;
1425
+ }
1426
+ const composeFrom = layerzeroData.source.tx.from;
1427
+ const composeMsg = layerzeroData.source.tx.payload.split(composeFrom.replace(/^0x/, ""))[1];
1428
+ const _message = buildEndpointV2LzComposePayload({
1429
+ nonce: layerzeroData.pathway.nonce,
1430
+ srcEid: layerzeroData.pathway.srcEid,
1431
+ amountLD: amountWei,
1432
+ composeFrom,
1433
+ composeMsg
1434
+ });
1435
+ const contractParams = [
1436
+ layerzeroData.destination.lzCompose.failedTx[0].from,
1437
+ layerzeroData.destination.lzCompose.failedTx[0].to,
1438
+ layerzeroData.guid,
1439
+ layerzeroData.destination.lzCompose.failedTx[0].index,
1440
+ _message,
1441
+ "0x"
1442
+ ];
1443
+ const dstOFT = isOriginLegacy ? originLayerzero.oft : originLayerzero.oftLegacy;
1444
+ const sendParam = {
1445
+ dstEid: USDT0_CONFIG[history.destination_chain.chainName].eid,
1446
+ to: addressToBytes32(toToken.chainType, history.receive_address),
1447
+ amountLD: amountWei,
1448
+ minAmountLD: 0n,
1449
+ extraOptions: Options.newOptions().toHex(),
1450
+ composeMsg: "0x",
1451
+ oftCmd: "0x"
1452
+ };
1453
+ const dstOFTContract = new ethers.Contract(dstOFT, OFT_ABI, this.provider);
1454
+ const msgFee = await dstOFTContract.quoteSend.staticCall(sendParam, false);
1455
+ let nativeFee = msgFee[0];
1456
+ nativeFee = nativeFee * NATIVE_MSG_FEE_BUFFER / 100n;
1457
+ const tx = await LayerZeroEndpointV2Contract.lzCompose(
1458
+ ...contractParams,
1459
+ {
1460
+ gasLimit: originLayerzero.composeOptionGas || 8e5,
1461
+ value: nativeFee
1462
+ }
1463
+ );
1464
+ const txReceipt = await tx.wait();
1465
+ if (txReceipt.status === 1) {
1466
+ return txReceipt.hash;
1467
+ }
1468
+ return null;
1469
+ }
1470
+ async signTypedData(params) {
1471
+ const { fromToken, amountWei, spender } = params;
1472
+ this.csl("EVM signTypedData", "blue-900", "params: %o", params);
1473
+ const provider = evmRpcFallbackProvider(fromToken);
1474
+ const value = amountWei;
1475
+ const tokenAddress = fromToken.contractAddress;
1476
+ const chainId = fromToken.chainId;
1477
+ const deadline = Math.floor(Date.now() / 1e3) + 60 * 60 * 24 * 3;
1478
+ const account = this.signer.address;
1479
+ const erc20 = new ethers.Contract(tokenAddress, erc20Abi, provider);
1480
+ const nonce = await erc20.nonces(account);
1481
+ const name = await erc20.name();
1482
+ let _version = "1";
1483
+ if (fromToken.symbol === "USDC") {
1484
+ _version = "2";
1485
+ }
1486
+ const domain = {
1487
+ name,
1488
+ version: _version,
1489
+ chainId: Number(chainId),
1490
+ verifyingContract: tokenAddress
1491
+ };
1492
+ const types = {
1493
+ Permit: [
1494
+ { name: "owner", type: "address" },
1495
+ { name: "spender", type: "address" },
1496
+ { name: "value", type: "uint256" },
1497
+ { name: "nonce", type: "uint256" },
1498
+ { name: "deadline", type: "uint256" }
1499
+ ]
1500
+ };
1501
+ const values = {
1502
+ owner: account,
1503
+ spender,
1504
+ value,
1505
+ nonce: nonce.toString(),
1506
+ deadline
1507
+ };
1508
+ const signature = await this.signer?.signTypedData(domain, types, values);
1509
+ const { v, r, s } = ethers.Signature.from(signature);
1510
+ try {
1511
+ const permitParams = [
1512
+ account,
1513
+ spender,
1514
+ value,
1515
+ deadline,
1516
+ v,
1517
+ r,
1518
+ s
1519
+ ];
1520
+ const permitResponse = await erc20.permit.staticCall(...permitParams);
1521
+ this.csl("EVM signTypedData", "green-500", "permit response: %o", permitResponse);
1522
+ } catch (error) {
1523
+ this.csl("EVM signTypedData", "red-500", "check permit signature failed: %o", error);
1524
+ throw new Error("Permit signature verification failed");
1525
+ }
1526
+ return {
1527
+ owner: account,
1528
+ value,
1529
+ deadline,
1530
+ nonce: Number(nonce),
1531
+ v,
1532
+ r,
1533
+ s
1534
+ };
1535
+ }
1536
+ };
1537
+
1538
+ export { EVMWallet, EVMWallet as default };
1539
+ //# sourceMappingURL=index.mjs.map
1540
+ //# sourceMappingURL=index.mjs.map