four-flap-meme-sdk 1.4.61 → 1.4.63

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.
@@ -24,6 +24,8 @@ export interface FourBundleBuyFirstSignParams {
24
24
  sellerPrivateKey: string;
25
25
  tokenAddress: string;
26
26
  config: FourBuyFirstSignConfig;
27
+ buyCount?: number;
28
+ sellCount?: number;
27
29
  }
28
30
  export interface FourBundleBuyFirstParams {
29
31
  buyerPrivateKey: string;
@@ -43,6 +45,8 @@ export type FourBuyFirstResult = {
43
45
  buyAmount: string;
44
46
  sellAmount: string;
45
47
  profitAmount?: string;
48
+ buyCount?: number;
49
+ sellCount?: number;
46
50
  };
47
51
  };
48
52
  export declare function fourBundleBuyFirstMerkle(params: FourBundleBuyFirstSignParams): Promise<FourBuyFirstResult>;
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { ethers, Contract, Wallet } from 'ethers';
7
7
  import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../utils/bundle-helpers.js';
8
- import { ADDRESSES, PROFIT_CONFIG, BLOCKRAZOR_BUILDER_EOA } from '../../utils/constants.js';
8
+ import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA } from '../../utils/constants.js';
9
9
  import { TM_ABI, HELPER3_ABI, TM_ADDRESS } from './swap-internal.js';
10
10
  import { getTxType, getGasPriceConfig, getProfitRecipient, getBribeAmount } from './config.js';
11
11
  import { trySell } from '../tm.js';
@@ -17,8 +17,61 @@ function getGasLimit(config, defaultGas = 800000) {
17
17
  const multiplier = config.gasLimitMultiplier ?? 1.0;
18
18
  return BigInt(Math.ceil(defaultGas * multiplier));
19
19
  }
