four-flap-meme-sdk 1.4.78 → 1.4.80

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.
@@ -18,10 +18,12 @@ export interface FourBuyFirstConfig extends CommonBundleConfig {
18
18
  waitTimeoutMs?: number;
19
19
  }
20
20
  export interface FourBundleBuyFirstSignParams {
21
- buyerPrivateKey: string;
21
+ buyerPrivateKey?: string;
22
+ buyerPrivateKeys?: string[];
22
23
  buyerFunds?: string;
23
24
  buyerFundsPercentage?: number;
24
- sellerPrivateKey: string;
25
+ sellerPrivateKey?: string;
26
+ sellerPrivateKeys?: string[];
25
27
  tokenAddress: string;
26
28
  config: FourBuyFirstSignConfig;
27
29
  buyCount?: number;
@@ -83,10 +83,26 @@ function splitAmount(totalAmount, count) {
83
83
  return amounts;
84
84
  }
85
85
  export async function fourBundleBuyFirstMerkle(params) {
86
- const { buyerPrivateKey, buyerFunds, buyerFundsPercentage, sellerPrivateKey, tokenAddress, config, buyCount: _buyCount, sellCount: _sellCount } = params;
87
- // ✅ 解析并验证买卖笔数
86
+ const { buyerPrivateKey, buyerPrivateKeys, buyerFunds, buyerFundsPercentage, sellerPrivateKey, sellerPrivateKeys, tokenAddress, config, buyCount: _buyCount, sellCount: _sellCount } = params;
87
+ // ✅ 判断是否为多钱包模式
88
+ const isMultiWalletMode = !!(buyerPrivateKeys && buyerPrivateKeys.length > 0) ||
89
+ !!(sellerPrivateKeys && sellerPrivateKeys.length > 0);
88
90
  const buyCount = _buyCount ?? 1;
89
91
  const sellCount = _sellCount ?? 1;
92
+ // ✅ 多钱包模式:使用单独的处理逻辑
93
+ if (isMultiWalletMode) {
94
+ return await fourBundleBuyFirstMultiWallet({
95
+ buyerPrivateKeys: buyerPrivateKeys || (buyerPrivateKey ? [buyerPrivateKey] : []),
96
+ sellerPrivateKeys: sellerPrivateKeys || (sellerPrivateKey ? [sellerPrivateKey] : []),
97
+ tokenAddress,
98
+ buyerFunds,
99
+ config
100
+ });
101
+ }
102
+ // ✅ 单钱包模式(向后兼容)
103
+ if (!buyerPrivateKey || !sellerPrivateKey) {
104
+ throw new Error('单钱包模式需要提供 buyerPrivateKey 和 sellerPrivateKey');
105
+ }
90
106
  validateSwapCounts(buyCount, sellCount);
91
107
  // ✅ 计算利润比例:每笔万分之3
92
108
  const totalTxCount = buyCount + sellCount;
@@ -292,3 +308,194 @@ async function planMultiNonces({ buyer, seller, buyCount, sellCount, extractProf
292
308
  const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
293
309
  return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
294
310
  }
311
+ /**
312
+ * ✅ Four 多钱包捆绑换手
313
+ * - 多个买方钱包执行买入(每个钱包1笔)
314
+ * - 多个卖方钱包执行卖出(每个钱包1笔)
315
+ * - 买入总价值 = 卖出总价值
316
+ * - ✅ 优化:最大化并行操作
317
+ */
318
+ async function fourBundleBuyFirstMultiWallet(params) {
319
+ const { buyerPrivateKeys, sellerPrivateKeys, tokenAddress, buyerFunds, config } = params;
320
+ const buyCount = buyerPrivateKeys.length;
321
+ const sellCount = sellerPrivateKeys.length;
322
+ if (buyCount === 0)
323
+ throw new Error('买方钱包数量不能为0');
324
+ if (sellCount === 0)
325
+ throw new Error('卖方钱包数量不能为0');
326
+ // 验证总交易数不超过限制
327
+ validateSwapCounts(buyCount, sellCount);
328
+ // ✅ 计算利润比例:每笔万分之6
329
+ const totalTxCount = buyCount + sellCount;
330
+ const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
331
+ const chainIdNum = config.chainId ?? 56;
332
+ const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
333
+ chainId: chainIdNum,
334
+ name: 'BSC'
335
+ });
336
+ const nonceManager = new NonceManager(provider);
337
+ // 创建所有钱包实例
338
+ const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, provider));
339
+ const sellers = sellerPrivateKeys.map(pk => new Wallet(pk, provider));
340
+ // 使用第一个卖方作为主卖方(支付贿赂和利润)
341
+ const mainSeller = sellers[0];
342
+ // ✅ 计算总交易金额
343
+ if (!buyerFunds) {
344
+ throw new Error('多钱包模式必须提供 buyerFunds(总交易金额)');
345
+ }
346
+ const totalFundsWei = ethers.parseEther(String(buyerFunds));
347
+ if (totalFundsWei <= 0n) {
348
+ throw new Error('交易金额必须大于0');
349
+ }
350
+ const finalGasLimit = getGasLimit(config);
351
+ const txType = getTxType(config);
352
+ const bribeAmount = getBribeAmount(config);
353
+ const needBribeTx = bribeAmount > 0n;
354
+ // ✅ 第一批并行:报价 + Gas价格 + 代币精度
355
+ const helper3 = new Contract(ADDRESSES.BSC.TokenManagerHelper3, HELPER3_ABI, provider);
356
+ const erc20 = new Contract(tokenAddress, [
357
+ 'function decimals() view returns (uint8)'
358
+ ], provider);
359
+ const [buyQuote, gasPrice, decimals] = await Promise.all([
360
+ helper3.tryBuy(tokenAddress, 0n, totalFundsWei),
361
+ getOptimizedGasPrice(provider, getGasPriceConfig(config)),
362
+ erc20.decimals()
363
+ ]);
364
+ const estimatedTokenAmount = buyQuote.estimatedAmount ?? buyQuote[2];
365
+ if (!estimatedTokenAmount || estimatedTokenAmount <= 0n) {
366
+ throw new Error('报价失败:无法估算可买入的代币数量');
367
+ }
368
+ // ✅ 将总金额平均分配给买方
369
+ const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
370
+ // ✅ 将代币平均分配给卖方
371
+ const sellAmountsWei = splitAmount(estimatedTokenAmount, sellCount);
372
+ // ✅ 第二批并行:估算利润 + 获取所有钱包 nonces
373
+ // 预先计算所有唯一钱包地址(去重)
374
+ const allWallets = [...sellers, ...buyers];
375
+ const uniqueAddresses = new Set(allWallets.map(w => w.address.toLowerCase()));
376
+ const uniqueWallets = allWallets.filter((w, i) => {
377
+ const addr = w.address.toLowerCase();
378
+ const firstIdx = allWallets.findIndex(x => x.address.toLowerCase() === addr);
379
+ return firstIdx === i;
380
+ });
381
+ const [sellResult, noncesArray] = await Promise.all([
382
+ trySell('BSC', config.rpcUrl, tokenAddress, estimatedTokenAmount),
383
+ nonceManager.getNextNoncesForWallets(uniqueWallets)
384
+ ]);
385
+ // 构建 nonces Map
386
+ const noncesMap = new Map();
387
+ uniqueWallets.forEach((wallet, i) => {
388
+ noncesMap.set(wallet.address.toLowerCase(), noncesArray[i]);
389
+ });
390
+ const estimatedSellFunds = sellResult.funds;
391
+ const profitAmount = (estimatedSellFunds * BigInt(profitRateBps)) / 10000n;
392
+ // ✅ 第三批并行:构建并签名所有交易
393
+ const allTransactions = [];
394
+ const tm = new Contract(TM_ADDRESS, TM_ABI, provider);
395
+ // 1. 贿赂交易(由主卖方支付)- 先处理以确定 nonce 偏移
396
+ let bribeTxPromise = null;
397
+ if (needBribeTx) {
398
+ const mainSellerAddr = mainSeller.address.toLowerCase();
399
+ const bribeNonce = noncesMap.get(mainSellerAddr);
400
+ noncesMap.set(mainSellerAddr, bribeNonce + 1);
401
+ bribeTxPromise = mainSeller.signTransaction({
402
+ to: BLOCKRAZOR_BUILDER_EOA,
403
+ value: bribeAmount,
404
+ nonce: bribeNonce,
405
+ gasPrice,
406
+ gasLimit: 21000n,
407
+ chainId: chainIdNum,
408
+ type: txType
409
+ });
410
+ }
411
+ // 2. 预分配所有 nonces(同步操作,避免竞态)
412
+ const buyerNonces = [];
413
+ const sellerNonces = [];
414
+ buyers.forEach(buyer => {
415
+ const addr = buyer.address.toLowerCase();
416
+ const nonce = noncesMap.get(addr);
417
+ buyerNonces.push(nonce);
418
+ noncesMap.set(addr, nonce + 1);
419
+ });
420
+ sellers.forEach(seller => {
421
+ const addr = seller.address.toLowerCase();
422
+ const nonce = noncesMap.get(addr);
423
+ sellerNonces.push(nonce);
424
+ noncesMap.set(addr, nonce + 1);
425
+ });
426
+ // 3. 并行构建所有买入交易
427
+ const buyTxPromises = buyers.map(async (buyer, i) => {
428
+ const tmBuyer = tm.connect(buyer);
429
+ const unsigned = await tmBuyer.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyer.address, buyAmountsWei[i], 0n, { value: buyAmountsWei[i] });
430
+ return buyer.signTransaction({
431
+ ...unsigned,
432
+ from: buyer.address,
433
+ nonce: buyerNonces[i],
434
+ gasLimit: finalGasLimit,
435
+ gasPrice,
436
+ chainId: chainIdNum,
437
+ type: txType,
438
+ value: buyAmountsWei[i]
439
+ });
440
+ });
441
+ // 4. 并行构建所有卖出交易
442
+ const sellTxPromises = sellers.map(async (seller, i) => {
443
+ const tmSeller = tm.connect(seller);
444
+ const unsigned = await tmSeller.sellToken.populateTransaction(0n, tokenAddress, sellAmountsWei[i], 0n);
445
+ return seller.signTransaction({
446
+ ...unsigned,
447
+ from: seller.address,
448
+ nonce: sellerNonces[i],
449
+ gasLimit: finalGasLimit,
450
+ gasPrice,
451
+ chainId: chainIdNum,
452
+ type: txType,
453
+ value: 0n
454
+ });
455
+ });
456
+ // ✅ 并行签名所有交易(贿赂 + 买入 + 卖出)
457
+ const [bribeTx, signedBuys, signedSells] = await Promise.all([
458
+ bribeTxPromise,
459
+ Promise.all(buyTxPromises),
460
+ Promise.all(sellTxPromises)
461
+ ]);
462
+ // 组装交易顺序:贿赂 → 买入 → 卖出
463
+ if (bribeTx)
464
+ allTransactions.push(bribeTx);
465
+ allTransactions.push(...signedBuys, ...signedSells);
466
+ // 5. 利润多跳转账(由主卖方支付)
467
+ let profitHopWallets;
468
+ if (profitAmount > 0n) {
469
+ const mainSellerAddr = mainSeller.address.toLowerCase();
470
+ const profitNonce = noncesMap.get(mainSellerAddr);
471
+ const profitHopResult = await buildProfitHopTransactions({
472
+ provider,
473
+ payerWallet: mainSeller,
474
+ profitAmount,
475
+ profitRecipient: getProfitRecipient(),
476
+ hopCount: PROFIT_HOP_COUNT,
477
+ gasPrice,
478
+ chainId: chainIdNum,
479
+ txType,
480
+ startNonce: profitNonce
481
+ });
482
+ allTransactions.push(...profitHopResult.signedTransactions);
483
+ profitHopWallets = profitHopResult.hopWallets;
484
+ }
485
+ nonceManager.clearTemp();
486
+ return {
487
+ signedTransactions: allTransactions,
488
+ profitHopWallets,
489
+ metadata: {
490
+ buyerAddress: buyers.map(b => b.address).join(','),
491
+ sellerAddress: sellers.map(s => s.address).join(','),
492
+ buyAmount: ethers.formatEther(totalFundsWei),
493
+ sellAmount: ethers.formatUnits(estimatedTokenAmount, decimals),
494
+ profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
495
+ buyCount,
496
+ sellCount,
497
+ buyAmounts: buyAmountsWei.map(amt => ethers.formatEther(amt)),
498
+ sellAmounts: sellAmountsWei.map(amt => ethers.formatUnits(amt, decimals))
499
+ }
500
+ };
501
+ }
@@ -22,8 +22,10 @@ export interface FlapBuyFirstConfig extends CommonBundleConfig {
22
22
  }
