four-flap-meme-sdk 1.3.8 → 1.3.10

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.
@@ -78,6 +78,10 @@ export interface SendBundleOptions {
78
78
  autoRetry?: boolean;
79
79
  /** 最大重试次数(默认2) */
80
80
  maxRetries?: number;
81
+ /** ✅ 新增:使用时间戳模式(Merkle 会在时间范围内尽快打包) */
82
+ useTimestampMode?: boolean;
83
+ /** ✅ 新增:时间戳窗口秒数(默认15秒,仅在 useTimestampMode=true 时生效) */
84
+ timestampWindowSeconds?: number;
81
85
  }
82
86
  /**
83
87
  * Bundle 发送结果
@@ -133,7 +133,7 @@ export class MerkleClient {
133
133
  if (!options.transactions || options.transactions.length === 0) {
134
134
  throw new Error('Transactions array cannot be empty');
135
135
  }
136
- const minBlockOffset = options.minBlockOffset ?? 3; // 默认至少3个区块的窗口
136
+ const minBlockOffset = options.minBlockOffset ?? 3;
137
137
  const autoRetry = options.autoRetry ?? false;
138
138
  const maxRetries = options.maxRetries ?? 2;
139
139
  let attempt = 0;
@@ -146,47 +146,47 @@ export class MerkleClient {
146
146
  let targetBlock;
147
147
  if (options.targetBlock) {
148
148
  targetBlock = options.targetBlock;
149
- // 检查目标区块是否已经过期
150
149
  if (targetBlock <= currentBlock) {
151
150
  if (autoRetry && attempt < maxRetries) {
152
151
  attempt++;
153
- await new Promise(resolve => setTimeout(resolve, 300)); // 短暂延迟
152
+ await new Promise(resolve => setTimeout(resolve, 300));
154
153
  continue;
155
154
  }
156
155
  throw new Error(`Target block ${targetBlock} has already passed (current block: ${currentBlock})`);
157
156
  }
158
157
  }
159
158
  else {
160
- // 动态计算:确保至少有 minBlockOffset 个区块的窗口
161
159
  const requestedOffset = options.blockOffset ?? 3;
162
160
  const actualOffset = Math.max(requestedOffset, minBlockOffset);
163
161
  targetBlock = currentBlock + actualOffset;
164
162
  }
165
- // 转换为十六进制
166
163
  const targetBlockHex = '0x' + targetBlock.toString(16);
164
+ // ✅ 优化:时间戳模式 - Merkle 会在时间范围内尽快打包
165
+ let minTimestamp = options.minTimestamp;
166
+ let maxTimestamp = options.maxTimestamp;
167
+ if (options.useTimestampMode && !minTimestamp && !maxTimestamp) {
168
+ const now = Math.floor(Date.now() / 1000);
169
+ const windowSeconds = options.timestampWindowSeconds ?? 15;
170
+ minTimestamp = now;
171
+ maxTimestamp = now + windowSeconds;
172
+ }
167
173
  // 发送 Bundle
168
174
  const bundleHash = await this.sendBundleRaw({
169
175
  txs: options.transactions,
170
176
  blockNumber: targetBlockHex,
171
- minTimestamp: options.minTimestamp,
172
- maxTimestamp: options.maxTimestamp,
177
+ minTimestamp,
178
+ maxTimestamp,
173
179
  });
174
- // 提取交易哈希(添加异常处理)
175
- const txHashes = [];
176
- for (let i = 0; i < options.transactions.length; i++) {
180
+ // ✅ 优化:并行提取交易哈希
181
+ const txHashes = options.transactions.map(rawTx => {
177
182
  try {
178
- const tx = Transaction.from(options.transactions[i]);
179
- if (tx.hash) {
180
- txHashes.push(tx.hash);
181
- }
182
- else {
183
- txHashes.push('');
184
- }
183
+ const tx = Transaction.from(rawTx);
184
+ return tx.hash || '';
185
185
  }
186
- catch (error) {
187
- txHashes.push('');
186
+ catch {
187
+ return '';
188
188
  }
189
- }
189
+ });
190
190
  return {
191
191
  bundleHash,
192
192
  txHashes,
@@ -196,19 +196,15 @@ export class MerkleClient {
196
196
  }
197
197
  catch (error) {
198
198
  lastError = error;
199
- // 如果启用了重试且还有重试次数
200
199
  if (autoRetry && attempt < maxRetries) {
201
200
  attempt++;
202
- // 等待一个区块时间再重试
203
201
  const blockTime = this.chainId === 56 ? 3000 : 12000;
204
202
  await new Promise(resolve => setTimeout(resolve, blockTime));
205
203
  continue;
206
204
  }
207
- // 没有重试或已达最大重试次数
208
205
  throw error;
209
206
  }
210
207
  }
211
- // 如果执行到这里,说明所有重试都失败了
212
208
  throw lastError || new Error('Bundle submission failed after all retries');
213
209
  }
214
210
  /**
@@ -254,71 +254,52 @@ async function resolveTokenImage(fourClient, tokenInfo, accessToken) {
254
254
  }
255
255
  throw new Error(getErrorMessage('IMAGE_REQUIRED'));
256
256
  }
257
- function resolveTokenAddress(createResp) {
258
- return (createResp?.tokenAddr ||
259
- createResp?.address ||
260
- createResp?.token ||
261
- ZERO_ADDRESS);
262
- }
263
257
  async function executeBuyFlow(options) {
264
258
  const { wallets, buyAmounts, tokenAddress, config, nonceManager, contractAddress, gasPrice, chainId, profitMode, txType } = options;
265
259
  const extractProfit = shouldExtractProfit(config);
266
260
  const analysis = analyzeBuyFunds(buyAmounts, config, extractProfit);
267
- const unsignedBuys = await populateBuyTransactions({
268
- buyers: wallets,
269
- contractAddress,
270
- tokenAddress,
271
- fundsList: analysis.fundsList
272
- });
273
261
  const finalGasLimit = getGasLimit(config);
274
- const signedBuys = await signBuyTransactions({
275
- unsignedTxs: unsignedBuys,
276
- wallets,
277
- nonceManager,
262
+ // 优化:并行执行 populateBuyTransactions 和获取 nonces
263
+ const [unsignedBuys, nonces] = await Promise.all([
264
+ populateBuyTransactions({
265
+ buyers: wallets,
266
+ contractAddress,
267
+ tokenAddress,
268
+ fundsList: analysis.fundsList
269
+ }),
270
+ Promise.all(wallets.map((wallet) => nonceManager.getNextNonce(wallet)))
271
+ ]);
272
+ // ✅ 如果需要利润转账,预先获取 payer 的下一个 nonce
273
+ let profitNonce;
274
+ if (extractProfit && analysis.totalProfit > 0n && profitMode.type === 'aggregated') {
275
+ const payer = profitMode.payer ?? wallets[0];
276
+ profitNonce = await nonceManager.getNextNonce(payer);
277
+ }
278
+ // ✅ 并行签名所有买入交易
279
+ const signedBuys = await Promise.all(unsignedBuys.map((unsigned, index) => wallets[index].signTransaction({
280
+ ...unsigned,
281
+ from: wallets[index].address,
282
+ nonce: nonces[index],
278
283
  gasLimit: finalGasLimit,
279
284
  gasPrice,
280
285
  chainId,
281
- txType,
282
- valueList: analysis.fundsList
283
- });
286
+ type: txType,
287
+ value: analysis.fundsList[index]
288
+ })));
284
289
  const signedTxs = [...signedBuys];
285
- // ✅ 添加利润转账交易
286
- if (extractProfit) {
287
- if (profitMode.type === 'individual') {
288
- // 每个买家单独支付利润
289
- for (let i = 0; i < analysis.profitList.length; i++) {
290
- if (analysis.profitList[i] > 0n) {
291
- const profitNonce = await nonceManager.getNextNonce(wallets[i]);
292
- const profitTx = await wallets[i].signTransaction({
293
- to: getProfitRecipient(),
294
- value: analysis.profitList[i],
295
- nonce: profitNonce,
296
- gasPrice,
297
- gasLimit: 21000n,
298
- chainId,
299
- type: txType
300
- });
301
- signedTxs.push(profitTx);
302
- }
303
- }
304
- }
305
- else {
306
- // 聚合模式:由指定的 payer 支付总利润
307
- if (analysis.totalProfit > 0n) {
308
- const payer = profitMode.payer ?? wallets[0];
309
- const profitNonce = await nonceManager.getNextNonce(payer);
310
- const profitTx = await payer.signTransaction({
311
- to: getProfitRecipient(),
312
- value: analysis.totalProfit,
313
- nonce: profitNonce,
314
- gasPrice,
315
- gasLimit: 21000n,
316
- chainId,
317
- type: txType
318
- });
319
- signedTxs.push(profitTx);
320
- }
321
- }
290
+ // ✅ 添加利润转账交易(使用预先获取的 nonce)
291
+ if (extractProfit && profitNonce !== undefined && profitMode.type === 'aggregated') {
292
+ const payer = profitMode.payer ?? wallets[0];
293
+ const profitTx = await payer.signTransaction({
294
+ to: getProfitRecipient(),
295
+ value: analysis.totalProfit,
296
+ nonce: profitNonce,
297
+ gasPrice,
298
+ gasLimit: 21000n,
299
+ chainId,
300
+ type: txType
301
+ });
302
+ signedTxs.push(profitTx);
322
303
  }
323
304
  const metadata = buildBuyMetadata(extractProfit, analysis.totalBuyAmount, analysis.totalProfit, wallets.length);
324
305
  return { signedTxs, metadata };
@@ -326,21 +307,28 @@ async function executeBuyFlow(options) {
326
307
  async function executeSellFlow(options) {
327
308
  const { wallets, sellAmounts, tokenAddress, minOutputAmounts, config, provider, nonceManager, contractAddress, gasPrice, chainId, rpcUrl, txType } = options;
328
309
  const amountsWei = sellAmounts.map((amount) => ethers.parseUnits(amount, 18));
329
- const sellOutputs = await resolveSellOutputs({
330
- minOutputAmounts,
331
- sellers: wallets,
332
- amountsWei,
333
- rpcUrl,
334
- tokenAddress
335
- });
336
- await ensureSellerBalances({ provider, tokenAddress, sellers: wallets, amountsWei });
337
- await ensureSellAllowances({
338
- provider,
339
- tokenAddress,
340
- sellers: wallets,
341
- amountsWei,
342
- spender: contractAddress
343
- });
310
+ const finalGasLimit = getGasLimit(config);
311
+ const extractProfit = shouldExtractProfit(config);
312
+ // ✅ 优化:并行执行 resolveSellOutputs、ensureSellerBalances、ensureSellAllowances 和获取 nonces
313
+ const [sellOutputs, , , nonces] = await Promise.all([
314
+ resolveSellOutputs({
315
+ minOutputAmounts,
316
+ sellers: wallets,
317
+ amountsWei,
318
+ rpcUrl,
319
+ tokenAddress
320
+ }),
321
+ ensureSellerBalances({ provider, tokenAddress, sellers: wallets, amountsWei }),
322
+ ensureSellAllowances({
323
+ provider,
324
+ tokenAddress,
325
+ sellers: wallets,
326
+ amountsWei,
327
+ spender: contractAddress
328
+ }),
329
+ Promise.all(wallets.map((wallet) => nonceManager.getNextNonce(wallet)))
330
+ ]);
331
+ // ✅ 构建未签名交易
344
332
  const unsignedSells = await populateSellTransactions({
345
333
  sellers: wallets,
346
334
  contractAddress,
@@ -348,18 +336,17 @@ async function executeSellFlow(options) {
348
336
  amountsWei,
349
337
  minOuts: sellOutputs.minOuts
350
338
  });
351
- const finalGasLimit = getGasLimit(config);
352
- const signedSells = await signSellTransactions({
353
- unsignedTxs: unsignedSells,
354
- wallets,
355
- nonceManager,
339
+ // 并行签名所有卖出交易
340
+ const signedSells = await Promise.all(unsignedSells.map((unsigned, index) => wallets[index].signTransaction({
341
+ ...unsigned,
342
+ from: wallets[index].address,
343
+ nonce: nonces[index],
356
344
  gasLimit: finalGasLimit,
357
345
  gasPrice,
358
346
  chainId,
359
- txType
360
- });
347
+ type: txType
348
+ })));
361
349
  const signedTxs = [...signedSells];
362
- const extractProfit = shouldExtractProfit(config);
363
350
  const { totalProfit, payer } = summarizeSellProfits(sellOutputs.quotedOutputs, wallets, config);
364
351
  await appendAggregatedProfitTransfer({
365
352
  extractProfit,
@@ -399,42 +386,6 @@ async function populateBuyTransactions(params) {
399
386
  const contracts = buyers.map((wallet) => new ethers.Contract(contractAddress, TM2_ABI, wallet));
400
387
  return Promise.all(contracts.map((contract, index) => contract.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyers[index].address, fundsList[index], 0n, { value: fundsList[index] })));
401
388
  }
402
- async function signBuyTransactions(params) {
403
- const { unsignedTxs, wallets, nonceManager, gasLimit, gasPrice, chainId, txType, valueList } = params;
404
- const nonces = await Promise.all(wallets.map((wallet) => nonceManager.getNextNonce(wallet)));
405
- return Promise.all(unsignedTxs.map((unsigned, index) => wallets[index].signTransaction({
406
- ...unsigned,
407
- from: wallets[index].address,
408
- nonce: nonces[index],
409
- gasLimit,
410
- gasPrice,
411
- chainId,
412
- type: txType,
413
- value: valueList[index]
414
- })));
415
- }
416
- async function appendIndividualProfitTransfers(params) {
417
- const { extractProfit, profitList, wallets, nonceManager, gasPrice, chainId, txType, signedTxs } = params;
418
- if (!extractProfit || profitList.length === 0) {
419
- return;
420
- }
421
- for (let i = 0; i < profitList.length; i++) {
422
- if (profitList[i] <= 0n) {
423
- continue;
424
- }
425
- const profitNonce = await nonceManager.getNextNonce(wallets[i]);
426
- const profitTx = await wallets[i].signTransaction({
427
- to: getProfitRecipient(),
428
- value: profitList[i],
429
- nonce: profitNonce,
430
- gasPrice,
431
- gasLimit: 21000n,
432
- chainId,
433
- type: txType
434
- });
435
- signedTxs.push(profitTx);
436
- }
437
- }
438
389
  function buildBuyMetadata(extractProfit, totalBuyAmount, totalProfit, buyerCount) {
439
390
  if (!extractProfit || totalBuyAmount <= 0n || totalProfit <= 0n) {
440
391
  return undefined;
@@ -493,19 +444,6 @@ async function populateSellTransactions(params) {
493
444
  const contracts = sellers.map((wallet) => new ethers.Contract(contractAddress, TM2_ABI, wallet));
494
445
  return Promise.all(contracts.map((contract, index) => contract.sellToken.populateTransaction(0n, tokenAddress, amountsWei[index], minOuts[index])));
495
446
  }
496
- async function signSellTransactions(params) {
497
- const { unsignedTxs, wallets, nonceManager, gasLimit, gasPrice, chainId, txType } = params;
498
- const nonces = await Promise.all(wallets.map((wallet) => nonceManager.getNextNonce(wallet)));
499
- return Promise.all(unsignedTxs.map((unsigned, index) => wallets[index].signTransaction({
500
- ...unsigned,
501
- from: wallets[index].address,
502
- nonce: nonces[index],
503
- gasLimit,
504
- gasPrice,
505
- chainId,
506
- type: txType
507
- })));
508
- }
509
447
  function summarizeSellProfits(quotedOutputs, sellers, config) {
510
448
  let totalProfit = 0n;
511
449
  let maxRevenue = 0n;
@@ -1,7 +1,7 @@
1
1
  import { ethers, Wallet, JsonRpcProvider, Contract } from 'ethers';
2
2
  import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
3
3
  import { ADDRESSES } from '../../utils/constants.js';
4
- import { getTxType, getBundleOptions, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient } from './config.js';
4
+ import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient } from './config.js';
5
5
  import { batchCheckAllowances } from '../../utils/erc20.js';
6
6
  // 常量
7
7
  const WBNB_ADDRESS = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
@@ -113,66 +113,6 @@ async function buildV3MultiHopTransactions(proxies, wallets, lpAddresses, exactT
113
113
  return proxy.swapV3MultiHop.populateTransaction(lpAddresses, exactTokenIn, amountsWei[i], minOuts[i], wallets[i].address, { value: txValue });
114
114
  }));
115
115
  }
116
- /**
117
- * 签名并提交 Bundle
118
- */
119
- async function signAndSendBundle(merkle, wallets, unsignedTxs, gasPrice, gasLimit, chainId, config, blockOffset) {
120
- const nonceManager = new NonceManager(merkle.getProvider());
121
- const txType = getTxType(config);
122
- try {
123
- const nonces = await Promise.all(wallets.map(w => nonceManager.getNextNonce(w)));
124
- const signedTxs = await Promise.all(unsignedTxs.map((unsigned, i) => wallets[i].signTransaction({
125
- ...unsigned,
126
- from: wallets[i].address,
127
- nonce: nonces[i],
128
- gasLimit,
129
- gasPrice,
130
- chainId,
131
- type: txType,
132
- value: unsigned.value // ✅ 显式保留 value(BNB 金额 + 手续费)
133
- })));
134
- const bundleResult = await merkle.sendBundle({
135
- transactions: signedTxs,
136
- ...getBundleOptions(config, blockOffset)
137
- });
138
- return bundleResult;
139
- }
140
- finally {
141
- // 确保清理临时 nonce 缓存
142
- nonceManager.clearTemp();
143
- }
144
- }
145
- /**
146
- * 等待 Bundle 确认并返回状态
147
- */
148
- async function waitForBundleResult(merkle, bundleResult, waitForConfirmation, waitTimeoutMs) {
149
- if (!waitForConfirmation) {
150
- return {
151
- bundleHash: bundleResult.bundleHash,
152
- txHashes: bundleResult.txHashes,
153
- results: [],
154
- successCount: 0,
155
- totalGasUsed: 0n,
156
- targetBlock: bundleResult.targetBlock
157
- };
158
- }
159
- const results = await merkle.waitForBundleConfirmation(bundleResult.txHashes, 1, waitTimeoutMs);
160
- const successCount = results.filter(r => r.success).length;
161
- const totalGasUsed = results.reduce((sum, r) => {
162
- if (r.gasUsed) {
163
- return sum + BigInt(r.gasUsed);
164
- }
165
- return sum;
166
- }, 0n);
167
- return {
168
- bundleHash: bundleResult.bundleHash,
169
- txHashes: bundleResult.txHashes,
170
- results,
171
- successCount,
172
- totalGasUsed,
173
- targetBlock: bundleResult.targetBlock
174
- };
175
- }
176
116
  // ==================== 主要导出函数 ====================
