four-flap-meme-sdk 1.4.11 → 1.4.13

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
- }
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 };
102
60
  }
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
- }
148
- }
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
- }
375
- }
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
- }
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;
417
211
  }
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,33 @@ export async function pancakeBatchSwapMerkle(params) {
935
740
  /**
936
741
  * PancakeSwap 快捷批量换手(资金自动流转)
937
742
  *
938
- * 流程:[贿赂] → [卖出] → [转账到子钱包] → [买入] → [利润]
743
+ * 流程:[贿赂] → [卖出] → [转账1, 转账2, ...] → [买入1, 买入2, ...] → [利润]
939
744
  *
940
745
  * 特点:
941
- * - 子钱包不需要预先有 BNB 余额
746
+ * - 子钱包不需要预先有余额
942
747
  * - 资金来自主钱包卖出代币所得
943
748
  * - 提升资金利用率
749
+ * - 支持 BNB 和 ERC20(如 USDT)两种模式
944
750
  *
945
- * 限制:最多 12 个买方(转账 + 买入 = 24 笔,加上贿赂/卖出/利润接近 25 笔限制)
751
+ * 限制:最多 23 个买方(转账 + 买入 = 46 笔,加上贿赂/卖出/利润 = 50 笔限制)
946
752
  */
947
753
  export async function pancakeQuickBatchSwapMerkle(params) {
948
- const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config } = params;
949
- // ✅ 校验买方数量(最多 12 个,因为每个买方需要 2 笔交易:转账 + 买入)
950
- const MAX_BUYERS = 12;
754
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18 } = params;
755
+ // ✅ 判断是否使用原生代币
756
+ const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
757
+ const WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'.toLowerCase();
758
+ // ✅ 校验买方数量
759
+ // BNB 模式:贿赂(1) + 卖出(1) + 转账(N) + 买入(N) + 利润(1) ≤ 50 → 2N + 3 ≤ 50 → N ≤ 23
760
+ // ERC20 模式:贿赂(1) + 卖出(1) + ERC20转账(N) + BNB Gas转账(N) + 买入(N) + 利润(1) ≤ 50 → 3N + 3 ≤ 50 → N ≤ 15
761
+ const MAX_BUYERS_NATIVE = 23;
762
+ const MAX_BUYERS_ERC20 = 15;
763
+ const MAX_BUYERS = useNativeToken ? MAX_BUYERS_NATIVE : MAX_BUYERS_ERC20;
951
764
  if (buyerPrivateKeys.length === 0) {
952
765
  throw new Error('至少需要一个买方钱包');
953
766
  }
