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.
- package/dist/abis/common.d.ts +85 -0
- package/dist/abis/common.js +242 -0
- package/dist/abis/index.d.ts +1 -0
- package/dist/abis/index.js +2 -0
- package/dist/contracts/tm-bundle-merkle/approve-tokenmanager.js +1 -6
- package/dist/contracts/tm-bundle-merkle/core.js +9 -16
- package/dist/contracts/tm-bundle-merkle/internal.js +6 -8
- package/dist/contracts/tm-bundle-merkle/pancake-proxy.d.ts +12 -6
- package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +224 -166
- package/dist/contracts/tm-bundle-merkle/private.js +6 -19
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +2 -7
- package/dist/contracts/tm-bundle-merkle/swap-internal.d.ts +1 -1
- package/dist/contracts/tm-bundle-merkle/swap-internal.js +9 -2
- package/dist/contracts/tm-bundle-merkle/swap.js +1 -3
- package/dist/contracts/tm-bundle-merkle/types.d.ts +20 -0
- package/dist/contracts/tm-bundle-merkle/utils.js +164 -175
- package/dist/dex/direct-router.d.ts +2 -1
- package/dist/dex/direct-router.js +25 -140
- package/dist/flap/constants.d.ts +2 -1
- package/dist/flap/constants.js +2 -1
- package/dist/flap/meta.js +6 -4
- package/dist/flap/portal-bundle-merkle/config.js +6 -12
- package/dist/flap/portal-bundle-merkle/core.js +8 -11
- package/dist/flap/portal-bundle-merkle/pancake-proxy.d.ts +12 -10
- package/dist/flap/portal-bundle-merkle/pancake-proxy.js +307 -370
- package/dist/flap/portal-bundle-merkle/private.js +1 -1
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +12 -30
- package/dist/flap/portal-bundle-merkle/swap.js +13 -26
- package/dist/flap/portal-bundle-merkle/types.d.ts +22 -2
- package/dist/flap/portal-bundle-merkle/utils.js +11 -16
- package/dist/index.d.ts +3 -2
- package/dist/index.js +9 -2
- package/dist/pancake/bundle-buy-first.js +56 -38
- package/dist/pancake/bundle-swap.js +114 -61
- package/dist/utils/bundle-helpers.d.ts +28 -0
- package/dist/utils/bundle-helpers.js +64 -0
- package/dist/utils/constants.d.ts +23 -1
- package/dist/utils/constants.js +37 -7
- package/dist/utils/erc20.js +17 -25
- package/dist/utils/lp-inspect.js +9 -20
- package/dist/utils/private-sale.js +1 -2
- package/dist/utils/quote-helpers.js +3 -29
- package/dist/utils/swap-helpers.js +1 -6
- package/dist/utils/wallet.d.ts +8 -13
- package/dist/utils/wallet.js +154 -342
- 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
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
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
|
|
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
|
-
*
|
|
67
|
+
* ✅ 构建 V2 交易(使用官方 Router)
|
|
90
68
|
*/
|
|
91
|
-
function
|
|
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(
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
93
|
+
* ✅ 构建 V3 单跳交易(使用官方 SwapRouter02 + multicall)
|
|
106
94
|
*/
|
|
107
|
-
async function buildV3SingleTransactions(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
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(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
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,
|
|
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(
|
|
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,
|
|
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
|
-
* 授权代币给
|
|
263
|
+
* ✅ 授权代币给 PancakeSwap Router(根据 routeType 选择 V2/V3)
|
|
179
264
|
*/
|
|
180
265
|
export async function approveFourPancakeProxy(params) {
|
|
181
|
-
const { privateKey, tokenAddress, amount, rpcUrl } = params;
|
|
182
|
-
|
|
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
|
-
* 批量授权代币给
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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
|
-
* 使用
|
|
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));
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
365
|
-
|
|
436
|
+
// ✅ V3 多跳:需要 v3Tokens 和 v3Fees 参数
|
|
437
|
+
if (!params.v3Tokens || params.v3Tokens.length < 2) {
|
|
438
|
+
throw new Error('v3Tokens(至少 2 个代币)是 V3 多跳必需的');
|
|
366
439
|
}
|
|
367
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
* 使用
|
|
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,
|
|
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
|
-
//
|
|
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
|
-
|
|
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);
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
561
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
};
|