four-flap-meme-sdk 1.4.3 → 1.4.5

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.
@@ -13,6 +13,7 @@ export interface FourSwapSignConfig {
13
13
  txType?: 0 | 2;
14
14
  chainId?: number;
15
15
  reserveGasBNB?: number;
16
+ bribeAmount?: number;
16
17
  }
17
18
  export interface FourSwapConfig extends CommonBundleConfig {
18
19
  apiKey: string;
@@ -15,6 +15,7 @@ export type FourSignConfig = {
15
15
  spPrivateKey?: string;
16
16
  spMode?: 'none' | 'timestampPersonalSign' | 'concatTxHash' | 'rawTimestamp';
17
17
  spVMode?: '27_28' | '0_1';
18
+ bribeAmount?: number;
18
19
  };
19
20
  export type FourBundleMerkleConfig = {
20
21
  apiKey: string;
@@ -14,6 +14,32 @@ import { PROFIT_CONFIG } from '../utils/constants.js';
14
14
  // 常量配置
15
15
  // ============================================================================
16
16
  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
17
+ // ============================================================================
18
+ // ✅ 方案 C:Provider 缓存(避免重复创建)
19
+ // ============================================================================
20
+ const providerCache = new Map();
21
+ const PROVIDER_CACHE_TTL = 60000; // 60秒后过期
22
+ const providerCacheTimestamps = new Map();
23
+ /**
24
+ * 获取缓存的 Provider 实例
25
+ * - 根据 rpcUrl + chainId 作为缓存 key
26
+ * - 60秒后自动失效,避免长连接问题
27
+ */
28
+ function getCachedProvider(rpcUrl, chainId, chainName) {
29
+ const cacheKey = `${rpcUrl}_${chainId}`;
30
+ const now = Date.now();
31
+ // 检查缓存是否存在且未过期
32
+ const cachedProvider = providerCache.get(cacheKey);
33
+ const timestamp = providerCacheTimestamps.get(cacheKey) || 0;
34
+ if (cachedProvider && (now - timestamp) < PROVIDER_CACHE_TTL) {
35
+ return cachedProvider;
36
+ }
37
+ // 创建新 Provider 并缓存
38
+ const provider = new JsonRpcProvider(rpcUrl, { chainId, name: chainName });
39
+ providerCache.set(cacheKey, provider);
40
+ providerCacheTimestamps.set(cacheKey, now);
41
+ return provider;
42
+ }
17
43
  const DEFAULT_GAS_LIMIT = 300000;
18
44
  const DEADLINE_MINUTES = 20;
19
45
  // ✅ BlockRazor Builder EOA 地址(用于贿赂,仅 BSC 链)
@@ -671,130 +697,102 @@ export async function directV2BatchBuy(params) {
671
697
  throw new Error('privateKeys 和 buyAmounts 长度必须相同');
672
698
  }
673
699
  const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
674
- const provider = new JsonRpcProvider(config.rpcUrl, { chainId, name: chain });
700
+ // 方案 C:使用缓存的 Provider
701
+ const provider = getCachedProvider(config.rpcUrl, chainId, chain);
675
702
  const useNative = isNativeToken(quoteToken);
676
703
  const wrappedNative = getWrappedNative(chain);
677
704
  // 创建钱包
678
705
  const wallets = privateKeys.map(pk => new Wallet(pk, provider));
679
- // ✅ 性能优化:如果前端已传入 nonces,则跳过 RPC 获取
680
- const [nonces, gasPrice] = await Promise.all([
706
+ // ✅ 预先计算所有金额(同步操作,无 RPC
707
+ const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
708
+ const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
709
+ const baseProfitWei = calculateProfitAmount(totalFlowWei);
710
+ // ✅ 方案 B:并行获取 nonces、gasPrice 和 ERC20 报价
711
+ const [nonces, gasPrice, nativeProfitWei] = await Promise.all([
681
712
  startNonces && startNonces.length === wallets.length
682
713
  ? Promise.resolve(startNonces)
683
714
  : new NonceManager(provider).getNextNoncesForWallets(wallets),
684
- getGasPrice(provider, config)
715
+ getGasPrice(provider, config),
716
+ // ERC20 报价提前并行获取
717
+ (!useNative && baseProfitWei > 0n && quoteToken)
718
+ ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
719
+ : Promise.resolve(baseProfitWei)
685
720
  ]);
721
+ // 确定最终利润金额
722
+ const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
686
723
  const gasLimit = getGasLimit(config, 250000);
687
724
  const txType = config.txType ?? 0;
688
725
  const deadline = getDeadline();
689
- const slippageBps = config.slippageBps ?? 100; // 默认 1%
690
726
  // 构建路径
691
727
  const inputToken = useNative ? wrappedNative : quoteToken;
692
728
  const path = [inputToken, tokenAddress];
693
- // 判断是否是 SwapRouter02 或 DYORSwap
729
+ // 判断 Router 类型
694
730
  const useSwapRouter02 = isSwapRouter02(chain, routerAddress);
695
731
  const useDYORSwap = isDYORSwap(chain, routerAddress);
696
- // 创建 Router 合约接口 (DYORSwap 使用手动编码,不需要 ABI)
697
732
  const routerIface = useSwapRouter02
698
733
  ? new ethers.Interface(SWAP_ROUTER02_V2_ABI)
699
734
  : new ethers.Interface(V2_ROUTER_ABI);
700
- // ✅ 优化:预先计算所有金额(同步操作)
701
- const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
702
- const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
703
- // ✅ 优化:构建未签名交易的辅助函数
735
+ // 构建交易数据的辅助函数
704
736
  const buildTxData = (wallet, amountWei) => {
705
737
  if (useDYORSwap) {
706
738
  const DYORSWAP_FACTORY = DIRECT_ROUTERS.XLAYER.DYORSWAP_FACTORY;
707
739
  const multicallData = [];
708
740
  multicallData.push(encodeDYORRefundETH(30));
709
741
  multicallData.push(encodeDYORSwapExactTokensForTokens(useNative ? 0n : amountWei, 0n, path, [], wallet.address, 1n, DYORSWAP_FACTORY));
710
- return {
711
- txData: encodeDYORMulticall(BigInt(deadline), multicallData),
712
- txValue: useNative ? amountWei : 0n
713
- };
742
+ return { txData: encodeDYORMulticall(BigInt(deadline), multicallData), txValue: useNative ? amountWei : 0n };
714
743
  }
715
744
  else if (useSwapRouter02) {
716
- const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
717
- amountWei,
718
- 0n,
719
- path,
720
- wallet.address,
721
- ]);
722
- return {
723
- txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData]]),
724
- txValue: useNative ? amountWei : 0n
725
- };
745
+ const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [amountWei, 0n, path, wallet.address]);
746
+ return { txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData]]), txValue: useNative ? amountWei : 0n };
726
747
  }
727
748
  else if (useNative) {
728
- return {
729
- txData: routerIface.encodeFunctionData('swapExactETHForTokensSupportingFeeOnTransferTokens', [
730
- 0n,
731
- path,
732
- wallet.address,
733
- deadline,
734
- ]),
735
- txValue: amountWei
736
- };
749
+ return { txData: routerIface.encodeFunctionData('swapExactETHForTokensSupportingFeeOnTransferTokens', [0n, path, wallet.address, deadline]), txValue: amountWei };
737
750
  }
738
751
  else {
739
- return {
740
- txData: routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [
741
- amountWei,
742
- 0n,
743
- path,
744
- wallet.address,
745
- deadline,
746
- ]),
747
- txValue: 0n
748
- };
752
+ return { txData: routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [amountWei, 0n, path, wallet.address, deadline]), txValue: 0n };
749
753
  }
