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