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.
@@ -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 { quoteV3 } from '../utils/quote-helpers.js';
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
- // ✅ V3 模式:直接从链上读取 lpFeeProfile 获取正确的 fee(与 bundleGraduateBuy 保持一致)
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
- // 批量获取 accountInfo
210
- const accounts = await aaManager.getMultipleAccountInfo(owners);
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 报价:使用 V3 Quoter(和 BSC 一样,考虑价格影响,获取准确报价)
365
+ // ✅ V3 报价:使用 Multicall3 批量查询(单次 RPC 请求获取所有钱包的报价)
271
366
  // PotatoSwap V3 QuoterV2: 0x5A6f3723346aF54a4D0693bfC1718D64d4915C3e
272
- const provider = new ethers.JsonRpcProvider(config.rpcUrl, { chainId: config.chainId, name: 'xlayer' });
273
- expectedTokenAmounts = await mapWithConcurrency(buyWeiList, 4, async (buyWei) => {
274
- if (buyWei <= 0n)
275
- return 0n;
276
- try {
277
- const result = await quoteV3(provider, wokb, params.tokenAddress, buyWei, 'XLAYER', v3Fee);
278
- return result.amountOut;
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()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.6.82",
3
+ "version": "1.6.83",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",