750
754
  };
751
- // ✅ 优化:选择金额最大的钱包支付贿赂和利润
755
+ // 选择金额最大的钱包支付贿赂和利润
752
756
  const maxFlowIndex = findMaxFlowIndex(flowAmounts);
753
- // 计算贿赂金额(仅 BSC 链)
757
+ // 计算贿赂金额(仅 BSC 链)
754
758
  const bribeWei = getBribeAmount(config, chain);
755
759
  const hasBribe = bribeWei > 0n && wallets.length > 0;
756
- // 计算 nonce 偏移:如果有贿赂交易,需要为金额最大的钱包预留 nonce
760
+ const hasProfit = profitWei > 0n;
761
+ // 计算 nonce 偏移
757
762
  const nonceOffsets = wallets.map((_, i) => i === maxFlowIndex && hasBribe ? 1 : 0);
758
- // ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
759
- const bribeTxs = [];
763
+ // ✅ 方案 A:并行签名所有交易(贿赂、主交易、利润)
764
+ const signPromises = [];
765
+ // 贿赂交易
760
766
  if (hasBribe) {
761
- const bribeTx = await buildBribeTransaction(wallets[maxFlowIndex], bribeWei, nonces[maxFlowIndex], // 使用原始 nonce
762
- gasPrice, chainId, txType);
763
- bribeTxs.push(bribeTx);
767
+ signPromises.push(buildBribeTransaction(wallets[maxFlowIndex], bribeWei, nonces[maxFlowIndex], gasPrice, chainId, txType)
768
+ .then(tx => ({ type: 'bribe', index: 0, tx })));
764
769
  }
765
- // ✅ 优化:并行签名所有交易
766
- const swapTxs = await Promise.all(wallets.map(async (wallet, i) => {
770
+ // 主交易(并行签名)
771
+ wallets.forEach((wallet, i) => {
767
772
  const { txData, txValue } = buildTxData(wallet, flowAmounts[i]);
768
- return wallet.signTransaction({
773
+ signPromises.push(wallet.signTransaction({
769
774
  to: routerAddress,
770
775
  data: txData,
771
776
  value: txValue,
772
- nonce: nonces[i] + nonceOffsets[i], // ✅ 考虑贿赂交易的 nonce 偏移
777
+ nonce: nonces[i] + nonceOffsets[i],
773
778
  gasLimit,
774
779
  gasPrice,
775
780
  chainId,
776
781
  type: txType,
777
- });
778
- }));
779
- let profitWei = calculateProfitAmount(totalFlowWei);
780
- // 修复:ERC20 交易时,将利润转换为原生代币
781
- if (!useNative && profitWei > 0n && quoteToken) {
782
- const nativeProfitWei = await getTokenToNativeQuote(provider, quoteToken, profitWei, chain);
783
- if (nativeProfitWei > 0n) {
784
- profitWei = nativeProfitWei;
785
- }
786
- else {
787
- profitWei = 0n; // 报价失败,跳过利润
788
- }
782
+ }).then(tx => ({ type: 'swap', index: i, tx })));
783
+ });
784
+ // 利润交易
785
+ if (hasProfit) {
786
+ const profitNonce = nonces[maxFlowIndex] + nonceOffsets[maxFlowIndex] + 1;
787
+ signPromises.push(buildProfitTransaction(wallets[maxFlowIndex], profitWei, profitNonce, gasPrice, chainId, txType)
788
+ .then(tx => ({ type: 'profit', index: 0, tx })));
789
789
  }
790
- // ✅ 利润交易
791
- const profitTxs = [];
792
- if (profitWei > 0n) {
793
- const profitNonce = nonces[maxFlowIndex] + nonceOffsets[maxFlowIndex] + 1; // ✅ 考虑贿赂交易的 nonce 偏移
794
- const profitTx = await buildProfitTransaction(wallets[maxFlowIndex], profitWei, profitNonce, gasPrice, chainId, txType);
795
- profitTxs.push(profitTx);
796
- }
797
- // ✅ 组装最终交易列表:贿赂 → 交易 → 利润
790
+ // ✅ 并行执行所有签名
791
+ const signedResults = await Promise.all(signPromises);
792
+ // 按类型分组并按顺序组装:贿赂 交易 → 利润
793
+ const bribeTxs = signedResults.filter(r => r.type === 'bribe').map(r => r.tx);
794
+ const swapTxs = signedResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
795
+ const profitTxs = signedResults.filter(r => r.type === 'profit').map(r => r.tx);
798
796
  const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
799
797
  return {
800
798
  signedTransactions: signedTxs,
@@ -811,27 +809,22 @@ export async function directV2BatchBuy(params) {
811
809
  export async function directV2BatchSell(params) {
812
810
  const { chain, privateKeys, sellPercentages, sellAmounts, tokenAddress, tokenDecimals: inputDecimals, routerAddress, quoteToken, startNonces, config, } = params;
813
811
  const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
814
- const provider = new JsonRpcProvider(config.rpcUrl, { chainId, name: chain });
812
+ // 方案 C:使用缓存的 Provider
813
+ const provider = getCachedProvider(config.rpcUrl, chainId, chain);
815
814
  const useNativeOutput = isNativeToken(quoteToken);
816
815
  const wrappedNative = getWrappedNative(chain);
817
816
  // 创建钱包
818
817
  const wallets = privateKeys.map(pk => new Wallet(pk, provider));
819
- // 获取代币合约
820
818
  const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
821
819
  // ✅ 性能优化:并行获取所有独立的 RPC 数据
822
- // ✅ 如果前端已传入 nonces,则跳过 RPC 获取
823
820
  const [fetchedDecimals, balances, nonces, gasPrice] = await Promise.all([
824
- // 1. 获取代币精度(如果未提供)
825
821
  inputDecimals !== undefined
826
822
  ? Promise.resolve(inputDecimals)
827
823
  : tokenContract.decimals().then((d) => Number(d)).catch(() => 18),
828
- // 2. 批量获取余额
829
824
  Promise.all(wallets.map(w => tokenContract.balanceOf(w.address))),
830
- // 3. 批量获取 nonce(如果前端已传入则跳过)
831
825
  startNonces && startNonces.length === wallets.length
832
826
  ? Promise.resolve(startNonces)
833
827
  : new NonceManager(provider).getNextNoncesForWallets(wallets),
834
- // 4. 获取 gas price
835
828
  getGasPrice(provider, config)
836
829
  ]);
837
830
  const tokenDecimals = fetchedDecimals;
@@ -842,7 +835,6 @@ export async function directV2BatchSell(params) {
842
835
  const sellAmountsWei = [];
843
836
  for (let i = 0; i < wallets.length; i++) {
844
837
  if (sellAmounts && sellAmounts[i]) {
845
- // ✅ 截断小数位数,避免超过代币精度导致 parseUnits 报错
846
838
  const truncatedAmount = truncateDecimals(sellAmounts[i], tokenDecimals);
847
839
  sellAmountsWei.push(ethers.parseUnits(truncatedAmount, tokenDecimals));
848
840
  }
@@ -851,29 +843,44 @@ export async function directV2BatchSell(params) {
851
843
  sellAmountsWei.push((balances[i] * BigInt(pct)) / 100n);
852
844
  }
853
845
  else {
854
- sellAmountsWei.push(balances[i]); // 默认全部卖出
846
+ sellAmountsWei.push(balances[i]);
855
847
  }
856
848
  }
857
- // 构建路径
849
+ const totalSellAmount = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
850
+ // ✅ 修复:对于卖出操作,利润应基于【得到的 BNB/原生代币】而不是【卖出的代币数量】
851
+ // 需要先获取卖出报价,然后基于预估得到的原生代币数量计算利润
852
+ const nativeProfitPromise = (async () => {
853
+ if (useNativeOutput) {
854
+ // 卖出代币 → 得到 BNB:先获取报价,再计算利润
855
+ const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
856
+ if (estimatedBNBOut <= 0n)
857
+ return 0n;
858
+ return calculateProfitAmount(estimatedBNBOut); // 0.3% × 得到的 BNB
859
+ }
860
+ else if (quoteToken) {
861
+ // 卖出代币 → 得到 ERC20:先计算 ERC20 利润,再转换为 BNB
862
+ const baseProfitWei = calculateProfitAmount(totalSellAmount);
863
+ if (baseProfitWei <= 0n)
864
+ return 0n;
865
+ return getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain);
866
+ }
867
+ return 0n;
868
+ })();
869
+ // 构建路径和 Router
858
870
  const outputToken = useNativeOutput ? wrappedNative : quoteToken;
859
871
  const path = [tokenAddress, outputToken];
860
- // ✅ 判断是否是 SwapRouter02 或 DYORSwap
861
872
  const useSwapRouter02 = isSwapRouter02(chain, routerAddress);
862
873
  const useDYORSwap = isDYORSwap(chain, routerAddress);
863
- // DYORSwap 使用手动编码,不需要 ABI
864
- const routerIface = useSwapRouter02
865
- ? new ethers.Interface(SWAP_ROUTER02_V2_ABI)
866
- : new ethers.Interface(V2_ROUTER_ABI);
867
- // ✅ 优化:构建卖出交易数据的辅助函数(同步)
874
+ const routerIface = useSwapRouter02 ? new ethers.Interface(SWAP_ROUTER02_V2_ABI) : new ethers.Interface(V2_ROUTER_ABI);
875
+ // 构建卖出交易数据的辅助函数
868
876
  const buildSellTxData = (wallet, sellAmount) => {
869
877
  if (useDYORSwap) {
870
878
  const DYORSWAP_FACTORY = DIRECT_ROUTERS.XLAYER.DYORSWAP_FACTORY;
871
879
  const ADDRESS_THIS = 2n;
872
880
  const multicallData = [];
873
881
  multicallData.push(encodeDYORSwapExactTokensForTokens(sellAmount, 0n, path, [DYORSWAP_FACTORY], ADDRESS_THIS, 1n, DYORSWAP_FACTORY));
874
- if (useNativeOutput) {
882
+ if (useNativeOutput)
875
883
  multicallData.push(encodeDYORUnwrapWETH9(0n, wallet.address, '0x'));
876
- }
877
884
  return encodeDYORMulticall(BigInt(deadline), multicallData);
878
885
  }
879
886
  else if (useSwapRouter02) {
@@ -886,8 +893,7 @@ export async function directV2BatchSell(params) {
886
893
  return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, multicallData]);
887
894
  }
888
895
  else {
889
- const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [sellAmount, 0n, path, wallet.address]);
890
- return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData]]);
896
+ return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [routerIface.encodeFunctionData('swapExactTokensForTokens', [sellAmount, 0n, path, wallet.address])]]);
891
897
  }
