@stableflow/wallet-evm 0.0.0-experimental-5a8afd7389-20260703

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,1548 @@
1
+ import { Csl, OpenAPI, erc20Abi, numberRemoveEndZero, getPrice, ExecTime, Service, SendType, usdtTokens } from '@stableflow/core';
2
+ import { LZ_RECEIVE_VALUE, addressToBytes32, getHopMsgFee, USDT0_LEGACY_MESH_TRANSFTER_FEE, quoteSignature, 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 (isOriginLegacy || isDestinationLegacy) {
387
+ result.fees.legacyMeshFeeUsd = numberRemoveEndZero(Big(amountWei || 0).div(10 ** params.fromToken.decimals).times(USDT0_LEGACY_MESH_TRANSFTER_FEE).toFixed(params.fromToken.decimals));
388
+ result.outputAmount = numberRemoveEndZero(Big(Big(amountWei || 0).div(10 ** params.fromToken.decimals)).minus(result.fees.legacyMeshFeeUsd || 0).toFixed(params.fromToken.decimals, 0));
389
+ }
390
+ if (!dry) {
391
+ execTime.breakpoint();
392
+ const oftData = await oftContractRead.quoteOFT.staticCall(sendParam);
393
+ const [, , oftReceipt] = oftData;
394
+ sendParam.minAmountLD = Big(amountWei).times(Big(1).minus(Big(slippageTolerance || 0).div(100))).toFixed(0);
395
+ result.outputAmount = numberRemoveEndZero(numberRemoveEndZero(Big(oftReceipt[1].toString()).div(10 ** params.toToken.decimals).toFixed(params.toToken.decimals, 0)));
396
+ execTime.log("quoteOFT.staticCall");
397
+ }
398
+ this.csl("EVMWallet quoteOFT", "red-600", "result.outputAmount: %o", result.outputAmount);
399
+ this.csl("EVMWallet quoteOFT", "red-600", "slippageTolerance: %o", slippageTolerance + "%");
400
+ this.csl("EVMWallet quoteOFT", "red-600", "Minimum received amount: %o", Big(amountWei).div(10 ** fromToken.decimals).times(Big(1).minus(Big(slippageTolerance || 0).div(100))).toFixed(6, 0));
401
+ if (Big(result.outputAmount).lt(Big(amountWei).div(10 ** fromToken.decimals).times(Big(1).minus(Big(slippageTolerance || 0).div(100))))) {
402
+ result.errMsg = "Slippage limit exceeded";
403
+ return result;
404
+ }
405
+ execTime.breakpoint();
406
+ const mergedCall = [
407
+ oftContractRead.quoteSend.staticCall(sendParam, payInLzToken)
408
+ ];
409
+ if (approvalRequired) {
410
+ mergedCall.push(
411
+ this.allowance({
412
+ dry,
413
+ contractAddress: fromToken.contractAddress,
414
+ spender: originLayerzeroAddress,
415
+ address: refundTo,
416
+ amountWei,
417
+ provider
418
+ })
419
+ );
420
+ }
421
+ const [msgFee, allowanceResult] = await Promise.all(mergedCall);
422
+ if (approvalRequired) {
423
+ result.needApprove = allowanceResult?.needApprove;
424
+ }
425
+ execTime.log("quoteSend.staticCall and allowance");
426
+ let nativeMsgFee = msgFee[0];
427
+ const lzMsgFee = msgFee[1];
428
+ result.totalEstimateSourceGas = nativeMsgFee;
429
+ nativeMsgFee = nativeMsgFee * NATIVE_MSG_FEE_BUFFER / 100n;
430
+ const nativeFeeUsd = Big(nativeMsgFee?.toString() || 0).div(10 ** fromToken.nativeToken.decimals).times(getPrice(prices, fromToken.nativeToken.symbol));
431
+ result.fees.nativeFee = numberRemoveEndZero(Big(nativeMsgFee?.toString() || 0).div(10 ** fromToken.nativeToken.decimals).toFixed(fromToken.nativeToken.decimals));
432
+ result.fees.nativeFeeUsd = numberRemoveEndZero(Big(nativeFeeUsd).toFixed(20));
433
+ result.fees.lzTokenFeeUsd = numberRemoveEndZero(Big(lzMsgFee?.toString() || 0).div(10 ** fromToken.decimals).toFixed(20));
434
+ const finalSendParam = [
435
+ sendParam,
436
+ {
437
+ nativeFee: nativeMsgFee,
438
+ lzTokenFee: lzMsgFee
439
+ },
440
+ refundTo,
441
+ { value: nativeMsgFee }
442
+ ];
443
+ execTime.breakpoint();
444
+ const ett = await this.estimateTransaction({
445
+ dry,
446
+ contract: oftContract,
447
+ method: "send",
448
+ param: finalSendParam,
449
+ fromToken,
450
+ prices,
451
+ evmGasFees
452
+ });
453
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
454
+ result.estimateSourceGas = ett.estimateSourceGas;
455
+ result.totalEstimateSourceGas += ett.estimateSourceGas;
456
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
457
+ finalSendParam[3].gasLimit = ett.estimateSourceGasLimit;
458
+ execTime.log("send.estimateGas");
459
+ result.sendParam = {
460
+ contract: oftContract,
461
+ method: "send",
462
+ param: finalSendParam
463
+ };
464
+ for (const feeKey in result.fees) {
465
+ if (excludeFees.includes(feeKey) || !/Usd$/.test(feeKey)) {
466
+ continue;
467
+ }
468
+ result.totalFeesUsd = Big(result.totalFeesUsd || 0).plus(result.fees[feeKey] || 0);
469
+ }
470
+ result.totalFeesUsd = numberRemoveEndZero(Big(result.totalFeesUsd).toFixed(20));
471
+ execTime.logTotal("quoteOFT");
472
+ return result;
473
+ }
474
+ async sendTransaction(params) {
475
+ const {
476
+ method,
477
+ contract,
478
+ param
479
+ } = params;
480
+ const overridesIndex = param.length - 1;
481
+ const overrides = param[overridesIndex] && typeof param[overridesIndex] === "object" && !Array.isArray(param[overridesIndex]) ? { ...param[overridesIndex] } : {};
482
+ if (!overrides.maxFeePerGas) {
483
+ try {
484
+ const feeData = await this.provider.getFeeData();
485
+ if (feeData.maxFeePerGas) {
486
+ overrides.maxFeePerGas = feeData.maxFeePerGas * 120n / 100n;
487
+ }
488
+ if (feeData.maxPriorityFeePerGas && !overrides.maxPriorityFeePerGas) {
489
+ overrides.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
490
+ }
491
+ } catch (error) {
492
+ this.csl("EVM sendTransaction", "red-500", "Failed to get fee data for gas buffer: %o", error);
493
+ }
494
+ }
495
+ const finalParam = [...param];
496
+ if (param[overridesIndex] && typeof param[overridesIndex] === "object" && !Array.isArray(param[overridesIndex])) {
497
+ finalParam[overridesIndex] = overrides;
498
+ } else {
499
+ finalParam.push(overrides);
500
+ }
501
+ try {
502
+ const tx = await contract[method](...finalParam);
503
+ return tx.hash;
504
+ } catch (error) {
505
+ this.csl("EVM sendTransaction", "red-500", "Error sending transaction: %o, message: %o", error, error.message);
506
+ let _finalErrorMessage = `Transaction failed: ${error.message}`;
507
+ if (error?.message?.includes("user rejected action")) {
508
+ _finalErrorMessage = error.message;
509
+ }
510
+ throw new Error(_finalErrorMessage);
511
+ }
512
+ }
513
+ /**
514
+ * Unified quote method that routes to specific quote methods based on type
515
+ * @param type Service type from Service
516
+ * @param params Parameters for the quote
517
+ */
518
+ async quote(type, params) {
519
+ switch (type) {
520
+ case Service.CCTP:
521
+ return await this.quoteCCTP(params);
522
+ case Service.Usdt0:
523
+ return await this.quoteOFT(params);
524
+ case Service.OneClick:
525
+ return await this.quoteOneClickProxy(params);
526
+ case Service.Native:
527
+ return await this.quoteNative(params);
528
+ case Service.FraxZero:
529
+ return await this.quoteFraxZero(params);
530
+ default:
531
+ throw new Error(`Unsupported quote type: ${type}`);
532
+ }
533
+ }
534
+ /**
535
+ * Unified send method that routes to specific send methods based on type
536
+ * @param type Send type from SendType enum
537
+ * @param params Parameters for the send transaction
538
+ */
539
+ async send(type, params) {
540
+ switch (type) {
541
+ case SendType.SEND:
542
+ return await this.sendTransaction(params);
543
+ case SendType.TRANSFER:
544
+ return await this.transfer(params);
545
+ default:
546
+ throw new Error(`Unsupported send type: ${type}`);
547
+ }
548
+ }
549
+ async quoteCCTP(params) {
550
+ const {
551
+ dry,
552
+ proxyAddress,
553
+ abi,
554
+ refundTo,
555
+ recipient,
556
+ amountWei,
557
+ // slippageTolerance,
558
+ fromToken,
559
+ toToken,
560
+ prices,
561
+ evmGasFees,
562
+ excludeFees,
563
+ destinationDomain,
564
+ sourceDomain
565
+ } = params;
566
+ const result = {
567
+ needApprove: false,
568
+ approveSpender: proxyAddress,
569
+ sendParam: void 0,
570
+ quoteParam: {
571
+ sourceDomain,
572
+ destinationDomain,
573
+ proxyAddress,
574
+ ...params
575
+ },
576
+ fees: {},
577
+ totalFeesUsd: void 0,
578
+ estimateSourceGas: void 0,
579
+ totalEstimateSourceGas: 0n,
580
+ estimateSourceGasUsd: void 0,
581
+ estimateTime: Math.floor(Math.random() * 8) + 3,
582
+ outputAmount: numberRemoveEndZero(Big(amountWei || 0).div(10 ** fromToken.decimals).toFixed(fromToken.decimals, 0))
583
+ };
584
+ const provider = evmRpcFallbackProvider(fromToken);
585
+ const execTime = new ExecTime({ type: `CCTP EVM ${fromToken.chainName}->${toToken.chainName}`, logStyle: "stone-200", isDebug: OpenAPI.DEBUG });
586
+ const proxyContract = new ethers.Contract(proxyAddress, abi, this.signer);
587
+ new ethers.Contract(proxyAddress, abi, provider);
588
+ let realRecipient = recipient;
589
+ execTime.breakpoint();
590
+ const getUsrNonce = async () => {
591
+ let userNonce2 = 0n;
592
+ try {
593
+ userNonce2 = await proxyContract.userNonces(refundTo);
594
+ } catch (error) {
595
+ }
596
+ return userNonce2;
597
+ };
598
+ const mergedCalls = [
599
+ getDestinationAssociatedTokenAddress({
600
+ recipient,
601
+ toToken
602
+ }),
603
+ getUsrNonce(),
604
+ this.allowance({
605
+ dry,
606
+ contractAddress: fromToken.contractAddress,
607
+ address: refundTo,
608
+ spender: proxyAddress,
609
+ amountWei,
610
+ provider
611
+ })
612
+ ];
613
+ const [destATA, userNonce, allowance] = await Promise.all(mergedCalls);
614
+ execTime.log("getDestinationATA & getUsrNonce & get allowance", "destATA: %o, userNonce: %o, allowance: %o", destATA, userNonce, allowance);
615
+ result.needCreateTokenAccount = destATA.needCreateTokenAccount;
616
+ if (destATA.associatedTokenAddress) {
617
+ realRecipient = destATA.associatedTokenAddress;
618
+ }
619
+ result.needApprove = allowance.needApprove;
620
+ execTime.breakpoint();
621
+ const signatureRes = await quoteSignature({
622
+ address: refundTo,
623
+ amount: numberRemoveEndZero(Big(amountWei || 0).div(10 ** fromToken.decimals).toFixed(fromToken.decimals, 0)),
624
+ destination_domain_id: destinationDomain,
625
+ receipt_address: realRecipient,
626
+ source_domain_id: sourceDomain,
627
+ user_nonce: Number(userNonce)
628
+ });
629
+ const {
630
+ bridge_fee,
631
+ finality_threshold,
632
+ max_fee,
633
+ mint_fee,
634
+ receipt_amount,
635
+ signature,
636
+ destination_caller
637
+ } = signatureRes;
638
+ execTime.log("quoteSignature");
639
+ result.fees.estimateMintGasUsd = numberRemoveEndZero(Big(mint_fee || 0).div(10 ** fromToken.decimals).toFixed(fromToken.decimals));
640
+ result.fees.bridgeFeeUsd = numberRemoveEndZero(Big(bridge_fee || 0).div(10 ** fromToken.decimals).toFixed(fromToken.decimals));
641
+ const chargedAmount = BigInt(amountWei) - BigInt(mint_fee);
642
+ result.outputAmount = numberRemoveEndZero(Big(receipt_amount || 0).div(10 ** fromToken.decimals).toFixed(fromToken.decimals, 0));
643
+ const depositParam = [
644
+ // originalAmount
645
+ amountWei,
646
+ // chargedAmount = originalAmount - gas fee
647
+ chargedAmount,
648
+ // destinationDomain
649
+ destinationDomain,
650
+ // mintRecipient
651
+ addressToBytes32(toToken.chainType, realRecipient),
652
+ // burnToken
653
+ fromToken.contractAddress,
654
+ // destinationCaller
655
+ destination_caller ? addressToBytes32(toToken.chainType, destination_caller) : "0x0000000000000000000000000000000000000000000000000000000000000000",
656
+ // maxFee
657
+ max_fee,
658
+ // minFinalityThreshold
659
+ finality_threshold,
660
+ // signature
661
+ signature
662
+ ];
663
+ execTime.breakpoint();
664
+ const ett = await this.estimateTransaction({
665
+ dry,
666
+ contract: proxyContract,
667
+ method: "depositWithFee",
668
+ param: depositParam,
669
+ fromToken,
670
+ prices,
671
+ evmGasFees
672
+ });
673
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
674
+ result.estimateSourceGas = ett.estimateSourceGas;
675
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
676
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
677
+ depositParam.push({ gasLimit: ett.estimateSourceGasLimit });
678
+ execTime.log("estimateTransaction");
679
+ result.sendParam = {
680
+ method: "depositWithFee",
681
+ contract: proxyContract,
682
+ param: depositParam
683
+ };
684
+ for (const feeKey in result.fees) {
685
+ if (excludeFees.includes(feeKey) || !/Usd$/.test(feeKey)) {
686
+ continue;
687
+ }
688
+ result.totalFeesUsd = Big(result.totalFeesUsd || 0).plus(result.fees[feeKey] || 0);
689
+ }
690
+ result.totalFeesUsd = numberRemoveEndZero(Big(result.totalFeesUsd).toFixed(20));
691
+ execTime.logTotal("quoteCCTP");
692
+ return result;
693
+ }
694
+ async quoteOneClickProxy(params) {
695
+ const {
696
+ dry,
697
+ proxyAddress,
698
+ abi,
699
+ fromToken,
700
+ refundTo,
701
+ depositAddress,
702
+ amountWei,
703
+ prices,
704
+ evmGasFees
705
+ } = params;
706
+ const execTime = new ExecTime({ type: `OneClick EVM ${fromToken.chainName}`, logStyle: "stone-300", isDebug: OpenAPI.DEBUG });
707
+ const result = { fees: {} };
708
+ const provider = evmRpcFallbackProvider(fromToken);
709
+ const proxyContract = new ethers.Contract(proxyAddress, abi, this.signer);
710
+ const proxyParam = [
711
+ // tokenAddress
712
+ fromToken.contractAddress,
713
+ // recipient
714
+ depositAddress,
715
+ // amount
716
+ amountWei
717
+ ];
718
+ execTime.breakpoint();
719
+ const mergedCalls = [
720
+ this.allowance({
721
+ dry,
722
+ contractAddress: fromToken.contractAddress,
723
+ address: refundTo,
724
+ spender: proxyAddress,
725
+ amountWei,
726
+ provider
727
+ }),
728
+ this.estimateTransaction({
729
+ dry,
730
+ contract: proxyContract,
731
+ method: "proxyTransfer",
732
+ param: proxyParam,
733
+ fromToken,
734
+ prices,
735
+ evmGasFees
736
+ })
737
+ ];
738
+ const [allowance, ett] = await Promise.all(mergedCalls);
739
+ result.needApprove = allowance.needApprove;
740
+ result.approveSpender = proxyAddress;
741
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
742
+ result.estimateSourceGas = ett.estimateSourceGas;
743
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
744
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
745
+ proxyParam.push({ gasLimit: ett.estimateSourceGasLimit });
746
+ execTime.log("allowance & estimateTransaction");
747
+ result.sendParam = {
748
+ method: "proxyTransfer",
749
+ contract: proxyContract,
750
+ param: proxyParam
751
+ };
752
+ execTime.logTotal("quoteOneClickPorxy");
753
+ return result;
754
+ }
755
+ async quoteNative(params) {
756
+ const {
757
+ quoteResponse,
758
+ bridgeRouterAddress,
759
+ ...restParams
760
+ } = params;
761
+ const {
762
+ dry,
763
+ amountWei,
764
+ refundTo,
765
+ fromToken,
766
+ toToken,
767
+ prices,
768
+ evmGasFees
769
+ } = restParams;
770
+ const result = {
771
+ ...quoteResponse,
772
+ fees: {},
773
+ needApprove: false,
774
+ approveSpender: bridgeRouterAddress,
775
+ quoteParam: {
776
+ ...restParams
777
+ },
778
+ sendParam: {
779
+ txRequest: quoteResponse.txRequest
780
+ },
781
+ totalFeesUsd: void 0,
782
+ estimateSourceGas: void 0,
783
+ totalEstimateSourceGas: 0n,
784
+ estimateSourceGasUsd: void 0,
785
+ estimateTime: quoteResponse.priority === "fast" ? Math.floor(Math.random() * 10) + 50 : Math.floor(Math.random() * 60) + 300,
786
+ outputAmount: 0
787
+ };
788
+ const execTime = new ExecTime({ type: `Native EVM ${fromToken.chainName}->${toToken.chainName}`, logStyle: "stone-400", isDebug: OpenAPI.DEBUG });
789
+ evmRpcFallbackProvider(fromToken);
790
+ const estimateNativeGas = async () => {
791
+ if (dry) {
792
+ const ett = await this.estimateTransaction({
793
+ dry,
794
+ fromToken,
795
+ prices,
796
+ evmGasFees
797
+ });
798
+ result.outputAmount = quoteResponse.buyerTokenAmount + "";
799
+ result.fees = {
800
+ estimateGasUsd: ett.estimateSourceGasUsd,
801
+ widgetFeeUsd: quoteResponse.widgetFeeUsd,
802
+ liquidityProviderFeeUsd: quoteResponse.liquidityProviderFeeUsd
803
+ };
804
+ result.estimateSourceGas = ett.estimateSourceGas;
805
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
806
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
807
+ result.totalFeesUsd = numberRemoveEndZero(Big(quoteResponse.totalFeeUsd).toFixed(20));
808
+ } else {
809
+ const ett = await this.estimateTransaction({
810
+ dry,
811
+ fromToken,
812
+ prices,
813
+ evmGasFees,
814
+ txRequest: quoteResponse.txRequest,
815
+ refundTo
816
+ });
817
+ result.outputAmount = Big(quoteResponse.amountOut || 0).div(10 ** (toToken.decimals || 6)).toFixed(toToken.decimals || 6, 0);
818
+ result.fees = {
819
+ estimateGasUsd: ett.estimateSourceGasUsd,
820
+ // maybe 100 = 1%
821
+ widgetFeeUsd: numberRemoveEndZero(Big(amountWei || 0).div(10 ** (fromToken.decimals || 6)).times(quoteResponse.widgetFee.feeRate || 0).toFixed(20, 0)),
822
+ // not return by api
823
+ liquidityProviderFeeUsd: "0"
824
+ };
825
+ result.estimateSourceGas = ett.estimateSourceGas;
826
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
827
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
828
+ result.totalFeesUsd = numberRemoveEndZero(Big(quoteResponse.amountOutBeforeFee || 0).minus(quoteResponse.amountOut).div(10 ** (fromToken.decimals || 6)).toFixed(20, 0));
829
+ result.txRequest.gasLimit = ett.estimateSourceGasLimit;
830
+ }
831
+ };
832
+ execTime.breakpoint();
833
+ const mergedCalls = [
834
+ this.allowance({
835
+ dry,
836
+ contractAddress: fromToken.contractAddress,
837
+ spender: bridgeRouterAddress,
838
+ address: refundTo,
839
+ amountWei
840
+ }),
841
+ estimateNativeGas()
842
+ ];
843
+ const [allowanceResult] = await Promise.all(mergedCalls);
844
+ result.needApprove = allowanceResult.needApprove;
845
+ execTime.log("allowance & estimateNativeGas");
846
+ execTime.logTotal("quoteNative");
847
+ return result;
848
+ }
849
+ async quoteFraxZero(params) {
850
+ const {
851
+ dry,
852
+ abi,
853
+ recipient,
854
+ amountWei,
855
+ slippageTolerance,
856
+ fromToken,
857
+ toToken,
858
+ prices,
859
+ evmGasFees,
860
+ excludeFees,
861
+ refundTo,
862
+ originLayerzero,
863
+ destinationLayerzero
864
+ } = params;
865
+ const {
866
+ eid: srcEid,
867
+ remoteHop,
868
+ lockbox
869
+ } = originLayerzero;
870
+ const {
871
+ eid: dstEid
872
+ } = destinationLayerzero;
873
+ const isFromEthereum = fromToken.chainId === 1;
874
+ const result = {
875
+ needApprove: false,
876
+ approveSpender: remoteHop,
877
+ sendParam: void 0,
878
+ quoteParam: {
879
+ ...params
880
+ },
881
+ fees: {},
882
+ totalFeesUsd: 0,
883
+ estimateSourceGas: 0n,
884
+ totalEstimateSourceGas: 0n,
885
+ estimateSourceGasUsd: 0,
886
+ estimateTime: 0,
887
+ outputAmount: numberRemoveEndZero(Big(amountWei || 0).div(10 ** params.fromToken.decimals).toFixed(params.fromToken.decimals, 0))
888
+ };
889
+ const provider = evmRpcFallbackProvider(fromToken);
890
+ const execTime = new ExecTime({ type: `FraxZero EVM ${fromToken.chainName}->${toToken.chainName}`, logStyle: "stone-500", isDebug: OpenAPI.DEBUG });
891
+ const remoteHopContract = new ethers.Contract(remoteHop, abi, this.signer);
892
+ const remoteHopContractRead = new ethers.Contract(remoteHop, abi, provider);
893
+ const sendParams = [
894
+ // _oft
895
+ isFromEthereum ? lockbox : fromToken.contractAddress,
896
+ // _dstEid
897
+ dstEid,
898
+ // _to
899
+ addressToBytes32(toToken.chainType, recipient),
900
+ // _amountLD
901
+ amountWei
902
+ ];
903
+ execTime.breakpoint();
904
+ const mergedCalls = [
905
+ this.allowance({
906
+ dry,
907
+ contractAddress: fromToken.contractAddress,
908
+ spender: remoteHop,
909
+ address: refundTo,
910
+ amountWei,
911
+ provider
912
+ }),
913
+ remoteHopContractRead.quote.staticCall(...sendParams)
914
+ ];
915
+ const [allowanceResult, msgFee] = await Promise.all(mergedCalls);
916
+ execTime.log("allowance & quote.staticCall");
917
+ result.needApprove = allowanceResult.needApprove;
918
+ let nativeMsgFee = msgFee[0];
919
+ this.csl("EVM quoteFraxZero", "blue-700", "nativeMsgFee: %o", nativeMsgFee);
920
+ nativeMsgFee = nativeMsgFee * NATIVE_MSG_FEE_BUFFER / 100n;
921
+ this.csl("EVM quoteFraxZero", "blue-700", "nativeMsgFee after buffer: %o", nativeMsgFee);
922
+ result.totalEstimateSourceGas = nativeMsgFee;
923
+ const nativeFeeUsd = Big(nativeMsgFee?.toString() || 0).div(10 ** fromToken.nativeToken.decimals).times(getPrice(prices, fromToken.nativeToken.symbol));
924
+ result.fees.nativeFee = numberRemoveEndZero(Big(nativeMsgFee?.toString() || 0).div(10 ** fromToken.nativeToken.decimals).toFixed(fromToken.nativeToken.decimals));
925
+ result.fees.nativeFeeUsd = numberRemoveEndZero(Big(nativeFeeUsd).toFixed(20));
926
+ result.fees.lzTokenFeeUsd = numberRemoveEndZero(Big(msgFee[1]?.toString() || 0).div(10 ** fromToken.decimals).toFixed(20));
927
+ this.csl("EVM quoteFraxZero", "blue-700", "msgFee: %o", msgFee);
928
+ execTime.breakpoint();
929
+ const finalSendParam = [...sendParams, { value: nativeMsgFee }];
930
+ const ett = await this.estimateTransaction({
931
+ dry,
932
+ contract: remoteHopContract,
933
+ method: "sendOFT",
934
+ param: finalSendParam,
935
+ fromToken,
936
+ prices,
937
+ evmGasFees
938
+ });
939
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
940
+ result.estimateSourceGas = ett.estimateSourceGas;
941
+ result.totalEstimateSourceGas += ett.estimateSourceGas;
942
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
943
+ finalSendParam[finalSendParam.length - 1].gasLimit = ett.estimateSourceGasLimit;
944
+ execTime.log("sendOFT.estimateTransaction");
945
+ result.sendParam = {
946
+ contract: remoteHopContract,
947
+ method: "sendOFT",
948
+ param: finalSendParam
949
+ };
950
+ for (const feeKey in result.fees) {
951
+ if (excludeFees.includes(feeKey) || !/Usd$/.test(feeKey)) {
952
+ continue;
953
+ }
954
+ result.totalFeesUsd = Big(result.totalFeesUsd || 0).plus(result.fees[feeKey] || 0);
955
+ }
956
+ result.totalFeesUsd = numberRemoveEndZero(Big(result.totalFeesUsd).toFixed(20));
957
+ execTime.logTotal("quoteFraxZero");
958
+ return result;
959
+ }
960
+ async preivewRedeemFrxUSD(params) {
961
+ const {
962
+ dry,
963
+ amountWei,
964
+ fromToken,
965
+ abi,
966
+ usdcCustodianAddress,
967
+ rwaCustodianAddress,
968
+ redemptionAddress
969
+ } = params;
970
+ if (dry) {
971
+ return {
972
+ maxUsdc: 0n,
973
+ maxRwa: 0n,
974
+ amountWeiBigInt: 0n,
975
+ // The token obtained from redeem is Ethereum USDC
976
+ totalAssetsOut: BigInt(Big(amountWei || 0).div(10 ** fromToken.decimals).times(10 ** FRAXZERO_MIDDLE_TOKEN_USDC.decimals).toFixed(0, 0)),
977
+ isInsufficientLiquidity: false
978
+ };
979
+ }
980
+ const execTime = new ExecTime({ type: `FraxZero EVM preivewRedeemFrxUSD ${fromToken.chainName}`, logStyle: "stone-600", isDebug: OpenAPI.DEBUG });
981
+ const provider = evmRpcFallbackProvider(fromToken);
982
+ const usdcCustodian = new ethers.Contract(usdcCustodianAddress, abi, provider);
983
+ const rwaCustodian = new ethers.Contract(rwaCustodianAddress, abi, provider);
984
+ const redemption = new ethers.Contract(redemptionAddress, abi, provider);
985
+ execTime.breakpoint();
986
+ const [usdcView, redemptionView] = await Promise.all([
987
+ usdcCustodian.mdwrComboView.staticCall(),
988
+ redemption.maxUstbRedemptionAmount.staticCall()
989
+ ]);
990
+ execTime.log("usdcCustodian & redemption");
991
+ const maxUsdc = usdcView[3];
992
+ const superstateTokenAmount = redemptionView[0];
993
+ const [maxRwa] = await redemption.calculateUsdcOut.staticCall(superstateTokenAmount);
994
+ const amountWeiBigInt = BigInt(amountWei || 0);
995
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "usdcCustodian maxSharesRedeemable(frxUSD wei amount): %o", maxUsdc);
996
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "rwaCustodian maxSharesRedeemable(USDC wei amount): %o", maxRwa);
997
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "amountWei: %o", amountWeiBigInt);
998
+ let totalAssetsOut = 0n;
999
+ let isInsufficientLiquidity = false;
1000
+ if (amountWeiBigInt <= maxUsdc) {
1001
+ execTime.breakpoint();
1002
+ const assetsOut = await usdcCustodian.previewRedeem.staticCall(amountWeiBigInt);
1003
+ execTime.log("usdcCustodian.previewRedeem (USDC only)");
1004
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "USDC path only, usdcCustodian previewRedeem input: %o, value: %o", amountWeiBigInt, assetsOut);
1005
+ totalAssetsOut = assetsOut;
1006
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "USDC path only, totalAssetsOut: %o", totalAssetsOut);
1007
+ } else {
1008
+ if (maxUsdc > 0n) {
1009
+ execTime.breakpoint();
1010
+ const usdcAssetsOut = await usdcCustodian.previewRedeem.staticCall(maxUsdc);
1011
+ execTime.log("usdcCustodian.previewRedeem (mixed)");
1012
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "USDC first (maxUsdc), usdcCustodian previewRedeem input: %o, value: %o", maxUsdc, usdcAssetsOut);
1013
+ totalAssetsOut += usdcAssetsOut;
1014
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "USDC first (maxUsdc), totalAssetsOut: %o", totalAssetsOut);
1015
+ }
1016
+ const rwaAmount = amountWeiBigInt - maxUsdc;
1017
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "RWA for remainder, rwaAmount: %o", rwaAmount);
1018
+ if (rwaAmount > 0n) {
1019
+ execTime.breakpoint();
1020
+ const superstateTokenInAmount = await rwaCustodian.previewRedeem.staticCall(rwaAmount);
1021
+ execTime.log("rwaCustodian.previewRedeem (mixed)");
1022
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "USDC first (maxUsdc), rwaCustodian previewRedeem input: %o, superstateTokenInAmount: %o", rwaAmount, superstateTokenInAmount);
1023
+ execTime.breakpoint();
1024
+ const [rwaAssetsOut] = await redemption.calculateUsdcOut.staticCall(superstateTokenInAmount);
1025
+ execTime.log("redemption.calculateUsdcOut (mixed)");
1026
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "USDC first (maxUsdc), rwaCustodian previewRedeem input: %o, value: %o", rwaAmount, rwaAssetsOut);
1027
+ totalAssetsOut += rwaAssetsOut;
1028
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "RWA for remainder, totalAssetsOut: %o", totalAssetsOut);
1029
+ if (rwaAssetsOut > maxRwa) {
1030
+ isInsufficientLiquidity = true;
1031
+ }
1032
+ }
1033
+ }
1034
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "final totalAssetsOut: %o", totalAssetsOut);
1035
+ this.csl("EVM preivewRedeemFrxUSD", "blue-700", "final insufficient liquidity: %o", isInsufficientLiquidity);
1036
+ execTime.logTotal("preivewRedeemFrxUSD");
1037
+ return {
1038
+ maxUsdc,
1039
+ maxRwa,
1040
+ amountWeiBigInt,
1041
+ totalAssetsOut,
1042
+ isInsufficientLiquidity
1043
+ };
1044
+ }
1045
+ async previewMintFrxUSD(params) {
1046
+ const {
1047
+ dry,
1048
+ amountWei,
1049
+ fromToken,
1050
+ abi,
1051
+ usdcCustodianAddress
1052
+ } = params;
1053
+ this.csl("EVM previewMintFrxUSD", "blue-700", "params: %o", params);
1054
+ if (dry) {
1055
+ return {
1056
+ maxUsdc: 0n,
1057
+ amountWeiBigInt: 0n,
1058
+ totalMax: 0n,
1059
+ // Minting means converting Ethereum USDC into Ethereum frxUSD
1060
+ totalAssetsOut: BigInt(Big(amountWei || 0).div(10 ** fromToken.decimals).times(10 ** FRAXZERO_MIDDLE_TOKEN_FRXUSD.decimals).toFixed(0, 0))
1061
+ };
1062
+ }
1063
+ const execTime = new ExecTime({ type: `FraxZero EVM previewMintFrxUSD ${fromToken.chainName}`, logStyle: "stone-700", isDebug: OpenAPI.DEBUG });
1064
+ const provider = evmRpcFallbackProvider(fromToken);
1065
+ const usdcCustodian = new ethers.Contract(usdcCustodianAddress, abi, provider);
1066
+ execTime.breakpoint();
1067
+ const usdcView = await usdcCustodian.mdwrComboView.staticCall();
1068
+ execTime.log("usdcCustodian.mdwrComboView");
1069
+ const maxUsdc = usdcView[0];
1070
+ const amountWeiBigInt = BigInt(amountWei || 0);
1071
+ const totalMax = maxUsdc;
1072
+ execTime.breakpoint();
1073
+ const totalAssetsOut = await usdcCustodian.previewDeposit.staticCall(amountWeiBigInt);
1074
+ execTime.log("usdcCustodian.previewDeposit");
1075
+ execTime.logTotal("previewMintFrxUSD");
1076
+ return {
1077
+ maxUsdc,
1078
+ amountWeiBigInt,
1079
+ totalMax,
1080
+ totalAssetsOut
1081
+ };
1082
+ }
1083
+ /**
1084
+ * Redeem frxUSD to USDC via USDC and/or RWA custodian contracts.
1085
+ * Uses mdwrComboView to get max redeemable liquidity and splits redemption across USDC/RWA paths.
1086
+ * Uses previewRedeem to get accurate output amount (USDC).
1087
+ */
1088
+ async redeemFrxUSD(params) {
1089
+ const {
1090
+ dry,
1091
+ recipient,
1092
+ amountWei,
1093
+ fromToken,
1094
+ toToken,
1095
+ prices,
1096
+ evmGasFees,
1097
+ refundTo,
1098
+ abi,
1099
+ usdcCustodianAddress,
1100
+ rwaCustodianAddress,
1101
+ redeemAndMintContractAddress,
1102
+ preivewRedeemResult
1103
+ } = params;
1104
+ this.csl("EVM redeemFrxUSD", "blue-700", "params: %o", params);
1105
+ const ETH_AVG_BLOCK_TIME = 12;
1106
+ const estimateTime = ETH_AVG_BLOCK_TIME * 2 + Math.floor(Math.random() * 11) - 5;
1107
+ const result = {
1108
+ needApprove: false,
1109
+ approveSpender: redeemAndMintContractAddress,
1110
+ sendParam: void 0,
1111
+ quoteParam: {
1112
+ ...params
1113
+ },
1114
+ fees: {},
1115
+ totalFeesUsd: 0,
1116
+ estimateSourceGas: 0n,
1117
+ totalEstimateSourceGas: 0n,
1118
+ estimateSourceGasUsd: 0,
1119
+ estimateTime,
1120
+ outputAmount: "0"
1121
+ };
1122
+ const provider = evmRpcFallbackProvider(fromToken);
1123
+ const execTime = new ExecTime({ type: `FraxZero EVM redeemFrxUSD ${fromToken.chainName}->${toToken.chainName}`, logStyle: "stone-800", isDebug: OpenAPI.DEBUG });
1124
+ new ethers.Contract(redeemAndMintContractAddress, abi, provider);
1125
+ const redeemContract = new ethers.Contract(redeemAndMintContractAddress, abi, this.signer);
1126
+ execTime.breakpoint();
1127
+ let finalPreivewRedeemResult = preivewRedeemResult;
1128
+ if (!finalPreivewRedeemResult) {
1129
+ finalPreivewRedeemResult = await this.preivewRedeemFrxUSD(params);
1130
+ }
1131
+ const {
1132
+ isInsufficientLiquidity,
1133
+ totalAssetsOut
1134
+ } = finalPreivewRedeemResult;
1135
+ execTime.log("preivewRedeemFrxUSD");
1136
+ if (isInsufficientLiquidity) {
1137
+ throw new Error("Insufficient liquidity");
1138
+ }
1139
+ result.outputAmount = numberRemoveEndZero(
1140
+ Big(totalAssetsOut.toString()).div(10 ** toToken.decimals).toFixed(toToken.decimals, 0)
1141
+ );
1142
+ execTime.breakpoint();
1143
+ const allowanceResult = await this.allowance({
1144
+ dry,
1145
+ contractAddress: fromToken.contractAddress,
1146
+ spender: redeemAndMintContractAddress,
1147
+ address: refundTo,
1148
+ amountWei,
1149
+ provider
1150
+ });
1151
+ result.needApprove = allowanceResult.needApprove;
1152
+ execTime.log("allowance");
1153
+ result.sendParam = {
1154
+ contract: redeemContract,
1155
+ method: "redeemToUsdcAndTransfer",
1156
+ param: [
1157
+ amountWei,
1158
+ recipient
1159
+ ]
1160
+ };
1161
+ execTime.breakpoint();
1162
+ const ett = await this.estimateTransaction({
1163
+ dry,
1164
+ contract: redeemContract,
1165
+ method: result.sendParam.method,
1166
+ param: [...result.sendParam.param, { gasLimit: DEFAULT_GAS_LIMIT_FAILED }],
1167
+ fromToken,
1168
+ prices,
1169
+ evmGasFees
1170
+ });
1171
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
1172
+ result.estimateSourceGas = ett.estimateSourceGas;
1173
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
1174
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
1175
+ result.totalFeesUsd = ett.estimateSourceGasUsd;
1176
+ result.sendParam.param[2] = { gasLimit: ett.estimateSourceGasLimit };
1177
+ execTime.log("estimateTransaction");
1178
+ execTime.logTotal("redeemFrxUSD");
1179
+ return result;
1180
+ }
1181
+ /**
1182
+ * Mint frxUSD by depositing USDC into the custodian contract.
1183
+ * Calls deposit(assetsIn, receiver) on usdcCustodianAddress.
1184
+ * assetsIn = USDC amount (6 decimals), sharesOut = frxUSD amount (18 decimals).
1185
+ */
1186
+ async mintFrxUSD(params) {
1187
+ const {
1188
+ dry,
1189
+ recipient,
1190
+ amountWei,
1191
+ fromToken,
1192
+ toToken,
1193
+ prices,
1194
+ evmGasFees,
1195
+ refundTo,
1196
+ abi,
1197
+ usdcCustodianAddress,
1198
+ previewMintResult
1199
+ } = params;
1200
+ this.csl("EVM mintFrxUSD", "blue-700", "params: %o", params);
1201
+ const ETH_AVG_BLOCK_TIME = 12;
1202
+ const estimateTime = ETH_AVG_BLOCK_TIME * 2 + Math.floor(Math.random() * 11) - 5;
1203
+ const result = {
1204
+ needApprove: false,
1205
+ approveSpender: usdcCustodianAddress,
1206
+ sendParam: void 0,
1207
+ quoteParam: {
1208
+ ...params
1209
+ },
1210
+ fees: {},
1211
+ totalFeesUsd: 0,
1212
+ estimateSourceGas: 0n,
1213
+ totalEstimateSourceGas: 0n,
1214
+ estimateSourceGasUsd: 0,
1215
+ estimateTime,
1216
+ outputAmount: "0"
1217
+ };
1218
+ const execTime = new ExecTime({ type: `FraxZero EVM mintFrxUSD ${fromToken.chainName}->${toToken.chainName}`, logStyle: "stone-900", isDebug: OpenAPI.DEBUG });
1219
+ const provider = evmRpcFallbackProvider(fromToken);
1220
+ execTime.breakpoint();
1221
+ const allowanceResult = await this.allowance({
1222
+ dry,
1223
+ contractAddress: fromToken.contractAddress,
1224
+ spender: usdcCustodianAddress,
1225
+ address: refundTo,
1226
+ amountWei,
1227
+ provider
1228
+ });
1229
+ result.needApprove = allowanceResult.needApprove;
1230
+ execTime.log("allowance");
1231
+ new ethers.Contract(usdcCustodianAddress, abi, provider);
1232
+ const usdcCustodianWithSigner = new ethers.Contract(usdcCustodianAddress, abi, this.signer);
1233
+ execTime.breakpoint();
1234
+ let finalPreviewMintResult = previewMintResult;
1235
+ if (!finalPreviewMintResult) {
1236
+ finalPreviewMintResult = await this.previewMintFrxUSD(params);
1237
+ }
1238
+ const {
1239
+ maxUsdc,
1240
+ amountWeiBigInt,
1241
+ totalMax,
1242
+ totalAssetsOut
1243
+ } = finalPreviewMintResult;
1244
+ execTime.log("previewMintFrxUSD");
1245
+ result.outputAmount = numberRemoveEndZero(
1246
+ Big(totalAssetsOut.toString()).div(10 ** toToken.decimals).toFixed(toToken.decimals, 0)
1247
+ );
1248
+ result.sendParam = {
1249
+ contract: usdcCustodianWithSigner,
1250
+ method: "deposit",
1251
+ param: [
1252
+ amountWeiBigInt,
1253
+ recipient
1254
+ ]
1255
+ };
1256
+ execTime.breakpoint();
1257
+ const ett = await this.estimateTransaction({
1258
+ dry,
1259
+ contract: usdcCustodianWithSigner,
1260
+ method: "deposit",
1261
+ param: [...result.sendParam.param, { gasLimit: DEFAULT_GAS_LIMIT_FAILED }],
1262
+ fromToken,
1263
+ prices,
1264
+ evmGasFees
1265
+ });
1266
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
1267
+ result.estimateSourceGas = ett.estimateSourceGas;
1268
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
1269
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
1270
+ result.totalFeesUsd = ett.estimateSourceGasUsd;
1271
+ result.sendParam.param[2] = { gasLimit: ett.estimateSourceGasLimit };
1272
+ execTime.log("estimateTransaction");
1273
+ execTime.logTotal("mintFrxUSD");
1274
+ return result;
1275
+ }
1276
+ async mintAndSendFrxUSD(params) {
1277
+ const {
1278
+ dry,
1279
+ recipient,
1280
+ amountWei,
1281
+ fromToken,
1282
+ toToken,
1283
+ prices,
1284
+ evmGasFees,
1285
+ refundTo,
1286
+ abi,
1287
+ usdcCustodianAddress,
1288
+ redeemAndMintContractAddress,
1289
+ originLayerzero,
1290
+ destinationLayerzero,
1291
+ previewMintResult
1292
+ } = params;
1293
+ this.csl("EVM mintAndSendFrxUSD", "blue-700", "params: %o", params);
1294
+ const {
1295
+ eid: dstEid
1296
+ } = destinationLayerzero;
1297
+ const ETH_AVG_BLOCK_TIME = 12;
1298
+ const estimateTime = ETH_AVG_BLOCK_TIME * 2 + Math.floor(Math.random() * 11) - 5;
1299
+ const result = {
1300
+ needApprove: false,
1301
+ approveSpender: redeemAndMintContractAddress,
1302
+ sendParam: void 0,
1303
+ quoteParam: {
1304
+ ...params
1305
+ },
1306
+ fees: {},
1307
+ totalFeesUsd: 0,
1308
+ estimateSourceGas: 0n,
1309
+ totalEstimateSourceGas: 0n,
1310
+ estimateSourceGasUsd: 0,
1311
+ estimateTime,
1312
+ outputAmount: "0"
1313
+ };
1314
+ const execTime = new ExecTime({ type: `FraxZero EVM mintAndSendFrxUSD ${fromToken.chainName}->${toToken.chainName}`, logStyle: "stone-950", isDebug: OpenAPI.DEBUG });
1315
+ const provider = evmRpcFallbackProvider(fromToken);
1316
+ execTime.breakpoint();
1317
+ const allowanceResult = await this.allowance({
1318
+ dry,
1319
+ contractAddress: fromToken.contractAddress,
1320
+ spender: redeemAndMintContractAddress,
1321
+ address: refundTo,
1322
+ amountWei,
1323
+ provider
1324
+ });
1325
+ result.needApprove = allowanceResult.needApprove;
1326
+ execTime.log("allowance");
1327
+ const redeemAndMintContractWithSigner = new ethers.Contract(redeemAndMintContractAddress, abi, this.signer);
1328
+ execTime.breakpoint();
1329
+ let finalPreviewMintResult = previewMintResult;
1330
+ if (!finalPreviewMintResult) {
1331
+ finalPreviewMintResult = await this.previewMintFrxUSD(params);
1332
+ }
1333
+ const {
1334
+ totalAssetsOut
1335
+ } = finalPreviewMintResult;
1336
+ execTime.log("previewMintFrxUSD");
1337
+ this.csl("EVM mintAndSendFrxUSD", "gray-600", "previewMintFrxUSD totalAssetsOut: %o", totalAssetsOut);
1338
+ result.outputAmount = numberRemoveEndZero(
1339
+ Big(totalAssetsOut.toString()).div(10 ** 18).toFixed(18, 0)
1340
+ );
1341
+ const mintAndSendParam = [
1342
+ // assetsIn
1343
+ amountWei,
1344
+ // dstEid
1345
+ dstEid,
1346
+ // receiver
1347
+ addressToBytes32(toToken.chainType, recipient)
1348
+ ];
1349
+ result.sendParam = {
1350
+ contract: redeemAndMintContractWithSigner,
1351
+ method: "mintAndSend",
1352
+ param: mintAndSendParam
1353
+ };
1354
+ execTime.breakpoint();
1355
+ const ett = await this.estimateTransaction({
1356
+ dry,
1357
+ contract: redeemAndMintContractWithSigner,
1358
+ method: "mintAndSend",
1359
+ param: [...mintAndSendParam, { gasLimit: DEFAULT_GAS_LIMIT_FAILED }],
1360
+ fromToken,
1361
+ prices,
1362
+ evmGasFees
1363
+ });
1364
+ result.fees.estimateGasUsd = ett.estimateSourceGasUsd;
1365
+ result.estimateSourceGas = ett.estimateSourceGas;
1366
+ result.totalEstimateSourceGas = ett.estimateSourceGas;
1367
+ result.estimateSourceGasUsd = ett.estimateSourceGasUsd;
1368
+ result.totalFeesUsd = ett.estimateSourceGasUsd;
1369
+ result.sendParam.param.push({ gasLimit: ett.estimateSourceGasLimit });
1370
+ execTime.log("estimateTransaction");
1371
+ execTime.logTotal("mintAndSendFrxUSD");
1372
+ return result;
1373
+ }
1374
+ async retryLayerzeroLzComponse(params) {
1375
+ const { Options } = await import('@layerzerolabs/lz-v2-utilities');
1376
+ const {
1377
+ layerzeroData,
1378
+ history
1379
+ } = params;
1380
+ const LayerZeroEndpointV2 = "0x1a44076050125825900e736c501f859c50fe728c";
1381
+ const LayerZeroEndpointV2ABI = [
1382
+ {
1383
+ "inputs": [
1384
+ {
1385
+ "internalType": "address",
1386
+ "name": "_from",
1387
+ "type": "address"
1388
+ },
1389
+ {
1390
+ "internalType": "address",
1391
+ "name": "_to",
1392
+ "type": "address"
1393
+ },
1394
+ {
1395
+ "internalType": "bytes32",
1396
+ "name": "_guid",
1397
+ "type": "bytes32"
1398
+ },
1399
+ {
1400
+ "internalType": "uint16",
1401
+ "name": "_index",
1402
+ "type": "uint16"
1403
+ },
1404
+ {
1405
+ "internalType": "bytes",
1406
+ "name": "_message",
1407
+ "type": "bytes"
1408
+ },
1409
+ {
1410
+ "internalType": "bytes",
1411
+ "name": "_extraData",
1412
+ "type": "bytes"
1413
+ }
1414
+ ],
1415
+ "name": "lzCompose",
1416
+ "outputs": [],
1417
+ "stateMutability": "payable",
1418
+ "type": "function"
1419
+ }
1420
+ ];
1421
+ const LayerZeroEndpointV2Contract = new ethers.Contract(LayerZeroEndpointV2, LayerZeroEndpointV2ABI, this.signer);
1422
+ const toToken = usdtTokens[history.destination_chain.blockchain];
1423
+ const amountWei = Big(history.token_in_amount).times(10 ** (toToken.decimals || 6)).toFixed(0);
1424
+ const originLayerzero = USDT0_CONFIG["Arbitrum"];
1425
+ const isOriginLegacy = layerzeroData.destination.lzCompose.failedTx[0].from.toLowerCase() === originLayerzero.oftLegacy?.toLowerCase();
1426
+ isOriginLegacy ? originLayerzero.lzReceiveOptionGasLegacy : originLayerzero.lzReceiveOptionGas;
1427
+ const destATA = await getDestinationAssociatedTokenAddress({
1428
+ recipient: history.receive_address,
1429
+ toToken
1430
+ });
1431
+ if (destATA.needCreateTokenAccount) {
1432
+ LZ_RECEIVE_VALUE[toToken.chainName] || 0;
1433
+ }
1434
+ const composeFrom = layerzeroData.source.tx.from;
1435
+ const composeMsg = layerzeroData.source.tx.payload.split(composeFrom.replace(/^0x/, ""))[1];
1436
+ const _message = buildEndpointV2LzComposePayload({
1437
+ nonce: layerzeroData.pathway.nonce,
1438
+ srcEid: layerzeroData.pathway.srcEid,
1439
+ amountLD: amountWei,
1440
+ composeFrom,
1441
+ composeMsg
1442
+ });
1443
+ const contractParams = [
1444
+ layerzeroData.destination.lzCompose.failedTx[0].from,
1445
+ layerzeroData.destination.lzCompose.failedTx[0].to,
1446
+ layerzeroData.guid,
1447
+ layerzeroData.destination.lzCompose.failedTx[0].index,
1448
+ _message,
1449
+ "0x"
1450
+ ];
1451
+ const dstOFT = isOriginLegacy ? originLayerzero.oft : originLayerzero.oftLegacy;
1452
+ const sendParam = {
1453
+ dstEid: USDT0_CONFIG[history.destination_chain.chainName].eid,
1454
+ to: addressToBytes32(toToken.chainType, history.receive_address),
1455
+ amountLD: amountWei,
1456
+ minAmountLD: 0n,
1457
+ extraOptions: Options.newOptions().toHex(),
1458
+ composeMsg: "0x",
1459
+ oftCmd: "0x"
1460
+ };
1461
+ const dstOFTContract = new ethers.Contract(dstOFT, OFT_ABI, this.provider);
1462
+ const msgFee = await dstOFTContract.quoteSend.staticCall(sendParam, false);
1463
+ let nativeFee = msgFee[0];
1464
+ nativeFee = nativeFee * NATIVE_MSG_FEE_BUFFER / 100n;
1465
+ const tx = await LayerZeroEndpointV2Contract.lzCompose(
1466
+ ...contractParams,
1467
+ {
1468
+ gasLimit: originLayerzero.composeOptionGas || 8e5,
1469
+ value: nativeFee
1470
+ }
1471
+ );
1472
+ const txReceipt = await tx.wait();
1473
+ if (txReceipt.status === 1) {
1474
+ return txReceipt.hash;
1475
+ }
1476
+ return null;
1477
+ }
1478
+ async signTypedData(params) {
1479
+ const { fromToken, amountWei, spender } = params;
1480
+ this.csl("EVM signTypedData", "blue-900", "params: %o", params);
1481
+ const provider = evmRpcFallbackProvider(fromToken);
1482
+ const value = amountWei;
1483
+ const tokenAddress = fromToken.contractAddress;
1484
+ const chainId = fromToken.chainId;
1485
+ const deadline = Math.floor(Date.now() / 1e3) + 60 * 60 * 24 * 3;
1486
+ const account = this.signer.address;
1487
+ const erc20 = new ethers.Contract(tokenAddress, erc20Abi, provider);
1488
+ const nonce = await erc20.nonces(account);
1489
+ const name = await erc20.name();
1490
+ let _version = "1";
1491
+ if (fromToken.symbol === "USDC") {
1492
+ _version = "2";
1493
+ }
1494
+ const domain = {
1495
+ name,
1496
+ version: _version,
1497
+ chainId: Number(chainId),
1498
+ verifyingContract: tokenAddress
1499
+ };
1500
+ const types = {
1501
+ Permit: [
1502
+ { name: "owner", type: "address" },
1503
+ { name: "spender", type: "address" },
1504
+ { name: "value", type: "uint256" },
1505
+ { name: "nonce", type: "uint256" },
1506
+ { name: "deadline", type: "uint256" }
1507
+ ]
1508
+ };
1509
+ const values = {
1510
+ owner: account,
1511
+ spender,
1512
+ value,
1513
+ nonce: nonce.toString(),
1514
+ deadline
1515
+ };
1516
+ const signature = await this.signer?.signTypedData(domain, types, values);
1517
+ const { v, r, s } = ethers.Signature.from(signature);
1518
+ try {
1519
+ const permitParams = [
1520
+ account,
1521
+ spender,
1522
+ value,
1523
+ deadline,
1524
+ v,
1525
+ r,
1526
+ s
1527
+ ];
1528
+ const permitResponse = await erc20.permit.staticCall(...permitParams);
1529
+ this.csl("EVM signTypedData", "green-500", "permit response: %o", permitResponse);
1530
+ } catch (error) {
1531
+ this.csl("EVM signTypedData", "red-500", "check permit signature failed: %o", error);
1532
+ throw new Error("Permit signature verification failed");
1533
+ }
1534
+ return {
1535
+ owner: account,
1536
+ value,
1537
+ deadline,
1538
+ nonce: Number(nonce),
1539
+ v,
1540
+ r,
1541
+ s
1542
+ };
1543
+ }
1544
+ };
1545
+
1546
+ export { EVMWallet, EVMWallet as default };
1547
+ //# sourceMappingURL=index.mjs.map
1548
+ //# sourceMappingURL=index.mjs.map