four-flap-meme-sdk 1.4.77 → 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.
Files changed (109) hide show
  1. package/dist/contracts/tm-bundle-merkle/swap-buy-first.d.ts +4 -2
  2. package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +195 -12
  3. package/dist/flap/portal-bundle-merkle/encryption.d.ts +16 -0
  4. package/dist/flap/portal-bundle-merkle/encryption.js +146 -0
  5. package/dist/flap/portal-bundle-merkle/swap-buy-first.d.ts +4 -2
  6. package/dist/flap/portal-bundle-merkle/swap-buy-first.js +222 -9
  7. package/dist/index.d.ts +0 -1
  8. package/dist/index.js +0 -1
  9. package/dist/pancake/bundle-buy-first.d.ts +4 -2
  10. package/dist/pancake/bundle-buy-first.js +293 -14
  11. package/package.json +3 -38
  12. package/dist/sol/constants.d.ts +0 -126
  13. package/dist/sol/constants.js +0 -145
  14. package/dist/sol/dex/index.d.ts +0 -8
  15. package/dist/sol/dex/index.js +0 -12
  16. package/dist/sol/dex/meteora/client.d.ts +0 -76
  17. package/dist/sol/dex/meteora/client.js +0 -219
  18. package/dist/sol/dex/meteora/damm-v1-bundle.d.ts +0 -61
  19. package/dist/sol/dex/meteora/damm-v1-bundle.js +0 -112
  20. package/dist/sol/dex/meteora/damm-v1.d.ts +0 -118
  21. package/dist/sol/dex/meteora/damm-v1.js +0 -315
  22. package/dist/sol/dex/meteora/damm-v2-bundle.d.ts +0 -82
  23. package/dist/sol/dex/meteora/damm-v2-bundle.js +0 -242
  24. package/dist/sol/dex/meteora/damm-v2.d.ts +0 -172
  25. package/dist/sol/dex/meteora/damm-v2.js +0 -632
  26. package/dist/sol/dex/meteora/dbc-bundle.d.ts +0 -123
  27. package/dist/sol/dex/meteora/dbc-bundle.js +0 -304
  28. package/dist/sol/dex/meteora/dbc.d.ts +0 -192
  29. package/dist/sol/dex/meteora/dbc.js +0 -619
  30. package/dist/sol/dex/meteora/dlmm-bundle.d.ts +0 -39
  31. package/dist/sol/dex/meteora/dlmm-bundle.js +0 -189
  32. package/dist/sol/dex/meteora/dlmm.d.ts +0 -157
  33. package/dist/sol/dex/meteora/dlmm.js +0 -671
  34. package/dist/sol/dex/meteora/index.d.ts +0 -25
  35. package/dist/sol/dex/meteora/index.js +0 -65
  36. package/dist/sol/dex/meteora/types.d.ts +0 -787
  37. package/dist/sol/dex/meteora/types.js +0 -110
  38. package/dist/sol/dex/orca/index.d.ts +0 -10
  39. package/dist/sol/dex/orca/index.js +0 -16
  40. package/dist/sol/dex/orca/orca-bundle.d.ts +0 -41
  41. package/dist/sol/dex/orca/orca-bundle.js +0 -173
  42. package/dist/sol/dex/orca/orca.d.ts +0 -65
  43. package/dist/sol/dex/orca/orca.js +0 -474
  44. package/dist/sol/dex/orca/types.d.ts +0 -263
  45. package/dist/sol/dex/orca/types.js +0 -38
  46. package/dist/sol/dex/orca/wavebreak-bundle.d.ts +0 -34
  47. package/dist/sol/dex/orca/wavebreak-bundle.js +0 -198
  48. package/dist/sol/dex/orca/wavebreak-types.d.ts +0 -227
  49. package/dist/sol/dex/orca/wavebreak-types.js +0 -23
  50. package/dist/sol/dex/orca/wavebreak.d.ts +0 -78
  51. package/dist/sol/dex/orca/wavebreak.js +0 -497
  52. package/dist/sol/dex/pump/index.d.ts +0 -9
  53. package/dist/sol/dex/pump/index.js +0 -14
  54. package/dist/sol/dex/pump/pump-bundle.d.ts +0 -92
  55. package/dist/sol/dex/pump/pump-bundle.js +0 -383
  56. package/dist/sol/dex/pump/pump-swap-bundle.d.ts +0 -103
  57. package/dist/sol/dex/pump/pump-swap-bundle.js +0 -380
  58. package/dist/sol/dex/pump/pump-swap.d.ts +0 -46
  59. package/dist/sol/dex/pump/pump-swap.js +0 -199
  60. package/dist/sol/dex/pump/pump.d.ts +0 -35
  61. package/dist/sol/dex/pump/pump.js +0 -352
  62. package/dist/sol/dex/pump/types.d.ts +0 -215
  63. package/dist/sol/dex/pump/types.js +0 -5
  64. package/dist/sol/dex/raydium/index.d.ts +0 -8
  65. package/dist/sol/dex/raydium/index.js +0 -12
  66. package/dist/sol/dex/raydium/launchlab.d.ts +0 -68
  67. package/dist/sol/dex/raydium/launchlab.js +0 -210
  68. package/dist/sol/dex/raydium/raydium-bundle.d.ts +0 -64
  69. package/dist/sol/dex/raydium/raydium-bundle.js +0 -324
  70. package/dist/sol/dex/raydium/raydium.d.ts +0 -40
  71. package/dist/sol/dex/raydium/raydium.js +0 -366
  72. package/dist/sol/dex/raydium/types.d.ts +0 -240
  73. package/dist/sol/dex/raydium/types.js +0 -5
  74. package/dist/sol/index.d.ts +0 -10
  75. package/dist/sol/index.js +0 -16
  76. package/dist/sol/jito/bundle.d.ts +0 -90
  77. package/dist/sol/jito/bundle.js +0 -263
  78. package/dist/sol/jito/index.d.ts +0 -7
  79. package/dist/sol/jito/index.js +0 -7
  80. package/dist/sol/jito/tip.d.ts +0 -51
  81. package/dist/sol/jito/tip.js +0 -83
  82. package/dist/sol/jito/types.d.ts +0 -100
  83. package/dist/sol/jito/types.js +0 -5
  84. package/dist/sol/token/create-complete.d.ts +0 -115
  85. package/dist/sol/token/create-complete.js +0 -235
  86. package/dist/sol/token/create-token.d.ts +0 -57
  87. package/dist/sol/token/create-token.js +0 -230
  88. package/dist/sol/token/index.d.ts +0 -9
  89. package/dist/sol/token/index.js +0 -14
  90. package/dist/sol/token/metadata-upload.d.ts +0 -86
  91. package/dist/sol/token/metadata-upload.js +0 -173
  92. package/dist/sol/token/metadata.d.ts +0 -92
  93. package/dist/sol/token/metadata.js +0 -274
  94. package/dist/sol/token/types.d.ts +0 -153
  95. package/dist/sol/token/types.js +0 -5
  96. package/dist/sol/types.d.ts +0 -176
  97. package/dist/sol/types.js +0 -7
  98. package/dist/sol/utils/balance.d.ts +0 -160
  99. package/dist/sol/utils/balance.js +0 -638
  100. package/dist/sol/utils/connection.d.ts +0 -78
  101. package/dist/sol/utils/connection.js +0 -168
  102. package/dist/sol/utils/index.d.ts +0 -9
  103. package/dist/sol/utils/index.js +0 -9
  104. package/dist/sol/utils/lp-inspect.d.ts +0 -129
  105. package/dist/sol/utils/lp-inspect.js +0 -900
  106. package/dist/sol/utils/transfer.d.ts +0 -125
  107. package/dist/sol/utils/transfer.js +0 -220
  108. package/dist/sol/utils/wallet.d.ts +0 -107
  109. package/dist/sol/utils/wallet.js +0 -210
