four-flap-meme-sdk 1.4.24 → 1.4.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/abis/common.d.ts +85 -0
  2. package/dist/abis/common.js +242 -0
  3. package/dist/abis/index.d.ts +1 -0
  4. package/dist/abis/index.js +2 -0
  5. package/dist/contracts/tm-bundle-merkle/approve-tokenmanager.js +1 -6
  6. package/dist/contracts/tm-bundle-merkle/core.js +9 -16
  7. package/dist/contracts/tm-bundle-merkle/internal.js +6 -8
  8. package/dist/contracts/tm-bundle-merkle/pancake-proxy.d.ts +12 -6
  9. package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +224 -166
  10. package/dist/contracts/tm-bundle-merkle/private.js +6 -19
  11. package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +2 -7
  12. package/dist/contracts/tm-bundle-merkle/swap-internal.d.ts +1 -1
  13. package/dist/contracts/tm-bundle-merkle/swap-internal.js +9 -2
  14. package/dist/contracts/tm-bundle-merkle/swap.js +1 -3
  15. package/dist/contracts/tm-bundle-merkle/types.d.ts +20 -0
  16. package/dist/contracts/tm-bundle-merkle/utils.js +164 -175
  17. package/dist/dex/direct-router.d.ts +2 -1
  18. package/dist/dex/direct-router.js +25 -140
  19. package/dist/flap/constants.d.ts +2 -1
  20. package/dist/flap/constants.js +2 -1
  21. package/dist/flap/meta.js +6 -4
  22. package/dist/flap/portal-bundle-merkle/config.js +6 -12
  23. package/dist/flap/portal-bundle-merkle/core.js +8 -11
  24. package/dist/flap/portal-bundle-merkle/pancake-proxy.d.ts +12 -10
  25. package/dist/flap/portal-bundle-merkle/pancake-proxy.js +307 -370
  26. package/dist/flap/portal-bundle-merkle/private.js +1 -1
  27. package/dist/flap/portal-bundle-merkle/swap-buy-first.js +12 -30
  28. package/dist/flap/portal-bundle-merkle/swap.js +13 -26
  29. package/dist/flap/portal-bundle-merkle/types.d.ts +22 -2
  30. package/dist/flap/portal-bundle-merkle/utils.js +11 -16
  31. package/dist/index.d.ts +3 -2
  32. package/dist/index.js +9 -2
  33. package/dist/pancake/bundle-buy-first.js +56 -38
  34. package/dist/pancake/bundle-swap.js +114 -61
  35. package/dist/utils/bundle-helpers.d.ts +28 -0
  36. package/dist/utils/bundle-helpers.js +64 -0
  37. package/dist/utils/constants.d.ts +23 -1
  38. package/dist/utils/constants.js +37 -7
  39. package/dist/utils/erc20.js +17 -25
  40. package/dist/utils/lp-inspect.js +9 -20
  41. package/dist/utils/private-sale.js +1 -2
  42. package/dist/utils/quote-helpers.js +3 -29
  43. package/dist/utils/swap-helpers.js +1 -6
  44. package/dist/utils/wallet.d.ts +8 -13
  45. package/dist/utils/wallet.js +154 -342
  46. package/package.json +1 -1
@@ -1,59 +1,37 @@
1
+ /**
2
+ * PancakeSwap 官方 Router 交易模块
3
+ *
4
+ * ✅ 使用官方 PancakeSwap V2/V3 Router(非代理合约)
5
+ * - V2 Router: 0x10ED43C718714eb63d5aA57B78B54704E256024E
6
+ * - V3 Router: 0x13f4EA83D0bd40E75C8222255bc855a974568Dd4
7
+ * - V3 Quoter: 0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997
8
+ */
1
9
  import { ethers, Wallet, JsonRpcProvider, Contract, Interface } from 'ethers';
2
- import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
3
- import { ADDRESSES } from '../../utils/constants.js';
10
+ import { NonceManager, getOptimizedGasPrice, getDeadline, encodeV3Path } from '../../utils/bundle-helpers.js';
11
+ import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA } from '../../utils/constants.js';
12
+ import { MULTICALL3_ABI, V2_ROUTER_ABI, V3_ROUTER02_ABI, V3_QUOTER_ABI, ERC20_ABI } from '../../abis/common.js';
4
13
  import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount } from './config.js';
