four-flap-meme-sdk 1.3.89 → 1.3.90

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.
Files changed (122) hide show
  1. package/dist/clients/blockrazor.js +1 -0
  2. package/dist/contracts/tm-bundle-merkle/core.js +3 -6
  3. package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +38 -30
  4. package/dist/contracts/tm-bundle-merkle/swap-buy-first.d.ts +1 -0
  5. package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +3 -4
  6. package/dist/contracts/tm-bundle-merkle/swap.d.ts +3 -0
  7. package/dist/contracts/tm-bundle-merkle/swap.js +2 -2
  8. package/dist/flap/portal-bundle-merkle/core.js +6 -2
  9. package/dist/flap/portal-bundle-merkle/pancake-proxy.js +35 -55
  10. package/dist/flap/portal-bundle-merkle/swap-buy-first.d.ts +2 -0
  11. package/dist/flap/portal-bundle-merkle/swap-buy-first.js +11 -6
  12. package/dist/flap/portal-bundle-merkle/swap.d.ts +2 -0
  13. package/dist/flap/portal-bundle-merkle/swap.js +22 -10
  14. package/dist/index.d.ts +2 -1
  15. package/dist/index.js +1 -0
  16. package/dist/pancake/bundle-buy-first.d.ts +1 -0
  17. package/dist/pancake/bundle-buy-first.js +9 -4
  18. package/dist/pancake/bundle-swap.d.ts +4 -0
  19. package/dist/pancake/bundle-swap.js +14 -7
  20. package/dist/sol/constants.d.ts +126 -0
  21. package/dist/sol/constants.js +145 -0
  22. package/dist/sol/dex/index.d.ts +8 -0
  23. package/dist/sol/dex/index.js +12 -0
  24. package/dist/sol/dex/meteora/client.d.ts +76 -0
  25. package/dist/sol/dex/meteora/client.js +219 -0
  26. package/dist/sol/dex/meteora/damm-v1-bundle.d.ts +61 -0
  27. package/dist/sol/dex/meteora/damm-v1-bundle.js +112 -0
  28. package/dist/sol/dex/meteora/damm-v1.d.ts +118 -0
  29. package/dist/sol/dex/meteora/damm-v1.js +315 -0
  30. package/dist/sol/dex/meteora/damm-v2-bundle.d.ts +82 -0
  31. package/dist/sol/dex/meteora/damm-v2-bundle.js +242 -0
  32. package/dist/sol/dex/meteora/damm-v2.d.ts +172 -0
  33. package/dist/sol/dex/meteora/damm-v2.js +632 -0
  34. package/dist/sol/dex/meteora/dbc-bundle.d.ts +123 -0
  35. package/dist/sol/dex/meteora/dbc-bundle.js +304 -0
  36. package/dist/sol/dex/meteora/dbc.d.ts +192 -0
  37. package/dist/sol/dex/meteora/dbc.js +619 -0
  38. package/dist/sol/dex/meteora/dlmm-bundle.d.ts +39 -0
  39. package/dist/sol/dex/meteora/dlmm-bundle.js +189 -0
  40. package/dist/sol/dex/meteora/dlmm.d.ts +146 -0
  41. package/dist/sol/dex/meteora/dlmm.js +593 -0
  42. package/dist/sol/dex/meteora/index.d.ts +25 -0
  43. package/dist/sol/dex/meteora/index.js +65 -0
  44. package/dist/sol/dex/meteora/types.d.ts +787 -0
  45. package/dist/sol/dex/meteora/types.js +110 -0
  46. package/dist/sol/dex/orca/index.d.ts +10 -0
  47. package/dist/sol/dex/orca/index.js +16 -0
  48. package/dist/sol/dex/orca/orca-bundle.d.ts +41 -0
  49. package/dist/sol/dex/orca/orca-bundle.js +173 -0
  50. package/dist/sol/dex/orca/orca.d.ts +65 -0
  51. package/dist/sol/dex/orca/orca.js +474 -0
  52. package/dist/sol/dex/orca/types.d.ts +263 -0
  53. package/dist/sol/dex/orca/types.js +38 -0
  54. package/dist/sol/dex/orca/wavebreak-bundle.d.ts +34 -0
  55. package/dist/sol/dex/orca/wavebreak-bundle.js +198 -0
  56. package/dist/sol/dex/orca/wavebreak-types.d.ts +227 -0
  57. package/dist/sol/dex/orca/wavebreak-types.js +23 -0
  58. package/dist/sol/dex/orca/wavebreak.d.ts +78 -0
  59. package/dist/sol/dex/orca/wavebreak.js +497 -0
  60. package/dist/sol/dex/pump/index.d.ts +9 -0
  61. package/dist/sol/dex/pump/index.js +14 -0
  62. package/dist/sol/dex/pump/pump-bundle.d.ts +92 -0
  63. package/dist/sol/dex/pump/pump-bundle.js +383 -0
  64. package/dist/sol/dex/pump/pump-swap-bundle.d.ts +103 -0
  65. package/dist/sol/dex/pump/pump-swap-bundle.js +380 -0
  66. package/dist/sol/dex/pump/pump-swap.d.ts +46 -0
  67. package/dist/sol/dex/pump/pump-swap.js +199 -0
  68. package/dist/sol/dex/pump/pump.d.ts +35 -0
  69. package/dist/sol/dex/pump/pump.js +352 -0
  70. package/dist/sol/dex/pump/types.d.ts +215 -0
  71. package/dist/sol/dex/pump/types.js +5 -0
  72. package/dist/sol/dex/raydium/index.d.ts +8 -0
  73. package/dist/sol/dex/raydium/index.js +12 -0
  74. package/dist/sol/dex/raydium/launchlab.d.ts +68 -0
  75. package/dist/sol/dex/raydium/launchlab.js +210 -0
  76. package/dist/sol/dex/raydium/raydium-bundle.d.ts +64 -0
  77. package/dist/sol/dex/raydium/raydium-bundle.js +324 -0
  78. package/dist/sol/dex/raydium/raydium.d.ts +40 -0
  79. package/dist/sol/dex/raydium/raydium.js +366 -0
  80. package/dist/sol/dex/raydium/types.d.ts +240 -0
  81. package/dist/sol/dex/raydium/types.js +5 -0
  82. package/dist/sol/index.d.ts +10 -0
  83. package/dist/sol/index.js +16 -0
  84. package/dist/sol/jito/bundle.d.ts +90 -0
  85. package/dist/sol/jito/bundle.js +263 -0
  86. package/dist/sol/jito/index.d.ts +7 -0
  87. package/dist/sol/jito/index.js +7 -0
  88. package/dist/sol/jito/tip.d.ts +51 -0
  89. package/dist/sol/jito/tip.js +83 -0
  90. package/dist/sol/jito/types.d.ts +100 -0
  91. package/dist/sol/jito/types.js +5 -0
  92. package/dist/sol/token/create-complete.d.ts +115 -0
  93. package/dist/sol/token/create-complete.js +235 -0
  94. package/dist/sol/token/create-token.d.ts +57 -0
  95. package/dist/sol/token/create-token.js +230 -0
  96. package/dist/sol/token/index.d.ts +9 -0
  97. package/dist/sol/token/index.js +14 -0
  98. package/dist/sol/token/metadata-upload.d.ts +86 -0
  99. package/dist/sol/token/metadata-upload.js +173 -0
  100. package/dist/sol/token/metadata.d.ts +92 -0
  101. package/dist/sol/token/metadata.js +274 -0
  102. package/dist/sol/token/types.d.ts +153 -0
  103. package/dist/sol/token/types.js +5 -0
  104. package/dist/sol/types.d.ts +176 -0
  105. package/dist/sol/types.js +7 -0
  106. package/dist/sol/utils/balance.d.ts +160 -0
  107. package/dist/sol/utils/balance.js +638 -0
  108. package/dist/sol/utils/connection.d.ts +78 -0
  109. package/dist/sol/utils/connection.js +168 -0
  110. package/dist/sol/utils/index.d.ts +9 -0
  111. package/dist/sol/utils/index.js +9 -0
  112. package/dist/sol/utils/lp-inspect.d.ts +129 -0
  113. package/dist/sol/utils/lp-inspect.js +521 -0
  114. package/dist/sol/utils/transfer.d.ts +125 -0
  115. package/dist/sol/utils/transfer.js +220 -0
  116. package/dist/sol/utils/wallet.d.ts +107 -0
  117. package/dist/sol/utils/wallet.js +210 -0
  118. package/dist/utils/erc20.d.ts +2 -108
  119. package/dist/utils/erc20.js +17 -65
  120. package/package.json +39 -4
  121. package/dist/flap/portal-bundle-merkle/encryption.d.ts +0 -16
  122. package/dist/flap/portal-bundle-merkle/encryption.js +0 -146