954
767
  if (buyerPrivateKeys.length > MAX_BUYERS) {
955
- throw new Error(`快捷模式买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
768
+ const mode = useNativeToken ? 'BNB' : 'ERC20';
769
+ throw new Error(`资金利用率模式(${mode})买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
956
770
  }
957
771
  // ✅ 校验分配模式
958
772
  if (!buyerRatios && !buyerAmounts) {
@@ -967,39 +781,101 @@ export async function pancakeQuickBatchSwapMerkle(params) {
967
781
  const context = createPancakeContext(config);
968
782
  const seller = new Wallet(sellerPrivateKey, context.provider);
969
783
  const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider));
784
+ // ✅ 校验卖出路径输出代币是否匹配
785
+ let sellOutputToken;
786
+ if (routeParams.routeType === 'v2') {
787
+ const { v2Path } = routeParams;
788
+ sellOutputToken = v2Path[v2Path.length - 1];
789
+ }
790
+ else if (routeParams.routeType === 'v3-single') {
791
+ const { v3TokenOut } = routeParams;
792
+ sellOutputToken = v3TokenOut;
793
+ }
794
+ else if (routeParams.routeType === 'v3-multi') {
795
+ const { v2Path } = routeParams;
796
+ if (v2Path && v2Path.length > 0) {
797
+ sellOutputToken = v2Path[v2Path.length - 1];
798
+ }
799
+ }
800
+ // 校验输出代币
801
+ if (useNativeToken) {
802
+ // 原生代币模式:输出必须是 WBNB
803
+ if (!sellOutputToken || sellOutputToken.toLowerCase() !== WBNB) {
804
+ throw new Error(`原生代币模式要求卖出路径以 WBNB 结尾(当前输出: ${sellOutputToken || '未知'})。` +
805
+ `请切换交易对或使用 ERC20 模式。`);
806
+ }
807
+ }
808
+ else {
809
+ // ERC20 模式:输出必须是指定的 quoteToken
810
+ if (!sellOutputToken || sellOutputToken.toLowerCase() !== quoteToken.toLowerCase()) {
811
+ throw new Error(`ERC20 模式要求卖出路径以 ${quoteToken} 结尾(当前输出: ${sellOutputToken || '未知'})。`);
812
+ }
813
+ }
970
814
  // ✅ 创建共享资源
971
815
  const nonceManager = new NonceManager(context.provider);
972
816
  const finalGasLimit = getGasLimit(config);
973
- const gasPrice = await getGasPrice(context.provider, config);
974
817
  const txType = config.txType ?? 0;
975
- // 计算卖出数量
976
- const { amount: sellAmountWei, decimals } = await calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage);
818
+ const ERC20_TRANSFER_GAS = 65000n; // ERC20 transfer 的 gas 限制
819
+ // 并行获取 gasPrice 和卖出数量
820
+ const [gasPrice, sellAmountResult] = await Promise.all([
821
+ getGasPrice(context.provider, config),
822
+ calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage)
823
+ ]);
824
+ const { amount: sellAmountWei, decimals } = sellAmountResult;
825
+ // ✅ 调试日志
826
+ console.log(`[pancakeQuickBatchSwapMerkle] 模式: ${useNativeToken ? 'BNB' : 'ERC20'}`);
827
+ console.log(`[pancakeQuickBatchSwapMerkle] 卖出数量: ${ethers.formatUnits(sellAmountWei, decimals)}`);
828
+ console.log(`[pancakeQuickBatchSwapMerkle] routeParams:`, JSON.stringify({
829
+ routeType: routeParams.routeType,
830
+ v2Path: routeParams.v2Path,
831
+ v3TokenIn: routeParams.v3TokenIn,
832
+ v3TokenOut: routeParams.v3TokenOut,
833
+ v3Fee: routeParams.v3Fee
834
+ }));
977
835
  // ✅ 获取卖出报价
978
836
  const quoteResult = await quoteSellOutput({
979
837
  routeParams,
980
838
  sellAmountWei,
981
839
  provider: context.provider
982
840
  });
983
- const estimatedBNBOut = quoteResult.estimatedBNBOut;
984
- // 计算利润(从卖出所得中预扣)
985
- const profitAmount = calculateProfitAmount(estimatedBNBOut);
986
- const distributableBNB = estimatedBNBOut - profitAmount; // 可分配金额
987
- // 计算每个子钱包分到的金额
841
+ const estimatedOutput = quoteResult.estimatedBNBOut;
842
+ const outputFormatted = useNativeToken
843
+ ? ethers.formatEther(estimatedOutput)
844
+ : ethers.formatUnits(estimatedOutput, quoteTokenDecimals);
845
+ console.log(`[pancakeQuickBatchSwapMerkle] 预估卖出所得: ${outputFormatted} ${useNativeToken ? 'BNB' : 'ERC20'}`);
846
+ // ✅ 计算利润(基于 BNB 价值)
847
+ let profitAmount;
848
+ if (useNativeToken) {
849
+ profitAmount = calculateProfitAmount(estimatedOutput);
850
+ }
851
+ else {
852
+ // ERC20 模式:需要将 ERC20 价值转换为 BNB
853
+ const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
854
+ const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
855
+ const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, estimatedOutput, version, fee);
856
+ profitAmount = calculateProfitAmount(estimatedBNBValue);
857
+ console.log(`[pancakeQuickBatchSwapMerkle] ERC20→BNB 报价: ${outputFormatted} → ${ethers.formatEther(estimatedBNBValue)} BNB`);
858
+ }
859
+ const distributableAmount = estimatedOutput - (useNativeToken ? profitAmount : 0n);
860
+ // ✅ 计算每个买方分到的金额
988
861
  let transferAmountsWei;