5
- // 已移除授权检查(前端负责确保授权)
6
- // BlockRazor Builder EOA 地址(用于贿赂)
7
- const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
8
- const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
9
- const MULTICALL3_ABI = [
10
- 'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) external payable returns (tuple(bool success, bytes returnData)[])'
11
- ];
12
- // 常量
13
- const WBNB_ADDRESS = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
14
- const FLAT_FEE = 0n; // 已移除合约固定手续费
15
- const DEFAULT_GAS_LIMIT = 800000; // ✅ 改为 number,配合 getGasLimit 使用
16
- const DEADLINE_MINUTES = 20;
17
- // PancakeSwapProxy ABI(PancakeSwap V3 没有 deadline 参数)
18
- const PANCAKE_PROXY_ABI = [
19
- 'function swapV2(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) external payable returns (uint256 amountOut)',
20
- 'function swapV3Single(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint256 amountOutMin, address to) external payable returns (uint256 amountOut)',
21
- 'function swapV3MultiHop(address[] calldata lpAddresses, address exactTokenIn, uint256 amountIn, uint256 amountOutMin, address to) external payable returns (uint256 amountOut)'
22
- ];
23
- // PancakeSwap V2 Router ABI(用于报价)
24
- const PANCAKE_V2_ROUTER_ABI = [
25
- 'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
26
- ];
27
- // PancakeSwap V3 QuoterV2 ABI(用于报价)
28
- const PANCAKE_V3_QUOTER_ABI = [
29
- 'function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96)) external returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)'
30
- ];
31
- const PANCAKE_V2_ROUTER_ADDRESS = '0x10ED43C718714eb63d5aA57B78B54704E256024E';
32
- const PANCAKE_V3_QUOTER_ADDRESS = '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997';
33
- // ERC20 ABI
34
- const ERC20_ABI = [
35
- 'function approve(address spender, uint256 amount) external returns (bool)',
36
- 'function allowance(address owner, address spender) external view returns (uint256)',
37
- 'function balanceOf(address account) external view returns (uint256)',
38
- 'function decimals() external view returns (uint8)'
39
- ];
14
+ // ==================== 常量 ====================
15
+ const MULTICALL3_ADDRESS = ADDRESSES.BSC.Multicall3;
16
+ const WBNB_ADDRESS = ADDRESSES.BSC.WBNB;
17
+ const DEFAULT_GAS_LIMIT = 800000;
18
+ // PancakeSwap 官方合约地址
19
+ const PANCAKE_V2_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV2Router;
20
+ const PANCAKE_V3_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV3Router;
21
+ const PANCAKE_V3_QUOTER_ADDRESS = ADDRESSES.BSC.PancakeV3Quoter;
22
+ // ==================== ABI 别名(从公共模块导入)====================
23
+ // 使用公共 ABI:V2_ROUTER_ABI, V3_ROUTER02_ABI (as V3_ROUTER_ABI), V3_QUOTER_ABI, ERC20_ABI, MULTICALL3_ABI
24
+ const V3_ROUTER_ABI = V3_ROUTER02_ABI;
40
25
  // ==================== 辅助函数 ====================
41
26
  /**
42
27
  * 获取 Gas Limit
43
- * 优先使用 config.gasLimit,否则使用默认值 * multiplier
44
- *
45
- * ✅ ethers v6 最佳实践:直接使用 bigint 类型
46
28
  */
47
29
  function getGasLimit(config, defaultGas = DEFAULT_GAS_LIMIT) {
48
- // 优先使用前端传入的 gasLimit
49
30
  if (config.gasLimit !== undefined) {
50
- // 如果已经是 bigint,直接返回;否则转换
51
31
  return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
52
32
  }
53
- // 使用默认值 * multiplier
54
33
  const multiplier = config.gasLimitMultiplier ?? 1.0;
55
34
  const calculatedGas = Math.ceil(defaultGas * multiplier);
56
- // JavaScript 原生 BigInt 转换(ethers v6 兼容)
57
35
  return BigInt(calculatedGas);
58
36
  }
59
37
  /**
@@ -66,12 +44,11 @@ async function getTokenDecimals(tokenAddress, provider) {
66
44
  return Number(decimals);
67
45
  }
68
46
  catch {
69
- // 默认返回 18,兼容大部分 ERC20
70
47
  return 18;
71
48
  }
72
49
  }
73
50
  /**
74
- * 判断是否需要发送 BNB(tokenIn 是 WBNB 时)
51
+ * 判断是否需要发送 BNB
75
52
  */