@@ -151,6 +151,7 @@ export class BlockRazorClient {
151
151
  const gasPrice = tx.gasPrice || 0n;
152
152
  const minGasPrice = ethers.parseUnits(String(MIN_GAS_PRICE_GWEI), 'gwei');
153
153
  if (gasPrice < minGasPrice) {
154
+ console.warn(`⚠️ 交易 Gas Price (${ethers.formatUnits(gasPrice, 'gwei')} Gwei) 低于最低要求 (${MIN_GAS_PRICE_GWEI} Gwei)`);
154
155
  }
155
156
  }
156
157
  catch {
@@ -412,8 +412,7 @@ async function resolveSellOutputs(params) {
412
412
  try {
413
413
  const result = await trySell('BSC', rpcUrl, tokenAddress, amountsWei[0]);
414
414
  const quotedOutputs = [result.funds];
415
- // ✅ 已移除滑点保护:minOuts 固定为 0
416
- const minOuts = [0n];
415
+ const minOuts = quotedOutputs.map((quote) => (quote * 95n) / 100n);
417
416
  return { minOuts, quotedOutputs };
418
417
  }
419
418
  catch {
@@ -446,8 +445,7 @@ async function resolveSellOutputs(params) {
446
445
  }
447
446
  return 0n;
448
447
  });
449
- // ✅ 已移除滑点保护:minOuts 固定为 0
450
- const minOuts = quotedOutputs.map(() => 0n);
448
+ const minOuts = quotedOutputs.map((quote) => (quote * 95n) / 100n);
451
449
  return { minOuts, quotedOutputs };
452
450
  }
453
451
  catch {
@@ -461,8 +459,7 @@ async function resolveSellOutputs(params) {
461
459
  return 0n;
462
460
  }
463
461
  }));
464
- // ✅ 已移除滑点保护:minOuts 固定为 0
465
- const minOuts = quotedOutputs.map(() => 0n);
462
+ const minOuts = quotedOutputs.map((quote) => (quote * 95n) / 100n);
466
463
  return { minOuts, quotedOutputs };
467
464
  }
468
465
  }
@@ -429,40 +429,48 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
429
429
  if (needApprovalIndexes.length > 0) {
430
430
  throw new Error(`需要授权: ${needApprovalIndexes.length} 个钱包尚未授权。请先完成授权后再卖出。`);
431
431
  }
432
- // ✅ 获取报价(用于计算利润),已移除滑点保护
432
+ // ✅ 自动获取报价或使用用户提供的 minOutputAmounts
433
+ let minOuts;
433
434
  let quotedOutputs;
