four-flap-meme-sdk 1.4.11 → 1.4.12

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.
@@ -33,144 +33,52 @@ async function ensureSellerApproval({ tokenAddress, seller, provider, decimals,
33
33
  });
34
34
  }
35
35
  async function quoteSellOutput({ routeParams, sellAmountWei, provider }) {
36
- const tokenIn = routeParams.routeType === 'v2'
37
- ? routeParams.v2Path[0]
38
- : routeParams.v3TokenIn;
39
- const tokenInLower = tokenIn.toLowerCase();
36
+ console.log(`[quoteSellOutput] 开始报价, routeType=${routeParams.routeType}, sellAmount=${sellAmountWei} wei`);
40
37
  // ==================== V2 报价 ====================
41
38
  if (routeParams.routeType === 'v2') {
42
39
  const { v2Path } = routeParams;
43
- const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, provider);
44
- // V2 策略 1:直接路径
45
- try {
46
- const amounts = await v2Router.getAmountsOut(sellAmountWei, v2Path);
47
- const amountOut = amounts[amounts.length - 1];
48
- if (amountOut > 0n) {
49
- console.log(`[quoteSellOutput] V2 直接路径成功: ${ethers.formatEther(amountOut)} BNB`);
50
- return { estimatedBNBOut: amountOut };
51
- }
52
- }
53
- catch (err) {
54
- console.log(`[quoteSellOutput] V2 直接路径失败: ${String(err).slice(0, 100)}`);
55
- }
56
- // V2 策略 2:多跳路径 (代币 → 稳定币 → WBNB)
57
- for (const stableCoin of STABLE_COINS) {
58
- if (tokenInLower === stableCoin.toLowerCase())
59
- continue;
60
- try {
61
- const multiHopPath = [tokenIn, stableCoin, WBNB_ADDRESS];
62
- const amounts = await v2Router.getAmountsOut(sellAmountWei, multiHopPath);
63
- const amountOut = amounts[amounts.length - 1];
64
- if (amountOut > 0n) {
65
- console.log(`[quoteSellOutput] V2 多跳路径成功 (via ${stableCoin.slice(0, 10)}...): ${ethers.formatEther(amountOut)} BNB`);
66
- return { estimatedBNBOut: amountOut };
67
- }
68
- }
69
- catch (err) {
70
- continue;
71
- }
40
+ const tokenIn = v2Path[0];
41
+ const tokenOut = v2Path[v2Path.length - 1];
42
+ console.log(`[quoteSellOutput] V2 路径: ${v2Path.join(' → ')}`);
43
+ // 只用 V2 报价
44
+ const result = await quoteV2(provider, tokenIn, tokenOut, sellAmountWei, 'BSC');
45
+ if (result.amountOut > 0n) {
46
+ console.log(`[quoteSellOutput] V2 报价成功: ${ethers.formatEther(result.amountOut)} BNB`);
47
+ return { estimatedBNBOut: result.amountOut };
72
48
  }
73
49
  throw new Error('V2 报价失败: 所有路径均无效');
74
50
  }
75
- // ==================== V3 报价 ====================
51
+ // ==================== V3 单跳报价 ====================
76
52
  if (routeParams.routeType === 'v3-single') {
77
53
  const params = routeParams;
78
- const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, PANCAKE_V3_QUOTER_ABI, provider);
79
- // 如果指定了 fee,只用指定的 fee;否则尝试所有费率
80
- const feesToTry = params.v3Fee ? [params.v3Fee] : V3_FEE_TIERS;
81
- console.log(`[quoteSellOutput] 开始 V3 报价: tokenIn=${params.v3TokenIn.slice(0, 10)}..., tokenOut=${params.v3TokenOut.slice(0, 10)}..., feesToTry=${feesToTry}`);
82
- // V3 策略 1:直接路径(尝试多个费率)
83
- for (const fee of feesToTry) {
84
- try {
85
- const result = await quoter.quoteExactInputSingle.staticCall({
86
- tokenIn: params.v3TokenIn,
87
- tokenOut: params.v3TokenOut,
88
- amountIn: sellAmountWei,
89
- fee: fee,
90
- sqrtPriceLimitX96: 0n
91
- });
92
- const amountOut = Array.isArray(result) ? result[0] : result;
93
- if (amountOut && amountOut > 0n) {
94
- console.log(`[quoteSellOutput] V3 直接路径成功 (fee=${fee}): ${ethers.formatEther(amountOut)} BNB`);
95
- return { estimatedBNBOut: amountOut };
96
- }
97
- }
98
- catch (err) {
99
- console.log(`[quoteSellOutput] V3 直接路径失败 (fee=${fee}): ${String(err).slice(0, 100)}`);
100
- continue;
101
- }
102
- }
103
- // V3 策略 2:多跳路径 (代币 → 稳定币 → WBNB)
104
- for (const stableCoin of STABLE_COINS) {
105
- if (tokenInLower === stableCoin.toLowerCase())
106
- continue;
107
- for (const fee1 of feesToTry) {
108
- try {
109
- // 第一跳:代币 → 稳定币
110
- const midResult = await quoter.quoteExactInputSingle.staticCall({
111
- tokenIn: params.v3TokenIn,
112
- tokenOut: stableCoin,
113
- amountIn: sellAmountWei,
114
- fee: fee1,
115
- sqrtPriceLimitX96: 0n
116
- });
117
- const midAmount = Array.isArray(midResult) ? midResult[0] : midResult;
118
- if (!midAmount || midAmount <= 0n)
119
- continue;
120
- console.log(`[quoteSellOutput] V3 第一跳成功: 代币 → ${stableCoin.slice(0, 10)}... = ${midAmount}`);
121
- // 第二跳:稳定币 → WBNB(尝试多个费率)
122
- const stableFees = [100, 500, 2500];
123
- for (const fee2 of stableFees) {
124
- try {
125
- const finalResult = await quoter.quoteExactInputSingle.staticCall({
126
- tokenIn: stableCoin,
127
- tokenOut: WBNB_ADDRESS,
128
- amountIn: midAmount,
129
- fee: fee2,
130
- sqrtPriceLimitX96: 0n
131
- });
132
- const amountOut = Array.isArray(finalResult) ? finalResult[0] : finalResult;
133
- if (amountOut && amountOut > 0n) {
134
- console.log(`[quoteSellOutput] V3 多跳路径成功 (${fee1}→${fee2}): ${ethers.formatEther(amountOut)} BNB`);
135
- return { estimatedBNBOut: amountOut };
136
- }
137
- }
138
- catch {
139
- continue;
140
- }
141
- }
142
- }
143
- catch (err) {
144
- console.log(`[quoteSellOutput] V3 第一跳失败 (fee=${fee1}): ${String(err).slice(0, 100)}`);
145
- continue;
146
- }
147
- }
54
+ console.log(`[quoteSellOutput] V3 单跳: ${params.v3TokenIn} ${params.v3TokenOut}, fee=${params.v3Fee}`);
55
+ // 只用 V3 报价,不 fallback 到 V2
56
+ const result = await quoteV3(provider, params.v3TokenIn, params.v3TokenOut, sellAmountWei, 'BSC', params.v3Fee);
57
+ if (result.amountOut > 0n) {
58
+ console.log(`[quoteSellOutput] V3 报价成功 (fee=${result.fee}): ${ethers.formatEther(result.amountOut)} BNB`);
59
+ return { estimatedBNBOut: result.amountOut };
148
60
  }
149
- // V3 全部失败,尝试 V2 fallback
150
- if (params.v2Path && params.v2Path.length >= 2) {
151
- try {
152
- const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, provider);
153
- const amounts = await v2Router.getAmountsOut(sellAmountWei, params.v2Path);
154
- const amountOut = amounts[amounts.length - 1];
155
- if (amountOut > 0n) {
156
- console.log(`[quoteSellOutput] V3→V2 fallback 成功: ${ethers.formatEther(amountOut)} BNB`);
157
- return { estimatedBNBOut: amountOut };
158
- }
159
- }
160
- catch (v2Error) {
161
- console.log(`[quoteSellOutput] V3→V2 fallback 失败: ${String(v2Error).slice(0, 100)}`);
61
+ // V3 报价失败时不再 fallback 到 V2,因为价格可能差异很大
62
+ throw new Error(`V3 单跳报价失败: tokenIn=${params.v3TokenIn}, tokenOut=${params.v3TokenOut}, fee=${params.v3Fee}`);
63
+ }
64
+ // ==================== V3 多跳报价 ====================
65
+ if (routeParams.routeType === 'v3-multi') {
66
+ const params = routeParams;
67
+ console.log(`[quoteSellOutput] V3 多跳: LPs=${params.v3LpAddresses?.join(', ')}`);
68
+ // V3 多跳:只用 V3 报价,不 fallback V2
69
+ if (params.v3LpAddresses && params.v3LpAddresses.length > 0) {
70
+ const tokenIn = params.v3ExactTokenIn;
71
+ const WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
72
+ const result = await quoteV3(provider, tokenIn, WBNB, sellAmountWei, 'BSC');
73
+ if (result.amountOut > 0n) {
74
+ console.log(`[quoteSellOutput] V3 多跳报价成功: ${ethers.formatEther(result.amountOut)} BNB`);
75
+ return { estimatedBNBOut: result.amountOut };
162
76
  }
163
77
  }
164
- throw new Error(`V3 QuoterV2 报价失败 (尝试费率: ${feesToTry.join(', ')}) 且 V2 路径无效`);
78
+ // V3 多跳报价失败时不再 fallback V2,因为价格可能差异很大
79
+ throw new Error(`V3 多跳报价失败: LPs=${params.v3LpAddresses?.join(', ')}, tokenIn=${params.v3ExactTokenIn}`);
165
80
  }
166
- // ==================== V3 多跳 ====================
167
- const params = routeParams;
168
- if (params.v2Path && params.v2Path.length >= 2) {
169
- const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, provider);
170
- const amounts = await v2Router.getAmountsOut(sellAmountWei, params.v2Path);
171
- return { estimatedBNBOut: amounts[amounts.length - 1] };
172
- }
173
- throw new Error('V3 多跳需要提供 v2Path 用于价格预估');
81
+ throw new Error(`不支持的路由类型: ${routeParams.routeType}`);
174
82
  }