76
53
  function needSendBNB(routeType, params) {
77
54
  if (routeType === 'v2' && params.v2Path && params.v2Path[0].toLowerCase() === WBNB_ADDRESS.toLowerCase()) {
@@ -85,50 +62,149 @@ function needSendBNB(routeType, params) {
85
62
  }
86
63
  return false;
87
64
  }
65
+ // ✅ getDeadline 从 bundle-helpers.js 导入
88
66
  /**
89
- * 计算 deadline
67
+ * 构建 V2 交易(使用官方 Router)
90
68
  */
91
- function getDeadline(minutes = DEADLINE_MINUTES) {
92
- return Math.floor(Date.now() / 1000) + 60 * minutes;
93
- }
94
- /**
95
- * 构建 V2 交易
96
- */
97
- async function buildV2Transactions(proxies, wallets, amountsWei, minOuts, path, isBuy, needBNB) {
69
+ async function buildV2Transactions(routers, wallets, amountsWei, minOuts, path, isBuy, needBNB) {
98
70
  const deadline = getDeadline();
99
- return Promise.all(proxies.map((proxy, i) => {
100
- const txValue = (isBuy && needBNB) ? amountsWei[i] + FLAT_FEE : FLAT_FEE;
101
- return proxy.swapV2.populateTransaction(amountsWei[i], minOuts[i], path, wallets[i].address, deadline, { value: txValue });
71
+ return Promise.all(routers.map(async (router, i) => {
72
+ if (isBuy && needBNB) {
73
+ // 买入:BNB Token,使用 swapExactETHForTokensSupportingFeeOnTransferTokens
74
+ return router.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(minOuts[i], // amountOutMin
75
+ path, // path
76
+ wallets[i].address, // to
77
+ deadline, // deadline
78
+ { value: amountsWei[i] } // 发送的 BNB
79
+ );
80
+ }
81
+ else {
82
+ // ✅ 卖出:Token → BNB,使用 swapExactTokensForETHSupportingFeeOnTransferTokens
83
+ return router.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(amountsWei[i], // amountIn
84
+ minOuts[i], // amountOutMin
85
+ path, // path
86
+ wallets[i].address, // to
87
+ deadline // deadline
88
+ );
89
+ }
102
90
  }));
103
91
  }
104
92
  /**
105
- * 构建 V3 单跳交易(PancakeSwap V3 没有 deadline
93
+ * 构建 V3 单跳交易(使用官方 SwapRouter02 + multicall
106
94
  */
107
- async function buildV3SingleTransactions(proxies, wallets, tokenIn, tokenOut, fee, amountsWei, minOuts, isBuy, needBNB) {
108
- return Promise.all(proxies.map((proxy, i) => {
109
- const txValue = (isBuy && needBNB) ? amountsWei[i] + FLAT_FEE : FLAT_FEE;
110
- return proxy.swapV3Single.populateTransaction(tokenIn, tokenOut, fee, amountsWei[i], minOuts[i], wallets[i].address, { value: txValue });
95
+ async function buildV3SingleTransactions(routers, wallets, tokenIn, tokenOut, fee, amountsWei, minOuts, isBuy, needBNB) {
96
+ const deadline = getDeadline();
97
+ const v3RouterIface = new Interface(V3_ROUTER_ABI);
98
+ return Promise.all(routers.map(async (router, i) => {
99
+ const isTokenOutWBNB = tokenOut.toLowerCase() === WBNB_ADDRESS.toLowerCase();
100
+ // ✅ 构建 exactInputSingle calldata
101
+ const exactInputSingleData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
102
+ tokenIn: tokenIn,
103
+ tokenOut: tokenOut,
104
+ fee: fee,
105
+ recipient: isTokenOutWBNB ? PANCAKE_V3_ROUTER_ADDRESS : wallets[i].address, // 如果输出是 WBNB,先发给 Router
106
+ amountIn: amountsWei[i],
107
+ amountOutMinimum: minOuts[i],
108
+ sqrtPriceLimitX96: 0n
109
+ }]);
110
+ if (isBuy && needBNB) {
111
+ // ✅ 买入:BNB → Token,只需要 exactInputSingle
112
+ // 通过 multicall 包装,传入 deadline 和 ETH value
113
+ return router.multicall.populateTransaction(deadline, [exactInputSingleData], { value: amountsWei[i] });
114
+ }
115
+ else if (!isBuy && isTokenOutWBNB) {
116
+ // ✅ 卖出:Token → WBNB → BNB,需要 exactInputSingle + unwrapWETH9
117
+ const unwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [
118
+ minOuts[i], // amountMinimum
119
+ wallets[i].address // recipient
120
+ ]);
121
+ return router.multicall.populateTransaction(deadline, [exactInputSingleData, unwrapData]);
122
+ }
123
+ else {
124
+ // ✅ Token → Token,只需要 exactInputSingle
125
+ return router.multicall.populateTransaction(deadline, [exactInputSingleData]);
126
+ }
111
127
  }));
112
128
  }
113
129
  /**
114
- * 构建 V3 多跳交易(PancakeSwap V3 没有 deadline
130
+ * 构建 V3 多跳交易(使用官方 SwapRouter02 exactInput
131
+ *
132
+ * @param routers - V3 Router 合约数组
133
+ * @param wallets - 钱包数组
134
+ * @param tokens - 代币路径 [tokenIn, tokenMid1, ..., tokenOut]
135
+ * @param fees - 费率路径 [fee0, fee1, ...]
136
+ * @param amountsWei - 每个钱包的输入金额
137
+ * @param minOuts - 最小输出金额
138
+ * @param isBuy - 是否为买入操作
139
+ * @param needBNB - 是否需要发送 BNB
115
140
  */
116
- async function buildV3MultiHopTransactions(proxies, wallets, lpAddresses, exactTokenIn, amountsWei, minOuts, isBuy, needBNB) {
117
- return Promise.all(proxies.map((proxy, i) => {
118
- const txValue = (isBuy && needBNB) ? amountsWei[i] + FLAT_FEE : FLAT_FEE;
119
- return proxy.swapV3MultiHop.populateTransaction(lpAddresses, exactTokenIn, amountsWei[i], minOuts[i], wallets[i].address, { value: txValue });
141
+ async function buildV3MultiHopTransactions(routers, wallets, tokens, fees, amountsWei, minOuts, isBuy, needBNB) {
142
+ if (!tokens || tokens.length < 2) {
143
+ throw new Error('V3 多跳需要至少 2 个代币(v3Tokens)');
144
+ }
145
+ if (!fees || fees.length !== tokens.length - 1) {
146
+ throw new Error(`V3 多跳费率数量 (${fees?.length || 0}) 必须等于代币数量 - 1 (${tokens.length - 1})(v3Fees)`);
147
+ }
148
+ const deadline = getDeadline();
149
+ const v3RouterIface = new Interface(V3_ROUTER_ABI);
150
+ // 编码正向 path(用于买入:BNB → Token)
151
+ const forwardPath = encodeV3Path(tokens, fees);
152
+ // 编码反向 path(用于卖出:Token → BNB)
153
+ const reversedTokens = [...tokens].reverse();
154
+ const reversedFees = [...fees].reverse();
155
+ const reversePath = encodeV3Path(reversedTokens, reversedFees);
156
+ const tokenOut = tokens[tokens.length - 1];
157
+ const isTokenOutWBNB = tokenOut.toLowerCase() === WBNB_ADDRESS.toLowerCase();
158
+ return Promise.all(routers.map(async (router, i) => {
159
+ if (isBuy && needBNB) {
160
+ // ✅ 买入:BNB → ... → Token
161
+ const swapData = v3RouterIface.encodeFunctionData('exactInput', [{
162
+ path: forwardPath,
163
+ recipient: wallets[i].address,
164
+ amountIn: amountsWei[i],
165
+ amountOutMinimum: minOuts[i]
166
+ }]);
167
+ return router.multicall.populateTransaction(deadline, [swapData], { value: amountsWei[i] });
168
+ }
169
+ else if (!isBuy) {
170
+ // ✅ 卖出:Token → ... → BNB
171
+ const swapData = v3RouterIface.encodeFunctionData('exactInput', [{
172
+ path: reversePath,
173
+ recipient: isTokenOutWBNB ? PANCAKE_V3_ROUTER_ADDRESS : wallets[i].address,
174
+ amountIn: amountsWei[i],
175
+ amountOutMinimum: minOuts[i]
176
+ }]);
177
+ if (isTokenOutWBNB) {
178
+ // 需要 unwrapWETH9
179
+ const unwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [
180
+ minOuts[i],
181
+ wallets[i].address
182
+ ]);
183
+ return router.multicall.populateTransaction(deadline, [swapData, unwrapData]);
184
+ }
185
+ return router.multicall.populateTransaction(deadline, [swapData]);
186
+ }
187
+ else {
188
+ // Token → Token(不需要 BNB)
189
+ const swapData = v3RouterIface.encodeFunctionData('exactInput', [{
190
+ path: forwardPath,
191
+ recipient: wallets[i].address,
192
+ amountIn: amountsWei[i],
193
+ amountOutMinimum: minOuts[i]
194
+ }]);
195
+ return router.multicall.populateTransaction(deadline, [swapData]);
196
+ }
120
197
  }));
121
198
  }
122
199
  /**
123
- * ✅ 使用 Multicall3 批量获取 V2 报价(单次 RPC)
200
+ * ✅ 使用 Multicall3 批量获取 V2 报价
124
201
  */
125
202
  async function batchGetV2Quotes(provider, amountsWei, v2Path) {
126
203
  if (amountsWei.length === 0)
127
204
  return [];
128
- // 如果只有 1 个,直接调用(避免 multicall 开销)
129
205
  if (amountsWei.length === 1) {
130
206
  try {
131
- const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, provider);
207
+ const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, V2_ROUTER_ABI, provider);
132
208
  const amounts = await v2Router.getAmountsOut(amountsWei[0], v2Path);
133
209
  return [amounts[amounts.length - 1]];
134
210
  }
@@ -137,7 +213,7 @@ async function batchGetV2Quotes(provider, amountsWei, v2Path) {
137
213
  }
138
214
  }
139
215
  try {
140
- const v2RouterIface = new Interface(PANCAKE_V2_ROUTER_ABI);
216
+ const v2RouterIface = new Interface(V2_ROUTER_ABI);
141
217
  const multicall = new Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
142
218
  const calls = amountsWei.map(amount => ({
143
219
  target: PANCAKE_V2_ROUTER_ADDRESS,
@@ -160,10 +236,9 @@ async function batchGetV2Quotes(provider, amountsWei, v2Path) {
160
236
  });
161
237
  }
162
238
  catch {
163
- // Multicall 失败,回退到并行调用
164
239
  return await Promise.all(amountsWei.map(async (amount) => {
165
240
  try {
166
- const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, provider);
241
+ const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, V2_ROUTER_ABI, provider);
167
242
  const amounts = await v2Router.getAmountsOut(amount, v2Path);
168
243
  return amounts[amounts.length - 1];
169
244
  }
@@ -173,26 +248,34 @@ async function batchGetV2Quotes(provider, amountsWei, v2Path) {
173
248
  }));
174
249
  }
175
250
  }
251
+ /**
252
+ * 根据 routeType 获取授权目标地址
253
+ */
254
+ function getApprovalTarget(routeType) {
255
+ if (routeType === 'v2') {
256
+ return PANCAKE_V2_ROUTER_ADDRESS;
257
+ }
258
+ // V3 路由授权给 V3 Router
259
+ return PANCAKE_V3_ROUTER_ADDRESS;
260
+ }
176
261
  // ==================== 主要导出函数 ====================
177
262
  /**
178
- * 授权代币给 PancakeSwapProxy
263
+ * 授权代币给 PancakeSwap Router(根据 routeType 选择 V2/V3)
179
264
  */
180
265
  export async function approveFourPancakeProxy(params) {
181
- const { privateKey, tokenAddress, amount, rpcUrl } = params;
182
- const pancakeProxyAddress = ADDRESSES.BSC.PancakeProxy;
266
+ const { privateKey, tokenAddress, amount, rpcUrl, routeType } = params;
267
+ // 根据 routeType 选择授权目标
268
+ const approvalTarget = getApprovalTarget(routeType || 'v2');
183
269
  const provider = new JsonRpcProvider(rpcUrl);
184
270
  const wallet = new Wallet(privateKey, provider);
185
271
  const token = new Contract(tokenAddress, ERC20_ABI, wallet);
186
- // 查询 decimals
187
272
  const decimals = await getTokenDecimals(tokenAddress, provider);
188
- // 检查当前授权额度
189
- const currentAllowance = await token.allowance(wallet.address, pancakeProxyAddress);
273
+ const currentAllowance = await token.allowance(wallet.address, approvalTarget);
190
274
  const amountBigInt = amount === 'max' ? ethers.MaxUint256 : ethers.parseUnits(amount, decimals);
191
275
  if (currentAllowance >= amountBigInt) {
192
276
  return { txHash: '', approved: true };
193
277
  }
194
- // 执行授权
195
- const tx = await token.approve(pancakeProxyAddress, amountBigInt);
278
+ const tx = await token.approve(approvalTarget, amountBigInt);
196
279
  const receipt = await tx.wait();
197
280
  return {
198
281
  txHash: receipt.hash,
@@ -200,26 +283,26 @@ export async function approveFourPancakeProxy(params) {
200
283
  };
201
284
  }
202
285
  /**
203
- * 批量授权代币给 PancakeSwapProxy(内部直接广播提交)
286
+ * 批量授权代币给 PancakeSwap Router
204
287
  */
205
288
  export async function approveFourPancakeProxyBatch(params) {
206
- const { privateKeys, tokenAddress, amounts, config } = params;
289
+ const { privateKeys, tokenAddress, amounts, config, routeType } = params;
207
290
  if (privateKeys.length === 0 || amounts.length !== privateKeys.length) {
208
291
  throw new Error('Private key count and amount count must match');
209
292
  }
210
- const pancakeProxyAddress = ADDRESSES.BSC.PancakeProxy;
293
+ // 根据 routeType 选择授权目标
294
+ const approvalTarget = getApprovalTarget(routeType || 'v2');
211
295
  const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
212
296
  chainId: 56,
213
297
  name: 'BSC'
214
298
  });
215
299
  const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
216
- // 查询 decimals
217
300
  const decimals = await getTokenDecimals(tokenAddress, provider);
218
301
  const wallets = privateKeys.map(k => new Wallet(k, provider));
219
302
  const amountsBigInt = amounts.map(a => a === 'max' ? ethers.MaxUint256 : ethers.parseUnits(a, decimals));
220
- // 检查现有授权,过滤掉已经足够的
303
+ // 检查现有授权
221
304
  const tokens = wallets.map(w => new Contract(tokenAddress, ERC20_ABI, w));
222
- const allowances = await Promise.all(tokens.map((token, i) => token.allowance(wallets[i].address, pancakeProxyAddress)));
305
+ const allowances = await Promise.all(tokens.map((token, i) => token.allowance(wallets[i].address, approvalTarget)));
223
306
  // 只授权不足的
224
307
  const needApproval = wallets.filter((_, i) => allowances[i] < amountsBigInt[i]);
225
308
  const needApprovalAmounts = amountsBigInt.filter((amount, i) => allowances[i] < amount);
@@ -233,10 +316,8 @@ export async function approveFourPancakeProxyBatch(params) {
233
316
  }
234
317
  // 构建授权交易
235
318
  const needApprovalTokens = needApproval.map(w => new Contract(tokenAddress, ERC20_ABI, w));
236
- const unsignedApprovals = await Promise.all(needApprovalTokens.map((token, i) => token.approve.populateTransaction(pancakeProxyAddress, needApprovalAmounts[i])));
237
- // 使用前端传入的 gasLimit,否则使用默认值
319
+ const unsignedApprovals = await Promise.all(needApprovalTokens.map((token, i) => token.approve.populateTransaction(approvalTarget, needApprovalAmounts[i])));
238
320
  const finalGasLimit = getGasLimit(config);
239
- // 签名授权交易
240
321
  const nonceManager = new NonceManager(provider);
241
322
  const nonces = await Promise.all(needApproval.map(w => nonceManager.getNextNonce(w)));
242
323
  const txType = getTxType(config);
@@ -250,7 +331,7 @@ export async function approveFourPancakeProxyBatch(params) {
250
331
  type: txType
251
332
  })));
252
333
  nonceManager.clearTemp();
253
- // ✅ 内部直接广播提交(逐个发送)
334
+ // 广播交易
254
335
  const txHashes = [];
255
336
  const errors = [];
256
337
  for (let i = 0; i < signedTxs.length; i++) {
@@ -283,57 +364,47 @@ export async function approveFourPancakeProxyBatch(params) {
283
364
  }
284
365
  }
285
366
  /**
286
- * 使用 PancakeSwapProxy 批量购买代币(Merkle 版本)
367
+ * 使用 PancakeSwap 官方 Router 批量购买代币
287
368
  */
288
369
  export async function fourPancakeProxyBatchBuyMerkle(params) {
289
370
  const { privateKeys, buyAmounts, tokenAddress, routeType, config } = params;
290
371
  if (privateKeys.length === 0 || buyAmounts.length !== privateKeys.length) {
291
372
  throw new Error('Private key count and buy amount count must match');
292
373
  }
293
- const pancakeProxyAddress = ADDRESSES.BSC.PancakeProxy;
294
- // ✅ 直接使用传入的 RPC URL 创建 provider(不依赖 Merkle)
295
374
  const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
296
375
  chainId: 56,
297
376
  name: 'BSC'
298
377
  });
299
378
  const txType = getTxType(config);
300
379
  const buyers = privateKeys.map(k => new Wallet(k, provider));
301
- const originalAmountsWei = buyAmounts.map(a => ethers.parseEther(a)); // BNB 固定 18 位
302
- // ✅ 利润提取配置
380
+ const originalAmountsWei = buyAmounts.map(a => ethers.parseEther(a));
303
381
  const extractProfit = shouldExtractProfit(config);
304
382
  const { totalProfit, remainingAmounts } = calculateBatchProfit(originalAmountsWei, config);
305
- const actualAmountsWei = remainingAmounts; // 扣除利润后用于购买的金额
383
+ const actualAmountsWei = remainingAmounts;
306
384
  const finalGasLimit = getGasLimit(config);
307
385
  const nonceManager = new NonceManager(provider);
308
- // ✅ 获取贿赂金额
309
386
  const bribeAmount = getBribeAmount(config);
310
387
  const needBribeTx = bribeAmount > 0n;
311
- // ✅ 优化:并行获取 gasPrice 和 tokenDecimals
312
388
  const [gasPrice, tokenDecimals] = await Promise.all([
313
389
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
314
390
  getTokenDecimals(tokenAddress, provider)
315
391
  ]);
316
- // 修复:先计算需要的 nonce 数量,再统一获取
317
- // buyers[0] 可能需要 3 个 nonce(贿赂 + 买入 + 利润转账)
392
+ // 计算 nonce 分配
318
393
  const needProfitTx = extractProfit && totalProfit > 0n;
319
- let buyer0NeedCount = 1; // 买入交易
394
+ let buyer0NeedCount = 1;
320
395
  if (needBribeTx)
321
- buyer0NeedCount++; // 贿赂交易
396
+ buyer0NeedCount++;
322
397
  if (needProfitTx)
323
- buyer0NeedCount++; // 利润交易
324
- // 获取 buyers[0] 的连续 nonces
398
+ buyer0NeedCount++;
325
399
  const buyer0Nonces = await nonceManager.getNextNonceBatch(buyers[0], buyer0NeedCount);
326
- // 获取其他 buyers 的 nonces(如果有)
327
400
  let otherNonces = [];
328
401
  if (buyers.length > 1) {
329
402
  otherNonces = await nonceManager.getNextNoncesForWallets(buyers.slice(1));
330
403
  }
331
- // 分配 nonces
332
404
  let buyer0NonceIdx = 0;
333
405
  const bribeNonce = needBribeTx ? buyer0Nonces[buyer0NonceIdx++] : undefined;
334
406
  const buyer0BuyNonce = buyer0Nonces[buyer0NonceIdx++];
335
407
  const profitNonce = needProfitTx ? buyer0Nonces[buyer0NonceIdx] : undefined;
336
- // 组装最终的 nonces 数组
337
408
  const nonces = [buyer0BuyNonce, ...otherNonces];
338
409
  // 计算 minOutputAmounts
339
410
  let minOuts;
@@ -343,35 +414,41 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
343
414
  else {
344
415
  minOuts = new Array(buyers.length).fill(0n);
345
416
  }
346
- // 判断是否需要发送 BNB
347
417
  const needBNB = needSendBNB(routeType, params);
348
- // 构建交易(使用扣除利润后的金额)
349
- const proxies = buyers.map(w => new Contract(pancakeProxyAddress, PANCAKE_PROXY_ABI, w));
418
+ // ✅ 使用官方 Router
419
+ let routers;
350
420
  let unsignedBuys;
351
421
  if (routeType === 'v2') {
352
422
  if (!params.v2Path || params.v2Path.length < 2) {
353
423
  throw new Error('v2Path is required for V2 routing');
354
424
  }
355
- unsignedBuys = await buildV2Transactions(proxies, buyers, actualAmountsWei, minOuts, params.v2Path, true, needBNB);
425
+ routers = buyers.map(w => new Contract(PANCAKE_V2_ROUTER_ADDRESS, V2_ROUTER_ABI, w));
426
+ unsignedBuys = await buildV2Transactions(routers, buyers, actualAmountsWei, minOuts, params.v2Path, true, needBNB);
356
427
  }
357
428
  else if (routeType === 'v3-single') {
358
429
  if (!params.v3TokenIn || !params.v3Fee) {
359
430
  throw new Error('v3TokenIn and v3Fee are required for V3 single-hop');
360
431
  }
361
- unsignedBuys = await buildV3SingleTransactions(proxies, buyers, params.v3TokenIn, tokenAddress, params.v3Fee, actualAmountsWei, minOuts, true, needBNB);
432
+ routers = buyers.map(w => new Contract(PANCAKE_V3_ROUTER_ADDRESS, V3_ROUTER_ABI, w));
433
+ unsignedBuys = await buildV3SingleTransactions(routers, buyers, params.v3TokenIn, tokenAddress, params.v3Fee, actualAmountsWei, minOuts, true, needBNB);
362
434
  }
363
435
  else if (routeType === 'v3-multi') {
364
- if (!params.v3LpAddresses || params.v3LpAddresses.length === 0 || !params.v3ExactTokenIn) {
365
- throw new Error('v3LpAddresses and v3ExactTokenIn are required for V3 multi-hop');
436
+ // V3 多跳:需要 v3Tokens v3Fees 参数
437
+ if (!params.v3Tokens || params.v3Tokens.length < 2) {
438
+ throw new Error('v3Tokens(至少 2 个代币)是 V3 多跳必需的');
366
439
  }
367
- unsignedBuys = await buildV3MultiHopTransactions(proxies, buyers, params.v3LpAddresses, params.v3ExactTokenIn, actualAmountsWei, minOuts, true, needBNB);
440
+ if (!params.v3Fees || params.v3Fees.length !== params.v3Tokens.length - 1) {
441
+ throw new Error(`v3Fees 长度必须等于 v3Tokens 长度 - 1`);
442
+ }
443
+ routers = buyers.map(w => new Contract(PANCAKE_V3_ROUTER_ADDRESS, V3_ROUTER_ABI, w));
444
+ unsignedBuys = await buildV3MultiHopTransactions(routers, buyers, params.v3Tokens, params.v3Fees, actualAmountsWei, minOuts, true, needBNB);
368
445
  }
369
446
  else {
370
447
  throw new Error(`Unsupported routeType: ${routeType}`);
371
448
  }
372
- // ✅ 并行签名所有交易(贿赂、买入、利润)
449
+ // 签名交易
373
450
  const signPromises = [];
374
- // 贿赂交易(索引 0)
451
+ // 贿赂交易
375
452
  if (needBribeTx && bribeNonce !== undefined) {
376
453
  signPromises.push(buyers[0].signTransaction({
377
454
  to: BLOCKRAZOR_BUILDER_EOA,
@@ -383,7 +460,7 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
383
460
  type: txType
384
461
  }));
385
462
  }
386
- // 买入交易(索引 1 ~ N)
463
+ // 买入交易
387
464
  unsignedBuys.forEach((unsigned, i) => {
388
465
  signPromises.push(buyers[i].signTransaction({
389
466
  ...unsigned,
@@ -396,7 +473,7 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
396
473
  value: unsigned.value
397
474
  }));
398
475
  });
399
- // 利润交易(索引 N+1)
476
+ // 利润交易
400
477
  if (extractProfit && totalProfit > 0n && profitNonce !== undefined) {
401
478
  signPromises.push(buyers[0].signTransaction({
402
479
  to: getProfitRecipient(),
@@ -408,28 +485,20 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
408
485
  type: txType
409
486
  }));
410
487
  }
411
- // ✅ 并行签名完成后按顺序返回
412
488
  const signedTxs = await Promise.all(signPromises);
413
- // ✅ 清理临时 nonce 缓存
414
489
  nonceManager.clearTemp();
415
- // ✅ 直接返回签名交易(不提交到Merkle)
416
- // ✅ 简化返回:只返回签名交易
417
490
  return {
418
491
  signedTransactions: signedTxs
419
492
  };
420
493
  }
421
494
  /**
422
- * 使用 PancakeSwapProxy 批量卖出代币(仅签名版本 - 不依赖 Merkle)
423
- * ✅ 精简版:只负责签名交易,不提交到 Merkle
424
- * ✅ 自动检查授权,智能处理授权+卖出
495
+ * 使用 PancakeSwap 官方 Router 批量卖出代币
425
496
  */
426
497
  export async function fourPancakeProxyBatchSellMerkle(params) {
427
498
  const { privateKeys, sellAmounts, tokenAddress, routeType, config } = params;
428
499
  if (privateKeys.length === 0 || sellAmounts.length !== privateKeys.length) {
429
500
  throw new Error('Private key count and sell amount count must match');
430
501
  }
431
- const pancakeProxyAddress = ADDRESSES.BSC.PancakeProxy;
432
- // ✅ 直接使用传入的 RPC URL 创建 provider(不依赖 Merkle)
433
502
  const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
434
503
  chainId: 56,
435
504
  name: 'BSC'
@@ -439,26 +508,22 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
439
508
  const finalGasLimit = getGasLimit(config);
440
509
  const nonceManager = new NonceManager(provider);
441
510
  const extractProfit = shouldExtractProfit(config);
442
- // ✅ 获取贿赂金额
443
511
  const bribeAmount = getBribeAmount(config);
444
512
  const needBribeTx = bribeAmount > 0n;
445
- // ✅ 优化:并行获取 gasPrice 和 tokenDecimals(已移除授权检查,前端负责)
446
513
  const [gasPrice, tokenDecimals] = await Promise.all([
447
514
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
448
515
  getTokenDecimals(tokenAddress, provider)
449
516
  ]);
450
517
  const amountsWei = sellAmounts.map(a => ethers.parseUnits(a, tokenDecimals));
451
- // ✅ 获取报价(用于计算利润),已移除滑点保护
518
+ // 获取报价
452
519
  let quotedOutputs;
453
- // ✅ 使用 Multicall3 批量获取报价(仅 V2 路由支持)
454
520
  if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
455
521
  quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
456
522
  }
457
523
  else if (routeType === 'v3-single') {
458
- // V3 单跳:并行调用(V3 Quoter 是 non-view 函数,不支持 Multicall3)
459
524
  quotedOutputs = await Promise.all(amountsWei.map(async (amount) => {
460
525
  try {
461
- const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, PANCAKE_V3_QUOTER_ABI, provider);
526
+ const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, V3_QUOTER_ABI, provider);
462
527
  const result = await quoter.quoteExactInputSingle.staticCall({
463
528
  tokenIn: tokenAddress,
464
529
  tokenOut: params.v3TokenOut,
@@ -474,15 +539,13 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
474
539
  }));
475
540
  }
476
541
  else if (routeType === 'v3-multi' && params.v2Path && params.v2Path.length >= 2) {
477
- // V3 多跳:使用 V2 备选路由
478
542
  quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
479
543
  }
480
544
  else {
481
545
  quotedOutputs = new Array(amountsWei.length).fill(0n);
482
546
  }
483
- // ✅ 已移除滑点保护:minOuts 固定为 0
484
547
  const minOuts = new Array(sellers.length).fill(0n);
485
- // ✅ 计算利润并找出收益最多的钱包
548
+ // 计算利润
486
549
  let totalProfit = 0n;
487
550
  let maxRevenueIndex = 0;
488
551
  let maxRevenue = 0n;
@@ -498,37 +561,31 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
498
561
  }
499
562
  }
500
563
  }
501
- // 修复:先计算需要的 nonce 数量,再统一获取
564
+ // 分配 nonces
502
565
  const needProfitTx = extractProfit && totalProfit > 0n && maxRevenue > 0n;
503
- // 分配 nonces:收益最多的钱包可能需要 3 个 nonce(贿赂 + 卖出 + 利润转账)
504
566
  let nonces;
505
567
  let bribeNonce;
506
568
  let profitNonce;
507
569
  if (needBribeTx || needProfitTx) {
508
- // 计算收益最多的钱包需要的 nonce 数量
509
- let maxRevenueNonceCount = 1; // 卖出交易
570
+ let maxRevenueNonceCount = 1;
510
571
  if (needBribeTx)
511
- maxRevenueNonceCount++; // 贿赂交易
572
+ maxRevenueNonceCount++;
512
573
  if (needProfitTx)
513
- maxRevenueNonceCount++; // 利润交易
514
- // 收益最多的钱包需要连续 nonce
574
+ maxRevenueNonceCount++;
515
575
  const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], maxRevenueNonceCount);
