four-flap-meme-sdk 1.3.39 → 1.3.41
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.
|
@@ -303,17 +303,25 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
|
|
|
303
303
|
const actualAmountsWei = remainingAmounts; // 扣除利润后用于购买的金额
|
|
304
304
|
const finalGasLimit = getGasLimit(config);
|
|
305
305
|
const nonceManager = new NonceManager(provider);
|
|
306
|
-
// ✅ 优化:并行获取 gasPrice
|
|
307
|
-
const [gasPrice, tokenDecimals
|
|
306
|
+
// ✅ 优化:并行获取 gasPrice 和 tokenDecimals
|
|
307
|
+
const [gasPrice, tokenDecimals] = await Promise.all([
|
|
308
308
|
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
309
|
-
getTokenDecimals(tokenAddress, provider)
|
|
310
|
-
nonceManager.getNextNoncesForWallets(buyers) // ✅ 批量获取 nonce
|
|
309
|
+
getTokenDecimals(tokenAddress, provider)
|
|
311
310
|
]);
|
|
312
|
-
// ✅
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
311
|
+
// ✅ 修复:先计算需要的 nonce 数量,再统一获取
|
|
312
|
+
// buyers[0] 可能需要 2 个 nonce(买入 + 利润转账)
|
|
313
|
+
const needProfitTx = extractProfit && totalProfit > 0n;
|
|
314
|
+
const buyer0NeedCount = needProfitTx ? 2 : 1;
|
|
315
|
+
// 获取 buyers[0] 的连续 nonces
|
|
316
|
+
const buyer0Nonces = await nonceManager.getNextNonceBatch(buyers[0], buyer0NeedCount);
|
|
317
|
+
// 获取其他 buyers 的 nonces(如果有)
|
|
318
|
+
let otherNonces = [];
|
|
319
|
+
if (buyers.length > 1) {
|
|
320
|
+
otherNonces = await nonceManager.getNextNoncesForWallets(buyers.slice(1));
|
|
321
|
+
}
|
|
322
|
+
// 组装最终的 nonces 数组
|
|
323
|
+
const nonces = [buyer0Nonces[0], ...otherNonces];
|
|
324
|
+
const profitNonce = needProfitTx ? buyer0Nonces[1] : undefined;
|
|
317
325
|
// 计算 minOutputAmounts
|
|
318
326
|
let minOuts;
|
|
319
327
|
if (params.minOutputAmounts && params.minOutputAmounts.length === buyers.length) {
|
|
@@ -401,12 +409,11 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
|
|
|
401
409
|
const finalGasLimit = getGasLimit(config);
|
|
402
410
|
const nonceManager = new NonceManager(provider);
|
|
403
411
|
const extractProfit = shouldExtractProfit(config);
|
|
404
|
-
// ✅ 优化:并行获取 gasPrice、tokenDecimals
|
|
405
|
-
const [gasPrice, tokenDecimals, allowances
|
|
412
|
+
// ✅ 优化:并行获取 gasPrice、tokenDecimals 和 allowances
|
|
413
|
+
const [gasPrice, tokenDecimals, allowances] = await Promise.all([
|
|
406
414
|
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
407
415
|
getTokenDecimals(tokenAddress, provider),
|
|
408
|
-
batchCheckAllowances(provider, tokenAddress, sellers.map(w => w.address), pancakeProxyAddress)
|
|
409
|
-
nonceManager.getNextNoncesForWallets(sellers) // ✅ 批量获取 nonce
|
|
416
|
+
batchCheckAllowances(provider, tokenAddress, sellers.map(w => w.address), pancakeProxyAddress)
|
|
410
417
|
]);
|
|
411
418
|
const amountsWei = sellAmounts.map(a => ethers.parseUnits(a, tokenDecimals));
|
|
412
419
|
// 找出需要授权的钱包索引
|
|
@@ -464,7 +471,7 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
|
|
|
464
471
|
// ✅ minOuts = 0,不设置滑点限制(大额交易更稳定)
|
|
465
472
|
minOuts = quotedOutputs.map(() => 0n);
|
|
466
473
|
}
|
|
467
|
-
// ✅
|
|
474
|
+
// ✅ 计算利润并找出收益最多的钱包
|
|
468
475
|
let totalProfit = 0n;
|
|
469
476
|
let maxRevenueIndex = 0;
|
|
470
477
|
let maxRevenue = 0n;
|
|
@@ -480,10 +487,35 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
|
|
|
480
487
|
}
|
|
481
488
|
}
|
|
482
489
|
}
|
|
483
|
-
// ✅
|
|
490
|
+
// ✅ 修复:先计算需要的 nonce 数量,再统一获取
|
|
491
|
+
const needProfitTx = extractProfit && totalProfit > 0n && maxRevenue > 0n;
|
|
492
|
+
// 分配 nonces:收益最多的钱包可能需要 2 个 nonce(卖出 + 利润转账)
|
|
493
|
+
let nonces;
|
|
484
494
|
let profitNonce;
|
|
485
|
-
if (
|
|
486
|
-
|
|
495
|
+
if (needProfitTx) {
|
|
496
|
+
// 收益最多的钱包需要 2 个连续 nonce
|
|
497
|
+
const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], 2);
|
|
498
|
+
// 其他钱包各需要 1 个 nonce
|
|
499
|
+
const otherSellers = sellers.filter((_, i) => i !== maxRevenueIndex);
|
|
500
|
+
const otherNonces = otherSellers.length > 0
|
|
501
|
+
? await nonceManager.getNextNoncesForWallets(otherSellers)
|
|
502
|
+
: [];
|
|
503
|
+
// 组装最终的 nonces 数组(保持原顺序)
|
|
504
|
+
nonces = [];
|
|
505
|
+
let otherIdx = 0;
|
|
506
|
+
for (let i = 0; i < sellers.length; i++) {
|
|
507
|
+
if (i === maxRevenueIndex) {
|
|
508
|
+
nonces.push(maxRevenueNonces[0]); // 卖出交易用第一个 nonce
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
nonces.push(otherNonces[otherIdx++]);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
profitNonce = maxRevenueNonces[1]; // 利润交易用第二个 nonce
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
// 不需要利润交易,所有钱包各 1 个 nonce
|
|
518
|
+
nonces = await nonceManager.getNextNoncesForWallets(sellers);
|
|
487
519
|
}
|
|
488
520
|
// 卖出不需要发送 BNB,只需要 flatFee
|
|
489
521
|
const needBNB = false;
|
|
@@ -79,6 +79,19 @@ const V2_ROUTER_ABI = [
|
|
|
79
79
|
// 报价
|
|
80
80
|
'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)',
|
|
81
81
|
];
|
|
82
|
+
/**
|
|
83
|
+
* SwapRouter02 的 V2 方法 ABI (PotatoSwap, PancakeSwap V3 Router)
|
|
84
|
+
* SwapRouter02 同时支持 V2 和 V3,V2 方法通过 multicall 调用
|
|
85
|
+
*/
|
|
86
|
+
const SWAP_ROUTER02_V2_ABI = [
|
|
87
|
+
// V2 交换方法 (SwapRouter02 内置)
|
|
88
|
+
'function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to) external payable returns (uint256 amountOut)',
|
|
89
|
+
// Multicall
|
|
90
|
+
'function multicall(uint256 deadline, bytes[] calldata data) external payable returns (bytes[] memory results)',
|
|
91
|
+
// 辅助方法
|
|
92
|
+
'function refundETH() external payable',
|
|
93
|
+
'function unwrapWETH9(uint256 amountMinimum, address recipient) external payable',
|
|
94
|
+
];
|
|
82
95
|
/**
|
|
83
96
|
* V3 SwapRouter02 ABI (PancakeSwap V3, 新版 Uniswap)
|
|
84
97
|
* - exactInputSingle: 不含 deadline(deadline 通过 multicall 传递)
|
|
@@ -228,6 +241,23 @@ async function buildProfitTransaction(wallet, profitAmountWei, nonce, gasPrice,
|
|
|
228
241
|
// ============================================================================
|
|
229
242
|
// V2 直接交易
|
|
230
243
|
// ============================================================================
|
|
244
|
+
/**
|
|
245
|
+
* 判断是否是 SwapRouter02 (PotatoSwap, PancakeSwap V3 等)
|
|
246
|
+
* SwapRouter02 的 V2 方法签名不同,需要特殊处理
|
|
247
|
+
*/
|
|
248
|
+
function isSwapRouter02(chain, routerAddress) {
|
|
249
|
+
const chainUpper = chain.toUpperCase();
|
|
250
|
+
const addrLower = routerAddress.toLowerCase();
|
|
251
|
+
// XLayer PotatoSwap SwapRouter02
|
|
252
|
+
if (chainUpper === 'XLAYER' && addrLower === DIRECT_ROUTERS.XLAYER.POTATOSWAP_V2.toLowerCase()) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
// BSC PancakeSwap V3 SwapRouter02
|
|
256
|
+
if (chainUpper === 'BSC' && addrLower === DIRECT_ROUTERS.BSC.PANCAKESWAP_V3.toLowerCase()) {
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
231
261
|
/**
|
|
232
262
|
* V2 批量买入(直接调用 Router)
|
|
233
263
|
*/
|
|
@@ -254,8 +284,12 @@ export async function directV2BatchBuy(params) {
|
|
|
254
284
|
// 构建路径
|
|
255
285
|
const inputToken = useNative ? wrappedNative : quoteToken;
|
|
256
286
|
const path = [inputToken, tokenAddress];
|
|
257
|
-
//
|
|
258
|
-
const
|
|
287
|
+
// ✅ 判断是否是 SwapRouter02
|
|
288
|
+
const useSwapRouter02 = isSwapRouter02(chain, routerAddress);
|
|
289
|
+
// 创建 Router 合约接口
|
|
290
|
+
const routerIface = useSwapRouter02
|
|
291
|
+
? new ethers.Interface(SWAP_ROUTER02_V2_ABI)
|
|
292
|
+
: new ethers.Interface(V2_ROUTER_ABI);
|
|
259
293
|
const signedTxs = [];
|
|
260
294
|
let totalFlowWei = 0n;
|
|
261
295
|
for (let i = 0; i < wallets.length; i++) {
|
|
@@ -264,9 +298,24 @@ export async function directV2BatchBuy(params) {
|
|
|
264
298
|
totalFlowWei += amountWei;
|
|
265
299
|
let txData;
|
|
266
300
|
let txValue;
|
|
267
|
-
if (
|
|
268
|
-
//
|
|
269
|
-
//
|
|
301
|
+
if (useSwapRouter02) {
|
|
302
|
+
// ✅ SwapRouter02 V2 方法:使用 multicall 包装
|
|
303
|
+
// SwapRouter02.swapExactTokensForTokens 不含 deadline,需要通过 multicall 传递
|
|
304
|
+
const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
|
|
305
|
+
amountWei,
|
|
306
|
+
0n, // amountOutMin
|
|
307
|
+
path,
|
|
308
|
+
wallet.address,
|
|
309
|
+
]);
|
|
310
|
+
// 使用 multicall 包装,传递 deadline
|
|
311
|
+
txData = routerIface.encodeFunctionData('multicall', [
|
|
312
|
+
deadline,
|
|
313
|
+
[swapData],
|
|
314
|
+
]);
|
|
315
|
+
txValue = useNative ? amountWei : 0n;
|
|
316
|
+
}
|
|
317
|
+
else if (useNative) {
|
|
318
|
+
// 传统 V2 Router: 原生币 → Token
|
|
270
319
|
txData = routerIface.encodeFunctionData('swapExactETHForTokensSupportingFeeOnTransferTokens', [
|
|
271
320
|
0n, // amountOutMin
|
|
272
321
|
path,
|
|
@@ -276,7 +325,7 @@ export async function directV2BatchBuy(params) {
|
|
|
276
325
|
txValue = amountWei;
|
|
277
326
|
}
|
|
278
327
|
else {
|
|
279
|
-
// ERC20 → Token (需要先授权)
|
|
328
|
+
// 传统 V2 Router: ERC20 → Token (需要先授权)
|
|
280
329
|
txData = routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [
|
|
281
330
|
amountWei,
|
|
282
331
|
0n, // amountOutMin
|
|
@@ -353,7 +402,11 @@ export async function directV2BatchSell(params) {
|
|
|
353
402
|
// 构建路径
|
|
354
403
|
const outputToken = useNativeOutput ? wrappedNative : quoteToken;
|
|
355
404
|
const path = [tokenAddress, outputToken];
|
|
356
|
-
|
|
405
|
+
// ✅ 判断是否是 SwapRouter02
|
|
406
|
+
const useSwapRouter02 = isSwapRouter02(chain, routerAddress);
|
|
407
|
+
const routerIface = useSwapRouter02
|
|
408
|
+
? new ethers.Interface(SWAP_ROUTER02_V2_ABI)
|
|
409
|
+
: new ethers.Interface(V2_ROUTER_ABI);
|
|
357
410
|
const approveIface = new ethers.Interface(ERC20_ABI);
|
|
358
411
|
const signedTxs = [];
|
|
359
412
|
let totalOutputEstimate = 0n;
|
|
@@ -385,7 +438,20 @@ export async function directV2BatchSell(params) {
|
|
|
385
438
|
}
|
|
386
439
|
// 卖出交易
|
|
387
440
|
let txData;
|
|
388
|
-
if (
|
|
441
|
+
if (useSwapRouter02) {
|
|
442
|
+
// ✅ SwapRouter02 V2 方法:使用 multicall 包装
|
|
443
|
+
const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
|
|
444
|
+
sellAmount,
|
|
445
|
+
0n, // amountOutMin
|
|
446
|
+
path,
|
|
447
|
+
wallet.address,
|
|
448
|
+
]);
|
|
449
|
+
txData = routerIface.encodeFunctionData('multicall', [
|
|
450
|
+
deadline,
|
|
451
|
+
[swapData],
|
|
452
|
+
]);
|
|
453
|
+
}
|
|
454
|
+
else if (useNativeOutput) {
|
|
389
455
|
txData = routerIface.encodeFunctionData('swapExactTokensForETHSupportingFeeOnTransferTokens', [
|
|
390
456
|
sellAmount,
|
|
391
457
|
0n,
|
|
@@ -354,8 +354,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
354
354
|
const finalGasLimit = getGasLimit(config);
|
|
355
355
|
const extractProfit = shouldExtractProfit(config);
|
|
356
356
|
const nonceManager = new NonceManager(provider);
|
|
357
|
-
// ✅ 优化:第一批并行 - gasPrice、tokenDecimals、allowances
|
|
358
|
-
const [gasPrice, tokenDecimals
|
|
357
|
+
// ✅ 优化:第一批并行 - gasPrice、tokenDecimals、allowances
|
|
358
|
+
const [gasPrice, tokenDecimals] = await Promise.all([
|
|
359
359
|
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
360
360
|
getTokenDecimals(tokenAddress, provider),
|
|
361
361
|
ensureAllowances({
|
|
@@ -363,8 +363,7 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
363
363
|
tokenAddress,
|
|
364
364
|
owners: sellers.map(w => w.address),
|
|
365
365
|
spender: ADDRESSES.BSC.PancakeProxy
|
|
366
|
-
})
|
|
367
|
-
nonceManager.getNextNoncesForWallets(sellers) // ✅ 批量获取 nonce
|
|
366
|
+
})
|
|
368
367
|
]);
|
|
369
368
|
const amountsWei = sellAmounts.map(amount => ethers.parseUnits(amount, tokenDecimals));
|
|
370
369
|
// ✅ 优化:第二批并行 - resolveSellOutputs 和 buildSellTransactions
|
|
@@ -376,6 +375,52 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
376
375
|
routeType,
|
|
377
376
|
amountsWei
|
|
378
377
|
});
|
|
378
|
+
// ✅ 修复:先计算利润和 maxRevenueIndex,再统一分配 nonces
|
|
379
|
+
let totalProfit = 0n;
|
|
380
|
+
let maxRevenueIndex = -1;
|
|
381
|
+
let maxRevenue = 0n;
|
|
382
|
+
if (extractProfit && quotedOutputs.length > 0) {
|
|
383
|
+
for (let i = 0; i < sellers.length; i++) {
|
|
384
|
+
const quoted = quotedOutputs[i];
|
|
385
|
+
if (quoted > 0n) {
|
|
386
|
+
const { profit } = calculateProfit(quoted, config);
|
|
387
|
+
totalProfit += profit;
|
|
388
|
+
if (quoted > maxRevenue) {
|
|
389
|
+
maxRevenue = quoted;
|
|
390
|
+
maxRevenueIndex = i;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// ✅ 修复:根据是否需要利润交易,统一分配 nonces
|
|
396
|
+
const needProfitTx = extractProfit && totalProfit > 0n && maxRevenueIndex >= 0;
|
|
397
|
+
let nonces;
|
|
398
|
+
let profitNonce;
|
|
399
|
+
if (needProfitTx) {
|
|
400
|
+
// maxRevenueIndex 钱包需要 2 个连续 nonce(卖出 + 利润)
|
|
401
|
+
const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], 2);
|
|
402
|
+
// 其他钱包各需要 1 个 nonce
|
|
403
|
+
const otherSellers = sellers.filter((_, i) => i !== maxRevenueIndex);
|
|
404
|
+
const otherNonces = otherSellers.length > 0
|
|
405
|
+
? await nonceManager.getNextNoncesForWallets(otherSellers)
|
|
406
|
+
: [];
|
|
407
|
+
// 组装最终的 nonces 数组(保持原顺序)
|
|
408
|
+
nonces = [];
|
|
409
|
+
let otherIdx = 0;
|
|
410
|
+
for (let i = 0; i < sellers.length; i++) {
|
|
411
|
+
if (i === maxRevenueIndex) {
|
|
412
|
+
nonces.push(maxRevenueNonces[0]); // 卖出交易用第一个 nonce
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
nonces.push(otherNonces[otherIdx++]);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
profitNonce = maxRevenueNonces[1]; // 利润交易用第二个 nonce
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
// 不需要利润交易,所有钱包各 1 个 nonce
|
|
422
|
+
nonces = await nonceManager.getNextNoncesForWallets(sellers);
|
|
423
|
+
}
|
|
379
424
|
const unsignedSells = await buildSellTransactions({
|
|
380
425
|
routeType,
|
|
381
426
|
params,
|
|
@@ -399,16 +444,19 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
399
444
|
value: txValue
|
|
400
445
|
});
|
|
401
446
|
}));
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
447
|
+
// ✅ 添加利润交易(使用预先分配的 profitNonce)
|
|
448
|
+
if (needProfitTx && profitNonce !== undefined) {
|
|
449
|
+
const profitTx = await sellers[maxRevenueIndex].signTransaction({
|
|
450
|
+
to: getProfitRecipient(),
|
|
451
|
+
value: totalProfit,
|
|
452
|
+
nonce: profitNonce,
|
|
453
|
+
gasPrice,
|
|
454
|
+
gasLimit: 21000n,
|
|
455
|
+
chainId,
|
|
456
|
+
type: getTxType(config)
|
|
457
|
+
});
|
|
458
|
+
signedTxs.push(profitTx);
|
|
459
|
+
}
|
|
412
460
|
nonceManager.clearTemp();
|
|
413
461
|
return {
|
|
414
462
|
signedTransactions: signedTxs
|
|
@@ -479,15 +527,31 @@ async function buildBuyTransactions({ routeType, params, proxies, wallets, token
|
|
|
479
527
|
throw new Error(`Unsupported routeType: ${routeType}`);
|
|
480
528
|
}
|
|
481
529
|
/**
|
|
482
|
-
* ✅
|
|
530
|
+
* ✅ 修复:明确分配 nonces,避免隐式状态依赖
|
|
483
531
|
*/
|
|
484
532
|
async function allocateProfitAwareNonces(wallets, extractProfit, maxIndex, totalProfit, nonceManager) {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
533
|
+
const needProfitTx = extractProfit && totalProfit > 0n && maxIndex >= 0 && maxIndex < wallets.length;
|
|
534
|
+
if (!needProfitTx) {
|
|
535
|
+
// 不需要利润交易,所有钱包各 1 个 nonce
|
|
536
|
+
return await nonceManager.getNextNoncesForWallets(wallets);
|
|
537
|
+
}
|
|
538
|
+
// 需要利润交易:maxIndex 钱包需要 2 个连续 nonce
|
|
539
|
+
const maxIndexNonces = await nonceManager.getNextNonceBatch(wallets[maxIndex], 2);
|
|
540
|
+
// 其他钱包各需要 1 个 nonce
|
|
541
|
+
const otherWallets = wallets.filter((_, i) => i !== maxIndex);
|
|
542
|
+
const otherNonces = otherWallets.length > 0
|
|
543
|
+
? await nonceManager.getNextNoncesForWallets(otherWallets)
|
|
544
|
+
: [];
|
|
545
|
+
// 组装最终的 nonces 数组(保持原顺序)
|
|
546
|
+
const nonces = [];
|
|
547
|
+
let otherIdx = 0;
|
|
548
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
549
|
+
if (i === maxIndex) {
|
|
550
|
+
nonces.push(maxIndexNonces[0]); // 买入交易用第一个 nonce
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
nonces.push(otherNonces[otherIdx++]);
|
|
554
|
+
}
|
|
491
555
|
}
|
|
492
556
|
return nonces;
|
|
493
557
|
}
|
|
@@ -641,36 +705,4 @@ async function buildSellTransactions({ routeType, params, proxies, wallets, toke
|
|
|
641
705
|
}
|
|
642
706
|
throw new Error(`Unsupported routeType: ${routeType}`);
|
|
643
707
|
}
|
|
644
|
-
|
|
645
|
-
if (!extractProfit || quotedOutputs.length === 0) {
|
|
646
|
-
return;
|
|
647
|
-
}
|
|
648
|
-
let totalProfit = 0n;
|
|
649
|
-
let maxRevenueIndex = -1;
|
|
650
|
-
let maxRevenue = 0n;
|
|
651
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
652
|
-
const quoted = quotedOutputs[i];
|
|
653
|
-
if (quoted > 0n) {
|
|
654
|
-
const { profit } = calculateProfit(quoted, config);
|
|
655
|
-
totalProfit += profit;
|
|
656
|
-
if (quoted > maxRevenue) {
|
|
657
|
-
maxRevenue = quoted;
|
|
658
|
-
maxRevenueIndex = i;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
if (totalProfit === 0n || maxRevenueIndex < 0) {
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
const profitNonce = await nonceManager.getNextNonce(wallets[maxRevenueIndex]);
|
|
666
|
-
const profitTx = await wallets[maxRevenueIndex].signTransaction({
|
|
667
|
-
to: getProfitRecipient(),
|
|
668
|
-
value: totalProfit,
|
|
669
|
-
nonce: profitNonce,
|
|
670
|
-
gasPrice,
|
|
671
|
-
gasLimit: 21000n,
|
|
672
|
-
chainId,
|
|
673
|
-
type: getTxType(config)
|
|
674
|
-
});
|
|
675
|
-
signedTxs.push(profitTx);
|
|
676
|
-
}
|
|
708
|
+
// ✅ appendSellProfitTransfer 已内联到 pancakeProxyBatchSellMerkle 中,避免 nonce 竞争问题
|
|
@@ -147,8 +147,8 @@ export async function flapBundleBuyFirstMerkle(params) {
|
|
|
147
147
|
});
|
|
148
148
|
const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
|
|
149
149
|
const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
|
|
150
|
-
// ✅
|
|
151
|
-
const [buyUnsigned, sellUnsigned,
|
|
150
|
+
// ✅ 第三批并行 - buyUnsigned、sellUnsigned、estimatedSellFunds(不含 nonce 相关操作)
|
|
151
|
+
const [buyUnsigned, sellUnsigned, estimatedSellFunds] = await Promise.all([
|
|
152
152
|
portalBuyer.swapExactInput.populateTransaction({
|
|
153
153
|
inputToken, // ✅ 使用动态输入代币
|
|
154
154
|
outputToken: tokenAddress,
|
|
@@ -164,29 +164,30 @@ export async function flapBundleBuyFirstMerkle(params) {
|
|
|
164
164
|
minOutputAmount: 0,
|
|
165
165
|
permitData: '0x'
|
|
166
166
|
}),
|
|
167
|
-
|
|
168
|
-
tokenAddress,
|
|
169
|
-
provider: chainContext.provider,
|
|
170
|
-
seller,
|
|
171
|
-
decimals: sellerInfo.decimals,
|
|
172
|
-
portalAddress: chainContext.portalAddress,
|
|
173
|
-
chainId: chainContext.chainId,
|
|
174
|
-
config,
|
|
175
|
-
nonceManager,
|
|
176
|
-
gasPrice,
|
|
177
|
-
txType
|
|
178
|
-
}),
|
|
179
|
-
estimateSellFunds(portalSeller, tokenAddress, sellAmountWei, outputToken),
|
|
180
|
-
// 预先规划 nonces(假设可能需要利润转账)
|
|
181
|
-
planNonces({
|
|
182
|
-
buyer,
|
|
183
|
-
seller,
|
|
184
|
-
approvalExists: true, // 保守估计,假设需要授权
|
|
185
|
-
extractProfit: true, // 保守估计,假设需要利润转账
|
|
186
|
-
sameAddress,
|
|
187
|
-
nonceManager
|
|
188
|
-
})
|
|
167
|
+
estimateSellFunds(portalSeller, tokenAddress, sellAmountWei, outputToken)
|
|
189
168
|
]);
|
|
169
|
+
// ✅ 修复:先构建授权交易(会消耗 nonce),再规划其他 nonce
|
|
170
|
+
const approvalTx = await buildApprovalTransaction({
|
|
171
|
+
tokenAddress,
|
|
172
|
+
provider: chainContext.provider,
|
|
173
|
+
seller,
|
|
174
|
+
decimals: sellerInfo.decimals,
|
|
175
|
+
portalAddress: chainContext.portalAddress,
|
|
176
|
+
chainId: chainContext.chainId,
|
|
177
|
+
config,
|
|
178
|
+
nonceManager,
|
|
179
|
+
gasPrice,
|
|
180
|
+
txType
|
|
181
|
+
});
|
|
182
|
+
// ✅ 修复:根据实际的授权情况规划 nonce
|
|
183
|
+
const noncePlan = await planNonces({
|
|
184
|
+
buyer,
|
|
185
|
+
seller,
|
|
186
|
+
approvalExists: approvalTx !== null, // ✅ 使用实际值
|
|
187
|
+
extractProfit: true,
|
|
188
|
+
sameAddress,
|
|
189
|
+
nonceManager
|
|
190
|
+
});
|
|
190
191
|
// ✅ 修复:基于卖出收益估算利润
|
|
191
192
|
const profitBase = estimatedSellFunds > 0n ? estimatedSellFunds : buyerFundsWei;
|
|
192
193
|
const tokenProfitAmount = calculateProfitAmount(profitBase);
|