20
+ // ==================== 多笔买卖常量 ====================
21
+ /** 最大 Bundle 签名数 */
22
+ const MAX_BUNDLE_SIGNATURES = 50;
23
+ /** 贿赂交易数 */
24
+ const BRIBE_TX_COUNT = 1;
25
+ /** 利润中转交易数(支付者 1 笔 + 中转钱包 PROFIT_HOP_COUNT 笔 + 最终接收 1 笔) */
26
+ const PROFIT_TX_COUNT = PROFIT_HOP_COUNT + 2; // 2 + 2 = 4
27
+ /** 最大买卖交易数 */
28
+ const MAX_SWAP_TX_COUNT = MAX_BUNDLE_SIGNATURES - BRIBE_TX_COUNT - PROFIT_TX_COUNT; // 50 - 1 - 4 = 45
29
+ /** 每笔交易利润比例(基点):3 bps = 0.03% = 万分之三 */
30
+ const PROFIT_RATE_PER_TX_BPS = 3;
31
+ /**
32
+ * 验证买卖笔数
33
+ */
34
+ function validateSwapCounts(buyCount, sellCount) {
35
+ if (buyCount < 1) {
36
+ throw new Error(`买入笔数必须至少为 1,当前为 ${buyCount}`);
37
+ }
38
+ if (sellCount < 1) {
39
+ throw new Error(`卖出笔数必须至少为 1,当前为 ${sellCount}`);
40
+ }
41
+ const totalSwaps = buyCount + sellCount;
42
+ if (totalSwaps > MAX_SWAP_TX_COUNT) {
43
+ throw new Error(`买卖笔数总和 (${totalSwaps}) 超出限制 (${MAX_SWAP_TX_COUNT}),` +
44
+ `最大签名数为 ${MAX_BUNDLE_SIGNATURES}(${BRIBE_TX_COUNT} 贿赂 + ${PROFIT_TX_COUNT} 利润 + ${MAX_SWAP_TX_COUNT} 买卖)`);
45
+ }
46
+ }
47
+ /**
48
+ * 将金额均匀拆分成多份
49
+ */
50
+ function splitAmount(totalAmount, count) {
51
+ if (count <= 0) {
52
+ throw new Error('拆分份数必须大于 0');
53
+ }
54
+ if (count === 1) {
55
+ return [totalAmount];
56
+ }
57
+ const baseAmount = totalAmount / BigInt(count);
58
+ const remainder = totalAmount % BigInt(count);
59
+ const amounts = [];
60
+ for (let i = 0; i < count - 1; i++) {
61
+ amounts.push(baseAmount);
62
+ }
63
+ amounts.push(baseAmount + remainder);
64
+ return amounts;
65
+ }
20
66
  export async function fourBundleBuyFirstMerkle(params) {
21
- const { buyerPrivateKey, buyerFunds, buyerFundsPercentage, sellerPrivateKey, tokenAddress, config } = params;
67
+ const { buyerPrivateKey, buyerFunds, buyerFundsPercentage, sellerPrivateKey, tokenAddress, config, buyCount: _buyCount, sellCount: _sellCount } = params;
68
+ // ✅ 解析并验证买卖笔数
69
+ const buyCount = _buyCount ?? 1;
70
+ const sellCount = _sellCount ?? 1;
71
+ validateSwapCounts(buyCount, sellCount);
72
+ // ✅ 计算利润比例:每笔万分之3
73
+ const totalTxCount = buyCount + sellCount;
74
+ const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
22
75
  const chainIdNum = config.chainId ?? 56;
23
76
  const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
24
77
  chainId: chainIdNum,
@@ -75,115 +128,82 @@ export async function fourBundleBuyFirstMerkle(params) {
75
128
  // ✅ 优化:第三批并行 - trySell、构建交易、获取 nonces
76
129
  const tmBuyer = new Contract(TM_ADDRESS, TM_ABI, buyer);
77
130
  const tmSeller = new Contract(TM_ADDRESS, TM_ABI, seller);
131
+ // ✅ 拆分买入和卖出金额
132
+ const buyAmountsWei = splitAmount(buyerFundsWei, buyCount);
133
+ const sellAmountsWei = splitAmount(sellAmountWei, sellCount);
78
134
  // 预先规划 nonces
79
135
  const extractProfit = true;
80
- const profitRateBps = PROFIT_CONFIG.RATE_BPS_SWAP; // 万分之六
81
136
  // ✅ 获取贿赂金额
82
137
  const bribeAmount = getBribeAmount(config);
83
138
  const needBribeTx = bribeAmount > 0n;
84
- // 计算需要的 nonce 数量
85
- let buyerNonceCount = 1; // 买入交易
86
- let sellerNonceCount = 1; // 卖出交易
87
- if (needBribeTx)
88
- sellerNonceCount++; // 贿赂交易(由卖方发送)
89
- if (extractProfit)
90
- sellerNonceCount++; // 利润交易
91
- // ✅ 优化:使用批量 nonce 获取(单次网络往返)
92
- const getNoncesPromise = sameAddress
93
- ? nonceManager.getNextNonceBatch(seller, buyerNonceCount + sellerNonceCount)
94
- .then(nonces => ({ buyerNonces: [], sellerNonces: nonces }))
95
- : nonceManager.getNextNoncesForWallets([buyer, seller])
96
- .then(initialNonces => ({
97
- // buyer 只需要 1 个 nonce
98
- buyerNonces: [initialNonces[0]],
99
- // seller 需要 sellerNonceCount 个连续 nonce,从初始 nonce 开始
100
- sellerNonces: Array.from({ length: sellerNonceCount }, (_, i) => initialNonces[1] + i)
101
- }));
102
- const [sellResult, buyUnsigned, sellUnsigned, noncesResult] = await Promise.all([
139
+ // 构建多笔买入和卖出交易
140
+ const buyUnsignedPromises = buyAmountsWei.map(amount => tmBuyer.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyer.address, amount, 0n, { value: amount }));
141
+ const sellUnsignedPromises = sellAmountsWei.map(amount => tmSeller.sellToken.populateTransaction(0n, tokenAddress, amount, 0n));
142
+ // ✅ 规划多笔交易 nonce
143
+ const multiNoncePlan = await planMultiNonces({
144
+ buyer,
145
+ seller,
146
+ buyCount,
147
+ sellCount,
148
+ extractProfit,
149
+ needBribeTx,
150
+ sameAddress,
151
+ nonceManager
152
+ });
153
+ const [sellResult, buyUnsignedArray, sellUnsignedArray] = await Promise.all([
103
154
  trySell('BSC', config.rpcUrl, tokenAddress, sellAmountWei),
104
- tmBuyer.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyer.address, buyerFundsWei, 0n, { value: buyerFundsWei }),
105
- tmSeller.sellToken.populateTransaction(0n, tokenAddress, sellAmountWei, 0n),
106
- getNoncesPromise
155
+ Promise.all(buyUnsignedPromises),
156
+ Promise.all(sellUnsignedPromises)
107
157
  ]);
108
- const { buyerNonces, sellerNonces } = noncesResult;
109
158
  const estimatedSellFunds = sellResult.funds;
110
159
  const profitAmount = extractProfit ? (estimatedSellFunds * BigInt(profitRateBps)) / 10000n : 0n;
111
- // 分配 nonces
112
- let buyerNonce;
113
- let sellerNonce;
114
- let bribeNonce;
115
- let profitNonce;
116
- if (sameAddress) {
117
- let idx = 0;
118
- if (needBribeTx)
119
- bribeNonce = sellerNonces[idx++];
120
- buyerNonce = sellerNonces[idx++];
121
- sellerNonce = sellerNonces[idx++];
122
- if (extractProfit)
123
- profitNonce = sellerNonces[idx];
124
- }
125
- else {
126
- buyerNonce = buyerNonces[0];
127
- let idx = 0;
128
- if (needBribeTx)
129
- bribeNonce = sellerNonces[idx++];
130
- sellerNonce = sellerNonces[idx++];
131
- if (extractProfit)
132
- profitNonce = sellerNonces[idx];
133
- }
134
- // ✅ 并行签名所有交易
135
- const signPromises = [];
136
160
  // ✅ 贿赂交易放在首位
137
161
  let bribeTx = null;
138
- if (needBribeTx && bribeNonce !== undefined) {
139
- signPromises.push(seller.signTransaction({
162
+ if (needBribeTx && multiNoncePlan.bribeNonce !== undefined) {
163
+ bribeTx = await seller.signTransaction({
140
164
  to: BLOCKRAZOR_BUILDER_EOA,
141
165
  value: bribeAmount,
142
- nonce: bribeNonce,
166
+ nonce: multiNoncePlan.bribeNonce,
143
167
  gasPrice,
144
168
  gasLimit: 21000n,
145
169
  chainId: chainIdNum,
146
170
  type: txType
147
- }));
171
+ });
148
172
  }
149
- // 买入和卖出交易
150
- signPromises.push(buyer.signTransaction({
151
- ...buyUnsigned,
173
+ // ✅ 并行签名所有买入和卖出交易
174
+ const buySignPromises = buyUnsignedArray.map((unsigned, i) => buyer.signTransaction({
175
+ ...unsigned,
152
176
  from: buyer.address,
153
- nonce: buyerNonce,
177
+ nonce: multiNoncePlan.buyerNonces[i],
154
178
  gasLimit: finalGasLimit,
155
179
  gasPrice,
156
180
  chainId: chainIdNum,
157
181
  type: txType,
158
- value: buyerFundsWei
159
- }),
160
- // 卖出交易 value 必须为 0,不能发送原生代币
161
- seller.signTransaction({
162
- ...sellUnsigned,
182
+ value: buyAmountsWei[i]
183
+ }));
184
+ const sellSignPromises = sellUnsignedArray.map((unsigned, i) => seller.signTransaction({
185
+ ...unsigned,
163
186
  from: seller.address,
164
- nonce: sellerNonce,
187
+ nonce: multiNoncePlan.sellerNonces[i],
165
188
  gasLimit: finalGasLimit,
166
189
  gasPrice,
167
190
  chainId: chainIdNum,
168
191
  type: txType,
169
- value: 0n // ✅ 卖出交易不发送原生代币
192
+ value: 0n
170
193
  }));
171
- const signedTxs = await Promise.all(signPromises);
172
- // 解析签名结果
173
- let idx = 0;
174
- if (needBribeTx)
175
- bribeTx = signedTxs[idx++];
176
- const signedBuy = signedTxs[idx++];
177
- const signedSell = signedTxs[idx++];
194
+ const [signedBuys, signedSells] = await Promise.all([
195
+ Promise.all(buySignPromises),
196
+ Promise.all(sellSignPromises)
197
+ ]);
178
198
  nonceManager.clearTemp();
179
- // ✅ 组装交易列表:贿赂 → 买入 → 卖出
199
+ // ✅ 组装交易列表:贿赂 → 买入(多笔) → 卖出(多笔)
180
200
  const allTransactions = [];
181
201
  if (bribeTx)
182
202
  allTransactions.push(bribeTx);
183
- allTransactions.push(signedBuy, signedSell);
203
+ allTransactions.push(...signedBuys, ...signedSells);
184
204
  // ✅ 利润多跳转账(强制 2 跳中转)
185
205
  let profitHopWallets;
186
- if (extractProfit && profitAmount > 0n && profitNonce !== undefined) {
206
+ if (extractProfit && profitAmount > 0n && multiNoncePlan.profitNonce !== undefined) {
187
207
  const profitHopResult = await buildProfitHopTransactions({
188
208
  provider,
189
209
  payerWallet: seller,
@@ -193,7 +213,7 @@ export async function fourBundleBuyFirstMerkle(params) {
193
213
  gasPrice,
194
214
  chainId: chainIdNum,
195
215
  txType,
196
- startNonce: profitNonce
216
+ startNonce: multiNoncePlan.profitNonce
197
217
  });
198
218
  allTransactions.push(...profitHopResult.signedTransactions);
199
219
  profitHopWallets = profitHopResult.hopWallets; // ✅ 收集利润多跳钱包
@@ -206,7 +226,42 @@ export async function fourBundleBuyFirstMerkle(params) {
206
226
  sellerAddress: seller.address,
207
227
  buyAmount: ethers.formatEther(buyerFundsWei),
208
228
  sellAmount: ethers.formatUnits(sellAmountWei, decimals),
209
- profitAmount: extractProfit ? ethers.formatEther(profitAmount) : undefined
229
+ profitAmount: extractProfit ? ethers.formatEther(profitAmount) : undefined,
230
+ // ✅ 返回多笔买卖信息
231
+ buyCount,
232
+ sellCount
210
233
  }
211
234
  };
212
235
  }
236
+ /**
237
+ * ✅ 规划多笔交易 nonce
238
+ * 交易顺序:贿赂 → 买入(多笔) → 卖出(多笔) → 利润
239
+ */
240
+ async function planMultiNonces({ buyer, seller, buyCount, sellCount, extractProfit, needBribeTx, sameAddress, nonceManager }) {
241
+ const profitNonceCount = extractProfit ? 1 : 0;
242
+ if (sameAddress) {
243
+ const bribeTxCount = needBribeTx ? 1 : 0;
244
+ const totalTxCount = bribeTxCount + buyCount + sellCount + profitNonceCount;
245
+ const nonces = await nonceManager.getNextNonceBatch(buyer, totalTxCount);
246
+ let idx = 0;
247
+ const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
248
+ const buyerNonces = nonces.slice(idx, idx + buyCount);
249
+ idx += buyCount;
250
+ const sellerNonces = nonces.slice(idx, idx + sellCount);
251
+ idx += sellCount;
252
+ const profitNonce = extractProfit ? nonces[idx] : undefined;
253
+ return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
254
+ }
255
+ const bribeTxCount = needBribeTx ? 1 : 0;
256
+ const sellerTxCount = bribeTxCount + sellCount + profitNonceCount;
257
+ const [buyerNonces, sellerNoncesAll] = await Promise.all([
258
+ nonceManager.getNextNonceBatch(buyer, buyCount),
259
+ nonceManager.getNextNonceBatch(seller, sellerTxCount)
260
+ ]);
261
+ let idx = 0;
262
+ const bribeNonce = needBribeTx ? sellerNoncesAll[idx++] : undefined;
263
+ const sellerNonces = sellerNoncesAll.slice(idx, idx + sellCount);
264
+ idx += sellCount;
265
+ const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
266
+ return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
267
+ }
@@ -30,6 +30,8 @@ export interface FlapBundleBuyFirstSignParams {
30
30
  config: FlapBuyFirstSignConfig;
31
31
  quoteToken?: string;
32
32
  quoteTokenDecimals?: number;
33
+ buyCount?: number;
34
+ sellCount?: number;
33
35
  }
34
36
  export interface FlapBundleBuyFirstParams {
35
37
  chain: FlapChain;
@@ -50,6 +52,8 @@ export type FlapBuyFirstResult = {
50
52
  buyAmount: string;
51
53
  sellAmount: string;
52
54
  profitAmount?: string;
55
+ buyCount?: number;
56
+ sellCount?: number;
53
57
  };
54
58
  };
55
59
  export declare function flapBundleBuyFirstMerkle(params: FlapBundleBuyFirstSignParams): Promise<FlapBuyFirstResult>;
@@ -57,6 +57,33 @@ function getGasLimit(config, defaultGas = 800000) {
57
57
  const multiplier = config.gasLimitMultiplier ?? 1.0;
58
58
  return BigInt(Math.ceil(defaultGas * multiplier));
59
59
  }
60
+ // ==================== 多笔买卖常量 ====================
61
+ /** 最大 Bundle 签名数 */
62
+ const MAX_BUNDLE_SIGNATURES = 50;
63
+ /** 贿赂交易数 */
64
+ const BRIBE_TX_COUNT = 1;
65
+ /** 利润中转交易数(支付者 1 笔 + 中转钱包 PROFIT_HOP_COUNT 笔 + 最终接收 1 笔) */
66
+ const PROFIT_TX_COUNT = PROFIT_HOP_COUNT + 2; // 2 + 2 = 4
67
+ /** 最大买卖交易数 */
68
+ const MAX_SWAP_TX_COUNT = MAX_BUNDLE_SIGNATURES - BRIBE_TX_COUNT - PROFIT_TX_COUNT; // 50 - 1 - 4 = 45
69
+ /** 每笔交易利润比例(基点):3 bps = 0.03% = 万分之三 */
70
+ const PROFIT_RATE_PER_TX_BPS = 3;
71
+ /**
72
+ * 验证买卖笔数
73
+ */
74
+ function validateSwapCounts(buyCount, sellCount) {
75
+ if (buyCount < 1) {
76
+ throw new Error(`买入笔数必须至少为 1,当前为 ${buyCount}`);
77
+ }
78
+ if (sellCount < 1) {
79
+ throw new Error(`卖出笔数必须至少为 1,当前为 ${sellCount}`);
80
+ }
81
+ const totalSwaps = buyCount + sellCount;
82
+ if (totalSwaps > MAX_SWAP_TX_COUNT) {
83
+ throw new Error(`买卖笔数总和 (${totalSwaps}) 超出限制 (${MAX_SWAP_TX_COUNT}),` +
84
+ `最大签名数为 ${MAX_BUNDLE_SIGNATURES}(${BRIBE_TX_COUNT} 贿赂 + ${PROFIT_TX_COUNT} 利润 + ${MAX_SWAP_TX_COUNT} 买卖)`);
85
+ }
86
+ }
60
87
  function getChainId(chain) {
61
88
  switch (chain) {
62
89
  case 'bsc': return 56;
@@ -73,7 +100,14 @@ function getNativeTokenName(chain) {
73
100
  }
74
101
  // 使用公共工具的 getGasLimit,移除本地重复实现
75
102
  export async function flapBundleBuyFirstMerkle(params) {
76
- const { chain, buyerPrivateKey, sellerPrivateKey, tokenAddress, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals } = params;
103
+ const { chain, buyerPrivateKey, sellerPrivateKey, tokenAddress, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals, buyCount: _buyCount, sellCount: _sellCount } = params;
104
+ // ✅ 解析并验证买卖笔数
105
+ const buyCount = _buyCount ?? 1;
106
+ const sellCount = _sellCount ?? 1;
107
+ validateSwapCounts(buyCount, sellCount);
108
+ // ✅ 计算利润比例:每笔万分之3
109
+ const totalTxCount = buyCount + sellCount;
110
+ const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
77
111
  const chainContext = createChainContext(chain, config.rpcUrl);
78
112
  const buyer = new Wallet(buyerPrivateKey, chainContext.provider);
79
113
  const seller = new Wallet(sellerPrivateKey, chainContext.provider);
@@ -123,97 +157,106 @@ export async function flapBundleBuyFirstMerkle(params) {
123
157
  });
124
158
  const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
125
159
  const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
126
- // ✅ 第三批并行 - buyUnsigned、sellUnsigned、estimatedSellFunds(不含 nonce 相关操作)
127
- const [buyUnsigned, sellUnsigned, estimatedSellFunds] = await Promise.all([
128
- portalBuyer.swapExactInput.populateTransaction({
129
- inputToken, // ✅ 使用动态输入代币
130
- outputToken: tokenAddress,
131
- inputAmount: buyerFundsWei,
132
- minOutputAmount: sellAmountWei,
133
- permitData: '0x'
134
- }, useNativeToken ? { value: buyerFundsWei } : {} // ✅ ERC20 购买时 value=0
135
- ),
136
- portalSeller.swapExactInput.populateTransaction({
137
- inputToken: tokenAddress,
138
- outputToken, // 使用动态输出代币
139
- inputAmount: sellAmountWei,
140
- minOutputAmount: 0,
141
- permitData: '0x'
142
- }),
160
+ // ✅ 拆分买入和卖出金额
161
+ const buyAmountsWei = splitAmount(buyerFundsWei, buyCount);
162
+ const sellAmountsWei = splitAmount(sellAmountWei, sellCount);
163
+ // ✅ 计算每笔买入预期获得的代币数量(用于 minOutputAmount)
164
+ const minOutputPerBuy = sellAmountWei / BigInt(buyCount);
165
+ // ✅ 第三批并行 - 构建多笔买入和卖出交易、estimatedSellFunds
166
+ const buyUnsignedPromises = buyAmountsWei.map(amount => portalBuyer.swapExactInput.populateTransaction({
167
+ inputToken,
168
+ outputToken: tokenAddress,
169
+ inputAmount: amount,
170
+ minOutputAmount: minOutputPerBuy,
171
+ permitData: '0x'
172
+ }, useNativeToken ? { value: amount } : {}));
173
+ const sellUnsignedPromises = sellAmountsWei.map(amount => portalSeller.swapExactInput.populateTransaction({
174
+ inputToken: tokenAddress,
175
+ outputToken,
176
+ inputAmount: amount,
177
+ minOutputAmount: 0,
178
+ permitData: '0x'
179
+ }));
180
+ const [buyUnsignedArray, sellUnsignedArray, estimatedSellFunds] = await Promise.all([
181
+ Promise.all(buyUnsignedPromises),
182
+ Promise.all(sellUnsignedPromises),
143
183
  estimateSellFunds(portalSeller, tokenAddress, sellAmountWei, outputToken)
144
184
  ]);
145
185
  // ✅ 获取贿赂金额
146
186
  const bribeAmount = getBribeAmount(config);
147
187
  const needBribeTx = bribeAmount > 0n;
148
- // ✅ 规划 nonce(已移除授权交易)
149
- const noncePlan = await planNonces({
188
+ // ✅ 规划多笔交易 nonce
189
+ const multiNoncePlan = await planMultiNonces({
150
190
  buyer,
151
191
  seller,
192
+ buyCount,
193
+ sellCount,
152
194
  extractProfit: true,
153
195
  needBribeTx,
154
196
  sameAddress,
155
197
  nonceManager
156
198
  });
157
- // ✅ 修复:基于卖出收益估算利润
199
+ // ✅ 修复:基于卖出收益估算利润(使用动态利润比例)
158
200
  const profitBase = estimatedSellFunds > 0n ? estimatedSellFunds : buyerFundsWei;
159
- const tokenProfitAmount = calculateProfitAmount(profitBase);
201
+ const tokenProfitAmount = calculateMultiProfitAmount(profitBase, profitRateBps);
160
202
  // ✅ ERC20 购买:获取代币利润等值的原生代币(BNB)报价
161
203
  let nativeProfitAmount = tokenProfitAmount; // 原生代币购买时直接使用
162
204
  if (!useNativeToken && tokenProfitAmount > 0n) {
163
205
  // 将代币利润转换为等值 BNB
164
206
  nativeProfitAmount = await getTokenToNativeQuote(chainContext.provider, inputToken, tokenProfitAmount, chainContext.chainId);
165
207
  }
166
- const buyTx = buildTransactionRequest(buyUnsigned, {
208
+ // 构建多笔买入交易
209
+ const buyTxs = buyUnsignedArray.map((unsigned, i) => buildTransactionRequest(unsigned, {
167
210
  from: buyer.address,
168
- nonce: noncePlan.buyerNonce,
211
+ nonce: multiNoncePlan.buyerNonces[i],
169
212
  gasLimit: finalGasLimit,
170
213
  gasPrice,
171
214
  priorityFee,
172
215
  chainId: chainContext.chainId,
173
216
  txType,
174
- value: useNativeToken ? buyerFundsWei : 0n // ✅ ERC20 购买时 value=0
175
- });
176
- // ✅ 卖出交易 value 必须为 0,不能发送原生代币
177
- const sellTx = buildTransactionRequest(sellUnsigned, {
217
+ value: useNativeToken ? buyAmountsWei[i] : 0n
218
+ }));
219
+ // ✅ 构建多笔卖出交易
220
+ const sellTxs = sellUnsignedArray.map((unsigned, i) => buildTransactionRequest(unsigned, {
178
221
  from: seller.address,
179
- nonce: noncePlan.sellerNonce,
222
+ nonce: multiNoncePlan.sellerNonces[i],
180
223
  gasLimit: finalGasLimit,
181
224
  gasPrice,
182
225
  priorityFee,
183
226
  chainId: chainContext.chainId,
184
227
  txType,
185
- value: 0n // ✅ 卖出交易不发送原生代币
186
- });
228
+ value: 0n
229
+ }));
187
230
  // ✅ 贿赂交易放在首位
188
231
  let bribeTx = null;
189
- if (needBribeTx && noncePlan.bribeNonce !== undefined) {
232
+ if (needBribeTx && multiNoncePlan.bribeNonce !== undefined) {
190
233
  bribeTx = await seller.signTransaction({
191
234
  to: BLOCKRAZOR_BUILDER_EOA,
192
235
  value: bribeAmount,
193
- nonce: noncePlan.bribeNonce,
236
+ nonce: multiNoncePlan.bribeNonce,
194
237
  gasPrice,
195
238
  gasLimit: 21000n,
196
239
  chainId: chainContext.chainId,
197
240
  type: txType
198
241
  });
199
242
  }
200
- // ✅ 并行签名买入和卖出交易
201
- const [signedBuy, signedSell] = await Promise.all([
202
- buyer.signTransaction(buyTx),
203
- seller.signTransaction(sellTx)
243
+ // ✅ 并行签名所有买入和卖出交易
244
+ const [signedBuys, signedSells] = await Promise.all([
245
+ Promise.all(buyTxs.map(tx => buyer.signTransaction(tx))),
246
+ Promise.all(sellTxs.map(tx => seller.signTransaction(tx)))
204
247
  ]);
205
248
  nonceManager.clearTemp();
206
- // ✅ 组装顺序:贿赂 → 买入 → 卖出
249
+ // ✅ 组装顺序:贿赂 → 买入(多笔) → 卖出(多笔)
207
250
  const allTransactions = [];
208
251
  if (bribeTx)
209
252
  allTransactions.push(bribeTx);
210
- allTransactions.push(signedBuy, signedSell);
253
+ allTransactions.push(...signedBuys, ...signedSells);
211
254
  // ✅ 利润多跳转账(强制 2 跳中转)
212
255
  const profitResult = await buildProfitTransaction({
213
256
  provider: chainContext.provider,
214
257
  seller,
215
258
  profitAmount: nativeProfitAmount,
216
- profitNonce: noncePlan.profitNonce,
259
+ profitNonce: multiNoncePlan.profitNonce,
217
260
  gasPrice,
218
261
  chainId: chainContext.chainId,
219
262
  txType
@@ -231,7 +274,10 @@ export async function flapBundleBuyFirstMerkle(params) {
231
274
  sellerAddress: seller.address,
232
275
  buyAmount: ethers.formatEther(buyerFundsWei),
233
276
  sellAmount: ethers.formatUnits(sellAmountWei, sellerInfo.decimals),
234
- profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined // ✅ 显示原生代币利润
277
+ profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined,
278
+ // ✅ 返回多笔买卖信息
279
+ buyCount,
280
+ sellCount
235
281
  }
236
282
  };
237
283
  }
@@ -345,6 +391,40 @@ function calculateProfitAmount(expectedFunds) {
345
391
  // 万分之六
346
392
  return (expectedFunds * BigInt(PROFIT_CONFIG.RATE_BPS_SWAP)) / 10000n;
347
393
  }
394
+ /**
395
+ * 计算多笔交易的利润金额
396
+ * @param expectedFunds - 预期收益
397
+ * @param rateBps - 利润比例(基点)
398
+ */
399
+ function calculateMultiProfitAmount(expectedFunds, rateBps) {
400
+ if (expectedFunds <= 0n || rateBps <= 0) {
401
+ return 0n;
402
+ }
403
+ return (expectedFunds * BigInt(rateBps)) / 10000n;
404
+ }
405
+ /**
406
+ * 将金额均匀拆分成多份
407
+ * @param totalAmount - 总金额
408
+ * @param count - 拆分份数
409
+ * @returns 拆分后的金额数组(最后一份包含余数)
410
+ */
411
+ function splitAmount(totalAmount, count) {
412
+ if (count <= 0) {
413
+ throw new Error('拆分份数必须大于 0');
414
+ }
415
+ if (count === 1) {
416
+ return [totalAmount];
417
+ }
418
+ const baseAmount = totalAmount / BigInt(count);
419
+ const remainder = totalAmount % BigInt(count);
420
+ // 前 count-1 份使用基础金额,最后一份包含余数
421
+ const amounts = [];
422
+ for (let i = 0; i < count - 1; i++) {
423
+ amounts.push(baseAmount);
424
+ }
425
+ amounts.push(baseAmount + remainder);
426
+ return amounts;
427
+ }
348
428
  /**
349
429
  * ✅ 规划 nonce
350
430
  * 交易顺序:贿赂 → 买入 → 卖出 → 利润
@@ -380,6 +460,45 @@ async function planNonces({ buyer, seller, extractProfit, needBribeTx, sameAddre
380
460
  const nonces = await nonceManager.getNextNoncesForWallets([buyer, seller]);
381
461
  return { buyerNonce: nonces[0], sellerNonce: nonces[1] };
382
462
  }
463
+ /**
464
+ * ✅ 规划多笔交易 nonce
465
+ * 交易顺序:贿赂 → 买入(多笔) → 卖出(多笔) → 利润
466
+ *
467
+ * 利润中转需要 PROFIT_HOP_COUNT + 1 个 nonce(支付者→跳1→跳2→利润地址)
468
+ */
469
+ async function planMultiNonces({ buyer, seller, buyCount, sellCount, extractProfit, needBribeTx, sameAddress, nonceManager }) {
470
+ // 利润中转交易数(支付者只需要 1 个 nonce,中转钱包的 nonce 由 buildProfitHopTransactions 内部处理)
471
+ const profitNonceCount = extractProfit ? 1 : 0;
472
+ if (sameAddress) {
473
+ // 同一地址:贿赂(可选) + 买入(多笔) + 卖出(多笔) + 利润(可选)
474
+ const bribeTxCount = needBribeTx ? 1 : 0;
475
+ const totalTxCount = bribeTxCount + buyCount + sellCount + profitNonceCount;
476
+ const nonces = await nonceManager.getNextNonceBatch(buyer, totalTxCount);
477
+ let idx = 0;
478
+ const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
479
+ const buyerNonces = nonces.slice(idx, idx + buyCount);
480
+ idx += buyCount;
481
+ const sellerNonces = nonces.slice(idx, idx + sellCount);
482
+ idx += sellCount;
483
+ const profitNonce = extractProfit ? nonces[idx] : undefined;
484
+ return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
485
+ }
486
+ // 不同地址
487
+ // 买方:buyCount 个 nonce
488
+ // 卖方:贿赂(可选) + sellCount + 利润(可选) 个 nonce
489
+ const bribeTxCount = needBribeTx ? 1 : 0;
490
+ const sellerTxCount = bribeTxCount + sellCount + profitNonceCount;
491
+ const [buyerNonces, sellerNoncesAll] = await Promise.all([
492
+ nonceManager.getNextNonceBatch(buyer, buyCount),
493
+ nonceManager.getNextNonceBatch(seller, sellerTxCount)
494
+ ]);
495
+ let idx = 0;
496
+ const bribeNonce = needBribeTx ? sellerNoncesAll[idx++] : undefined;
497
+ const sellerNonces = sellerNoncesAll.slice(idx, idx + sellCount);
498
+ idx += sellCount;
499
+ const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
500
+ return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
501
+ }
383
502
  function buildTransactionRequest(unsigned, { from, nonce, gasLimit, gasPrice, priorityFee, chainId, txType, value }) {
384
503
  const tx = {
385
504
  ...unsigned,
@@ -55,6 +55,8 @@ export interface PancakeBundleBuyFirstSignParams {
55
55
  quoteToken?: string;
56
56
  quoteTokenDecimals?: number;
57
57
  startNonces?: number[];
58
+ buyCount?: number;
59
+ sellCount?: number;
58
60
  }
59
61
  export interface PancakeBundleBuyFirstParams {
60
62
  buyerPrivateKey: string;
@@ -75,6 +77,8 @@ export type PancakeBuyFirstResult = {
75
77
  buyAmount: string;
76
78
  sellAmount: string;
77
79
  profitAmount?: string;
80
+ buyCount?: number;
81
+ sellCount?: number;
78
82
  };
79
83
  };
80
84
  export declare function pancakeBundleBuyFirstMerkle(params: PancakeBundleBuyFirstSignParams): Promise<PancakeBuyFirstResult>;
@@ -44,9 +44,62 @@ const PANCAKE_V3_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV3Router;
44
44
  // 常量
45
45
  const FLAT_FEE = 0n;
46
46
  const WBNB_ADDRESS = ADDRESSES.BSC.WBNB;
47
+ // ==================== 多笔买卖常量 ====================
48
+ /** 最大 Bundle 签名数 */
49
+ const MAX_BUNDLE_SIGNATURES = 50;
50
+ /** 贿赂交易数 */
51
+ const BRIBE_TX_COUNT = 1;
52
+ /** 利润中转交易数(支付者 1 笔 + 中转钱包 PROFIT_HOP_COUNT 笔 + 最终接收 1 笔) */
53
+ const PROFIT_TX_COUNT = PROFIT_HOP_COUNT + 2; // 2 + 2 = 4
54
+ /** 最大买卖交易数 */
55
+ const MAX_SWAP_TX_COUNT = MAX_BUNDLE_SIGNATURES - BRIBE_TX_COUNT - PROFIT_TX_COUNT; // 50 - 1 - 4 = 45
56
+ /** 每笔交易利润比例(基点):3 bps = 0.03% = 万分之三 */
57
+ const PROFIT_RATE_PER_TX_BPS = 3;
58
+ /**
59
+ * 验证买卖笔数
60
+ */
61
+ function validateSwapCounts(buyCount, sellCount) {
62
+ if (buyCount < 1) {
63
+ throw new Error(`买入笔数必须至少为 1,当前为 ${buyCount}`);
64
+ }
65
+ if (sellCount < 1) {
66
+ throw new Error(`卖出笔数必须至少为 1,当前为 ${sellCount}`);
67
+ }
68
+ const totalSwaps = buyCount + sellCount;
69
+ if (totalSwaps > MAX_SWAP_TX_COUNT) {
70
+ throw new Error(`买卖笔数总和 (${totalSwaps}) 超出限制 (${MAX_SWAP_TX_COUNT}),` +
71
+ `最大签名数为 ${MAX_BUNDLE_SIGNATURES}(${BRIBE_TX_COUNT} 贿赂 + ${PROFIT_TX_COUNT} 利润 + ${MAX_SWAP_TX_COUNT} 买卖)`);
72
+ }
73
+ }
74
+ /**
75
+ * 将金额均匀拆分成多份
76
+ */
77
+ function splitAmount(totalAmount, count) {
78
+ if (count <= 0) {
79
+ throw new Error('拆分份数必须大于 0');
80
+ }
81
+ if (count === 1) {
82
+ return [totalAmount];
83
+ }
84
+ const baseAmount = totalAmount / BigInt(count);
85
+ const remainder = totalAmount % BigInt(count);
86
+ const amounts = [];
87
+ for (let i = 0; i < count - 1; i++) {
88
+ amounts.push(baseAmount);
89
+ }
90
+ amounts.push(baseAmount + remainder);
91
+ return amounts;
92
+ }
47
93
  export async function pancakeBundleBuyFirstMerkle(params) {
48
- const { buyerPrivateKey, sellerPrivateKey, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18, startNonces // ✅ 可选:前端预获取的 nonces
49
- } = params;
94
+ const { buyerPrivateKey, sellerPrivateKey, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18, startNonces, // ✅ 可选:前端预获取的 nonces
95
+ buyCount: _buyCount, sellCount: _sellCount } = params;
96
+ // ✅ 解析并验证买卖笔数
97
+ const buyCount = _buyCount ?? 1;
98
+ const sellCount = _sellCount ?? 1;
99
+ validateSwapCounts(buyCount, sellCount);
100
+ // ✅ 计算利润比例:每笔万分之3
101
+ const totalTxCount = buyCount + sellCount;
102
+ const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
50
103
  // ✅ 判断是否使用原生代币(BNB)或 ERC20 代币(如 USDT)
51
104
  const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
52
105
  const context = createPancakeContext(config);
@@ -68,10 +121,14 @@ export async function pancakeBundleBuyFirstMerkle(params) {
68
121
  buyerFundsWei: buyerFundsInfo.buyerFundsWei,
69
122
  provider: context.provider
70
123
  });
71
- const swapUnsigned = await buildRouteTransactions({
124
+ // 拆分买入和卖出金额
125
+ const buyAmountsWei = splitAmount(buyerFundsInfo.buyerFundsWei, buyCount);
126
+ const sellAmountsWei = splitAmount(quoteResult.quotedTokenOut, sellCount);
127
+ // ✅ 构建多笔买入和卖出交易
128
+ const swapUnsignedArray = await buildMultiRouteTransactions({
72
129
  routeParams,
73
- buyerFundsWei: buyerFundsInfo.buyerFundsWei,
74
- sellAmountToken: quoteResult.quotedTokenOut,
130
+ buyAmountsWei,
131
+ sellAmountsWei,
75
132
  buyer,
76
133
  seller,
77
134
  tokenAddress,
@@ -92,63 +149,65 @@ export async function pancakeBundleBuyFirstMerkle(params) {
92
149
  const gasPrice = await getGasPrice(context.provider, config);
93
150
  const txType = config.txType ?? 0;
94
151
  const nonceManager = new NonceManager(context.provider);
95
- // ✅ 修复:基于买入金额估算利润,而不是基于卖出预估(因为代币可能还没有流动性)
152
+ // ✅ 修复:基于买入金额估算利润(使用动态利润比例)
96
153
  const estimatedProfitFromSell = await estimateProfitAmount({
97
154
  provider: context.provider,
98
155
  tokenAddress,
99
156
  sellAmountToken: quoteResult.quotedTokenOut,
100
- routeParams // ✅ 传递路由参数
157
+ routeParams
101
158
  });
102
- // 万分之六
103
- const profitBase = estimatedProfitFromSell > 0n ? estimatedProfitFromSell : (buyerFundsInfo.buyerFundsWei * BigInt(PROFIT_CONFIG.RATE_BPS_SWAP)) / 10000n;
104
- const profitAmount = profitBase;
159
+ // 使用动态利润比例
160
+ const profitBase = estimatedProfitFromSell > 0n ? estimatedProfitFromSell : buyerFundsInfo.buyerFundsWei;
161
+ const profitAmount = (profitBase * BigInt(profitRateBps)) / 10000n;
105
162
  // ✅ 获取贿赂金额
106
163
  const bribeAmount = config.bribeAmount && config.bribeAmount > 0
107
164
  ? ethers.parseEther(String(config.bribeAmount))
108
165
  : 0n;
109
166
  const needBribeTx = bribeAmount > 0n;
110
- // ✅ 如果前端传入了 startNonces,直接使用(性能优化,避免 nonce 冲突)
111
- const noncePlan = startNonces && startNonces.length >= (sameAddress ? 1 : 2)
112
- ? buildNoncePlanFromStartNonces(startNonces, sameAddress, profitAmount > 0n, needBribeTx)
113
- : await planNonces({
114
- buyer,
115
- seller,
116
- sameAddress,
117
- extractProfit: profitAmount > 0n,
118
- needBribeTx,
119
- nonceManager
120
- });
167
+ // ✅ 规划多笔交易 nonce(忽略 startNonces,使用新的多笔规划)
168
+ const multiNoncePlan = await planMultiNonces({
169
+ buyer,
170
+ seller,
171
+ buyCount,
172
+ sellCount,
173
+ sameAddress,
174
+ extractProfit: profitAmount > 0n,
175
+ needBribeTx,
176
+ nonceManager
177
+ });
121
178
  // ✅ 贿赂交易放在首位(由卖方发送,与利润交易同一钱包)
122
179
  let bribeTx = null;
123
- if (needBribeTx && noncePlan.bribeNonce !== undefined) {
180
+ if (needBribeTx && multiNoncePlan.bribeNonce !== undefined) {
124
181
  bribeTx = await seller.signTransaction({
125
182
  to: BLOCKRAZOR_BUILDER_EOA,
126
183
  value: bribeAmount,
127
- nonce: noncePlan.bribeNonce,
184
+ nonce: multiNoncePlan.bribeNonce,
128
185
  gasPrice,
129
186
  gasLimit: 21000n,
130
187
  chainId: context.chainId,
131
188
  type: txType
132
189
  });
133
190
  }
134
- const signedBuy = await buyer.signTransaction({
135
- ...swapUnsigned.buyUnsigned,
191
+ // 并行签名所有买入交易
192
+ const signedBuys = await Promise.all(swapUnsignedArray.buyUnsignedArray.map((unsigned, i) => buyer.signTransaction({
193
+ ...unsigned,
136
194
  from: buyer.address,
137
- nonce: noncePlan.buyerNonce,
195
+ nonce: multiNoncePlan.buyerNonces[i],
138
196
  gasLimit: finalGasLimit,
139
197
  gasPrice,
140
198
  chainId: context.chainId,
141
199
  type: txType
142
- });
143
- const signedSell = await seller.signTransaction({
144
- ...swapUnsigned.sellUnsigned,
200
+ })));
201
+ // 并行签名所有卖出交易
202
+ const signedSells = await Promise.all(swapUnsignedArray.sellUnsignedArray.map((unsigned, i) => seller.signTransaction({
203
+ ...unsigned,
145
204
  from: seller.address,
146
- nonce: noncePlan.sellerNonce,
205
+ nonce: multiNoncePlan.sellerNonces[i],
147
206
  gasLimit: finalGasLimit,
148
207
  gasPrice,
149
208
  chainId: context.chainId,
150
209
  type: txType
151
- });
210
+ })));
152
211
  nonceManager.clearTemp();
153
212
  validateFinalBalances({
154
213
  sameAddress,
@@ -162,17 +221,17 @@ export async function pancakeBundleBuyFirstMerkle(params) {
162
221
  provider: context.provider,
163
222
  buyerAddress: buyer.address
164
223
  });
165
- // ✅ 组装顺序:贿赂 → 买入 → 卖出
224
+ // ✅ 组装顺序:贿赂 → 买入(多笔) → 卖出(多笔)
166
225
  const allTransactions = [];
167
226
  if (bribeTx)
168
227
  allTransactions.push(bribeTx);
169
- allTransactions.push(signedBuy, signedSell);
228
+ allTransactions.push(...signedBuys, ...signedSells);
170
229
  // ✅ 利润多跳转账(强制 2 跳中转)
171
230
  const profitResult = await buildProfitTransaction({
172
231
  provider: context.provider,
173
232
  seller,
174
233
  profitAmount,
175
- profitNonce: noncePlan.profitNonce,
234
+ profitNonce: multiNoncePlan.profitNonce,
176
235
  gasPrice,
177
236
  chainId: context.chainId,
178
237
  txType
@@ -192,7 +251,10 @@ export async function pancakeBundleBuyFirstMerkle(params) {
192
251
  ? ethers.formatEther(buyerFundsInfo.buyerFundsWei)
193
252
  : ethers.formatUnits(buyerFundsInfo.buyerFundsWei, quoteTokenDecimals),
194
253
  sellAmount: quoteResult.quotedTokenOut.toString(),
195
- profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
254
+ profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
255
+ // ✅ 返回多笔买卖信息
256
+ buyCount,
257
+ sellCount
196
258
  }
197
259
  };
198
260
  }
@@ -362,6 +424,62 @@ async function buildRouteTransactions({ routeParams, buyerFundsWei, sellAmountTo
362
424
  // V3 多跳暂不支持官方合约
363
425
  throw new Error('V3 多跳路由暂不支持官方合约,请使用 V2 路由或 V3 单跳');
364
426
  }
427
+ /**
428
+ * ✅ 构建多笔买入和卖出交易
429
+ */
430
+ async function buildMultiRouteTransactions({ routeParams, buyAmountsWei, sellAmountsWei, buyer, seller, tokenAddress, useNativeToken = true }) {
431
+ const deadline = getDeadline();
432
+ if (routeParams.routeType === 'v2') {
433
+ const { v2Path } = routeParams;
434
+ const reversePath = [...v2Path].reverse();
435
+ const v2RouterBuyer = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, buyer);
436
+ const v2RouterSeller = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, seller);
437
+ // 构建多笔买入交易
438
+ const buyUnsignedArray = await Promise.all(buyAmountsWei.map(amount => {
439
+ const buyValue = useNativeToken ? amount + FLAT_FEE : FLAT_FEE;
440
+ return v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, v2Path, buyer.address, deadline, { value: buyValue });
441
+ }));
442
+ // 构建多笔卖出交易
443
+ const sellUnsignedArray = await Promise.all(sellAmountsWei.map(amount => v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(amount, 0n, reversePath, seller.address, deadline)));
444
+ return { buyUnsignedArray, sellUnsignedArray };
445
+ }
446
+ if (routeParams.routeType === 'v3-single') {
447
+ const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
448
+ const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
449
+ const v3RouterBuyer = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, buyer);
450
+ const v3RouterSeller = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, seller);
451
+ // 构建多笔买入交易
452
+ const buyUnsignedArray = await Promise.all(buyAmountsWei.map(amount => {
453
+ const buyValue = useNativeToken ? amount + FLAT_FEE : FLAT_FEE;
454
+ const buySwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
455
+ tokenIn: v3TokenIn,
456
+ tokenOut: v3TokenOut,
457
+ fee: v3Fee,
458
+ recipient: buyer.address,
459
+ amountIn: amount,
460
+ amountOutMinimum: 0n,
461
+ sqrtPriceLimitX96: 0n
462
+ }]);
463
+ return v3RouterBuyer.multicall.populateTransaction(deadline, [buySwapData], { value: buyValue });
464
+ }));
465
+ // 构建多笔卖出交易
466
+ const sellUnsignedArray = await Promise.all(sellAmountsWei.map(amount => {
467
+ const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
468
+ tokenIn: v3TokenOut,
469
+ tokenOut: v3TokenIn,
470
+ fee: v3Fee,
471
+ recipient: PANCAKE_V3_ROUTER_ADDRESS,
472
+ amountIn: amount,
473
+ amountOutMinimum: 0n,
474
+ sqrtPriceLimitX96: 0n
475
+ }]);
476
+ const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]);
477
+ return v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]);
478
+ }));
479
+ return { buyUnsignedArray, sellUnsignedArray };
480
+ }
481
+ throw new Error('V3 多跳路由暂不支持官方合约,请使用 V2 路由或 V3 单跳');
482
+ }
365
483
  /**
366
484
  * ✅ 使用 quote-helpers 统一报价(估算利润)
367
485
  */
@@ -416,6 +534,38 @@ async function planNonces({ buyer, seller, sameAddress, extractProfit, needBribe
416
534
  ]);
417
535
  return { buyerNonce, sellerNonce };
418
536
  }
537
+ /**
538
+ * ✅ 规划多笔交易 nonce
539
+ * 交易顺序:贿赂 → 买入(多笔) → 卖出(多笔) → 利润
540
+ */
541
+ async function planMultiNonces({ buyer, seller, buyCount, sellCount, sameAddress, extractProfit, needBribeTx, nonceManager }) {
542
+ const profitNonceCount = extractProfit ? 1 : 0;
543
+ if (sameAddress) {
544
+ const bribeTxCount = needBribeTx ? 1 : 0;
545
+ const totalTxCount = bribeTxCount + buyCount + sellCount + profitNonceCount;
546
+ const nonces = await nonceManager.getNextNonceBatch(buyer, totalTxCount);
547
+ let idx = 0;
548
+ const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
549
+ const buyerNonces = nonces.slice(idx, idx + buyCount);
550
+ idx += buyCount;
551
+ const sellerNonces = nonces.slice(idx, idx + sellCount);
552
+ idx += sellCount;
553
+ const profitNonce = extractProfit ? nonces[idx] : undefined;
554
+ return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
555
+ }
556
+ const bribeTxCount = needBribeTx ? 1 : 0;
557
+ const sellerTxCount = bribeTxCount + sellCount + profitNonceCount;
558
+ const [buyerNonces, sellerNoncesAll] = await Promise.all([
559
+ nonceManager.getNextNonceBatch(buyer, buyCount),
560
+ nonceManager.getNextNonceBatch(seller, sellerTxCount)
561
+ ]);
562
+ let idx = 0;
563
+ const bribeNonce = needBribeTx ? sellerNoncesAll[idx++] : undefined;
564
+ const sellerNonces = sellerNoncesAll.slice(idx, idx + sellCount);
565
+ idx += sellCount;
566
+ const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
567
+ return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
568
+ }
419
569
  /**
420
570
  * 构建利润多跳转账交易(强制 2 跳中转)
421
571
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.61",
3
+ "version": "1.4.63",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",