516
- // 其他钱包各需要 1 个 nonce
517
576
  const otherSellers = sellers.filter((_, i) => i !== maxRevenueIndex);
518
577
  const otherNonces = otherSellers.length > 0
519
578
  ? await nonceManager.getNextNoncesForWallets(otherSellers)
520
579
  : [];
521
- // 分配收益最多钱包的 nonces
522
580
  let maxRevenueNonceIdx = 0;
523
581
  bribeNonce = needBribeTx ? maxRevenueNonces[maxRevenueNonceIdx++] : undefined;
524
582
  const maxRevenueSellNonce = maxRevenueNonces[maxRevenueNonceIdx++];
525
583
  profitNonce = needProfitTx ? maxRevenueNonces[maxRevenueNonceIdx] : undefined;
526
- // 组装最终的 nonces 数组(保持原顺序)
527
584
  nonces = [];
528
585
  let otherIdx = 0;
529
586
  for (let i = 0; i < sellers.length; i++) {
530
587
  if (i === maxRevenueIndex) {
531
- nonces.push(maxRevenueSellNonce); // 卖出交易 nonce
588
+ nonces.push(maxRevenueSellNonce);
532
589
  }
533
590
  else {
534
591
  nonces.push(otherNonces[otherIdx++]);
@@ -536,38 +593,43 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
536
593
  }
537
594
  }
