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,66 +1,57 @@
1
- import { ethers, Wallet, JsonRpcProvider, Contract, Interface } from 'ethers';
2
- import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
3
- import { ADDRESSES } from '../../utils/constants.js';
4
- import { CHAIN_ID_MAP, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA } from './config.js';
5
- const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
6
- const MULTICALL3_ABI = [
7
- 'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) external payable returns (tuple(bool success, bytes returnData)[])'
8
- ];
9
- // 常量
10
- const WBNB_ADDRESS = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
11
- const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
12
- const FLAT_FEE = 0n; // ✅ 已移除合约固定手续费
13
- const DEFAULT_GAS_LIMIT = 800000; // ✅ 改为 number,配合 getGasLimit 使用
14
- const DEADLINE_MINUTES = 20;
15
- // PancakeSwapProxy ABI(PancakeSwap V3 没有 deadline 参数)
16
- const PANCAKE_PROXY_ABI = [
17
- 'function swapV2(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) external payable returns (uint256 amountOut)',
18
- 'function swapV3Single(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint256 amountOutMin, address to) external payable returns (uint256 amountOut)',
19
- 'function swapV3MultiHop(address[] calldata lpAddresses, address exactTokenIn, uint256 amountIn, uint256 amountOutMin, address to) external payable returns (uint256 amountOut)'
20
- ];
21
- // PancakeSwap V2 Router ABI(用于报价)
22
- const PANCAKE_V2_ROUTER_ABI = [
23
- 'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
24
- ];
25
- // PancakeSwap V3 QuoterV2 ABI(用于报价)
26
- const PANCAKE_V3_QUOTER_ABI = [
27
- 'function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96)) external returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)'
28
- ];
29
- const PANCAKE_V2_ROUTER_ADDRESS = '0x10ED43C718714eb63d5aA57B78B54704E256024E';
30
- const PANCAKE_V3_QUOTER_ADDRESS = '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997';
31
- // ERC20 ABI
32
- const ERC20_ABI = [
33
- 'function approve(address spender, uint256 amount) external returns (bool)',
34
- 'function allowance(address owner, address spender) external view returns (uint256)',
35
- 'function balanceOf(address account) external view returns (uint256)',
36
- 'function decimals() external view returns (uint8)'
37
- ];
38
- // ==================== 辅助函数 ====================
39
1
  /**
40
- * 获取 Gas Limit
41
- * 优先使用 config.gasLimit,否则使用默认值 * multiplier
2
+ * PancakeSwap 官方 Router 交易模块(Flap 版本,支持多链)
42
3
  *
43
- * ✅ ethers v6 最佳实践:直接使用 bigint 类型
4
+ * ✅ 使用官方 PancakeSwap V2/V3 Router(非代理合约)
5
+ * - V2 Router (BSC): 0x10ED43C718714eb63d5aA57B78B54704E256024E
6
+ * - V3 Router (BSC): 0x13f4EA83D0bd40E75C8222255bc855a974568Dd4
7
+ * - V3 Quoter (BSC): 0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997
44
8
  */
9
+ import { ethers, Wallet, JsonRpcProvider, Contract, Interface } from 'ethers';
10
+ import { NonceManager, getOptimizedGasPrice, getDeadline, encodeV3Path } from '../../utils/bundle-helpers.js';
11
+ import { ADDRESSES, ZERO_ADDRESS } from '../../utils/constants.js';
12
+ import { MULTICALL3_ABI, V2_ROUTER_ABI, V3_ROUTER02_ABI, V3_QUOTER_ABI, ERC20_ABI } from '../../abis/common.js';
13
+ import { CHAIN_ID_MAP, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA } from './config.js';
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
+ const V3_ROUTER_ABI = V3_ROUTER02_ABI;
24
+ // ==================== 官方 ABI ====================
25
+ // ✅ ABI 从公共模块导入:V2_ROUTER_ABI, V3_ROUTER02_ABI (as V3_ROUTER_ABI), V3_QUOTER_ABI, ERC20_ABI, MULTICALL3_ABI
26
+ // ✅ 工具函数从 bundle-helpers.js 导入:getDeadline, encodeV3Path
27
+ // ==================== Provider 缓存 ====================
28
+ const providerCache = new Map();
29
+ const PROVIDER_CACHE_TTL_MS = 60 * 1000;
30
+ // Token Decimals 缓存
31
+ const tokenDecimalsCache = new Map();
32
+ function getCachedProvider(chain, rpcUrl, chainId) {
33
+ const cacheKey = `${chain}-${rpcUrl}`;
34
+ const now = Date.now();
35
+ const cached = providerCache.get(cacheKey);
36
+ if (cached && cached.expireAt > now) {
37
+ return cached.provider;
38
+ }
39
+ const resolvedChainId = chainId ?? (CHAIN_ID_MAP[chain] || 56);
40
+ const provider = new JsonRpcProvider(rpcUrl, { chainId: resolvedChainId, name: chain });
41
+ providerCache.set(cacheKey, { provider, expireAt: now + PROVIDER_CACHE_TTL_MS });
42
+ return provider;
43
+ }
44
+ // ==================== 辅助函数 ====================
45
45
  function getGasLimit(config, defaultGas = DEFAULT_GAS_LIMIT) {
46
- // 优先使用前端传入的 gasLimit
47
46
  if (config.gasLimit !== undefined) {
48
- // 如果已经是 bigint,直接返回;否则转换
49
47
  return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
50
48
  }
51
- // 使用默认值 * multiplier
52
49
  const multiplier = config.gasLimitMultiplier ?? 1.0;
53
50
  const calculatedGas = Math.ceil(defaultGas * multiplier);
54
- // JavaScript 原生 BigInt 转换(ethers v6 兼容)
55
51
  return BigInt(calculatedGas);
56
52
  }
