four-flap-meme-sdk 1.1.82 → 1.1.83
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/pancake-proxy.js +116 -11
- package/dist/contracts/tm-bundle-merkle/private.js +139 -11
- package/dist/flap/portal-bundle-merkle/config.d.ts +29 -0
- package/dist/flap/portal-bundle-merkle/config.js +47 -0
- package/dist/flap/portal-bundle-merkle/pancake-proxy.js +116 -11
- package/dist/flap/portal-bundle-merkle/private.js +148 -13
- package/dist/flap/portal-bundle-merkle/types.d.ts +3 -0
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import { ethers, Wallet, JsonRpcProvider, Contract } from 'ethers';
|
|
|
2
2
|
import { MerkleClient } from '../../clients/merkle.js';
|
|
3
3
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
4
4
|
import { ADDRESSES } from '../../utils/constants.js';
|
|
5
|
-
import { getTxType, getBundleOptions, getGasPriceConfig } from './config.js';
|
|
5
|
+
import { getTxType, getBundleOptions, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit } from './config.js';
|
|
6
6
|
// 常量
|
|
7
7
|
const WBNB_ADDRESS = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
|
|
8
8
|
const FLAT_FEE = ethers.parseEther('0.0001'); // 合约固定手续费
|
|
@@ -262,7 +262,11 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
|
|
|
262
262
|
const blockOffset = config.bundleBlockOffset ?? 2;
|
|
263
263
|
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
264
264
|
const buyers = privateKeys.map(k => new Wallet(k, provider));
|
|
265
|
-
const
|
|
265
|
+
const originalAmountsWei = buyAmounts.map(a => ethers.parseEther(a)); // BNB 固定 18 位
|
|
266
|
+
// ✅ 利润提取配置
|
|
267
|
+
const extractProfit = shouldExtractProfit(config);
|
|
268
|
+
const { totalProfit, remainingAmounts } = calculateBatchProfit(originalAmountsWei, config);
|
|
269
|
+
const actualAmountsWei = remainingAmounts; // 扣除利润后用于购买的金额
|
|
266
270
|
// 查询 tokenOut decimals
|
|
267
271
|
const tokenDecimals = await getTokenDecimals(tokenAddress, provider);
|
|
268
272
|
// 计算 minOutputAmounts
|
|
@@ -275,34 +279,80 @@ export async function fourPancakeProxyBatchBuyMerkle(params) {
|
|
|
275
279
|
}
|
|
276
280
|
// 判断是否需要发送 BNB
|
|
277
281
|
const needBNB = needSendBNB(routeType, params);
|
|
278
|
-
//
|
|
282
|
+
// 构建交易(使用扣除利润后的金额)
|
|
279
283
|
const proxies = buyers.map(w => new Contract(pancakeProxyAddress, PANCAKE_PROXY_ABI, w));
|
|
280
284
|
let unsignedBuys;
|
|
281
285
|
if (routeType === 'v2') {
|
|
282
286
|
if (!params.v2Path || params.v2Path.length < 2) {
|
|
283
287
|
throw new Error('v2Path is required for V2 routing');
|
|
284
288
|
}
|
|
285
|
-
unsignedBuys = await buildV2Transactions(proxies, buyers,
|
|
289
|
+
unsignedBuys = await buildV2Transactions(proxies, buyers, actualAmountsWei, minOuts, params.v2Path, true, needBNB);
|
|
286
290
|
}
|
|
287
291
|
else if (routeType === 'v3-single') {
|
|
288
292
|
if (!params.v3TokenIn || !params.v3Fee) {
|
|
289
293
|
throw new Error('v3TokenIn and v3Fee are required for V3 single-hop');
|
|
290
294
|
}
|
|
291
|
-
unsignedBuys = await buildV3SingleTransactions(proxies, buyers, params.v3TokenIn, tokenAddress, params.v3Fee,
|
|
295
|
+
unsignedBuys = await buildV3SingleTransactions(proxies, buyers, params.v3TokenIn, tokenAddress, params.v3Fee, actualAmountsWei, minOuts, true, needBNB);
|
|
292
296
|
}
|
|
293
297
|
else if (routeType === 'v3-multi') {
|
|
294
298
|
if (!params.v3LpAddresses || params.v3LpAddresses.length === 0 || !params.v3ExactTokenIn) {
|
|
295
299
|
throw new Error('v3LpAddresses and v3ExactTokenIn are required for V3 multi-hop');
|
|
296
300
|
}
|
|
297
|
-
unsignedBuys = await buildV3MultiHopTransactions(proxies, buyers, params.v3LpAddresses, params.v3ExactTokenIn,
|
|
301
|
+
unsignedBuys = await buildV3MultiHopTransactions(proxies, buyers, params.v3LpAddresses, params.v3ExactTokenIn, actualAmountsWei, minOuts, true, needBNB);
|
|
298
302
|
}
|
|
299
303
|
else {
|
|
300
304
|
throw new Error(`Unsupported routeType: ${routeType}`);
|
|
301
305
|
}
|
|
302
306
|
// ✅ 使用前端传入的 gasLimit,否则使用默认值
|
|
303
307
|
const finalGasLimit = getGasLimit(config);
|
|
304
|
-
//
|
|
305
|
-
const
|
|
308
|
+
// ✅ 直接签名和提交(内联处理以支持利润转账)
|
|
309
|
+
const nonceManager = new NonceManager(provider);
|
|
310
|
+
const nonces = await Promise.all(buyers.map(w => nonceManager.getNextNonce(w)));
|
|
311
|
+
const signedTxs = await Promise.all(unsignedBuys.map((unsigned, i) => buyers[i].signTransaction({
|
|
312
|
+
...unsigned,
|
|
313
|
+
from: buyers[i].address,
|
|
314
|
+
nonce: nonces[i],
|
|
315
|
+
gasLimit: finalGasLimit,
|
|
316
|
+
gasPrice,
|
|
317
|
+
chainId: 56,
|
|
318
|
+
type: getTxType(config),
|
|
319
|
+
value: unsigned.value // ✅ 显式保留 value(BNB 金额 + 手续费)
|
|
320
|
+
})));
|
|
321
|
+
// ✅ 添加利润转账(从第一个钱包转出总利润)
|
|
322
|
+
if (extractProfit && totalProfit > 0n) {
|
|
323
|
+
const profitNonce = await nonceManager.getNextNonce(buyers[0]);
|
|
324
|
+
const profitTx = await buyers[0].signTransaction({
|
|
325
|
+
to: config.profitRecipient,
|
|
326
|
+
value: totalProfit,
|
|
327
|
+
nonce: profitNonce,
|
|
328
|
+
gasPrice,
|
|
329
|
+
gasLimit: 21000n,
|
|
330
|
+
chainId: 56,
|
|
331
|
+
type: getTxType(config)
|
|
332
|
+
});
|
|
333
|
+
signedTxs.push(profitTx);
|
|
334
|
+
}
|
|
335
|
+
// ✅ 如果配置了 skipSubmit,直接返回
|
|
336
|
+
if (config.skipSubmit) {
|
|
337
|
+
nonceManager.clearTemp();
|
|
338
|
+
return {
|
|
339
|
+
bundleHash: '',
|
|
340
|
+
status: {
|
|
341
|
+
bundleHash: '',
|
|
342
|
+
txHashes: [],
|
|
343
|
+
results: [],
|
|
344
|
+
successCount: 0,
|
|
345
|
+
totalGasUsed: 0n,
|
|
346
|
+
targetBlock: 0
|
|
347
|
+
},
|
|
348
|
+
buyTxs: signedTxs
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
const bundleResult = await merkle.sendBundle({
|
|
352
|
+
transactions: signedTxs,
|
|
353
|
+
...getBundleOptions(config, blockOffset)
|
|
354
|
+
});
|
|
355
|
+
nonceManager.clearTemp();
|
|
306
356
|
const status = await waitForBundleResult(merkle, bundleResult, config.waitForConfirmation ?? false, config.waitTimeoutMs ?? 120000);
|
|
307
357
|
return {
|
|
308
358
|
bundleHash: bundleResult.bundleHash,
|
|
@@ -478,11 +528,66 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
|
|
|
478
528
|
}
|
|
479
529
|
// ✅ 使用前端传入的 gasLimit,否则使用默认值
|
|
480
530
|
const finalGasLimit = getGasLimit(config);
|
|
481
|
-
//
|
|
482
|
-
console.log('🚀
|
|
531
|
+
// ✅ 直接签名和提交(内联处理以支持利润转账)
|
|
532
|
+
console.log('🚀 签名卖出交易...\n');
|
|
533
|
+
const nonceManager = new NonceManager(provider);
|
|
534
|
+
const nonces = await Promise.all(sellers.map(w => nonceManager.getNextNonce(w)));
|
|
535
|
+
const signedTxs = await Promise.all(unsignedSells.map((unsigned, i) => sellers[i].signTransaction({
|
|
536
|
+
...unsigned,
|
|
537
|
+
from: sellers[i].address,
|
|
538
|
+
nonce: nonces[i],
|
|
539
|
+
gasLimit: finalGasLimit,
|
|
540
|
+
gasPrice,
|
|
541
|
+
chainId: 56,
|
|
542
|
+
type: getTxType(config),
|
|
543
|
+
value: unsigned.value // ✅ 显式保留 value(手续费)
|
|
544
|
+
})));
|
|
545
|
+
// ✅ 基于 minFunds 为每个钱包添加利润转账(如果设置且 > 0)
|
|
546
|
+
const extractProfit = shouldExtractProfit(config);
|
|
547
|
+
if (extractProfit && minOuts.length > 0) {
|
|
548
|
+
// 为每个有 minOut > 0 的钱包添加利润转账
|
|
549
|
+
for (let i = 0; i < sellers.length; i++) {
|
|
550
|
+
if (minOuts[i] > 0n) {
|
|
551
|
+
const { profit } = calculateProfit(minOuts[i], config);
|
|
552
|
+
if (profit > 0n) {
|
|
553
|
+
const profitNonce = await nonceManager.getNextNonce(sellers[i]);
|
|
554
|
+
const profitTx = await sellers[i].signTransaction({
|
|
555
|
+
to: config.profitRecipient,
|
|
556
|
+
value: profit,
|
|
557
|
+
nonce: profitNonce,
|
|
558
|
+
gasPrice,
|
|
559
|
+
gasLimit: 21000n,
|
|
560
|
+
chainId: 56,
|
|
561
|
+
type: getTxType(config)
|
|
562
|
+
});
|
|
563
|
+
signedTxs.push(profitTx);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
nonceManager.clearTemp();
|
|
569
|
+
// ✅ 如果配置了 skipSubmit,直接返回
|
|
570
|
+
if (config.skipSubmit) {
|
|
571
|
+
return {
|
|
572
|
+
bundleHash: '',
|
|
573
|
+
status: {
|
|
574
|
+
bundleHash: '',
|
|
575
|
+
txHashes: [],
|
|
576
|
+
results: [],
|
|
577
|
+
successCount: 0,
|
|
578
|
+
totalGasUsed: 0n,
|
|
579
|
+
targetBlock: 0
|
|
580
|
+
},
|
|
581
|
+
sellTxs: signedTxs
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
console.log('🚀 提交卖出 Bundle...\n');
|
|
483
585
|
let bundleResult;
|
|
484
586
|
try {
|
|
485
|
-
bundleResult = await
|
|
587
|
+
bundleResult = await merkle.sendBundle({
|
|
588
|
+
transactions: signedTxs,
|
|
589
|
+
...getBundleOptions(config, blockOffset)
|
|
590
|
+
});
|
|
486
591
|
console.log(` ✅ Bundle 已提交: ${bundleResult.bundleHash}`);
|
|
487
592
|
}
|
|
488
593
|
catch (error) {
|
|
@@ -2,7 +2,7 @@ import { ethers, Wallet } from 'ethers';
|
|
|
2
2
|
import { MerkleClient } from '../../clients/merkle.js';
|
|
3
3
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
4
4
|
import { ADDRESSES } from '../../utils/constants.js';
|
|
5
|
-
import { getTxType, getBundleOptions, getGasPriceConfig } from './config.js';
|
|
5
|
+
import { getTxType, getBundleOptions, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit } from './config.js';
|
|
6
6
|
// ==================== TokenManager2 ABI(仅需要的方法)====================
|
|
7
7
|
const TM2_ABI = [
|
|
8
8
|
'function buyTokenAMAP(uint256 origin, address token, address to, uint256 funds, uint256 minAmount) payable',
|
|
@@ -23,25 +23,61 @@ export async function fourPrivateBuyMerkle(params) {
|
|
|
23
23
|
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
24
24
|
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
25
25
|
const gasMultiplier = config.gasLimitMultiplier ?? 1.0;
|
|
26
|
-
const
|
|
26
|
+
const originalFundsWei = ethers.parseEther(funds);
|
|
27
|
+
// ✅ 利润提取配置
|
|
28
|
+
const extractProfit = shouldExtractProfit(config);
|
|
29
|
+
let profitWei = 0n;
|
|
30
|
+
let actualFundsWei = originalFundsWei;
|
|
31
|
+
if (extractProfit) {
|
|
32
|
+
const { profit, remaining } = calculateProfit(originalFundsWei, config);
|
|
33
|
+
profitWei = profit;
|
|
34
|
+
actualFundsWei = remaining; // ✅ 扣除利润后的金额用于购买
|
|
35
|
+
}
|
|
27
36
|
const minAmount = 0n;
|
|
37
|
+
const signedTxs = [];
|
|
28
38
|
const tm2 = new ethers.Contract(tmAddr, TM2_ABI, wallet);
|
|
29
39
|
// ✅ 使用 TokenManager2 原始方法:buyTokenAMAP
|
|
30
|
-
const unsigned = await tm2.buyTokenAMAP.populateTransaction(0n, tokenAddress, to ?? wallet.address,
|
|
40
|
+
const unsigned = await tm2.buyTokenAMAP.populateTransaction(0n, tokenAddress, to ?? wallet.address, actualFundsWei, minAmount, { value: actualFundsWei } // ✅ 使用扣除利润后的金额
|
|
41
|
+
);
|
|
31
42
|
const gasLimit = BigInt(Math.ceil(800000 * gasMultiplier));
|
|
43
|
+
let currentNonce = await wallet.getNonce();
|
|
32
44
|
const req = {
|
|
33
45
|
...unsigned,
|
|
34
46
|
from: wallet.address,
|
|
35
|
-
nonce:
|
|
47
|
+
nonce: currentNonce,
|
|
36
48
|
gasLimit: gasLimit,
|
|
37
49
|
gasPrice,
|
|
38
50
|
chainId: 56,
|
|
39
51
|
type: getTxType(config),
|
|
40
|
-
value:
|
|
52
|
+
value: actualFundsWei // ✅ funds = msg.value(合约要求两者相等)
|
|
41
53
|
};
|
|
42
54
|
const signed = await wallet.signTransaction(req);
|
|
55
|
+
signedTxs.push(signed);
|
|
56
|
+
// ✅ 添加利润转账
|
|
57
|
+
if (extractProfit && profitWei > 0n) {
|
|
58
|
+
const profitTx = await wallet.signTransaction({
|
|
59
|
+
to: config.profitRecipient,
|
|
60
|
+
value: profitWei,
|
|
61
|
+
nonce: currentNonce + 1,
|
|
62
|
+
gasPrice,
|
|
63
|
+
gasLimit: 21000n,
|
|
64
|
+
chainId: 56,
|
|
65
|
+
type: getTxType(config)
|
|
66
|
+
});
|
|
67
|
+
signedTxs.push(profitTx);
|
|
68
|
+
}
|
|
69
|
+
// ✅ 如果配置了 skipSubmit,直接返回
|
|
70
|
+
if (config.skipSubmit) {
|
|
71
|
+
return {
|
|
72
|
+
txHash: '',
|
|
73
|
+
success: false,
|
|
74
|
+
blockNumber: undefined,
|
|
75
|
+
gasUsed: undefined,
|
|
76
|
+
error: undefined
|
|
77
|
+
};
|
|
78
|
+
}
|
|
43
79
|
const bundleResult = await merkle.sendBundle({
|
|
44
|
-
transactions:
|
|
80
|
+
transactions: signedTxs,
|
|
45
81
|
...getBundleOptions(config)
|
|
46
82
|
});
|
|
47
83
|
const waitForConfirmation = config.waitForConfirmation ?? false;
|
|
@@ -129,6 +165,7 @@ export async function fourPrivateSellMerkle(params) {
|
|
|
129
165
|
// ✅ Step 3: 卖出交易
|
|
130
166
|
console.log('💰 提交卖出交易...');
|
|
131
167
|
const nonceManager = new NonceManager(provider);
|
|
168
|
+
const signedTxs = [];
|
|
132
169
|
const tm2 = new ethers.Contract(tmAddr, TM2_ABI, wallet);
|
|
133
170
|
const sellUnsigned = await tm2.sellToken.populateTransaction(0n, tokenAddress, amountWei, minOut);
|
|
134
171
|
const sellNonce = await nonceManager.getNextNonce(wallet);
|
|
@@ -143,9 +180,38 @@ export async function fourPrivateSellMerkle(params) {
|
|
|
143
180
|
type: getTxType(config)
|
|
144
181
|
};
|
|
145
182
|
const signedSell = await wallet.signTransaction(sellReq);
|
|
183
|
+
signedTxs.push(signedSell);
|
|
184
|
+
// ✅ 基于 minFunds 计算利润(如果设置且 > 0)
|
|
185
|
+
const extractProfit = shouldExtractProfit(config);
|
|
186
|
+
if (extractProfit && minOut > 0n) {
|
|
187
|
+
const { profit } = calculateProfit(minOut, config);
|
|
188
|
+
if (profit > 0n) {
|
|
189
|
+
const profitNonce = await nonceManager.getNextNonce(wallet);
|
|
190
|
+
const profitTx = await wallet.signTransaction({
|
|
191
|
+
to: config.profitRecipient,
|
|
192
|
+
value: profit,
|
|
193
|
+
nonce: profitNonce,
|
|
194
|
+
gasPrice,
|
|
195
|
+
gasLimit: 21000n,
|
|
196
|
+
chainId: 56,
|
|
197
|
+
type: getTxType(config)
|
|
198
|
+
});
|
|
199
|
+
signedTxs.push(profitTx);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
146
202
|
nonceManager.clearTemp();
|
|
203
|
+
// ✅ 如果配置了 skipSubmit,直接返回(暂不支持,因为卖出方法需要等待确认)
|
|
204
|
+
if (config.skipSubmit) {
|
|
205
|
+
return {
|
|
206
|
+
txHash: '',
|
|
207
|
+
success: false,
|
|
208
|
+
blockNumber: undefined,
|
|
209
|
+
gasUsed: undefined,
|
|
210
|
+
error: undefined
|
|
211
|
+
};
|
|
212
|
+
}
|
|
147
213
|
const bundleResult = await merkle.sendBundle({
|
|
148
|
-
transactions:
|
|
214
|
+
transactions: signedTxs,
|
|
149
215
|
...getBundleOptions(config)
|
|
150
216
|
});
|
|
151
217
|
const waitForConfirmation = config.waitForConfirmation ?? false;
|
|
@@ -188,10 +254,14 @@ export async function fourBatchPrivateBuyMerkle(params) {
|
|
|
188
254
|
const signedTxs = [];
|
|
189
255
|
const nonceManager = new NonceManager(provider);
|
|
190
256
|
const wallets = privateKeys.map((k) => new Wallet(k, provider));
|
|
191
|
-
const
|
|
257
|
+
const originalAmountsWei = fundsList.map((a) => ethers.parseEther(a));
|
|
258
|
+
// ✅ 利润提取配置
|
|
259
|
+
const extractProfit = shouldExtractProfit(config);
|
|
260
|
+
const { totalProfit, remainingAmounts } = calculateBatchProfit(originalAmountsWei, config);
|
|
261
|
+
const actualAmountsWei = remainingAmounts; // 扣除利润后用于购买的金额
|
|
192
262
|
const tm2Contracts = wallets.map((w) => new ethers.Contract(tmAddr, TM2_ABI, w));
|
|
193
|
-
// ✅ 使用 TokenManager2 原始方法:buyTokenAMAP
|
|
194
|
-
const unsignedList = await Promise.all(tm2Contracts.map((c, i) => c.buyTokenAMAP.populateTransaction(0n, tokenAddress, wallets[i].address,
|
|
263
|
+
// ✅ 使用 TokenManager2 原始方法:buyTokenAMAP(使用扣除利润后的金额)
|
|
264
|
+
const unsignedList = await Promise.all(tm2Contracts.map((c, i) => c.buyTokenAMAP.populateTransaction(0n, tokenAddress, wallets[i].address, actualAmountsWei[i], 0n, { value: actualAmountsWei[i] })));
|
|
195
265
|
const finalGasLimit = BigInt(Math.ceil(800000 * gasMultiplier));
|
|
196
266
|
const gasLimits = new Array(wallets.length).fill(finalGasLimit);
|
|
197
267
|
const nonces = await Promise.all(wallets.map((w) => nonceManager.getNextNonce(w)));
|
|
@@ -203,9 +273,36 @@ export async function fourBatchPrivateBuyMerkle(params) {
|
|
|
203
273
|
gasPrice,
|
|
204
274
|
chainId: 56,
|
|
205
275
|
type: getTxType(config),
|
|
206
|
-
value:
|
|
276
|
+
value: actualAmountsWei[i] // ✅ funds = msg.value(合约要求两者相等)
|
|
207
277
|
})));
|
|
208
278
|
signedTxs.push(...signedList);
|
|
279
|
+
// ✅ 添加利润转账(从第一个钱包转出总利润)
|
|
280
|
+
if (extractProfit && totalProfit > 0n) {
|
|
281
|
+
const profitNonce = await nonceManager.getNextNonce(wallets[0]);
|
|
282
|
+
const profitTx = await wallets[0].signTransaction({
|
|
283
|
+
to: config.profitRecipient,
|
|
284
|
+
value: totalProfit,
|
|
285
|
+
nonce: profitNonce,
|
|
286
|
+
gasPrice,
|
|
287
|
+
gasLimit: 21000n,
|
|
288
|
+
chainId: 56,
|
|
289
|
+
type: getTxType(config)
|
|
290
|
+
});
|
|
291
|
+
signedTxs.push(profitTx);
|
|
292
|
+
}
|
|
293
|
+
// ✅ 如果配置了 skipSubmit,直接返回
|
|
294
|
+
if (config.skipSubmit) {
|
|
295
|
+
const status = {
|
|
296
|
+
bundleHash: '',
|
|
297
|
+
txHashes: [],
|
|
298
|
+
results: [],
|
|
299
|
+
successCount: 0,
|
|
300
|
+
totalGasUsed: 0n,
|
|
301
|
+
targetBlock: 0
|
|
302
|
+
};
|
|
303
|
+
nonceManager.clearTemp();
|
|
304
|
+
return { bundleHash: '', status, txs: signedTxs };
|
|
305
|
+
}
|
|
209
306
|
const bundleResult = await merkle.sendBundle({
|
|
210
307
|
transactions: signedTxs,
|
|
211
308
|
...getBundleOptions(config)
|
|
@@ -319,8 +416,39 @@ export async function fourBatchPrivateSellMerkle(params) {
|
|
|
319
416
|
type: getTxType(config)
|
|
320
417
|
})));
|
|
321
418
|
signedTxs.push(...signedSells);
|
|
419
|
+
// ✅ 基于 minFunds 为每个钱包添加利润转账(如果设置且 > 0)
|
|
420
|
+
const extractProfit = shouldExtractProfit(config);
|
|
421
|
+
if (extractProfit && minOut > 0n) {
|
|
422
|
+
const { profit } = calculateProfit(minOut, config);
|
|
423
|
+
if (profit > 0n) {
|
|
424
|
+
// 为每个钱包添加利润转账
|
|
425
|
+
const profitNonces = await Promise.all(wallets.map(w => nonceManager.getNextNonce(w)));
|
|
426
|
+
const profitTxs = await Promise.all(wallets.map((wallet, i) => wallet.signTransaction({
|
|
427
|
+
to: config.profitRecipient,
|
|
428
|
+
value: profit,
|
|
429
|
+
nonce: profitNonces[i],
|
|
430
|
+
gasPrice,
|
|
431
|
+
gasLimit: 21000n,
|
|
432
|
+
chainId: 56,
|
|
433
|
+
type: getTxType(config)
|
|
434
|
+
})));
|
|
435
|
+
signedTxs.push(...profitTxs);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
322
438
|
// ✅ 清理临时 nonce 缓存
|
|
323
439
|
nonceManager.clearTemp();
|
|
440
|
+
// ✅ 如果配置了 skipSubmit,直接返回
|
|
441
|
+
if (config.skipSubmit) {
|
|
442
|
+
const status = {
|
|
443
|
+
bundleHash: '',
|
|
444
|
+
txHashes: [],
|
|
445
|
+
results: [],
|
|
446
|
+
successCount: 0,
|
|
447
|
+
totalGasUsed: 0n,
|
|
448
|
+
targetBlock: 0
|
|
449
|
+
};
|
|
450
|
+
return { bundleHash: '', status, txs: signedTxs };
|
|
451
|
+
}
|
|
324
452
|
const bundleResult = await merkle.sendBundle({
|
|
325
453
|
transactions: signedTxs,
|
|
326
454
|
...getBundleOptions(config)
|
|
@@ -30,3 +30,32 @@ export declare function getBundleOptions(config: FlapBundleMerkleConfig, blockOf
|
|
|
30
30
|
maxRetries: number;
|
|
31
31
|
};
|
|
32
32
|
export declare function getGasPriceConfig(config: FlapBundleMerkleConfig): any;
|
|
33
|
+
/**
|
|
34
|
+
* 检查是否需要提取利润
|
|
35
|
+
*/
|
|
36
|
+
export declare function shouldExtractProfit(config: FlapBundleMerkleConfig): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* 获取利润率(单位:基点 bps,1 bps = 0.01%)
|
|
39
|
+
* 默认 30 bps = 0.3% = 千分之3
|
|
40
|
+
*/
|
|
41
|
+
export declare function getProfitRateBps(config: FlapBundleMerkleConfig): number;
|
|
42
|
+
/**
|
|
43
|
+
* 计算利润和剩余金额
|
|
44
|
+
* @param amount 原始金额
|
|
45
|
+
* @param config 配置
|
|
46
|
+
* @returns { profit: 利润金额, remaining: 剩余金额 }
|
|
47
|
+
*/
|
|
48
|
+
export declare function calculateProfit(amount: bigint, config: FlapBundleMerkleConfig): {
|
|
49
|
+
profit: bigint;
|
|
50
|
+
remaining: bigint;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* 批量计算利润
|
|
54
|
+
* @param amounts 原始金额数组
|
|
55
|
+
* @param config 配置
|
|
56
|
+
* @returns { totalProfit: 总利润, remainingAmounts: 剩余金额数组 }
|
|
57
|
+
*/
|
|
58
|
+
export declare function calculateBatchProfit(amounts: bigint[], config: FlapBundleMerkleConfig): {
|
|
59
|
+
totalProfit: bigint;
|
|
60
|
+
remainingAmounts: bigint[];
|
|
61
|
+
};
|
|
@@ -61,3 +61,50 @@ export function getGasPriceConfig(config) {
|
|
|
61
61
|
}
|
|
62
62
|
return gasPriceConfig;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* 检查是否需要提取利润
|
|
66
|
+
*/
|
|
67
|
+
export function shouldExtractProfit(config) {
|
|
68
|
+
return !!(config.profitRecipient && config.profitRecipient !== ethers.ZeroAddress);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 获取利润率(单位:基点 bps,1 bps = 0.01%)
|
|
72
|
+
* 默认 30 bps = 0.3% = 千分之3
|
|
73
|
+
*/
|
|
74
|
+
export function getProfitRateBps(config) {
|
|
75
|
+
return config.profitRateBps ?? 30;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 计算利润和剩余金额
|
|
79
|
+
* @param amount 原始金额
|
|
80
|
+
* @param config 配置
|
|
81
|
+
* @returns { profit: 利润金额, remaining: 剩余金额 }
|
|
82
|
+
*/
|
|
83
|
+
export function calculateProfit(amount, config) {
|
|
84
|
+
if (!shouldExtractProfit(config)) {
|
|
85
|
+
return { profit: 0n, remaining: amount };
|
|
86
|
+
}
|
|
87
|
+
const rateBps = getProfitRateBps(config);
|
|
88
|
+
const profit = (amount * BigInt(rateBps)) / 10000n;
|
|
89
|
+
const remaining = amount - profit;
|
|
90
|
+
return { profit, remaining };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 批量计算利润
|
|
94
|
+
* @param amounts 原始金额数组
|
|
95
|
+
* @param config 配置
|
|
96
|
+
* @returns { totalProfit: 总利润, remainingAmounts: 剩余金额数组 }
|
|
97
|
+
*/
|
|
98
|
+
export function calculateBatchProfit(amounts, config) {
|
|
99
|
+
if (!shouldExtractProfit(config)) {
|
|
100
|
+
return { totalProfit: 0n, remainingAmounts: amounts };
|
|
101
|
+
}
|
|
102
|
+
let totalProfit = 0n;
|
|
103
|
+
const remainingAmounts = [];
|
|
104
|
+
for (const amount of amounts) {
|
|
105
|
+
const { profit, remaining } = calculateProfit(amount, config);
|
|
106
|
+
totalProfit += profit;
|
|
107
|
+
remainingAmounts.push(remaining);
|
|
108
|
+
}
|
|
109
|
+
return { totalProfit, remainingAmounts };
|
|
110
|
+
}
|
|
@@ -2,7 +2,7 @@ import { ethers, Wallet, JsonRpcProvider, Contract } from 'ethers';
|
|
|
2
2
|
import { MerkleClient } from '../../clients/merkle.js';
|
|
3
3
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
4
4
|
import { ADDRESSES } from '../../utils/constants.js';
|
|
5
|
-
import { CHAIN_ID_MAP, getTxType, getBundleOptions, getGasPriceConfig } from './config.js';
|
|
5
|
+
import { CHAIN_ID_MAP, getTxType, getBundleOptions, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit } from './config.js';
|
|
6
6
|
// 常量
|
|
7
7
|
const WBNB_ADDRESS = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
|
|
8
8
|
const FLAT_FEE = ethers.parseEther('0.0001'); // 合约固定手续费
|
|
@@ -264,7 +264,11 @@ export async function pancakeProxyBatchBuyMerkle(params) {
|
|
|
264
264
|
const blockOffset = config.bundleBlockOffset ?? 2;
|
|
265
265
|
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
266
266
|
const buyers = privateKeys.map(k => new Wallet(k, provider));
|
|
267
|
-
const
|
|
267
|
+
const originalAmountsWei = buyAmounts.map(a => ethers.parseEther(a)); // BNB 固定 18 位
|
|
268
|
+
// ✅ 利润提取配置
|
|
269
|
+
const extractProfit = shouldExtractProfit(config);
|
|
270
|
+
const { totalProfit, remainingAmounts } = calculateBatchProfit(originalAmountsWei, config);
|
|
271
|
+
const actualAmountsWei = remainingAmounts; // 扣除利润后用于购买的金额
|
|
268
272
|
// 查询 tokenOut decimals
|
|
269
273
|
const tokenDecimals = await getTokenDecimals(tokenAddress, provider);
|
|
270
274
|
// 计算 minOutputAmounts
|
|
@@ -277,34 +281,80 @@ export async function pancakeProxyBatchBuyMerkle(params) {
|
|
|
277
281
|
}
|
|
278
282
|
// 判断是否需要发送 BNB
|
|
279
283
|
const needBNB = needSendBNB(routeType, params);
|
|
280
|
-
//
|
|
284
|
+
// 构建交易(使用扣除利润后的金额)
|
|
281
285
|
const proxies = buyers.map(w => new Contract(pancakeProxyAddress, PANCAKE_PROXY_ABI, w));
|
|
282
286
|
let unsignedBuys;
|
|
283
287
|
if (routeType === 'v2') {
|
|
284
288
|
if (!params.v2Path || params.v2Path.length < 2) {
|
|
285
289
|
throw new Error('v2Path is required for V2 routing');
|
|
286
290
|
}
|
|
287
|
-
unsignedBuys = await buildV2Transactions(proxies, buyers,
|
|
291
|
+
unsignedBuys = await buildV2Transactions(proxies, buyers, actualAmountsWei, minOuts, params.v2Path, true, needBNB);
|
|
288
292
|
}
|
|
289
293
|
else if (routeType === 'v3-single') {
|
|
290
294
|
if (!params.v3TokenIn || !params.v3Fee) {
|
|
291
295
|
throw new Error('v3TokenIn and v3Fee are required for V3 single-hop');
|
|
292
296
|
}
|
|
293
|
-
unsignedBuys = await buildV3SingleTransactions(proxies, buyers, params.v3TokenIn, tokenAddress, params.v3Fee,
|
|
297
|
+
unsignedBuys = await buildV3SingleTransactions(proxies, buyers, params.v3TokenIn, tokenAddress, params.v3Fee, actualAmountsWei, minOuts, true, needBNB);
|
|
294
298
|
}
|
|
295
299
|
else if (routeType === 'v3-multi') {
|
|
296
300
|
if (!params.v3LpAddresses || params.v3LpAddresses.length === 0 || !params.v3ExactTokenIn) {
|
|
297
301
|
throw new Error('v3LpAddresses and v3ExactTokenIn are required for V3 multi-hop');
|
|
298
302
|
}
|
|
299
|
-
unsignedBuys = await buildV3MultiHopTransactions(proxies, buyers, params.v3LpAddresses, params.v3ExactTokenIn,
|
|
303
|
+
unsignedBuys = await buildV3MultiHopTransactions(proxies, buyers, params.v3LpAddresses, params.v3ExactTokenIn, actualAmountsWei, minOuts, true, needBNB);
|
|
300
304
|
}
|
|
301
305
|
else {
|
|
302
306
|
throw new Error(`Unsupported routeType: ${routeType}`);
|
|
303
307
|
}
|
|
304
308
|
// ✅ 使用前端传入的 gasLimit,否则使用默认值
|
|
305
309
|
const finalGasLimit = getGasLimit(config);
|
|
306
|
-
//
|
|
307
|
-
const
|
|
310
|
+
// ✅ 直接签名和提交(内联处理以支持利润转账)
|
|
311
|
+
const nonceManager = new NonceManager(provider);
|
|
312
|
+
const nonces = await Promise.all(buyers.map(w => nonceManager.getNextNonce(w)));
|
|
313
|
+
const signedTxs = await Promise.all(unsignedBuys.map((unsigned, i) => buyers[i].signTransaction({
|
|
314
|
+
...unsigned,
|
|
315
|
+
from: buyers[i].address,
|
|
316
|
+
nonce: nonces[i],
|
|
317
|
+
gasLimit: finalGasLimit,
|
|
318
|
+
gasPrice,
|
|
319
|
+
chainId,
|
|
320
|
+
type: getTxType(config),
|
|
321
|
+
value: unsigned.value // ✅ 显式保留 value(BNB 金额 + 手续费)
|
|
322
|
+
})));
|
|
323
|
+
// ✅ 添加利润转账(从第一个钱包转出总利润)
|
|
324
|
+
if (extractProfit && totalProfit > 0n) {
|
|
325
|
+
const profitNonce = await nonceManager.getNextNonce(buyers[0]);
|
|
326
|
+
const profitTx = await buyers[0].signTransaction({
|
|
327
|
+
to: config.profitRecipient,
|
|
328
|
+
value: totalProfit,
|
|
329
|
+
nonce: profitNonce,
|
|
330
|
+
gasPrice,
|
|
331
|
+
gasLimit: 21000n,
|
|
332
|
+
chainId,
|
|
333
|
+
type: getTxType(config)
|
|
334
|
+
});
|
|
335
|
+
signedTxs.push(profitTx);
|
|
336
|
+
}
|
|
337
|
+
// ✅ 如果配置了 skipSubmit,直接返回
|
|
338
|
+
if (config.skipSubmit) {
|
|
339
|
+
nonceManager.clearTemp();
|
|
340
|
+
return {
|
|
341
|
+
bundleHash: '',
|
|
342
|
+
status: {
|
|
343
|
+
bundleHash: '',
|
|
344
|
+
txHashes: [],
|
|
345
|
+
results: [],
|
|
346
|
+
successCount: 0,
|
|
347
|
+
totalGasUsed: 0n,
|
|
348
|
+
targetBlock: 0
|
|
349
|
+
},
|
|
350
|
+
buyTxs: signedTxs
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const bundleResult = await merkle.sendBundle({
|
|
354
|
+
transactions: signedTxs,
|
|
355
|
+
...getBundleOptions(config, blockOffset)
|
|
356
|
+
});
|
|
357
|
+
nonceManager.clearTemp();
|
|
308
358
|
const status = await waitForBundleResult(merkle, bundleResult, config.waitForConfirmation ?? false, config.waitTimeoutMs ?? 120000);
|
|
309
359
|
return {
|
|
310
360
|
bundleHash: bundleResult.bundleHash,
|
|
@@ -481,11 +531,66 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
481
531
|
}
|
|
482
532
|
// ✅ 使用前端传入的 gasLimit,否则使用默认值
|
|
483
533
|
const finalGasLimit = getGasLimit(config);
|
|
484
|
-
//
|
|
485
|
-
console.log('🚀
|
|
534
|
+
// ✅ 直接签名和提交(内联处理以支持利润转账)
|
|
535
|
+
console.log('🚀 签名卖出交易...\n');
|
|
536
|
+
const nonceManager = new NonceManager(provider);
|
|
537
|
+
const nonces = await Promise.all(sellers.map(w => nonceManager.getNextNonce(w)));
|
|
538
|
+
const signedTxs = await Promise.all(unsignedSells.map((unsigned, i) => sellers[i].signTransaction({
|
|
539
|
+
...unsigned,
|
|
540
|
+
from: sellers[i].address,
|
|
541
|
+
nonce: nonces[i],
|
|
542
|
+
gasLimit: finalGasLimit,
|
|
543
|
+
gasPrice,
|
|
544
|
+
chainId,
|
|
545
|
+
type: getTxType(config),
|
|
546
|
+
value: unsigned.value // ✅ 显式保留 value(手续费)
|
|
547
|
+
})));
|
|
548
|
+
// ✅ 基于 minFunds 为每个钱包添加利润转账(如果设置且 > 0)
|
|
549
|
+
const extractProfit = shouldExtractProfit(config);
|
|
550
|
+
if (extractProfit && minOuts.length > 0) {
|
|
551
|
+
// 为每个有 minOut > 0 的钱包添加利润转账
|
|
552
|
+
for (let i = 0; i < sellers.length; i++) {
|
|
553
|
+
if (minOuts[i] > 0n) {
|
|
554
|
+
const { profit } = calculateProfit(minOuts[i], config);
|
|
555
|
+
if (profit > 0n) {
|
|
556
|
+
const profitNonce = await nonceManager.getNextNonce(sellers[i]);
|
|
557
|
+
const profitTx = await sellers[i].signTransaction({
|
|
558
|
+
to: config.profitRecipient,
|
|
559
|
+
value: profit,
|
|
560
|
+
nonce: profitNonce,
|
|
561
|
+
gasPrice,
|
|
562
|
+
gasLimit: 21000n,
|
|
563
|
+
chainId,
|
|
564
|
+
type: getTxType(config)
|
|
565
|
+
});
|
|
566
|
+
signedTxs.push(profitTx);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
nonceManager.clearTemp();
|
|
572
|
+
// ✅ 如果配置了 skipSubmit,直接返回
|
|
573
|
+
if (config.skipSubmit) {
|
|
574
|
+
return {
|
|
575
|
+
bundleHash: '',
|
|
576
|
+
status: {
|
|
577
|
+
bundleHash: '',
|
|
578
|
+
txHashes: [],
|
|
579
|
+
results: [],
|
|
580
|
+
successCount: 0,
|
|
581
|
+
totalGasUsed: 0n,
|
|
582
|
+
targetBlock: 0
|
|
583
|
+
},
|
|
584
|
+
sellTxs: signedTxs
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
console.log('🚀 提交卖出 Bundle...\n');
|
|
486
588
|
let bundleResult;
|
|
487
589
|
try {
|
|
488
|
-
bundleResult = await
|
|
590
|
+
bundleResult = await merkle.sendBundle({
|
|
591
|
+
transactions: signedTxs,
|
|
592
|
+
...getBundleOptions(config, blockOffset)
|
|
593
|
+
});
|
|
489
594
|
console.log(` ✅ Bundle 已提交: ${bundleResult.bundleHash}`);
|
|
490
595
|
}
|
|
491
596
|
catch (error) {
|
|
@@ -2,7 +2,7 @@ import { ethers, Wallet } from 'ethers';
|
|
|
2
2
|
import { MerkleClient } from '../../clients/merkle.js';
|
|
3
3
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
4
4
|
import { FLAP_PORTAL_ADDRESSES } from '../constants.js';
|
|
5
|
-
import { CHAIN_ID_MAP, PORTAL_ABI, getTxType, getBundleOptions, getGasPriceConfig } from './config.js';
|
|
5
|
+
import { CHAIN_ID_MAP, PORTAL_ABI, getTxType, getBundleOptions, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit } from './config.js';
|
|
6
6
|
/**
|
|
7
7
|
* 私有购买(单笔)(Merkle 版本)
|
|
8
8
|
*/
|
|
@@ -19,32 +19,68 @@ export async function flapPrivateBuyMerkle(params) {
|
|
|
19
19
|
const portalAddr = FLAP_PORTAL_ADDRESSES[chain];
|
|
20
20
|
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
21
21
|
const gasMultiplier = config.gasLimitMultiplier ?? 1.0;
|
|
22
|
-
const
|
|
22
|
+
const originalAmountWei = ethers.parseEther(amountIn);
|
|
23
|
+
// ✅ 利润提取配置
|
|
24
|
+
const extractProfit = shouldExtractProfit(config);
|
|
25
|
+
let profitWei = 0n;
|
|
26
|
+
let actualAmountWei = originalAmountWei;
|
|
27
|
+
if (extractProfit) {
|
|
28
|
+
const { profit, remaining } = calculateProfit(originalAmountWei, config);
|
|
29
|
+
profitWei = profit;
|
|
30
|
+
actualAmountWei = remaining; // ✅ 扣除利润后的金额用于购买
|
|
31
|
+
}
|
|
23
32
|
const minAmountOut = minOutputAmount
|
|
24
33
|
? (typeof minOutputAmount === 'string' ? ethers.parseUnits(minOutputAmount, 18) : minOutputAmount)
|
|
25
34
|
: 0n;
|
|
35
|
+
const signedTxs = [];
|
|
26
36
|
const portal = new ethers.Contract(portalAddr, PORTAL_ABI, wallet);
|
|
27
37
|
const unsigned = await portal.swapExactInput.populateTransaction({
|
|
28
38
|
inputToken: '0x0000000000000000000000000000000000000000',
|
|
29
39
|
outputToken: tokenAddress,
|
|
30
|
-
inputAmount:
|
|
40
|
+
inputAmount: actualAmountWei, // ✅ 使用扣除利润后的金额
|
|
31
41
|
minOutputAmount: minAmountOut,
|
|
32
42
|
permitData: '0x',
|
|
33
|
-
}, { value:
|
|
43
|
+
}, { value: actualAmountWei } // ✅ 使用扣除利润后的金额
|
|
44
|
+
);
|
|
34
45
|
const gasLimit = BigInt(Math.ceil(800000 * gasMultiplier));
|
|
46
|
+
let currentNonce = await wallet.getNonce();
|
|
35
47
|
const req = {
|
|
36
48
|
...unsigned,
|
|
37
49
|
from: wallet.address,
|
|
38
|
-
nonce:
|
|
50
|
+
nonce: currentNonce,
|
|
39
51
|
gasLimit: gasLimit,
|
|
40
52
|
gasPrice,
|
|
41
53
|
chainId,
|
|
42
54
|
type: getTxType(config),
|
|
43
|
-
value:
|
|
55
|
+
value: actualAmountWei // ✅ 使用扣除利润后的金额
|
|
44
56
|
};
|
|
45
57
|
const signed = await wallet.signTransaction(req);
|
|
58
|
+
signedTxs.push(signed);
|
|
59
|
+
// ✅ 添加利润转账
|
|
60
|
+
if (extractProfit && profitWei > 0n) {
|
|
61
|
+
const profitTx = await wallet.signTransaction({
|
|
62
|
+
to: config.profitRecipient,
|
|
63
|
+
value: profitWei,
|
|
64
|
+
nonce: currentNonce + 1,
|
|
65
|
+
gasPrice,
|
|
66
|
+
gasLimit: 21000n,
|
|
67
|
+
chainId,
|
|
68
|
+
type: getTxType(config)
|
|
69
|
+
});
|
|
70
|
+
signedTxs.push(profitTx);
|
|
71
|
+
}
|
|
72
|
+
// ✅ 如果配置了 skipSubmit,直接返回
|
|
73
|
+
if (config.skipSubmit) {
|
|
74
|
+
return {
|
|
75
|
+
txHash: '',
|
|
76
|
+
success: false,
|
|
77
|
+
blockNumber: undefined,
|
|
78
|
+
gasUsed: undefined,
|
|
79
|
+
error: undefined
|
|
80
|
+
};
|
|
81
|
+
}
|
|
46
82
|
const bundleResult = await merkle.sendBundle({
|
|
47
|
-
transactions:
|
|
83
|
+
transactions: signedTxs,
|
|
48
84
|
...getBundleOptions(config)
|
|
49
85
|
});
|
|
50
86
|
const waitForConfirmation = config.waitForConfirmation ?? false;
|
|
@@ -96,18 +132,48 @@ export async function flapPrivateSellMerkle(params) {
|
|
|
96
132
|
permitData: '0x',
|
|
97
133
|
});
|
|
98
134
|
const gasLimit = BigInt(Math.ceil(800000 * gasMultiplier));
|
|
135
|
+
const signedTxs = [];
|
|
136
|
+
let currentNonce = await wallet.getNonce();
|
|
99
137
|
const req = {
|
|
100
138
|
...unsigned,
|
|
101
139
|
from: wallet.address,
|
|
102
|
-
nonce:
|
|
140
|
+
nonce: currentNonce,
|
|
103
141
|
gasLimit: gasLimit,
|
|
104
142
|
gasPrice,
|
|
105
143
|
chainId,
|
|
106
144
|
type: getTxType(config)
|
|
107
145
|
};
|
|
108
146
|
const signed = await wallet.signTransaction(req);
|
|
147
|
+
signedTxs.push(signed);
|
|
148
|
+
// ✅ 基于 minFunds 计算利润(如果设置且 > 0)
|
|
149
|
+
const extractProfit = shouldExtractProfit(config);
|
|
150
|
+
if (extractProfit && minOut > 0n) {
|
|
151
|
+
const { profit } = calculateProfit(minOut, config);
|
|
152
|
+
if (profit > 0n) {
|
|
153
|
+
const profitTx = await wallet.signTransaction({
|
|
154
|
+
to: config.profitRecipient,
|
|
155
|
+
value: profit,
|
|
156
|
+
nonce: currentNonce + 1,
|
|
157
|
+
gasPrice,
|
|
158
|
+
gasLimit: 21000n,
|
|
159
|
+
chainId,
|
|
160
|
+
type: getTxType(config)
|
|
161
|
+
});
|
|
162
|
+
signedTxs.push(profitTx);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// ✅ 如果配置了 skipSubmit,直接返回
|
|
166
|
+
if (config.skipSubmit) {
|
|
167
|
+
return {
|
|
168
|
+
txHash: '',
|
|
169
|
+
success: false,
|
|
170
|
+
blockNumber: undefined,
|
|
171
|
+
gasUsed: undefined,
|
|
172
|
+
error: undefined
|
|
173
|
+
};
|
|
174
|
+
}
|
|
109
175
|
const bundleResult = await merkle.sendBundle({
|
|
110
|
-
transactions:
|
|
176
|
+
transactions: signedTxs,
|
|
111
177
|
...getBundleOptions(config)
|
|
112
178
|
});
|
|
113
179
|
const waitForConfirmation = config.waitForConfirmation ?? false;
|
|
@@ -151,7 +217,11 @@ export async function flapBatchPrivateBuyMerkle(params) {
|
|
|
151
217
|
const signedTxs = [];
|
|
152
218
|
const nonceManager = new NonceManager(provider);
|
|
153
219
|
const wallets = privateKeys.map(k => new Wallet(k, provider));
|
|
154
|
-
const
|
|
220
|
+
const originalAmountsWei = amountsIn.map(a => ethers.parseEther(a));
|
|
221
|
+
// ✅ 利润提取配置
|
|
222
|
+
const extractProfit = shouldExtractProfit(config);
|
|
223
|
+
const { totalProfit, remainingAmounts } = calculateBatchProfit(originalAmountsWei, config);
|
|
224
|
+
const actualAmountsWei = remainingAmounts; // 扣除利润后用于购买的金额
|
|
155
225
|
let minOuts;
|
|
156
226
|
if (minOutputAmounts && minOutputAmounts.length === wallets.length) {
|
|
157
227
|
minOuts = minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseUnits(m, 18) : m);
|
|
@@ -163,10 +233,11 @@ export async function flapBatchPrivateBuyMerkle(params) {
|
|
|
163
233
|
const unsignedList = await Promise.all(portals.map((portal, i) => portal.swapExactInput.populateTransaction({
|
|
164
234
|
inputToken: '0x0000000000000000000000000000000000000000',
|
|
165
235
|
outputToken: tokenAddress,
|
|
166
|
-
inputAmount:
|
|
236
|
+
inputAmount: actualAmountsWei[i], // ✅ 使用扣除利润后的金额
|
|
167
237
|
minOutputAmount: minOuts[i],
|
|
168
238
|
permitData: '0x',
|
|
169
|
-
}, { value:
|
|
239
|
+
}, { value: actualAmountsWei[i] } // ✅ 使用扣除利润后的金额
|
|
240
|
+
)));
|
|
170
241
|
const finalGasLimit = BigInt(Math.ceil(800000 * gasMultiplier));
|
|
171
242
|
const gasLimits = new Array(wallets.length).fill(finalGasLimit);
|
|
172
243
|
const nonces = await Promise.all(wallets.map(w => nonceManager.getNextNonce(w)));
|
|
@@ -178,9 +249,36 @@ export async function flapBatchPrivateBuyMerkle(params) {
|
|
|
178
249
|
gasPrice,
|
|
179
250
|
chainId,
|
|
180
251
|
type: getTxType(config),
|
|
181
|
-
value:
|
|
252
|
+
value: actualAmountsWei[i] // ✅ 使用扣除利润后的金额
|
|
182
253
|
})));
|
|
183
254
|
signedTxs.push(...signedList);
|
|
255
|
+
// ✅ 添加利润转账(从第一个钱包转出总利润)
|
|
256
|
+
if (extractProfit && totalProfit > 0n) {
|
|
257
|
+
const profitNonce = await nonceManager.getNextNonce(wallets[0]);
|
|
258
|
+
const profitTx = await wallets[0].signTransaction({
|
|
259
|
+
to: config.profitRecipient,
|
|
260
|
+
value: totalProfit,
|
|
261
|
+
nonce: profitNonce,
|
|
262
|
+
gasPrice,
|
|
263
|
+
gasLimit: 21000n,
|
|
264
|
+
chainId,
|
|
265
|
+
type: getTxType(config)
|
|
266
|
+
});
|
|
267
|
+
signedTxs.push(profitTx);
|
|
268
|
+
}
|
|
269
|
+
// ✅ 如果配置了 skipSubmit,直接返回
|
|
270
|
+
if (config.skipSubmit) {
|
|
271
|
+
const status = {
|
|
272
|
+
bundleHash: '',
|
|
273
|
+
txHashes: [],
|
|
274
|
+
results: [],
|
|
275
|
+
successCount: 0,
|
|
276
|
+
totalGasUsed: 0n,
|
|
277
|
+
targetBlock: 0
|
|
278
|
+
};
|
|
279
|
+
nonceManager.clearTemp();
|
|
280
|
+
return { bundleHash: '', status, txs: signedTxs };
|
|
281
|
+
}
|
|
184
282
|
const bundleResult = await merkle.sendBundle({
|
|
185
283
|
transactions: signedTxs,
|
|
186
284
|
...getBundleOptions(config)
|
|
@@ -269,6 +367,43 @@ export async function flapBatchPrivateSellMerkle(params) {
|
|
|
269
367
|
type: getTxType(config)
|
|
270
368
|
})));
|
|
271
369
|
signedTxs.push(...signedList);
|
|
370
|
+
// ✅ 基于 minFunds 为每个钱包添加利润转账(如果设置且 > 0)
|
|
371
|
+
const extractProfit = shouldExtractProfit(config);
|
|
372
|
+
if (extractProfit && minOuts.length > 0) {
|
|
373
|
+
// 为每个有 minOut > 0 的钱包添加利润转账
|
|
374
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
375
|
+
if (minOuts[i] > 0n) {
|
|
376
|
+
const { profit } = calculateProfit(minOuts[i], config);
|
|
377
|
+
if (profit > 0n) {
|
|
378
|
+
const profitNonce = await nonceManager.getNextNonce(wallets[i]);
|
|
379
|
+
const profitTx = await wallets[i].signTransaction({
|
|
380
|
+
to: config.profitRecipient,
|
|
381
|
+
value: profit,
|
|
382
|
+
nonce: profitNonce,
|
|
383
|
+
gasPrice,
|
|
384
|
+
gasLimit: 21000n,
|
|
385
|
+
chainId,
|
|
386
|
+
type: getTxType(config)
|
|
387
|
+
});
|
|
388
|
+
signedTxs.push(profitTx);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// ✅ 清理临时 nonce 缓存
|
|
394
|
+
nonceManager.clearTemp();
|
|
395
|
+
// ✅ 如果配置了 skipSubmit,直接返回
|
|
396
|
+
if (config.skipSubmit) {
|
|
397
|
+
const status = {
|
|
398
|
+
bundleHash: '',
|
|
399
|
+
txHashes: [],
|
|
400
|
+
results: [],
|
|
401
|
+
successCount: 0,
|
|
402
|
+
totalGasUsed: 0n,
|
|
403
|
+
targetBlock: 0
|
|
404
|
+
};
|
|
405
|
+
return { bundleHash: '', status, txs: signedTxs };
|
|
406
|
+
}
|
|
272
407
|
const bundleResult = await merkle.sendBundle({
|
|
273
408
|
transactions: signedTxs,
|
|
274
409
|
...getBundleOptions(config)
|
|
@@ -14,6 +14,9 @@ export type FlapBundleMerkleConfig = {
|
|
|
14
14
|
waitTimeoutMs?: number;
|
|
15
15
|
slippageBps?: number;
|
|
16
16
|
skipQuoteOnError?: boolean;
|
|
17
|
+
profitRecipient?: string;
|
|
18
|
+
profitRateBps?: number;
|
|
19
|
+
skipSubmit?: boolean;
|
|
17
20
|
};
|
|
18
21
|
export type FlapChainForMerkleBundle = 'BSC';
|
|
19
22
|
export type MerkleTransactionStatus = {
|