538
595
  else {
539
- // 不需要贿赂和利润交易,所有钱包各 1 个 nonce
540
596
  nonces = await nonceManager.getNextNoncesForWallets(sellers);
541
597
  }
542
- // 卖出不需要发送 BNB,只需要 flatFee
598
+ // 使用官方 Router 构建卖出交易
543
599
  const needBNB = false;
544
- // 构建交易
545
- const proxies = sellers.map(w => new Contract(pancakeProxyAddress, PANCAKE_PROXY_ABI, w));
600
+ let routers;
546
601
  let unsignedSells;
547
602
  if (routeType === 'v2') {
548
603
  if (!params.v2Path || params.v2Path.length < 2) {
549
604
  throw new Error('v2Path is required for V2 routing');
550
605
  }
551
- unsignedSells = await buildV2Transactions(proxies, sellers, amountsWei, minOuts, params.v2Path, false, needBNB);
606
+ routers = sellers.map(w => new Contract(PANCAKE_V2_ROUTER_ADDRESS, V2_ROUTER_ABI, w));
607
+ unsignedSells = await buildV2Transactions(routers, sellers, amountsWei, minOuts, params.v2Path, false, needBNB);
552
608
  }
553
609
  else if (routeType === 'v3-single') {
554
610
  if (!params.v3TokenOut || !params.v3Fee) {
555
611
  throw new Error('v3TokenOut and v3Fee are required for V3 single-hop');
556
612
  }
557
- unsignedSells = await buildV3SingleTransactions(proxies, sellers, tokenAddress, params.v3TokenOut, params.v3Fee, amountsWei, minOuts, false, needBNB);
613
+ routers = sellers.map(w => new Contract(PANCAKE_V3_ROUTER_ADDRESS, V3_ROUTER_ABI, w));
614
+ unsignedSells = await buildV3SingleTransactions(routers, sellers, tokenAddress, params.v3TokenOut, params.v3Fee, amountsWei, minOuts, false, needBNB);
558
615
  }