57
- /**
58
- * 查询代币 decimals(带缓存)
59
- * ✅ 代币精度不会变化,缓存后永久有效
60
- */
61
53
  async function getTokenDecimals(tokenAddress, provider) {
62
54
  const cacheKey = tokenAddress.toLowerCase();
63
- // ✅ 检查缓存
64
55
  const cached = tokenDecimalsCache.get(cacheKey);
65
56
  if (cached !== undefined) {
66
57
  return cached;
@@ -69,22 +60,15 @@ async function getTokenDecimals(tokenAddress, provider) {
69
60
  const token = new Contract(tokenAddress, ERC20_ABI, provider);
70
61
  const decimals = await token.decimals();
71
62
  const result = Number(decimals);
72
- // ✅ 缓存结果
73
63
  tokenDecimalsCache.set(cacheKey, result);
74
64
  return result;
75
65
  }
76
66
  catch {
77
- // 默认返回 18,兼容大部分 ERC20(也缓存)
78
67
  tokenDecimalsCache.set(cacheKey, 18);
79
68
  return 18;
80
69
  }
81
70
  }
82
- /**
83
- * 判断是否需要发送 BNB(tokenIn 是 WBNB 且没有使用 ERC20 代币购买时)
84
- * ✅ 新增:如果使用 quoteToken(如 USDT),则不需要发送 BNB
85
- */
86
71
  function needSendBNB(routeType, params, useNativeToken = true) {
87
- // 如果使用 ERC20 代币购买,不需要发送 BNB
88
72
  if (!useNativeToken) {
89
73
  return false;
90
74
  }
@@ -99,81 +83,134 @@ function needSendBNB(routeType, params, useNativeToken = true) {
99
83
  }
100
84
  return false;
101
85
  }
102
- /**
103
- * 判断是否使用原生代币(BNB)
104
- */
105
86
  function isUsingNativeToken(quoteToken) {
106
87
  return !quoteToken || quoteToken === ZERO_ADDRESS || quoteToken.toLowerCase() === WBNB_ADDRESS.toLowerCase();
107
88
  }
89
+ // ✅ getDeadline 从 bundle-helpers.js 导入
108
90
  /**
109
- * 计算 deadline
91
+ * 根据 routeType 获取授权目标地址
110
92
  */
111
- function getDeadline(minutes = DEADLINE_MINUTES) {
112
- return Math.floor(Date.now() / 1000) + 60 * minutes;
93
+ function getApprovalTarget(routeType) {
94
+ if (routeType === 'v2') {
95
+ return PANCAKE_V2_ROUTER_ADDRESS;
96
+ }
97
+ return PANCAKE_V3_ROUTER_ADDRESS;
113
98
  }
114
99
  /**
115
- * 构建 V2 交易
100
+ * 构建 V2 交易(使用官方 Router)
116
101
  */
117
- async function buildV2Transactions(proxies, wallets, amountsWei, minOuts, path, isBuy, needBNB) {
102
+ async function buildV2Transactions(routers, wallets, amountsWei, minOuts, path, isBuy, needBNB) {
118
103
  const deadline = getDeadline();
119
- return Promise.all(proxies.map((proxy, i) => {
120
- const txValue = (isBuy && needBNB) ? amountsWei[i] + FLAT_FEE : FLAT_FEE;
121
- return proxy.swapV2.populateTransaction(amountsWei[i], minOuts[i], path, wallets[i].address, deadline, { value: txValue });
104
+ return Promise.all(routers.map(async (router, i) => {
105
+ if (isBuy && needBNB) {
106
+ return router.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(minOuts[i], path, wallets[i].address, deadline, { value: amountsWei[i] });
107
+ }
108
+ else {
109
+ return router.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(amountsWei[i], minOuts[i], path, wallets[i].address, deadline);
110
+ }
122
111
  }));
123
112
  }
124
113
  /**
125
- * 构建 V3 单跳交易(V3 单跳没有 deadline
114
+ * 构建 V3 单跳交易(使用官方 SwapRouter02 + multicall
126
115
  */
127
- async function buildV3SingleTransactions(proxies, wallets, tokenIn, tokenOut, fee, amountsWei, minOuts, isBuy, needBNB) {
128
- return Promise.all(proxies.map((proxy, i) => {
129
- const txValue = (isBuy && needBNB) ? amountsWei[i] + FLAT_FEE : FLAT_FEE;
130
- return proxy.swapV3Single.populateTransaction(tokenIn, tokenOut, fee, amountsWei[i], minOuts[i], wallets[i].address, { value: txValue });
116
+ async function buildV3SingleTransactions(routers, wallets, tokenIn, tokenOut, fee, amountsWei, minOuts, isBuy, needBNB) {
117
+ const deadline = getDeadline();
118
+ const v3RouterIface = new Interface(V3_ROUTER_ABI);
119
+ return Promise.all(routers.map(async (router, i) => {
120
+ const isTokenOutWBNB = tokenOut.toLowerCase() === WBNB_ADDRESS.toLowerCase();
121
+ const exactInputSingleData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
122
+ tokenIn: tokenIn,
123
+ tokenOut: tokenOut,
124
+ fee: fee,
125
+ recipient: isTokenOutWBNB ? PANCAKE_V3_ROUTER_ADDRESS : wallets[i].address,
126
+ amountIn: amountsWei[i],
127
+ amountOutMinimum: minOuts[i],
128
+ sqrtPriceLimitX96: 0n
129
+ }]);
130
+ if (isBuy && needBNB) {
131
+ return router.multicall.populateTransaction(deadline, [exactInputSingleData], { value: amountsWei[i] });
132
+ }
133
+ else if (!isBuy && isTokenOutWBNB) {
134
+ const unwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [
135
+ minOuts[i],
136
+ wallets[i].address
137
+ ]);
138
+ return router.multicall.populateTransaction(deadline, [exactInputSingleData, unwrapData]);
139
+ }
140
+ else {
141
+ return router.multicall.populateTransaction(deadline, [exactInputSingleData]);
142
+ }
131
143
  }));
132
144
  }
133
145
  /**
134
- * 构建 V3 多跳交易(PancakeSwap V3 没有 deadline
146
+ * 构建 V3 多跳交易(使用官方 SwapRouter02 exactInput
135
147
  */
136
- async function buildV3MultiHopTransactions(proxies, wallets, lpAddresses, exactTokenIn, amountsWei, minOuts, isBuy, needBNB) {
137
- return Promise.all(proxies.map((proxy, i) => {
138
- const txValue = (isBuy && needBNB) ? amountsWei[i] + FLAT_FEE : FLAT_FEE;
139
- return proxy.swapV3MultiHop.populateTransaction(lpAddresses, exactTokenIn, amountsWei[i], minOuts[i], wallets[i].address, { value: txValue });
148
+ async function buildV3MultiHopTransactions(routers, wallets, tokens, fees, amountsWei, minOuts, isBuy, needBNB) {
149
+ if (!tokens || tokens.length < 2) {
150
+ throw new Error('V3 多跳需要至少 2 个代币(v3Tokens)');
151
+ }
152
+ if (!fees || fees.length !== tokens.length - 1) {
153
+ throw new Error(`V3 多跳费率数量 (${fees?.length || 0}) 必须等于代币数量 - 1 (${tokens.length - 1})(v3Fees)`);
154
+ }
155
+ const deadline = getDeadline();
156
+ const v3RouterIface = new Interface(V3_ROUTER_ABI);
157
+ const forwardPath = encodeV3Path(tokens, fees);
158
+ const reversedTokens = [...tokens].reverse();
159
+ const reversedFees = [...fees].reverse();
160
+ const reversePath = encodeV3Path(reversedTokens, reversedFees);
161
+ const tokenOut = tokens[tokens.length - 1];
162
+ const isTokenOutWBNB = tokenOut.toLowerCase() === WBNB_ADDRESS.toLowerCase();
163
+ return Promise.all(routers.map(async (router, i) => {
164
+ if (isBuy && needBNB) {
165
+ const swapData = v3RouterIface.encodeFunctionData('exactInput', [{
166
+ path: forwardPath,
167
+ recipient: wallets[i].address,
168
+ amountIn: amountsWei[i],
169
+ amountOutMinimum: minOuts[i]
170
+ }]);
171
+ return router.multicall.populateTransaction(deadline, [swapData], { value: amountsWei[i] });
172
+ }
173
+ else if (!isBuy) {
174
+ const swapData = v3RouterIface.encodeFunctionData('exactInput', [{
175
+ path: reversePath,
176
+ recipient: isTokenOutWBNB ? PANCAKE_V3_ROUTER_ADDRESS : wallets[i].address,
177
+ amountIn: amountsWei[i],
178
+ amountOutMinimum: minOuts[i]
179
+ }]);
180
+ if (isTokenOutWBNB) {
181
+ const unwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [minOuts[i], wallets[i].address]);
182
+ return router.multicall.populateTransaction(deadline, [swapData, unwrapData]);
183
+ }
184
+ return router.multicall.populateTransaction(deadline, [swapData]);
185
+ }
186
+ else {
187
+ const swapData = v3RouterIface.encodeFunctionData('exactInput', [{
188
+ path: forwardPath,
189
+ recipient: wallets[i].address,
190
+ amountIn: amountsWei[i],
191
+ amountOutMinimum: minOuts[i]
192
+ }]);
193
+ return router.multicall.populateTransaction(deadline, [swapData]);
194
+ }
140
195
  }));
141
196
  }
142
197
  // ==================== 主要导出函数 ====================
143
198
  /**
144
- * ✅ 获取缓存的 Provider(通用方法)
145
- */
146
- function getCachedProvider(chain, rpcUrl, chainId) {
147
- const cacheKey = `${chain}-${rpcUrl}`;
148
- const now = Date.now();
149
- const cached = providerCache.get(cacheKey);
150
- if (cached && cached.expireAt > now) {
151
- return cached.provider;
152
- }
153
- const resolvedChainId = chainId ?? (CHAIN_ID_MAP[chain] || 56);
154
- const provider = new JsonRpcProvider(rpcUrl, { chainId: resolvedChainId, name: chain });
155
- providerCache.set(cacheKey, { provider, expireAt: now + PROVIDER_CACHE_TTL_MS });
156
- return provider;
157
- }
158
- /**
159
- * 授权代币给 PancakeSwapProxy
199
+ * ✅ 授权代币给 PancakeSwap Router
160
200
  */
161
201
  export async function approvePancakeProxy(params) {
162
- const { chain, privateKey, tokenAddress, amount, rpcUrl } = params;
163
- const pancakeProxyAddress = ADDRESSES.BSC.PancakeProxy;
164
- const provider = getCachedProvider(chain, rpcUrl); // ✅ 使用缓存
202
+ const { chain, privateKey, tokenAddress, amount, rpcUrl, routeType } = params;
203
+ const approvalTarget = getApprovalTarget(routeType || 'v2');
204
+ const provider = getCachedProvider(chain, rpcUrl);
165
205
  const wallet = new Wallet(privateKey, provider);
166
206
  const token = new Contract(tokenAddress, ERC20_ABI, wallet);
167
- // 查询 decimals
168
207
  const decimals = await getTokenDecimals(tokenAddress, provider);
169
- // 检查当前授权额度
170
- const currentAllowance = await token.allowance(wallet.address, pancakeProxyAddress);
208
+ const currentAllowance = await token.allowance(wallet.address, approvalTarget);
171
209
  const amountBigInt = amount === 'max' ? ethers.MaxUint256 : ethers.parseUnits(amount, decimals);
172
210
  if (currentAllowance >= amountBigInt) {
173
211
  return { txHash: '', approved: true };
174
212
  }
175
- // 执行授权
176
- const tx = await token.approve(pancakeProxyAddress, amountBigInt);
213
+ const tx = await token.approve(approvalTarget, amountBigInt);
177
214
  const receipt = await tx.wait();
178
215
  return {
179
216
  txHash: receipt.hash,
@@ -181,25 +218,22 @@ export async function approvePancakeProxy(params) {
181
218
  };
182
219
  }
183
220
  /**
184
- * 批量授权代币给 PancakeSwapProxy(内部直接广播提交)
221
+ * 批量授权代币给 PancakeSwap Router
185
222
  */
186
223
  export async function approvePancakeProxyBatch(params) {
187
- const { chain, privateKeys, tokenAddress, amounts, config } = params;
224
+ const { chain, privateKeys, tokenAddress, amounts, config, routeType } = params;
188
225
  if (privateKeys.length === 0 || amounts.length !== privateKeys.length) {
189
226
  throw new Error('Private key count and amount count must match');
190
227
  }
191
- const pancakeProxyAddress = ADDRESSES.BSC.PancakeProxy;
228
+ const approvalTarget = getApprovalTarget(routeType || 'v2');
192
229
  const chainId = CHAIN_ID_MAP[chain];
193
- const provider = getCachedProvider(chain, config.rpcUrl, chainId); // ✅ 使用缓存
230
+ const provider = getCachedProvider(chain, config.rpcUrl, chainId);
194
231
  const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
195
- // 查询 decimals
196
232
  const decimals = await getTokenDecimals(tokenAddress, provider);
197
233
  const wallets = privateKeys.map(k => new Wallet(k, provider));
198
234
  const amountsBigInt = amounts.map(a => a === 'max' ? ethers.MaxUint256 : ethers.parseUnits(a, decimals));
199
- // 检查现有授权,过滤掉已经足够的
200
235
  const tokens = wallets.map(w => new Contract(tokenAddress, ERC20_ABI, w));
201
- const allowances = await Promise.all(tokens.map((token, i) => token.allowance(wallets[i].address, pancakeProxyAddress)));
202
- // 只授权不足的
236
+ const allowances = await Promise.all(tokens.map((token, i) => token.allowance(wallets[i].address, approvalTarget)));
203
237
  const needApproval = wallets.filter((_, i) => allowances[i] < amountsBigInt[i]);
204
238
  const needApprovalAmounts = amountsBigInt.filter((amount, i) => allowances[i] < amount);
205
239
  if (needApproval.length === 0) {
@@ -210,12 +244,9 @@ export async function approvePancakeProxyBatch(params) {
210
244
  message: '所有钱包已授权'
211
245
  };
212
246
  }
213
- // 构建授权交易
214
247
  const needApprovalTokens = needApproval.map(w => new Contract(tokenAddress, ERC20_ABI, w));
215
- const unsignedApprovals = await Promise.all(needApprovalTokens.map((token, i) => token.approve.populateTransaction(pancakeProxyAddress, needApprovalAmounts[i])));
216
- // 使用前端传入的 gasLimit,否则使用默认值
248
+ const unsignedApprovals = await Promise.all(needApprovalTokens.map((token, i) => token.approve.populateTransaction(approvalTarget, needApprovalAmounts[i])));
217
249
  const finalGasLimit = getGasLimit(config);
218
- // 签名授权交易
219
250
  const nonceManager = new NonceManager(provider);
220
251
  const nonces = await Promise.all(needApproval.map(w => nonceManager.getNextNonce(w)));
221
252
  const signedTxs = await Promise.all(unsignedApprovals.map((unsigned, i) => needApproval[i].signTransaction({
@@ -228,7 +259,6 @@ export async function approvePancakeProxyBatch(params) {
228
259
  type: getTxType(config)
229
260
  })));
230
261
  nonceManager.clearTemp();
231
- // ✅ 内部直接广播提交(逐个发送)
232
262
  const txHashes = [];
233
263
  const errors = [];
234
264
  for (let i = 0; i < signedTxs.length; i++) {
@@ -261,78 +291,85 @@ export async function approvePancakeProxyBatch(params) {
261
291
  }
262
292
  }
263
293
  /**
264
- * PancakeSwapProxy 批量购买(仅签名版本 - 不依赖 Merkle)
265
- * ✅ 精简版:只负责签名交易,不提交到 Merkle
294
+ * 使用 PancakeSwap 官方 Router 批量购买代币
266
295
  */
267
296
  export async function pancakeProxyBatchBuyMerkle(params) {
268
297
  const { chain, privateKeys, buyAmounts, tokenAddress, routeType, config, quoteToken, quoteTokenDecimals } = params;
269
298
  if (privateKeys.length === 0 || buyAmounts.length !== privateKeys.length) {
270
299
  throw new Error('Private key count and buy amount count must match');
271
300
  }
272
- const { provider, chainId } = createChainContext(chain, config.rpcUrl);
273
- const buyers = createWallets(privateKeys, provider);
301
+ const chainId = CHAIN_ID_MAP[chain];
302
+ const provider = getCachedProvider(chain, config.rpcUrl, chainId);
303
+ const buyers = privateKeys.map(k => new Wallet(k, provider));
274
304
  const extractProfit = shouldExtractProfit(config);
275
305
  const nonceManager = new NonceManager(provider);
276
306
  const finalGasLimit = getGasLimit(config);
277
- // ✅ 判断是否使用原生代币(BNB)或 ERC20 代币(如 USDT)
278
307
  const useNativeToken = isUsingNativeToken(quoteToken);
279
308
  const originalAmountsWei = buyAmounts.map(amount => ethers.parseEther(amount));
280
309
  const { totalProfit, remainingAmounts } = calculateBatchProfit(originalAmountsWei, config);
281
310
  const maxFundsIndex = findMaxAmountIndex(originalAmountsWei);
282
- // ✅ ERC20 购买时:跳过利润提取(因为用户没有 BNB 可以转)
283
311
  const shouldExtractProfitForBuy = extractProfit && useNativeToken;
284
312
  const nativeProfitAmount = shouldExtractProfitForBuy ? totalProfit : 0n;
285
- if (!useNativeToken && extractProfit) {
286
- }
287
- // ✅ 如果使用 ERC20 代币购买,需要根据精度转换金额
288
313
  let actualAmountsWei = remainingAmounts;
289
314
  if (!useNativeToken && quoteTokenDecimals !== undefined && quoteTokenDecimals !== 18) {
290
315
  const decimalsDiff = 18 - quoteTokenDecimals;
291
316
  const divisor = BigInt(10 ** decimalsDiff);
292
317
  actualAmountsWei = remainingAmounts.map(amount => amount / divisor);
293
318
  }
294
- // ✅ 优化:如果前端传入了 gasPrice 和 nonces,跳过 RPC 调用
295
319
  const presetGasPrice = config.gasPrice;
296
320
  const presetNonces = config.nonces;
297
- // ✅ 只获取必需的数据(跳过已有的)
298
321
  const [gasPrice, tokenDecimals, nonces] = await Promise.all([
299
- // gasPrice:优先使用前端传入的
300
322
  presetGasPrice !== undefined
301
323
  ? Promise.resolve(presetGasPrice)
302
324
  : getOptimizedGasPrice(provider, getGasPriceConfig(config)),
303
- // tokenDecimals:有缓存
304
325
  getTokenDecimals(tokenAddress, provider),
305
- // nonces:优先使用前端传入的
306
326
  presetNonces && presetNonces.length === buyers.length
307
327
  ? Promise.resolve(presetNonces)
308
328
  : allocateProfitAwareNonces(buyers, shouldExtractProfitForBuy, maxFundsIndex, nativeProfitAmount, nonceManager)
309
329
  ]);
310
- const minOuts = resolveBuyMinOutputs(params, buyers.length, tokenDecimals);
330
+ const minOuts = new Array(buyers.length).fill(0n);
311
331
  const needBNB = needSendBNB(routeType, params, useNativeToken);
312
- const proxies = createPancakeProxies(buyers, ADDRESSES.BSC.PancakeProxy);
313
- // ✅ 构建交易
314
- const unsignedBuys = await buildBuyTransactions({
315
- routeType,
316
- params,
317
- proxies,
318
- wallets: buyers,
319
- tokenAddress,
320
- actualAmountsWei,
321
- minOuts,
322
- needBNB
323
- });
324
- // 并行签名所有交易(贿赂、买入、利润)
332
+ // 使用官方 Router
333
+ let routers;
334
+ let unsignedBuys;
335
+ if (routeType === 'v2') {
336
+ if (!params.v2Path || params.v2Path.length < 2) {
337
+ throw new Error('v2Path is required for V2 routing');
338
+ }
339
+ routers = buyers.map(w => new Contract(PANCAKE_V2_ROUTER_ADDRESS, V2_ROUTER_ABI, w));
340
+ unsignedBuys = await buildV2Transactions(routers, buyers, actualAmountsWei, minOuts, params.v2Path, true, needBNB);
341
+ }
342
+ else if (routeType === 'v3-single') {
343
+ if (!params.v3TokenIn || !params.v3Fee) {
344
+ throw new Error('v3TokenIn and v3Fee are required for V3 single-hop');
345
+ }
346
+ routers = buyers.map(w => new Contract(PANCAKE_V3_ROUTER_ADDRESS, V3_ROUTER_ABI, w));
347
+ unsignedBuys = await buildV3SingleTransactions(routers, buyers, params.v3TokenIn, tokenAddress, params.v3Fee, actualAmountsWei, minOuts, true, needBNB);
348
+ }
349
+ else if (routeType === 'v3-multi') {
350
+ // ✅ V3 多跳:需要 v3Tokens 和 v3Fees 参数
351
+ if (!params.v3Tokens || params.v3Tokens.length < 2) {
352
+ throw new Error('v3Tokens(至少 2 个代币)是 V3 多跳必需的');
353
+ }
354
+ if (!params.v3Fees || params.v3Fees.length !== params.v3Tokens.length - 1) {
355
+ throw new Error(`v3Fees 长度必须等于 v3Tokens 长度 - 1`);
356
+ }
357
+ routers = buyers.map(w => new Contract(PANCAKE_V3_ROUTER_ADDRESS, V3_ROUTER_ABI, w));
358
+ unsignedBuys = await buildV3MultiHopTransactions(routers, buyers, params.v3Tokens, params.v3Fees, actualAmountsWei, minOuts, true, needBNB);
359
+ }
360
+ else {
361
+ throw new Error(`Unsupported routeType: ${routeType}`);
362
+ }
325
363
  const bribeAmount = getBribeAmount(config);
326
364
  const needBribeTx = bribeAmount > 0n && maxFundsIndex >= 0 && buyers.length > 0;
327
365
  const txType = getTxType(config);
328
- // 调整 maxFundsIndex 钱包的 nonce(如果有贿赂交易)
329
366
  let bribeNonce;
367
+ const mutableNonces = [...nonces];
330
368
  if (needBribeTx) {
331
- bribeNonce = nonces[maxFundsIndex];
332
- nonces[maxFundsIndex] = bribeNonce + 1;
369
+ bribeNonce = mutableNonces[maxFundsIndex];
370
+ mutableNonces[maxFundsIndex] = bribeNonce + 1;
333
371
  }
334
372
  const signPromises = [];
335
- // 贿赂交易(索引 0)
336
373
  if (needBribeTx && bribeNonce !== undefined) {
337
374
  signPromises.push(buyers[maxFundsIndex].signTransaction({
338
375
  to: BLOCKRAZOR_BUILDER_EOA,
@@ -344,13 +381,12 @@ export async function pancakeProxyBatchBuyMerkle(params) {
344
381
  type: txType
345
382
  }));
346
383
  }
347
- // 买入交易(索引 1 ~ N)
348
384
  unsignedBuys.forEach((unsigned, i) => {
349
- const txValue = useNativeToken ? unsigned.value : FLAT_FEE;
385
+ const txValue = useNativeToken ? unsigned.value : 0n;
350
386
  signPromises.push(buyers[i].signTransaction({
351
387
  ...unsigned,
352
388
  from: buyers[i].address,
353
- nonce: nonces[i],
389
+ nonce: mutableNonces[i],
354
390
  gasLimit: finalGasLimit,
355
391
  gasPrice,
356
392
  chainId,
@@ -358,9 +394,8 @@ export async function pancakeProxyBatchBuyMerkle(params) {
358
394
  value: txValue
359
395
  }));
360
396
  });
361
- // 利润交易(索引 N+1)
362
397
  if (shouldExtractProfitForBuy && nativeProfitAmount > 0n && maxFundsIndex >= 0) {
363
- const profitNonce = nonces[maxFundsIndex] + 1;
398
+ const profitNonce = mutableNonces[maxFundsIndex] + 1;
364
399
  signPromises.push(buyers[maxFundsIndex].signTransaction({
365
400
  to: getProfitRecipient(),
366
401
  value: nativeProfitAmount,
@@ -371,7 +406,6 @@ export async function pancakeProxyBatchBuyMerkle(params) {
371
406
  type: txType
372
407
  }));
373
408
  }
374
- // ✅ 并行签名完成后按顺序返回
375
409
  const signedTxs = await Promise.all(signPromises);
376
410
  nonceManager.clearTemp();
377
411
  return {
@@ -379,46 +413,56 @@ export async function pancakeProxyBatchBuyMerkle(params) {
379
413
  };
380
414
  }
381
415
  /**
382
- * 使用 PancakeSwapProxy 批量卖出代币(Merkle 版本)
383
- * ✅ 自动检查授权,智能处理授权+卖出
384
- */
385
- /**
386
- * PancakeSwapProxy 批量卖出(仅签名版本 - 不依赖 Merkle)
387
- * ✅ 精简版:只负责签名交易,不提交到 Merkle
416
+ * 使用 PancakeSwap 官方 Router 批量卖出代币
388
417
  */
389
418
  export async function pancakeProxyBatchSellMerkle(params) {
390
419
  const { chain, privateKeys, sellAmounts, tokenAddress, routeType, config } = params;
391
420
  if (privateKeys.length === 0 || sellAmounts.length !== privateKeys.length) {
392
421
  throw new Error('Private key count and sell amount count must match');
393
422
  }
394
- const { provider, chainId } = createChainContext(chain, config.rpcUrl);
395
- const sellers = createWallets(privateKeys, provider);
423
+ const chainId = CHAIN_ID_MAP[chain];
424
+ const provider = getCachedProvider(chain, config.rpcUrl, chainId);
425
+ const sellers = privateKeys.map(k => new Wallet(k, provider));
396
426
  const finalGasLimit = getGasLimit(config);
397
427
  const extractProfit = shouldExtractProfit(config);
398
428
  const nonceManager = new NonceManager(provider);
399
- // ✅ 优化:如果前端传入了 gasPrice 和 nonces,跳过 RPC 调用
400
429
  const presetGasPrice = config.gasPrice;
401
430
  const presetNonces = config.nonces;
402
- // ✅ 优化:并行获取 gasPrice 和 tokenDecimals(已移除授权检查)
403
431
  const [gasPrice, tokenDecimals] = await Promise.all([
404
- // gasPrice:优先使用前端传入的
405
432
  presetGasPrice !== undefined
406
433
  ? Promise.resolve(presetGasPrice)
407
434
  : getOptimizedGasPrice(provider, getGasPriceConfig(config)),
408
- // tokenDecimals:有缓存
409
435
  getTokenDecimals(tokenAddress, provider)
410
436
  ]);
411
437
  const amountsWei = sellAmounts.map(amount => ethers.parseUnits(amount, tokenDecimals));
412
- const proxies = createPancakeProxies(sellers, ADDRESSES.BSC.PancakeProxy);
413
- // 获取报价(用于计算利润)
414
- const { minOuts, quotedOutputs } = await resolveSellOutputs({
415
- params,
416
- provider,
417
- tokenAddress,
418
- routeType,
419
- amountsWei
420
- });
421
- // 修复:先计算利润和 maxRevenueIndex,再统一分配 nonces
438
+ // 获取报价
439
+ let quotedOutputs;
440
+ if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
441
+ quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
442
+ }
443
+ else if (routeType === 'v3-single') {
444
+ quotedOutputs = await Promise.all(amountsWei.map(async (amount) => {
445
+ try {
446
+ const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, V3_QUOTER_ABI, provider);
447
+ const result = await quoter.quoteExactInputSingle.staticCall({
448
+ tokenIn: tokenAddress,
449
+ tokenOut: params.v3TokenOut,
450
+ amountIn: amount,
451
+ fee: params.v3Fee,
452
+ sqrtPriceLimitX96: 0
453
+ });
454
+ return result[0];
455
+ }
456
+ catch {
457
+ return 0n;
458
+ }
459
+ }));
460
+ }
461
+ else {
462
+ quotedOutputs = new Array(amountsWei.length).fill(0n);
463
+ }
464
+ const minOuts = new Array(sellers.length).fill(0n);
465
+ // 计算利润
422
466
  let totalProfit = 0n;
423
467
  let maxRevenueIndex = -1;
424
468
  let maxRevenue = 0n;
@@ -435,68 +479,81 @@ export async function pancakeProxyBatchSellMerkle(params) {
435
479
  }
436
480
  }
437
481
  }
438
- // ✅ 修复:根据是否需要贿赂/利润交易,统一分配 nonces
439
482
  const bribeAmount = getBribeAmount(config);
440
483
  const needBribeTx = bribeAmount > 0n && maxRevenueIndex >= 0;
441
484
  const needProfitTx = extractProfit && totalProfit > 0n && maxRevenueIndex >= 0;
442
- // 计算 maxRevenueIndex 钱包需要的 nonce 数量:贿赂(可选) + 卖出 + 利润(可选)
443
485
  const maxRevenueNonceCount = 1 + (needBribeTx ? 1 : 0) + (needProfitTx ? 1 : 0);
444
486
  let nonces;
445
487
  let bribeNonce;
446
488
  let profitNonce;
447
- // ✅ 优化:如果前端传入了 nonces,直接使用
448
489
  if (presetNonces && presetNonces.length === sellers.length) {
449
490
  nonces = [...presetNonces];
450
491
  if (needBribeTx) {
451
492
  bribeNonce = nonces[maxRevenueIndex];
452
- nonces[maxRevenueIndex] = bribeNonce + 1; // 卖出交易 nonce +1
493
+ nonces[maxRevenueIndex] = bribeNonce + 1;
453
494
  }
454
495
  profitNonce = needProfitTx ? nonces[maxRevenueIndex] + 1 : undefined;
455
496
  }
456
497
  else if (maxRevenueNonceCount > 1 && maxRevenueIndex >= 0) {
457
- // maxRevenueIndex 钱包需要多个连续 nonce
458
498
  const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], maxRevenueNonceCount);
459
- // 其他钱包各需要 1 个 nonce
460
499
  const otherSellers = sellers.filter((_, i) => i !== maxRevenueIndex);
461
500
  const otherNonces = otherSellers.length > 0
462
501
  ? await nonceManager.getNextNoncesForWallets(otherSellers)
463
502
  : [];
464
- // 组装最终的 nonces 数组(保持原顺序)
465
503
  nonces = [];
466
504
  let otherIdx = 0;
467
505
  let nonceIdx = 0;
468
506
  if (needBribeTx) {
469
- bribeNonce = maxRevenueNonces[nonceIdx++]; // 贿赂交易用第一个 nonce
507
+ bribeNonce = maxRevenueNonces[nonceIdx++];
470
508
  }
471
509
  for (let i = 0; i < sellers.length; i++) {
472
510
  if (i === maxRevenueIndex) {
473
- nonces.push(maxRevenueNonces[nonceIdx++]); // 卖出交易
511
+ nonces.push(maxRevenueNonces[nonceIdx++]);
474
512
  }
475
513
  else {
476
514
  nonces.push(otherNonces[otherIdx++]);
477
515
  }
478
516
  }
479
517
  if (needProfitTx) {
480
- profitNonce = maxRevenueNonces[nonceIdx]; // 利润交易用最后一个 nonce
518
+ profitNonce = maxRevenueNonces[nonceIdx];
481
519
  }
482
520
  }
483
521
  else {
484
- // 不需要额外交易,所有钱包各 1 个 nonce
485
522
  nonces = await nonceManager.getNextNoncesForWallets(sellers);
486
523
  }
487
- // ✅ 并行构建未签名交易和签名
488
- const unsignedSells = await buildSellTransactions({
489
- routeType,
490
- params,
491
- proxies,
492
- wallets: sellers,
493
- tokenAddress,
494
- amountsWei,
495
- minOuts
496
- });
524
+ // ✅ 使用官方 Router 构建卖出交易
525
+ let routers;
526
+ let unsignedSells;
527
+ if (routeType === 'v2') {
528
+ if (!params.v2Path || params.v2Path.length < 2) {
529
+ throw new Error('v2Path is required for V2 routing');
530
+ }
531
+ routers = sellers.map(w => new Contract(PANCAKE_V2_ROUTER_ADDRESS, V2_ROUTER_ABI, w));
532
+ unsignedSells = await buildV2Transactions(routers, sellers, amountsWei, minOuts, params.v2Path, false, false);
533
+ }
534
+ else if (routeType === 'v3-single') {
535
+ if (!params.v3TokenOut || !params.v3Fee) {
536
+ throw new Error('v3TokenOut and v3Fee are required for V3 single-hop');
537
+ }
538
+ routers = sellers.map(w => new Contract(PANCAKE_V3_ROUTER_ADDRESS, V3_ROUTER_ABI, w));
539
+ unsignedSells = await buildV3SingleTransactions(routers, sellers, tokenAddress, params.v3TokenOut, params.v3Fee, amountsWei, minOuts, false, false);
540
+ }
541
+ else if (routeType === 'v3-multi') {
542
+ // ✅ V3 多跳:需要 v3Tokens 和 v3Fees 参数
543
+ if (!params.v3Tokens || params.v3Tokens.length < 2) {
544
+ throw new Error('v3Tokens(至少 2 个代币)是 V3 多跳必需的');
545
+ }
546
+ if (!params.v3Fees || params.v3Fees.length !== params.v3Tokens.length - 1) {
547
+ throw new Error(`v3Fees 长度必须等于 v3Tokens 长度 - 1`);
548
+ }
549
+ routers = sellers.map(w => new Contract(PANCAKE_V3_ROUTER_ADDRESS, V3_ROUTER_ABI, w));
550
+ unsignedSells = await buildV3MultiHopTransactions(routers, sellers, params.v3Tokens, params.v3Fees, amountsWei, minOuts, false, false);
551
+ }
552
+ else {
553
+ throw new Error(`Unsupported routeType: ${routeType}`);
554
+ }
497
555
  const txType = getTxType(config);
498
556
  const signPromises = [];
499
- // 贿赂交易(索引 0)
500
557
  if (needBribeTx && bribeNonce !== undefined) {
501
558
  signPromises.push(sellers[maxRevenueIndex].signTransaction({
502
559
  to: BLOCKRAZOR_BUILDER_EOA,
@@ -508,9 +565,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
508
565
  type: txType
509
566
  }));
510
567
  }
511
- // 卖出交易(索引 1 ~ N)
512
568
  unsignedSells.forEach((unsigned, i) => {
513
- const txValue = unsigned.value;
569
+ const txValue = unsigned.value ?? 0n;
514
570
  signPromises.push(sellers[i].signTransaction({
515
571
  ...unsigned,
516
572
  from: sellers[i].address,
@@ -522,7 +578,6 @@ export async function pancakeProxyBatchSellMerkle(params) {
522
578
  value: txValue
523
579
  }));
524
580
  });
525
- // 利润交易(索引 N+1)
526
581
  if (needProfitTx && profitNonce !== undefined) {
527
582
  signPromises.push(sellers[maxRevenueIndex].signTransaction({
528
583
  to: getProfitRecipient(),
@@ -534,34 +589,64 @@ export async function pancakeProxyBatchSellMerkle(params) {
534
589
  type: txType
535
590
  }));
536
591
  }
537
- // ✅ 并行签名完成后按顺序返回
538
592
  const signedTxs = await Promise.all(signPromises);
539
593
  nonceManager.clearTemp();
540
594
  return {
541
595
  signedTransactions: signedTxs
542
596
  };
543
597
  }
544
- // Provider 缓存(复用连接,减少初始化开销)
545
- const providerCache = new Map();
546
- const PROVIDER_CACHE_TTL_MS = 60 * 1000; // 60秒缓存
547
- // ✅ Token Decimals 缓存(代币精度不会变化)
548
- const tokenDecimalsCache = new Map();
549
- function createChainContext(chain, rpcUrl) {
550
- const chainId = CHAIN_ID_MAP[chain];
551
- const cacheKey = `${chain}-${rpcUrl}`;
552
- const now = Date.now();
553
- // 检查缓存
554
- const cached = providerCache.get(cacheKey);
555
- if (cached && cached.expireAt > now) {
556
- return { chainId, provider: cached.provider };
598
+ // ==================== 内部工具函数 ====================
599
+ /**
600
+ * 使用 Multicall3 批量获取 V2 报价
601
+ */
602
+ async function batchGetV2Quotes(provider, amountsWei, v2Path) {
603
+ if (amountsWei.length === 0)
604
+ return [];
605
+ if (amountsWei.length === 1) {
606
+ try {
607
+ const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, V2_ROUTER_ABI, provider);
608
+ const amounts = await v2Router.getAmountsOut(amountsWei[0], v2Path);
609
+ return [amounts[amounts.length - 1]];
610
+ }
611
+ catch {
612
+ return [0n];
613
+ }
614
+ }
615
+ try {
616
+ const v2RouterIface = new Interface(V2_ROUTER_ABI);
617
+ const multicall = new Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
618
+ const calls = amountsWei.map(amount => ({
619
+ target: PANCAKE_V2_ROUTER_ADDRESS,
620
+ allowFailure: true,
621
+ callData: v2RouterIface.encodeFunctionData('getAmountsOut', [amount, v2Path])
622
+ }));
623
+ const results = await multicall.aggregate3.staticCall(calls);
624
+ return results.map((r) => {
625
+ if (r.success && r.returnData && r.returnData !== '0x') {
626
+ try {
627
+ const decoded = v2RouterIface.decodeFunctionResult('getAmountsOut', r.returnData);
628
+ const amounts = decoded[0];
629
+ return amounts[amounts.length - 1];
630
+ }
631
+ catch {
632
+ return 0n;
633
+ }
634
+ }
635
+ return 0n;
636
+ });
637
+ }
638
+ catch {
639
+ return await Promise.all(amountsWei.map(async (amount) => {
640
+ try {
641
+ const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, V2_ROUTER_ABI, provider);
642
+ const amounts = await v2Router.getAmountsOut(amount, v2Path);
643
+ return amounts[amounts.length - 1];
644
+ }
645
+ catch {
646
+ return 0n;
647
+ }
648
+ }));
557
649
  }
558
- // ✅ 创建新 Provider 并缓存
559
- const provider = new JsonRpcProvider(rpcUrl, { chainId, name: chain });
560
- providerCache.set(cacheKey, { provider, expireAt: now + PROVIDER_CACHE_TTL_MS });
561
- return { chainId, provider };
562
- }
563
- function createWallets(privateKeys, provider) {
564
- return privateKeys.map(key => new Wallet(key, provider));
565
650
  }
566
651
  function findMaxAmountIndex(amounts) {
567
652
  if (!amounts.length) {
@@ -577,56 +662,21 @@ function findMaxAmountIndex(amounts) {
577
662
  }
578
663
  return maxIndex;
579
664
  }
580
- function resolveBuyMinOutputs(_params, walletCount, _tokenDecimals) {
581
- // ✅ 已移除滑点保护:minOutput 固定为 0
582
- return new Array(walletCount).fill(0n);
583
- }
584
- function createPancakeProxies(wallets, proxyAddress) {
585
- return wallets.map(wallet => new Contract(proxyAddress, PANCAKE_PROXY_ABI, wallet));
586
- }
587
- async function buildBuyTransactions({ routeType, params, proxies, wallets, tokenAddress, actualAmountsWei, minOuts, needBNB }) {
588
- if (routeType === 'v2') {
589
- if (!params.v2Path || params.v2Path.length < 2) {
590
- throw new Error('v2Path is required for V2 routing');
591
- }
592
- return await buildV2Transactions(proxies, wallets, actualAmountsWei, minOuts, params.v2Path, true, needBNB);
593
- }
594
- if (routeType === 'v3-single') {
595
- if (!params.v3TokenIn || !params.v3Fee) {
596
- throw new Error('v3TokenIn and v3Fee are required for V3 single-hop');
597
- }
598
- return await buildV3SingleTransactions(proxies, wallets, params.v3TokenIn, tokenAddress, params.v3Fee, actualAmountsWei, minOuts, true, needBNB);
599
- }
600
- if (routeType === 'v3-multi') {
601
- if (!params.v3LpAddresses || params.v3LpAddresses.length === 0 || !params.v3ExactTokenIn) {
602
- throw new Error('v3LpAddresses and v3ExactTokenIn are required for V3 multi-hop');
603
- }
604
- return await buildV3MultiHopTransactions(proxies, wallets, params.v3LpAddresses, params.v3ExactTokenIn, actualAmountsWei, minOuts, true, needBNB);
605
- }
606
- throw new Error(`Unsupported routeType: ${routeType}`);
607
- }
608
- /**
609
- * ✅ 修复:明确分配 nonces,避免隐式状态依赖
610
- */
611
665
  async function allocateProfitAwareNonces(wallets, extractProfit, maxIndex, totalProfit, nonceManager) {
612
666
  const needProfitTx = extractProfit && totalProfit > 0n && maxIndex >= 0 && maxIndex < wallets.length;
613
667
  if (!needProfitTx) {
614
- // 不需要利润交易,所有钱包各 1 个 nonce
615
668
  return await nonceManager.getNextNoncesForWallets(wallets);
616
669
  }
617
- // 需要利润交易:maxIndex 钱包需要 2 个连续 nonce
618
670
  const maxIndexNonces = await nonceManager.getNextNonceBatch(wallets[maxIndex], 2);
619
- // 其他钱包各需要 1 个 nonce
620
671
  const otherWallets = wallets.filter((_, i) => i !== maxIndex);
621
672
  const otherNonces = otherWallets.length > 0
622
673
  ? await nonceManager.getNextNoncesForWallets(otherWallets)
623
674
  : [];
624
- // 组装最终的 nonces 数组(保持原顺序)
625
675
  const nonces = [];
626
676
  let otherIdx = 0;
627
677
  for (let i = 0; i < wallets.length; i++) {
628
678
  if (i === maxIndex) {
629
- nonces.push(maxIndexNonces[0]); // 买入交易用第一个 nonce
679
+ nonces.push(maxIndexNonces[0]);
630
680
  }
631
681
  else {
632
682
  nonces.push(otherNonces[otherIdx++]);
@@ -634,116 +684,3 @@ async function allocateProfitAwareNonces(wallets, extractProfit, maxIndex, total
634
684
  }
635
685
  return nonces;
636
686
  }
637
- /**
638
- * ✅ 获取卖出报价(用于计算利润,不用于滑点保护)
639
- * ✅ 已移除滑点保护:minOutput 固定为 0
640
- */
641
- async function resolveSellOutputs({ params, provider, tokenAddress, routeType, amountsWei }) {
642
- // ✅ 已移除滑点保护:minOutput 固定为 0
643
- const minOuts = new Array(amountsWei.length).fill(0n);
644
- // 如果只有 1 个,直接调用(避免 multicall 开销)
645
- if (amountsWei.length === 1) {
646
- const quotedOutput = await getSingleQuote(params, provider, tokenAddress, routeType, amountsWei[0]);
647
- return { quotedOutputs: [quotedOutput], minOuts };
648
- }
649
- // ✅ 使用 Multicall3 批量获取报价(仅 V2 路由支持,用于计算利润)
650
- if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
651
- try {
652
- const v2RouterIface = new Interface(PANCAKE_V2_ROUTER_ABI);
653
- const multicall = new Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
654
- const calls = amountsWei.map(amount => ({
655
- target: PANCAKE_V2_ROUTER_ADDRESS,
656
- allowFailure: true,
657
- callData: v2RouterIface.encodeFunctionData('getAmountsOut', [amount, params.v2Path])
658
- }));
659
- const results = await multicall.aggregate3.staticCall(calls);
660
- const quotedOutputs = results.map((r) => {
661
- if (r.success && r.returnData && r.returnData !== '0x') {
662
- try {
663
- const decoded = v2RouterIface.decodeFunctionResult('getAmountsOut', r.returnData);
664
- const amounts = decoded[0];
665
- return amounts[amounts.length - 1];
666
- }
667
- catch {
668
- return 0n;
669
- }
670
- }
671
- return 0n;
672
- });
673
- return { quotedOutputs, minOuts };
674
- }
675
- catch {
676
- // Multicall 失败,回退到并行调用
677
- }
678
- }
679
- // 回退:并行调用(V3 路由或 Multicall 失败时)
680
- const quotedOutputs = await Promise.all(amountsWei.map(amount => getSingleQuote(params, provider, tokenAddress, routeType, amount)));
681
- return { quotedOutputs, minOuts };
682
- }
683
- /**
684
- * 获取单个报价
685
- */
686
- async function getSingleQuote(params, provider, tokenAddress, routeType, amount) {
687
- try {
688
- if (routeType === 'v2') {
689
- if (!params.v2Path || params.v2Path.length < 2) {
690
- throw new Error('v2Path is required for V2 routing');
691
- }
692
- const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, provider);
693
- const amounts = await v2Router.getAmountsOut(amount, params.v2Path);
694
- return amounts[amounts.length - 1];
695
- }
696
- if (routeType === 'v3-single') {
697
- if (!params.v3TokenOut || !params.v3Fee) {
698
- throw new Error('v3TokenOut and v3Fee are required for V3 single-hop');
699
- }
700
- const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, PANCAKE_V3_QUOTER_ABI, provider);
701
- const result = await quoter.quoteExactInputSingle.staticCall({
702
- tokenIn: tokenAddress,
703
- tokenOut: params.v3TokenOut,
704
- amountIn: amount,
705
- fee: params.v3Fee,
706
- sqrtPriceLimitX96: 0
707
- });
708
- return result[0];
709
- }
710
- if (routeType === 'v3-multi') {
711
- if (!params.v3LpAddresses || params.v3LpAddresses.length === 0 || !params.v3ExactTokenIn) {
712
- throw new Error('v3LpAddresses and v3ExactTokenIn are required for V3 multi-hop');
713
- }
714
- if (params.v2Path && params.v2Path.length >= 2) {
715
- const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, provider);
716
- const amounts = await v2Router.getAmountsOut(amount, params.v2Path);
717
- return amounts[amounts.length - 1];
718
- }
719
- return 0n;
720
- }
721
- throw new Error(`Unsupported routeType: ${routeType}`);
722
- }
723
- catch {
724
- return 0n;
725
- }
726
- }
727
- async function buildSellTransactions({ routeType, params, proxies, wallets, tokenAddress, amountsWei, minOuts }) {
728
- const needBNB = false;
729
- if (routeType === 'v2') {
730
- if (!params.v2Path || params.v2Path.length < 2) {
731
- throw new Error('v2Path is required for V2 routing');
732
- }
733
- return await buildV2Transactions(proxies, wallets, amountsWei, minOuts, params.v2Path, false, needBNB);
734
- }
735
- if (routeType === 'v3-single') {
736
- if (!params.v3TokenOut || !params.v3Fee) {
737
- throw new Error('v3TokenOut and v3Fee are required for V3 single-hop');
738
- }
739
- return await buildV3SingleTransactions(proxies, wallets, tokenAddress, params.v3TokenOut, params.v3Fee, amountsWei, minOuts, false, needBNB);
740
- }
741
- if (routeType === 'v3-multi') {
742
- if (!params.v3LpAddresses || params.v3LpAddresses.length === 0 || !params.v3ExactTokenIn) {
743
- throw new Error('v3LpAddresses and v3ExactTokenIn are required for V3 multi-hop');
744
- }
745
- return await buildV3MultiHopTransactions(proxies, wallets, params.v3LpAddresses, params.v3ExactTokenIn, amountsWei, minOuts, false, needBNB);
746
- }
747
- throw new Error(`Unsupported routeType: ${routeType}`);
748
- }
749
- // ✅ 贿赂交易和利润交易已内联到各函数中,确保正确的顺序:贿赂 → 主交易 → 利润