892
898
  }
893
899
  else if (useNativeOutput) {
@@ -897,26 +903,22 @@ export async function directV2BatchSell(params) {
897
903
  return routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [sellAmount, 0n, path, wallet.address, deadline]);
898
904
  }
899
905
  };
900
- // ✅ 性能优化:直接用 sellAmountsWei 找最大输出钱包(跳过 N 次 getAmountsOut RPC 调用)
901
- // 因为卖出金额越大,输出也越大(假设线性关系),无需额外报价
902
906
  const maxOutputIndex = findMaxFlowIndex(sellAmountsWei);
903
- // ✅ 计算贿赂金额(仅 BSC 链)
904
907
  const bribeWei = getBribeAmount(config, chain);
905
908
  const hasBribe = bribeWei > 0n && wallets.length > 0;
906
- // ✅ 计算 nonce 偏移(仅贿赂,已移除授权)
907
909
  const nonceOffsets = wallets.map((_, i) => i === maxOutputIndex && hasBribe ? 1 : 0);
908
- // ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
909
- const bribeTxs = [];
910
+ // ✅ 方案 A:并行签名所有交易(贿赂、卖出、利润)+ 并行获取 ERC20 报价
911
+ const signPromises = [];
912
+ // 贿赂交易
910
913
  if (hasBribe) {
911
- const bribeTx = await buildBribeTransaction(wallets[maxOutputIndex], bribeWei, nonces[maxOutputIndex], // 使用原始 nonce
912
- gasPrice, chainId, txType);
913
- bribeTxs.push(bribeTx);
914
+ signPromises.push(buildBribeTransaction(wallets[maxOutputIndex], bribeWei, nonces[maxOutputIndex], gasPrice, chainId, txType)
915
+ .then(tx => ({ type: 'bribe', index: 0, tx })));
914
916
  }
915
- // ✅ 优化:并行签名所有卖出交易(已移除授权交易)
916
- const swapTxs = await Promise.all(wallets.map(async (wallet, i) => {
917
+ // 卖出交易(并行签名)
918
+ wallets.forEach((wallet, i) => {
917
919
  if (sellAmountsWei[i] <= 0n)
918
- return null;
919
- return wallet.signTransaction({
920
+ return;
921
+ signPromises.push(wallet.signTransaction({
920
922
  to: routerAddress,
921
923
  data: buildSellTxData(wallet, sellAmountsWei[i]),
922
924
  value: 0n,
@@ -925,38 +927,32 @@ export async function directV2BatchSell(params) {
925
927
  gasPrice,
926
928
  chainId,
927
929
  type: txType,
928
- });
929
- })).then(results => results.filter((tx) => tx !== null));
930
- // ✅ 使用 sellAmountsWei 作为输出估计(因为已跳过 getAmountsOut 调用)
931
- const totalOutputEstimate = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
932
- let profitWei = calculateProfitAmount(totalOutputEstimate);
933
- // ✅ 修复:ERC20 输出时,将利润转换为原生代币
934
- if (!useNativeOutput && profitWei > 0n && quoteToken) {
935
- const nativeProfitWei = await getTokenToNativeQuote(provider, quoteToken, profitWei, chain);
936
- if (nativeProfitWei > 0n) {
937
- profitWei = nativeProfitWei;
938
- }
939
- else {
940
- profitWei = 0n;
941
- }
942
- }
943
- // ✅ 利润交易
944
- const profitTxs = [];
930
+ }).then(tx => ({ type: 'swap', index: i, tx })));
931
+ });
932
+ // ✅ 并行执行:签名 + ERC20 报价
933
+ const [signedResults, nativeProfitWei] = await Promise.all([
934
+ Promise.all(signPromises),
935
+ nativeProfitPromise
936
+ ]);
937
+ const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
938
+ // 利润交易(需要等 ERC20 报价完成才能确定金额)
939
+ let profitTx = null;
945
940
  if (profitWei > 0n && wallets.length > 0) {
946
- // 利润交易 nonce = 原始 nonce + 贿赂偏移 + 1(卖出交易)
947
941
  const profitNonce = nonces[maxOutputIndex] + nonceOffsets[maxOutputIndex] + 1;
948
- const profitTx = await buildProfitTransaction(wallets[maxOutputIndex], // 使用输出最大的钱包
949
- profitWei, profitNonce, gasPrice, chainId, txType);
950
- profitTxs.push(profitTx);
942
+ profitTx = await buildProfitTransaction(wallets[maxOutputIndex], profitWei, profitNonce, gasPrice, chainId, txType);
951
943
  }
952
- // ✅ 组装最终交易列表:贿赂 → 卖出 → 利润
944
+ // 按类型分组并按顺序组装
945
+ const validResults = signedResults.filter((r) => r !== null);
946
+ const bribeTxs = validResults.filter(r => r.type === 'bribe').map(r => r.tx);
947
+ const swapTxs = validResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
948
+ const profitTxs = profitTx ? [profitTx] : [];
953
949
  const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
954
950
  return {
955
951
  signedTransactions: signedTxs,
956
952
  metadata: {
957
953
  profitAmount: ethers.formatEther(profitWei),
958
954
  profitRecipient: PROFIT_CONFIG.RECIPIENT,
959
- totalFlow: ethers.formatEther(totalOutputEstimate),
955
+ totalFlow: ethers.formatEther(totalSellAmount),
960
956
  },
961
957
  };
962
958
  }