23
23
  export interface FlapBundleBuyFirstSignParams {
24
24
  chain: FlapChain;
25
- buyerPrivateKey: string;
26
- sellerPrivateKey: string;
25
+ buyerPrivateKey?: string;
26
+ buyerPrivateKeys?: string[];
27
+ sellerPrivateKey?: string;
28
+ sellerPrivateKeys?: string[];
27
29
  tokenAddress: string;
28
30
  buyerFunds?: string;
29
31
  buyerFundsPercentage?: number;
@@ -100,10 +100,29 @@ function getNativeTokenName(chain) {
100
100
  }
101
101
  // 使用公共工具的 getGasLimit,移除本地重复实现
102
102
  export async function flapBundleBuyFirstMerkle(params) {
103
- const { chain, buyerPrivateKey, sellerPrivateKey, tokenAddress, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals, buyCount: _buyCount, sellCount: _sellCount } = params;
104
- // ✅ 解析并验证买卖笔数
103
+ const { chain, buyerPrivateKey, buyerPrivateKeys, sellerPrivateKey, sellerPrivateKeys, tokenAddress, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals, buyCount: _buyCount, sellCount: _sellCount } = params;
104
+ // ✅ 判断是否为多钱包模式
105
+ const isMultiWalletMode = !!(buyerPrivateKeys && buyerPrivateKeys.length > 0) ||
106
+ !!(sellerPrivateKeys && sellerPrivateKeys.length > 0);
105
107
  const buyCount = _buyCount ?? 1;
106
108
  const sellCount = _sellCount ?? 1;
109
+ // ✅ 多钱包模式:使用单独的处理逻辑
110
+ if (isMultiWalletMode) {
111
+ return await flapBundleBuyFirstMultiWallet({
112
+ chain,
113
+ buyerPrivateKeys: buyerPrivateKeys || (buyerPrivateKey ? [buyerPrivateKey] : []),
114
+ sellerPrivateKeys: sellerPrivateKeys || (sellerPrivateKey ? [sellerPrivateKey] : []),
115
+ tokenAddress,
116
+ buyerFunds,
117
+ config,
118
+ quoteToken,
119
+ quoteTokenDecimals
120
+ });
121
+ }
122
+ // ✅ 单钱包模式(向后兼容)
123
+ if (!buyerPrivateKey || !sellerPrivateKey) {
124
+ throw new Error('单钱包模式需要提供 buyerPrivateKey 和 sellerPrivateKey');
125
+ }
107
126
  validateSwapCounts(buyCount, sellCount);
108
127
  // ✅ 计算利润比例:每笔万分之3
109
128
  const totalTxCount = buyCount + sellCount;
@@ -585,3 +604,211 @@ function createChainContext(chain, rpcUrl) {
585
604
  }
586
605
  return { chainId, nativeToken, portalAddress, provider };
587
606
  }
607
+ /**
608
+ * ✅ Flap 多钱包捆绑换手
609
+ * - 多个买方钱包执行买入(每个钱包1笔)
610
+ * - 多个卖方钱包执行卖出(每个钱包1笔)
611
+ * - 买入总价值 = 卖出总价值
612
+ * - ✅ 优化:最大化并行操作
613
+ */
614
+ async function flapBundleBuyFirstMultiWallet(params) {
615
+ const { chain, buyerPrivateKeys, sellerPrivateKeys, tokenAddress, buyerFunds, config, quoteToken, quoteTokenDecimals = 18 } = params;
616
+ const buyCount = buyerPrivateKeys.length;
617
+ const sellCount = sellerPrivateKeys.length;
618
+ if (buyCount === 0)
619
+ throw new Error('买方钱包数量不能为0');
620
+ if (sellCount === 0)
621
+ throw new Error('卖方钱包数量不能为0');
622
+ // 验证总交易数不超过限制
623
+ validateSwapCounts(buyCount, sellCount);
624
+ // ✅ 计算利润比例:每笔万分之6
625
+ const totalTxCount = buyCount + sellCount;
626
+ const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
627
+ const chainContext = createChainContext(chain, config.rpcUrl);
628
+ const nonceManager = new NonceManager(chainContext.provider);
629
+ // 创建所有钱包实例
630
+ const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
631
+ const sellers = sellerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
632
+ // 使用第一个卖方作为主卖方(支付贿赂和利润)
633
+ const mainSeller = sellers[0];
634
+ // ✅ 判断是否使用原生代币
635
+ const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
636
+ const inputToken = useNativeToken ? ZERO_ADDRESS : quoteToken;
637
+ const outputToken = inputToken;
638
+ // ✅ 计算总交易金额
639
+ if (!buyerFunds) {
640
+ throw new Error('多钱包模式必须提供 buyerFunds(总交易金额)');
641
+ }
642
+ const totalFundsWei = useNativeToken
643
+ ? ethers.parseEther(String(buyerFunds))
644
+ : ethers.parseUnits(String(buyerFunds), quoteTokenDecimals);
645
+ if (totalFundsWei <= 0n) {
646
+ throw new Error('交易金额必须大于0');
647
+ }
648
+ const finalGasLimit = getGasLimit(config);
649
+ const txType = getTxType(config);
650
+ const bribeAmount = getBribeAmount(config);
651
+ const needBribeTx = bribeAmount > 0n;
652
+ // ✅ 第一批并行:报价 + Gas价格 + 代币精度 + 所有钱包 nonces
653
+ // 预先计算所有唯一钱包地址(去重)
654
+ const allWallets = [...sellers, ...buyers];
655
+ const uniqueWallets = allWallets.filter((w, i) => {
656
+ const addr = w.address.toLowerCase();
657
+ const firstIdx = allWallets.findIndex(x => x.address.toLowerCase() === addr);
658
+ return firstIdx === i;
659
+ });
660
+ const erc20 = new Contract(tokenAddress, ERC20_BALANCE_ABI, chainContext.provider);
661
+ const [quoteResult, gasPrice, decimals, noncesArray] = await Promise.all([
662
+ quoteBuyerOutput({
663
+ portalAddress: chainContext.portalAddress,
664
+ tokenAddress,
665
+ buyerFundsWei: totalFundsWei,
666
+ provider: chainContext.provider,
667
+ skipQuoteOnError: config.skipQuoteOnError,
668
+ inputToken
669
+ }),
670
+ getOptimizedGasPrice(chainContext.provider, getGasPriceConfig(config)),
671
+ erc20.decimals(),
672
+ nonceManager.getNextNoncesForWallets(uniqueWallets)
673
+ ]);
674
+ // 构建 nonces Map
675
+ const noncesMap = new Map();
676
+ uniqueWallets.forEach((wallet, i) => {
677
+ noncesMap.set(wallet.address.toLowerCase(), noncesArray[i]);
678
+ });
679
+ const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
680
+ // ✅ 将总金额平均分配给买方
681
+ const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
682
+ // ✅ 将代币平均分配给卖方
683
+ const sellAmountsWei = splitAmount(quoteResult.sellAmountWei, sellCount);
684
+ // ✅ 第二批并行:估算利润 + ERC20 转原生代币报价(如需要)
685
+ const portal = new Contract(chainContext.portalAddress, PORTAL_ABI, chainContext.provider);
686
+ const estimatedSellFunds = await estimateSellFunds(portal, tokenAddress, quoteResult.sellAmountWei, outputToken);
687
+ const profitBase = estimatedSellFunds > 0n ? estimatedSellFunds : totalFundsWei;
688
+ const tokenProfitAmount = (profitBase * BigInt(profitRateBps)) / 10000n;
689
+ // ERC20 购买:获取代币利润等值的原生代币报价
690
+ let nativeProfitAmount = tokenProfitAmount;
691
+ if (!useNativeToken && tokenProfitAmount > 0n) {
692
+ nativeProfitAmount = await getTokenToNativeQuote(chainContext.provider, inputToken, tokenProfitAmount, chainContext.chainId);
693
+ }
694
+ // ✅ 第三批并行:构建并签名所有交易
695
+ const allTransactions = [];
696
+ // 1. 贿赂交易(由主卖方支付)- 先处理以确定 nonce 偏移
697
+ let bribeTxPromise = null;
698
+ if (needBribeTx) {
699
+ const mainSellerAddr = mainSeller.address.toLowerCase();
700
+ const bribeNonce = noncesMap.get(mainSellerAddr);
701
+ noncesMap.set(mainSellerAddr, bribeNonce + 1);
702
+ bribeTxPromise = mainSeller.signTransaction({
703
+ to: BLOCKRAZOR_BUILDER_EOA,
704
+ value: bribeAmount,
705
+ nonce: bribeNonce,
706
+ gasPrice,
707
+ gasLimit: 21000n,
708
+ chainId: chainContext.chainId,
709
+ type: txType
710
+ });
711
+ }
712
+ // 2. 预分配所有 nonces(同步操作,避免竞态)
713
+ const buyerNonces = [];
714
+ const sellerNonces = [];
715
+ buyers.forEach(buyer => {
716
+ const addr = buyer.address.toLowerCase();
717
+ const nonce = noncesMap.get(addr);
718
+ buyerNonces.push(nonce);
719
+ noncesMap.set(addr, nonce + 1);
720
+ });
721
+ sellers.forEach(seller => {
722
+ const addr = seller.address.toLowerCase();
723
+ const nonce = noncesMap.get(addr);
724
+ sellerNonces.push(nonce);
725
+ noncesMap.set(addr, nonce + 1);
726
+ });
727
+ // 3. 并行构建所有买入交易
728
+ const buyTxPromises = buyers.map(async (buyer, i) => {
729
+ const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
730
+ const unsigned = await portalBuyer.swapExactInput.populateTransaction({
731
+ inputToken,
732
+ outputToken: tokenAddress,
733
+ inputAmount: buyAmountsWei[i],
734
+ minOutputAmount: 0n,
735
+ permitData: '0x'
736
+ }, useNativeToken ? { value: buyAmountsWei[i] } : {});
737
+ return buyer.signTransaction(buildTransactionRequest(unsigned, {
738
+ from: buyer.address,
739
+ nonce: buyerNonces[i],
740
+ gasLimit: finalGasLimit,
741
+ gasPrice,
742
+ priorityFee,
743
+ chainId: chainContext.chainId,
744
+ txType,
745
+ value: useNativeToken ? buyAmountsWei[i] : 0n
746
+ }));
747
+ });
748
+ // 4. 并行构建所有卖出交易
749
+ const sellTxPromises = sellers.map(async (seller, i) => {
750
+ const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
751
+ const unsigned = await portalSeller.swapExactInput.populateTransaction({
752
+ inputToken: tokenAddress,
753
+ outputToken,
754
+ inputAmount: sellAmountsWei[i],
755
+ minOutputAmount: 0n,
756
+ permitData: '0x'
757
+ });
758
+ return seller.signTransaction(buildTransactionRequest(unsigned, {
759
+ from: seller.address,
760
+ nonce: sellerNonces[i],
761
+ gasLimit: finalGasLimit,
762
+ gasPrice,
763
+ priorityFee,
764
+ chainId: chainContext.chainId,
765
+ txType,
766
+ value: 0n
767
+ }));
768
+ });
769
+ // ✅ 并行签名所有交易(贿赂 + 买入 + 卖出)
770
+ const [bribeTx, signedBuys, signedSells] = await Promise.all([
771
+ bribeTxPromise,
772
+ Promise.all(buyTxPromises),
773
+ Promise.all(sellTxPromises)
774
+ ]);
775
+ // 组装交易顺序:贿赂 → 买入 → 卖出
776
+ if (bribeTx)
777
+ allTransactions.push(bribeTx);
778
+ allTransactions.push(...signedBuys, ...signedSells);
779
+ // 5. 利润多跳转账(由主卖方支付)
780
+ let profitHopWallets;
781
+ if (nativeProfitAmount > 0n) {
782
+ const mainSellerAddr = mainSeller.address.toLowerCase();
783
+ const profitNonce = noncesMap.get(mainSellerAddr);
784
+ const profitResult = await buildProfitTransaction({
785
+ provider: chainContext.provider,
786
+ seller: mainSeller,
787
+ profitAmount: nativeProfitAmount,
788
+ profitNonce,
789
+ gasPrice,
790
+ chainId: chainContext.chainId,
791
+ txType
792
+ });
793
+ if (profitResult) {
794
+ allTransactions.push(...profitResult.signedTransactions);
795
+ profitHopWallets = profitResult.hopWallets;
796
+ }
797
+ }
798
+ nonceManager.clearTemp();
799
+ return {
800
+ signedTransactions: allTransactions,
801
+ profitHopWallets,
802
+ metadata: {
803
+ buyerAddress: buyers.map(b => b.address).join(','),
804
+ sellerAddress: sellers.map(s => s.address).join(','),
805
+ buyAmount: ethers.formatEther(totalFundsWei),
806
+ sellAmount: ethers.formatUnits(quoteResult.sellAmountWei, decimals),
807
+ profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined,
808
+ buyCount,
809
+ sellCount,
810
+ buyAmounts: buyAmountsWei.map(amt => ethers.formatEther(amt)),
811
+ sellAmounts: sellAmountsWei.map(amt => ethers.formatUnits(amt, decimals))
812
+ }
813
+ };
814
+ }
@@ -45,8 +45,10 @@ export interface PancakeBuyFirstConfig extends CommonBundleConfig {
45
45
  waitTimeoutMs?: number;
46
46
  }
47
47
  export interface PancakeBundleBuyFirstSignParams {
48
- buyerPrivateKey: string;
49
- sellerPrivateKey: string;
48
+ buyerPrivateKey?: string;
49
+ buyerPrivateKeys?: string[];
50
+ sellerPrivateKey?: string;
51
+ sellerPrivateKeys?: string[];
50
52
  tokenAddress: string;
51
53
  routeParams: RouteParams;
52
54
  buyerFunds?: string;
@@ -109,11 +109,32 @@ function splitAmount(totalAmount, count) {
109
109
  return amounts;
110
110
  }
111
111
  export async function pancakeBundleBuyFirstMerkle(params) {
112
- const { buyerPrivateKey, sellerPrivateKey, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18, startNonces, // ✅ 可选:前端预获取的 nonces
112
+ const { buyerPrivateKey, buyerPrivateKeys, sellerPrivateKey, sellerPrivateKeys, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18, startNonces, // ✅ 可选:前端预获取的 nonces
113
113
  buyCount: _buyCount, sellCount: _sellCount } = params;
114
- // ✅ 解析并验证买卖笔数
114
+ // ✅ 判断是否为多钱包模式
115
+ const isMultiWalletMode = !!(buyerPrivateKeys && buyerPrivateKeys.length > 0) ||
116
+ !!(sellerPrivateKeys && sellerPrivateKeys.length > 0);
117
+ // ✅ 多钱包模式:buyCount/sellCount 代表钱包数量
118
+ // 单钱包模式(向后兼容):buyCount/sellCount 代表同一钱包执行的交易笔数
115
119
  const buyCount = _buyCount ?? 1;
116
120
  const sellCount = _sellCount ?? 1;
121
+ // ✅ 多钱包模式:使用单独的处理逻辑
122
+ if (isMultiWalletMode) {
123
+ return await pancakeBundleBuyFirstMultiWallet({
124
+ buyerPrivateKeys: buyerPrivateKeys || (buyerPrivateKey ? [buyerPrivateKey] : []),
125
+ sellerPrivateKeys: sellerPrivateKeys || (sellerPrivateKey ? [sellerPrivateKey] : []),
126
+ tokenAddress,
127
+ routeParams,
128
+ buyerFunds,
129
+ config,
130
+ quoteToken,
131
+ quoteTokenDecimals
132
+ });
133
+ }
134
+ // ✅ 单钱包模式(向后兼容):验证买卖笔数
135
+ if (!buyerPrivateKey || !sellerPrivateKey) {
136
+ throw new Error('单钱包模式需要提供 buyerPrivateKey 和 sellerPrivateKey');
137
+ }
117
138
  validateSwapCounts(buyCount, sellCount);
118
139
  // ✅ 计算利润比例:每笔万分之3
119
140
  const totalTxCount = buyCount + sellCount;
@@ -728,3 +749,285 @@ function buildNoncePlanFromStartNonces(startNonces, sameAddress, profitNeeded, n
728
749
  const buyerNonce = startNonces[1];
729
750
  return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
730
751
  }
752
+ /**
753
+ * ✅ 多钱包捆绑换手
754
+ * - 多个买方钱包执行买入(每个钱包1笔)
755
+ * - 多个卖方钱包执行卖出(每个钱包1笔)
756
+ * - 买入总价值 = 卖出总价值
757
+ * - ✅ 优化:最大化并行操作
758
+ */
759
+ async function pancakeBundleBuyFirstMultiWallet(params) {
760
+ const { buyerPrivateKeys, sellerPrivateKeys, tokenAddress, routeParams, buyerFunds, config, quoteToken, quoteTokenDecimals = 18 } = params;
761
+ const buyCount = buyerPrivateKeys.length;
762
+ const sellCount = sellerPrivateKeys.length;
763
+ if (buyCount === 0)
764
+ throw new Error('买方钱包数量不能为0');
765
+ if (sellCount === 0)
766
+ throw new Error('卖方钱包数量不能为0');
767
+ // 验证总交易数不超过限制
768
+ validateSwapCounts(buyCount, sellCount);
769
+ // ✅ 计算利润比例:每笔万分之6
770
+ const totalTxCount = buyCount + sellCount;
771
+ const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
772
+ // ✅ 判断是否使用原生代币
773
+ const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
774
+ const context = createPancakeContext(config);
775
+ const nonceManager = new NonceManager(context.provider);
776
+ // 创建所有钱包实例
777
+ const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider));
778
+ const sellers = sellerPrivateKeys.map(pk => new Wallet(pk, context.provider));
779
+ // 使用第一个卖方作为主卖方(支付贿赂和利润)
780
+ const mainSeller = sellers[0];
781
+ // ✅ 计算总交易金额
782
+ let totalFundsWei;
783
+ if (buyerFunds) {
784
+ totalFundsWei = useNativeToken
785
+ ? ethers.parseEther(String(buyerFunds))
786
+ : ethers.parseUnits(String(buyerFunds), quoteTokenDecimals);
787
+ }
788
+ else {
789
+ throw new Error('多钱包模式必须提供 buyerFunds(总交易金额)');
790
+ }
791
+ if (totalFundsWei <= 0n) {
792
+ throw new Error('交易金额必须大于0');
793
+ }
794
+ const finalGasLimit = getGasLimit(config);
795
+ const txType = config.txType ?? 0;
796
+ const deadline = BigInt(getDeadline());
797
+ const bribeAmount = config.bribeAmount && config.bribeAmount > 0
798
+ ? ethers.parseEther(String(config.bribeAmount))
799
+ : 0n;
800
+ const needBribeTx = bribeAmount > 0n;
801
+ // ✅ 第一批并行:报价 + Gas价格 + 所有钱包 nonces
802
+ // 预先计算所有唯一钱包地址(去重)
803
+ const allWallets = [...sellers, ...buyers];
804
+ const uniqueWallets = allWallets.filter((w, i) => {
805
+ const addr = w.address.toLowerCase();
806
+ const firstIdx = allWallets.findIndex(x => x.address.toLowerCase() === addr);
807
+ return firstIdx === i;
808
+ });
809
+ const [quoteResult, gasPrice, noncesArray] = await Promise.all([
810
+ quoteTokenOutput({
811
+ routeParams,
812
+ buyerFundsWei: totalFundsWei,
813
+ provider: context.provider
814
+ }),
815
+ getGasPrice(context.provider, config),
816
+ nonceManager.getNextNoncesForWallets(uniqueWallets)
817
+ ]);
818
+ // 构建 nonces Map
819
+ const noncesMap = new Map();
820
+ uniqueWallets.forEach((wallet, i) => {
821
+ noncesMap.set(wallet.address.toLowerCase(), noncesArray[i]);
822
+ });
823
+ // ✅ 将总金额平均分配给买方
824
+ const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
825
+ // ✅ 将代币平均分配给卖方
826
+ const sellAmountsWei = splitAmount(quoteResult.quotedTokenOut, sellCount);
827
+ // ✅ 第二批并行:估算利润(可以和交易构建并行,但需要先获取报价)
828
+ const estimatedProfitFromSell = await estimateProfitAmount({
829
+ provider: context.provider,
830
+ tokenAddress,
831
+ sellAmountToken: quoteResult.quotedTokenOut,
832
+ routeParams
833
+ });
834
+ const profitBase = estimatedProfitFromSell > 0n ? estimatedProfitFromSell : totalFundsWei;
835
+ const profitAmount = (profitBase * BigInt(profitRateBps)) / 10000n;
836
+ // ✅ 第三批并行:构建并签名所有交易
837
+ const allTransactions = [];
838
+ // 1. 贿赂交易(由主卖方支付)- 先处理以确定 nonce 偏移
839
+ let bribeTxPromise = null;
840
+ if (needBribeTx) {
841
+ const mainSellerAddr = mainSeller.address.toLowerCase();
842
+ const bribeNonce = noncesMap.get(mainSellerAddr);
843
+ noncesMap.set(mainSellerAddr, bribeNonce + 1);
844
+ bribeTxPromise = mainSeller.signTransaction({
845
+ to: BLOCKRAZOR_BUILDER_EOA,
846
+ value: bribeAmount,
847
+ nonce: bribeNonce,
848
+ gasPrice,
849
+ gasLimit: 21000n,
850
+ chainId: context.chainId,
851
+ type: txType
852
+ });
853
+ }
854
+ // 2. 预分配所有 nonces(同步操作,避免竞态)
855
+ const buyerNonces = [];
856
+ const sellerNonces = [];
857
+ buyers.forEach(buyer => {
858
+ const addr = buyer.address.toLowerCase();
859
+ const nonce = noncesMap.get(addr);
860
+ buyerNonces.push(nonce);
861
+ noncesMap.set(addr, nonce + 1);
862
+ });
863
+ sellers.forEach(seller => {
864
+ const addr = seller.address.toLowerCase();
865
+ const nonce = noncesMap.get(addr);
866
+ sellerNonces.push(nonce);
867
+ noncesMap.set(addr, nonce + 1);
868
+ });
869
+ // 3. 并行构建所有买入交易
870
+ const buyTxPromises = buyers.map(async (buyer, i) => {
871
+ const unsigned = await buildSingleBuyTx({
872
+ routeParams,
873
+ buyAmount: buyAmountsWei[i],
874
+ buyer,
875
+ tokenAddress,
876
+ useNativeToken,
877
+ deadline
878
+ });
879
+ return buyer.signTransaction({
880
+ ...unsigned,
881
+ from: buyer.address,
882
+ nonce: buyerNonces[i],
883
+ gasLimit: finalGasLimit,
884
+ gasPrice,
885
+ chainId: context.chainId,
886
+ type: txType
887
+ });
888
+ });
889
+ // 4. 并行构建所有卖出交易
890
+ const sellTxPromises = sellers.map(async (seller, i) => {
891
+ const unsigned = await buildSingleSellTx({
892
+ routeParams,
893
+ sellAmount: sellAmountsWei[i],
894
+ seller,
895
+ tokenAddress,
896
+ useNativeToken,
897
+ deadline
898
+ });
899
+ return seller.signTransaction({
900
+ ...unsigned,
901
+ from: seller.address,
902
+ nonce: sellerNonces[i],
903
+ gasLimit: finalGasLimit,
904
+ gasPrice,
905
+ chainId: context.chainId,
906
+ type: txType
907
+ });
908
+ });
909
+ // ✅ 并行签名所有交易(贿赂 + 买入 + 卖出)
910
+ const [bribeTx, signedBuys, signedSells] = await Promise.all([
911
+ bribeTxPromise,
912
+ Promise.all(buyTxPromises),
913
+ Promise.all(sellTxPromises)
914
+ ]);
915
+ // 组装交易顺序:贿赂 → 买入 → 卖出
916
+ if (bribeTx)
917
+ allTransactions.push(bribeTx);
918
+ allTransactions.push(...signedBuys, ...signedSells);
919
+ // 5. 利润多跳转账(由主卖方支付)
920
+ let profitHopWallets;
921
+ if (profitAmount > 0n) {
922
+ const mainSellerAddr = mainSeller.address.toLowerCase();
923
+ const profitNonce = noncesMap.get(mainSellerAddr);
924
+ const profitResult = await buildProfitTransaction({
925
+ provider: context.provider,
926
+ seller: mainSeller,
927
+ profitAmount,
928
+ profitNonce,
929
+ gasPrice,
930
+ chainId: context.chainId,
931
+ txType
932
+ });
933
+ if (profitResult) {
934
+ allTransactions.push(...profitResult.signedTransactions);
935
+ profitHopWallets = profitResult.hopWallets;
936
+ }
937
+ }
938
+ nonceManager.clearTemp();
939
+ return {
940
+ signedTransactions: allTransactions,
941
+ profitHopWallets,
942
+ metadata: {
943
+ buyerAddress: buyers.map(b => b.address).join(','),
944
+ sellerAddress: sellers.map(s => s.address).join(','),
945
+ buyAmount: useNativeToken
946
+ ? ethers.formatEther(totalFundsWei)
947
+ : ethers.formatUnits(totalFundsWei, quoteTokenDecimals),
948
+ sellAmount: quoteResult.quotedTokenOut.toString(),
949
+ profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
950
+ buyCount,
951
+ sellCount,
952
+ buyAmounts: buyAmountsWei.map(amt => useNativeToken
953
+ ? ethers.formatEther(amt)
954
+ : ethers.formatUnits(amt, quoteTokenDecimals)),
955
+ sellAmounts: sellAmountsWei.map(amt => amt.toString())
956
+ }
957
+ };
958
+ }
959
+ // ✅ 构建单笔买入交易
960
+ async function buildSingleBuyTx({ routeParams, buyAmount, buyer, tokenAddress, useNativeToken, deadline }) {
961
+ if (routeParams.routeType === 'v2') {
962
+ const { v2Path } = routeParams;
963
+ const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, buyer);
964
+ if (useNativeToken) {
965
+ return await v2Router.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, v2Path, buyer.address, deadline, { value: buyAmount });
966
+ }
967
+ else {
968
+ return await v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmount, 0n, v2Path, buyer.address, deadline);
969
+ }
970
+ }
971
+ if (routeParams.routeType === 'v3-single') {
972
+ const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
973
+ const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
974
+ const v3Router = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, buyer);
975
+ const buyValue = useNativeToken ? buyAmount : 0n;
976
+ const buySwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
977
+ tokenIn: v3TokenIn,
978
+ tokenOut: v3TokenOut,
979
+ fee: v3Fee,
980
+ recipient: buyer.address,
981
+ amountIn: buyAmount,
982
+ amountOutMinimum: 0n,
983
+ sqrtPriceLimitX96: 0n
984
+ }]);
985
+ return await v3Router.multicall.populateTransaction(deadline, [buySwapData], { value: buyValue });
986
+ }
987
+ throw new Error('V3 多跳路由暂不支持');
988
+ }
989
+ // ✅ 构建单笔卖出交易
990
+ async function buildSingleSellTx({ routeParams, sellAmount, seller, tokenAddress, useNativeToken, deadline }) {
991
+ if (routeParams.routeType === 'v2') {
992
+ const { v2Path } = routeParams;
993
+ const reversePath = [...v2Path].reverse();
994
+ const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, seller);
995
+ if (useNativeToken) {
996
+ return await v2Router.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmount, 0n, reversePath, seller.address, deadline);
997
+ }
998
+ else {
999
+ return await v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmount, 0n, reversePath, seller.address, deadline);
1000
+ }
1001
+ }
1002
+ if (routeParams.routeType === 'v3-single') {
1003
+ const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
1004
+ const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
1005
+ const v3Router = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, seller);
1006
+ if (useNativeToken) {
1007
+ const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
1008
+ tokenIn: v3TokenOut,
1009
+ tokenOut: v3TokenIn,
1010
+ fee: v3Fee,
1011
+ recipient: PANCAKE_V3_ROUTER_ADDRESS,
1012
+ amountIn: sellAmount,
1013
+ amountOutMinimum: 0n,
1014
+ sqrtPriceLimitX96: 0n
1015
+ }]);
1016
+ const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]);
1017
+ return await v3Router.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]);
1018
+ }
1019
+ else {
1020
+ const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
1021
+ tokenIn: v3TokenOut,
1022
+ tokenOut: v3TokenIn,
1023
+ fee: v3Fee,
1024
+ recipient: seller.address,
1025
+ amountIn: sellAmount,
1026
+ amountOutMinimum: 0n,
1027
+ sqrtPriceLimitX96: 0n
1028
+ }]);
1029
+ return await v3Router.multicall.populateTransaction(deadline, [sellSwapData]);
1030
+ }
1031
+ }
1032
+ throw new Error('V3 多跳路由暂不支持');
1033
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.78",
3
+ "version": "1.4.80",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",