434
- // 使用 Multicall3 批量获取报价(仅 V2 路由支持)
435
- if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
436
- quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
437
- }
438
- else if (routeType === 'v3-single') {
439
- // V3 单跳:并行调用(V3 Quoter 是 non-view 函数,不支持 Multicall3)
440
- quotedOutputs = await Promise.all(amountsWei.map(async (amount) => {
441
- try {
442
- const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, PANCAKE_V3_QUOTER_ABI, provider);
443
- const result = await quoter.quoteExactInputSingle.staticCall({
444
- tokenIn: tokenAddress,
445
- tokenOut: params.v3TokenOut,
446
- amountIn: amount,
447
- fee: params.v3Fee,
448
- sqrtPriceLimitX96: 0
449
- });
450
- return result[0];
451
- }
452
- catch {
453
- return 0n;
454
- }
455
- }));
456
- }
457
- else if (routeType === 'v3-multi' && params.v2Path && params.v2Path.length >= 2) {
458
- // V3 多跳:使用 V2 备选路由
459
- quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
435
+ if (params.minOutputAmounts && params.minOutputAmounts.length === sellers.length) {
436
+ // 用户提供了 minOutputAmounts,跳过报价查询
437
+ minOuts = params.minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseEther(m) : m);
438
+ quotedOutputs = minOuts.map(m => m * 100n / 95n); // 反推预期收益
460
439
  }
461
440
  else {
462
- quotedOutputs = new Array(amountsWei.length).fill(0n);
441
+ // 使用 Multicall3 批量获取报价(仅 V2 路由支持)
442
+ if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
443
+ quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
444
+ }
445
+ else if (routeType === 'v3-single') {
446
+ // V3 单跳:并行调用(V3 Quoter 是 non-view 函数,不支持 Multicall3)
447
+ quotedOutputs = await Promise.all(amountsWei.map(async (amount) => {
448
+ try {
449
+ const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, PANCAKE_V3_QUOTER_ABI, provider);
450
+ const result = await quoter.quoteExactInputSingle.staticCall({
451
+ tokenIn: tokenAddress,
452
+ tokenOut: params.v3TokenOut,
453
+ amountIn: amount,
454
+ fee: params.v3Fee,
455
+ sqrtPriceLimitX96: 0
456
+ });
457
+ return result[0];
458
+ }
459
+ catch {
460
+ return 0n;
461
+ }
462
+ }));
463
+ }
464
+ else if (routeType === 'v3-multi' && params.v2Path && params.v2Path.length >= 2) {
465
+ // V3 多跳:使用 V2 备选路由
466
+ quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
467
+ }
468
+ else {
469
+ quotedOutputs = new Array(amountsWei.length).fill(0n);
470
+ }
471
+ // ✅ minOuts = 0,不设置滑点限制(大额交易更稳定)
472
+ minOuts = quotedOutputs.map(() => 0n);
463
473
  }
464
- // ✅ 已移除滑点保护:minOuts 固定为 0
465
- const minOuts = new Array(sellers.length).fill(0n);
466
474
  // ✅ 计算利润并找出收益最多的钱包
467
475
  let totalProfit = 0n;
468
476
  let maxRevenueIndex = 0;
@@ -7,6 +7,7 @@ import { CommonBundleConfig } from '../../utils/bundle-helpers.js';
7
7
  import { FourSignConfig } from './types.js';
8
8
  export interface FourBuyFirstSignConfig extends FourSignConfig {
9
9
  reserveGasBNB?: number;
10
+ slippageBps?: number;
10
11
  }
11
12
  export interface FourBuyFirstConfig extends CommonBundleConfig {
12
13
  apiKey: string;
@@ -82,8 +82,8 @@ export async function fourBundleBuyFirstMerkle(params) {
82
82
  // ✅ 优化:第三批并行 - trySell、构建交易、获取 nonces
83
83
  const tmBuyer = new Contract(TM_ADDRESS, TM_ABI, buyer);
84
84
  const tmSeller = new Contract(TM_ADDRESS, TM_ABI, seller);
85
- // 已移除滑点保护:minBuyAmount 固定为 0
86
- const minBuyAmount = 0n;
85
+ const slippageBps = config.slippageBps ?? 100;
86
+ const minBuyAmount = (estimatedTokenAmount * BigInt(10000 - slippageBps)) / 10000n;
87
87
  // 预先规划 nonces
88
88
  const extractProfit = true;
89
89
  const profitRateBps = getProfitRateBps();
@@ -121,8 +121,7 @@ export async function fourBundleBuyFirstMerkle(params) {
121
121
  ]);
122
122
  const { buyerNonces, sellerNonces } = noncesResult;
123
123
  const estimatedSellFunds = sellResult.funds;
124
- // ✅ 已移除滑点保护:minSellFunds 固定为 0
125
- const minSellFunds = 0n;
124
+ const minSellFunds = (estimatedSellFunds * BigInt(10000 - slippageBps)) / 10000n;
126
125
  const profitAmount = extractProfit ? (estimatedSellFunds * BigInt(profitRateBps)) / 10000n : 0n;
127
126
  // 更新卖出交易的 minSellFunds
128
127
  sellUnsigned.data = tmSeller.interface.encodeFunctionData('sellToken', [
@@ -29,6 +29,7 @@ export interface FourBundleSwapSignParams {
29
29
  sellPercentage?: number;
30
30
  buyerPrivateKey: string;
31
31
  tokenAddress: string;
32
+ slippageTolerance?: number;
32
33
  config: FourSwapSignConfig;
33
34
  }
34
35
  export interface FourBundleSwapParams {
@@ -37,6 +38,7 @@ export interface FourBundleSwapParams {
37
38
  sellPercentage?: number;
38
39
  buyerPrivateKey: string;
39
40
  tokenAddress: string;
41
+ slippageTolerance?: number;
40
42
  config: FourSwapConfig;
41
43
  }
42
44
  /**
@@ -67,6 +69,7 @@ export interface FourBatchSwapSignParams {
67
69
  buyerPrivateKeys: string[];
68
70
  buyerRatios?: number[];
69
71
  tokenAddress: string;
72
+ slippageTolerance?: number;
70
73
  config: FourSwapSignConfig;
71
74
  }
72
75
  /**
@@ -12,7 +12,7 @@ import { TM_ABI, HELPER3_ABI, TM_ADDRESS } from './swap-internal.js';
12
12
  * Four内盘捆绑换手
13
13
  */
14
14
  export async function fourBundleSwapMerkle(params) {
15
- const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, config } = params;
15
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, slippageTolerance = 0.5, config } = params;
16
16
  const chainIdNum = config.chainId ?? 56;
17
17
  const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
18
18
  chainId: chainIdNum,
@@ -185,7 +185,7 @@ export async function fourBundleSwapMerkle(params) {
185
185
  * 限制:最多 24 个买方(服务器限制 25 笔交易,包含 1 笔利润交易)
186
186
  */
187
187
  export async function fourBatchSwapMerkle(params) {
188
- const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, tokenAddress, config } = params;
188
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, tokenAddress, slippageTolerance = 0.5, config } = params;
189
189
  // ✅ 校验买方数量(最多 24 个)
190
190
  const MAX_BUYERS = 24;
191
191
  if (buyerPrivateKeys.length === 0) {
@@ -628,8 +628,12 @@ async function quoteSellOutputsWithQuote(portal, tokenAddress, amountsWei, outpu
628
628
  }));
629
629
  }
630
630
  }
