four-flap-meme-sdk 1.4.10 → 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 路径无效`);
165
- }
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] };
78
+ // V3 多跳报价失败时不再 fallback V2,因为价格可能差异很大
79
+ throw new Error(`V3 多跳报价失败: LPs=${params.v3LpAddresses?.join(', ')}, tokenIn=${params.v3ExactTokenIn}`);
172
80
  }
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,26 +182,45 @@ 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;
294
188
  }
295
189
  return (estimatedBNBOut * BigInt(PROFIT_CONFIG.RATE_BPS)) / 10000n;
296
190
  }
191
+ /**
192
+ * ✅ 获取 ERC20 代币 → 原生代币(BNB)的报价
193
+ * 用于将 ERC20 利润转换为 BNB
194
+ * 使用共享的报价函数
195
+ */
196
+ async function getERC20ToNativeQuote(provider, tokenAddress, tokenAmount, version = 'v2', fee) {
197
+ if (tokenAmount <= 0n)
198
+ return 0n;
199
+ const tokenLower = tokenAddress.toLowerCase();
200
+ const wbnbLower = WBNB_ADDRESS.toLowerCase();
201
+ // 如果代币本身就是 WBNB,直接返回
202
+ if (tokenLower === wbnbLower) {
203
+ return tokenAmount;
204
+ }
205
+ // 使用共享的报价函数
206
+ if (version === 'v3') {
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;
211
+ }
212
+ console.warn(`[getERC20ToNativeQuote] V3 报价失败`);
213
+ return 0n;
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 报价失败`);
222
+ return 0n;
223
+ }
297
224
  async function validateFinalBalances({ sameAddress, buyerBalance, buyAmountBNB, reserveGas, gasLimit, gasPrice, useNativeToken = true, quoteTokenDecimals = 18, provider, buyerAddress }) {
298
225
  const gasCost = gasLimit * gasPrice;
299
226
  if (sameAddress) {
@@ -343,6 +270,7 @@ import { ethers, Contract, Wallet } from 'ethers';
343
270
  import { calculateSellAmount } from '../utils/swap-helpers.js';
344
271
  import { NonceManager } from '../utils/bundle-helpers.js';
345
272
  import { ADDRESSES, PROFIT_CONFIG } from '../utils/constants.js';
273
+ import { quoteV2, quoteV3 } from '../utils/quote-helpers.js';
346
274
  // ✅ BlockRazor Builder EOA 地址(用于贿赂)
347
275
  // 参考文档: https://blockrazor.gitbook.io/blockrazor/bsc/block-builder/send-bundle
348
276
  const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
@@ -378,30 +306,13 @@ const PANCAKE_PROXY_ABI = [
378
306
  'function swapV3Single(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint256 amountOutMin, address to) payable returns (uint256)',
379
307
  'function swapV3MultiHop(address[] calldata lpAddresses, address exactTokenIn, uint256 amountIn, uint256 amountOutMin, address to) payable returns (uint256)'
380
308
  ];
381
- // PancakeSwap V2 Router ABI(用于报价)
382
- const PANCAKE_V2_ROUTER_ABI = [
383
- 'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
384
- ];
385
- // PancakeSwap V3 QuoterV2 ABI(用于报价)
386
- const PANCAKE_V3_QUOTER_ABI = [
387
- 'function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96)) external returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)',
388
- 'function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksCrossedList, uint256 gasEstimate)'
389
- ];
390
309
  // 兼容旧代码:默认使用 BSC 地址
391
310
  const PANCAKE_PROXY_ADDRESS = ADDRESSES.BSC.PancakeProxy;
392
- const PANCAKE_V2_ROUTER_ADDRESS = '0x10ED43C718714eb63d5aA57B78B54704E256024E';
393
- const PANCAKE_V3_QUOTER_ADDRESS = '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997';
311
+ // V3 Quoter ABI 和地址已移至 ../utils/quote-helpers.ts
394
312
  // 代理合约手续费
395
313
  const FLAT_FEE = 0n; // ✅ 已移除合约固定手续费
396
314
  const WBNB_ADDRESS = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
397
- // ✅ 常用稳定币地址(用于多跳报价)
398
- const STABLE_COINS = [
399
- '0x55d398326f99059fF775485246999027B3197955', // USDT
400
- '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', // USDC
401
- '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56', // BUSD
402
- ];
403
- // ✅ V3 常用费率档位
404
- 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
405
316
  const ERC20_ALLOWANCE_ABI = [
406
317
  'function allowance(address,address) view returns (uint256)',
407
318
  'function approve(address spender,uint256 amount) returns (bool)',
@@ -422,12 +333,16 @@ export async function pancakeBundleSwapMerkle(params) {
422
333
  const seller = new Wallet(sellerPrivateKey, context.provider);
423
334
  const buyer = new Wallet(buyerPrivateKey, context.provider);
424
335
  const sameAddress = seller.address.toLowerCase() === buyer.address.toLowerCase();
425
- // ✅ 提前创建 NonceManager 和获取 gasPrice,供所有交易共享
336
+ // ✅ 提前创建 NonceManager,供所有交易共享
426
337
  const nonceManager = new NonceManager(context.provider);
427
338
  const finalGasLimit = getGasLimit(config);
428
- const gasPrice = await getGasPrice(context.provider, config);
429
339
  const txType = config.txType ?? 0;
430
- 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;
431
346
  // ✅ 先构建授权交易(会消耗 nonce)
432
347
  const approvalTx = await ensureSellerApproval({
433
348
  tokenAddress,
@@ -463,7 +378,24 @@ export async function pancakeBundleSwapMerkle(params) {
463
378
  tokenAddress,
464
379
  useNativeToken
465
380
  });
466
- const profitAmount = calculateProfitAmount(quoteResult.estimatedBNBOut);
381
+ // 修复:利润计算应基于 BNB 数量,不是 ERC20 数量
382
+ // 如果输出是原生代币(BNB),直接使用报价结果
383
+ // 如果输出是 ERC20(如 USDT),需要先转换为 BNB 等值
384
+ let profitAmount;
385
+ if (useNativeToken) {
386
+ // 输出是 BNB,直接计算利润
387
+ profitAmount = calculateProfitAmount(quoteResult.estimatedBNBOut);
388
+ console.log(`[pancakeBundleSwapMerkle] 原生代币利润: ${ethers.formatEther(profitAmount)} BNB`);
389
+ }
390
+ else {
391
+ // 输出是 ERC20,需要先获取 ERC20 → BNB 的报价
392
+ const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
393
+ const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
394
+ const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, quoteResult.estimatedBNBOut, // 这实际上是 ERC20 数量
395
+ version, fee);
396
+ profitAmount = calculateProfitAmount(estimatedBNBValue);
397
+ console.log(`[pancakeBundleSwapMerkle] ERC20→BNB 报价: ${ethers.formatUnits(quoteResult.estimatedBNBOut, quoteTokenDecimals)} ${quoteToken?.slice(0, 10)}... → ${ethers.formatEther(estimatedBNBValue)} BNB, 利润: ${ethers.formatEther(profitAmount)} BNB`);
398
+ }
467
399
  // ✅ 获取贿赂金额
468
400
  const bribeAmount = config.bribeAmount && config.bribeAmount > 0
469
401
  ? ethers.parseEther(String(config.bribeAmount))
@@ -479,10 +411,11 @@ export async function pancakeBundleSwapMerkle(params) {
479
411
  needBribeTx, // ✅ 新增
480
412
  nonceManager
481
413
  });
482
- // ✅ 贿赂交易放在首位(由卖方发送,与利润交易同一钱包)
483
- let bribeTx = null;
414
+ // ✅ 并行签名所有交易
415
+ const signPromises = [];
416
+ // 贿赂交易
484
417
  if (needBribeTx && noncePlan.bribeNonce !== undefined) {
485
- bribeTx = await seller.signTransaction({
418
+ signPromises.push(seller.signTransaction({
486
419
  to: BLOCKRAZOR_BUILDER_EOA,
487
420
  value: bribeAmount,
488
421
  nonce: noncePlan.bribeNonce,
@@ -490,9 +423,10 @@ export async function pancakeBundleSwapMerkle(params) {
490
423
  gasLimit: 21000n,
491
424
  chainId: context.chainId,
492
425
  type: txType
493
- });
426
+ }).then(tx => ({ type: 'bribe', tx })));
494
427
  }
495
- const signedSell = await seller.signTransaction({
428
+ // 卖出交易
429
+ signPromises.push(seller.signTransaction({
496
430
  ...swapUnsigned.sellUnsigned,
497
431
  from: seller.address,
498
432
  nonce: noncePlan.sellerNonce,
@@ -500,8 +434,9 @@ export async function pancakeBundleSwapMerkle(params) {
500
434
  gasPrice,
501
435
  chainId: context.chainId,
502
436
  type: txType
503
- });
504
- const signedBuy = await buyer.signTransaction({
437
+ }).then(tx => ({ type: 'sell', tx })));
438
+ // 买入交易
439
+ signPromises.push(buyer.signTransaction({
505
440
  ...swapUnsigned.buyUnsigned,
506
441
  from: buyer.address,
507
442
  nonce: noncePlan.buyerNonce,
@@ -509,16 +444,26 @@ export async function pancakeBundleSwapMerkle(params) {
509
444
  gasPrice,
510
445
  chainId: context.chainId,
511
446
  type: txType
512
- });
513
- // ✅ 利润交易放在末尾(由卖方发送,与贿赂交易同一钱包)
514
- const profitTx = await buildProfitTransaction({
515
- wallet: seller, // ✅ 改为卖方发送(与贿赂交易一致)
516
- profitAmount,
517
- profitNonce: noncePlan.profitNonce,
518
- gasPrice,
519
- chainId: context.chainId,
520
- txType
521
- });
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;
522
467
  nonceManager.clearTemp();
523
468
  validateFinalBalances({
524
469
  sameAddress,
@@ -580,10 +525,13 @@ export async function pancakeBatchSwapMerkle(params) {
580
525
  // ✅ 创建共享资源
581
526
  const nonceManager = new NonceManager(context.provider);
582
527
  const finalGasLimit = getGasLimit(config);
583
- const gasPrice = await getGasPrice(context.provider, config);
584
528
  const txType = config.txType ?? 0;
585
- // ✅ 计算卖出数量
586
- 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;
587
535
  // ✅ 构建授权交易(如果需要)
588
536
  const approvalTx = await ensureSellerApproval({
589
537
  tokenAddress,
@@ -711,8 +659,19 @@ export async function pancakeBatchSwapMerkle(params) {
711
659
  type: txType
712
660
  });
713
661
  }
714
- // 利润交易放在末尾(由卖方发送,与贿赂交易一致)
715
- const profitAmount = calculateProfitAmount(estimatedBNBOut);
662
+ // ✅ 修复:利润计算应基于 BNB 数量,不是 ERC20 数量
663
+ let profitAmount;
664
+ if (useNativeToken) {
665
+ profitAmount = calculateProfitAmount(estimatedBNBOut);
666
+ console.log(`[pancakeBatchSwapMerkle] 原生代币利润: ${ethers.formatEther(profitAmount)} BNB`);
667
+ }
668
+ else {
669
+ const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
670
+ const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
671
+ const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, estimatedBNBOut, version, fee);
672
+ profitAmount = calculateProfitAmount(estimatedBNBValue);
673
+ console.log(`[pancakeBatchSwapMerkle] ERC20→BNB 报价: ${ethers.formatUnits(estimatedBNBOut, quoteTokenDecimals)} → ${ethers.formatEther(estimatedBNBValue)} BNB, 利润: ${ethers.formatEther(profitAmount)} BNB`);
674
+ }
716
675
  let profitTx = null;
717
676
  if (profitAmount > 0n) {
718
677
  // ✅ 利润由卖方发送(与贿赂交易同一钱包)
@@ -781,24 +740,25 @@ export async function pancakeBatchSwapMerkle(params) {
781
740
  /**
782
741
  * PancakeSwap 快捷批量换手(资金自动流转)
783
742
  *
784
- * 流程:[贿赂] → [卖出] → [转账到子钱包] → [买入] → [利润]
743
+ * 流程:[贿赂] → [卖出] → [转账1, 转账2, ...] → [买入1, 买入2, ...] → [利润]
785
744
  *
786
745
  * 特点:
787
746
  * - 子钱包不需要预先有 BNB 余额
788
747
  * - 资金来自主钱包卖出代币所得
789
748
  * - 提升资金利用率
790
749
  *
791
- * 限制:最多 12 个买方(转账 + 买入 = 24 笔,加上贿赂/卖出/利润接近 25 笔限制)
750
+ * 限制:最多 23 个买方(转账 + 买入 = 46 笔,加上贿赂/卖出/利润 = 50 笔限制)
792
751
  */
793
752
  export async function pancakeQuickBatchSwapMerkle(params) {
794
753
  const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config } = params;
795
- // ✅ 校验买方数量(最多 12 个,因为每个买方需要 2 笔交易:转账 + 买入)
796
- const MAX_BUYERS = 12;
754
+ // ✅ 校验买方数量(最多 23 个:贿赂(1) + 卖出(1) + 转账(N) + 买入(N) + 利润(1) ≤ 50)
755
+ // 2N + 3 ≤ 50 → N ≤ 23
756
+ const MAX_BUYERS = 23;
797
757
  if (buyerPrivateKeys.length === 0) {
798
758
  throw new Error('至少需要一个买方钱包');
799
759
  }
800
760
  if (buyerPrivateKeys.length > MAX_BUYERS) {
801
- throw new Error(`快捷模式买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
761
+ throw new Error(`资金利用率模式买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
802
762
  }
803
763
  // ✅ 校验分配模式
804
764
  if (!buyerRatios && !buyerAmounts) {
@@ -812,14 +772,27 @@ export async function pancakeQuickBatchSwapMerkle(params) {
812
772
  }
813
773
  const context = createPancakeContext(config);
814
774
  const seller = new Wallet(sellerPrivateKey, context.provider);
775
+ // ✅ 买方需要私钥(因为买方自己执行买入交易)
815
776
  const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider));
816
777
  // ✅ 创建共享资源
817
778
  const nonceManager = new NonceManager(context.provider);
818
779
  const finalGasLimit = getGasLimit(config);
819
- const gasPrice = await getGasPrice(context.provider, config);
820
780
  const txType = config.txType ?? 0;
821
- // ✅ 计算卖出数量
822
- 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
+ }));
823
796
  // ✅ 获取卖出报价
824
797
  const quoteResult = await quoteSellOutput({
825
798
  routeParams,
@@ -827,10 +800,15 @@ export async function pancakeQuickBatchSwapMerkle(params) {
827
800
  provider: context.provider
828
801
  });
829
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
+ }
830
808
  // ✅ 计算利润(从卖出所得中预扣)
831
809
  const profitAmount = calculateProfitAmount(estimatedBNBOut);
832
810
  const distributableBNB = estimatedBNBOut - profitAmount; // 可分配金额
833
- // ✅ 计算每个子钱包分到的金额
811
+ // ✅ 计算每个买方分到的 BNB(用于转账和买入)
834
812
  let transferAmountsWei;
835
813
  if (buyerAmounts && buyerAmounts.length === buyers.length) {
836
814
  // 数量模式:使用指定的金额
@@ -855,17 +833,19 @@ export async function pancakeQuickBatchSwapMerkle(params) {
855
833
  const bribeAmount = config.bribeAmount && config.bribeAmount > 0
856
834
  ? ethers.parseEther(String(config.bribeAmount))
857
835
  : 0n;
858
- // ✅ 验证主钱包余额(需要支付 Gas 费用)
836
+ // ✅ 验证主钱包余额(需要支付 Gas 费用 + 贿赂)
859
837
  const sellerBalance = await seller.provider.getBalance(seller.address);
860
- const estimatedGasCost = gasPrice * finalGasLimit * BigInt(3 + buyers.length * 2); // 贿赂 + 卖出 + 转账 + 利润
861
- if (sellerBalance < bribeAmount + estimatedGasCost) {
862
- 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`);
863
843
  }
864
844
  // ==================== 规划 Nonce ====================
865
- // 卖方: [贿赂(可选)] → [卖出] → [转账1, 转账2, ...] → [利润(可选)]
866
- // 买方: [买入]
845
+ // 流程: [贿赂] → [卖出] → [转账1, 转账2, ...] → [买入1, 买入2, ...] → [利润]
867
846
  let sellerNonce = await nonceManager.getNextNonce(seller);
868
- // 贿赂交易(首位)
847
+ const deadline = Math.floor(Date.now() / 1000) + 600;
848
+ // ==================== 1. 贿赂交易 ====================
869
849
  let bribeTx = null;
870
850
  if (bribeAmount > 0n) {
871
851
  bribeTx = await seller.signTransaction({
@@ -877,10 +857,10 @@ export async function pancakeQuickBatchSwapMerkle(params) {
877
857
  chainId: context.chainId,
878
858
  type: txType
879
859
  });
860
+ console.log(`[pancakeQuickBatchSwapMerkle] 贿赂交易已签名`);
880
861
  }
881
- // 卖出交易
862
+ // ==================== 2. 卖出交易 ====================
882
863
  const proxySeller = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, seller);
883
- const deadline = Math.floor(Date.now() / 1000) + 600;
884
864
  let sellUnsigned;
885
865
  if (routeParams.routeType === 'v2') {
886
866
  const { v2Path } = routeParams;
@@ -903,12 +883,16 @@ export async function pancakeQuickBatchSwapMerkle(params) {
903
883
  chainId: context.chainId,
904
884
  type: txType
905
885
  });
906
- // ✅ 转账交易(主钱包 → 各子钱包)
886
+ console.log(`[pancakeQuickBatchSwapMerkle] 卖出交易已签名`);
887
+ // ==================== 3. 转账交易(卖方 → 各买方)====================
907
888
  const transferTxs = [];
908
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;
909
893
  const transferTx = await seller.signTransaction({
910
894
  to: buyers[i].address,
911
- value: transferAmountsWei[i],
895
+ value: transferValue,
912
896
  nonce: sellerNonce++,
913
897
  gasPrice,
914
898
  gasLimit: 21000n,
@@ -917,22 +901,11 @@ export async function pancakeQuickBatchSwapMerkle(params) {
917
901
  });
918
902
  transferTxs.push(transferTx);
919
903
  }
920
- // 利润交易
921
- let profitTx = null;
922
- if (profitAmount > 0n) {
923
- profitTx = await seller.signTransaction({
924
- to: PROFIT_CONFIG.RECIPIENT,
925
- value: profitAmount,
926
- nonce: sellerNonce++,
927
- gasPrice,
928
- gasLimit: 21000n,
929
- chainId: context.chainId,
930
- type: txType
931
- });
932
- }
933
- // ✅ 并行获取所有买方的 nonce
904
+ console.log(`[pancakeQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名`);
905
+ // ==================== 4. 买入交易(各买方执行)====================
906
+ // 并行获取所有买方的 nonce
934
907
  const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
935
- // ✅ 并行构建买入交易
908
+ // 并行签名所有买入交易
936
909
  const signedBuys = await Promise.all(buyers.map(async (buyer, i) => {
937
910
  const buyAmount = transferAmountsWei[i];
938
911
  const proxyBuyer = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, buyer);
@@ -961,16 +934,38 @@ export async function pancakeQuickBatchSwapMerkle(params) {
961
934
  type: txType
962
935
  });
963
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
+ }
964
952
  nonceManager.clearTemp();
965
- // 按顺序组装交易数组:贿赂 → 卖出 → 转账 → 买入 → 利润
953
+ // ==================== 组装交易数组 ====================
954
+ // 顺序:贿赂 → 卖出 → 转账1, 转账2, ... → 买入1, 买入2, ... → 利润
966
955
  const signedTransactions = [];
967
956
  if (bribeTx)
968
- signedTransactions.push(bribeTx); // 贿赂(首位)
969
- signedTransactions.push(signedSell); // 卖出
970
- signedTransactions.push(...transferTxs); // 转账到各子钱包
971
- signedTransactions.push(...signedBuys); // 各子钱包买入
957
+ signedTransactions.push(bribeTx);
958
+ signedTransactions.push(signedSell);
959
+ signedTransactions.push(...transferTxs);
960
+ signedTransactions.push(...signedBuys);
972
961
  if (profitTx)
973
- 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}`);
974
969
  return {
975
970
  signedTransactions,
976
971
  metadata: {