559
616
  else if (routeType === 'v3-multi') {
560
- if (!params.v3LpAddresses || params.v3LpAddresses.length === 0 || !params.v3ExactTokenIn) {
561
- throw new Error('v3LpAddresses and v3ExactTokenIn are required for V3 multi-hop');
617
+ // V3 多跳:需要 v3Tokens v3Fees 参数
618
+ if (!params.v3Tokens || params.v3Tokens.length < 2) {
619
+ throw new Error('v3Tokens(至少 2 个代币)是 V3 多跳必需的');
620
+ }
621
+ if (!params.v3Fees || params.v3Fees.length !== params.v3Tokens.length - 1) {
622
+ throw new Error(`v3Fees 长度必须等于 v3Tokens 长度 - 1`);
562
623
  }
563
- unsignedSells = await buildV3MultiHopTransactions(proxies, sellers, params.v3LpAddresses, params.v3ExactTokenIn, amountsWei, minOuts, false, needBNB);
624
+ routers = sellers.map(w => new Contract(PANCAKE_V3_ROUTER_ADDRESS, V3_ROUTER_ABI, w));
625
+ unsignedSells = await buildV3MultiHopTransactions(routers, sellers, params.v3Tokens, params.v3Fees, amountsWei, minOuts, false, needBNB);
564
626
  }
565
627
  else {
566
628
  throw new Error(`Unsupported routeType: ${routeType}`);
567
629
  }
568
- // ✅ 并行签名所有交易(贿赂、卖出、利润)
630
+ // 签名交易
569
631
  const signPromises = [];
570
- // 贿赂交易(索引 0)
632
+ // 贿赂交易
571
633
  if (needBribeTx && bribeNonce !== undefined) {
572
634
  signPromises.push(sellers[maxRevenueIndex].signTransaction({
573
635
  to: BLOCKRAZOR_BUILDER_EOA,
@@ -579,7 +641,7 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
579
641
  type: txType
580
642
  }));
581
643
  }
582
- // 卖出交易(索引 1 ~ N)
644
+ // 卖出交易
583
645
  unsignedSells.forEach((unsigned, i) => {
584
646
  signPromises.push(sellers[i].signTransaction({
585
647
  ...unsigned,
@@ -592,7 +654,7 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
592
654
  value: unsigned.value
593
655
  }));
594
656
  });
595
- // 利润交易(索引 N+1)
657
+ // 利润交易
596
658
  if (extractProfit && totalProfit > 0n && profitNonce !== undefined) {
597
659
  signPromises.push(sellers[maxRevenueIndex].signTransaction({
598
660
  to: getProfitRecipient(),
@@ -604,12 +666,8 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
604
666
  type: txType
605
667
  }));
606
668
  }
607
- // ✅ 并行签名完成后按顺序返回
608
669
  const signedTxs = await Promise.all(signPromises);
609
- // ✅ 清理临时 nonce 缓存
610
670
  nonceManager.clearTemp();
611
- // ✅ 直接返回签名交易(不提交到Merkle)
612
- // ✅ 简化返回:只返回签名交易
613
671
  return {
614
672
  signedTransactions: signedTxs
615
673
  };