631
- function resolveMinOutputs(_provided, walletCount, _quotedOutputs) {
632
- // 已移除滑点保护:minOutput 固定为 0
631
+ function resolveMinOutputs(provided, walletCount, _quotedOutputs) {
632
+ if (provided && provided.length === walletCount) {
633
+ return provided.map(m => typeof m === 'string' ? ethers.parseEther(m) : BigInt(m));
634
+ }
635
+ // ✅ 默认 minOutput = 0,不设置滑点限制
636
+ // 原因:大额交易时 5% 滑点可能不够,导致交易失败
633
637
  return Array(walletCount).fill(0n);
634
638
  }
635
639
  // ✅ appendSellProfitTransaction 已内联到 batchSellWithBundleMerkle 中,避免 nonce 竞争问题
@@ -56,27 +56,16 @@ function getGasLimit(config, defaultGas = DEFAULT_GAS_LIMIT) {
56
56
  return BigInt(calculatedGas);
57
57
  }
58
58
  /**
59
- * 查询代币 decimals(带缓存)
60
- * ✅ 代币精度不会变化,缓存后永久有效
59
+ * 查询代币 decimals
61
60
  */
62
61
  async function getTokenDecimals(tokenAddress, provider) {
63
- const cacheKey = tokenAddress.toLowerCase();
64
- // ✅ 检查缓存
65
- const cached = tokenDecimalsCache.get(cacheKey);
66
- if (cached !== undefined) {
67
- return cached;
68
- }
69
62
  try {
70
63
  const token = new Contract(tokenAddress, ERC20_ABI, provider);
71
64
  const decimals = await token.decimals();
72
- const result = Number(decimals);
73
- // ✅ 缓存结果
74
- tokenDecimalsCache.set(cacheKey, result);
75
- return result;
65
+ return Number(decimals);
76
66
  }
77
67
  catch {
78
- // 默认返回 18,兼容大部分 ERC20(也缓存)
79
- tokenDecimalsCache.set(cacheKey, 18);
68
+ // 默认返回 18,兼容大部分 ERC20
80
69
  return 18;
81
70
  }
82
71
  }
@@ -292,21 +281,11 @@ export async function pancakeProxyBatchBuyMerkle(params) {
292
281
  const divisor = BigInt(10 ** decimalsDiff);
293
282
  actualAmountsWei = remainingAmounts.map(amount => amount / divisor);
294
283
  }
295
- // ✅ 优化:如果前端传入了 gasPricenonces,跳过 RPC 调用
296
- const presetGasPrice = config.gasPrice;
297
- const presetNonces = config.nonces;
298
- // ✅ 只获取必需的数据(跳过已有的)
284
+ // ✅ 优化:第一批并行 - gasPrice、tokenDecimals、nonces(JSON-RPC 批量请求)
299
285
  const [gasPrice, tokenDecimals, nonces] = await Promise.all([
300
- // gasPrice:优先使用前端传入的
301
- presetGasPrice !== undefined
302
- ? Promise.resolve(presetGasPrice)
303
- : getOptimizedGasPrice(provider, getGasPriceConfig(config)),
304
- // tokenDecimals:有缓存
286
+ getOptimizedGasPrice(provider, getGasPriceConfig(config)),
305
287
  getTokenDecimals(tokenAddress, provider),
306
- // nonces:优先使用前端传入的
307
- presetNonces && presetNonces.length === buyers.length
308
- ? Promise.resolve(presetNonces)
309
- : allocateProfitAwareNonces(buyers, shouldExtractProfitForBuy, maxFundsIndex, nativeProfitAmount, nonceManager)
288
+ allocateProfitAwareNonces(buyers, shouldExtractProfitForBuy, maxFundsIndex, nativeProfitAmount, nonceManager)
310
289
  ]);
311
290
  const minOuts = resolveBuyMinOutputs(params, buyers.length, tokenDecimals);
312
291
  const needBNB = needSendBNB(routeType, params, useNativeToken);
@@ -371,18 +350,10 @@ export async function pancakeProxyBatchSellMerkle(params) {
371
350
  const finalGasLimit = getGasLimit(config);
372
351
  const extractProfit = shouldExtractProfit(config);
373
352
  const nonceManager = new NonceManager(provider);
374
- // ✅ 优化:如果前端传入了 gasPrice 和 nonces,跳过 RPC 调用
375
- const presetGasPrice = config.gasPrice;
376
- const presetNonces = config.nonces;
377
353
  // ✅ 优化:第一批并行 - gasPrice、tokenDecimals、allowances
378
354
  const [gasPrice, tokenDecimals] = await Promise.all([
379
- // gasPrice:优先使用前端传入的
380
- presetGasPrice !== undefined
381
- ? Promise.resolve(presetGasPrice)
382
- : getOptimizedGasPrice(provider, getGasPriceConfig(config)),
383
- // tokenDecimals:有缓存
355
+ getOptimizedGasPrice(provider, getGasPriceConfig(config)),
384
356
  getTokenDecimals(tokenAddress, provider),
385
- // allowances:必须检查
386
357
  ensureAllowances({
387
358
  provider,
388
359
  tokenAddress,
@@ -391,8 +362,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
391
362
  })
392
363
  ]);
393
364
  const amountsWei = sellAmounts.map(amount => ethers.parseUnits(amount, tokenDecimals));
365
+ // ✅ 优化:第二批并行 - resolveSellOutputs 和 buildSellTransactions
394
366
  const proxies = createPancakeProxies(sellers, ADDRESSES.BSC.PancakeProxy);
395
- // 获取报价(用于计算利润)
396
367
  const { minOuts, quotedOutputs } = await resolveSellOutputs({
397
368
  params,
398
369
  provider,
@@ -421,12 +392,7 @@ export async function pancakeProxyBatchSellMerkle(params) {
421
392
  const needProfitTx = extractProfit && totalProfit > 0n && maxRevenueIndex >= 0;
422
393
  let nonces;
423
394
  let profitNonce;
424
- // 优化:如果前端传入了 nonces,直接使用
425
- if (presetNonces && presetNonces.length === sellers.length) {
426
- nonces = presetNonces;
427
- profitNonce = needProfitTx ? presetNonces[maxRevenueIndex] + 1 : undefined;
428
- }
429
- else if (needProfitTx) {
395
+ if (needProfitTx) {
430
396
  // maxRevenueIndex 钱包需要 2 个连续 nonce(卖出 + 利润)
431
397
  const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], 2);
432
398
  // 其他钱包各需要 1 个 nonce
@@ -495,8 +461,6 @@ export async function pancakeProxyBatchSellMerkle(params) {
495
461
  // ✅ Provider 缓存(复用连接,减少初始化开销)
496
462
  const providerCache = new Map();
497
463
  const PROVIDER_CACHE_TTL_MS = 60 * 1000; // 60秒缓存
498
- // ✅ Token Decimals 缓存(代币精度不会变化)
499
- const tokenDecimalsCache = new Map();
500
464
  function createChainContext(chain, rpcUrl) {
501
465
  const chainId = CHAIN_ID_MAP[chain];
502
466
  const cacheKey = `${chain}-${rpcUrl}`;
@@ -528,8 +492,10 @@ function findMaxAmountIndex(amounts) {
528
492
  }
529
493
  return maxIndex;
530
494
  }
531
- function resolveBuyMinOutputs(_params, walletCount, _tokenDecimals) {
532
- // 已移除滑点保护:minOutput 固定为 0
495
+ function resolveBuyMinOutputs(params, walletCount, tokenDecimals) {
496
+ if (params.minOutputAmounts && params.minOutputAmounts.length === walletCount) {
497
+ return params.minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseUnits(m, tokenDecimals) : m);
498
+ }
533
499
  return new Array(walletCount).fill(0n);
534
500
  }
535
501
  function createPancakeProxies(wallets, proxyAddress) {
@@ -610,18 +576,26 @@ async function ensureAllowances({ provider, tokenAddress, owners, spender }) {
610
576
  }
611
577
  }
612
578
  /**
613
- * ✅ 获取卖出报价(用于计算利润,不用于滑点保护)
614
- * ✅ 已移除滑点保护:minOutput 固定为 0
579
+ * ✅ 使用 Multicall3 批量获取卖出报价(单次 RPC)
615
580
  */
616
581
  async function resolveSellOutputs({ params, provider, tokenAddress, routeType, amountsWei }) {
617
- // 已移除滑点保护:minOutput 固定为 0
618
- const minOuts = new Array(amountsWei.length).fill(0n);
582
+ // 如果已提供 minOutputAmounts,直接使用,跳过报价查询
583
+ if (params.minOutputAmounts && params.minOutputAmounts.length === amountsWei.length) {
584
+ const minOuts = params.minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseEther(m) : m);
585
+ return {
586
+ minOuts,
587
+ quotedOutputs: minOuts.map(m => m * 100n / 95n)
588
+ };
589
+ }
619
590
  // 如果只有 1 个,直接调用(避免 multicall 开销)
620
591
  if (amountsWei.length === 1) {
621
592
  const quotedOutput = await getSingleQuote(params, provider, tokenAddress, routeType, amountsWei[0]);
622
- return { quotedOutputs: [quotedOutput], minOuts };
593
+ return {
594
+ quotedOutputs: [quotedOutput],
595
+ minOuts: [0n] // ✅ minOutput = 0,不设置滑点限制
596
+ };
623
597
  }
624
- // ✅ 使用 Multicall3 批量获取报价(仅 V2 路由支持,用于计算利润)
598
+ // ✅ 使用 Multicall3 批量获取报价(仅 V2 路由支持)
625
599
  if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
626
600
  try {
627
601
  const v2RouterIface = new Interface(PANCAKE_V2_ROUTER_ABI);
@@ -645,7 +619,10 @@ async function resolveSellOutputs({ params, provider, tokenAddress, routeType, a
645
619
  }
646
620
  return 0n;
647
621
  });
648
- return { quotedOutputs, minOuts };
622
+ return {
623
+ quotedOutputs,
624
+ minOuts: quotedOutputs.map(() => 0n) // ✅ minOutput = 0,不设置滑点限制
625
+ };
649
626
  }
650
627
  catch {
651
628
  // Multicall 失败,回退到并行调用
@@ -653,7 +630,10 @@ async function resolveSellOutputs({ params, provider, tokenAddress, routeType, a
653
630
  }
654
631
  // 回退:并行调用(V3 路由或 Multicall 失败时)
655
632
  const quotedOutputs = await Promise.all(amountsWei.map(amount => getSingleQuote(params, provider, tokenAddress, routeType, amount)));
656
- return { quotedOutputs, minOuts };
633
+ return {
634
+ quotedOutputs,
635
+ minOuts: quotedOutputs.map(() => 0n) // ✅ minOutput = 0,不设置滑点限制
636
+ };
657
637
  }
658
638
  /**
659
639
  * 获取单个报价
@@ -8,6 +8,7 @@ import { FlapSignConfig } from './config.js';
8
8
  export type FlapChain = 'bsc' | 'xlayer' | 'base';
9
9
  export interface FlapBuyFirstSignConfig extends FlapSignConfig {
10
10
  reserveGasETH?: number;
11
+ slippageBps?: number;
11
12
  skipQuoteOnError?: boolean;
12
13
  }
13
14
  export interface FlapBuyFirstConfig extends CommonBundleConfig {
@@ -15,6 +16,7 @@ export interface FlapBuyFirstConfig extends CommonBundleConfig {
15
16
  customRpcUrl?: string;
16
17
  bundleBlockOffset?: number;
17
18
  reserveGasETH?: number;
19
+ slippageBps?: number;
18
20
  skipQuoteOnError?: boolean;
19
21
  waitForConfirmation?: boolean;
20
22
  waitTimeoutMs?: number;
@@ -124,11 +124,12 @@ export async function flapBundleBuyFirstMerkle(params) {
124
124
  ]);
125
125
  const { buyerFundsWei, buyerBalance } = buyerFundsResult;
126
126
  const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
127
- // ✅ 获取报价(已移除滑点保护)
127
+ // ✅ 获取报价
128
128
  const quoteResult = await quoteBuyerOutput({
129
129
  portalAddress: chainContext.portalAddress,
130
130
  tokenAddress,
131
131
  buyerFundsWei,
132
+ slippageBps: config.slippageBps,
132
133
  provider: chainContext.provider,
133
134
  skipQuoteOnError: config.skipQuoteOnError,
134
135
  inputToken // ✅ 传递输入代币
@@ -299,31 +300,35 @@ async function calculateBuyerFunds({ buyer, buyerFunds, buyerFundsPercentage, re
299
300
  }
300
301
  return { buyerFundsWei, buyerBalance };
301
302
  }
302
- async function quoteBuyerOutput({ portalAddress, tokenAddress, buyerFundsWei, provider, skipQuoteOnError, inputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
303
+ async function quoteBuyerOutput({ portalAddress, tokenAddress, buyerFundsWei, slippageBps, provider, skipQuoteOnError, inputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
303
304
  }) {
304
305
  let quotedToken = 0n;
306
+ let minOutToken = 0n;
305
307
  const portal = new Contract(portalAddress, PORTAL_ABI, provider);
308
+ const safeSlippage = Math.max(0, Math.min(5000, slippageBps ?? 100));
306
309
  try {
307
310
  quotedToken = await portal.quoteExactInput.staticCall({
308
311
  inputToken, // ✅ 使用动态输入代币
309
312
  outputToken: tokenAddress,
310
313
  inputAmount: buyerFundsWei
311
314
  });
315
+ const keep = BigInt(10000 - safeSlippage);
316
+ minOutToken = (quotedToken * keep) / 10000n;
312
317
  }
313
318
  catch (error) {
314
319
  if (skipQuoteOnError ?? true) {
315
320
  quotedToken = 0n;
321
+ minOutToken = 0n;
316
322
  }
317
323
  else {
318
324
  throw new Error(`买入报价失败: ${error}`);
319
325
  }
320
326
  }
321
- // 已移除滑点保护:minOutToken 固定为 0
322
- const sellAmountWei = quotedToken;
327
+ const sellAmountWei = minOutToken > 0n ? minOutToken : quotedToken;
323
328
  if (sellAmountWei <= 0n) {
324
- throw new Error('卖方卖出数量为 0:报价失败');
329
+ throw new Error('卖方卖出数量为 0:报价失败或滑点过高');
325
330
  }
326
- return { quotedToken, minOutToken: 0n, sellAmountWei };
331
+ return { quotedToken, minOutToken, sellAmountWei };
327
332
  }
328
333
  async function ensureSellerBalance({ tokenAddress, provider, seller, sellAmountWei, skipBalanceCheck }) {
329
334
  const erc20 = new Contract(tokenAddress, ERC20_BALANCE_ABI, provider);
@@ -13,6 +13,7 @@ export interface FlapSwapSignConfig {
13
13
  txType?: 0 | 2;
14
14
  chainId?: number;
15
15
  reserveGasETH?: number;
16
+ slippageBps?: number;
16
17
  skipQuoteOnError?: boolean;
17
18
  skipApprovalCheck?: boolean;
18
19
  }
@@ -22,6 +23,7 @@ export interface FlapSwapConfig extends CommonBundleConfig {
22
23
  customRpcUrl?: string;
23
24
  bundleBlockOffset?: number;
24
25
  reserveGasETH?: number;
26
+ slippageBps?: number;
25
27
  skipQuoteOnError?: boolean;
26
28
  waitForConfirmation?: boolean;
27
29
  waitTimeoutMs?: number;
@@ -116,7 +116,7 @@ export async function flapBundleSwapMerkle(params) {
116
116
  ]);
117
117
  const { amount: sellAmountWei, decimals } = sellAmountResult;
118
118
  const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
119
- // ✅ 优化:第二批并行 - approval、quote(已移除滑点保护)
119
+ // ✅ 优化:第二批并行 - approval、quote
120
120
  const [approvalTx, quote] = await Promise.all([
121
121
  config.skipApprovalCheck
122
122
  ? Promise.resolve(null)
@@ -134,6 +134,7 @@ export async function flapBundleSwapMerkle(params) {
134
134
  tokenAddress,
135
135
  sellAmountWei,
136
136
  provider: chainContext.provider,
137
+ slippageBps: config.slippageBps,
137
138
  skipQuoteOnError: config.skipQuoteOnError,
138
139
  outputToken // ✅ 传递输出代币
139
140
  })
@@ -143,6 +144,7 @@ export async function flapBundleSwapMerkle(params) {
143
144
  buyer,
144
145
  quotedNative: quote.quotedNative,
145
146
  reserveGasEth: config.reserveGasETH,
147
+ slippageBps: config.slippageBps,
146
148
  nativeToken: chainContext.nativeToken,
147
149
  useNativeToken,
148
150
  quoteToken,
@@ -299,28 +301,32 @@ async function buildApprovalTransaction({ tokenAddress, seller, provider, decima
299
301
  type: txType
300
302
  });
301
303
  }
302
- async function quoteSellOutput({ portalAddress, tokenAddress, sellAmountWei, provider, skipQuoteOnError, outputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
304
+ async function quoteSellOutput({ portalAddress, tokenAddress, sellAmountWei, provider, slippageBps, skipQuoteOnError, outputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
303
305
  }) {
304
306
  const portal = new Contract(portalAddress, PORTAL_ABI, provider);
307
+ const safeSlippage = Math.max(0, Math.min(5000, slippageBps ?? 100));
305
308
  try {
306
309
  const quotedNative = await portal.quoteExactInput.staticCall({
307
310
  inputToken: tokenAddress,
308
311
  outputToken, // ✅ 使用动态输出代币
309
312
  inputAmount: sellAmountWei
310
313
  });
311
- // 已移除滑点保护:minOutNative 固定为 0
312
- return { quotedNative, minOutNative: 0n };
314
+ const keep = BigInt(10000 - safeSlippage);
315
+ const minOutNative = (quotedNative * keep) / 10000n;
316
+ return { quotedNative, minOutNative };
313
317
  }
314
318
  catch (err) {
315
319
  if (skipQuoteOnError ?? true) {
320
+ console.warn(`⚠️ 报价失败,使用 minOut = 0: ${err}`);
316
321
  return { quotedNative: 0n, minOutNative: 0n };
317
322
  }
318
323
  throw new Error(`卖出报价失败: ${err}`);
319
324
  }
320
325
  }
321
326
  const ERC20_BALANCE_OF_ABI = ['function balanceOf(address) view returns (uint256)'];
322
- async function calculateBuyerNeed({ buyer, quotedNative, reserveGasEth, nativeToken, useNativeToken = true, quoteToken, quoteTokenDecimals = 18, provider }) {
327
+ async function calculateBuyerNeed({ buyer, quotedNative, reserveGasEth, slippageBps, nativeToken, useNativeToken = true, quoteToken, quoteTokenDecimals = 18, provider }) {
323
328
  const reserveGas = ethers.parseEther((reserveGasEth || 0.0005).toString());
329
+ const safeSlippage = Math.max(0, Math.min(5000, slippageBps ?? 100));
324
330
  // ✅ 根据是否使用原生代币获取不同的余额
325
331
  let buyerBalance;
326
332
  if (useNativeToken) {
@@ -331,8 +337,11 @@ async function calculateBuyerNeed({ buyer, quotedNative, reserveGasEth, nativeTo
331
337
  const erc20 = new Contract(quoteToken, ERC20_BALANCE_OF_ABI, provider || buyer.provider);
332
338
  buyerBalance = await erc20.balanceOf(buyer.address);
333
339
  }
334
- // 已移除滑点保护:直接使用报价金额
335
- const estimatedBuyerNeed = quotedNative;
340
+ let estimatedBuyerNeed = 0n;
341
+ if (quotedNative > 0n) {
342
+ const increase = BigInt(10000 + safeSlippage);
343
+ estimatedBuyerNeed = (quotedNative * increase) / 10000n;
344
+ }
336
345
  // ✅ 原生代币需要预留 Gas,ERC20 不需要
337
346
  const buyerNeedTotal = useNativeToken
338
347
  ? estimatedBuyerNeed + reserveGas
@@ -474,7 +483,7 @@ export async function flapBatchSwapMerkle(params) {
474
483
  ]);
475
484
  const { amount: sellAmountWei, decimals } = sellAmountResult;
476
485
  const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
477
- // ✅ 并行获取:授权、报价(已移除滑点保护)
486
+ // ✅ 并行获取:授权、报价
478
487
  const [approvalTx, quote] = await Promise.all([
479
488
  config.skipApprovalCheck
480
489
  ? Promise.resolve(null)
@@ -492,12 +501,15 @@ export async function flapBatchSwapMerkle(params) {
492
501
  tokenAddress,
493
502
  sellAmountWei,
494
503
  provider: chainContext.provider,
504
+ slippageBps: config.slippageBps,
495
505
  skipQuoteOnError: config.skipQuoteOnError,
496
506
  outputToken
497
507
  })
498
508
  ]);
499
- // ✅ 计算每个买方的买入金额(按比例分配,已移除滑点保护)
500
- const totalBuyAmount = quote.quotedNative;
509
+ // ✅ 计算每个买方的买入金额(按比例分配)
510
+ const safeSlippage = Math.max(0, Math.min(5000, config.slippageBps ?? 100));
511
+ const increase = BigInt(10000 + safeSlippage);
512
+ const totalBuyAmount = (quote.quotedNative * increase) / 10000n;
501
513
  let buyAmountsWei;
502
514
  if (params.buyerRatios && params.buyerRatios.length === buyers.length) {
503
515
  // 按比例分配
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  export * as Abis from './abis/index.js';
2
+ export * as Sol from './sol/index.js';
2
3
  export { ADDRESSES, CHAIN } from './utils/constants.js';
3
4
  export { isExclusiveOnChain, isExclusiveOffChain } from './utils/mpcExclusive.js';
4
- export { ensureSellApprovalV1, checkSellApprovalV1, ensureSellApprovalV2, checkSellApprovalV2, ensureSellApproval, checkSellApproval, ensureFlapSellApproval, checkFlapSellApproval, ensureFlapSellApprovalBatch, checkFlapSellApprovalBatch, checkAllowance, approveToken, checkAllowanceBatch, approveTokenBatch, checkAllowanceRaw, approveTokenRaw, checkAllowanceBatchRaw, approveTokenBatchRaw, type EnsureAllowanceBatchItemResult, type ApproveTokenBatchParams, type ApproveTokenBatchRawParams, type ApproveTokenBatchResult, type ApproveTokenBatchSignResult } from './utils/erc20.js';
5
+ export { ensureSellApprovalV1, checkSellApprovalV1, ensureSellApprovalV2, checkSellApprovalV2, ensureSellApproval, checkSellApproval, ensureFlapSellApproval, checkFlapSellApproval, ensureFlapSellApprovalBatch, checkFlapSellApprovalBatch, checkAllowance, approveToken, checkAllowanceBatch, approveTokenBatch, checkAllowanceRaw, approveTokenRaw, checkAllowanceBatchRaw, approveTokenBatchRaw, type EnsureAllowanceBatchItemResult, type ApproveTokenBatchParams, type ApproveTokenBatchRawParams, type ApproveTokenBatchResult } from './utils/erc20.js';
5
6
  export { parseFourError, type FourErrorCode } from './utils/errors.js';
6
7
  export { getTokenManagerV1, getTokenManagerV2, getTokenManagerHelper3, getTokenManagerV1Writer, getTokenManagerV2Writer, getTokenManagerHelper3Writer, getTokenManagerAddress, type ChainName } from './utils/contract-factory.js';
7
8
  export { FourClient, buildLoginMessage, type FourConfig, type GenerateNonceReq, type LoginReq, type CreateTokenReq, type CreateTokenResp } from './clients/four.js';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * as Abis from './abis/index.js';
2
+ export * as Sol from './sol/index.js';
2
3
  export { ADDRESSES, CHAIN } from './utils/constants.js';
3
4
  export { isExclusiveOnChain, isExclusiveOffChain } from './utils/mpcExclusive.js';
4
5
  export {
@@ -13,6 +13,7 @@ export interface PancakeBuyFirstSignConfig {
13
13
  txType?: 0 | 2;
14
14
  chainId?: number;
15
15
  reserveGasBNB?: number;
16
+ slippageBps?: number;
16
17
  skipQuoteOnError?: boolean;
17
18
  skipApprovalCheck?: boolean;
18
19
  }
@@ -98,7 +98,8 @@ export async function pancakeBundleBuyFirstMerkle(params) {
98
98
  const buyerNeed = calculateBuyerNeed({
99
99
  quotedNative,
100
100
  buyerBalance: buyerFundsInfo.buyerBalance,
101
- reserveGas: buyerFundsInfo.reserveGas
101
+ reserveGas: buyerFundsInfo.reserveGas,
102
+ slippageBps: config.slippageBps
102
103
  });
103
104
  const finalGasLimit = getGasLimit(config);
104
105
  const gasPrice = await getGasPrice(context.provider, config);
@@ -302,9 +303,13 @@ async function quoteSellerNative({ provider, tokenAddress, sellAmountToken }) {
302
303
  return 0n;
303
304
  }
304
305
  }
305
- function calculateBuyerNeed({ quotedNative, buyerBalance, reserveGas }) {
306
- // 已移除滑点保护:直接使用报价金额
307
- const estimatedBuyerNeed = quotedNative;
306
+ function calculateBuyerNeed({ quotedNative, buyerBalance, reserveGas, slippageBps }) {
307
+ let estimatedBuyerNeed = 0n;
308
+ if (quotedNative > 0n) {
309
+ const safeSlippage = Math.max(0, Math.min(5000, slippageBps ?? 100));
310
+ const increase = BigInt(10000 + safeSlippage);
311
+ estimatedBuyerNeed = (quotedNative * increase) / 10000n;
312
+ }
308
313
  const buyerNeedTotal = estimatedBuyerNeed + reserveGas;
309
314
  if (buyerBalance < buyerNeedTotal) {
310
315
  throw new Error(`买方余额不足:\n - 需要: ${ethers.formatEther(buyerNeedTotal)} BNB\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`);