989
862
  if (buyerAmounts && buyerAmounts.length === buyers.length) {
990
- // 数量模式:使用指定的金额
991
- transferAmountsWei = buyerAmounts.map(amt => ethers.parseEther(amt));
992
- // 校验总金额不超过可分配金额
863
+ // 数量模式
864
+ transferAmountsWei = buyerAmounts.map(amt => useNativeToken
865
+ ? ethers.parseEther(amt)
866
+ : ethers.parseUnits(amt, quoteTokenDecimals));
993
867
  const totalTransfer = transferAmountsWei.reduce((a, b) => a + b, 0n);
994
- if (totalTransfer > distributableBNB) {
995
- throw new Error(`指定的买入总金额 (${ethers.formatEther(totalTransfer)} BNB) 超过可分配金额 (${ethers.formatEther(distributableBNB)} BNB)`);
868
+ if (totalTransfer > distributableAmount) {
869
+ const formatted = useNativeToken
870
+ ? ethers.formatEther(distributableAmount)
871
+ : ethers.formatUnits(distributableAmount, quoteTokenDecimals);
872
+ throw new Error(`指定的买入总金额超过可分配金额 (${formatted})`);
996
873
  }
997
874
  }
998
875
  else if (buyerRatios && buyerRatios.length === buyers.length) {
999
- // 比例模式:按比例分配可分配金额
876
+ // 比例模式
1000
877
  transferAmountsWei = buyerRatios.map(ratio => {
1001
- const amount = (distributableBNB * BigInt(Math.round(ratio * 10000))) / 10000n;
1002
- return amount;
878
+ return (distributableAmount * BigInt(Math.round(ratio * 10000))) / 10000n;
1003
879
  });
1004
880
  }
1005
881
  else {
@@ -1009,17 +885,30 @@ export async function pancakeQuickBatchSwapMerkle(params) {
1009
885
  const bribeAmount = config.bribeAmount && config.bribeAmount > 0
1010
886
  ? ethers.parseEther(String(config.bribeAmount))
1011
887
  : 0n;
1012
- // ✅ 验证主钱包余额(需要支付 Gas 费用)
888
+ // ✅ 验证主钱包余额
1013
889
  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`);
890
+ let sellerGasCost;
891
+ let sellerRequired;
892
+ if (useNativeToken) {
893
+ // BNB 模式:贿赂(21000) + 卖出(gasLimit) + N个原生转账(21000 each) + 利润(21000)
894
+ sellerGasCost = gasPrice * (21000n + finalGasLimit + 21000n * BigInt(buyers.length) + 21000n);
895
+ sellerRequired = bribeAmount + sellerGasCost;
896
+ }
897
+ else {
898
+ // ERC20 模式:
899
+ // - 贿赂(21000) + 卖出(gasLimit) + N个ERC20转账(65000 each) + N个BNB Gas转账(21000 each) + 利润(21000)
900
+ // - 还需要给买家转 BNB 用于支付 FLAT_FEE 和买入 Gas
901
+ sellerGasCost = gasPrice * (21000n + finalGasLimit + (ERC20_TRANSFER_GAS + 21000n) * BigInt(buyers.length) + 21000n);
902
+ const buyerGasNeeded = (gasPrice * finalGasLimit + FLAT_FEE) * BigInt(buyers.length);
903
+ sellerRequired = bribeAmount + sellerGasCost + buyerGasNeeded;
904
+ }
905
+ if (sellerBalance < sellerRequired) {
906
+ throw new Error(`主钱包 BNB 余额不足: 需要约 ${ethers.formatEther(sellerRequired)} BNB (贿赂: ${ethers.formatEther(bribeAmount)}, Gas: ${ethers.formatEther(sellerGasCost)}), 实际 ${ethers.formatEther(sellerBalance)} BNB`);
1017
907
  }
1018
908
  // ==================== 规划 Nonce ====================
1019
- // 卖方: [贿赂(可选)] → [卖出] → [转账1, 转账2, ...] → [利润(可选)]
1020
- // 买方: [买入]
1021
909
  let sellerNonce = await nonceManager.getNextNonce(seller);
1022
- // 贿赂交易(首位)
910
+ const deadline = Math.floor(Date.now() / 1000) + 600;
911
+ // ==================== 1. 贿赂交易 ====================
1023
912
  let bribeTx = null;
1024
913
  if (bribeAmount > 0n) {
1025
914
  bribeTx = await seller.signTransaction({
@@ -1031,10 +920,10 @@ export async function pancakeQuickBatchSwapMerkle(params) {
1031
920
  chainId: context.chainId,
1032
921
  type: txType
1033
922
  });
923
+ console.log(`[pancakeQuickBatchSwapMerkle] 贿赂交易已签名`);
1034
924
  }
1035
- // 卖出交易
925
+ // ==================== 2. 卖出交易 ====================
1036
926
  const proxySeller = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, seller);
1037
- const deadline = Math.floor(Date.now() / 1000) + 600;
1038
927
  let sellUnsigned;
1039
928
  if (routeParams.routeType === 'v2') {
1040
929
  const { v2Path } = routeParams;
@@ -1057,40 +946,72 @@ export async function pancakeQuickBatchSwapMerkle(params) {
1057
946
  chainId: context.chainId,
1058
947
  type: txType
1059
948
  });
1060
- // ✅ 转账交易(主钱包 → 各子钱包)
949
+ console.log(`[pancakeQuickBatchSwapMerkle] 卖出交易已签名`);
950
+ // ==================== 3. 转账交易 ====================
1061
951
  const transferTxs = [];
1062
- for (let i = 0; i < buyers.length; i++) {
1063
- const transferTx = await seller.signTransaction({
1064
- to: buyers[i].address,
1065
- value: transferAmountsWei[i],
1066
- nonce: sellerNonce++,
1067
- gasPrice,
1068
- gasLimit: 21000n,
1069
- chainId: context.chainId,
1070
- type: txType
1071
- });
1072
- transferTxs.push(transferTx);
952
+ if (useNativeToken) {
953
+ // 原生代币模式:直接 BNB 转账
954
+ for (let i = 0; i < buyers.length; i++) {
955
+ const buyerGasCost = gasPrice * finalGasLimit;
956
+ const transferValue = transferAmountsWei[i] + FLAT_FEE + buyerGasCost;
957
+ const transferTx = await seller.signTransaction({
958
+ to: buyers[i].address,
959
+ value: transferValue,
960
+ nonce: sellerNonce++,
961
+ gasPrice,
962
+ gasLimit: 21000n,
963
+ chainId: context.chainId,
964
+ type: txType
965
+ });
966
+ transferTxs.push(transferTx);
967
+ }
1073
968
  }
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
- });
969
+ else {
970
+ // ERC20 模式:ERC20 transfer 调用
971
+ const erc20Interface = new ethers.Interface([
972
+ 'function transfer(address to, uint256 amount) returns (bool)'
973
+ ]);
974
+ for (let i = 0; i < buyers.length; i++) {
975
+ const transferData = erc20Interface.encodeFunctionData('transfer', [
976
+ buyers[i].address,
977
+ transferAmountsWei[i]
978
+ ]);
979
+ const transferTx = await seller.signTransaction({
980
+ to: quoteToken,
981
+ data: transferData,
982
+ value: 0n,
983
+ nonce: sellerNonce++,
984
+ gasPrice,
985
+ gasLimit: ERC20_TRANSFER_GAS,
986
+ chainId: context.chainId,
987
+ type: txType
988
+ });
989
+ transferTxs.push(transferTx);
990
+ }
991
+ // ERC20 模式:额外转账 Gas 费用给买家(用于支付 FLAT_FEE 和买入 Gas)
992
+ for (let i = 0; i < buyers.length; i++) {
993
+ const buyerGasCost = gasPrice * finalGasLimit + FLAT_FEE;
994
+ const gasTx = await seller.signTransaction({
995
+ to: buyers[i].address,
996
+ value: buyerGasCost,
997
+ nonce: sellerNonce++,
998
+ gasPrice,
999
+ gasLimit: 21000n,
1000
+ chainId: context.chainId,
1001
+ type: txType
1002
+ });
1003
+ transferTxs.push(gasTx);
1004
+ }
1086
1005
  }
1087
- // 并行获取所有买方的 nonce
1006
+ console.log(`[pancakeQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名`);
1007
+ // ==================== 4. 买入交易 ====================
1088
1008
  const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
1089
- // ✅ 并行构建买入交易
1090
1009
  const signedBuys = await Promise.all(buyers.map(async (buyer, i) => {
1091
1010
  const buyAmount = transferAmountsWei[i];
1092
1011
  const proxyBuyer = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, buyer);
1093
- const buyValue = buyAmount + FLAT_FEE;
1012
+ // BNB 模式:value = buyAmount + FLAT_FEE
1013
+ // ERC20 模式:value = FLAT_FEE(买入金额是 ERC20,通过授权支付)
1014
+ const buyValue = useNativeToken ? buyAmount + FLAT_FEE : FLAT_FEE;
1094
1015
  let buyUnsigned;
1095
1016
  if (routeParams.routeType === 'v2') {
1096
1017
  const { v2Path } = routeParams;
@@ -1115,25 +1036,52 @@ export async function pancakeQuickBatchSwapMerkle(params) {
1115
1036
  type: txType
1116
1037
  });
1117
1038
  }));
1039
+ console.log(`[pancakeQuickBatchSwapMerkle] ${signedBuys.length} 笔买入交易已签名`);
1040
+ // ==================== 5. 利润交易 ====================
1041
+ let profitTx = null;
1042
+ if (profitAmount > 0n) {
1043
+ profitTx = await seller.signTransaction({
1044
+ to: PROFIT_CONFIG.RECIPIENT,
1045
+ value: profitAmount,
1046
+ nonce: sellerNonce++,
1047
+ gasPrice,
1048
+ gasLimit: 21000n,
1049
+ chainId: context.chainId,
1050
+ type: txType
1051
+ });
1052
+ console.log(`[pancakeQuickBatchSwapMerkle] 利润交易已签名`);
1053
+ }
1118
1054
  nonceManager.clearTemp();
1119
- // 按顺序组装交易数组:贿赂 → 卖出 → 转账 → 买入 → 利润
1055
+ // ==================== 组装交易数组 ====================
1120
1056
  const signedTransactions = [];
1121
1057
  if (bribeTx)
1122
- signedTransactions.push(bribeTx); // 贿赂(首位)
1123
- signedTransactions.push(signedSell); // 卖出
1124
- signedTransactions.push(...transferTxs); // 转账到各子钱包
1125
- signedTransactions.push(...signedBuys); // 各子钱包买入
1058
+ signedTransactions.push(bribeTx);
1059
+ signedTransactions.push(signedSell);
1060
+ signedTransactions.push(...transferTxs);
1061
+ signedTransactions.push(...signedBuys);
1126
1062
  if (profitTx)
1127
- signedTransactions.push(profitTx); // 利润(末尾)
1063
+ signedTransactions.push(profitTx);
1064
+ console.log(`[pancakeQuickBatchSwapMerkle] 交易组装完成: ${signedTransactions.length} 笔`);
1065
+ console.log(` - 贿赂: ${bribeTx ? 1 : 0}`);
1066
+ console.log(` - 卖出: 1`);
1067
+ console.log(` - 转账: ${transferTxs.length}`);
1068
+ console.log(` - 买入: ${signedBuys.length}`);
1069
+ console.log(` - 利润: ${profitTx ? 1 : 0}`);
1070
+ const outputUnit = useNativeToken ? 'BNB' : 'ERC20';
1128
1071
  return {
1129
1072
  signedTransactions,
1130
1073
  metadata: {
1131
1074
  sellerAddress: seller.address,
1132
1075
  buyerAddresses: buyers.map(b => b.address),
1133
1076
  sellAmount: ethers.formatUnits(sellAmountWei, decimals),
1134
- estimatedBNBOut: ethers.formatEther(estimatedBNBOut),
1135
- transferAmounts: transferAmountsWei.map(amt => ethers.formatEther(amt)),
1136
- profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
1077
+ estimatedOutput: useNativeToken
1078
+ ? ethers.formatEther(estimatedOutput)
1079
+ : ethers.formatUnits(estimatedOutput, quoteTokenDecimals),
1080
+ transferAmounts: transferAmountsWei.map(amt => useNativeToken
1081
+ ? ethers.formatEther(amt)
1082
+ : ethers.formatUnits(amt, quoteTokenDecimals)),
1083
+ profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
1084
+ useNativeToken
1137
1085
  }
1138
1086
  };
1139
1087
  }