177
117
  /**
178
118
  * 授权代币给 PancakeSwapProxy
@@ -303,11 +243,19 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
303
243
  const extractProfit = shouldExtractProfit(config);
304
244
  const { totalProfit, remainingAmounts } = calculateBatchProfit(originalAmountsWei, config);
305
245
  const actualAmountsWei = remainingAmounts; // 扣除利润后用于购买的金额
306
- // 优化:并行获取 gasPrice 和 tokenDecimals
307
- const [gasPrice, tokenDecimals] = await Promise.all([
246
+ const finalGasLimit = getGasLimit(config);
247
+ const nonceManager = new NonceManager(provider);
248
+ // ✅ 优化:并行获取 gasPrice、tokenDecimals 和 nonces
249
+ const [gasPrice, tokenDecimals, nonces] = await Promise.all([
308
250
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
309
- getTokenDecimals(tokenAddress, provider)
251
+ getTokenDecimals(tokenAddress, provider),
252
+ Promise.all(buyers.map(w => nonceManager.getNextNonce(w)))
310
253
  ]);
254
+ // ✅ 如果需要利润转账,预先获取 nonce
255
+ let profitNonce;
256
+ if (extractProfit && totalProfit > 0n) {
257
+ profitNonce = await nonceManager.getNextNonce(buyers[0]);
258
+ }
311
259
  // 计算 minOutputAmounts
312
260
  let minOuts;
313
261
  if (params.minOutputAmounts && params.minOutputAmounts.length === buyers.length) {
@@ -342,11 +290,7 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
342
290
  else {
343
291
  throw new Error(`Unsupported routeType: ${routeType}`);
344
292
  }
345
- // ✅ 使用前端传入的 gasLimit,否则使用默认值
346
- const finalGasLimit = getGasLimit(config);
347
- // ✅ 优化:并行获取 nonces(直接签名和提交以支持利润转账)
348
- const nonceManager = new NonceManager(provider);
349
- const nonces = await Promise.all(buyers.map(w => nonceManager.getNextNonce(w)));
293
+ // ✅ 并行签名所有买入交易
350
294
  const signedTxs = await Promise.all(unsignedBuys.map((unsigned, i) => buyers[i].signTransaction({
351
295
  ...unsigned,
352
296
  from: buyers[i].address,
@@ -357,9 +301,8 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
357
301
  type: txType,
358
302
  value: unsigned.value // ✅ 显式保留 value(BNB 金额 + 手续费)
359
303
  })));
360
- // ✅ 添加利润转账(从第一个钱包转出总利润)
361
- if (extractProfit && totalProfit > 0n) {
362
- const profitNonce = await nonceManager.getNextNonce(buyers[0]);
304
+ // ✅ 添加利润转账(使用预先获取的 nonce)
305
+ if (extractProfit && totalProfit > 0n && profitNonce !== undefined) {
363
306
  const profitTx = await buyers[0].signTransaction({
364
307
  to: getProfitRecipient(),
365
308
  value: totalProfit,
@@ -397,14 +340,17 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
397
340
  });
398
341
  const txType = getTxType(config);
399
342
  const sellers = privateKeys.map(k => new Wallet(k, provider));
400
- // 优化:并行获取 gasPrice 和 tokenDecimals
401
- const [gasPrice, tokenDecimals] = await Promise.all([
343
+ const finalGasLimit = getGasLimit(config);
344
+ const nonceManager = new NonceManager(provider);
345
+ const extractProfit = shouldExtractProfit(config);
346
+ // ✅ 优化:并行获取 gasPrice、tokenDecimals、allowances 和 nonces
347
+ const [gasPrice, tokenDecimals, allowances, nonces] = await Promise.all([
402
348
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
403
- getTokenDecimals(tokenAddress, provider)
349
+ getTokenDecimals(tokenAddress, provider),
350
+ batchCheckAllowances(provider, tokenAddress, sellers.map(w => w.address), pancakeProxyAddress),
351
+ Promise.all(sellers.map(w => nonceManager.getNextNonce(w)))
404
352
  ]);
405
353
  const amountsWei = sellAmounts.map(a => ethers.parseUnits(a, tokenDecimals));
406
- // ✅ Step 1: 使用 Multicall3 批量检查授权状态
407
- const allowances = await batchCheckAllowances(provider, tokenAddress, sellers.map(w => w.address), pancakeProxyAddress);
408
354
  // 找出需要授权的钱包索引
409
355
  const needApprovalIndexes = [];
410
356
  const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
@@ -463,6 +409,27 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
463
409
  // minOuts 设为预期收益的 95%(滑点保护)
464
410
  minOuts = quotedOutputs.map(q => q * 95n / 100n);
465
411
  }
412
+ // ✅ 计算利润并找出收益最多的钱包(提前计算,预先获取 nonce)
413
+ let totalProfit = 0n;
414
+ let maxRevenueIndex = 0;
415
+ let maxRevenue = 0n;
416
+ if (extractProfit && quotedOutputs.length > 0) {
417
+ for (let i = 0; i < sellers.length; i++) {
418
+ if (quotedOutputs[i] > 0n) {
419
+ const { profit } = calculateProfit(quotedOutputs[i], config);
420
+ totalProfit += profit;
421
+ if (quotedOutputs[i] > maxRevenue) {
422
+ maxRevenue = quotedOutputs[i];
423
+ maxRevenueIndex = i;
424
+ }
425
+ }
426
+ }
427
+ }
428
+ // ✅ 如果需要利润转账,预先获取 nonce
429
+ let profitNonce;
430
+ if (extractProfit && totalProfit > 0n && maxRevenue > 0n) {
431
+ profitNonce = await nonceManager.getNextNonce(sellers[maxRevenueIndex]);
432
+ }
466
433
  // 卖出不需要发送 BNB,只需要 flatFee
467
434
  const needBNB = false;
468
435
  // 构建交易
@@ -489,18 +456,7 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
489
456
  else {
490
457
  throw new Error(`Unsupported routeType: ${routeType}`);
491
458
  }
492
- // ✅ Step 4: 验证钱包余额(确保有足够 BNB 支付手续费)
493
- const balances = await Promise.all(sellers.map(w => provider.getBalance(w.address)));
494
- const minRequiredBNB = ethers.parseEther('0.001'); // 至少需要 0.001 BNB(包括 gas 和手续费)
495
- const insufficientBalances = balances.filter(b => b < minRequiredBNB);
496
- if (insufficientBalances.length > 0) {
497
- throw new Error(`${insufficientBalances.length} 个钱包 BNB 余额不足 0.001 BNB`);
498
- }
499
- // ✅ 使用前端传入的 gasLimit,否则使用默认值
500
- const finalGasLimit = getGasLimit(config);
501
- // ✅ 直接签名和提交(内联处理以支持利润转账)
502
- const nonceManager = new NonceManager(provider);
503
- const nonces = await Promise.all(sellers.map(w => nonceManager.getNextNonce(w)));
459
+ // ✅ 并行签名所有卖出交易
504
460
  const signedTxs = await Promise.all(unsignedSells.map((unsigned, i) => sellers[i].signTransaction({
505
461
  ...unsigned,
506
462
  from: sellers[i].address,
@@ -511,38 +467,18 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
511
467
  type: txType,
512
468
  value: unsigned.value // ✅ 显式保留 value(手续费)
513
469
  })));
514
- // ✅ 汇总所有利润,由收益最多的钱包一次性转出(节省交易数)
515
- const extractProfit = shouldExtractProfit(config);
516
- if (extractProfit && quotedOutputs.length > 0) {
517
- let totalProfit = 0n;
518
- let maxRevenueIndex = 0;
519
- let maxRevenue = 0n;
520
- // 计算总利润并找出收益最多的钱包
521
- for (let i = 0; i < sellers.length; i++) {
522
- if (quotedOutputs[i] > 0n) {
523
- const { profit } = calculateProfit(quotedOutputs[i], config);
524
- totalProfit += profit;
525
- // 记录收益最多的钱包索引
526
- if (quotedOutputs[i] > maxRevenue) {
527
- maxRevenue = quotedOutputs[i];
528
- maxRevenueIndex = i;
529
- }
530
- }
531
- }
532
- // ✅ 由收益最多的钱包一次性转出总利润(该钱包最有能力支付)
533
- if (totalProfit > 0n && maxRevenue > 0n) {
534
- const profitNonce = await nonceManager.getNextNonce(sellers[maxRevenueIndex]);
535
- const profitTx = await sellers[maxRevenueIndex].signTransaction({
536
- to: getProfitRecipient(),
537
- value: totalProfit,
538
- nonce: profitNonce,
539
- gasPrice,
540
- gasLimit: 21000n,
541
- chainId: 56,
542
- type: txType
543
- });
544
- signedTxs.push(profitTx);
545
- }
470
+ // ✅ 添加利润转账(使用预先获取的 nonce)
471
+ if (extractProfit && totalProfit > 0n && profitNonce !== undefined) {
472
+ const profitTx = await sellers[maxRevenueIndex].signTransaction({
473
+ to: getProfitRecipient(),
474
+ value: totalProfit,
475
+ nonce: profitNonce,
476
+ gasPrice,
477
+ gasLimit: 21000n,
478
+ chainId: 56,
479
+ type: txType
480
+ });
481
+ signedTxs.push(profitTx);
546
482
  }
547
483
  // ✅ 清理临时 nonce 缓存
548
484
  nonceManager.clearTemp();
@@ -22,6 +22,10 @@ export interface MerkleSubmitConfig {
22
22
  autoRetryBundle?: boolean;
23
23
  /** 最大重试次数(可选,默认2) */
