four-flap-meme-sdk 1.3.99 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contracts/tm-bundle-merkle/core.js +4 -23
- package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +50 -60
- package/dist/contracts/tm-bundle-merkle/private.js +151 -96
- package/dist/dex/direct-router.d.ts +4 -0
- package/dist/dex/direct-router.js +23 -19
- package/dist/flap/portal-bundle-merkle/pancake-proxy.js +58 -71
- package/dist/flap/portal-bundle-merkle/private.js +185 -72
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
|
351
|
-
const [sellOutputs, ,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
372
|
+
// ✅ 并行签名所有交易(贿赂、买入、利润)
|
|
373
|
+
const signPromises = [];
|
|
374
|
+
// 贿赂交易(索引 0)
|
|
374
375
|
if (needBribeTx && bribeNonce !== undefined) {
|
|
375
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
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
|
|
444
|
-
const [gasPrice, tokenDecimals
|
|
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
|
|
568
|
+
// ✅ 并行签名所有交易(贿赂、卖出、利润)
|
|
569
|
+
const signPromises = [];
|
|
570
|
+
// 贿赂交易(索引 0)
|
|
582
571
|
if (needBribeTx && bribeNonce !== undefined) {
|
|
583
|
-
|
|
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
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
-
|
|
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
|
|
5
|
-
|
|
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,
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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:
|
|
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、
|
|
112
|
-
const [gasPrice,
|
|
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
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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:
|
|
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 (
|
|
198
|
-
//
|
|
199
|
-
const payerNonces = await nonceManager.getNextNonceBatch(wallets[maxFundsIndex],
|
|
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[
|
|
259
|
+
nonces.push(payerNonces[nonceIdx++]); // 买入交易
|
|
211
260
|
}
|
|
212
261
|
else {
|
|
213
262
|
nonces.push(otherNonces[otherIdx++]);
|
|
214
263
|
}
|
|
215
264
|
}
|
|
216
|
-
|
|
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
|
-
|
|
238
|
-
// ✅
|
|
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
|
-
// ✅
|
|
279
|
-
const [gasPrice, quotedOutputs
|
|
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
|
-
//
|
|
307
|
-
const minOuts =
|
|
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 (
|
|
355
|
-
//
|
|
356
|
-
const payerNonces = await nonceManager.getNextNonceBatch(wallets[maxRevenueIndex],
|
|
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[
|
|
407
|
+
nonces.push(payerNonces[nonceIdx++]); // 卖出交易
|
|
368
408
|
}
|
|
369
409
|
else {
|
|
370
410
|
nonces.push(otherNonces[otherIdx++]);
|
|
371
411
|
}
|
|
372
412
|
}
|
|
373
|
-
|
|
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
|
-
|
|
395
|
-
// ✅
|
|
449
|
+
signedTxs.push(...signedSells);
|
|
450
|
+
// ✅ 利润交易放在末尾
|
|
396
451
|
if (needProfitTx && profitNonce !== undefined) {
|
|
397
452
|
const profitTx = await wallets[maxRevenueIndex].signTransaction({
|
|
398
453
|
to: getProfitRecipient(),
|
|
@@ -55,6 +55,7 @@ export interface DirectV2BuyParams {
|
|
|
55
55
|
routerAddress: string;
|
|
56
56
|
quoteToken?: string;
|
|
57
57
|
quoteTokenDecimals?: number;
|
|
58
|
+
startNonces?: number[];
|
|
58
59
|
config: DirectRouterSignConfig;
|
|
59
60
|
}
|
|
60
61
|
export interface DirectV2SellParams {
|
|
@@ -62,6 +63,7 @@ export interface DirectV2SellParams {
|
|
|
62
63
|
privateKeys: string[];
|
|
63
64
|
sellPercentages?: number[];
|
|
64
65
|
sellAmounts?: string[];
|
|
66
|
+
startNonces?: number[];
|
|
65
67
|
tokenAddress: string;
|
|
66
68
|
tokenDecimals?: number;
|
|
67
69
|
routerAddress: string;
|
|
@@ -77,6 +79,7 @@ export interface DirectV3BuyParams {
|
|
|
77
79
|
fee: number;
|
|
78
80
|
quoteToken?: string;
|
|
79
81
|
quoteTokenDecimals?: number;
|
|
82
|
+
startNonces?: number[];
|
|
80
83
|
config: DirectRouterSignConfig;
|
|
81
84
|
}
|
|
82
85
|
export interface DirectV3SellParams {
|
|
@@ -89,6 +92,7 @@ export interface DirectV3SellParams {
|
|
|
89
92
|
routerAddress: string;
|
|
90
93
|
fee: number;
|
|
91
94
|
quoteToken?: string;
|
|
95
|
+
startNonces?: number[];
|
|
92
96
|
config: DirectRouterSignConfig;
|
|
93
97
|
}
|
|
94
98
|
export interface DirectRouterResult {
|