four-flap-meme-sdk 1.6.68 → 1.6.70
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.
|
@@ -17,8 +17,8 @@ import { createAAAccountManager, encodeExecute, createWallet } from './aa-accoun
|
|
|
17
17
|
import { encodeBuyCall, encodeSellCall, PortalQuery, lpFeeProfileToV3Fee } from './portal-ops.js';
|
|
18
18
|
import { encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactETHForTokensV3, encodeSwapExactTokensForETHV3, } from './dex.js';
|
|
19
19
|
import { encodeApproveCall } from './portal-ops.js';
|
|
20
|
-
import { FLAP_PORTAL, WOKB, XLAYER_CHAIN_ID, DEFAULT_RPC_URL, ENTRYPOINT_V06, SIMPLE_ACCOUNT_FACTORY, PARTICLE_BUNDLER_URL, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER,
|
|
21
|
-
import { PROFIT_CONFIG
|
|
20
|
+
import { FLAP_PORTAL, WOKB, XLAYER_CHAIN_ID, DEFAULT_RPC_URL, ENTRYPOINT_V06, SIMPLE_ACCOUNT_FACTORY, PARTICLE_BUNDLER_URL, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, } from './constants.js';
|
|
21
|
+
import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
22
22
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
23
23
|
// ============================================================================
|
|
24
24
|
// 工具函数
|
|
@@ -63,69 +63,6 @@ function normalizeConfig(cfg) {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
// ============================================================================
|
|
66
|
-
// V3 报价工具函数
|
|
67
|
-
// ============================================================================
|
|
68
|
-
const V3_FACTORY_ABI = [
|
|
69
|
-
'function getPool(address tokenA, address tokenB, uint24 fee) view returns (address pool)',
|
|
70
|
-
];
|
|
71
|
-
const V3_POOL_ABI = [
|
|
72
|
-
'function slot0() view returns (uint160 sqrtPriceX96,int24 tick,uint16 observationIndex,uint16 observationCardinality,uint16 observationCardinalityNext,uint8 feeProtocol,bool unlocked)',
|
|
73
|
-
'function token0() view returns (address)',
|
|
74
|
-
'function token1() view returns (address)',
|
|
75
|
-
];
|
|
76
|
-
const V3_FEE_DENOMINATOR = 1000000n;
|
|
77
|
-
/**
|
|
78
|
-
* 使用 V3 Pool 的 slot0 获取 Token → WOKB 的报价(卖出方向)
|
|
79
|
-
*/
|
|
80
|
-
async function quoteV3SellViaSlot0(params) {
|
|
81
|
-
try {
|
|
82
|
-
const { rpcUrl, tokenAddress, tokenAmount, fee } = params;
|
|
83
|
-
if (!tokenAddress || tokenAmount <= 0n)
|
|
84
|
-
return 0n;
|
|
85
|
-
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
86
|
-
const wokbLower = WOKB.toLowerCase();
|
|
87
|
-
const tokenLower = tokenAddress.toLowerCase();
|
|
88
|
-
if (wokbLower === tokenLower)
|
|
89
|
-
return tokenAmount;
|
|
90
|
-
const factory = new ethers.Contract(POTATOSWAP_V3_FACTORY, V3_FACTORY_ABI, provider);
|
|
91
|
-
const poolAddr = await factory.getPool(tokenAddress, WOKB, fee);
|
|
92
|
-
if (!poolAddr || poolAddr.toLowerCase() === ZERO_ADDRESS.toLowerCase())
|
|
93
|
-
return 0n;
|
|
94
|
-
const pool = new ethers.Contract(poolAddr, V3_POOL_ABI, provider);
|
|
95
|
-
const [t0, t1, slot0] = await Promise.all([pool.token0(), pool.token1(), pool.slot0()]);
|
|
96
|
-
if (!t0 || !t1 || !slot0)
|
|
97
|
-
return 0n;
|
|
98
|
-
const sqrtPriceX96 = BigInt(slot0[0]);
|
|
99
|
-
if (sqrtPriceX96 <= 0n)
|
|
100
|
-
return 0n;
|
|
101
|
-
// 扣除手续费
|
|
102
|
-
const amountInLessFee = (tokenAmount * (V3_FEE_DENOMINATOR - BigInt(fee))) / V3_FEE_DENOMINATOR;
|
|
103
|
-
if (amountInLessFee <= 0n)
|
|
104
|
-
return 0n;
|
|
105
|
-
const Q192 = 2n ** 192n;
|
|
106
|
-
const num = sqrtPriceX96 * sqrtPriceX96;
|
|
107
|
-
const t0Lower = String(t0).toLowerCase();
|
|
108
|
-
const t1Lower = String(t1).toLowerCase();
|
|
109
|
-
// sqrtPriceX96 表示 token1/token0 的现货价
|
|
110
|
-
// 卖出方向:Token → WOKB
|
|
111
|
-
if (tokenLower === t0Lower && wokbLower === t1Lower) {
|
|
112
|
-
// Token 是 token0,WOKB 是 token1
|
|
113
|
-
// price = token1/token0 = WOKB/Token, 所以 wokbOut = tokenIn * price = tokenIn * num / Q192
|
|
114
|
-
return (amountInLessFee * num) / Q192;
|
|
115
|
-
}
|
|
116
|
-
if (tokenLower === t1Lower && wokbLower === t0Lower) {
|
|
117
|
-
// Token 是 token1,WOKB 是 token0
|
|
118
|
-
// price = token1/token0 = Token/WOKB, 所以 wokbOut = tokenIn / price = tokenIn * Q192 / num
|
|
119
|
-
return (amountInLessFee * Q192) / num;
|
|
120
|
-
}
|
|
121
|
-
return 0n;
|
|
122
|
-
}
|
|
123
|
-
catch (e) {
|
|
124
|
-
console.warn('[quoteV3SellViaSlot0] 报价失败:', e);
|
|
125
|
-
return 0n;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
// ============================================================================
|
|
129
66
|
// 买入 UserOps 构建
|
|
130
67
|
// ============================================================================
|
|
131
68
|
/**
|
|
@@ -400,21 +337,6 @@ export async function buildBundleSellOps(params) {
|
|
|
400
337
|
targetContract = portal;
|
|
401
338
|
}
|
|
402
339
|
else if (poolType === 'v3') {
|
|
403
|
-
// ✅ V3 卖出报价:使用 slot0 计算预期输出,并应用 2% 滑点保护
|
|
404
|
-
let amountOutMinimum = 0n;
|
|
405
|
-
try {
|
|
406
|
-
const quotedOut = await quoteV3SellViaSlot0({
|
|
407
|
-
rpcUrl: config.rpcUrl,
|
|
408
|
-
tokenAddress: params.tokenAddress,
|
|
409
|
-
tokenAmount: sellAmount,
|
|
410
|
-
fee: v3Fee,
|
|
411
|
-
});
|
|
412
|
-
// 应用 2% 滑点保护
|
|
413
|
-
amountOutMinimum = (quotedOut * 98n) / 100n;
|
|
414
|
-
}
|
|
415
|
-
catch (e) {
|
|
416
|
-
console.warn(`[buildBundleSellOps] V3 卖出报价失败,使用 amountOutMinimum=0:`, e);
|
|
417
|
-
}
|
|
418
340
|
swapData = encodeSwapExactTokensForETHV3({
|
|
419
341
|
tokenIn: params.tokenAddress,
|
|
420
342
|
tokenOut: wokb,
|
|
@@ -423,7 +345,7 @@ export async function buildBundleSellOps(params) {
|
|
|
423
345
|
unwrapRecipient: sender,
|
|
424
346
|
deadline,
|
|
425
347
|
amountIn: sellAmount,
|
|
426
|
-
amountOutMinimum,
|
|
348
|
+
amountOutMinimum: 0n,
|
|
427
349
|
sqrtPriceLimitX96: 0n,
|
|
428
350
|
});
|
|
429
351
|
targetContract = routerAddress;
|
package/dist/xlayer/wash-ops.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { ethers } from 'ethers';
|
|
16
16
|
import { createAAAccountManager, encodeExecute, createWallet } from './aa-account.js';
|
|
17
17
|
import { encodeBuyCall, encodeSellCall, PortalQuery, lpFeeProfileToV3Fee } from './portal-ops.js';
|
|
18
|
-
import { encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee,
|
|
18
|
+
import { encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, DexQuery, } from './dex.js';
|
|
19
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';
|
|
20
20
|
import { PROFIT_CONFIG, ZERO_ADDRESS } from '../utils/constants.js';
|
|
21
21
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
@@ -266,22 +266,30 @@ export async function buildWashOps(params) {
|
|
|
266
266
|
});
|
|
267
267
|
}
|
|
268
268
|
else if (poolType === 'v3') {
|
|
269
|
-
// ✅ V3
|
|
269
|
+
// ✅ V3 报价:使用 V2 Router 的报价(因为卖出使用 V2 Router 聚合路由)
|
|
270
|
+
// V2 Router 会自动聚合路由到最优池子(包括 V3 池子)
|
|
271
|
+
const dexQuery = new DexQuery({ rpcUrl: config.rpcUrl, routerAddress: POTATOSWAP_V2_ROUTER, wokbAddress: wokb });
|
|
270
272
|
expectedTokenAmounts = await mapWithConcurrency(buyWeiList, 4, async (buyWei) => {
|
|
271
273
|
if (buyWei <= 0n)
|
|
272
274
|
return 0n;
|
|
273
275
|
try {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
tokenAddress: params.tokenAddress,
|
|
277
|
-
wokbAmount: buyWei,
|
|
278
|
-
fee: v3Fee,
|
|
279
|
-
});
|
|
280
|
-
// 应用 2% 滑点保护,避免因价格波动导致卖出失败
|
|
281
|
-
return (quoted * 98n) / 100n;
|
|
276
|
+
// 使用 V2 Router 的报价,反映实际聚合路由路径
|
|
277
|
+
return await dexQuery.quoteOkbToToken(buyWei, params.tokenAddress);
|
|
282
278
|
}
|
|
283
279
|
catch {
|
|
284
|
-
|
|
280
|
+
// 降级:使用 slot0 现货价,应用 5% 安全边际
|
|
281
|
+
try {
|
|
282
|
+
const slotQuoted = await quoteV3BuyViaSlot0({
|
|
283
|
+
rpcUrl: config.rpcUrl,
|
|
284
|
+
tokenAddress: params.tokenAddress,
|
|
285
|
+
wokbAmount: buyWei,
|
|
286
|
+
fee: v3Fee,
|
|
287
|
+
});
|
|
288
|
+
return (slotQuoted * 95n) / 100n;
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
return 0n;
|
|
292
|
+
}
|
|
285
293
|
}
|
|
286
294
|
});
|
|
287
295
|
}
|
|
@@ -327,31 +335,18 @@ export async function buildWashOps(params) {
|
|
|
327
335
|
sellCallData = encodeExecute(portal, 0n, sellSwapData);
|
|
328
336
|
}
|
|
329
337
|
else if (poolType === 'v3') {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
amountOutMinimum: 0n,
|
|
338
|
-
sqrtPriceLimitX96: 0n,
|
|
339
|
-
});
|
|
340
|
-
buyCallData = encodeExecute(routerAddress, buyWei, buySwapData);
|
|
338
|
+
// ✅ V3 买入:改用 V2 Router(V2 Router 会自动聚合路由到 V3 池子)
|
|
339
|
+
// 这样买入和卖出都使用同一个路由器,报价也一致
|
|
340
|
+
const buySwapData = encodeSwapExactETHForTokensSupportingFee(0n, [wokb, params.tokenAddress], sender, // ✅ 使用已验证的 sender 变量
|
|
341
|
+
deadline);
|
|
342
|
+
buyCallData = encodeExecute(POTATOSWAP_V2_ROUTER, buyWei, buySwapData);
|
|
343
|
+
// ✅ V3 卖出:改用 V2 Router(V2 Router 会自动聚合路由到 V3 池子)
|
|
344
|
+
// 这样只需要授权给 V2 Router,不需要额外授权给 V3 Router
|
|
341
345
|
const expectedTokenAmount = expectedTokenAmounts[i] || 0n;
|
|
342
|
-
const sellSwapData =
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
recipient: sender, // ✅ 使用已验证的 sender 变量
|
|
347
|
-
unwrapRecipient: sender, // ✅ 使用已验证的 sender 变量
|
|
348
|
-
routerAddress, // ✅ 传递正确的 Router 地址
|
|
349
|
-
deadline,
|
|
350
|
-
amountIn: expectedTokenAmount,
|
|
351
|
-
amountOutMinimum: 0n,
|
|
352
|
-
sqrtPriceLimitX96: 0n,
|
|
353
|
-
});
|
|
354
|
-
sellCallData = encodeExecute(routerAddress, 0n, sellSwapData);
|
|
346
|
+
const sellSwapData = encodeSwapExactTokensForETHSupportingFee(expectedTokenAmount, 0n, [params.tokenAddress, wokb], sender, // ✅ 使用已验证的 sender 变量
|
|
347
|
+
deadline);
|
|
348
|
+
// 使用 V2 Router 地址进行卖出
|
|
349
|
+
sellCallData = encodeExecute(POTATOSWAP_V2_ROUTER, 0n, sellSwapData);
|
|
355
350
|
}
|
|
356
351
|
else {
|
|
357
352
|
// V2
|
|
@@ -384,9 +379,9 @@ export async function buildWashOps(params) {
|
|
|
384
379
|
opType: 'sell',
|
|
385
380
|
});
|
|
386
381
|
}
|
|
387
|
-
// 固定 Gas
|
|
382
|
+
// 固定 Gas(买卖操作支持前端传入)
|
|
388
383
|
const profitCallGasLimit = 120000n;
|
|
389
|
-
const tradeCallGasLimit = 400000n;
|
|
384
|
+
const tradeCallGasLimit = params.tradeGasLimit ?? 400000n;
|
|
390
385
|
// 构造 UserOps
|
|
391
386
|
const built = await mapWithConcurrency(allSkeletons, 20, async (sk) => {
|
|
392
387
|
const callGasLimit = sk.opType === 'profit' ? profitCallGasLimit : tradeCallGasLimit;
|