24
24
  maxBundleRetries?: number;
25
+ /** ✅ 新增:使用时间戳模式(Merkle 会在时间范围内尽快打包) */
26
+ useTimestampMode?: boolean;
27
+ /** ✅ 新增:时间戳窗口秒数(默认15秒,仅在 useTimestampMode=true 时生效) */
28
+ timestampWindowSeconds?: number;
25
29
  }
26
30
  /**
27
31
  * Bundle提交结果
@@ -6,6 +6,21 @@
6
6
  */
7
7
  import { MerkleClient } from '../../clients/merkle.js';
8
8
  import { ethers } from 'ethers';
9
+ // ✅ 优化:MerkleClient 缓存,避免重复创建
10
+ const merkleClientCache = new Map();
11
+ function getMerkleClient(config) {
12
+ const cacheKey = `${config.apiKey}-${config.chainId ?? 56}-${config.customRpcUrl ?? ''}`;
13
+ let client = merkleClientCache.get(cacheKey);
14
+ if (!client) {
15
+ client = new MerkleClient({
16
+ apiKey: config.apiKey,
17
+ chainId: config.chainId ?? 56,
18
+ customRpcUrl: config.customRpcUrl
19
+ });
20
+ merkleClientCache.set(cacheKey, client);
21
+ }
22
+ return client;
23
+ }
9
24
  /**
10
25
  * 提交已签名的交易到Merkle(服务器端使用)
11
26
  *
@@ -52,19 +67,18 @@ export async function submitBundleToMerkle(signedTransactions, config) {
52
67
  error: 'apiKey is required in config'
53
68
  };
54
69
  }
55
- // 初始化Merkle客户端
56
- const merkle = new MerkleClient({
57
- apiKey: config.apiKey,
58
- chainId: config.chainId ?? 56,
59
- customRpcUrl: config.customRpcUrl
60
- });
70
+ // ✅ 优化:使用缓存的 MerkleClient(避免重复创建 JsonRpcProvider)
71
+ const merkle = getMerkleClient(config);
61
72
  // 提交Bundle
62
73
  const bundleResult = await merkle.sendBundle({
63
74
  transactions: signedTransactions,
64
75
  blockOffset: config.bundleBlockOffset ?? 3,
65
76
  minBlockOffset: config.minBlockOffset ?? 3,
66
77
  autoRetry: config.autoRetryBundle ?? false,
67
- maxRetries: config.maxBundleRetries ?? 2
78
+ maxRetries: config.maxBundleRetries ?? 2,
79
+ // ✅ 新增:时间戳模式
80
+ useTimestampMode: config.useTimestampMode,
81
+ timestampWindowSeconds: config.timestampWindowSeconds
68
82
  });
69
83
  // ✅ 提交成功
70
84
  return {