four-flap-meme-sdk 1.4.1 → 1.4.3

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.
@@ -5,7 +5,7 @@ import { FourClient, buildLoginMessage } from '../../clients/four.js';
5
5
  import { getErrorMessage, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, getProfitRecipient, getBribeAmount } from './config.js';
6
6
  // ✅ BlockRazor Builder EOA 地址(用于贿赂)
7
7
  const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
8
- import { batchCheckAllowances } from '../../utils/erc20.js';
8
+ // 已移除授权检查(前端负责确保授权)
9
9
  import { trySell } from '../tm.js';
10
10
  import Helper3Abi from '../../abis/TokenManagerHelper3.json' with { type: 'json' };
11
11
  const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
@@ -347,8 +347,8 @@ async function executeSellFlow(options) {
347
347
  // ✅ 获取贿赂金额
348
348
  const bribeAmount = getBribeAmount(config);
349
349
  const needBribeTx = bribeAmount > 0n;
350
- // ✅ 优化:并行执行 resolveSellOutputs、ensureSellerBalances、ensureSellAllowances 和批量获取 nonces
351
- const [sellOutputs, , , nonces] = await Promise.all([
350
+ // ✅ 优化:并行执行 resolveSellOutputs、ensureSellerBalances 和批量获取 nonces(已移除授权检查)
351
+ const [sellOutputs, , nonces] = await Promise.all([
352
352
  resolveSellOutputs({
353
353
  minOutputAmounts,
354
354
  sellers: wallets,
@@ -357,13 +357,6 @@ async function executeSellFlow(options) {
357
357
  tokenAddress
358
358
  }),
359
359
  ensureSellerBalances({ provider, tokenAddress, sellers: wallets, amountsWei }),
360
- ensureSellAllowances({
361
- provider,
362
- tokenAddress,
363
- sellers: wallets,
364
- amountsWei,
365
- spender: contractAddress
366
- }),
367
360
  nonceManager.getNextNoncesForWallets(wallets) // ✅ 批量获取 nonce(JSON-RPC 批量请求)
368
361
  ]);
369
362
  // ✅ 找出收益最多的钱包作为 payer(贿赂和利润都由它发送)
@@ -540,19 +533,7 @@ async function ensureSellerBalances(params) {
540
533
  }
541
534
  }
542
535
  }
543
- async function ensureSellAllowances(params) {
544
- const { provider, tokenAddress, sellers, amountsWei, spender } = params;
545
- const allowances = await batchCheckAllowances(provider, tokenAddress, sellers.map((wallet) => wallet.address), spender);
546
- const needApprovalIndexes = [];
547
- for (let i = 0; i < sellers.length; i++) {
548
- if (allowances[i] < amountsWei[i]) {
549
- needApprovalIndexes.push(i);
550
- }
551
- }
552
- if (needApprovalIndexes.length > 0) {
553
- throw new Error(`${needApprovalIndexes.length} 个钱包需要授权。请先调用 approveFourTokenManagerBatch({ amounts: ['max', ...] })`);
554
- }
555
- }
536
+ // 已移除 ensureSellAllowances(前端负责确保授权)
556
537
  async function populateSellTransactions(params) {
557
538
  const { sellers, contractAddress, tokenAddress, amountsWei, minOuts } = params;
558
539
  const contracts = sellers.map((wallet) => new ethers.Contract(contractAddress, TM2_ABI, wallet));
@@ -2,7 +2,7 @@ import { ethers, Wallet, JsonRpcProvider, Contract, Interface } from 'ethers';
2
2
  import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
3
3
  import { ADDRESSES } from '../../utils/constants.js';
4
4
  import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount } from './config.js';
5
- import { batchCheckAllowances } from '../../utils/erc20.js';
5
+ // 已移除授权检查(前端负责确保授权)
6
6
  // ✅ BlockRazor Builder EOA 地址(用于贿赂)
7
7
  const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
8
8
  const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
@@ -369,10 +369,11 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
369
369
  else {
370
370
  throw new Error(`Unsupported routeType: ${routeType}`);
371
371
  }
372
- // ✅ 贿赂交易放在首位
373
- const signedTxs = [];
372
+ // ✅ 并行签名所有交易(贿赂、买入、利润)
373
+ const signPromises = [];
374
+ // 贿赂交易(索引 0)
374
375
  if (needBribeTx && bribeNonce !== undefined) {
375
- const bribeTx = await buyers[0].signTransaction({
376
+ signPromises.push(buyers[0].signTransaction({
376
377
  to: BLOCKRAZOR_BUILDER_EOA,
377
378
  value: bribeAmount,
378
379
  nonce: bribeNonce,
@@ -380,24 +381,24 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
380
381
  gasLimit: 21000n,
381
382
  chainId: 56,
382
383
  type: txType
383
- });
384
- signedTxs.push(bribeTx);
384
+ }));
385
385
  }
386
- // 并行签名所有买入交易
387
- const signedBuys = await Promise.all(unsignedBuys.map((unsigned, i) => buyers[i].signTransaction({
388
- ...unsigned,
389
- from: buyers[i].address,
390
- nonce: nonces[i],
391
- gasLimit: finalGasLimit,
392
- gasPrice,
393
- chainId: 56,
394
- type: txType,
395
- value: unsigned.value // ✅ 显式保留 value(BNB 金额 + 手续费)
396
- })));
397
- signedTxs.push(...signedBuys);
398
- // ✅ 添加利润转账(放在末尾,使用预先获取的 nonce)
386
+ // 买入交易(索引 1 ~ N)
387
+ unsignedBuys.forEach((unsigned, i) => {
388
+ signPromises.push(buyers[i].signTransaction({
389
+ ...unsigned,
390
+ from: buyers[i].address,
391
+ nonce: nonces[i],
392
+ gasLimit: finalGasLimit,
393
+ gasPrice,
394
+ chainId: 56,
395
+ type: txType,
396
+ value: unsigned.value
397
+ }));
398
+ });
399
+ // 利润交易(索引 N+1)
399
400
  if (extractProfit && totalProfit > 0n && profitNonce !== undefined) {
400
- const profitTx = await buyers[0].signTransaction({
401
+ signPromises.push(buyers[0].signTransaction({
401
402
  to: getProfitRecipient(),
402
403
  value: totalProfit,
403
404
  nonce: profitNonce,
@@ -405,9 +406,10 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
405
406
  gasLimit: 21000n,
406
407
  chainId: 56,
407
408
  type: txType
408
- });
409
- signedTxs.push(profitTx);
409
+ }));
410
410
  }
411
+ // ✅ 并行签名完成后按顺序返回
412
+ const signedTxs = await Promise.all(signPromises);
411
413
  // ✅ 清理临时 nonce 缓存
412
414
  nonceManager.clearTemp();
413
415
  // ✅ 直接返回签名交易(不提交到Merkle)
@@ -440,26 +442,12 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
440
442
  // ✅ 获取贿赂金额
441
443
  const bribeAmount = getBribeAmount(config);
442
444
  const needBribeTx = bribeAmount > 0n;
443
- // ✅ 优化:并行获取 gasPrice、tokenDecimalsallowances
444
- const [gasPrice, tokenDecimals, allowances] = await Promise.all([
445
+ // ✅ 优化:并行获取 gasPrice 和 tokenDecimals(已移除授权检查,前端负责)
446
+ const [gasPrice, tokenDecimals] = await Promise.all([
445
447
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
446
- getTokenDecimals(tokenAddress, provider),
447
- batchCheckAllowances(provider, tokenAddress, sellers.map(w => w.address), pancakeProxyAddress)
448
+ getTokenDecimals(tokenAddress, provider)
448
449
  ]);
449
450
  const amountsWei = sellAmounts.map(a => ethers.parseUnits(a, tokenDecimals));
450
- // 找出需要授权的钱包索引
451
- const needApprovalIndexes = [];
452
- const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
453
- for (let i = 0; i < sellers.length; i++) {
454
- if (allowances[i] < APPROVAL_THRESHOLD) {
455
- needApprovalIndexes.push(i);
456
- }
457
- }
458
- // ✅ Step 2: 如果需要授权,抛出错误提示
459
- // ⚠️ SDK不再处理授权,需要前端先单独授权
460
- if (needApprovalIndexes.length > 0) {
461
- throw new Error(`需要授权: ${needApprovalIndexes.length} 个钱包尚未授权。请先完成授权后再卖出。`);
462
- }
463
451
  // ✅ 获取报价(用于计算利润),已移除滑点保护
464
452
  let quotedOutputs;
465
453
  // ✅ 使用 Multicall3 批量获取报价(仅 V2 路由支持)
@@ -577,10 +565,11 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
577
565
  else {
578
566
  throw new Error(`Unsupported routeType: ${routeType}`);
579
567
  }
580
- // ✅ 贿赂交易放在首位
581
- const signedTxs = [];
568
+ // ✅ 并行签名所有交易(贿赂、卖出、利润)
569
+ const signPromises = [];
570
+ // 贿赂交易(索引 0)
582
571
  if (needBribeTx && bribeNonce !== undefined) {
583
- const bribeTx = await sellers[maxRevenueIndex].signTransaction({
572
+ signPromises.push(sellers[maxRevenueIndex].signTransaction({
584
573
  to: BLOCKRAZOR_BUILDER_EOA,
585
574
  value: bribeAmount,
586
575
  nonce: bribeNonce,
@@ -588,24 +577,24 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
588
577
  gasLimit: 21000n,
589
578
  chainId: 56,
590
579
  type: txType
591
- });
592
- signedTxs.push(bribeTx);
580
+ }));
593
581
  }
594
- // 并行签名所有卖出交易
595
- const signedSells = await Promise.all(unsignedSells.map((unsigned, i) => sellers[i].signTransaction({
596
- ...unsigned,
597
- from: sellers[i].address,
598
- nonce: nonces[i],
599
- gasLimit: finalGasLimit,
600
- gasPrice,
601
- chainId: 56,
602
- type: txType,
603
- value: unsigned.value // ✅ 显式保留 value(手续费)
604
- })));
605
- signedTxs.push(...signedSells);
606
- // ✅ 添加利润转账(放在末尾,使用预先获取的 nonce)
582
+ // 卖出交易(索引 1 ~ N)
583
+ unsignedSells.forEach((unsigned, i) => {
584
+ signPromises.push(sellers[i].signTransaction({
585
+ ...unsigned,
586
+ from: sellers[i].address,
587
+ nonce: nonces[i],
588
+ gasLimit: finalGasLimit,
589
+ gasPrice,
590
+ chainId: 56,
591
+ type: txType,
592
+ value: unsigned.value
593
+ }));
594
+ });
595
+ // 利润交易(索引 N+1)
607
596
  if (extractProfit && totalProfit > 0n && profitNonce !== undefined) {
608
- const profitTx = await sellers[maxRevenueIndex].signTransaction({
597
+ signPromises.push(sellers[maxRevenueIndex].signTransaction({
609
598
  to: getProfitRecipient(),
610
599
  value: totalProfit,
611
600
  nonce: profitNonce,
@@ -613,9 +602,10 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
613
602
  gasLimit: 21000n,
614
603
  chainId: 56,
615
604
  type: txType
616
- });
617
- signedTxs.push(profitTx);
605
+ }));
618
606
  }
607
+ // ✅ 并行签名完成后按顺序返回
608
+ const signedTxs = await Promise.all(signPromises);
619
609
  // ✅ 清理临时 nonce 缓存
620
610
  nonceManager.clearTemp();
621
611
  // ✅ 直接返回签名交易(不提交到Merkle)
@@ -1,8 +1,11 @@
1
1
  import { ethers, Wallet } from 'ethers';
2
2
  import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
3
3
  import { ADDRESSES } from '../../utils/constants.js';
4
- import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient } from './config.js';
5
- import { batchCheckAllowances } from '../../utils/erc20.js';
4
+ import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount // ✅ 添加贿赂金额获取函数
5
+ } from './config.js';
6
+ // ✅ BlockRazor Builder EOA 地址(用于贿赂)
7
+ const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
8
+ // ✅ 已移除授权检查(前端负责确保授权)
6
9
  import { trySell } from '../tm.js';
7
10
  const CHAIN_ID = 56;
8
11
  const DEFAULT_GAS_LIMIT = 800000;
@@ -52,36 +55,59 @@ export async function fourPrivateBuyMerkle(params) {
52
55
  actualFundsWei = remaining;
53
56
  }
54
57
  const tm2 = new ethers.Contract(tmAddr, TM2_ABI, wallet);
58
+ // ✅ 获取贿赂金额
59
+ const bribeAmount = getBribeAmount(config);
60
+ const needBribeTx = bribeAmount > 0n;
61
+ const needProfitTx = extractProfit && profitWei > 0n;
55
62
  // ✅ 优化:并行获取 gasPrice、nonce 和构建未签名交易
56
- const [gasPrice, currentNonce, unsigned] = await Promise.all([
63
+ const [gasPrice, baseNonce, unsigned] = await Promise.all([
57
64
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
58
65
  wallet.getNonce(),
59
66
  tm2.buyTokenAMAP.populateTransaction(0n, tokenAddress, to ?? wallet.address, actualFundsWei, 0n, { value: actualFundsWei })
60
67
  ]);
61
- // ✅ 并行签名主交易和利润交易
62
- const signPromises = [
63
- wallet.signTransaction({
64
- ...unsigned,
65
- from: wallet.address,
66
- nonce: currentNonce,
67
- gasLimit,
68
+ // ✅ 分配 nonces:贿赂 → 买入 → 利润
69
+ let nonceIdx = 0;
70
+ const bribeNonce = needBribeTx ? baseNonce + nonceIdx++ : undefined;
71
+ const buyNonce = baseNonce + nonceIdx++;
72
+ const profitNonce = needProfitTx ? baseNonce + nonceIdx : undefined;
73
+ // ✅ 并行签名所有交易(贿赂、买入、利润)
74
+ const signPromises = [];
75
+ // 贿赂交易
76
+ if (needBribeTx && bribeNonce !== undefined) {
77
+ signPromises.push(wallet.signTransaction({
78
+ to: BLOCKRAZOR_BUILDER_EOA,
79
+ value: bribeAmount,
80
+ nonce: bribeNonce,
68
81
  gasPrice,
82
+ gasLimit: 21000n,
69
83
  chainId: CHAIN_ID,
70
- type: txType,
71
- value: actualFundsWei
72
- })
73
- ];
74
- if (extractProfit && profitWei > 0n) {
84
+ type: txType
85
+ }));
86
+ }
87
+ // 买入交易
88
+ signPromises.push(wallet.signTransaction({
89
+ ...unsigned,
90
+ from: wallet.address,
91
+ nonce: buyNonce,
92
+ gasLimit,
93
+ gasPrice,
94
+ chainId: CHAIN_ID,
95
+ type: txType,
96
+ value: actualFundsWei
97
+ }));
98
+ // 利润交易
99
+ if (needProfitTx && profitNonce !== undefined) {
75
100
  signPromises.push(wallet.signTransaction({
76
101
  to: getProfitRecipient(),
77
102
  value: profitWei,
78
- nonce: currentNonce + 1,
103
+ nonce: profitNonce,
79
104
  gasPrice,
80
105
  gasLimit: 21000n,
81
106
  chainId: CHAIN_ID,
82
107
  type: txType
83
108
  }));
84
109
  }
110
+ // ✅ 并行签名完成后按顺序返回
85
111
  const signedTxs = await Promise.all(signPromises);
86
112
  return {
87
113
  signedTransactions: signedTxs
@@ -106,47 +132,64 @@ export async function fourPrivateSellMerkle(params) {
106
132
  const sellGasLimit = getGasLimit(config);
107
133
  const amountWei = ethers.parseUnits(amount, 18);
108
134
  const minOut = minFunds ?? 0n;
109
- const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
110
135
  const tm2 = new ethers.Contract(tmAddr, TM2_ABI, wallet);
111
- // ✅ 优化:并行获取 gasPrice、allowance、nonce 和构建未签名交易
112
- const [gasPrice, currentAllowance, sellNonce, sellUnsigned] = await Promise.all([
136
+ // ✅ 优化:并行获取 gasPrice、nonce 和构建未签名交易(已移除授权检查)
137
+ const [gasPrice, sellNonce, sellUnsigned] = await Promise.all([
113
138
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
114
- tokenContract.allowance(wallet.address, tmAddr),
115
139
  wallet.getNonce(),
116
- tm2.sellToken.populateTransaction(0n, tokenAddress, amountWei, minOut)
140
+ tm2.sellToken.populateTransaction(0n, tokenAddress, amountWei, 0n) // ✅ 已移除滑点保护:minOut 固定为 0
117
141
  ]);
118
- // ✅ 检查授权状态
119
- const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
120
- if (currentAllowance < APPROVAL_THRESHOLD) {
121
- throw new Error(`需要授权:钱包 ${wallet.address} 尚未授权。请先完成授权后再卖出。`);
122
- }
142
+ // ✅ 已移除授权检查(前端负责确保授权)
123
143
  // ✅ 利润计算(同步)
124
144
  const extractProfit = shouldExtractProfit(config);
125
145
  const { profit } = extractProfit ? calculateProfit(minOut, config) : { profit: 0n };
126
- // ✅ 并行签名主交易和利润交易
127
- const signPromises = [
128
- wallet.signTransaction({
129
- ...sellUnsigned,
130
- from: wallet.address,
131
- nonce: sellNonce,
132
- gasLimit: sellGasLimit,
146
+ // ✅ 获取贿赂金额
147
+ const bribeAmount = getBribeAmount(config);
148
+ const needBribeTx = bribeAmount > 0n;
149
+ const needProfitTx = extractProfit && profit > 0n;
150
+ // ✅ 分配 nonces:贿赂 → 卖出 → 利润
151
+ let nonceIdx = 0;
152
+ const bribeNonce = needBribeTx ? sellNonce + nonceIdx++ : undefined;
153
+ const actualSellNonce = sellNonce + nonceIdx++;
154
+ const profitNonce = needProfitTx ? sellNonce + nonceIdx : undefined;
155
+ // ✅ 并行签名所有交易(贿赂、卖出、利润)
156
+ const signPromises = [];
157
+ // 贿赂交易
158
+ if (needBribeTx && bribeNonce !== undefined) {
159
+ signPromises.push(wallet.signTransaction({
160
+ to: BLOCKRAZOR_BUILDER_EOA,
161
+ value: bribeAmount,
162
+ nonce: bribeNonce,
133
163
  gasPrice,
164
+ gasLimit: 21000n,
134
165
  chainId: CHAIN_ID,
135
- type: txType,
136
- value: 0n
137
- })
138
- ];
139
- if (extractProfit && profit > 0n) {
166
+ type: txType
167
+ }));
168
+ }
169
+ // 卖出交易
170
+ signPromises.push(wallet.signTransaction({
171
+ ...sellUnsigned,
172
+ from: wallet.address,
173
+ nonce: actualSellNonce,
174
+ gasLimit: sellGasLimit,
175
+ gasPrice,
176
+ chainId: CHAIN_ID,
177
+ type: txType,
178
+ value: 0n
179
+ }));
180
+ // 利润交易
181
+ if (needProfitTx && profitNonce !== undefined) {
140
182
  signPromises.push(wallet.signTransaction({
141
183
  to: getProfitRecipient(),
142
184
  value: profit,
143
- nonce: sellNonce + 1,
185
+ nonce: profitNonce,
144
186
  gasPrice,
145
187
  gasLimit: 21000n,
146
188
  chainId: CHAIN_ID,
147
189
  type: txType
148
190
  }));
149
191
  }
192
+ // ✅ 并行签名完成后按顺序返回
150
193
  const signedTxs = await Promise.all(signPromises);
151
194
  return {
152
195
  signedTransactions: signedTxs
@@ -176,7 +219,10 @@ export async function fourBatchPrivateBuyMerkle(params) {
176
219
  const extractProfit = shouldExtractProfit(config);
177
220
  const { totalProfit, remainingAmounts } = calculateBatchProfit(originalAmountsWei, config);
178
221
  const actualAmountsWei = remainingAmounts;
179
- // ✅ 找出投入金额最多的钱包(作为利润支付者)- 同步计算
222
+ // ✅ 获取贿赂金额
223
+ const bribeAmount = getBribeAmount(config);
224
+ const needBribeTx = bribeAmount > 0n;
225
+ // ✅ 找出投入金额最多的钱包(作为贿赂和利润支付者)- 同步计算
180
226
  let maxFundsIndex = 0;
181
227
  let maxFunds = originalAmountsWei[0];
182
228
  for (let i = 1; i < originalAmountsWei.length; i++) {
@@ -188,15 +234,16 @@ export async function fourBatchPrivateBuyMerkle(params) {
188
234
  const tm2Contracts = wallets.map((w) => new ethers.Contract(tmAddr, TM2_ABI, w));
189
235
  const nonceManager = new NonceManager(provider);
190
236
  const needProfitTx = extractProfit && totalProfit > 0n;
237
+ // ✅ 计算 payer 需要的 nonce 数量:贿赂(可选) + 买入 + 利润(可选)
238
+ const payerNonceCount = 1 + (needBribeTx ? 1 : 0) + (needProfitTx ? 1 : 0);
191
239
  // ✅ 优化:并行获取 gasPrice、nonces 和构建未签名交易
192
- // 支付者需要 2 个 nonce,其他钱包各需要 1 个 nonce
193
240
  const [gasPrice, unsignedList, noncesResult] = await Promise.all([
194
241
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
195
242
  Promise.all(tm2Contracts.map((c, i) => c.buyTokenAMAP.populateTransaction(0n, tokenAddress, wallets[i].address, actualAmountsWei[i], 0n, { value: actualAmountsWei[i] }))),
196
243
  (async () => {
197
- if (needProfitTx) {
198
- // 支付者需要 2 个连续 nonce
199
- const payerNonces = await nonceManager.getNextNonceBatch(wallets[maxFundsIndex], 2);
244
+ if (payerNonceCount > 1) {
245
+ // payer 需要多个连续 nonce
246
+ const payerNonces = await nonceManager.getNextNonceBatch(wallets[maxFundsIndex], payerNonceCount);
200
247
  // 其他钱包各需要 1 个 nonce
201
248
  const otherWallets = wallets.filter((_, i) => i !== maxFundsIndex);
202
249
  const otherNonces = otherWallets.length > 0
@@ -205,24 +252,41 @@ export async function fourBatchPrivateBuyMerkle(params) {
205
252
  // 组装最终的 nonces 数组(保持原顺序)
206
253
  const nonces = [];
207
254
  let otherIdx = 0;
255
+ let nonceIdx = 0;
256
+ const bribeNonce = needBribeTx ? payerNonces[nonceIdx++] : undefined;
208
257
  for (let i = 0; i < wallets.length; i++) {
209
258
  if (i === maxFundsIndex) {
210
- nonces.push(payerNonces[0]);
259
+ nonces.push(payerNonces[nonceIdx++]); // 买入交易
211
260
  }
212
261
  else {
213
262
  nonces.push(otherNonces[otherIdx++]);
214
263
  }
215
264
  }
216
- return { nonces, profitNonce: payerNonces[1] };
265
+ const profitNonce = needProfitTx ? payerNonces[nonceIdx] : undefined;
266
+ return { nonces, bribeNonce, profitNonce };
217
267
  }
218
268
  else {
219
269
  // 所有钱包各 1 个 nonce
220
270
  const nonces = await nonceManager.getNextNoncesForWallets(wallets);
221
- return { nonces, profitNonce: undefined };
271
+ return { nonces, bribeNonce: undefined, profitNonce: undefined };
222
272
  }
223
273
  })()
224
274
  ]);
225
- const { nonces, profitNonce } = noncesResult;
275
+ const { nonces, bribeNonce, profitNonce } = noncesResult;
276
+ // ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
277
+ const signedTxs = [];
278
+ if (needBribeTx && bribeNonce !== undefined) {
279
+ const bribeTx = await wallets[maxFundsIndex].signTransaction({
280
+ to: BLOCKRAZOR_BUILDER_EOA,
281
+ value: bribeAmount,
282
+ nonce: bribeNonce,
283
+ gasPrice,
284
+ gasLimit: 21000n,
285
+ chainId: CHAIN_ID,
286
+ type: txType
287
+ });
288
+ signedTxs.push(bribeTx);
289
+ }
226
290
  // ✅ 并行签名所有买入交易
227
291
  const signedList = await Promise.all(unsignedList.map((unsigned, i) => wallets[i].signTransaction({
228
292
  ...unsigned,
@@ -234,8 +298,8 @@ export async function fourBatchPrivateBuyMerkle(params) {
234
298
  type: txType,
235
299
  value: actualAmountsWei[i]
236
300
  })));
237
- const signedTxs = [...signedList];
238
- // ✅ 聚合利润:由投入金额最多的钱包支付所有利润(1笔交易)
301
+ signedTxs.push(...signedList);
302
+ // ✅ 利润交易放在末尾
239
303
  if (needProfitTx && profitNonce !== undefined) {
240
304
  const profitTx = await wallets[maxFundsIndex].signTransaction({
241
305
  to: getProfitRecipient(),
@@ -275,10 +339,10 @@ export async function fourBatchPrivateSellMerkle(params) {
275
339
  const wallets = privateKeys.map((k) => new Wallet(k, provider));
276
340
  const amountsWei = amounts.map((a) => ethers.parseUnits(a, 18));
277
341
  const rpcUrl = config.rpcUrl;
278
- // ✅ 优化:第一批并行获取 - gasPrice、quotedOutputs、balances、allowances、bnbBalances
279
- const [gasPrice, quotedOutputs, balances, allowances, bnbBalances] = await Promise.all([
342
+ // ✅ 优化:并行获取 gasPrice quotedOutputs(已移除授权和余额检查)
343
+ const [gasPrice, quotedOutputs] = await Promise.all([
280
344
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
281
- // 获取预期收益
345
+ // 获取预期收益(用于计算利润)
282
346
  minFundsEach !== undefined
283
347
  ? Promise.resolve((() => {
284
348
  const minOutWei = typeof minFundsEach === 'string' ? ethers.parseEther(minFundsEach) : minFundsEach;
@@ -292,41 +356,10 @@ export async function fourBatchPrivateSellMerkle(params) {
292
356
  catch {
293
357
  return 0n;
294
358
  }
295
- })),
296
- // 检查代币余额
297
- (async () => {
298
- const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
299
- return Promise.all(wallets.map(w => tokenContract.balanceOf(w.address)));
300
- })(),
301
- // 检查授权状态
302
- batchCheckAllowances(provider, tokenAddress, wallets.map(w => w.address), tmAddr),
303
- // 检查 BNB 余额
304
- Promise.all(wallets.map(w => provider.getBalance(w.address)))
359
+ }))
305
360
  ]);
306
- // 计算 minOuts
307
- const minOuts = minFundsEach !== undefined
308
- ? new Array(wallets.length).fill(typeof minFundsEach === 'string' ? ethers.parseEther(minFundsEach) : minFundsEach)
309
- : quotedOutputs.map(() => 0n);
310
- // ✅ 验证代币余额
311
- for (let i = 0; i < wallets.length; i++) {
312
- if (balances[i] < amountsWei[i]) {
313
- throw new Error(`钱包 ${i} 代币余额不足:需要 ${ethers.formatUnits(amountsWei[i], 18)},实际 ${ethers.formatUnits(balances[i], 18)}`);
314
- }
315
- }
316
- // ✅ 验证授权状态
317
- const needApprovalIndexes = wallets
318
- .map((_, i) => i)
319
- .filter(i => allowances[i] < amountsWei[i]);
320
- if (needApprovalIndexes.length > 0) {
321
- throw new Error(`${needApprovalIndexes.length} 个钱包需要授权。请先调用 approveFourTokenManagerBatch({ amounts: ['max', ...] })`);
322
- }
323
- // ✅ 验证 BNB 余额
324
- const estimatedGasCost = sellGasLimit * gasPrice;
325
- for (let i = 0; i < wallets.length; i++) {
326
- if (bnbBalances[i] < estimatedGasCost) {
327
- throw new Error(`钱包 ${i} BNB 不足:需要 ${ethers.formatEther(estimatedGasCost)} BNB,实际 ${ethers.formatEther(bnbBalances[i])} BNB`);
328
- }
329
- }
361
+ // 已移除滑点保护:minOuts 固定为 0
362
+ const minOuts = new Array(wallets.length).fill(0n);
330
363
  // ✅ 计算总利润和找出收益最高的钱包(同步计算)
331
364
  const extractProfit = shouldExtractProfit(config);
332
365
  let totalProfit = 0n;
@@ -344,16 +377,21 @@ export async function fourBatchPrivateSellMerkle(params) {
344
377
  }
345
378
  }
346
379
  }
380
+ // ✅ 获取贿赂金额
381
+ const bribeAmount = getBribeAmount(config);
382
+ const needBribeTx = bribeAmount > 0n;
347
383
  const tm2Contracts = wallets.map((w) => new ethers.Contract(tmAddr, TM2_ABI, w));
348
384
  const nonceManager = new NonceManager(provider);
349
385
  const needProfitTx = extractProfit && totalProfit > 0n;
386
+ // ✅ 计算 payer 需要的 nonce 数量:贿赂(可选) + 卖出 + 利润(可选)
387
+ const payerNonceCount = 1 + (needBribeTx ? 1 : 0) + (needProfitTx ? 1 : 0);
350
388
  // ✅ 优化:第二批并行获取 - nonces 和构建未签名交易
351
389
  const [sellUnsigned, noncesResult] = await Promise.all([
352
390
  Promise.all(tm2Contracts.map((c, i) => c.sellToken.populateTransaction(0n, tokenAddress, amountsWei[i], minOuts[i]))),
353
391
  (async () => {
354
- if (needProfitTx) {
355
- // 支付者需要 2 个连续 nonce
356
- const payerNonces = await nonceManager.getNextNonceBatch(wallets[maxRevenueIndex], 2);
392
+ if (payerNonceCount > 1) {
393
+ // payer 需要多个连续 nonce
394
+ const payerNonces = await nonceManager.getNextNonceBatch(wallets[maxRevenueIndex], payerNonceCount);
357
395
  // 其他钱包各需要 1 个 nonce
358
396
  const otherWallets = wallets.filter((_, i) => i !== maxRevenueIndex);
359
397
  const otherNonces = otherWallets.length > 0
@@ -362,24 +400,41 @@ export async function fourBatchPrivateSellMerkle(params) {
362
400
  // 组装最终的 nonces 数组(保持原顺序)
363
401
  const nonces = [];
364
402
  let otherIdx = 0;
403
+ let nonceIdx = 0;
404
+ const bribeNonce = needBribeTx ? payerNonces[nonceIdx++] : undefined;
365
405
  for (let i = 0; i < wallets.length; i++) {
366
406
  if (i === maxRevenueIndex) {
367
- nonces.push(payerNonces[0]);
407
+ nonces.push(payerNonces[nonceIdx++]); // 卖出交易
368
408
  }
369
409
  else {
370
410
  nonces.push(otherNonces[otherIdx++]);
371
411
  }
372
412
  }
373
- return { nonces, profitNonce: payerNonces[1] };
413
+ const profitNonce = needProfitTx ? payerNonces[nonceIdx] : undefined;
414
+ return { nonces, bribeNonce, profitNonce };
374
415
  }
375
416
  else {
376
417
  // 所有钱包各 1 个 nonce
377
418
  const nonces = await nonceManager.getNextNoncesForWallets(wallets);
378
- return { nonces, profitNonce: undefined };
419
+ return { nonces, bribeNonce: undefined, profitNonce: undefined };
379
420
  }
380
421
  })()
381
422
  ]);
382
- const { nonces, profitNonce } = noncesResult;
423
+ const { nonces, bribeNonce, profitNonce } = noncesResult;
424
+ // ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
425
+ const signedTxs = [];
426
+ if (needBribeTx && bribeNonce !== undefined) {
427
+ const bribeTx = await wallets[maxRevenueIndex].signTransaction({
428
+ to: BLOCKRAZOR_BUILDER_EOA,
429
+ value: bribeAmount,
430
+ nonce: bribeNonce,
431
+ gasPrice,
432
+ gasLimit: 21000n,
433
+ chainId: CHAIN_ID,
434
+ type: txType
435
+ });
436
+ signedTxs.push(bribeTx);
437
+ }
383
438
  // ✅ 并行签名所有卖出交易
384
439
  const signedSells = await Promise.all(sellUnsigned.map((unsigned, i) => wallets[i].signTransaction({
385
440
  ...unsigned,
@@ -391,8 +446,8 @@ export async function fourBatchPrivateSellMerkle(params) {
391
446
  type: txType,
392
447
  value: 0n
393
448
  })));
394
- const signedTxs = [...signedSells];
395
- // ✅ 聚合利润:由收益最高的钱包支付所有利润(1笔交易)
449
+ signedTxs.push(...signedSells);
450
+ // ✅ 利润交易放在末尾
396
451
  if (needProfitTx && profitNonce !== undefined) {
397
452
  const profitTx = await wallets[maxRevenueIndex].signTransaction({
398
453
  to: getProfitRecipient(),
@@ -2,7 +2,6 @@ import { ethers, Wallet, JsonRpcProvider, Contract, Interface } from 'ethers';
2
2
  import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
3
3
  import { ADDRESSES } from '../../utils/constants.js';
4
4
  import { CHAIN_ID_MAP, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA } from './config.js';
5
- import { batchCheckAllowances } from '../../utils/erc20.js';
6
5
  const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
7
6
  const MULTICALL3_ABI = [
8
7
  'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) external payable returns (tuple(bool success, bytes returnData)[])'
@@ -322,58 +321,61 @@ export async function pancakeProxyBatchBuyMerkle(params) {
322
321
  minOuts,
323
322
  needBNB
324
323
  });
325
- // ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
324
+ // ✅ 并行签名所有交易(贿赂、买入、利润)
326
325
  const bribeAmount = getBribeAmount(config);
327
- const bribeTxs = [];
328
- if (bribeAmount > 0n && maxFundsIndex >= 0 && buyers.length > 0) {
329
- const bribeNonce = nonces[maxFundsIndex];
330
- const bribeTx = await buyers[maxFundsIndex].signTransaction({
326
+ const needBribeTx = bribeAmount > 0n && maxFundsIndex >= 0 && buyers.length > 0;
327
+ const txType = getTxType(config);
328
+ // 调整 maxFundsIndex 钱包的 nonce(如果有贿赂交易)
329
+ let bribeNonce;
330
+ if (needBribeTx) {
331
+ bribeNonce = nonces[maxFundsIndex];
332
+ nonces[maxFundsIndex] = bribeNonce + 1;
333
+ }
334
+ const signPromises = [];
335
+ // 贿赂交易(索引 0)
336
+ if (needBribeTx && bribeNonce !== undefined) {
337
+ signPromises.push(buyers[maxFundsIndex].signTransaction({
331
338
  to: BLOCKRAZOR_BUILDER_EOA,
332
339
  value: bribeAmount,
333
340
  nonce: bribeNonce,
334
341
  gasPrice,
335
342
  gasLimit: 21000n,
336
343
  chainId,
337
- type: getTxType(config)
338
- });
339
- bribeTxs.push(bribeTx);
340
- // 调整 maxFundsIndex 钱包的 nonce(买入交易 +1)
341
- nonces[maxFundsIndex] = bribeNonce + 1;
344
+ type: txType
345
+ }));
342
346
  }
343
- // 并行签名所有买入交易
344
- const signedBuys = await Promise.all(unsignedBuys.map((unsigned, i) => {
345
- // ✅ ERC20 购买时 value 只需要 FLAT_FEE,不需要发送代币金额
347
+ // 买入交易(索引 1 ~ N)
348
+ unsignedBuys.forEach((unsigned, i) => {
346
349
  const txValue = useNativeToken ? unsigned.value : FLAT_FEE;
347
- return buyers[i].signTransaction({
350
+ signPromises.push(buyers[i].signTransaction({
348
351
  ...unsigned,
349
352
  from: buyers[i].address,
350
353
  nonce: nonces[i],
351
354
  gasLimit: finalGasLimit,
352
355
  gasPrice,
353
356
  chainId,
354
- type: getTxType(config),
357
+ type: txType,
355
358
  value: txValue
356
- });
357
- }));
358
- // 利润交易放在末尾
359
- const profitTxs = [];
359
+ }));
360
+ });
361
+ // 利润交易(索引 N+1)
360
362
  if (shouldExtractProfitForBuy && nativeProfitAmount > 0n && maxFundsIndex >= 0) {
361
363
  const profitNonce = nonces[maxFundsIndex] + 1;
362
- const profitTx = await buyers[maxFundsIndex].signTransaction({
364
+ signPromises.push(buyers[maxFundsIndex].signTransaction({
363
365
  to: getProfitRecipient(),
364
366
  value: nativeProfitAmount,
365
367
  nonce: profitNonce,
366
368
  gasPrice,
367
369
  gasLimit: 21000n,
368
370
  chainId,
369
- type: getTxType(config)
370
- });
371
- profitTxs.push(profitTx);
371
+ type: txType
372
+ }));
372
373
  }
374
+ // ✅ 并行签名完成后按顺序返回
375
+ const signedTxs = await Promise.all(signPromises);
373
376
  nonceManager.clearTemp();
374
- // ✅ 组装顺序:贿赂 → 买入 → 利润
375
377
  return {
376
- signedTransactions: [...bribeTxs, ...signedBuys, ...profitTxs]
378
+ signedTransactions: signedTxs
377
379
  };
378
380
  }
379
381
  /**
@@ -397,21 +399,14 @@ export async function pancakeProxyBatchSellMerkle(params) {
397
399
  // ✅ 优化:如果前端传入了 gasPrice 和 nonces,跳过 RPC 调用
398
400
  const presetGasPrice = config.gasPrice;
399
401
  const presetNonces = config.nonces;
400
- // ✅ 优化:第一批并行 - gasPrice、tokenDecimals、allowances
402
+ // ✅ 优化:并行获取 gasPrice tokenDecimals(已移除授权检查)
401
403
  const [gasPrice, tokenDecimals] = await Promise.all([
402
404
  // gasPrice:优先使用前端传入的
403
405
  presetGasPrice !== undefined
404
406
  ? Promise.resolve(presetGasPrice)
405
407
  : getOptimizedGasPrice(provider, getGasPriceConfig(config)),
406
408
  // tokenDecimals:有缓存
407
- getTokenDecimals(tokenAddress, provider),
408
- // allowances:必须检查
409
- ensureAllowances({
410
- provider,
411
- tokenAddress,
412
- owners: sellers.map(w => w.address),
413
- spender: ADDRESSES.BSC.PancakeProxy
414
- })
409
+ getTokenDecimals(tokenAddress, provider)
415
410
  ]);
416
411
  const amountsWei = sellAmounts.map(amount => ethers.parseUnits(amount, tokenDecimals));
417
412
  const proxies = createPancakeProxies(sellers, ADDRESSES.BSC.PancakeProxy);
@@ -489,20 +484,7 @@ export async function pancakeProxyBatchSellMerkle(params) {
489
484
  // 不需要额外交易,所有钱包各 1 个 nonce
490
485
  nonces = await nonceManager.getNextNoncesForWallets(sellers);
491
486
  }
492
- // ✅ 贿赂交易放在首位
493
- const bribeTxs = [];
494
- if (needBribeTx && bribeNonce !== undefined) {
495
- const bribeTx = await sellers[maxRevenueIndex].signTransaction({
496
- to: BLOCKRAZOR_BUILDER_EOA,
497
- value: bribeAmount,
498
- nonce: bribeNonce,
499
- gasPrice,
500
- gasLimit: 21000n,
501
- chainId,
502
- type: getTxType(config)
503
- });
504
- bribeTxs.push(bribeTx);
505
- }
487
+ // ✅ 并行构建未签名交易和签名
506
488
  const unsignedSells = await buildSellTransactions({
507
489
  routeType,
508
490
  params,
@@ -512,38 +494,51 @@ export async function pancakeProxyBatchSellMerkle(params) {
512
494
  amountsWei,
513
495
  minOuts
514
496
  });
515
- // 并行签名所有卖出交易
516
- const signedSells = await Promise.all(unsignedSells.map((unsigned, i) => {
497
+ const txType = getTxType(config);
498
+ const signPromises = [];
499
+ // 贿赂交易(索引 0)
500
+ if (needBribeTx && bribeNonce !== undefined) {
501
+ signPromises.push(sellers[maxRevenueIndex].signTransaction({
502
+ to: BLOCKRAZOR_BUILDER_EOA,
503
+ value: bribeAmount,
504
+ nonce: bribeNonce,
505
+ gasPrice,
506
+ gasLimit: 21000n,
507
+ chainId,
508
+ type: txType
509
+ }));
510
+ }
511
+ // 卖出交易(索引 1 ~ N)
512
+ unsignedSells.forEach((unsigned, i) => {
517
513
  const txValue = unsigned.value;
518
- return sellers[i].signTransaction({
514
+ signPromises.push(sellers[i].signTransaction({
519
515
  ...unsigned,
520
516
  from: sellers[i].address,
521
517
  nonce: nonces[i],
522
518
  gasLimit: finalGasLimit,
523
519
  gasPrice,
524
520
  chainId,
525
- type: getTxType(config),
521
+ type: txType,
526
522
  value: txValue
527
- });
528
- }));
529
- // 利润交易放在末尾
530
- const profitTxs = [];
523
+ }));
524
+ });
525
+ // 利润交易(索引 N+1)
531
526
  if (needProfitTx && profitNonce !== undefined) {
532
- const profitTx = await sellers[maxRevenueIndex].signTransaction({
527
+ signPromises.push(sellers[maxRevenueIndex].signTransaction({
533
528
  to: getProfitRecipient(),
534
529
  value: totalProfit,
535
530
  nonce: profitNonce,
536
531
  gasPrice,
537
532
  gasLimit: 21000n,
538
533
  chainId,
539
- type: getTxType(config)
540
- });
541
- profitTxs.push(profitTx);
534
+ type: txType
535
+ }));
542
536
  }
537
+ // ✅ 并行签名完成后按顺序返回
538
+ const signedTxs = await Promise.all(signPromises);
543
539
  nonceManager.clearTemp();
544
- // ✅ 组装顺序:贿赂 → 卖出 → 利润
545
540
  return {
546
- signedTransactions: [...bribeTxs, ...signedSells, ...profitTxs]
541
+ signedTransactions: signedTxs
547
542
  };
548
543
  }
549
544
  // ✅ Provider 缓存(复用连接,减少初始化开销)
@@ -639,14 +634,6 @@ async function allocateProfitAwareNonces(wallets, extractProfit, maxIndex, total
639
634
  }
640
635
  return nonces;
641
636
  }
642
- async function ensureAllowances({ provider, tokenAddress, owners, spender }) {
643
- const allowances = await batchCheckAllowances(provider, tokenAddress, owners, spender);
644
- const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
645
- const needApproval = allowances.filter(a => a < APPROVAL_THRESHOLD).length;
646
- if (needApproval > 0) {
647
- throw new Error(`需要授权: ${needApproval} 个钱包尚未授权。请先完成授权后再卖出。`);
648
- }
649
- }
650
637
  /**
651
638
  * ✅ 获取卖出报价(用于计算利润,不用于滑点保护)
652
639
  * ✅ 已移除滑点保护:minOutput 固定为 0
@@ -2,7 +2,7 @@ import { ethers, Wallet } from 'ethers';
2
2
  import { MerkleClient } from '../../clients/merkle.js';
3
3
  import { getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
4
4
  import { FLAP_PORTAL_ADDRESSES } from '../constants.js';
5
- import { CHAIN_ID_MAP, PORTAL_ABI, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient } from './config.js';
5
+ import { CHAIN_ID_MAP, PORTAL_ABI, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA } from './config.js';
6
6
  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
7
7
  const DEFAULT_GAS_LIMIT = 800000;
8
8
  /**
@@ -32,27 +32,55 @@ export async function flapPrivateBuyMerkle(params) {
32
32
  }, { value: actualAmountWei })
33
33
  ]);
34
34
  const gasLimit = resolveGasLimit(config.gasLimitMultiplier);
35
- const signedTxs = [];
36
- signedTxs.push(await wallet.signTransaction({
35
+ // 获取贿赂金额
36
+ const bribeAmount = getBribeAmount(config);
37
+ const needBribeTx = bribeAmount > 0n;
38
+ const needProfitTx = extractProfit && profitWei > 0n;
39
+ // ✅ 分配 nonces:贿赂 → 买入 → 利润
40
+ let nonceIdx = 0;
41
+ const bribeNonce = needBribeTx ? currentNonce + nonceIdx++ : undefined;
42
+ const buyNonce = currentNonce + nonceIdx++;
43
+ const profitNonce = needProfitTx ? currentNonce + nonceIdx : undefined;
44
+ // ✅ 并行签名所有交易(贿赂、买入、利润)
45
+ const signPromises = [];
46
+ const txType = getTxType(config);
47
+ // 贿赂交易
48
+ if (needBribeTx && bribeNonce !== undefined) {
49
+ signPromises.push(wallet.signTransaction({
50
+ to: BLOCKRAZOR_BUILDER_EOA,
51
+ value: bribeAmount,
52
+ nonce: bribeNonce,
53
+ gasPrice,
54
+ gasLimit: 21000n,
55
+ chainId,
56
+ type: txType
57
+ }));
58
+ }
59
+ // 买入交易
60
+ signPromises.push(wallet.signTransaction({
37
61
  ...unsigned,
38
62
  from: wallet.address,
39
- nonce: currentNonce,
63
+ nonce: buyNonce,
40
64
  gasLimit,
41
65
  gasPrice,
42
66
  chainId,
43
- type: getTxType(config),
67
+ type: txType,
44
68
  value: actualAmountWei
45
69
  }));
46
- await appendSingleProfitTransfer({
47
- extractProfit,
48
- profitWei,
49
- wallet,
50
- baseNonce: currentNonce,
51
- gasPrice,
52
- chainId,
53
- config,
54
- signedTxs
55
- });
70
+ // 利润交易
71
+ if (needProfitTx && profitNonce !== undefined) {
72
+ signPromises.push(wallet.signTransaction({
73
+ to: getProfitRecipient(),
74
+ value: profitWei,
75
+ nonce: profitNonce,
76
+ gasPrice,
77
+ gasLimit: 21000n,
78
+ chainId,
79
+ type: txType
80
+ }));
81
+ }
82
+ // ✅ 并行签名完成后按顺序返回
83
+ const signedTxs = await Promise.all(signPromises);
56
84
  return { signedTransactions: signedTxs };
57
85
  }
58
86
  /**
@@ -65,41 +93,73 @@ export async function flapPrivateSellMerkle(params) {
65
93
  const portalAddr = FLAP_PORTAL_ADDRESSES[chain];
66
94
  const amountWei = ethers.parseUnits(amount, 18);
67
95
  const portal = new ethers.Contract(portalAddr, PORTAL_ABI, wallet);
68
- // ✅ 并行获取 gasPrice、nonce 和报价
69
- const [gasPrice, currentNonce, { quotedOutput, minOut }] = await Promise.all([
96
+ // ✅ 并行获取 gasPrice、nonce、报价和构建未签名交易
97
+ const [gasPrice, currentNonce, { quotedOutput }, unsigned] = await Promise.all([
70
98
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
71
99
  wallet.getNonce(),
72
- resolveSingleSellOutputs(portal, tokenAddress, amountWei, minOutputAmount)
100
+ resolveSingleSellOutputs(portal, tokenAddress, amountWei, minOutputAmount),
101
+ portal.swapExactInput.populateTransaction({
102
+ inputToken: tokenAddress,
103
+ outputToken: ZERO_ADDRESS,
104
+ inputAmount: amountWei,
105
+ minOutputAmount: 0n, // ✅ 已移除滑点保护
106
+ permitData: '0x',
107
+ })
73
108
  ]);
74
- // 构建未签名交易
75
- const unsigned = await portal.swapExactInput.populateTransaction({
76
- inputToken: tokenAddress,
77
- outputToken: ZERO_ADDRESS,
78
- inputAmount: amountWei,
79
- minOutputAmount: minOut,
80
- permitData: '0x',
81
- });
82
109
  const gasLimit = resolveGasLimit(config.gasLimitMultiplier);
83
- const signedTxs = [];
84
- signedTxs.push(await wallet.signTransaction({
110
+ const txType = getTxType(config);
111
+ // ✅ 获取贿赂金额
112
+ const bribeAmount = getBribeAmount(config);
113
+ const needBribeTx = bribeAmount > 0n;
114
+ // ✅ 利润计算
115
+ const extractProfit = shouldExtractProfit(config);
116
+ const { profit: profitWei } = extractProfit && quotedOutput > 0n
117
+ ? calculateProfit(quotedOutput, config)
118
+ : { profit: 0n };
119
+ const needProfitTx = extractProfit && profitWei > 0n;
120
+ // ✅ 分配 nonces:贿赂 → 卖出 → 利润
121
+ let nonceIdx = 0;
122
+ const bribeNonce = needBribeTx ? currentNonce + nonceIdx++ : undefined;
123
+ const sellNonce = currentNonce + nonceIdx++;
124
+ const profitNonce = needProfitTx ? currentNonce + nonceIdx : undefined;
125
+ // ✅ 并行签名所有交易(贿赂、卖出、利润)
126
+ const signPromises = [];
127
+ // 贿赂交易
128
+ if (needBribeTx && bribeNonce !== undefined) {
129
+ signPromises.push(wallet.signTransaction({
130
+ to: BLOCKRAZOR_BUILDER_EOA,
131
+ value: bribeAmount,
132
+ nonce: bribeNonce,
133
+ gasPrice,
134
+ gasLimit: 21000n,
135
+ chainId,
136
+ type: txType
137
+ }));
138
+ }
139
+ // 卖出交易
140
+ signPromises.push(wallet.signTransaction({
85
141
  ...unsigned,
86
142
  from: wallet.address,
87
- nonce: currentNonce,
143
+ nonce: sellNonce,
88
144
  gasLimit,
89
145
  gasPrice,
90
146
  chainId,
91
- type: getTxType(config)
147
+ type: txType
92
148
  }));
93
- await appendSingleSellProfitTransfer({
94
- extractProfit: shouldExtractProfit(config),
95
- quotedOutput,
96
- wallet,
97
- baseNonce: currentNonce,
98
- gasPrice,
99
- chainId,
100
- config,
101
- signedTxs
102
- });
149
+ // 利润交易
150
+ if (needProfitTx && profitNonce !== undefined) {
151
+ signPromises.push(wallet.signTransaction({
152
+ to: getProfitRecipient(),
153
+ value: profitWei,
154
+ nonce: profitNonce,
155
+ gasPrice,
156
+ gasLimit: 21000n,
157
+ chainId,
158
+ type: txType
159
+ }));
160
+ }
161
+ // ✅ 并行签名完成后按顺序返回
162
+ const signedTxs = await Promise.all(signPromises);
103
163
  return { signedTransactions: signedTxs };
104
164
  }
105
165
  /**
@@ -121,6 +181,10 @@ export async function flapBatchPrivateBuyMerkle(params) {
121
181
  const maxFundsIndex = findMaxIndex(originalAmountsWei);
122
182
  const minOuts = resolveBatchMinOutputs(minOutputAmounts, wallets.length);
123
183
  const portals = wallets.map(w => new ethers.Contract(portalAddr, PORTAL_ABI, w));
184
+ // ✅ 获取贿赂金额
185
+ const bribeAmount = getBribeAmount(config);
186
+ const needBribeTx = bribeAmount > 0n;
187
+ const needProfitTx = extractProfit && totalProfit > 0n;
124
188
  // ✅ 并行获取 gasPrice、所有 nonces 和构建未签名交易
125
189
  const [gasPrice, initialNonces, unsignedList] = await Promise.all([
126
190
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
@@ -133,14 +197,39 @@ export async function flapBatchPrivateBuyMerkle(params) {
133
197
  permitData: '0x',
134
198
  }, { value: actualAmountsWei[i] })))
135
199
  ]);
136
- // ✅ 为需要支付利润的钱包预留额外 nonce
200
+ // ✅ 分配 nonces:贿赂(可选) → 买入 → 利润(可选)
201
+ let payerNonceOffset = 0;
202
+ let bribeNonce;
203
+ if (needBribeTx && maxFundsIndex >= 0) {
204
+ bribeNonce = initialNonces[maxFundsIndex];
205
+ payerNonceOffset++;
206
+ }
137
207
  const nonces = initialNonces.map((n, i) => {
138
- // 如果这个钱包需要支付利润,它的主交易 nonce 不变,利润交易用 nonce+1
208
+ if (i === maxFundsIndex) {
209
+ return n + payerNonceOffset; // payer 的买入 nonce 需要偏移
210
+ }
139
211
  return n;
140
212
  });
213
+ const profitNonce = needProfitTx && maxFundsIndex >= 0
214
+ ? initialNonces[maxFundsIndex] + payerNonceOffset + 1
215
+ : undefined;
141
216
  const gasLimit = resolveGasLimit(config.gasLimitMultiplier);
142
217
  const gasLimits = new Array(wallets.length).fill(gasLimit);
143
- // 并行签名所有交易
218
+ const signedTxs = [];
219
+ // ✅ 贿赂交易放在首位
220
+ if (needBribeTx && bribeNonce !== undefined && maxFundsIndex >= 0) {
221
+ const bribeTx = await wallets[maxFundsIndex].signTransaction({
222
+ to: BLOCKRAZOR_BUILDER_EOA,
223
+ value: bribeAmount,
224
+ nonce: bribeNonce,
225
+ gasPrice,
226
+ gasLimit: 21000n,
227
+ chainId,
228
+ type: getTxType(config)
229
+ });
230
+ signedTxs.push(bribeTx);
231
+ }
232
+ // ✅ 并行签名所有买入交易
144
233
  const signedList = await signBatchTransactions({
145
234
  unsignedList,
146
235
  wallets,
@@ -151,10 +240,9 @@ export async function flapBatchPrivateBuyMerkle(params) {
151
240
  config,
152
241
  values: actualAmountsWei
153
242
  });
154
- const signedTxs = [...signedList];
155
- // 添加利润交易(使用 nonce + 1)
156
- if (extractProfit && totalProfit > 0n && maxFundsIndex >= 0) {
157
- const profitNonce = initialNonces[maxFundsIndex] + 1;
243
+ signedTxs.push(...signedList);
244
+ // 利润交易放在末尾
245
+ if (needProfitTx && profitNonce !== undefined && maxFundsIndex >= 0) {
158
246
  const profitTx = await wallets[maxFundsIndex].signTransaction({
159
247
  to: getProfitRecipient(),
160
248
  value: totalProfit,
@@ -182,50 +270,75 @@ export async function flapBatchPrivateSellMerkle(params) {
182
270
  const amountsWei = amounts.map(a => ethers.parseUnits(a, 18));
183
271
  const portal = new ethers.Contract(portalAddr, PORTAL_ABI, provider);
184
272
  const portals = wallets.map(w => new ethers.Contract(portalAddr, PORTAL_ABI, w));
185
- // ✅ 并行获取 gasPrice、所有 nonces 和所有报价
186
- const [gasPrice, initialNonces, quotedOutputs] = await Promise.all([
273
+ // ✅ 并行获取 gasPrice、所有 nonces、所有报价和构建未签名交易
274
+ const [gasPrice, initialNonces, quotedOutputs, unsignedList] = await Promise.all([
187
275
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
188
276
  Promise.all(wallets.map(w => w.getNonce())),
189
277
  Promise.all(amountsWei.map(amount => portal.quoteExactInput.staticCall({
190
278
  inputToken: tokenAddress,
191
279
  outputToken: ZERO_ADDRESS,
192
280
  inputAmount: amount
193
- }).catch(() => 0n)))
281
+ }).catch(() => 0n))),
282
+ // ✅ 并行构建未签名交易(已移除滑点保护:minOuts 固定为 0)
283
+ Promise.all(portals.map((p, i) => p.swapExactInput.populateTransaction({
284
+ inputToken: tokenAddress,
285
+ outputToken: ZERO_ADDRESS,
286
+ inputAmount: amountsWei[i],
287
+ minOutputAmount: 0n,
288
+ permitData: '0x'
289
+ })))
194
290
  ]);
195
- // 计算 minOuts
196
- let minOuts;
197
- if (minOutputAmounts && minOutputAmounts.length === wallets.length) {
198
- minOuts = minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseEther(m) : m);
199
- }
200
- else {
201
- minOuts = quotedOutputs.map(quoted => quoted * 95n / 100n);
202
- }
203
- // ✅ 并行构建未签名交易
204
- const unsignedList = await Promise.all(portals.map((p, i) => p.swapExactInput.populateTransaction({
205
- inputToken: tokenAddress,
206
- outputToken: ZERO_ADDRESS,
207
- inputAmount: amountsWei[i],
208
- minOutputAmount: minOuts[i],
209
- permitData: '0x'
210
- })));
211
291
  const extractProfit = shouldExtractProfit(config);
212
292
  const { totalProfit, maxRevenueIndex } = summarizeSellProfits(quotedOutputs, extractProfit, config);
293
+ // ✅ 获取贿赂金额
294
+ const bribeAmount = getBribeAmount(config);
295
+ const needBribeTx = bribeAmount > 0n;
296
+ const needProfitTx = extractProfit && totalProfit > 0n;
297
+ // ✅ 分配 nonces:贿赂(可选) → 卖出 → 利润(可选)
298
+ let payerNonceOffset = 0;
299
+ let bribeNonce;
300
+ if (needBribeTx && maxRevenueIndex >= 0) {
301
+ bribeNonce = initialNonces[maxRevenueIndex];
302
+ payerNonceOffset++;
303
+ }
304
+ const nonces = initialNonces.map((n, i) => {
305
+ if (i === maxRevenueIndex) {
306
+ return n + payerNonceOffset; // payer 的卖出 nonce 需要偏移
307
+ }
308
+ return n;
309
+ });
310
+ const profitNonce = needProfitTx && maxRevenueIndex >= 0
311
+ ? initialNonces[maxRevenueIndex] + payerNonceOffset + 1
312
+ : undefined;
213
313
  const gasLimit = resolveGasLimit(config.gasLimitMultiplier);
214
314
  const gasLimits = new Array(wallets.length).fill(gasLimit);
315
+ const signedTxs = [];
316
+ // ✅ 贿赂交易放在首位
317
+ if (needBribeTx && bribeNonce !== undefined && maxRevenueIndex >= 0) {
318
+ const bribeTx = await wallets[maxRevenueIndex].signTransaction({
319
+ to: BLOCKRAZOR_BUILDER_EOA,
320
+ value: bribeAmount,
321
+ nonce: bribeNonce,
322
+ gasPrice,
323
+ gasLimit: 21000n,
324
+ chainId,
325
+ type: getTxType(config)
326
+ });
327
+ signedTxs.push(bribeTx);
328
+ }
215
329
  // ✅ 并行签名所有卖出交易
216
330
  const signedList = await signBatchTransactions({
217
331
  unsignedList,
218
332
  wallets,
219
- nonces: initialNonces,
333
+ nonces,
220
334
  gasLimits,
221
335
  gasPrice,
222
336
  chainId,
223
337
  config
224
338
  });
225
- const signedTxs = [...signedList];
226
- // 添加利润交易(使用 nonce + 1)
227
- if (extractProfit && totalProfit > 0n && maxRevenueIndex >= 0) {
228
- const profitNonce = initialNonces[maxRevenueIndex] + 1;
339
+ signedTxs.push(...signedList);
340
+ // 利润交易放在末尾
341
+ if (needProfitTx && profitNonce !== undefined && maxRevenueIndex >= 0) {
229
342
  const profitTx = await wallets[maxRevenueIndex].signTransaction({
230
343
  to: getProfitRecipient(),
231
344
  value: totalProfit,
@@ -315,7 +428,7 @@ async function resolveSingleSellOutputs(portal, tokenAddress, amountWei, minOutp
315
428
  });
316
429
  return {
317
430
  quotedOutput,
318
- minOut: quotedOutput * 95n / 100n
431
+ minOut: 0n // 已移除滑点保护:minOut 固定为 0
319
432
  };
320
433
  }
321
434
  catch {
@@ -1,4 +1,5 @@
1
1
  import { JsonRpcProvider } from 'ethers';
2
+ import { NonceManager } from './bundle-helpers.js';
2
3
  export type EnsureAllowanceResult = {
3
4
  alreadyApproved: boolean;
4
5
  currentAllowance: bigint;
@@ -190,6 +191,8 @@ export type ApproveTokenBatchRawParams = {
190
191
  gasLimit?: number;
191
192
  /** 链ID(可选,默认56=BSC,signOnly=true 时生效) */
192
193
  chainId?: number;
194
+ /** 外部 NonceManager(可选,用于同一钱包签多笔交易时共享 nonce 状态) */
195
+ nonceManager?: NonceManager;
193
196
  };
194
197
  /** signOnly=false 时的返回结果(直接发送交易) */
195
198
  export type ApproveTokenBatchResult = {
@@ -1,5 +1,6 @@
1
1
  import { Contract, Wallet, JsonRpcProvider, Interface, parseUnits } from 'ethers';
2
2
  import { ADDRESSES } from './constants.js';
3
+ import { NonceManager } from './bundle-helpers.js';
3
4
  const ERC20_ABI = [
4
5
  { "constant": false, "inputs": [{ "name": "spender", "type": "address" }, { "name": "value", "type": "uint256" }], "name": "approve", "outputs": [{ "name": "", "type": "bool" }], "type": "function" },
5
6
  { "constant": true, "inputs": [{ "name": "owner", "type": "address" }, { "name": "spender", "type": "address" }], "name": "allowance", "outputs": [{ "name": "", "type": "uint256" }], "type": "function" },
@@ -488,7 +489,7 @@ export async function approveTokenBatch(params) {
488
489
  return approveTokenBatchRaw({ rpcUrl, privateKeys, tokenAddress, spenderAddress, amounts, signOnly, gasPriceGwei, gasLimit, chainId });
489
490
  }
490
491
  export async function approveTokenBatchRaw(params) {
491
- const { rpcUrl, privateKeys, tokenAddress, spenderAddress, amounts, signOnly, gasPriceGwei, gasLimit, chainId = 56 } = params;
492
+ const { rpcUrl, privateKeys, tokenAddress, spenderAddress, amounts, signOnly, gasPriceGwei, gasLimit, chainId = 56, nonceManager: externalNonceManager } = params;
492
493
  if (privateKeys.length === 0 || amounts.length !== privateKeys.length) {
493
494
  throw new Error('❌ 私钥数量和授权数量必须匹配');
494
495
  }
@@ -507,10 +508,12 @@ export async function approveTokenBatchRaw(params) {
507
508
  : amount);
508
509
  // ==================== signOnly=true:只签名不提交 ====================
509
510
  if (signOnly) {
511
+ // ✅ 使用 NonceManager 管理 nonce(和买卖交易一样)
512
+ const nonceManager = externalNonceManager || new NonceManager(provider);
510
513
  // ✅ 并行获取:当前授权额度 + nonces + gasPrice
511
514
  const [currentAllowances, nonces, fetchedGasPrice] = await Promise.all([
512
515
  batchCheckAllowances(provider, normalizedToken, ownerAddresses, normalizedSpender),
513
- Promise.all(wallets.map(w => provider.getTransactionCount(w.address, 'latest'))),
516
+ nonceManager.getNextNoncesForWallets(wallets), // 使用 NonceManager 批量获取 nonce
514
517
  gasPriceGwei ? Promise.resolve(parseUnits(gasPriceGwei.toString(), 'gwei')) : provider.getFeeData().then(fee => fee.gasPrice || parseUnits('3', 'gwei'))
515
518
  ]);
516
519
  const finalGasPrice = fetchedGasPrice;
@@ -537,7 +540,7 @@ export async function approveTokenBatchRaw(params) {
537
540
  const signedTx = await wallet.signTransaction({
538
541
  to: normalizedToken,
539
542
  data: txData,
540
- nonce: nonces[i],
543
+ nonce: nonces[i], // ✅ NonceManager 已经处理好递增
541
544
  gasLimit: finalGasLimit,
542
545
  gasPrice: finalGasPrice,
543
546
  chainId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",