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.
@@ -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 amountsWei = buyAmounts.map(a => ethers.parseEther(a)); // BNB 固定 18 位
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, amountsWei, minOuts, params.v2Path, true, needBNB);
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, amountsWei, minOuts, true, needBNB);
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, amountsWei, minOuts, true, needBNB);
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 bundleResult = await signAndSendBundle(merkle, buyers, unsignedBuys, gasPrice, finalGasLimit, 56, config, blockOffset);
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('🚀 签名并提交卖出 Bundle...\n');
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 signAndSendBundle(merkle, sellers, unsignedSells, gasPrice, finalGasLimit, 56, config, blockOffset);
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 fundsWei = ethers.parseEther(funds);
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, fundsWei, minAmount, { value: fundsWei });
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: await wallet.getNonce(),
47
+ nonce: currentNonce,
36
48
  gasLimit: gasLimit,
37
49
  gasPrice,
38
50
  chainId: 56,
39
51
  type: getTxType(config),
40
- value: fundsWei // ✅ funds = msg.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: [signed],
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: [signedSell],
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 amountsWei = fundsList.map((a) => ethers.parseEther(a));
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, amountsWei[i], 0n, { value: amountsWei[i] })));
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: amountsWei[i] // ✅ funds = msg.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 amountsWei = buyAmounts.map(a => ethers.parseEther(a)); // BNB 固定 18 位
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, amountsWei, minOuts, params.v2Path, true, needBNB);
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, amountsWei, minOuts, true, needBNB);
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, amountsWei, minOuts, true, needBNB);
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 bundleResult = await signAndSendBundle(merkle, buyers, unsignedBuys, gasPrice, finalGasLimit, chainId, config, blockOffset);
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('🚀 签名并提交卖出 Bundle...\n');
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 signAndSendBundle(merkle, sellers, unsignedSells, gasPrice, finalGasLimit, chainId, config, blockOffset);
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 amountWei = ethers.parseEther(amountIn);
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: amountWei,
40
+ inputAmount: actualAmountWei, // ✅ 使用扣除利润后的金额
31
41
  minOutputAmount: minAmountOut,
32
42
  permitData: '0x',
33
- }, { value: amountWei });
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: await wallet.getNonce(),
50
+ nonce: currentNonce,
39
51
  gasLimit: gasLimit,
40
52
  gasPrice,
41
53
  chainId,
42
54
  type: getTxType(config),
43
- value: amountWei
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: [signed],
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: await wallet.getNonce(),
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: [signed],
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 amountsWei = amountsIn.map(a => ethers.parseEther(a));
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: amountsWei[i],
236
+ inputAmount: actualAmountsWei[i], // ✅ 使用扣除利润后的金额
167
237
  minOutputAmount: minOuts[i],
168
238
  permitData: '0x',
169
- }, { value: amountsWei[i] })));
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: amountsWei[i]
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 = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.1.82",
3
+ "version": "1.1.83",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",