@@ -976,121 +972,79 @@ export async function directV3BatchBuy(params) {
976
972
  throw new Error('privateKeys 和 buyAmounts 长度必须相同');
977
973
  }
978
974
  const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
979
- const provider = new JsonRpcProvider(config.rpcUrl, { chainId, name: chain });
975
+ // 方案 C:使用缓存的 Provider
976
+ const provider = getCachedProvider(config.rpcUrl, chainId, chain);
980
977
  const useNative = isNativeToken(quoteToken);
981
978
  const wrappedNative = getWrappedNative(chain);
982
- // ✅ 判断是否使用旧版 SwapRouter(Monad Uniswap V3)
983
979
  const useLegacyRouter = isLegacySwapRouter(chain, routerAddress);
984
980
  const routerAbi = useLegacyRouter ? V3_ROUTER_LEGACY_ABI : V3_ROUTER02_ABI;
985
981
  const wallets = privateKeys.map(pk => new Wallet(pk, provider));
986
- // ✅ 性能优化:如果前端已传入 nonces,则跳过 RPC 获取
987
- const [nonces, gasPrice] = await Promise.all([
982
+ // ✅ 预先计算所有金额(同步操作,无 RPC
983
+ const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
984
+ const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
985
+ const baseProfitWei = calculateProfitAmount(totalFlowWei);
986
+ // ✅ 方案 B:并行获取 nonces、gasPrice 和 ERC20 报价
987
+ const [nonces, gasPrice, nativeProfitWei] = await Promise.all([
988
988
  startNonces && startNonces.length === wallets.length
989
989
  ? Promise.resolve(startNonces)
990
990
  : new NonceManager(provider).getNextNoncesForWallets(wallets),
991
- getGasPrice(provider, config)
991
+ getGasPrice(provider, config),
992
+ (!useNative && baseProfitWei > 0n && quoteToken)
993
+ ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
994
+ : Promise.resolve(baseProfitWei)
992
995
  ]);
996
+ const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
993
997
  const gasLimit = getGasLimit(config, 300000);
994
998
  const txType = config.txType ?? 0;
995
999
  const routerIface = new ethers.Interface(routerAbi);
996
1000
  const inputToken = useNative ? wrappedNative : quoteToken;
997
1001
  const deadline = getDeadline();
998
- // ✅ 优化:预先计算所有金额
999
- const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
1000
- const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
1001
- // ✅ 优化:构建交易数据的辅助函数
1002
+ // 构建交易数据的辅助函数
1002
1003
  const buildV3BuyTxData = (wallet, amountWei) => {
1003
1004
  if (useLegacyRouter) {
1004
- const swapParams = {
1005
- tokenIn: inputToken,
1006
- tokenOut: tokenAddress,
1007
- fee: fee,
1008
- recipient: wallet.address,
1009
- deadline: deadline,
1010
- amountIn: amountWei,
1011
- amountOutMinimum: 0n,
1012
- sqrtPriceLimitX96: 0n,
1013
- };
1005
+ const swapParams = { tokenIn: inputToken, tokenOut: tokenAddress, fee, recipient: wallet.address, deadline, amountIn: amountWei, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
1014
1006
  if (useNative) {
1015
1007
  const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1016
1008
  const refundData = routerIface.encodeFunctionData('refundETH', []);
1017
- return {
1018
- txData: routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, refundData]]),
1019
- txValue: amountWei
1020
- };
1021
- }
1022
- else {
1023
- return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
1009
+ return { txData: routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, refundData]]), txValue: amountWei };
1024
1010
  }
1011
+ return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
1025
1012
  }
1026
1013
  else {
1027
- const swapParams = {
1028
- tokenIn: inputToken,
1029
- tokenOut: tokenAddress,
1030
- fee: fee,
1031
- recipient: wallet.address,
1032
- amountIn: amountWei,
1033
- amountOutMinimum: 0n,
1034
- sqrtPriceLimitX96: 0n,
1035
- };
1014
+ const swapParams = { tokenIn: inputToken, tokenOut: tokenAddress, fee, recipient: wallet.address, amountIn: amountWei, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
1036
1015
  if (useNative) {
1037
1016
  const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1038
1017
  const refundData = routerIface.encodeFunctionData('refundETH', []);
1039
- return {
1040
- txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, refundData]]),
1041
- txValue: amountWei
1042
- };
1043
- }
1044
- else {
1045
- return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
1018
+ return { txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, refundData]]), txValue: amountWei };
1046
1019
  }
1020
+ return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
1047
1021
  }
1048
1022
  };
1049
- // ✅ 优化:选择金额最大的钱包支付贿赂和利润
1050
1023
  const maxFlowIndex = findMaxFlowIndex(flowAmounts);
1051
- // ✅ 计算贿赂金额(仅 BSC 链)
1052
1024
  const bribeWei = getBribeAmount(config, chain);
1053
1025
  const hasBribe = bribeWei > 0n && wallets.length > 0;
1054
- // 计算 nonce 偏移
1026
+ const hasProfit = profitWei > 0n;
1055
1027
  const nonceOffsets = wallets.map((_, i) => i === maxFlowIndex && hasBribe ? 1 : 0);
1056
- // ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
1057
- const bribeTxs = [];
1028
+ // ✅ 方案 A:并行签名所有交易(贿赂、主交易、利润)
1029
+ const signPromises = [];
1058
1030
  if (hasBribe) {
1059
- const bribeTx = await buildBribeTransaction(wallets[maxFlowIndex], bribeWei, nonces[maxFlowIndex], gasPrice, chainId, txType);
1060
- bribeTxs.push(bribeTx);
1031
+ signPromises.push(buildBribeTransaction(wallets[maxFlowIndex], bribeWei, nonces[maxFlowIndex], gasPrice, chainId, txType)
1032
+ .then(tx => ({ type: 'bribe', index: 0, tx })));
1061
1033
  }
