four-flap-meme-sdk 1.6.82 → 1.6.83
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/xlayer/wash-ops.js +126 -35
- package/package.json +1 -1
package/dist/xlayer/wash-ops.js
CHANGED
|
@@ -12,14 +12,15 @@
|
|
|
12
12
|
* - 利润比例:PROFIT_CONFIG.RATE_BPS (40 bps = 0.4%)
|
|
13
13
|
* - 利润接收者:PROFIT_CONFIG.RECIPIENT
|
|
14
14
|
*/
|
|
15
|
-
import { ethers } from 'ethers';
|
|
15
|
+
import { ethers, Contract, Interface } from 'ethers';
|
|
16
16
|
import { createAAAccountManager, encodeExecute, createWallet } from './aa-account.js';
|
|
17
17
|
import { encodeBuyCall, encodeSellCall, PortalQuery, lpFeeProfileToV3Fee } from './portal-ops.js';
|
|
18
18
|
import { encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactETHForTokensV3, encodeSwapExactTokensForETHV3, DexQuery, } from './dex.js';
|
|
19
|
-
import { FLAP_PORTAL, WOKB, XLAYER_CHAIN_ID, DEFAULT_RPC_URL, ENTRYPOINT_V06, SIMPLE_ACCOUNT_FACTORY, PARTICLE_BUNDLER_URL, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, POTATOSWAP_V3_FACTORY, } from './constants.js';
|
|
19
|
+
import { FLAP_PORTAL, WOKB, XLAYER_CHAIN_ID, DEFAULT_RPC_URL, ENTRYPOINT_V06, SIMPLE_ACCOUNT_FACTORY, PARTICLE_BUNDLER_URL, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, POTATOSWAP_V3_FACTORY, MULTICALL3, } from './constants.js';
|
|
20
20
|
import { PROFIT_CONFIG, ZERO_ADDRESS } from '../utils/constants.js';
|
|
21
21
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
22
|
-
import {
|
|
22
|
+
import { MULTICALL3_ABI, V3_QUOTER_ABI } from '../abis/common.js';
|
|
23
|
+
import { QUOTE_CONFIG } from '../utils/quote-helpers.js';
|
|
23
24
|
// ============================================================================
|
|
24
25
|
// V3 报价工具函数
|
|
25
26
|
// ============================================================================
|
|
@@ -83,6 +84,95 @@ async function quoteV3BuyViaSlot0(params) {
|
|
|
83
84
|
return 0n;
|
|
84
85
|
}
|
|
85
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* ✅ 使用 Multicall3 批量查询 V3 报价
|
|
89
|
+
* 将多个 quoteExactInputSingle 调用合并成一个 RPC 请求
|
|
90
|
+
*/
|
|
91
|
+
async function batchQuoteV3WithMulticall(params) {
|
|
92
|
+
const { rpcUrl, tokenIn, tokenOut, amountsIn, fee } = params;
|
|
93
|
+
// 过滤掉无效金额
|
|
94
|
+
if (!amountsIn.length)
|
|
95
|
+
return [];
|
|
96
|
+
const v3QuoterAddress = QUOTE_CONFIG.XLAYER.v3Quoter;
|
|
97
|
+
if (!v3QuoterAddress) {
|
|
98
|
+
console.warn('[batchQuoteV3WithMulticall] XLayer V3 Quoter 未配置');
|
|
99
|
+
return amountsIn.map(() => 0n);
|
|
100
|
+
}
|
|
101
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
102
|
+
const quoterIface = new Interface(V3_QUOTER_ABI);
|
|
103
|
+
const multicallIface = new Interface(MULTICALL3_ABI);
|
|
104
|
+
// 构建所有报价调用
|
|
105
|
+
const calls = amountsIn.map((amountIn, idx) => {
|
|
106
|
+
if (amountIn <= 0n) {
|
|
107
|
+
// 对于无效金额,使用占位调用(会失败,但不影响其他调用)
|
|
108
|
+
return {
|
|
109
|
+
target: v3QuoterAddress,
|
|
110
|
+
allowFailure: true,
|
|
111
|
+
callData: quoterIface.encodeFunctionData('quoteExactInputSingle', [{
|
|
112
|
+
tokenIn,
|
|
113
|
+
tokenOut,
|
|
114
|
+
amountIn: 0n,
|
|
115
|
+
fee,
|
|
116
|
+
sqrtPriceLimitX96: 0n
|
|
117
|
+
}]),
|
|
118
|
+
originalIdx: idx,
|
|
119
|
+
originalAmount: amountIn,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
target: v3QuoterAddress,
|
|
124
|
+
allowFailure: true,
|
|
125
|
+
callData: quoterIface.encodeFunctionData('quoteExactInputSingle', [{
|
|
126
|
+
tokenIn,
|
|
127
|
+
tokenOut,
|
|
128
|
+
amountIn,
|
|
129
|
+
fee,
|
|
130
|
+
sqrtPriceLimitX96: 0n
|
|
131
|
+
}]),
|
|
132
|
+
originalIdx: idx,
|
|
133
|
+
originalAmount: amountIn,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
try {
|
|
137
|
+
const multicall3 = new Contract(MULTICALL3, MULTICALL3_ABI, provider);
|
|
138
|
+
const results = await multicall3.aggregate3.staticCall(calls.map(c => ({ target: c.target, allowFailure: c.allowFailure, callData: c.callData })));
|
|
139
|
+
// 解析结果
|
|
140
|
+
return results.map((result, idx) => {
|
|
141
|
+
if (!result.success || calls[idx].originalAmount <= 0n) {
|
|
142
|
+
return 0n;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const decoded = quoterIface.decodeFunctionResult('quoteExactInputSingle', result.returnData);
|
|
146
|
+
return decoded[0];
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return 0n;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
console.warn('[batchQuoteV3WithMulticall] Multicall3 失败,回退到串行查询:', e);
|
|
155
|
+
// 回退到串行查询
|
|
156
|
+
return await Promise.all(amountsIn.map(async (amountIn) => {
|
|
157
|
+
if (amountIn <= 0n)
|
|
158
|
+
return 0n;
|
|
159
|
+
try {
|
|
160
|
+
const quoter = new Contract(v3QuoterAddress, V3_QUOTER_ABI, provider);
|
|
161
|
+
const result = await quoter.quoteExactInputSingle.staticCall({
|
|
162
|
+
tokenIn,
|
|
163
|
+
tokenOut,
|
|
164
|
+
amountIn,
|
|
165
|
+
fee,
|
|
166
|
+
sqrtPriceLimitX96: 0n
|
|
167
|
+
});
|
|
168
|
+
return (Array.isArray(result) ? result[0] : result);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return 0n;
|
|
172
|
+
}
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
86
176
|
// ============================================================================
|
|
87
177
|
// 工具函数
|
|
88
178
|
// ============================================================================
|
|
@@ -169,24 +259,7 @@ export async function buildWashOps(params) {
|
|
|
169
259
|
// ✅ 归一化配置,填充默认值(与前端 normalizeXLayerAAConfig 保持一致)
|
|
170
260
|
const config = normalizeConfig(params.config);
|
|
171
261
|
const aaManager = createAAAccountManager(config);
|
|
172
|
-
// ✅
|
|
173
|
-
// 不再依赖前端传入的 v3Fee 参数,避免前端传入错误值导致交易失败
|
|
174
|
-
let v3Fee = params.v3Fee || 2500;
|
|
175
|
-
if (poolType === 'v3') {
|
|
176
|
-
try {
|
|
177
|
-
const portalQuery = new PortalQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId });
|
|
178
|
-
const tokenState = await portalQuery.getTokenV7(params.tokenAddress);
|
|
179
|
-
const correctV3Fee = lpFeeProfileToV3Fee(tokenState.lpFeeProfile);
|
|
180
|
-
if (correctV3Fee !== v3Fee) {
|
|
181
|
-
console.log(`[buildWashOps] V3 Fee 校正: 前端传入 ${v3Fee}, 链上 lpFeeProfile=${tokenState.lpFeeProfile} → 正确 fee=${correctV3Fee}`);
|
|
182
|
-
}
|
|
183
|
-
v3Fee = correctV3Fee;
|
|
184
|
-
}
|
|
185
|
-
catch (e) {
|
|
186
|
-
console.warn(`[buildWashOps] 读取 lpFeeProfile 失败,使用前端传入的 v3Fee=${v3Fee}:`, e);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
// ✅ 预先过滤无效私钥并保持索引对应
|
|
262
|
+
// ✅ 预先过滤无效私钥并保持索引对应(同步操作,不需要等待)
|
|
190
263
|
const validEntries = [];
|
|
191
264
|
for (let i = 0; i < params.ownerPrivateKeys.length; i++) {
|
|
192
265
|
const pk = String(params.ownerPrivateKeys[i] || '').trim();
|
|
@@ -206,8 +279,30 @@ export async function buildWashOps(params) {
|
|
|
206
279
|
const ownerWallets = validEntries.map(e => createWallet(e.pk, config));
|
|
207
280
|
const owners = ownerWallets.map(w => w.address);
|
|
208
281
|
const buyAmountsOkb = validEntries.map(e => e.amount);
|
|
209
|
-
//
|
|
210
|
-
|
|
282
|
+
// ✅ 并行获取:账户信息 + V3 Fee(减少总等待时间)
|
|
283
|
+
let v3Fee = params.v3Fee || 2500;
|
|
284
|
+
const parallelTasks = [];
|
|
285
|
+
// 任务1:批量获取 accountInfo
|
|
286
|
+
const accountsPromise = aaManager.getMultipleAccountInfo(owners);
|
|
287
|
+
// 任务2:V3 模式下获取正确的 fee(与账户信息并行)
|
|
288
|
+
if (poolType === 'v3') {
|
|
289
|
+
parallelTasks.push((async () => {
|
|
290
|
+
try {
|
|
291
|
+
const portalQuery = new PortalQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId });
|
|
292
|
+
const tokenState = await portalQuery.getTokenV7(params.tokenAddress);
|
|
293
|
+
const correctV3Fee = lpFeeProfileToV3Fee(tokenState.lpFeeProfile);
|
|
294
|
+
if (correctV3Fee !== v3Fee) {
|
|
295
|
+
console.log(`[buildWashOps] V3 Fee 校正: 前端传入 ${v3Fee}, 链上 lpFeeProfile=${tokenState.lpFeeProfile} → 正确 fee=${correctV3Fee}`);
|
|
296
|
+
}
|
|
297
|
+
v3Fee = correctV3Fee;
|
|
298
|
+
}
|
|
299
|
+
catch (e) {
|
|
300
|
+
console.warn(`[buildWashOps] 读取 lpFeeProfile 失败,使用前端传入的 v3Fee=${v3Fee}:`, e);
|
|
301
|
+
}
|
|
302
|
+
})());
|
|
303
|
+
}
|
|
304
|
+
// 等待所有并行任务完成
|
|
305
|
+
const [accounts] = await Promise.all([accountsPromise, ...parallelTasks]);
|
|
211
306
|
// 确保 accounts 数量与 ownerWallets 匹配
|
|
212
307
|
if (accounts.length !== ownerWallets.length) {
|
|
213
308
|
console.error(`[buildWashOps] accounts 数量 (${accounts.length}) 与 ownerWallets 数量 (${ownerWallets.length}) 不匹配`);
|
|
@@ -267,19 +362,15 @@ export async function buildWashOps(params) {
|
|
|
267
362
|
});
|
|
268
363
|
}
|
|
269
364
|
else if (poolType === 'v3') {
|
|
270
|
-
// ✅ V3 报价:使用
|
|
365
|
+
// ✅ V3 报价:使用 Multicall3 批量查询(单次 RPC 请求获取所有钱包的报价)
|
|
271
366
|
// PotatoSwap V3 QuoterV2: 0x5A6f3723346aF54a4D0693bfC1718D64d4915C3e
|
|
272
|
-
|
|
273
|
-
expectedTokenAmounts = await
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
catch {
|
|
281
|
-
return 0n;
|
|
282
|
-
}
|
|
367
|
+
console.log(`[buildWashOps] V3 批量报价: ${buyWeiList.length} 个钱包, fee=${v3Fee}`);
|
|
368
|
+
expectedTokenAmounts = await batchQuoteV3WithMulticall({
|
|
369
|
+
rpcUrl: config.rpcUrl,
|
|
370
|
+
tokenIn: wokb,
|
|
371
|
+
tokenOut: params.tokenAddress,
|
|
372
|
+
amountsIn: buyWeiList,
|
|
373
|
+
fee: v3Fee,
|
|
283
374
|
});
|
|
284
375
|
}
|
|
285
376
|
console.log(`[buildWashOps] 预测买入代币数量 (${poolType}):`, expectedTokenAmounts.map(a => a.toString()));
|