four-flap-meme-sdk 1.3.80 → 1.3.82
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/approve-tokenmanager.d.ts +1 -0
- package/dist/contracts/tm-bundle-merkle/approve-tokenmanager.js +30 -28
- package/dist/contracts/tm-bundle-merkle/internal.d.ts +9 -1
- package/dist/contracts/tm-bundle-merkle/internal.js +53 -11
- package/dist/contracts/tm-bundle-merkle/private.d.ts +4 -0
- package/dist/contracts/tm-bundle-merkle/private.js +195 -183
- package/dist/contracts/tm-bundle-merkle/submit.d.ts +2 -2
- package/dist/contracts/tm-bundle-merkle/submit.js +18 -20
- package/dist/contracts/tm-bundle.js +224 -113
- package/dist/flap/portal-bundle-merkle/core.js +3 -3
- package/dist/flap/portal-bundle-merkle/private.js +87 -107
- package/dist/utils/airdrop-sweep.js +86 -76
- package/dist/utils/erc20.d.ts +1 -0
- package/dist/utils/erc20.js +22 -20
- package/dist/utils/lp-inspect.js +77 -65
- package/dist/utils/stealth-transfer.js +34 -37
- package/dist/utils/swap-helpers.js +8 -4
- package/package.json +1 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ethers, Wallet } from 'ethers';
|
|
2
|
-
// import { MerkleClient } from '../../clients/merkle.js';
|
|
3
2
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
4
3
|
import { ADDRESSES } from '../../utils/constants.js';
|
|
5
4
|
import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient } from './config.js';
|
|
@@ -29,6 +28,7 @@ function getGasLimit(config, defaultGas = DEFAULT_GAS_LIMIT) {
|
|
|
29
28
|
/**
|
|
30
29
|
* 私有购买(单笔)(仅签名版本 - 不依赖 Merkle)
|
|
31
30
|
* ✅ 精简版:只负责签名交易,不提交到 Merkle
|
|
31
|
+
* ✅ 优化:并行获取 gasPrice 和 nonce
|
|
32
32
|
*/
|
|
33
33
|
export async function fourPrivateBuyMerkle(params) {
|
|
34
34
|
const { privateKey, tokenAddress, funds, to, config } = params;
|
|
@@ -39,41 +39,40 @@ export async function fourPrivateBuyMerkle(params) {
|
|
|
39
39
|
});
|
|
40
40
|
const wallet = new Wallet(privateKey, provider);
|
|
41
41
|
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
42
|
-
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
43
42
|
const txType = getTxType(config);
|
|
44
43
|
const gasLimit = getGasLimit(config);
|
|
45
44
|
const originalFundsWei = ethers.parseEther(funds);
|
|
46
|
-
// ✅
|
|
45
|
+
// ✅ 利润提取配置(同步计算)
|
|
47
46
|
const extractProfit = shouldExtractProfit(config);
|
|
48
47
|
let profitWei = 0n;
|
|
49
48
|
let actualFundsWei = originalFundsWei;
|
|
50
49
|
if (extractProfit) {
|
|
51
50
|
const { profit, remaining } = calculateProfit(originalFundsWei, config);
|
|
52
51
|
profitWei = profit;
|
|
53
|
-
actualFundsWei = remaining;
|
|
52
|
+
actualFundsWei = remaining;
|
|
54
53
|
}
|
|
55
|
-
const minAmount = 0n;
|
|
56
|
-
const signedTxs = [];
|
|
57
54
|
const tm2 = new ethers.Contract(tmAddr, TM2_ABI, wallet);
|
|
58
|
-
// ✅
|
|
59
|
-
const unsigned = await
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
55
|
+
// ✅ 优化:并行获取 gasPrice、nonce 和构建未签名交易
|
|
56
|
+
const [gasPrice, currentNonce, unsigned] = await Promise.all([
|
|
57
|
+
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
58
|
+
wallet.getNonce(),
|
|
59
|
+
tm2.buyTokenAMAP.populateTransaction(0n, tokenAddress, to ?? wallet.address, actualFundsWei, 0n, { value: actualFundsWei })
|
|
60
|
+
]);
|
|
61
|
+
// ✅ 并行签名主交易和利润交易
|
|
62
|
+
const signPromises = [
|
|
63
|
+
wallet.signTransaction({
|
|
64
|
+
...unsigned,
|
|
65
|
+
from: wallet.address,
|
|
66
|
+
nonce: currentNonce,
|
|
67
|
+
gasLimit,
|
|
68
|
+
gasPrice,
|
|
69
|
+
chainId: CHAIN_ID,
|
|
70
|
+
type: txType,
|
|
71
|
+
value: actualFundsWei
|
|
72
|
+
})
|
|
73
|
+
];
|
|
75
74
|
if (extractProfit && profitWei > 0n) {
|
|
76
|
-
|
|
75
|
+
signPromises.push(wallet.signTransaction({
|
|
77
76
|
to: getProfitRecipient(),
|
|
78
77
|
value: profitWei,
|
|
79
78
|
nonce: currentNonce + 1,
|
|
@@ -81,11 +80,9 @@ export async function fourPrivateBuyMerkle(params) {
|
|
|
81
80
|
gasLimit: 21000n,
|
|
82
81
|
chainId: CHAIN_ID,
|
|
83
82
|
type: txType
|
|
84
|
-
});
|
|
85
|
-
signedTxs.push(profitTx);
|
|
83
|
+
}));
|
|
86
84
|
}
|
|
87
|
-
|
|
88
|
-
// ✅ 简化返回:只返回签名交易
|
|
85
|
+
const signedTxs = await Promise.all(signPromises);
|
|
89
86
|
return {
|
|
90
87
|
signedTransactions: signedTxs
|
|
91
88
|
};
|
|
@@ -94,6 +91,7 @@ export async function fourPrivateBuyMerkle(params) {
|
|
|
94
91
|
* 私有卖出(单笔)(仅签名版本 - 不依赖 Merkle)
|
|
95
92
|
* ✅ 精简版:只负责签名交易,不提交到 Merkle
|
|
96
93
|
* ✅ 自动检查授权,智能处理授权+卖出
|
|
94
|
+
* ✅ 优化:并行获取 gasPrice、allowance、nonce
|
|
97
95
|
*/
|
|
98
96
|
export async function fourPrivateSellMerkle(params) {
|
|
99
97
|
const { privateKey, tokenAddress, amount, minFunds, config } = params;
|
|
@@ -104,64 +102,52 @@ export async function fourPrivateSellMerkle(params) {
|
|
|
104
102
|
});
|
|
105
103
|
const wallet = new Wallet(privateKey, provider);
|
|
106
104
|
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
107
|
-
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
108
105
|
const txType = getTxType(config);
|
|
109
106
|
const sellGasLimit = getGasLimit(config);
|
|
110
107
|
const amountWei = ethers.parseUnits(amount, 18);
|
|
111
108
|
const minOut = minFunds ?? 0n;
|
|
112
|
-
// ✅ Step 1: 检查授权状态
|
|
113
109
|
const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
|
|
114
|
-
const
|
|
115
|
-
//
|
|
110
|
+
const tm2 = new ethers.Contract(tmAddr, TM2_ABI, wallet);
|
|
111
|
+
// ✅ 优化:并行获取 gasPrice、allowance、nonce 和构建未签名交易
|
|
112
|
+
const [gasPrice, currentAllowance, sellNonce, sellUnsigned] = await Promise.all([
|
|
113
|
+
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
114
|
+
tokenContract.allowance(wallet.address, tmAddr),
|
|
115
|
+
wallet.getNonce(),
|
|
116
|
+
tm2.sellToken.populateTransaction(0n, tokenAddress, amountWei, minOut)
|
|
117
|
+
]);
|
|
118
|
+
// ✅ 检查授权状态
|
|
116
119
|
const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
|
|
117
|
-
// ✅ Step 2: 如果需要授权,抛出错误提示
|
|
118
|
-
// ⚠️ SDK不再处理授权,需要前端先单独授权
|
|
119
120
|
if (currentAllowance < APPROVAL_THRESHOLD) {
|
|
120
121
|
throw new Error(`需要授权:钱包 ${wallet.address} 尚未授权。请先完成授权后再卖出。`);
|
|
121
122
|
}
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
// ✅ Step 3: 卖出交易
|
|
125
|
-
const nonceManager = new NonceManager(provider);
|
|
126
|
-
const signedTxs = [];
|
|
127
|
-
const tm2 = new ethers.Contract(tmAddr, TM2_ABI, wallet);
|
|
128
|
-
const sellUnsigned = await tm2.sellToken.populateTransaction(0n, tokenAddress, amountWei, minOut);
|
|
129
|
-
const sellNonce = await nonceManager.getNextNonce(wallet);
|
|
130
|
-
// ✅ 卖出交易 value 必须为 0,不能发送原生代币
|
|
131
|
-
const sellReq = {
|
|
132
|
-
...sellUnsigned,
|
|
133
|
-
from: wallet.address,
|
|
134
|
-
nonce: sellNonce,
|
|
135
|
-
gasLimit: sellGasLimit,
|
|
136
|
-
gasPrice,
|
|
137
|
-
chainId: CHAIN_ID,
|
|
138
|
-
type: txType,
|
|
139
|
-
value: 0n // ✅ 卖出交易不发送原生代币
|
|
140
|
-
};
|
|
141
|
-
const signedSell = await wallet.signTransaction(sellReq);
|
|
142
|
-
signedTxs.push(signedSell);
|
|
143
|
-
// ✅ 基于 minFunds 计算利润
|
|
123
|
+
// ✅ 利润计算(同步)
|
|
144
124
|
const extractProfit = shouldExtractProfit(config);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
125
|
+
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,
|
|
133
|
+
gasPrice,
|
|
134
|
+
chainId: CHAIN_ID,
|
|
135
|
+
type: txType,
|
|
136
|
+
value: 0n
|
|
137
|
+
})
|
|
138
|
+
];
|
|
139
|
+
if (extractProfit && profit > 0n) {
|
|
140
|
+
signPromises.push(wallet.signTransaction({
|
|
141
|
+
to: getProfitRecipient(),
|
|
142
|
+
value: profit,
|
|
143
|
+
nonce: sellNonce + 1,
|
|
144
|
+
gasPrice,
|
|
145
|
+
gasLimit: 21000n,
|
|
146
|
+
chainId: CHAIN_ID,
|
|
147
|
+
type: txType
|
|
148
|
+
}));
|
|
160
149
|
}
|
|
161
|
-
|
|
162
|
-
nonceManager.clearTemp();
|
|
163
|
-
// ✅ 直接返回签名交易(不提交到Merkle)
|
|
164
|
-
// ✅ 简化返回:只返回签名交易
|
|
150
|
+
const signedTxs = await Promise.all(signPromises);
|
|
165
151
|
return {
|
|
166
152
|
signedTransactions: signedTxs
|
|
167
153
|
};
|
|
@@ -169,6 +155,7 @@ export async function fourPrivateSellMerkle(params) {
|
|
|
169
155
|
/**
|
|
170
156
|
* 批量私有购买(仅签名版本 - 不依赖 Merkle)
|
|
171
157
|
* ✅ 精简版:只负责签名交易,不提交到 Merkle
|
|
158
|
+
* ✅ 优化:并行获取 gasPrice 和 nonces,批量构建交易
|
|
172
159
|
*/
|
|
173
160
|
export async function fourBatchPrivateBuyMerkle(params) {
|
|
174
161
|
const { privateKeys, fundsList, tokenAddress, config } = params;
|
|
@@ -181,18 +168,15 @@ export async function fourBatchPrivateBuyMerkle(params) {
|
|
|
181
168
|
name: 'BSC'
|
|
182
169
|
});
|
|
183
170
|
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
184
|
-
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
185
171
|
const txType = getTxType(config);
|
|
186
172
|
const finalGasLimit = getGasLimit(config);
|
|
187
|
-
const signedTxs = [];
|
|
188
|
-
const nonceManager = new NonceManager(provider);
|
|
189
173
|
const wallets = privateKeys.map((k) => new Wallet(k, provider));
|
|
190
174
|
const originalAmountsWei = fundsList.map((a) => ethers.parseEther(a));
|
|
191
|
-
// ✅
|
|
175
|
+
// ✅ 利润提取配置(同步计算)
|
|
192
176
|
const extractProfit = shouldExtractProfit(config);
|
|
193
177
|
const { totalProfit, remainingAmounts } = calculateBatchProfit(originalAmountsWei, config);
|
|
194
|
-
const actualAmountsWei = remainingAmounts;
|
|
195
|
-
// ✅
|
|
178
|
+
const actualAmountsWei = remainingAmounts;
|
|
179
|
+
// ✅ 找出投入金额最多的钱包(作为利润支付者)- 同步计算
|
|
196
180
|
let maxFundsIndex = 0;
|
|
197
181
|
let maxFunds = originalAmountsWei[0];
|
|
198
182
|
for (let i = 1; i < originalAmountsWei.length; i++) {
|
|
@@ -202,51 +186,69 @@ export async function fourBatchPrivateBuyMerkle(params) {
|
|
|
202
186
|
}
|
|
203
187
|
}
|
|
204
188
|
const tm2Contracts = wallets.map((w) => new ethers.Contract(tmAddr, TM2_ABI, w));
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
//
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
189
|
+
const nonceManager = new NonceManager(provider);
|
|
190
|
+
const needProfitTx = extractProfit && totalProfit > 0n;
|
|
191
|
+
// ✅ 优化:并行获取 gasPrice、nonces 和构建未签名交易
|
|
192
|
+
// 支付者需要 2 个 nonce,其他钱包各需要 1 个 nonce
|
|
193
|
+
const [gasPrice, unsignedList, noncesResult] = await Promise.all([
|
|
194
|
+
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
195
|
+
Promise.all(tm2Contracts.map((c, i) => c.buyTokenAMAP.populateTransaction(0n, tokenAddress, wallets[i].address, actualAmountsWei[i], 0n, { value: actualAmountsWei[i] }))),
|
|
196
|
+
(async () => {
|
|
197
|
+
if (needProfitTx) {
|
|
198
|
+
// 支付者需要 2 个连续 nonce
|
|
199
|
+
const payerNonces = await nonceManager.getNextNonceBatch(wallets[maxFundsIndex], 2);
|
|
200
|
+
// 其他钱包各需要 1 个 nonce
|
|
201
|
+
const otherWallets = wallets.filter((_, i) => i !== maxFundsIndex);
|
|
202
|
+
const otherNonces = otherWallets.length > 0
|
|
203
|
+
? await nonceManager.getNextNoncesForWallets(otherWallets)
|
|
204
|
+
: [];
|
|
205
|
+
// 组装最终的 nonces 数组(保持原顺序)
|
|
206
|
+
const nonces = [];
|
|
207
|
+
let otherIdx = 0;
|
|
208
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
209
|
+
if (i === maxFundsIndex) {
|
|
210
|
+
nonces.push(payerNonces[0]);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
nonces.push(otherNonces[otherIdx++]);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { nonces, profitNonce: payerNonces[1] };
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// 所有钱包各 1 个 nonce
|
|
220
|
+
const nonces = await nonceManager.getNextNoncesForWallets(wallets);
|
|
221
|
+
return { nonces, profitNonce: undefined };
|
|
222
|
+
}
|
|
223
|
+
})()
|
|
224
|
+
]);
|
|
225
|
+
const { nonces, profitNonce } = noncesResult;
|
|
226
|
+
// ✅ 并行签名所有买入交易
|
|
222
227
|
const signedList = await Promise.all(unsignedList.map((unsigned, i) => wallets[i].signTransaction({
|
|
223
228
|
...unsigned,
|
|
224
229
|
from: wallets[i].address,
|
|
225
230
|
nonce: nonces[i],
|
|
226
|
-
gasLimit:
|
|
231
|
+
gasLimit: finalGasLimit,
|
|
227
232
|
gasPrice,
|
|
228
233
|
chainId: CHAIN_ID,
|
|
229
234
|
type: txType,
|
|
230
|
-
value: actualAmountsWei[i]
|
|
235
|
+
value: actualAmountsWei[i]
|
|
231
236
|
})));
|
|
232
|
-
signedTxs
|
|
237
|
+
const signedTxs = [...signedList];
|
|
233
238
|
// ✅ 聚合利润:由投入金额最多的钱包支付所有利润(1笔交易)
|
|
234
|
-
if (
|
|
235
|
-
const profitNonce = nonces[maxFundsIndex] + 1;
|
|
239
|
+
if (needProfitTx && profitNonce !== undefined) {
|
|
236
240
|
const profitTx = await wallets[maxFundsIndex].signTransaction({
|
|
237
241
|
to: getProfitRecipient(),
|
|
238
242
|
value: totalProfit,
|
|
239
243
|
nonce: profitNonce,
|
|
240
244
|
gasPrice,
|
|
241
245
|
gasLimit: 21000n,
|
|
242
|
-
chainId:
|
|
243
|
-
type:
|
|
246
|
+
chainId: CHAIN_ID,
|
|
247
|
+
type: txType
|
|
244
248
|
});
|
|
245
249
|
signedTxs.push(profitTx);
|
|
246
250
|
}
|
|
247
|
-
// ✅ 清理临时 nonce 缓存
|
|
248
251
|
nonceManager.clearTemp();
|
|
249
|
-
// ✅ 简化返回:只返回签名交易
|
|
250
252
|
return {
|
|
251
253
|
signedTransactions: signedTxs
|
|
252
254
|
};
|
|
@@ -255,6 +257,7 @@ export async function fourBatchPrivateBuyMerkle(params) {
|
|
|
255
257
|
* 批量私有卖出(仅签名版本 - 不依赖 Merkle)
|
|
256
258
|
* ✅ 精简版:只负责签名交易,不提交到 Merkle
|
|
257
259
|
* ✅ 自动包含授权交易,无需前端单独调用
|
|
260
|
+
* ✅ 优化:并行获取所有查询数据,批量获取 nonces
|
|
258
261
|
*/
|
|
259
262
|
export async function fourBatchPrivateSellMerkle(params) {
|
|
260
263
|
const { privateKeys, amounts, tokenAddress, config, minFundsEach } = params;
|
|
@@ -267,73 +270,64 @@ export async function fourBatchPrivateSellMerkle(params) {
|
|
|
267
270
|
name: 'BSC'
|
|
268
271
|
});
|
|
269
272
|
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
270
|
-
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
271
273
|
const txType = getTxType(config);
|
|
272
274
|
const sellGasLimit = getGasLimit(config);
|
|
273
|
-
const signedTxs = [];
|
|
274
|
-
const nonceManager = new NonceManager(provider);
|
|
275
275
|
const wallets = privateKeys.map((k) => new Wallet(k, provider));
|
|
276
276
|
const amountsWei = amounts.map((a) => ethers.parseUnits(a, 18));
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
277
|
+
const rpcUrl = config.rpcUrl;
|
|
278
|
+
// ✅ 优化:第一批并行获取 - gasPrice、quotedOutputs、balances、allowances、bnbBalances
|
|
279
|
+
const [gasPrice, quotedOutputs, balances, allowances, bnbBalances] = await Promise.all([
|
|
280
|
+
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
281
|
+
// 获取预期收益
|
|
282
|
+
minFundsEach !== undefined
|
|
283
|
+
? Promise.resolve((() => {
|
|
284
|
+
const minOutWei = typeof minFundsEach === 'string' ? ethers.parseEther(minFundsEach) : minFundsEach;
|
|
285
|
+
return new Array(wallets.length).fill(minOutWei * 100n / 95n);
|
|
286
|
+
})())
|
|
287
|
+
: Promise.all(amountsWei.map(async (amount) => {
|
|
288
|
+
try {
|
|
289
|
+
const result = await trySell('BSC', rpcUrl, tokenAddress, amount);
|
|
290
|
+
return result.funds;
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return 0n;
|
|
294
|
+
}
|
|
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)))
|
|
305
|
+
]);
|
|
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
|
+
// ✅ 验证代币余额
|
|
304
311
|
for (let i = 0; i < wallets.length; i++) {
|
|
305
312
|
if (balances[i] < amountsWei[i]) {
|
|
306
|
-
throw new Error(`钱包 ${i}
|
|
307
|
-
`需要 ${ethers.formatUnits(amountsWei[i], 18)},` +
|
|
308
|
-
`实际 ${ethers.formatUnits(balances[i], 18)}`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
// ✅ Step 1: 检查授权状态
|
|
312
|
-
const allowances = await batchCheckAllowances(provider, tokenAddress, wallets.map(w => w.address), tmAddr);
|
|
313
|
-
const needApprovalIndexes = [];
|
|
314
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
315
|
-
if (allowances[i] < amountsWei[i]) {
|
|
316
|
-
needApprovalIndexes.push(i);
|
|
313
|
+
throw new Error(`钱包 ${i} 代币余额不足:需要 ${ethers.formatUnits(amountsWei[i], 18)},实际 ${ethers.formatUnits(balances[i], 18)}`);
|
|
317
314
|
}
|
|
318
315
|
}
|
|
316
|
+
// ✅ 验证授权状态
|
|
317
|
+
const needApprovalIndexes = wallets
|
|
318
|
+
.map((_, i) => i)
|
|
319
|
+
.filter(i => allowances[i] < amountsWei[i]);
|
|
319
320
|
if (needApprovalIndexes.length > 0) {
|
|
320
|
-
throw new Error(`${needApprovalIndexes.length}
|
|
321
|
-
`请先调用 approveFourTokenManagerBatch({ amounts: ['max', ...] })`);
|
|
321
|
+
throw new Error(`${needApprovalIndexes.length} 个钱包需要授权。请先调用 approveFourTokenManagerBatch({ amounts: ['max', ...] })`);
|
|
322
322
|
}
|
|
323
|
-
// ✅
|
|
324
|
-
const bnbBalances = await Promise.all(wallets.map(w => provider.getBalance(w.address)));
|
|
323
|
+
// ✅ 验证 BNB 余额
|
|
325
324
|
const estimatedGasCost = sellGasLimit * gasPrice;
|
|
326
325
|
for (let i = 0; i < wallets.length; i++) {
|
|
327
326
|
if (bnbBalances[i] < estimatedGasCost) {
|
|
328
|
-
throw new Error(`钱包 ${i} BNB
|
|
329
|
-
`需要 ${ethers.formatEther(estimatedGasCost)} BNB,` +
|
|
330
|
-
`实际 ${ethers.formatEther(bnbBalances[i])} BNB`);
|
|
327
|
+
throw new Error(`钱包 ${i} BNB 不足:需要 ${ethers.formatEther(estimatedGasCost)} BNB,实际 ${ethers.formatEther(bnbBalances[i])} BNB`);
|
|
331
328
|
}
|
|
332
329
|
}
|
|
333
|
-
// ✅
|
|
334
|
-
const tm2Contracts = wallets.map((w) => new ethers.Contract(tmAddr, TM2_ABI, w));
|
|
335
|
-
const sellUnsigned = await Promise.all(tm2Contracts.map((c, i) => c.sellToken.populateTransaction(0n, tokenAddress, amountsWei[i], minOuts[i])));
|
|
336
|
-
// ✅ 计算总利润和找出收益最高的钱包
|
|
330
|
+
// ✅ 计算总利润和找出收益最高的钱包(同步计算)
|
|
337
331
|
const extractProfit = shouldExtractProfit(config);
|
|
338
332
|
let totalProfit = 0n;
|
|
339
333
|
let maxRevenueIndex = 0;
|
|
@@ -350,37 +344,56 @@ export async function fourBatchPrivateSellMerkle(params) {
|
|
|
350
344
|
}
|
|
351
345
|
}
|
|
352
346
|
}
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
347
|
+
const tm2Contracts = wallets.map((w) => new ethers.Contract(tmAddr, TM2_ABI, w));
|
|
348
|
+
const nonceManager = new NonceManager(provider);
|
|
349
|
+
const needProfitTx = extractProfit && totalProfit > 0n;
|
|
350
|
+
// ✅ 优化:第二批并行获取 - nonces 和构建未签名交易
|
|
351
|
+
const [sellUnsigned, noncesResult] = await Promise.all([
|
|
352
|
+
Promise.all(tm2Contracts.map((c, i) => c.sellToken.populateTransaction(0n, tokenAddress, amountsWei[i], minOuts[i]))),
|
|
353
|
+
(async () => {
|
|
354
|
+
if (needProfitTx) {
|
|
355
|
+
// 支付者需要 2 个连续 nonce
|
|
356
|
+
const payerNonces = await nonceManager.getNextNonceBatch(wallets[maxRevenueIndex], 2);
|
|
357
|
+
// 其他钱包各需要 1 个 nonce
|
|
358
|
+
const otherWallets = wallets.filter((_, i) => i !== maxRevenueIndex);
|
|
359
|
+
const otherNonces = otherWallets.length > 0
|
|
360
|
+
? await nonceManager.getNextNoncesForWallets(otherWallets)
|
|
361
|
+
: [];
|
|
362
|
+
// 组装最终的 nonces 数组(保持原顺序)
|
|
363
|
+
const nonces = [];
|
|
364
|
+
let otherIdx = 0;
|
|
365
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
366
|
+
if (i === maxRevenueIndex) {
|
|
367
|
+
nonces.push(payerNonces[0]);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
nonces.push(otherNonces[otherIdx++]);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return { nonces, profitNonce: payerNonces[1] };
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
// 所有钱包各 1 个 nonce
|
|
377
|
+
const nonces = await nonceManager.getNextNoncesForWallets(wallets);
|
|
378
|
+
return { nonces, profitNonce: undefined };
|
|
379
|
+
}
|
|
380
|
+
})()
|
|
381
|
+
]);
|
|
382
|
+
const { nonces, profitNonce } = noncesResult;
|
|
383
|
+
// ✅ 并行签名所有卖出交易
|
|
370
384
|
const signedSells = await Promise.all(sellUnsigned.map((unsigned, i) => wallets[i].signTransaction({
|
|
371
385
|
...unsigned,
|
|
372
386
|
from: wallets[i].address,
|
|
373
|
-
nonce:
|
|
374
|
-
gasLimit:
|
|
387
|
+
nonce: nonces[i],
|
|
388
|
+
gasLimit: sellGasLimit,
|
|
375
389
|
gasPrice,
|
|
376
390
|
chainId: CHAIN_ID,
|
|
377
391
|
type: txType,
|
|
378
|
-
value: 0n
|
|
392
|
+
value: 0n
|
|
379
393
|
})));
|
|
380
|
-
signedTxs
|
|
394
|
+
const signedTxs = [...signedSells];
|
|
381
395
|
// ✅ 聚合利润:由收益最高的钱包支付所有利润(1笔交易)
|
|
382
|
-
if (
|
|
383
|
-
const profitNonce = sellNonces[maxRevenueIndex] + 1;
|
|
396
|
+
if (needProfitTx && profitNonce !== undefined) {
|
|
384
397
|
const profitTx = await wallets[maxRevenueIndex].signTransaction({
|
|
385
398
|
to: getProfitRecipient(),
|
|
386
399
|
value: totalProfit,
|
|
@@ -393,7 +406,6 @@ export async function fourBatchPrivateSellMerkle(params) {
|
|
|
393
406
|
signedTxs.push(profitTx);
|
|
394
407
|
}
|
|
395
408
|
nonceManager.clearTemp();
|
|
396
|
-
// ✅ 简化返回:只返回签名交易
|
|
397
409
|
return {
|
|
398
410
|
signedTransactions: signedTxs
|
|
399
411
|
};
|
|
@@ -224,9 +224,9 @@ export interface DirectSubmitResult {
|
|
|
224
224
|
errorSummary?: string;
|
|
225
225
|
}
|
|
226
226
|
/**
|
|
227
|
-
*
|
|
227
|
+
* 并行广播到 RPC(用于不支持 Bundle 的链,如 Monad)
|
|
228
228
|
*
|
|
229
|
-
*
|
|
229
|
+
* ✅ 优化:默认使用并行广播,速度更快
|
|
230
230
|
*
|
|
231
231
|
* @param signedTransactions 签名后的交易数组
|
|
232
232
|
* @param config 直接广播配置
|
|
@@ -251,9 +251,9 @@ export async function submitMultipleBundlesToBlockRazorParallel(bundles, config)
|
|
|
251
251
|
return await Promise.all(promises);
|
|
252
252
|
}
|
|
253
253
|
/**
|
|
254
|
-
*
|
|
254
|
+
* 并行广播到 RPC(用于不支持 Bundle 的链,如 Monad)
|
|
255
255
|
*
|
|
256
|
-
*
|
|
256
|
+
* ✅ 优化:默认使用并行广播,速度更快
|
|
257
257
|
*
|
|
258
258
|
* @param signedTransactions 签名后的交易数组
|
|
259
259
|
* @param config 直接广播配置
|
|
@@ -308,21 +308,10 @@ export async function submitDirectToRpc(signedTransactions, config) {
|
|
|
308
308
|
chainId,
|
|
309
309
|
name: chainName
|
|
310
310
|
});
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
const errors = [];
|
|
314
|
-
// 逐笔广播
|
|
315
|
-
for (let i = 0; i < signedTransactions.length; i++) {
|
|
316
|
-
const signedTx = signedTransactions[i];
|
|
311
|
+
// ✅ 并行广播所有交易
|
|
312
|
+
const broadcastPromises = signedTransactions.map(async (signedTx, i) => {
|
|
317
313
|
try {
|
|
318
|
-
// 广播交易
|
|
319
314
|
const txResponse = await provider.broadcastTransaction(signedTx);
|
|
320
|
-
results.push({
|
|
321
|
-
index: i,
|
|
322
|
-
success: true,
|
|
323
|
-
txHash: txResponse.hash
|
|
324
|
-
});
|
|
325
|
-
txHashes.push(txResponse.hash);
|
|
326
315
|
// 如果需要等待确认
|
|
327
316
|
if (config.waitForConfirmation) {
|
|
328
317
|
try {
|
|
@@ -335,18 +324,27 @@ export async function submitDirectToRpc(signedTransactions, config) {
|
|
|
335
324
|
console.warn(`⚠️ [${chainName}] 等待交易确认超时: ${txResponse.hash}`);
|
|
336
325
|
}
|
|
337
326
|
}
|
|
327
|
+
return {
|
|
328
|
+
index: i,
|
|
329
|
+
success: true,
|
|
330
|
+
txHash: txResponse.hash
|
|
331
|
+
};
|
|
338
332
|
}
|
|
339
333
|
catch (error) {
|
|
340
334
|
const errorMessage = error?.message || String(error);
|
|
341
|
-
|
|
335
|
+
console.error(`❌ [${chainName}] 交易 ${i + 1}/${totalTransactions} 广播失败:`, errorMessage);
|
|
336
|
+
return {
|
|
342
337
|
index: i,
|
|
343
338
|
success: false,
|
|
344
339
|
error: errorMessage
|
|
345
|
-
}
|
|
346
|
-
errors.push(`交易 ${i + 1}: ${errorMessage}`);
|
|
347
|
-
console.error(`❌ [${chainName}] 交易 ${i + 1}/${totalTransactions} 广播失败:`, errorMessage);
|
|
340
|
+
};
|
|
348
341
|
}
|
|
349
|
-
}
|
|
342
|
+
});
|
|
343
|
+
const results = await Promise.all(broadcastPromises);
|
|
344
|
+
// 按索引排序结果
|
|
345
|
+
results.sort((a, b) => a.index - b.index);
|
|
346
|
+
const txHashes = results.filter(r => r.success && r.txHash).map(r => r.txHash);
|
|
347
|
+
const errors = results.filter(r => !r.success).map(r => `交易 ${r.index + 1}: ${r.error}`);
|
|
350
348
|
const successCount = txHashes.length;
|
|
351
349
|
const failedCount = totalTransactions - successCount;
|
|
352
350
|
return {
|