1062
- // 优化:并行签名所有交易
1063
- const swapTxs = await Promise.all(wallets.map(async (wallet, i) => {
1034
+ wallets.forEach((wallet, i) => {
1064
1035
  const { txData, txValue } = buildV3BuyTxData(wallet, flowAmounts[i]);
1065
- return wallet.signTransaction({
1066
- to: routerAddress,
1067
- data: txData,
1068
- value: txValue,
1069
- nonce: nonces[i] + nonceOffsets[i],
1070
- gasLimit,
1071
- gasPrice,
1072
- chainId,
1073
- type: txType,
1074
- });
1075
- }));
1076
- let profitWei = calculateProfitAmount(totalFlowWei);
1077
- // ✅ 修复:ERC20 交易时,将利润转换为原生代币
1078
- if (!useNative && profitWei > 0n && quoteToken) {
1079
- const nativeProfitWei = await getTokenToNativeQuote(provider, quoteToken, profitWei, chain);
1080
- if (nativeProfitWei > 0n) {
1081
- profitWei = nativeProfitWei;
1082
- }
1083
- else {
1084
- profitWei = 0n;
1085
- }
1036
+ signPromises.push(wallet.signTransaction({ to: routerAddress, data: txData, value: txValue, nonce: nonces[i] + nonceOffsets[i], gasLimit, gasPrice, chainId, type: txType })
1037
+ .then(tx => ({ type: 'swap', index: i, tx })));
1038
+ });
1039
+ if (hasProfit) {
1040
+ const profitNonce = nonces[maxFlowIndex] + nonceOffsets[maxFlowIndex] + 1;
1041
+ signPromises.push(buildProfitTransaction(wallets[maxFlowIndex], profitWei, profitNonce, gasPrice, chainId, txType)
1042
+ .then(tx => ({ type: 'profit', index: 0, tx })));
1086
1043
  }
1087
- // 利润交易
1088
- const profitTxs = [];
1089
- if (profitWei > 0n) {
1090
- const profitTx = await buildProfitTransaction(wallets[maxFlowIndex], profitWei, nonces[maxFlowIndex] + nonceOffsets[maxFlowIndex] + 1, gasPrice, chainId, txType);
1091
- profitTxs.push(profitTx);
1092
- }
1093
- // ✅ 组装最终交易列表:贿赂 → 交易 → 利润
1044
+ const signedResults = await Promise.all(signPromises);
1045
+ const bribeTxs = signedResults.filter(r => r.type === 'bribe').map(r => r.tx);
1046
+ const swapTxs = signedResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
1047
+ const profitTxs = signedResults.filter(r => r.type === 'profit').map(r => r.tx);
1094
1048
  const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
1095
1049
  return {
1096
1050
  signedTransactions: signedTxs,
@@ -1111,24 +1065,20 @@ export async function directV3BatchBuy(params) {
1111
1065
  export async function directV3BatchSell(params) {
1112
1066
  const { chain, privateKeys, sellPercentages, sellAmounts, tokenAddress, tokenDecimals = 18, routerAddress, fee, quoteToken, startNonces, config, } = params;
1113
1067
  const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
1114
- const provider = new JsonRpcProvider(config.rpcUrl, { chainId, name: chain });
1068
+ // 方案 C:使用缓存的 Provider
1069
+ const provider = getCachedProvider(config.rpcUrl, chainId, chain);
1115
1070
  const useNativeOutput = isNativeToken(quoteToken);
1116
1071
  const wrappedNative = getWrappedNative(chain);
1117
- // ✅ 判断是否使用旧版 SwapRouter(Monad Uniswap V3)
1118
1072
  const useLegacyRouter = isLegacySwapRouter(chain, routerAddress);
1119
1073
  const routerAbi = useLegacyRouter ? V3_ROUTER_LEGACY_ABI : V3_ROUTER02_ABI;
1120
1074
  const wallets = privateKeys.map(pk => new Wallet(pk, provider));
1121
1075
  const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
1122
- // ✅ 性能优化:并行获取所有独立的 RPC 数据
1123
- // ✅ 如果前端已传入 nonces,则跳过 RPC 获取
1076
+ // ✅ 并行获取所有 RPC 数据
1124
1077
  const [balances, nonces, gasPrice] = await Promise.all([
1125
- // 1. 批量获取余额
1126
1078
  Promise.all(wallets.map(w => tokenContract.balanceOf(w.address))),
1127
- // 2. 批量获取 nonce(如果前端已传入则跳过)
1128
1079
  startNonces && startNonces.length === wallets.length
1129
1080
  ? Promise.resolve(startNonces)
1130
1081
  : new NonceManager(provider).getNextNoncesForWallets(wallets),
1131
- // 3. 获取 gas price
1132
1082
  getGasPrice(provider, config)
1133
1083
  ]);
1134
1084
  const gasLimit = getGasLimit(config, 350000);
@@ -1147,71 +1097,60 @@ export async function directV3BatchSell(params) {
1147
1097
  sellAmountsWei.push(balances[i]);
1148
1098
  }
1149
1099
  }
1100
+ const totalSellAmount = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
1101
+ // ✅ 修复:对于卖出操作,利润应基于【得到的 BNB/原生代币】而不是【卖出的代币数量】
1102
+ // 需要先获取卖出报价,然后基于预估得到的原生代币数量计算利润
1103
+ const nativeProfitPromise = (async () => {
1104
+ if (useNativeOutput) {
1105
+ // 卖出代币 → 得到 BNB:先获取报价,再计算利润
1106
+ const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
1107
+ if (estimatedBNBOut <= 0n)
1108
+ return 0n;
1109
+ return calculateProfitAmount(estimatedBNBOut); // 0.3% × 得到的 BNB
1110
+ }
1111
+ else if (quoteToken) {
1112
+ // 卖出代币 → 得到 ERC20:先计算 ERC20 利润,再转换为 BNB
1113
+ const baseProfitWei = calculateProfitAmount(totalSellAmount);
1114
+ if (baseProfitWei <= 0n)
1115
+ return 0n;
1116
+ return getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain);
1117
+ }
1118
+ return 0n;
1119
+ })();
1150
1120
  const routerIface = new ethers.Interface(routerAbi);
1151
1121
  const outputToken = useNativeOutput ? wrappedNative : quoteToken;
1152
1122
  const deadline = getDeadline();
1153
- // ✅ 优化:构建卖出交易数据的辅助函数
1123
+ // 构建卖出交易数据的辅助函数
1154
1124
  const buildV3SellTxData = (wallet, sellAmount) => {
1155
1125
  if (useLegacyRouter) {
1156
- const swapParams = {
1157
- tokenIn: tokenAddress,
1158
- tokenOut: outputToken,
1159
- fee: fee,
1160
- recipient: useNativeOutput ? routerAddress : wallet.address,
1161
- deadline: deadline,
1162
- amountIn: sellAmount,
1163
- amountOutMinimum: 0n,
1164
- sqrtPriceLimitX96: 0n,
1165
- };
1126
+ const swapParams = { tokenIn: tokenAddress, tokenOut: outputToken, fee, recipient: useNativeOutput ? routerAddress : wallet.address, deadline, amountIn: sellAmount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
1166
1127
  if (useNativeOutput) {
1167
- const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1168
- const unwrapData = routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address]);
1169
- return routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, unwrapData]]);
1170
- }
1171
- else {
1172
- return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1128
+ return routerIface.encodeFunctionData('multicall(bytes[])', [[routerIface.encodeFunctionData('exactInputSingle', [swapParams]), routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address])]]);
1173
1129
  }
1130
+ return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1174
1131
  }
1175
1132
  else {
1176
- const swapParams = {
1177
- tokenIn: tokenAddress,
1178
- tokenOut: outputToken,
1179
- fee: fee,
1180
- recipient: useNativeOutput ? routerAddress : wallet.address,
1181
- amountIn: sellAmount,
1182
- amountOutMinimum: 0n,
1183
- sqrtPriceLimitX96: 0n,
1184
- };
1133
+ const swapParams = { tokenIn: tokenAddress, tokenOut: outputToken, fee, recipient: useNativeOutput ? routerAddress : wallet.address, amountIn: sellAmount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
1185
1134
  if (useNativeOutput) {
1186
- const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1187
- const unwrapData = routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address]);
1188
- return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, unwrapData]]);
1189
- }
1190
- else {
1191
- return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1135
+ return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [routerIface.encodeFunctionData('exactInputSingle', [swapParams]), routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address])]]);
1192
1136
  }
