four-flap-meme-sdk 1.4.78 → 1.4.79

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,176 @@ 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
+ async function fourBundleBuyFirstMultiWallet(params) {
318
+ const { buyerPrivateKeys, sellerPrivateKeys, tokenAddress, buyerFunds, config } = params;
319
+ const buyCount = buyerPrivateKeys.length;
320
+ const sellCount = sellerPrivateKeys.length;
321
+ if (buyCount === 0)
322
+ throw new Error('买方钱包数量不能为0');
323
+ if (sellCount === 0)
324
+ throw new Error('卖方钱包数量不能为0');
325
+ // 验证总交易数不超过限制
326
+ validateSwapCounts(buyCount, sellCount);
327
+ // ✅ 计算利润比例:每笔万分之6
328
+ const totalTxCount = buyCount + sellCount;
329
+ const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
330
+ const chainIdNum = config.chainId ?? 56;
331
+ const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
332
+ chainId: chainIdNum,
333
+ name: 'BSC'
334
+ });
335
+ const nonceManager = new NonceManager(provider);
336
+ // 创建所有钱包实例
337
+ const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, provider));
338
+ const sellers = sellerPrivateKeys.map(pk => new Wallet(pk, provider));
339
+ // 使用第一个卖方作为主卖方(支付贿赂和利润)
340
+ const mainSeller = sellers[0];
341
+ // ✅ 计算总交易金额
342
+ if (!buyerFunds) {
343
+ throw new Error('多钱包模式必须提供 buyerFunds(总交易金额)');
344
+ }
345
+ const totalFundsWei = ethers.parseEther(String(buyerFunds));
346
+ if (totalFundsWei <= 0n) {
347
+ throw new Error('交易金额必须大于0');
348
+ }
349
+ // ✅ 获取报价:买入能获得多少代币
350
+ const helper3 = new Contract(ADDRESSES.BSC.TokenManagerHelper3, HELPER3_ABI, provider);
351
+ const buyQuote = await helper3.tryBuy(tokenAddress, 0n, totalFundsWei);
352
+ const estimatedTokenAmount = buyQuote.estimatedAmount ?? buyQuote[2];
353
+ if (!estimatedTokenAmount || estimatedTokenAmount <= 0n) {
354
+ throw new Error('报价失败:无法估算可买入的代币数量');
355
+ }
356
+ // ✅ 将总金额平均分配给买方
357
+ const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
358
+ // ✅ 将代币平均分配给卖方
359
+ const sellAmountsWei = splitAmount(estimatedTokenAmount, sellCount);
360
+ const finalGasLimit = getGasLimit(config);
361
+ const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
362
+ const txType = getTxType(config);
363
+ // ✅ 估算利润
364
+ const sellResult = await trySell('BSC', config.rpcUrl, tokenAddress, estimatedTokenAmount);
365
+ const estimatedSellFunds = sellResult.funds;
366
+ const profitAmount = (estimatedSellFunds * BigInt(profitRateBps)) / 10000n;
367
+ // ✅ 获取贿赂金额
368
+ const bribeAmount = getBribeAmount(config);
369
+ const needBribeTx = bribeAmount > 0n;
370
+ // ✅ 获取所有钱包的 nonces
371
+ const noncesMap = new Map();
372
+ await Promise.all([...sellers, ...buyers].map(async (wallet) => {
373
+ const addr = wallet.address.toLowerCase();
374
+ if (!noncesMap.has(addr)) {
375
+ const nonce = await nonceManager.getNextNonce(wallet);
376
+ noncesMap.set(addr, nonce);
377
+ }
378
+ }));
379
+ // ✅ 构建交易列表
380
+ const allTransactions = [];
381
+ // 1. 贿赂交易(由主卖方支付)
382
+ if (needBribeTx) {
383
+ const mainSellerAddr = mainSeller.address.toLowerCase();
384
+ const bribeNonce = noncesMap.get(mainSellerAddr);
385
+ noncesMap.set(mainSellerAddr, bribeNonce + 1);
386
+ const bribeTx = await mainSeller.signTransaction({
387
+ to: BLOCKRAZOR_BUILDER_EOA,
388
+ value: bribeAmount,
389
+ nonce: bribeNonce,
390
+ gasPrice,
391
+ gasLimit: 21000n,
392
+ chainId: chainIdNum,
393
+ type: txType
394
+ });
395
+ allTransactions.push(bribeTx);
396
+ }
397
+ // 2. 构建所有买入交易
398
+ const tm = new Contract(TM_ADDRESS, TM_ABI, provider);
399
+ const buyTxPromises = buyers.map(async (buyer, i) => {
400
+ const buyAmount = buyAmountsWei[i];
401
+ const buyerAddr = buyer.address.toLowerCase();
402
+ const nonce = noncesMap.get(buyerAddr);
403
+ noncesMap.set(buyerAddr, nonce + 1);
404
+ const tmBuyer = tm.connect(buyer);
405
+ const unsigned = await tmBuyer.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyer.address, buyAmount, 0n, { value: buyAmount });
406
+ return buyer.signTransaction({
407
+ ...unsigned,
408
+ from: buyer.address,
409
+ nonce,
410
+ gasLimit: finalGasLimit,
411
+ gasPrice,
412
+ chainId: chainIdNum,
413
+ type: txType,
414
+ value: buyAmount
415
+ });
416
+ });
417
+ // 3. 构建所有卖出交易
418
+ const sellTxPromises = sellers.map(async (seller, i) => {
419
+ const sellAmount = sellAmountsWei[i];
420
+ const sellerAddr = seller.address.toLowerCase();
421
+ const nonce = noncesMap.get(sellerAddr);
422
+ noncesMap.set(sellerAddr, nonce + 1);
423
+ const tmSeller = tm.connect(seller);
424
+ const unsigned = await tmSeller.sellToken.populateTransaction(0n, tokenAddress, sellAmount, 0n);
425
+ return seller.signTransaction({
426
+ ...unsigned,
427
+ from: seller.address,
428
+ nonce,
429
+ gasLimit: finalGasLimit,
430
+ gasPrice,
431
+ chainId: chainIdNum,
432
+ type: txType,
433
+ value: 0n
434
+ });
435
+ });
436
+ // ✅ 并行签名所有买卖交易
437
+ const [signedBuys, signedSells] = await Promise.all([
438
+ Promise.all(buyTxPromises),
439
+ Promise.all(sellTxPromises)
440
+ ]);
441
+ // 先买后卖:买入交易在前
442
+ allTransactions.push(...signedBuys, ...signedSells);
443
+ // 4. 利润多跳转账(由主卖方支付)
444
+ let profitHopWallets;
445
+ if (profitAmount > 0n) {
446
+ const mainSellerAddr = mainSeller.address.toLowerCase();
447
+ const profitNonce = noncesMap.get(mainSellerAddr);
448
+ const profitHopResult = await buildProfitHopTransactions({
449
+ provider,
450
+ payerWallet: mainSeller,
451
+ profitAmount,
452
+ profitRecipient: getProfitRecipient(),
453
+ hopCount: PROFIT_HOP_COUNT,
454
+ gasPrice,
455
+ chainId: chainIdNum,
456
+ txType,
457
+ startNonce: profitNonce
458
+ });
459
+ allTransactions.push(...profitHopResult.signedTransactions);
460
+ profitHopWallets = profitHopResult.hopWallets;
461
+ }
462
+ nonceManager.clearTemp();
463
+ // 获取代币精度用于显示
464
+ const erc20 = new Contract(tokenAddress, [
465
+ 'function decimals() view returns (uint8)'
466
+ ], provider);
467
+ const decimals = await erc20.decimals();
468
+ return {
469
+ signedTransactions: allTransactions,
470
+ profitHopWallets,
471
+ metadata: {
472
+ buyerAddress: buyers.map(b => b.address).join(','),
473
+ sellerAddress: sellers.map(s => s.address).join(','),
474
+ buyAmount: ethers.formatEther(totalFundsWei),
475
+ sellAmount: ethers.formatUnits(estimatedTokenAmount, decimals),
476
+ profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
477
+ buyCount,
478
+ sellCount,
479
+ buyAmounts: buyAmountsWei.map(amt => ethers.formatEther(amt)),
480
+ sellAmounts: sellAmountsWei.map(amt => ethers.formatUnits(amt, decimals))
481
+ }
482
+ };
483
+ }
@@ -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,201 @@ 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
+ async function flapBundleBuyFirstMultiWallet(params) {
614
+ const { chain, buyerPrivateKeys, sellerPrivateKeys, tokenAddress, buyerFunds, config, quoteToken, quoteTokenDecimals = 18 } = params;
615
+ const buyCount = buyerPrivateKeys.length;
616
+ const sellCount = sellerPrivateKeys.length;
617
+ if (buyCount === 0)
618
+ throw new Error('买方钱包数量不能为0');
619
+ if (sellCount === 0)
620
+ throw new Error('卖方钱包数量不能为0');
621
+ // 验证总交易数不超过限制
622
+ validateSwapCounts(buyCount, sellCount);
623
+ // ✅ 计算利润比例:每笔万分之6
624
+ const totalTxCount = buyCount + sellCount;
625
+ const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
626
+ const chainContext = createChainContext(chain, config.rpcUrl);
627
+ const nonceManager = new NonceManager(chainContext.provider);
628
+ // 创建所有钱包实例
629
+ const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
630
+ const sellers = sellerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
631
+ // 使用第一个卖方作为主卖方(支付贿赂和利润)
632
+ const mainSeller = sellers[0];
633
+ // ✅ 判断是否使用原生代币
634
+ const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
635
+ const inputToken = useNativeToken ? ZERO_ADDRESS : quoteToken;
636
+ const outputToken = inputToken;
637
+ // ✅ 计算总交易金额
638
+ if (!buyerFunds) {
639
+ throw new Error('多钱包模式必须提供 buyerFunds(总交易金额)');
640
+ }
641
+ const totalFundsWei = useNativeToken
642
+ ? ethers.parseEther(String(buyerFunds))
643
+ : ethers.parseUnits(String(buyerFunds), quoteTokenDecimals);
644
+ if (totalFundsWei <= 0n) {
645
+ throw new Error('交易金额必须大于0');
646
+ }
647
+ // ✅ 获取报价:买入能获得多少代币
648
+ const quoteResult = await quoteBuyerOutput({
649
+ portalAddress: chainContext.portalAddress,
650
+ tokenAddress,
651
+ buyerFundsWei: totalFundsWei,
652
+ provider: chainContext.provider,
653
+ skipQuoteOnError: config.skipQuoteOnError,
654
+ inputToken
655
+ });
656
+ // ✅ 将总金额平均分配给买方
657
+ const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
658
+ // ✅ 将代币平均分配给卖方
659
+ const sellAmountsWei = splitAmount(quoteResult.sellAmountWei, sellCount);
660
+ const finalGasLimit = getGasLimit(config);
661
+ const gasPrice = await getOptimizedGasPrice(chainContext.provider, getGasPriceConfig(config));
662
+ const txType = getTxType(config);
663
+ const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
664
+ // ✅ 估算利润
665
+ const portal = new Contract(chainContext.portalAddress, PORTAL_ABI, chainContext.provider);
666
+ const estimatedSellFunds = await estimateSellFunds(portal, tokenAddress, quoteResult.sellAmountWei, outputToken);
667
+ const profitBase = estimatedSellFunds > 0n ? estimatedSellFunds : totalFundsWei;
668
+ const tokenProfitAmount = (profitBase * BigInt(profitRateBps)) / 10000n;
669
+ // ERC20 购买:获取代币利润等值的原生代币报价
670
+ let nativeProfitAmount = tokenProfitAmount;
671
+ if (!useNativeToken && tokenProfitAmount > 0n) {
672
+ nativeProfitAmount = await getTokenToNativeQuote(chainContext.provider, inputToken, tokenProfitAmount, chainContext.chainId);
673
+ }
674
+ // ✅ 获取贿赂金额
675
+ const bribeAmount = getBribeAmount(config);
676
+ const needBribeTx = bribeAmount > 0n;
677
+ // ✅ 获取所有钱包的 nonces
678
+ const noncesMap = new Map();
679
+ await Promise.all([...sellers, ...buyers].map(async (wallet) => {
680
+ const addr = wallet.address.toLowerCase();
681
+ if (!noncesMap.has(addr)) {
682
+ const nonce = await nonceManager.getNextNonce(wallet);
683
+ noncesMap.set(addr, nonce);
684
+ }
685
+ }));
686
+ // ✅ 构建交易列表
687
+ const allTransactions = [];
688
+ // 1. 贿赂交易(由主卖方支付)
689
+ if (needBribeTx) {
690
+ const mainSellerAddr = mainSeller.address.toLowerCase();
691
+ const bribeNonce = noncesMap.get(mainSellerAddr);
692
+ noncesMap.set(mainSellerAddr, bribeNonce + 1);
693
+ const bribeTx = await mainSeller.signTransaction({
694
+ to: BLOCKRAZOR_BUILDER_EOA,
695
+ value: bribeAmount,
696
+ nonce: bribeNonce,
697
+ gasPrice,
698
+ gasLimit: 21000n,
699
+ chainId: chainContext.chainId,
700
+ type: txType
701
+ });
702
+ allTransactions.push(bribeTx);
703
+ }
704
+ // 2. 构建所有买入交易
705
+ const buyTxPromises = buyers.map(async (buyer, i) => {
706
+ const buyAmount = buyAmountsWei[i];
707
+ const buyerAddr = buyer.address.toLowerCase();
708
+ const nonce = noncesMap.get(buyerAddr);
709
+ noncesMap.set(buyerAddr, nonce + 1);
710
+ const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
711
+ const unsigned = await portalBuyer.swapExactInput.populateTransaction({
712
+ inputToken,
713
+ outputToken: tokenAddress,
714
+ inputAmount: buyAmount,
715
+ minOutputAmount: 0n,
716
+ permitData: '0x'
717
+ }, useNativeToken ? { value: buyAmount } : {});
718
+ return buyer.signTransaction(buildTransactionRequest(unsigned, {
719
+ from: buyer.address,
720
+ nonce,
721
+ gasLimit: finalGasLimit,
722
+ gasPrice,
723
+ priorityFee,
724
+ chainId: chainContext.chainId,
725
+ txType,
726
+ value: useNativeToken ? buyAmount : 0n
727
+ }));
728
+ });
729
+ // 3. 构建所有卖出交易
730
+ const sellTxPromises = sellers.map(async (seller, i) => {
731
+ const sellAmount = sellAmountsWei[i];
732
+ const sellerAddr = seller.address.toLowerCase();
733
+ const nonce = noncesMap.get(sellerAddr);
734
+ noncesMap.set(sellerAddr, nonce + 1);
735
+ const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
736
+ const unsigned = await portalSeller.swapExactInput.populateTransaction({
737
+ inputToken: tokenAddress,
738
+ outputToken,
739
+ inputAmount: sellAmount,
740
+ minOutputAmount: 0n,
741
+ permitData: '0x'
742
+ });
743
+ return seller.signTransaction(buildTransactionRequest(unsigned, {
744
+ from: seller.address,
745
+ nonce,
746
+ gasLimit: finalGasLimit,
747
+ gasPrice,
748
+ priorityFee,
749
+ chainId: chainContext.chainId,
750
+ txType,
751
+ value: 0n
752
+ }));
753
+ });
754
+ // ✅ 并行签名所有买卖交易
755
+ const [signedBuys, signedSells] = await Promise.all([
756
+ Promise.all(buyTxPromises),
757
+ Promise.all(sellTxPromises)
758
+ ]);
759
+ // 先买后卖:买入交易在前
760
+ allTransactions.push(...signedBuys, ...signedSells);
761
+ // 4. 利润多跳转账(由主卖方支付)
762
+ let profitHopWallets;
763
+ if (nativeProfitAmount > 0n) {
764
+ const mainSellerAddr = mainSeller.address.toLowerCase();
765
+ const profitNonce = noncesMap.get(mainSellerAddr);
766
+ const profitResult = await buildProfitTransaction({
767
+ provider: chainContext.provider,
768
+ seller: mainSeller,
769
+ profitAmount: nativeProfitAmount,
770
+ profitNonce,
771
+ gasPrice,
772
+ chainId: chainContext.chainId,
773
+ txType
774
+ });
775
+ if (profitResult) {
776
+ allTransactions.push(...profitResult.signedTransactions);
777
+ profitHopWallets = profitResult.hopWallets;
778
+ }
779
+ }
780
+ nonceManager.clearTemp();
781
+ // 获取代币精度
782
+ const sellerInfo = await ensureSellerBalance({
783
+ tokenAddress,
784
+ provider: chainContext.provider,
785
+ seller: mainSeller,
786
+ sellAmountWei: 0n,
787
+ skipBalanceCheck: true
788
+ });
789
+ return {
790
+ signedTransactions: allTransactions,
791
+ profitHopWallets,
792
+ metadata: {
793
+ buyerAddress: buyers.map(b => b.address).join(','),
794
+ sellerAddress: sellers.map(s => s.address).join(','),
795
+ buyAmount: ethers.formatEther(totalFundsWei),
796
+ sellAmount: ethers.formatUnits(quoteResult.sellAmountWei, sellerInfo.decimals),
797
+ profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined,
798
+ buyCount,
799
+ sellCount,
800
+ buyAmounts: buyAmountsWei.map(amt => ethers.formatEther(amt)),
801
+ sellAmounts: sellAmountsWei.map(amt => ethers.formatUnits(amt, sellerInfo.decimals))
802
+ }
803
+ };
804
+ }
@@ -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,270 @@ 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
+ async function pancakeBundleBuyFirstMultiWallet(params) {
759
+ const { buyerPrivateKeys, sellerPrivateKeys, tokenAddress, routeParams, buyerFunds, config, quoteToken, quoteTokenDecimals = 18 } = params;
760
+ const buyCount = buyerPrivateKeys.length;
761
+ const sellCount = sellerPrivateKeys.length;
762
+ if (buyCount === 0)
763
+ throw new Error('买方钱包数量不能为0');
764
+ if (sellCount === 0)
765
+ throw new Error('卖方钱包数量不能为0');
766
+ // 验证总交易数不超过限制
767
+ validateSwapCounts(buyCount, sellCount);
768
+ // ✅ 计算利润比例:每笔万分之6
769
+ const totalTxCount = buyCount + sellCount;
770
+ const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
771
+ // ✅ 判断是否使用原生代币
772
+ const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
773
+ const context = createPancakeContext(config);
774
+ const nonceManager = new NonceManager(context.provider);
775
+ // 创建所有钱包实例
776
+ const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider));
777
+ const sellers = sellerPrivateKeys.map(pk => new Wallet(pk, context.provider));
778
+ // 使用第一个卖方作为主卖方(支付贿赂和利润)
779
+ const mainSeller = sellers[0];
780
+ // ✅ 计算总交易金额
781
+ let totalFundsWei;
782
+ if (buyerFunds) {
783
+ totalFundsWei = useNativeToken
784
+ ? ethers.parseEther(String(buyerFunds))
785
+ : ethers.parseUnits(String(buyerFunds), quoteTokenDecimals);
786
+ }
787
+ else {
788
+ throw new Error('多钱包模式必须提供 buyerFunds(总交易金额)');
789
+ }
790
+ if (totalFundsWei <= 0n) {
791
+ throw new Error('交易金额必须大于0');
792
+ }
793
+ // ✅ 获取报价:买入能获得多少代币
794
+ const quoteResult = await quoteTokenOutput({
795
+ routeParams,
796
+ buyerFundsWei: totalFundsWei,
797
+ provider: context.provider
798
+ });
799
+ // ✅ 将总金额平均分配给买方
800
+ const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
801
+ // ✅ 将代币平均分配给卖方
802
+ const sellAmountsWei = splitAmount(quoteResult.quotedTokenOut, sellCount);
803
+ const finalGasLimit = getGasLimit(config);
804
+ const gasPrice = await getGasPrice(context.provider, config);
805
+ const txType = config.txType ?? 0;
806
+ const deadline = BigInt(getDeadline());
807
+ // ✅ 估算利润
808
+ const estimatedProfitFromSell = await estimateProfitAmount({
809
+ provider: context.provider,
810
+ tokenAddress,
811
+ sellAmountToken: quoteResult.quotedTokenOut,
812
+ routeParams
813
+ });
814
+ const profitBase = estimatedProfitFromSell > 0n ? estimatedProfitFromSell : totalFundsWei;
815
+ const profitAmount = (profitBase * BigInt(profitRateBps)) / 10000n;
816
+ // ✅ 获取贿赂金额
817
+ const bribeAmount = config.bribeAmount && config.bribeAmount > 0
818
+ ? ethers.parseEther(String(config.bribeAmount))
819
+ : 0n;
820
+ const needBribeTx = bribeAmount > 0n;
821
+ // ✅ 获取所有钱包的 nonces
822
+ const allWallets = [...sellers, ...buyers];
823
+ const noncesMap = new Map();
824
+ await Promise.all(allWallets.map(async (wallet) => {
825
+ const addr = wallet.address.toLowerCase();
826
+ if (!noncesMap.has(addr)) {
827
+ const nonce = await nonceManager.getNextNonce(wallet);
828
+ noncesMap.set(addr, nonce);
829
+ }
830
+ }));
831
+ // ✅ 构建交易列表
832
+ const allTransactions = [];
833
+ // 1. 贿赂交易(由主卖方支付)
834
+ if (needBribeTx) {
835
+ const mainSellerAddr = mainSeller.address.toLowerCase();
836
+ const bribeNonce = noncesMap.get(mainSellerAddr);
837
+ noncesMap.set(mainSellerAddr, bribeNonce + 1);
838
+ const bribeTx = await mainSeller.signTransaction({
839
+ to: BLOCKRAZOR_BUILDER_EOA,
840
+ value: bribeAmount,
841
+ nonce: bribeNonce,
842
+ gasPrice,
843
+ gasLimit: 21000n,
844
+ chainId: context.chainId,
845
+ type: txType
846
+ });
847
+ allTransactions.push(bribeTx);
848
+ }
849
+ // 2. 构建所有买入交易
850
+ const buyTxPromises = buyers.map(async (buyer, i) => {
851
+ const buyAmount = buyAmountsWei[i];
852
+ const buyerAddr = buyer.address.toLowerCase();
853
+ const nonce = noncesMap.get(buyerAddr);
854
+ noncesMap.set(buyerAddr, nonce + 1);
855
+ const unsigned = await buildSingleBuyTx({
856
+ routeParams,
857
+ buyAmount,
858
+ buyer,
859
+ tokenAddress,
860
+ useNativeToken,
861
+ deadline
862
+ });
863
+ return buyer.signTransaction({
864
+ ...unsigned,
865
+ from: buyer.address,
866
+ nonce,
867
+ gasLimit: finalGasLimit,
868
+ gasPrice,
869
+ chainId: context.chainId,
870
+ type: txType
871
+ });
872
+ });
873
+ // 3. 构建所有卖出交易
874
+ const sellTxPromises = sellers.map(async (seller, i) => {
875
+ const sellAmount = sellAmountsWei[i];
876
+ const sellerAddr = seller.address.toLowerCase();
877
+ const nonce = noncesMap.get(sellerAddr);
878
+ noncesMap.set(sellerAddr, nonce + 1);
879
+ const unsigned = await buildSingleSellTx({
880
+ routeParams,
881
+ sellAmount,
882
+ seller,
883
+ tokenAddress,
884
+ useNativeToken,
885
+ deadline
886
+ });
887
+ return seller.signTransaction({
888
+ ...unsigned,
889
+ from: seller.address,
890
+ nonce,
891
+ gasLimit: finalGasLimit,
892
+ gasPrice,
893
+ chainId: context.chainId,
894
+ type: txType
895
+ });
896
+ });
897
+ // ✅ 并行签名所有买卖交易
898
+ const [signedBuys, signedSells] = await Promise.all([
899
+ Promise.all(buyTxPromises),
900
+ Promise.all(sellTxPromises)
901
+ ]);
902
+ // 先买后卖:买入交易在前
903
+ allTransactions.push(...signedBuys, ...signedSells);
904
+ // 4. 利润多跳转账(由主卖方支付)
905
+ let profitHopWallets;
906
+ if (profitAmount > 0n) {
907
+ const mainSellerAddr = mainSeller.address.toLowerCase();
908
+ const profitNonce = noncesMap.get(mainSellerAddr);
909
+ const profitResult = await buildProfitTransaction({
910
+ provider: context.provider,
911
+ seller: mainSeller,
912
+ profitAmount,
913
+ profitNonce,
914
+ gasPrice,
915
+ chainId: context.chainId,
916
+ txType
917
+ });
918
+ if (profitResult) {
919
+ allTransactions.push(...profitResult.signedTransactions);
920
+ profitHopWallets = profitResult.hopWallets;
921
+ }
922
+ }
923
+ nonceManager.clearTemp();
924
+ return {
925
+ signedTransactions: allTransactions,
926
+ profitHopWallets,
927
+ metadata: {
928
+ buyerAddress: buyers.map(b => b.address).join(','),
929
+ sellerAddress: sellers.map(s => s.address).join(','),
930
+ buyAmount: useNativeToken
931
+ ? ethers.formatEther(totalFundsWei)
932
+ : ethers.formatUnits(totalFundsWei, quoteTokenDecimals),
933
+ sellAmount: quoteResult.quotedTokenOut.toString(),
934
+ profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
935
+ buyCount,
936
+ sellCount,
937
+ buyAmounts: buyAmountsWei.map(amt => useNativeToken
938
+ ? ethers.formatEther(amt)
939
+ : ethers.formatUnits(amt, quoteTokenDecimals)),
940
+ sellAmounts: sellAmountsWei.map(amt => amt.toString())
941
+ }
942
+ };
943
+ }
944
+ // ✅ 构建单笔买入交易
945
+ async function buildSingleBuyTx({ routeParams, buyAmount, buyer, tokenAddress, useNativeToken, deadline }) {
946
+ if (routeParams.routeType === 'v2') {
947
+ const { v2Path } = routeParams;
948
+ const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, buyer);
949
+ if (useNativeToken) {
950
+ return await v2Router.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, v2Path, buyer.address, deadline, { value: buyAmount });
951
+ }
952
+ else {
953
+ return await v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmount, 0n, v2Path, buyer.address, deadline);
954
+ }
955
+ }
956
+ if (routeParams.routeType === 'v3-single') {
957
+ const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
958
+ const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
959
+ const v3Router = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, buyer);
960
+ const buyValue = useNativeToken ? buyAmount : 0n;
961
+ const buySwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
962
+ tokenIn: v3TokenIn,
963
+ tokenOut: v3TokenOut,
964
+ fee: v3Fee,
965
+ recipient: buyer.address,
966
+ amountIn: buyAmount,
967
+ amountOutMinimum: 0n,
968
+ sqrtPriceLimitX96: 0n
969
+ }]);
970
+ return await v3Router.multicall.populateTransaction(deadline, [buySwapData], { value: buyValue });
971
+ }
972
+ throw new Error('V3 多跳路由暂不支持');
973
+ }
974
+ // ✅ 构建单笔卖出交易
975
+ async function buildSingleSellTx({ routeParams, sellAmount, seller, tokenAddress, useNativeToken, deadline }) {
976
+ if (routeParams.routeType === 'v2') {
977
+ const { v2Path } = routeParams;
978
+ const reversePath = [...v2Path].reverse();
979
+ const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, seller);
980
+ if (useNativeToken) {
981
+ return await v2Router.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmount, 0n, reversePath, seller.address, deadline);
982
+ }
983
+ else {
984
+ return await v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmount, 0n, reversePath, seller.address, deadline);
985
+ }
986
+ }
987
+ if (routeParams.routeType === 'v3-single') {
988
+ const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
989
+ const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
990
+ const v3Router = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, seller);
991
+ if (useNativeToken) {
992
+ const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
993
+ tokenIn: v3TokenOut,
994
+ tokenOut: v3TokenIn,
995
+ fee: v3Fee,
996
+ recipient: PANCAKE_V3_ROUTER_ADDRESS,
997
+ amountIn: sellAmount,
998
+ amountOutMinimum: 0n,
999
+ sqrtPriceLimitX96: 0n
1000
+ }]);
1001
+ const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]);
1002
+ return await v3Router.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]);
1003
+ }
1004
+ else {
1005
+ const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
1006
+ tokenIn: v3TokenOut,
1007
+ tokenOut: v3TokenIn,
1008
+ fee: v3Fee,
1009
+ recipient: seller.address,
1010
+ amountIn: sellAmount,
1011
+ amountOutMinimum: 0n,
1012
+ sqrtPriceLimitX96: 0n
1013
+ }]);
1014
+ return await v3Router.multicall.populateTransaction(deadline, [sellSwapData]);
1015
+ }
1016
+ }
1017
+ throw new Error('V3 多跳路由暂不支持');
1018
+ }
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.79",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",