four-flap-meme-sdk 1.6.67 → 1.6.68

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, } from './constants.js';
21
- import { PROFIT_CONFIG } from '../utils/constants.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, POTATOSWAP_V3_FACTORY, } from './constants.js';
21
+ import { PROFIT_CONFIG, ZERO_ADDRESS } from '../utils/constants.js';
22
22
  import { mapWithConcurrency } from '../utils/concurrency.js';
23
23
  // ============================================================================
24
24
  // 工具函数
@@ -63,6 +63,69 @@ 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
+ // ============================================================================
66
129
  // 买入 UserOps 构建
67
130
  // ============================================================================
68
131
  /**
@@ -337,6 +400,21 @@ export async function buildBundleSellOps(params) {
337
400
  targetContract = portal;
338
401
  }
339
402
  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
+ }
340
418
  swapData = encodeSwapExactTokensForETHV3({
341
419
  tokenIn: params.tokenAddress,
342
420
  tokenOut: wokb,
@@ -345,7 +423,7 @@ export async function buildBundleSellOps(params) {
345
423
  unwrapRecipient: sender,
346
424
  deadline,
347
425
  amountIn: sellAmount,
348
- amountOutMinimum: 0n,
426
+ amountOutMinimum,
349
427
  sqrtPriceLimitX96: 0n,
350
428
  });
351
429
  targetContract = routerAddress;
@@ -16,10 +16,73 @@ 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
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, } from './constants.js';
20
- import { PROFIT_CONFIG } from '../utils/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, } from './constants.js';
20
+ import { PROFIT_CONFIG, ZERO_ADDRESS } from '../utils/constants.js';
21
21
  import { mapWithConcurrency } from '../utils/concurrency.js';
22
22
  // ============================================================================
23
+ // V3 报价工具函数
24
+ // ============================================================================
25
+ const V3_FACTORY_ABI = [
26
+ 'function getPool(address tokenA, address tokenB, uint24 fee) view returns (address pool)',
27
+ ];
28
+ const V3_POOL_ABI = [
29
+ 'function slot0() view returns (uint160 sqrtPriceX96,int24 tick,uint16 observationIndex,uint16 observationCardinality,uint16 observationCardinalityNext,uint8 feeProtocol,bool unlocked)',
30
+ 'function token0() view returns (address)',
31
+ 'function token1() view returns (address)',
32
+ ];
33
+ const V3_FEE_DENOMINATOR = 1000000n;
34
+ /**
35
+ * 使用 V3 Pool 的 slot0 获取 WOKB → Token 的报价(买入方向)
36
+ */
37
+ async function quoteV3BuyViaSlot0(params) {
38
+ try {
39
+ const { rpcUrl, tokenAddress, wokbAmount, fee } = params;
40
+ if (!tokenAddress || wokbAmount <= 0n)
41
+ return 0n;
42
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
43
+ const wokbLower = WOKB.toLowerCase();
44
+ const tokenLower = tokenAddress.toLowerCase();
45
+ if (wokbLower === tokenLower)
46
+ return wokbAmount;
47
+ const factory = new ethers.Contract(POTATOSWAP_V3_FACTORY, V3_FACTORY_ABI, provider);
48
+ const poolAddr = await factory.getPool(tokenAddress, WOKB, fee);
49
+ if (!poolAddr || poolAddr.toLowerCase() === ZERO_ADDRESS.toLowerCase())
50
+ return 0n;
51
+ const pool = new ethers.Contract(poolAddr, V3_POOL_ABI, provider);
52
+ const [t0, t1, slot0] = await Promise.all([pool.token0(), pool.token1(), pool.slot0()]);
53
+ if (!t0 || !t1 || !slot0)
54
+ return 0n;
55
+ const sqrtPriceX96 = BigInt(slot0[0]);
56
+ if (sqrtPriceX96 <= 0n)
57
+ return 0n;
58
+ // 扣除手续费
59
+ const amountInLessFee = (wokbAmount * (V3_FEE_DENOMINATOR - BigInt(fee))) / V3_FEE_DENOMINATOR;
60
+ if (amountInLessFee <= 0n)
61
+ return 0n;
62
+ const Q192 = 2n ** 192n;
63
+ const num = sqrtPriceX96 * sqrtPriceX96;
64
+ const t0Lower = String(t0).toLowerCase();
65
+ const t1Lower = String(t1).toLowerCase();
66
+ // sqrtPriceX96 表示 token1/token0 的现货价
67
+ // 买入方向:WOKB → Token
68
+ if (wokbLower === t0Lower && tokenLower === t1Lower) {
69
+ // WOKB 是 token0,Token 是 token1
70
+ // price = token1/token0, 所以 tokenOut = wokbIn * price = wokbIn * num / Q192
71
+ return (amountInLessFee * num) / Q192;
72
+ }
73
+ if (wokbLower === t1Lower && tokenLower === t0Lower) {
74
+ // WOKB 是 token1,Token 是 token0
75
+ // price = token1/token0 = WOKB/Token, 所以 tokenOut = wokbIn / price = wokbIn * Q192 / num
76
+ return (amountInLessFee * Q192) / num;
77
+ }
78
+ return 0n;
79
+ }
80
+ catch (e) {
81
+ console.warn('[quoteV3BuyViaSlot0] 报价失败:', e);
82
+ return 0n;
83
+ }
84
+ }
85
+ // ============================================================================
23
86
  // 工具函数
24
87
  // ============================================================================
25
88
  /**
@@ -203,13 +266,19 @@ export async function buildWashOps(params) {
203
266
  });
204
267
  }
205
268
  else if (poolType === 'v3') {
206
- // V3 使用 Portal quoteExactInput 作为 fallback(理想情况下应使用 Quoter 合约)
207
- const portalQuery = new PortalQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId });
269
+ // V3 使用 slot0 现货价计算预期代币输出(与 dex-bundle-swap 保持一致)
208
270
  expectedTokenAmounts = await mapWithConcurrency(buyWeiList, 4, async (buyWei) => {
209
271
  if (buyWei <= 0n)
210
272
  return 0n;
211
273
  try {
212
- return await portalQuery.quoteExactInput(wokb, params.tokenAddress, buyWei);
274
+ const quoted = await quoteV3BuyViaSlot0({
275
+ rpcUrl: config.rpcUrl,
276
+ tokenAddress: params.tokenAddress,
277
+ wokbAmount: buyWei,
278
+ fee: v3Fee,
279
+ });
280
+ // 应用 2% 滑点保护,避免因价格波动导致卖出失败
281
+ return (quoted * 98n) / 100n;
213
282
  }
214
283
  catch {
215
284
  return 0n;
@@ -276,6 +345,7 @@ export async function buildWashOps(params) {
276
345
  fee: v3Fee,
277
346
  recipient: sender, // ✅ 使用已验证的 sender 变量
278
347
  unwrapRecipient: sender, // ✅ 使用已验证的 sender 变量
348
+ routerAddress, // ✅ 传递正确的 Router 地址
279
349
  deadline,
280
350
  amountIn: expectedTokenAmount,
281
351
  amountOutMinimum: 0n,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.6.67",
3
+ "version": "1.6.68",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",