1137
+ return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1193
1138
  }
1194
1139
  };
1195
- // ✅ 先找出输出最大的钱包索引(用于支付贿赂和利润)
1196
- // 对于 V3 卖出,使用卖出金额作为输出估计
1197
1140
  const maxOutputIndex = findMaxFlowIndex(sellAmountsWei);
1198
- // ✅ 计算贿赂金额(仅 BSC 链)
1199
1141
  const bribeWei = getBribeAmount(config, chain);
1200
1142
  const hasBribe = bribeWei > 0n && wallets.length > 0;
1201
- // ✅ 计算 nonce 偏移(仅贿赂,已移除授权)
1202
1143
  const nonceOffsets = wallets.map((_, i) => i === maxOutputIndex && hasBribe ? 1 : 0);
1203
- // ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
1204
- const bribeTxs = [];
1144
+ // ✅ 方案 A:并行签名所有交易(贿赂、卖出)+ 并行获取 ERC20 报价
1145
+ const signPromises = [];
1205
1146
  if (hasBribe) {
1206
- const bribeTx = await buildBribeTransaction(wallets[maxOutputIndex], bribeWei, nonces[maxOutputIndex], // 使用原始 nonce
1207
- gasPrice, chainId, txType);
1208
- bribeTxs.push(bribeTx);
1147
+ signPromises.push(buildBribeTransaction(wallets[maxOutputIndex], bribeWei, nonces[maxOutputIndex], gasPrice, chainId, txType)
1148
+ .then(tx => ({ type: 'bribe', index: 0, tx })));
1209
1149
  }
1210
- // 优化:并行签名所有卖出交易(已移除授权交易)
1211
- const swapTxs = await Promise.all(wallets.map(async (wallet, i) => {
1150
+ wallets.forEach((wallet, i) => {
1212
1151
  if (sellAmountsWei[i] <= 0n)
1213
- return null;
1214
- return wallet.signTransaction({
1152
+ return;
1153
+ signPromises.push(wallet.signTransaction({
1215
1154
  to: routerAddress,
1216
1155
  data: buildV3SellTxData(wallet, sellAmountsWei[i]),
1217
1156
  value: 0n,
@@ -1220,38 +1159,32 @@ export async function directV3BatchSell(params) {
1220
1159
  gasPrice,
1221
1160
  chainId,
1222
1161
  type: txType,
1223
- });
1224
- })).then(results => results.filter((tx) => tx !== null));
1225
- // ✅ 使用 sellAmountsWei 计算总输出
1226
- const totalOutputEstimate = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
1227
- let profitWei = calculateProfitAmount(totalOutputEstimate);
1228
- // ✅ 修复:ERC20 输出时,将利润转换为原生代币
1229
- if (!useNativeOutput && profitWei > 0n && quoteToken) {
1230
- const nativeProfitWei = await getTokenToNativeQuote(provider, quoteToken, profitWei, chain);
1231
- if (nativeProfitWei > 0n) {
1232
- profitWei = nativeProfitWei;
1233
- }
1234
- else {
1235
- profitWei = 0n;
1236
- }
1237
- }
1238
- // ✅ 利润交易
1239
- const profitTxs = [];
1162
+ }).then(tx => ({ type: 'swap', index: i, tx })));
1163
+ });
1164
+ // ✅ 并行执行:签名 + ERC20 报价
1165
+ const [signedResults, nativeProfitWei] = await Promise.all([
1166
+ Promise.all(signPromises),
1167
+ nativeProfitPromise
1168
+ ]);
1169
+ const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
1170
+ // 利润交易(需要等 ERC20 报价完成才能确定金额)
1171
+ let profitTx = null;
1240
1172
  if (profitWei > 0n && wallets.length > 0) {
1241
- // 利润交易 nonce = 原始 nonce + 贿赂偏移 + 1(卖出交易)
1242
1173
  const profitNonce = nonces[maxOutputIndex] + nonceOffsets[maxOutputIndex] + 1;
1243
- const profitTx = await buildProfitTransaction(wallets[maxOutputIndex], // 使用输出最大的钱包
1244
- profitWei, profitNonce, gasPrice, chainId, txType);
1245
- profitTxs.push(profitTx);
1174
+ profitTx = await buildProfitTransaction(wallets[maxOutputIndex], profitWei, profitNonce, gasPrice, chainId, txType);
1246
1175
  }
1247
- // ✅ 组装最终交易列表:贿赂 → 卖出 → 利润
1176
+ // 按类型分组并按顺序组装
1177
+ const validResults = signedResults.filter((r) => r !== null);
1178
+ const bribeTxs = validResults.filter(r => r.type === 'bribe').map(r => r.tx);
1179
+ const swapTxs = validResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
1180
+ const profitTxs = profitTx ? [profitTx] : [];
1248
1181
  const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
1249
1182
  return {
1250
1183
  signedTransactions: signedTxs,
1251
1184
  metadata: {
1252
1185
  profitAmount: ethers.formatEther(profitWei),
1253
1186
  profitRecipient: PROFIT_CONFIG.RECIPIENT,
1254
- totalFlow: ethers.formatEther(totalOutputEstimate),
1187
+ totalFlow: ethers.formatEther(totalSellAmount),
1255
1188
  },
1256
1189
  };
1257
1190
  }
@@ -15,6 +15,7 @@ export interface FlapSwapSignConfig {
15
15
  reserveGasETH?: number;
16
16
  skipQuoteOnError?: boolean;
17
17
  skipApprovalCheck?: boolean;
18
+ bribeAmount?: number;
18
19
  }
19
20
  export type FlapChain = 'bsc' | 'xlayer' | 'base';
20
21
  export interface FlapSwapConfig extends CommonBundleConfig {
package/dist/index.d.ts CHANGED
@@ -38,7 +38,7 @@ export { inspectTokenLP, getFactoryFromRouter, registerDYORSwap, registerDex, ge
38
38
  export { disperseWithBundle, sweepWithBundle, type DisperseSignParams, type SweepSignParams, type SignedTransactionsResult, type DisperseParams, type SweepParams, type BundleSubmitResult } from './utils/airdrop-sweep.js';
39
39
  export { fourBundleSwapMerkle, fourBatchSwapMerkle, type FourSwapSignConfig, type FourSwapConfig, type FourBundleSwapSignParams, type FourBundleSwapParams, type FourSwapResult, type FourBatchSwapSignParams, type FourBatchSwapResult } from './contracts/tm-bundle-merkle/swap.js';
40
40
  export { flapBundleSwapMerkle, flapBatchSwapMerkle, type FlapSwapSignConfig, type FlapSwapConfig, type FlapBundleSwapSignParams, type FlapBundleSwapParams, type FlapSwapResult, type FlapBatchSwapSignParams, type FlapBatchSwapResult } from './flap/portal-bundle-merkle/swap.js';
41
- export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle, type PancakeSwapSignConfig, type PancakeBundleSwapSignParams, type PancakeSwapConfig, type PancakeBundleSwapParams, type PancakeSwapResult, type PancakeBatchSwapSignParams, type PancakeBatchSwapResult, type SwapRouteType, type V2RouteParams, type V3SingleRouteParams, type V3MultiRouteParams, type RouteParams } from './pancake/bundle-swap.js';
41
+ export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle, pancakeQuickBatchSwapMerkle, type PancakeSwapSignConfig, type PancakeBundleSwapSignParams, type PancakeSwapConfig, type PancakeBundleSwapParams, type PancakeSwapResult, type PancakeBatchSwapSignParams, type PancakeBatchSwapResult, type PancakeQuickBatchSwapParams, type PancakeQuickBatchSwapResult, type SwapRouteType, type V2RouteParams, type V3SingleRouteParams, type V3MultiRouteParams, type RouteParams } from './pancake/bundle-swap.js';
42
42
  export { fourBundleBuyFirstMerkle, type FourBuyFirstConfig, type FourBundleBuyFirstParams, type FourBuyFirstSignConfig, type FourBundleBuyFirstSignParams, type FourBuyFirstResult } from './contracts/tm-bundle-merkle/swap-buy-first.js';