175
83
  async function buildSwapTransactions({ routeParams, sellAmountWei, buyAmountBNB, buyer, seller, tokenAddress, useNativeToken = true }) {
176
84
  const proxySeller = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, seller);
@@ -274,20 +182,6 @@ async function planNonces({ seller, buyer, sameAddress, approvalExists, profitNe
274
182
  ]);
275
183
  return { sellerNonce, buyerNonce };
276
184
  }
277
- async function buildProfitTransaction({ wallet, profitAmount, profitNonce, gasPrice, chainId, txType }) {
278
- if (!profitNonce || profitAmount === 0n) {
279
- return null;
280
- }
281
- return await wallet.signTransaction({
282
- to: PROFIT_CONFIG.RECIPIENT,
283
- value: profitAmount,
284
- nonce: profitNonce,
285
- gasPrice,
286
- gasLimit: 21000n,
287
- chainId,
288
- type: txType
289
- });
290
- }
291
185
  function calculateProfitAmount(estimatedBNBOut) {
292
186
  if (estimatedBNBOut <= 0n) {
293
187
  return 0n;
@@ -297,13 +191,7 @@ function calculateProfitAmount(estimatedBNBOut) {
297
191
  /**
298
192
  * ✅ 获取 ERC20 代币 → 原生代币(BNB)的报价
299
193
  * 用于将 ERC20 利润转换为 BNB
300
- *
301
- * @param provider - Provider 实例
302
- * @param tokenAddress - ERC20 代币地址(如 USDT)
303
- * @param tokenAmount - 代币数量(wei)
304
- * @param version - 'v2' | 'v3',指定使用哪个版本的报价
305
- * @param fee - V3 费率档位(仅 V3 时使用)
306
- * @returns 等值的 BNB 数量(wei),失败返回 0n
194
+ * 使用共享的报价函数
307
195
  */
308
196
  async function getERC20ToNativeQuote(provider, tokenAddress, tokenAmount, version = 'v2', fee) {
309
197
  if (tokenAmount <= 0n)
@@ -314,110 +202,23 @@ async function getERC20ToNativeQuote(provider, tokenAddress, tokenAmount, versio
314
202
  if (tokenLower === wbnbLower) {
315
203
  return tokenAmount;
316
204
  }
317
- // ==================== V2 报价 ====================
318
- if (version === 'v2') {
319
- const router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, provider);
320
- // V2 策略 1:直接路径 代币 → WBNB
321
- try {
322
- const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, WBNB_ADDRESS]);
323
- const nativeAmount = amounts[1];
324
- if (nativeAmount > 0n) {
325
- console.log(`[getERC20ToNativeQuote] V2 直接路径成功: ${ethers.formatEther(nativeAmount)} BNB`);
326
- return nativeAmount;
327
- }
328
- }
329
- catch {
330
- // V2 直接路径失败,尝试多跳
331
- }
332
- // V2 策略 2:多跳路径 代币 → 稳定币 → WBNB
333
- for (const stableCoin of STABLE_COINS) {
334
- if (tokenLower === stableCoin.toLowerCase()) {
335
- // 代币本身就是稳定币
336
- try {
337
- const amounts = await router.getAmountsOut(tokenAmount, [stableCoin, WBNB_ADDRESS]);
338
- const nativeAmount = amounts[1];
339
- if (nativeAmount > 0n) {
340
- console.log(`[getERC20ToNativeQuote] V2 稳定币直接路径成功: ${ethers.formatEther(nativeAmount)} BNB`);
341
- return nativeAmount;
342
- }
343
- }
344
- catch {
345
- continue;
346
- }
347
- }
348
- }
349
- console.warn(`[getERC20ToNativeQuote] V2 所有报价路径均失败`);
350
- return 0n;
351
- }
352
- // ==================== V3 报价 ====================
205
+ // 使用共享的报价函数
353
206
  if (version === 'v3') {
354
- const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, PANCAKE_V3_QUOTER_ABI, provider);
355
- const feesToTry = fee ? [fee] : V3_FEE_TIERS;
356
- // V3 策略 1:直接路径 代币 → WBNB
357
- for (const tryFee of feesToTry) {
358
- try {
359
- const result = await quoter.quoteExactInputSingle.staticCall({
360
- tokenIn: tokenAddress,
361
- tokenOut: WBNB_ADDRESS,
362
- amountIn: tokenAmount,
363
- fee: tryFee,
364
- sqrtPriceLimitX96: 0n
365
- });
366
- const amountOut = Array.isArray(result) ? result[0] : result;
367
- if (amountOut && amountOut > 0n) {
368
- console.log(`[getERC20ToNativeQuote] V3 直接路径成功 (fee=${tryFee}): ${ethers.formatEther(amountOut)} BNB`);
369
- return amountOut;
370
- }
371
- }
372
- catch {
373
- continue;
374
- }
207
+ const result = await quoteV3(provider, tokenAddress, WBNB_ADDRESS, tokenAmount, 'BSC', fee);
208
+ if (result.amountOut > 0n) {
209
+ console.log(`[getERC20ToNativeQuote] V3 报价成功: ${ethers.formatEther(result.amountOut)} BNB`);
210
+ return result.amountOut;
375
211
  }
376
- // V3 策略 2:多跳路径 代币 → 稳定币 → WBNB
377
- for (const stableCoin of STABLE_COINS) {
378
- if (tokenLower === stableCoin.toLowerCase())
379
- continue;
380
- for (const fee1 of feesToTry) {
381
- try {
382
- const midResult = await quoter.quoteExactInputSingle.staticCall({
383
- tokenIn: tokenAddress,
384
- tokenOut: stableCoin,
385
- amountIn: tokenAmount,
386
- fee: fee1,
387
- sqrtPriceLimitX96: 0n
388
- });
389
- const midAmount = Array.isArray(midResult) ? midResult[0] : midResult;
390
- if (!midAmount || midAmount <= 0n)
391
- continue;
392
- const stableFees = [100, 500, 2500];
393
- for (const fee2 of stableFees) {
394
- try {
395
- const finalResult = await quoter.quoteExactInputSingle.staticCall({
396
- tokenIn: stableCoin,
397
- tokenOut: WBNB_ADDRESS,
398
- amountIn: midAmount,
399
- fee: fee2,
400
- sqrtPriceLimitX96: 0n
401
- });
402
- const amountOut = Array.isArray(finalResult) ? finalResult[0] : finalResult;
403
- if (amountOut && amountOut > 0n) {
404
- console.log(`[getERC20ToNativeQuote] V3 多跳路径成功 (${fee1}→${fee2}): ${ethers.formatEther(amountOut)} BNB`);
405
- return amountOut;
406
- }
407
- }
408
- catch {
409
- continue;
410
- }
411
- }
412
- }
413
- catch {
414
- continue;
415
- }
416
- }
417
- }
418
- console.warn(`[getERC20ToNativeQuote] V3 所有报价路径均失败`);
212
+ console.warn(`[getERC20ToNativeQuote] V3 报价失败`);
419
213
  return 0n;
420
214
  }
215
+ // V2 报价
216
+ const result = await quoteV2(provider, tokenAddress, WBNB_ADDRESS, tokenAmount, 'BSC');
217
+ if (result.amountOut > 0n) {
218
+ console.log(`[getERC20ToNativeQuote] V2 报价成功: ${ethers.formatEther(result.amountOut)} BNB`);
219
+ return result.amountOut;
220
+ }
221
+ console.warn(`[getERC20ToNativeQuote] V2 报价失败`);
421
222
  return 0n;
422
223
  }
423
224
  async function validateFinalBalances({ sameAddress, buyerBalance, buyAmountBNB, reserveGas, gasLimit, gasPrice, useNativeToken = true, quoteTokenDecimals = 18, provider, buyerAddress }) {
@@ -469,6 +270,7 @@ import { ethers, Contract, Wallet } from 'ethers';
469
270
  import { calculateSellAmount } from '../utils/swap-helpers.js';
470
271
  import { NonceManager } from '../utils/bundle-helpers.js';
471
272
  import { ADDRESSES, PROFIT_CONFIG } from '../utils/constants.js';
273
+ import { quoteV2, quoteV3 } from '../utils/quote-helpers.js';
472
274
  // ✅ BlockRazor Builder EOA 地址(用于贿赂)
473
275
  // 参考文档: https://blockrazor.gitbook.io/blockrazor/bsc/block-builder/send-bundle
474
276
  const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
@@ -504,30 +306,13 @@ const PANCAKE_PROXY_ABI = [
504
306
  'function swapV3Single(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint256 amountOutMin, address to) payable returns (uint256)',
505
307
  'function swapV3MultiHop(address[] calldata lpAddresses, address exactTokenIn, uint256 amountIn, uint256 amountOutMin, address to) payable returns (uint256)'
506
308
  ];
507
- // PancakeSwap V2 Router ABI(用于报价)
508
- const PANCAKE_V2_ROUTER_ABI = [
509
- 'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
510
- ];
511
- // PancakeSwap V3 QuoterV2 ABI(用于报价)
512
- const PANCAKE_V3_QUOTER_ABI = [
513
- 'function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96)) external returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)',
514
- 'function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksCrossedList, uint256 gasEstimate)'
515
- ];
516
309
  // 兼容旧代码:默认使用 BSC 地址
517
310
  const PANCAKE_PROXY_ADDRESS = ADDRESSES.BSC.PancakeProxy;
518
- const PANCAKE_V2_ROUTER_ADDRESS = '0x10ED43C718714eb63d5aA57B78B54704E256024E';
519
- const PANCAKE_V3_QUOTER_ADDRESS = '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997';
311
+ // V3 Quoter ABI 和地址已移至 ../utils/quote-helpers.ts
520
312
  // 代理合约手续费
521
313
  const FLAT_FEE = 0n; // ✅ 已移除合约固定手续费
522
314
  const WBNB_ADDRESS = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
523
- // ✅ 常用稳定币地址(用于多跳报价)
524
- const STABLE_COINS = [
525
- '0x55d398326f99059fF775485246999027B3197955', // USDT
526
- '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', // USDC
527
- '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56', // BUSD
528
- ];
529
- // ✅ V3 常用费率档位
530
- const V3_FEE_TIERS = [100, 500, 2500, 10000]; // 0.01%, 0.05%, 0.25%, 1%
315
+ // ✅ STABLE_COINS 和 V3_FEE_TIERS 已移至 ../utils/quote-helpers.ts
531
316
  const ERC20_ALLOWANCE_ABI = [
532
317
  'function allowance(address,address) view returns (uint256)',
533
318
  'function approve(address spender,uint256 amount) returns (bool)',
@@ -548,12 +333,16 @@ export async function pancakeBundleSwapMerkle(params) {
548
333
  const seller = new Wallet(sellerPrivateKey, context.provider);
549
334
  const buyer = new Wallet(buyerPrivateKey, context.provider);
550
335
  const sameAddress = seller.address.toLowerCase() === buyer.address.toLowerCase();
551
- // ✅ 提前创建 NonceManager 和获取 gasPrice,供所有交易共享
336
+ // ✅ 提前创建 NonceManager,供所有交易共享
552
337
  const nonceManager = new NonceManager(context.provider);
553
338
  const finalGasLimit = getGasLimit(config);
554
- const gasPrice = await getGasPrice(context.provider, config);
555
339
  const txType = config.txType ?? 0;
556
- const { amount: sellAmountWei, decimals } = await calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage);
340
+ // 并行获取 gasPrice 和卖出数量
341
+ const [gasPrice, sellAmountResult] = await Promise.all([
342
+ getGasPrice(context.provider, config),
343
+ calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage)
344
+ ]);
345
+ const { amount: sellAmountWei, decimals } = sellAmountResult;
557
346
  // ✅ 先构建授权交易(会消耗 nonce)
558
347
  const approvalTx = await ensureSellerApproval({
559
348
  tokenAddress,
@@ -622,10 +411,11 @@ export async function pancakeBundleSwapMerkle(params) {
622
411
  needBribeTx, // ✅ 新增
623
412
  nonceManager
624
413
  });
625
- // ✅ 贿赂交易放在首位(由卖方发送,与利润交易同一钱包)
626
- let bribeTx = null;
414
+ // ✅ 并行签名所有交易
415
+ const signPromises = [];
416
+ // 贿赂交易
627
417
  if (needBribeTx && noncePlan.bribeNonce !== undefined) {
628
- bribeTx = await seller.signTransaction({
418
+ signPromises.push(seller.signTransaction({
629
419
  to: BLOCKRAZOR_BUILDER_EOA,
630
420
  value: bribeAmount,
631
421
  nonce: noncePlan.bribeNonce,
@@ -633,9 +423,10 @@ export async function pancakeBundleSwapMerkle(params) {
633
423
  gasLimit: 21000n,
634
424
  chainId: context.chainId,
635
425
  type: txType
636
- });
426
+ }).then(tx => ({ type: 'bribe', tx })));
637
427
  }
638
- const signedSell = await seller.signTransaction({
428
+ // 卖出交易
429
+ signPromises.push(seller.signTransaction({
639
430
  ...swapUnsigned.sellUnsigned,
640
431
  from: seller.address,
641
432
  nonce: noncePlan.sellerNonce,
@@ -643,8 +434,9 @@ export async function pancakeBundleSwapMerkle(params) {
643
434
  gasPrice,
644
435
  chainId: context.chainId,
645
436
  type: txType
646
- });
647
- const signedBuy = await buyer.signTransaction({
437
+ }).then(tx => ({ type: 'sell', tx })));
438
+ // 买入交易
439
+ signPromises.push(buyer.signTransaction({
648
440
  ...swapUnsigned.buyUnsigned,
649
441
  from: buyer.address,
650
442
  nonce: noncePlan.buyerNonce,
@@ -652,16 +444,26 @@ export async function pancakeBundleSwapMerkle(params) {
652
444
  gasPrice,
653
445
  chainId: context.chainId,
654
446
  type: txType
655
- });
656
- // ✅ 利润交易放在末尾(由卖方发送,与贿赂交易同一钱包)
657
- const profitTx = await buildProfitTransaction({
658
- wallet: seller, // ✅ 改为卖方发送(与贿赂交易一致)
659
- profitAmount,
660
- profitNonce: noncePlan.profitNonce,
661
- gasPrice,
662
- chainId: context.chainId,
663
- txType
664
- });
447
+ }).then(tx => ({ type: 'buy', tx })));
448
+ // 利润交易
449
+ if (profitAmount > 0n && noncePlan.profitNonce !== undefined) {
450
+ signPromises.push(seller.signTransaction({
451
+ to: PROFIT_CONFIG.RECIPIENT,
452
+ value: profitAmount,
453
+ nonce: noncePlan.profitNonce,
454
+ gasPrice,
455
+ gasLimit: 21000n,
456
+ chainId: context.chainId,
457
+ type: txType
458
+ }).then(tx => ({ type: 'profit', tx })));
459
+ }
460
+ // ✅ 并行执行所有签名
461
+ const signedResults = await Promise.all(signPromises);
462
+ // 按类型提取结果
463
+ const bribeTx = signedResults.find(r => r.type === 'bribe')?.tx || null;
464
+ const signedSell = signedResults.find(r => r.type === 'sell').tx;
465
+ const signedBuy = signedResults.find(r => r.type === 'buy').tx;
466
+ const profitTx = signedResults.find(r => r.type === 'profit')?.tx || null;
665
467
  nonceManager.clearTemp();
666
468
  validateFinalBalances({
667
469
  sameAddress,
@@ -723,10 +525,13 @@ export async function pancakeBatchSwapMerkle(params) {
723
525
  // ✅ 创建共享资源
724
526
  const nonceManager = new NonceManager(context.provider);
725
527
  const finalGasLimit = getGasLimit(config);
726
- const gasPrice = await getGasPrice(context.provider, config);
727
528
  const txType = config.txType ?? 0;
728
- // ✅ 计算卖出数量
729
- const { amount: sellAmountWei, decimals } = await calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage);
529
+ // ✅ 并行获取 gasPrice 和卖出数量
530
+ const [gasPrice, sellAmountResult] = await Promise.all([
531
+ getGasPrice(context.provider, config),
532
+ calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage)
533
+ ]);
534
+ const { amount: sellAmountWei, decimals } = sellAmountResult;
730
535
  // ✅ 构建授权交易(如果需要)
731
536
  const approvalTx = await ensureSellerApproval({
732
537
  tokenAddress,
@@ -935,24 +740,25 @@ export async function pancakeBatchSwapMerkle(params) {
935
740
  /**
936
741
  * PancakeSwap 快捷批量换手(资金自动流转)
937
742
  *
938
- * 流程:[贿赂] → [卖出] → [转账到子钱包] → [买入] → [利润]
743
+ * 流程:[贿赂] → [卖出] → [转账1, 转账2, ...] → [买入1, 买入2, ...] → [利润]
939
744
  *
940
745
  * 特点:
941
746
  * - 子钱包不需要预先有 BNB 余额
942
747
  * - 资金来自主钱包卖出代币所得
943
748
  * - 提升资金利用率
944
749
  *
945
- * 限制:最多 12 个买方(转账 + 买入 = 24 笔,加上贿赂/卖出/利润接近 25 笔限制)
750
+ * 限制:最多 23 个买方(转账 + 买入 = 46 笔,加上贿赂/卖出/利润 = 50 笔限制)
946
751
  */
947
752
  export async function pancakeQuickBatchSwapMerkle(params) {
948
753
  const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config } = params;
949
- // ✅ 校验买方数量(最多 12 个,因为每个买方需要 2 笔交易:转账 + 买入)
950
- const MAX_BUYERS = 12;
754
+ // ✅ 校验买方数量(最多 23 个:贿赂(1) + 卖出(1) + 转账(N) + 买入(N) + 利润(1) ≤ 50)
755
+ // 2N + 3 ≤ 50 → N ≤ 23
756
+ const MAX_BUYERS = 23;
951
757
  if (buyerPrivateKeys.length === 0) {
952
758
  throw new Error('至少需要一个买方钱包');
953
759
  }
954
760
  if (buyerPrivateKeys.length > MAX_BUYERS) {
955
- throw new Error(`快捷模式买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
761
+ throw new Error(`资金利用率模式买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
956
762
  }
957
763
  // ✅ 校验分配模式
958
764
  if (!buyerRatios && !buyerAmounts) {
@@ -966,14 +772,27 @@ export async function pancakeQuickBatchSwapMerkle(params) {
966
772
  }
967
773
  const context = createPancakeContext(config);
968
774
  const seller = new Wallet(sellerPrivateKey, context.provider);
775
+ // ✅ 买方需要私钥(因为买方自己执行买入交易)
969
776
  const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider));
970
777
  // ✅ 创建共享资源
971
778
  const nonceManager = new NonceManager(context.provider);
972
779
  const finalGasLimit = getGasLimit(config);
973
- const gasPrice = await getGasPrice(context.provider, config);
974
780
  const txType = config.txType ?? 0;
975
- // ✅ 计算卖出数量
976
- const { amount: sellAmountWei, decimals } = await calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage);
781
+ // ✅ 并行获取 gasPrice 和卖出数量
782
+ const [gasPrice, sellAmountResult] = await Promise.all([
783
+ getGasPrice(context.provider, config),
784
+ calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage)
785
+ ]);
786
+ const { amount: sellAmountWei, decimals } = sellAmountResult;
787
+ // ✅ 调试日志:打印卖出数量
788
+ console.log(`[pancakeQuickBatchSwapMerkle] 卖出数量: ${ethers.formatUnits(sellAmountWei, decimals)} (${sellAmountWei} wei, decimals=${decimals})`);
789
+ console.log(`[pancakeQuickBatchSwapMerkle] routeParams:`, JSON.stringify({
790
+ routeType: routeParams.routeType,
791
+ v2Path: routeParams.v2Path,
792
+ v3TokenIn: routeParams.v3TokenIn,
793
+ v3TokenOut: routeParams.v3TokenOut,
794
+ v3Fee: routeParams.v3Fee
795
+ }));
977
796
  // ✅ 获取卖出报价
978
797
  const quoteResult = await quoteSellOutput({
979
798
  routeParams,
@@ -981,10 +800,15 @@ export async function pancakeQuickBatchSwapMerkle(params) {
981
800
  provider: context.provider
982
801
  });
983
802
  const estimatedBNBOut = quoteResult.estimatedBNBOut;
803
+ console.log(`[pancakeQuickBatchSwapMerkle] 预估卖出所得: ${ethers.formatEther(estimatedBNBOut)} BNB (${estimatedBNBOut} wei)`);
804
+ // ✅ 安全检查:如果报价超过 10 BNB,警告可能有问题
805
+ if (estimatedBNBOut > ethers.parseEther('10')) {
806
+ console.warn(`[pancakeQuickBatchSwapMerkle] ⚠️ 报价异常高: ${ethers.formatEther(estimatedBNBOut)} BNB,请检查路径和数量!`);
807
+ }
984
808
  // ✅ 计算利润(从卖出所得中预扣)
985
809
  const profitAmount = calculateProfitAmount(estimatedBNBOut);
986
810
  const distributableBNB = estimatedBNBOut - profitAmount; // 可分配金额
987
- // ✅ 计算每个子钱包分到的金额
811
+ // ✅ 计算每个买方分到的 BNB(用于转账和买入)
988
812
  let transferAmountsWei;
989
813
  if (buyerAmounts && buyerAmounts.length === buyers.length) {
990
814
  // 数量模式:使用指定的金额
@@ -1009,17 +833,19 @@ export async function pancakeQuickBatchSwapMerkle(params) {
1009
833
  const bribeAmount = config.bribeAmount && config.bribeAmount > 0
1010
834
  ? ethers.parseEther(String(config.bribeAmount))
1011
835
  : 0n;
1012
- // ✅ 验证主钱包余额(需要支付 Gas 费用)
836
+ // ✅ 验证主钱包余额(需要支付 Gas 费用 + 贿赂)
1013
837
  const sellerBalance = await seller.provider.getBalance(seller.address);
1014
- const estimatedGasCost = gasPrice * finalGasLimit * BigInt(3 + buyers.length * 2); // 贿赂 + 卖出 + 转账 + 利润
1015
- if (sellerBalance < bribeAmount + estimatedGasCost) {
1016
- throw new Error(`主钱包 BNB 余额不足 (用于支付 Gas): 需要约 ${ethers.formatEther(bribeAmount + estimatedGasCost)} BNB, 实际 ${ethers.formatEther(sellerBalance)} BNB`);
838
+ // 卖方需要的 Gas:贿赂(21000) + 卖出(gasLimit) + N个转账(21000 each) + 利润(21000)
839
+ const sellerGasCost = gasPrice * (21000n + finalGasLimit + 21000n * BigInt(buyers.length) + 21000n);
840
+ const sellerRequired = bribeAmount + sellerGasCost;
841
+ if (sellerBalance < sellerRequired) {
842
+ throw new Error(`主钱包 BNB 余额不足: 需要约 ${ethers.formatEther(sellerRequired)} BNB (贿赂: ${ethers.formatEther(bribeAmount)}, Gas: ${ethers.formatEther(sellerGasCost)}), 实际 ${ethers.formatEther(sellerBalance)} BNB`);
1017
843
  }
1018
844
  // ==================== 规划 Nonce ====================
1019
- // 卖方: [贿赂(可选)] → [卖出] → [转账1, 转账2, ...] → [利润(可选)]
1020
- // 买方: [买入]
845
+ // 流程: [贿赂] → [卖出] → [转账1, 转账2, ...] → [买入1, 买入2, ...] → [利润]
1021
846
  let sellerNonce = await nonceManager.getNextNonce(seller);
1022
- // 贿赂交易(首位)
847
+ const deadline = Math.floor(Date.now() / 1000) + 600;
848
+ // ==================== 1. 贿赂交易 ====================
1023
849
  let bribeTx = null;
1024
850
  if (bribeAmount > 0n) {
1025
851
  bribeTx = await seller.signTransaction({
@@ -1031,10 +857,10 @@ export async function pancakeQuickBatchSwapMerkle(params) {
1031
857
  chainId: context.chainId,
1032
858
  type: txType
1033
859
  });
860
+ console.log(`[pancakeQuickBatchSwapMerkle] 贿赂交易已签名`);
1034
861
  }
1035
- // 卖出交易
862
+ // ==================== 2. 卖出交易 ====================
1036
863
  const proxySeller = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, seller);
1037
- const deadline = Math.floor(Date.now() / 1000) + 600;
1038
864
  let sellUnsigned;
1039
865
  if (routeParams.routeType === 'v2') {
1040
866
  const { v2Path } = routeParams;
@@ -1057,12 +883,16 @@ export async function pancakeQuickBatchSwapMerkle(params) {
1057
883
  chainId: context.chainId,
1058
884
  type: txType
1059
885
  });
1060
- // ✅ 转账交易(主钱包 → 各子钱包)
886
+ console.log(`[pancakeQuickBatchSwapMerkle] 卖出交易已签名`);
887
+ // ==================== 3. 转账交易(卖方 → 各买方)====================
1061
888
  const transferTxs = [];
1062
889
  for (let i = 0; i < buyers.length; i++) {
890
+ // 转账金额 = 买入金额 + FLAT_FEE + 买方 Gas 费用
891
+ const buyerGasCost = gasPrice * finalGasLimit;
892
+ const transferValue = transferAmountsWei[i] + FLAT_FEE + buyerGasCost;
1063
893
  const transferTx = await seller.signTransaction({
1064
894
  to: buyers[i].address,
1065
- value: transferAmountsWei[i],
895
+ value: transferValue,
1066
896
  nonce: sellerNonce++,
1067
897
  gasPrice,
1068
898
  gasLimit: 21000n,
@@ -1071,22 +901,11 @@ export async function pancakeQuickBatchSwapMerkle(params) {
1071
901
  });
1072
902
  transferTxs.push(transferTx);
1073
903
  }
1074
- // 利润交易
1075
- let profitTx = null;
1076
- if (profitAmount > 0n) {
1077
- profitTx = await seller.signTransaction({
1078
- to: PROFIT_CONFIG.RECIPIENT,
1079
- value: profitAmount,
1080
- nonce: sellerNonce++,
1081
- gasPrice,
1082
- gasLimit: 21000n,
1083
- chainId: context.chainId,
1084
- type: txType
1085
- });
1086
- }
1087
- // ✅ 并行获取所有买方的 nonce
904
+ console.log(`[pancakeQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名`);
905
+ // ==================== 4. 买入交易(各买方执行)====================
906
+ // 并行获取所有买方的 nonce
1088
907
  const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
1089
- // ✅ 并行构建买入交易
908
+ // 并行签名所有买入交易
1090
909
  const signedBuys = await Promise.all(buyers.map(async (buyer, i) => {
1091
910
  const buyAmount = transferAmountsWei[i];
1092
911
  const proxyBuyer = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, buyer);
@@ -1115,16 +934,38 @@ export async function pancakeQuickBatchSwapMerkle(params) {
1115
934
  type: txType
1116
935
  });
1117
936
  }));
937
+ console.log(`[pancakeQuickBatchSwapMerkle] ${signedBuys.length} 笔买入交易已签名`);
938
+ // ==================== 5. 利润交易 ====================
939
+ let profitTx = null;
940
+ if (profitAmount > 0n) {
941
+ profitTx = await seller.signTransaction({
942
+ to: PROFIT_CONFIG.RECIPIENT,
943
+ value: profitAmount,
944
+ nonce: sellerNonce++,
945
+ gasPrice,
946
+ gasLimit: 21000n,
947
+ chainId: context.chainId,
948
+ type: txType
949
+ });
950
+ console.log(`[pancakeQuickBatchSwapMerkle] 利润交易已签名`);
951
+ }
1118
952
  nonceManager.clearTemp();
1119
- // 按顺序组装交易数组:贿赂 → 卖出 → 转账 → 买入 → 利润
953
+ // ==================== 组装交易数组 ====================
954
+ // 顺序:贿赂 → 卖出 → 转账1, 转账2, ... → 买入1, 买入2, ... → 利润
1120
955
  const signedTransactions = [];
1121
956
  if (bribeTx)
1122
- signedTransactions.push(bribeTx); // 贿赂(首位)
1123
- signedTransactions.push(signedSell); // 卖出
1124
- signedTransactions.push(...transferTxs); // 转账到各子钱包
1125
- signedTransactions.push(...signedBuys); // 各子钱包买入
957
+ signedTransactions.push(bribeTx);
958
+ signedTransactions.push(signedSell);
959
+ signedTransactions.push(...transferTxs);
960
+ signedTransactions.push(...signedBuys);
1126
961
  if (profitTx)
1127
- signedTransactions.push(profitTx); // 利润(末尾)
962
+ signedTransactions.push(profitTx);
963
+ console.log(`[pancakeQuickBatchSwapMerkle] 交易组装完成: ${signedTransactions.length} 笔`);
964
+ console.log(` - 贿赂: ${bribeTx ? 1 : 0}`);
965
+ console.log(` - 卖出: 1`);
966
+ console.log(` - 转账: ${transferTxs.length}`);
967
+ console.log(` - 买入: ${signedBuys.length}`);
968
+ console.log(` - 利润: ${profitTx ? 1 : 0}`);
1128
969
  return {
1129
970
  signedTransactions,
1130
971
  metadata: {