@@ -66,8 +66,8 @@ const BRIBE_TX_COUNT = 1;
66
66
  const PROFIT_TX_COUNT = PROFIT_HOP_COUNT + 2; // 2 + 2 = 4
67
67
  /** 最大买卖交易数 */
68
68
  const MAX_SWAP_TX_COUNT = MAX_BUNDLE_SIGNATURES - BRIBE_TX_COUNT - PROFIT_TX_COUNT; // 50 - 1 - 4 = 45
69
- /** 每笔交易利润比例(基点):3 bps = 0.03% = 万分之三 */
70
- const PROFIT_RATE_PER_TX_BPS = 3;
69
+ /** 每笔交易利润比例(基点):6 bps = 0.06% = 万分之六 */
70
+ const PROFIT_RATE_PER_TX_BPS = 6;
71
71
  /**
72
72
  * 验证买卖笔数
73
73
  */
@@ -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;
@@ -160,16 +179,12 @@ export async function flapBundleBuyFirstMerkle(params) {
160
179
  // ✅ 拆分买入和卖出金额
161
180
  const buyAmountsWei = splitAmount(buyerFundsWei, buyCount);
162
181
  const sellAmountsWei = splitAmount(sellAmountWei, sellCount);
163
- // ✅ 多笔买入时,minOutputAmount 设置为 0 以避免滑点导致交易失败
164
- // 原因:每笔买入会改变池子价格,后续买入获得的代币会变少
165
- // 单笔买入时使用正常的 minOutputAmount 作为保护
166
- const minOutputPerBuy = buyCount === 1 ? sellAmountWei : 0n;
167
182
  // ✅ 第三批并行 - 构建多笔买入和卖出交易、estimatedSellFunds
168
183
  const buyUnsignedPromises = buyAmountsWei.map(amount => portalBuyer.swapExactInput.populateTransaction({
169
184
  inputToken,
170
185
  outputToken: tokenAddress,
171
186
  inputAmount: amount,
172
- minOutputAmount: minOutputPerBuy,
187
+ minOutputAmount: 0n,
173
188
  permitData: '0x'
174
189
  }, useNativeToken ? { value: amount } : {}));
175
190
  const sellUnsignedPromises = sellAmountsWei.map(amount => portalSeller.swapExactInput.populateTransaction({
@@ -589,3 +604,201 @@ function createChainContext(chain, rpcUrl) {
589
604
  }
590
605
  return { chainId, nativeToken, portalAddress, provider };
591
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
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export * as Abis from './abis/index.js';
2
- export * as Sol from './sol/index.js';
3
2
  export { ERC20_ABI, ERC20_BALANCE_ABI, ERC20_ALLOWANCE_ABI, MULTICALL3_ABI, V2_ROUTER_ABI, V2_ROUTER_QUOTE_ABI, V3_ROUTER02_ABI, V3_ROUTER_LEGACY_ABI, V3_QUOTER_ABI, V2_FACTORY_ABI, V2_PAIR_ABI, V3_FACTORY_ABI, V3_POOL_ABI, FLAP_PORTAL_ABI, TM2_ABI, HELPER3_ABI, } from './abis/common.js';
4
3
  export { ADDRESSES, CHAIN, BLOCKRAZOR_BUILDER_EOA as BUILDER_EOA, ZERO_ADDRESS as COMMON_ZERO_ADDRESS, DEFAULT_DEADLINE_MINUTES, V3_FEE_TIERS as COMMON_V3_FEE_TIERS, } from './utils/constants.js';
5
4
  export { isExclusiveOnChain, isExclusiveOffChain } from './utils/mpcExclusive.js';
package/dist/index.js CHANGED
@@ -2,7 +2,6 @@
2
2
  // 公共 ABI(统一管理)
3
3
  // ============================================================================
4
4
  export * as Abis from './abis/index.js';
5
- export * as Sol from './sol/index.js';
6
5
  export { ERC20_ABI, ERC20_BALANCE_ABI, ERC20_ALLOWANCE_ABI, MULTICALL3_ABI, V2_ROUTER_ABI, V2_ROUTER_QUOTE_ABI, V3_ROUTER02_ABI, V3_ROUTER_LEGACY_ABI, V3_QUOTER_ABI, V2_FACTORY_ABI, V2_PAIR_ABI, V3_FACTORY_ABI, V3_POOL_ABI, FLAP_PORTAL_ABI, TM2_ABI, HELPER3_ABI, } from './abis/common.js';
7
6
  // ============================================================================
8
7
  // 公共常量(统一管理)
@@ -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;
@@ -43,7 +43,6 @@ const PANCAKE_V2_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV2Router;
43
43
  const PANCAKE_V3_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV3Router;
44
44
  // 常量
45
45
  const FLAT_FEE = 0n;
46
- const WBNB_ADDRESS = ADDRESSES.BSC.WBNB;
47
46
  // ==================== 多笔买卖常量 ====================
48
47
  /** 最大 Bundle 签名数 */
49
48
  const MAX_BUNDLE_SIGNATURES = 50;
@@ -53,8 +52,8 @@ const BRIBE_TX_COUNT = 1;
53
52
  const PROFIT_TX_COUNT = PROFIT_HOP_COUNT + 2; // 2 + 2 = 4
54
53
  /** 最大买卖交易数 */
55
54
  const MAX_SWAP_TX_COUNT = MAX_BUNDLE_SIGNATURES - BRIBE_TX_COUNT - PROFIT_TX_COUNT; // 50 - 1 - 4 = 45
56
- /** 每笔交易利润比例(基点):3 bps = 0.03% = 万分之三 */
57
- const PROFIT_RATE_PER_TX_BPS = 3;
55
+ /** 每笔交易利润比例(基点):6 bps = 0.06% = 万分之六 */
56
+ const PROFIT_RATE_PER_TX_BPS = 6;
58
57
  /**
59
58
  * 验证买卖笔数
60
59
  */
@@ -110,11 +109,32 @@ function splitAmount(totalAmount, count) {
110
109
  return amounts;
111
110
  }
112
111
  export async function pancakeBundleBuyFirstMerkle(params) {
113
- 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
114
113
  buyCount: _buyCount, sellCount: _sellCount } = params;
115
- // ✅ 解析并验证买卖笔数
114
+ // ✅ 判断是否为多钱包模式
115
+ const isMultiWalletMode = !!(buyerPrivateKeys && buyerPrivateKeys.length > 0) ||
116
+ !!(sellerPrivateKeys && sellerPrivateKeys.length > 0);
117
+ // ✅ 多钱包模式:buyCount/sellCount 代表钱包数量
118
+ // 单钱包模式(向后兼容):buyCount/sellCount 代表同一钱包执行的交易笔数
116
119
  const buyCount = _buyCount ?? 1;
117
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
+ }
118
138
  validateSwapCounts(buyCount, sellCount);
119
139
  // ✅ 计算利润比例:每笔万分之3
120
140
  const totalTxCount = buyCount + sellCount;
@@ -140,17 +160,9 @@ export async function pancakeBundleBuyFirstMerkle(params) {
140
160
  buyerFundsWei: buyerFundsInfo.buyerFundsWei,
141
161
  provider: context.provider
142
162
  });
143
- // ✅ 多笔交易时添加滑点保护,避免因价格变动导致代币不足
144
- // 原因:多笔买入会累积滑点,实际获得的代币少于一次性报价
145
- // 滑点比例:buyCount * 1%(每多一笔买入增加 1% 保护)
146
- let adjustedSellAmount = quoteResult.quotedTokenOut;
147
- if (buyCount > 1 || sellCount > 1) {
148
- const slippageBps = BigInt(buyCount * 100); // buyCount=3 → 3%
149
- adjustedSellAmount = quoteResult.quotedTokenOut * (10000n - slippageBps) / 10000n;
150
- }
151
163
  // ✅ 拆分买入和卖出金额
152
164
  const buyAmountsWei = splitAmount(buyerFundsInfo.buyerFundsWei, buyCount);
153
- const sellAmountsWei = splitAmount(adjustedSellAmount, sellCount);
165
+ const sellAmountsWei = splitAmount(quoteResult.quotedTokenOut, sellCount);
154
166
  // ✅ 构建多笔买入和卖出交易
155
167
  const swapUnsignedArray = await buildMultiRouteTransactions({
156
168
  routeParams,
@@ -737,3 +749,270 @@ function buildNoncePlanFromStartNonces(startNonces, sameAddress, profitNeeded, n
737
749
  const buyerNonce = startNonces[1];
738
750
  return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
739
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
+ }