43
43
  export { flapBundleBuyFirstMerkle, type FlapBuyFirstSignConfig, type FlapBuyFirstConfig, type FlapBundleBuyFirstSignParams, type FlapBundleBuyFirstParams, type FlapBuyFirstResult } from './flap/portal-bundle-merkle/swap-buy-first.js';
44
44
  export { pancakeBundleBuyFirstMerkle, type PancakeBuyFirstSignConfig, type PancakeBuyFirstConfig, type PancakeBundleBuyFirstSignParams, type PancakeBundleBuyFirstParams, type PancakeBuyFirstResult } from './pancake/bundle-buy-first.js';
package/dist/index.js CHANGED
@@ -56,7 +56,7 @@ export { fourBundleSwapMerkle, fourBatchSwapMerkle } from './contracts/tm-bundle
56
56
  // Flap内盘换手
57
57
  export { flapBundleSwapMerkle, flapBatchSwapMerkle } from './flap/portal-bundle-merkle/swap.js';
58
58
  // PancakeSwap V2/V3通用换手
59
- export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle } from './pancake/bundle-swap.js';
59
+ export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle, pancakeQuickBatchSwapMerkle } from './pancake/bundle-swap.js';
60
60
  // 先买后卖(Buy-First)入口
61
61
  export { fourBundleBuyFirstMerkle } from './contracts/tm-bundle-merkle/swap-buy-first.js';
62
62
  export { flapBundleBuyFirstMerkle } from './flap/portal-bundle-merkle/swap-buy-first.js';
@@ -115,3 +115,45 @@ export interface PancakeBatchSwapResult {
115
115
  * 交易顺序:[授权(可选)] → [卖出] → [买入1, 买入2, ..., 买入N] → [利润]
116
116
  */
117
117
  export declare function pancakeBatchSwapMerkle(params: PancakeBatchSwapSignParams): Promise<PancakeBatchSwapResult>;
118
+ /**
119
+ * 快捷批量换手参数
120
+ * 特点:子钱包不需要预先有余额,资金来自主钱包卖出所得
121
+ */
122
+ export interface PancakeQuickBatchSwapParams {
123
+ sellerPrivateKey: string;
124
+ sellAmount?: string;
125
+ sellPercentage?: number;
126
+ buyerPrivateKeys: string[];
127
+ buyerRatios?: number[];
128
+ buyerAmounts?: string[];
129
+ tokenAddress: string;
130
+ routeParams: RouteParams;
131
+ config: PancakeSwapSignConfig;
132
+ }
133
+ /**
134
+ * 快捷批量换手结果
135
+ */
136
+ export interface PancakeQuickBatchSwapResult {
137
+ signedTransactions: string[];
138
+ metadata?: {
139
+ sellerAddress: string;
140
+ buyerAddresses: string[];
141
+ sellAmount: string;
142
+ estimatedBNBOut: string;
143
+ transferAmounts: string[];
144
+ profitAmount?: string;
145
+ };
146
+ }
147
+ /**
148
+ * PancakeSwap 快捷批量换手(资金自动流转)
149
+ *
150
+ * 流程:[贿赂] → [卖出] → [转账到子钱包] → [买入] → [利润]
151
+ *
152
+ * 特点:
153
+ * - 子钱包不需要预先有 BNB 余额
154
+ * - 资金来自主钱包卖出代币所得
155
+ * - 提升资金利用率
156
+ *
157
+ * 限制:最多 12 个买方(转账 + 买入 = 24 笔,加上贿赂/卖出/利润接近 25 笔限制)
158
+ */
159
+ export declare function pancakeQuickBatchSwapMerkle(params: PancakeQuickBatchSwapParams): Promise<PancakeQuickBatchSwapResult>;
@@ -667,3 +667,208 @@ export async function pancakeBatchSwapMerkle(params) {
667
667
  }
668
668
  };
669
669
  }
670
+ /**
671
+ * PancakeSwap 快捷批量换手(资金自动流转)
672
+ *
673
+ * 流程:[贿赂] → [卖出] → [转账到子钱包] → [买入] → [利润]
674
+ *
675
+ * 特点:
676
+ * - 子钱包不需要预先有 BNB 余额
677
+ * - 资金来自主钱包卖出代币所得
678
+ * - 提升资金利用率
679
+ *
680
+ * 限制:最多 12 个买方(转账 + 买入 = 24 笔,加上贿赂/卖出/利润接近 25 笔限制)
681
+ */
682
+ export async function pancakeQuickBatchSwapMerkle(params) {
683
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config } = params;
684
+ // ✅ 校验买方数量(最多 12 个,因为每个买方需要 2 笔交易:转账 + 买入)
685
+ const MAX_BUYERS = 12;
686
+ if (buyerPrivateKeys.length === 0) {
687
+ throw new Error('至少需要一个买方钱包');
688
+ }
689
+ if (buyerPrivateKeys.length > MAX_BUYERS) {
690
+ throw new Error(`快捷模式买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
691
+ }
692
+ // ✅ 校验分配模式
693
+ if (!buyerRatios && !buyerAmounts) {
694
+ throw new Error('必须提供 buyerRatios 或 buyerAmounts');
695
+ }
696
+ if (buyerRatios && buyerRatios.length !== buyerPrivateKeys.length) {
697
+ throw new Error(`buyerRatios 长度 (${buyerRatios.length}) 与 buyerPrivateKeys 长度 (${buyerPrivateKeys.length}) 不匹配`);
698
+ }
699
+ if (buyerAmounts && buyerAmounts.length !== buyerPrivateKeys.length) {
700
+ throw new Error(`buyerAmounts 长度 (${buyerAmounts.length}) 与 buyerPrivateKeys 长度 (${buyerPrivateKeys.length}) 不匹配`);
701
+ }
702
+ const context = createPancakeContext(config);
703
+ const seller = new Wallet(sellerPrivateKey, context.provider);
704
+ const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider));
705
+ // ✅ 创建共享资源
706
+ const nonceManager = new NonceManager(context.provider);
707
+ const finalGasLimit = getGasLimit(config);
708
+ const gasPrice = await getGasPrice(context.provider, config);
709
+ const txType = config.txType ?? 0;
710
+ // ✅ 计算卖出数量
711
+ const { amount: sellAmountWei, decimals } = await calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage);
712
+ // ✅ 获取卖出报价
713
+ const quoteResult = await quoteSellOutput({
714
+ routeParams,
715
+ sellAmountWei,
716
+ provider: context.provider
717
+ });
718
+ const estimatedBNBOut = quoteResult.estimatedBNBOut;
719
+ // ✅ 计算利润(从卖出所得中预扣)
720
+ const profitAmount = calculateProfitAmount(estimatedBNBOut);
721
+ const distributableBNB = estimatedBNBOut - profitAmount; // 可分配金额
722
+ // ✅ 计算每个子钱包分到的金额
723
+ let transferAmountsWei;
724
+ if (buyerAmounts && buyerAmounts.length === buyers.length) {
725
+ // 数量模式:使用指定的金额
726
+ transferAmountsWei = buyerAmounts.map(amt => ethers.parseEther(amt));
727
+ // 校验总金额不超过可分配金额
728
+ const totalTransfer = transferAmountsWei.reduce((a, b) => a + b, 0n);
729
+ if (totalTransfer > distributableBNB) {
730
+ throw new Error(`指定的买入总金额 (${ethers.formatEther(totalTransfer)} BNB) 超过可分配金额 (${ethers.formatEther(distributableBNB)} BNB)`);
731
+ }
732
+ }
733
+ else if (buyerRatios && buyerRatios.length === buyers.length) {
734
+ // 比例模式:按比例分配可分配金额
735
+ transferAmountsWei = buyerRatios.map(ratio => {
736
+ const amount = (distributableBNB * BigInt(Math.round(ratio * 10000))) / 10000n;
737
+ return amount;
738
+ });
739
+ }
740
+ else {
741
+ throw new Error('必须提供 buyerRatios 或 buyerAmounts');
742
+ }
743
+ // ✅ 获取贿赂金额
744
+ const bribeAmount = config.bribeAmount && config.bribeAmount > 0
745
+ ? ethers.parseEther(String(config.bribeAmount))
746
+ : 0n;
747
+ // ✅ 验证主钱包余额(需要支付 Gas 费用)
748
+ const sellerBalance = await seller.provider.getBalance(seller.address);
749
+ const estimatedGasCost = gasPrice * finalGasLimit * BigInt(3 + buyers.length * 2); // 贿赂 + 卖出 + 转账 + 利润
750
+ if (sellerBalance < bribeAmount + estimatedGasCost) {
751
+ throw new Error(`主钱包 BNB 余额不足 (用于支付 Gas): 需要约 ${ethers.formatEther(bribeAmount + estimatedGasCost)} BNB, 实际 ${ethers.formatEther(sellerBalance)} BNB`);
752
+ }
753
+ // ==================== 规划 Nonce ====================
754
+ // 卖方: [贿赂(可选)] → [卖出] → [转账1, 转账2, ...] → [利润(可选)]
755
+ // 买方: [买入]
756
+ let sellerNonce = await nonceManager.getNextNonce(seller);
757
+ // ✅ 贿赂交易(首位)
758
+ let bribeTx = null;
759
+ if (bribeAmount > 0n) {
760
+ bribeTx = await seller.signTransaction({
761
+ to: BLOCKRAZOR_BUILDER_EOA,
762
+ value: bribeAmount,
763
+ nonce: sellerNonce++,
764
+ gasPrice,
765
+ gasLimit: 21000n,
766
+ chainId: context.chainId,
767
+ type: txType
768
+ });
769
+ }
770
+ // ✅ 卖出交易
771
+ const proxySeller = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, seller);
772
+ const deadline = Math.floor(Date.now() / 1000) + 600;
773
+ let sellUnsigned;
774
+ if (routeParams.routeType === 'v2') {
775
+ const { v2Path } = routeParams;
776
+ sellUnsigned = await proxySeller.swapV2.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline, { value: FLAT_FEE });
777
+ }
778
+ else if (routeParams.routeType === 'v3-single') {
779
+ const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
780
+ sellUnsigned = await proxySeller.swapV3Single.populateTransaction(v3TokenIn, v3TokenOut, v3Fee, sellAmountWei, 0n, seller.address, { value: FLAT_FEE });
781
+ }
782
+ else {
783
+ const { v3LpAddresses, v3ExactTokenIn } = routeParams;
784
+ sellUnsigned = await proxySeller.swapV3MultiHop.populateTransaction(v3LpAddresses, v3ExactTokenIn, sellAmountWei, 0n, seller.address, { value: FLAT_FEE });
785
+ }
786
+ const signedSell = await seller.signTransaction({
787
+ ...sellUnsigned,
788
+ from: seller.address,
789
+ nonce: sellerNonce++,
790
+ gasLimit: finalGasLimit,
791
+ gasPrice,
792
+ chainId: context.chainId,
793
+ type: txType
794
+ });
795
+ // ✅ 转账交易(主钱包 → 各子钱包)
796
+ const transferTxs = [];
797
+ for (let i = 0; i < buyers.length; i++) {
798
+ const transferTx = await seller.signTransaction({
799
+ to: buyers[i].address,
800
+ value: transferAmountsWei[i],
801
+ nonce: sellerNonce++,
802
+ gasPrice,
803
+ gasLimit: 21000n,
804
+ chainId: context.chainId,
805
+ type: txType
806
+ });
807
+ transferTxs.push(transferTx);
808
+ }
809
+ // ✅ 利润交易
810
+ let profitTx = null;
811
+ if (profitAmount > 0n) {
812
+ profitTx = await seller.signTransaction({
813
+ to: PROFIT_CONFIG.RECIPIENT,
814
+ value: profitAmount,
815
+ nonce: sellerNonce++,
816
+ gasPrice,
817
+ gasLimit: 21000n,
818
+ chainId: context.chainId,
819
+ type: txType
820
+ });
821
+ }
822
+ // ✅ 并行获取所有买方的 nonce
823
+ const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
824
+ // ✅ 并行构建买入交易
825
+ const signedBuys = await Promise.all(buyers.map(async (buyer, i) => {
826
+ const buyAmount = transferAmountsWei[i];
827
+ const proxyBuyer = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, buyer);
828
+ const buyValue = buyAmount + FLAT_FEE;
829
+ let buyUnsigned;
830
+ if (routeParams.routeType === 'v2') {
831
+ const { v2Path } = routeParams;
832
+ const reversePath = [...v2Path].reverse();
833
+ buyUnsigned = await proxyBuyer.swapV2.populateTransaction(buyAmount, 0n, reversePath, buyer.address, deadline, { value: buyValue });
834
+ }
835
+ else if (routeParams.routeType === 'v3-single') {
836
+ const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
837
+ buyUnsigned = await proxyBuyer.swapV3Single.populateTransaction(v3TokenOut, v3TokenIn, v3Fee, buyAmount, 0n, buyer.address, { value: buyValue });
838
+ }
839
+ else {
840
+ const { v3LpAddresses } = routeParams;
841
+ buyUnsigned = await proxyBuyer.swapV3MultiHop.populateTransaction(v3LpAddresses, tokenAddress, buyAmount, 0n, buyer.address, { value: buyValue });
842
+ }
843
+ return buyer.signTransaction({
844
+ ...buyUnsigned,
845
+ from: buyer.address,
846
+ nonce: buyerNonces[i],
847
+ gasLimit: finalGasLimit,
848
+ gasPrice,
849
+ chainId: context.chainId,
850
+ type: txType
851
+ });
852
+ }));
853
+ nonceManager.clearTemp();
854
+ // ✅ 按顺序组装交易数组:贿赂 → 卖出 → 转账 → 买入 → 利润
855
+ const signedTransactions = [];
856
+ if (bribeTx)
857
+ signedTransactions.push(bribeTx); // 贿赂(首位)
858
+ signedTransactions.push(signedSell); // 卖出
859
+ signedTransactions.push(...transferTxs); // 转账到各子钱包
860
+ signedTransactions.push(...signedBuys); // 各子钱包买入
861
+ if (profitTx)
862
+ signedTransactions.push(profitTx); // 利润(末尾)
863
+ return {
864
+ signedTransactions,
865
+ metadata: {
866
+ sellerAddress: seller.address,
867
+ buyerAddresses: buyers.map(b => b.address),
868
+ sellAmount: ethers.formatUnits(sellAmountWei, decimals),
869
+ estimatedBNBOut: ethers.formatEther(estimatedBNBOut),
870
+ transferAmounts: transferAmountsWei.map(amt => ethers.formatEther(amt)),
871
+ profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
872
+ }
873
+ };
874
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",