four-flap-meme-sdk 1.3.73 → 1.3.76

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.
@@ -16,6 +16,25 @@ import { PROFIT_CONFIG } from '../utils/constants.js';
16
16
  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
17
17
  const DEFAULT_GAS_LIMIT = 300000;
18
18
  const DEADLINE_MINUTES = 20;
19
+ // ✅ Router ABI(用于 ERC20 → 原生代币报价)
20
+ const QUOTE_ROUTER_ABI = [
21
+ 'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
22
+ ];
23
+ // ✅ 各链的报价 Router 和包装代币地址
24
+ const QUOTE_CONFIG = {
25
+ BSC: {
26
+ router: '0x10ED43C718714eb63d5aA57B78B54704E256024E', // PancakeSwap V2
27
+ wrappedNative: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', // WBNB
28
+ },
29
+ MONAD: {
30
+ router: '0xb1bc24c34e88f7d43d5923034e3a14b24daacff9', // PancakeSwap V2
31
+ wrappedNative: '0x3bd359c1119da7da1d913d1c4d2b7c461115433a', // WMON
32
+ },
33
+ XLAYER: {
34
+ router: '0x881fb2f98c13d521009464e7d1cbf16e1b394e8e', // PotatoSwap V2
35
+ wrappedNative: '0xe538905cf8410324e03a5a23c1c177a474d59b2b', // WOKB
36
+ },
37
+ };
19
38
  /**
20
39
  * 截断小数位数,避免超过代币精度导致 parseUnits 报错
21
40
  * 例如:truncateDecimals("21906.025000000000000000", 1) => "21906.0"
@@ -504,6 +523,52 @@ async function getGasPrice(provider, config) {
504
523
  function isNativeToken(quoteToken) {
505
524
  return !quoteToken || quoteToken === ZERO_ADDRESS;
506
525
  }
526
+ /**
527
+ * ✅ 获取 ERC20 代币 → 原生代币的报价
528
+ * 用于将 ERC20 利润转换为原生代币(和 core.ts 逻辑一致)
529
+ * @param provider - Provider 实例
530
+ * @param tokenAddress - ERC20 代币地址
531
+ * @param tokenAmount - 代币数量(wei)
532
+ * @param chain - 链名称
533
+ * @returns 等值的原生代币数量(wei),失败返回 0n
534
+ */
535
+ async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chain) {
536
+ if (tokenAmount <= 0n)
537
+ return 0n;
538
+ const chainUpper = chain.toUpperCase();
539
+ const config = QUOTE_CONFIG[chainUpper];
540
+ if (!config) {
541
+ console.warn(`[getTokenToNativeQuote] 不支持的链: ${chain}`);
542
+ return 0n;
543
+ }
544
+ try {
545
+ const router = new Contract(config.router, QUOTE_ROUTER_ABI, provider);
546
+ const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, config.wrappedNative]);
547
+ const nativeAmount = amounts[1];
548
+ console.log(`[getTokenToNativeQuote] ${ethers.formatEther(tokenAmount)} Token → ${ethers.formatEther(nativeAmount)} Native`);
549
+ return nativeAmount;
550
+ }
551
+ catch (error) {
552
+ console.warn(`[getTokenToNativeQuote] 报价失败,跳过利润提取:`, error);
553
+ return 0n; // 报价失败返回 0(可能是流动性不足或路径不存在)
554
+ }
555
+ }
556
+ /**
557
+ * ✅ 找到金额最大的钱包索引(和 core.ts 逻辑一致)
558
+ */
559
+ function findMaxFlowIndex(amounts) {
560
+ if (amounts.length === 0)
561
+ return 0;
562
+ let maxIndex = 0;
563
+ let maxValue = amounts[0];
564
+ for (let i = 1; i < amounts.length; i++) {
565
+ if (amounts[i] > maxValue) {
566
+ maxValue = amounts[i];
567
+ maxIndex = i;
568
+ }
569
+ }
570
+ return maxIndex;
571
+ }
507
572
  /** 计算利润金额 */
508
573
  function calculateProfitAmount(totalFlowWei) {
509
574
  return (totalFlowWei * BigInt(PROFIT_CONFIG.RATE_BPS)) / 10000n;
@@ -602,98 +667,61 @@ export async function directV2BatchBuy(params) {
602
667
  const routerIface = useSwapRouter02
603
668
  ? new ethers.Interface(SWAP_ROUTER02_V2_ABI)
604
669
  : new ethers.Interface(V2_ROUTER_ABI);
605
- const signedTxs = [];
606
- let totalFlowWei = 0n;
607
- for (let i = 0; i < wallets.length; i++) {
608
- const wallet = wallets[i];
609
- const amountWei = ethers.parseUnits(buyAmounts[i], quoteTokenDecimals);
610
- totalFlowWei += amountWei;
611
- let txData;
612
- let txValue;
670
+ // 优化:预先计算所有金额(同步操作)
671
+ const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
672
+ const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
673
+ // 优化:构建未签名交易的辅助函数
674
+ const buildTxData = (wallet, amountWei) => {
613
675
  if (useDYORSwap) {
614
- // ✅ DYORSwap: 使用手动编码的 calldata
615
- //
616
- // 根据成功交易分析,买入流程:
617
- // 1. refundETH - 用于退还多余的 ETH (使用固定的 ETHBack_Dividend 地址)
618
- // 2. swapExactTokensForTokens - 执行交换
619
676
  const DYORSWAP_FACTORY = DIRECT_ROUTERS.XLAYER.DYORSWAP_FACTORY;
620
677
  const multicallData = [];
621
- // 1. refundETH(fee) - 使用固定的 ETHBack_Dividend 地址
622
- // fee = 30 (0x1e) 是成功交易中使用的值
623
- const refundData = encodeDYORRefundETH(30);
624
- multicallData.push(refundData);
625
- // 2. swapExactTokensForTokens
626
- // 参数: amountIn, amountOutMin, path, pools, to, flag, factory
627
- const swapData = encodeDYORSwapExactTokensForTokens(useNative ? 0n : amountWei, // amountIn: ETH 买入时为 0
628
- 0n, // amountOutMin
629
- path, // path: [WOKB, token]
630
- [], // pools (不使用)
631
- wallet.address, // to = 用户地址
632
- 1n, // flag = 1
633
- DYORSWAP_FACTORY // factory
634
- );
635
- multicallData.push(swapData);
636
- // 使用 multicall(uint256 deadline, bytes[])
637
- txData = encodeDYORMulticall(BigInt(deadline), multicallData);
638
- txValue = useNative ? amountWei : 0n;
678
+ multicallData.push(encodeDYORRefundETH(30));
679
+ multicallData.push(encodeDYORSwapExactTokensForTokens(useNative ? 0n : amountWei, 0n, path, [], wallet.address, 1n, DYORSWAP_FACTORY));
680
+ return {
681
+ txData: encodeDYORMulticall(BigInt(deadline), multicallData),
682
+ txValue: useNative ? amountWei : 0n
683
+ };
639
684
  }
640
685
  else if (useSwapRouter02) {
641
- // SwapRouter02: 使用 multicall(deadline, bytes[]) 组合调用
642
- // ABI 中没有 swapExactETHForTokens,只有 swapExactTokensForTokens
643
- if (useNative) {
644
- // ETH -> 代币:直接用 swapExactTokensForTokens(参考之前成功的交易)
645
- // 之前成功的交易只用了一个 swapExactTokensForTokens,没有 wrapETH
646
- // SwapRouter02 会自动处理 msg.value 的 ETH
647
- const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
648
- amountWei,
649
- 0n, // amountOutMin
650
- path, // path: [WETH, ..., tokenOut]
651
- wallet.address, // to
652
- ]);
653
- // 使用 multicall(uint256 deadline, bytes[]) - 带 deadline 的版本
654
- txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
655
- deadline,
656
- [swapData],
657
- ]);
658
- txValue = amountWei;
659
- }
660
- else {
661
- // 代币 -> 代币:也用 multicall 包装
662
- const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
663
- amountWei,
664
- 0n, // amountOutMin
665
- path,
666
- wallet.address, // to
667
- ]);
668
- txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
669
- deadline,
670
- [swapData],
671
- ]);
672
- txValue = 0n;
673
- }
674
- }
675
- else if (useNative) {
676
- // 传统 V2 Router: 原生币 → Token
677
- txData = routerIface.encodeFunctionData('swapExactETHForTokensSupportingFeeOnTransferTokens', [
678
- 0n, // amountOutMin
686
+ const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
687
+ amountWei,
688
+ 0n,
679
689
  path,
680
690
  wallet.address,
681
- deadline,
682
691
  ]);
683
- txValue = amountWei;
692
+ return {
693
+ txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData]]),
694
+ txValue: useNative ? amountWei : 0n
695
+ };
696
+ }
697
+ else if (useNative) {
698
+ return {
699
+ txData: routerIface.encodeFunctionData('swapExactETHForTokensSupportingFeeOnTransferTokens', [
700
+ 0n,
701
+ path,
702
+ wallet.address,
703
+ deadline,
704
+ ]),
705
+ txValue: amountWei
706
+ };
684
707
  }
685
708
  else {
686
- // 传统 V2 Router: ERC20 → Token (需要先授权)
687
- txData = routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [
688
- amountWei,
689
- 0n, // amountOutMin
690
- path,
691
- wallet.address,
692
- deadline,
693
- ]);
694
- txValue = 0n;
709
+ return {
710
+ txData: routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [
711
+ amountWei,
712
+ 0n,
713
+ path,
714
+ wallet.address,
715
+ deadline,
716
+ ]),
717
+ txValue: 0n
718
+ };
695
719
  }
696
- const tx = {
720
+ };
721
+ // ✅ 优化:并行签名所有交易
722
+ const signedTxs = await Promise.all(wallets.map(async (wallet, i) => {
723
+ const { txData, txValue } = buildTxData(wallet, flowAmounts[i]);
724
+ return wallet.signTransaction({
697
725
  to: routerAddress,
698
726
  data: txData,
699
727
  value: txValue,
@@ -702,15 +730,26 @@ export async function directV2BatchBuy(params) {
702
730
  gasPrice,
703
731
  chainId,
704
732
  type: txType,
705
- };
706
- const signedTx = await wallet.signTransaction(tx);
707
- signedTxs.push(signedTx);
733
+ });
734
+ }));
735
+ // ✅ 优化:选择金额最大的钱包支付利润(和 core.ts 逻辑一致)
736
+ const maxFlowIndex = findMaxFlowIndex(flowAmounts);
737
+ let profitWei = calculateProfitAmount(totalFlowWei);
738
+ // ✅ 修复:ERC20 交易时,将利润转换为原生代币
739
+ if (!useNative && profitWei > 0n && quoteToken) {
740
+ const nativeProfitWei = await getTokenToNativeQuote(provider, quoteToken, profitWei, chain);
741
+ if (nativeProfitWei > 0n) {
742
+ profitWei = nativeProfitWei;
743
+ console.log(`[V2 Buy] ERC20 利润转换: ${ethers.formatEther(calculateProfitAmount(totalFlowWei))} Token → ${ethers.formatEther(profitWei)} Native`);
744
+ }
745
+ else {
746
+ console.log(`[V2 Buy] ERC20 利润报价失败,跳过利润提取`);
747
+ profitWei = 0n; // 报价失败,跳过利润
748
+ }
708
749
  }
709
- // 生成利润交易(使用第一个钱包支付)
710
- const profitWei = calculateProfitAmount(totalFlowWei);
711
750
  if (profitWei > 0n) {
712
- const profitNonce = nonces[0] + 1; // 第一个钱包的下一个 nonce
713
- const profitTx = await buildProfitTransaction(wallets[0], profitWei, profitNonce, gasPrice, chainId, txType);
751
+ const profitNonce = nonces[maxFlowIndex] + 1; // 使用金额最大的钱包
752
+ const profitTx = await buildProfitTransaction(wallets[maxFlowIndex], profitWei, profitNonce, gasPrice, chainId, txType);
714
753
  signedTxs.push(profitTx);
715
754
  }
716
755
  return {
@@ -783,163 +822,129 @@ export async function directV2BatchSell(params) {
783
822
  ? new ethers.Interface(SWAP_ROUTER02_V2_ABI)
784
823
  : new ethers.Interface(V2_ROUTER_ABI);
785
824
  const approveIface = new ethers.Interface(ERC20_ABI);
786
- const signedTxs = [];
787
- let totalOutputEstimate = 0n;
788
- let currentNonceOffset = new Array(wallets.length).fill(0);
789
- // 检查授权并构建交易
790
- for (let i = 0; i < wallets.length; i++) {
791
- const wallet = wallets[i];
792
- const sellAmount = sellAmountsWei[i];
793
- if (sellAmount <= 0n)
794
- continue;
795
- // 检查授权
796
- if (!config.skipApprovalCheck) {
797
- const allowance = await tokenContract.allowance(wallet.address, routerAddress);
798
- if (allowance < sellAmount) {
799
- // 需要授权
800
- const approveTx = {
801
- to: tokenAddress,
802
- data: approveIface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]),
803
- value: 0n,
804
- nonce: nonces[i] + currentNonceOffset[i],
805
- gasLimit: 60000n,
806
- gasPrice,
807
- chainId,
808
- type: txType,
809
- };
810
- signedTxs.push(await wallet.signTransaction(approveTx));
811
- currentNonceOffset[i]++;
812
- }
813
- }
814
- // 卖出交易
815
- let txData;
825
+ // 优化:构建卖出交易数据的辅助函数(同步)
826
+ const buildSellTxData = (wallet, sellAmount) => {
816
827
  if (useDYORSwap) {
817
- // ✅ DYORSwap: 使用手动编码的 calldata
818
- //
819
- // 卖出流程 (根据成功交易分析):
820
- // 1. swapExactTokensForTokens - 代币换成 WOKB,发到 address(2) = Router
821
- // 2. unwrapWETH9 - 将 WOKB 解包为 OKB 发给用户
822
828
  const DYORSWAP_FACTORY = DIRECT_ROUTERS.XLAYER.DYORSWAP_FACTORY;
823
- const ADDRESS_THIS = 2n; // address(2) 作为 uint256
829
+ const ADDRESS_THIS = 2n;
824
830
  const multicallData = [];
825
- // 1. swapExactTokensForTokens
826
- // 参数: amountIn, amountOutMin, path, pools, to, flag, factory
827
- const swapData = encodeDYORSwapExactTokensForTokens(sellAmount, // amountIn
828
- 0n, // amountOutMin
829
- path, // path: [token, WOKB]
830
- [DYORSWAP_FACTORY], // pools
831
- ADDRESS_THIS, // to = address(2) - 让 Router 接收 WOKB
832
- 1n, // flag = 1
833
- DYORSWAP_FACTORY // factory
834
- );
835
- multicallData.push(swapData);
831
+ multicallData.push(encodeDYORSwapExactTokensForTokens(sellAmount, 0n, path, [DYORSWAP_FACTORY], ADDRESS_THIS, 1n, DYORSWAP_FACTORY));
836
832
  if (useNativeOutput) {
837
- // 2. unwrapWETH9 - 将 WOKB 解包为 OKB 发给用户
838
- const unwrapData = encodeDYORUnwrapWETH9(0n, // amountMinimum = 0
839
- wallet.address, // recipient = 用户
840
- '0x' // pools = 空 bytes
841
- );
842
- multicallData.push(unwrapData);
833
+ multicallData.push(encodeDYORUnwrapWETH9(0n, wallet.address, '0x'));
843
834
  }
844
- // 使用 multicall(uint256 deadline, bytes[])
845
- txData = encodeDYORMulticall(BigInt(deadline), multicallData);
835
+ return encodeDYORMulticall(BigInt(deadline), multicallData);
846
836
  }
847
837
  else if (useSwapRouter02) {
848
- // ✅ SwapRouter02: 使用 multicall(deadline, bytes[]) 组合调用
849
- //
850
- // 重要:SwapRouter02 的 V2 swapExactTokensForTokens 直接把代币发给 to 地址
851
- // 如果需要 ETH,把 WETH 发给 Router,然后调用 unwrapWETH9
852
- //
853
- // 根据合约源码,address(2) = ADDRESS_THIS = Router 合约自己
854
838
  const ADDRESS_THIS = '0x0000000000000000000000000000000000000002';
855
839
  if (useNativeOutput) {
856
- // 代币 -> ETH:
857
- // 方案1:swapExactTokensForTokens(to=Router) + unwrapWETH9
858
- // 方案2:直接发 WETH 给用户(用户需要手动 unwrap)
859
- //
860
- // 使用方案1:发送到 Router 地址(真实地址,不是 address(2)
861
- const routerRealAddress = routerAddress; // SwapRouter02 的真实地址
862
- const multicallData = [];
863
- // 1. swapExactTokensForTokens - 从用户转代币,换成 WETH 发到 Router 真实地址
864
- const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
865
- sellAmount,
866
- 0n, // amountOutMin
867
- path, // path: [token, ..., WETH]
868
- ADDRESS_THIS, // to = address(2),让 Router 接收 WETH
869
- ]);
870
- multicallData.push(swapData);
871
- // 2. unwrapWETH9 - 将 Router 中的 WETH 解包为 ETH 发送给用户
872
- const unwrapData = routerIface.encodeFunctionData('unwrapWETH9(uint256,address)', [
873
- 0n, // amountMinimum = 0,接受任意数量
874
- wallet.address, // recipient = 用户
875
- ]);
876
- multicallData.push(unwrapData);
877
- // 使用 multicall(uint256 deadline, bytes[]) - 带 deadline 的版本
878
- txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
879
- deadline,
880
- multicallData,
881
- ]);
840
+ const multicallData = [
841
+ routerIface.encodeFunctionData('swapExactTokensForTokens', [sellAmount, 0n, path, ADDRESS_THIS]),
842
+ routerIface.encodeFunctionData('unwrapWETH9(uint256,address)', [0n, wallet.address]),
843
+ ];
844
+ return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, multicallData]);
882
845
  }
883
846
  else {
884
- // 代币 -> 代币:直接 swapExactTokensForTokens
885
- const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
886
- sellAmount,
887
- 0n, // amountOutMin
888
- path,
889
- wallet.address, // to = 用户
890
- ]);
891
- txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
892
- deadline,
893
- [swapData],
894
- ]);
847
+ const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [sellAmount, 0n, path, wallet.address]);
848
+ return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData]]);
895
849
  }
896
850
  }
897
851
  else if (useNativeOutput) {
898
- txData = routerIface.encodeFunctionData('swapExactTokensForETHSupportingFeeOnTransferTokens', [
899
- sellAmount,
900
- 0n,
901
- path,
902
- wallet.address,
903
- deadline,
904
- ]);
852
+ return routerIface.encodeFunctionData('swapExactTokensForETHSupportingFeeOnTransferTokens', [sellAmount, 0n, path, wallet.address, deadline]);
905
853
  }
906
854
  else {
907
- txData = routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [
908
- sellAmount,
909
- 0n,
910
- path,
911
- wallet.address,
912
- deadline,
913
- ]);
855
+ return routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [sellAmount, 0n, path, wallet.address, deadline]);
914
856
  }
915
- const sellTx = {
857
+ };
858
+ // ✅ 优化:并行检查所有授权
859
+ let allowances = [];
860
+ if (!config.skipApprovalCheck) {
861
+ allowances = await Promise.all(wallets.map(w => tokenContract.allowance(w.address, routerAddress)));
862
+ }
863
+ // ✅ 优化:计算每个钱包的 nonce 偏移(是否需要授权)
864
+ const currentNonceOffset = wallets.map((_, i) => {
865
+ if (config.skipApprovalCheck)
866
+ return 0;
867
+ return allowances[i] < sellAmountsWei[i] ? 1 : 0;
868
+ });
869
+ // ✅ 优化:并行签名所有授权交易
870
+ const approvalTxPromises = wallets.map(async (wallet, i) => {
871
+ if (config.skipApprovalCheck || sellAmountsWei[i] <= 0n)
872
+ return null;
873
+ if (allowances[i] >= sellAmountsWei[i])
874
+ return null;
875
+ return wallet.signTransaction({
876
+ to: tokenAddress,
877
+ data: approveIface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]),
878
+ value: 0n,
879
+ nonce: nonces[i],
880
+ gasLimit: 60000n,
881
+ gasPrice,
882
+ chainId,
883
+ type: txType,
884
+ });
885
+ });
886
+ const approvalTxResults = await Promise.all(approvalTxPromises);
887
+ // ✅ 优化:并行签名所有卖出交易
888
+ const sellTxPromises = wallets.map(async (wallet, i) => {
889
+ if (sellAmountsWei[i] <= 0n)
890
+ return null;
891
+ return wallet.signTransaction({
916
892
  to: routerAddress,
917
- data: txData,
893
+ data: buildSellTxData(wallet, sellAmountsWei[i]),
918
894
  value: 0n,
919
895
  nonce: nonces[i] + currentNonceOffset[i],
920
896
  gasLimit,
921
897
  gasPrice,
922
898
  chainId,
923
899
  type: txType,
924
- };
925
- signedTxs.push(await wallet.signTransaction(sellTx));
926
- currentNonceOffset[i]++;
927
- // 估算输出(用于计算利润)
900
+ });
901
+ });
902
+ const sellTxResults = await Promise.all(sellTxPromises);
903
+ // ✅ 优化:并行获取所有报价
904
+ const router = new Contract(routerAddress, V2_ROUTER_ABI, provider);
905
+ const quotePromises = wallets.map(async (_, i) => {
906
+ if (sellAmountsWei[i] <= 0n)
907
+ return sellAmountsWei[i];
928
908
  try {
929
- const router = new Contract(routerAddress, V2_ROUTER_ABI, provider);
930
- const amounts = await router.getAmountsOut(sellAmount, path);
931
- totalOutputEstimate += amounts[amounts.length - 1];
909
+ const amounts = await router.getAmountsOut(sellAmountsWei[i], path);
910
+ return amounts[amounts.length - 1];
932
911
  }
933
912
  catch {
934
- // 报价失败,使用 sellAmount 作为估算
935
- totalOutputEstimate += sellAmount;
913
+ return sellAmountsWei[i];
914
+ }
915
+ });
916
+ const outputEstimates = await Promise.all(quotePromises);
917
+ // ✅ 按顺序组装签名交易:先授权,后卖出
918
+ const signedTxs = [];
919
+ for (let i = 0; i < wallets.length; i++) {
920
+ const approvalTx = approvalTxResults[i];
921
+ const sellTx = sellTxResults[i];
922
+ if (approvalTx)
923
+ signedTxs.push(approvalTx);
924
+ if (sellTx) {
925
+ signedTxs.push(sellTx);
926
+ currentNonceOffset[i]++; // 更新 offset 供利润交易使用
927
+ }
928
+ }
929
+ const totalOutputEstimate = outputEstimates.reduce((sum, o) => sum + o, 0n);
930
+ // ✅ 优化:选择输出最大的钱包支付利润(和 core.ts 逻辑一致)
931
+ const maxOutputIndex = findMaxFlowIndex(outputEstimates);
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
+ console.log(`[V2 Sell] ERC20 利润转换: ${ethers.formatEther(calculateProfitAmount(totalOutputEstimate))} Token → ${ethers.formatEther(profitWei)} Native`);
939
+ }
940
+ else {
941
+ console.log(`[V2 Sell] ERC20 利润报价失败,跳过利润提取`);
942
+ profitWei = 0n;
936
943
  }
937
944
  }
938
- // 生成利润交易
939
- const profitWei = calculateProfitAmount(totalOutputEstimate);
940
945
  if (profitWei > 0n && wallets.length > 0) {
941
- // 使用第一个有足够余额的钱包支付利润
942
- const profitTx = await buildProfitTransaction(wallets[0], profitWei, nonces[0] + currentNonceOffset[0], gasPrice, chainId, txType);
946
+ const profitTx = await buildProfitTransaction(wallets[maxOutputIndex], // ✅ 使用输出最大的钱包
947
+ profitWei, nonces[maxOutputIndex] + currentNonceOffset[maxOutputIndex], gasPrice, chainId, txType);
943
948
  signedTxs.push(profitTx);
944
949
  }
945
950
  return {
@@ -982,41 +987,36 @@ export async function directV3BatchBuy(params) {
982
987
  const txType = config.txType ?? 0;
983
988
  const routerIface = new ethers.Interface(routerAbi);
984
989
  const inputToken = useNative ? wrappedNative : quoteToken;
985
- const signedTxs = [];
986
- let totalFlowWei = 0n;
987
990
  const deadline = getDeadline();
988
- for (let i = 0; i < wallets.length; i++) {
989
- const wallet = wallets[i];
990
- const amountWei = ethers.parseUnits(buyAmounts[i], quoteTokenDecimals);
991
- totalFlowWei += amountWei;
992
- let txData;
993
- let txValue;
991
+ // 优化:预先计算所有金额
992
+ const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
993
+ const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
994
+ // 优化:构建交易数据的辅助函数
995
+ const buildV3BuyTxData = (wallet, amountWei) => {
994
996
  if (useLegacyRouter) {
995
- // ✅ 旧版 SwapRouter: exactInputSingle 包含 deadline
996
997
  const swapParams = {
997
998
  tokenIn: inputToken,
998
999
  tokenOut: tokenAddress,
999
1000
  fee: fee,
1000
1001
  recipient: wallet.address,
1001
- deadline: deadline, // ✅ 旧版在 struct 内部
1002
+ deadline: deadline,
1002
1003
  amountIn: amountWei,
1003
- amountOutMinimum: 0n, // 设为 0,依赖 MEV 保护
1004
+ amountOutMinimum: 0n,
1004
1005
  sqrtPriceLimitX96: 0n,
1005
1006
  };
1006
1007
  if (useNative) {
1007
- // 旧版 multicall 不带 deadline
1008
1008
  const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1009
1009
  const refundData = routerIface.encodeFunctionData('refundETH', []);
1010
- txData = routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, refundData]]);
1011
- txValue = amountWei;
1010
+ return {
1011
+ txData: routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, refundData]]),
1012
+ txValue: amountWei
1013
+ };
1012
1014
  }
1013
1015
  else {
1014
- txData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1015
- txValue = 0n;
1016
+ return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
1016
1017
  }
1017
1018
  }
1018
1019
  else {
1019
- // ✅ SwapRouter02: exactInputSingle 不含 deadline
1020
1020
  const swapParams = {
1021
1021
  tokenIn: inputToken,
1022
1022
  tokenOut: tokenAddress,
@@ -1027,18 +1027,22 @@ export async function directV3BatchBuy(params) {
1027
1027
  sqrtPriceLimitX96: 0n,
1028
1028
  };
1029
1029
  if (useNative) {
1030
- // SwapRouter02 的 multicall 带 deadline
1031
1030
  const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1032
1031
  const refundData = routerIface.encodeFunctionData('refundETH', []);
1033
- txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, refundData]]);
1034
- txValue = amountWei;
1032
+ return {
1033
+ txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, refundData]]),
1034
+ txValue: amountWei
1035
+ };
1035
1036
  }
1036
1037
  else {
1037
- txData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1038
- txValue = 0n;
1038
+ return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
1039
1039
  }
1040
1040
  }
1041
- const tx = {
1041
+ };
1042
+ // ✅ 优化:并行签名所有交易
1043
+ const signedTxs = await Promise.all(wallets.map(async (wallet, i) => {
1044
+ const { txData, txValue } = buildV3BuyTxData(wallet, flowAmounts[i]);
1045
+ return wallet.signTransaction({
1042
1046
  to: routerAddress,
1043
1047
  data: txData,
1044
1048
  value: txValue,
@@ -1047,13 +1051,26 @@ export async function directV3BatchBuy(params) {
1047
1051
  gasPrice,
1048
1052
  chainId,
1049
1053
  type: txType,
1050
- };
1051
- signedTxs.push(await wallet.signTransaction(tx));
1054
+ });
1055
+ }));
1056
+ // ✅ 优化:选择金额最大的钱包支付利润
1057
+ const maxFlowIndex = findMaxFlowIndex(flowAmounts);
1058
+ let profitWei = calculateProfitAmount(totalFlowWei);
1059
+ // ✅ 修复:ERC20 交易时,将利润转换为原生代币
1060
+ if (!useNative && profitWei > 0n && quoteToken) {
1061
+ const nativeProfitWei = await getTokenToNativeQuote(provider, quoteToken, profitWei, chain);
1062
+ if (nativeProfitWei > 0n) {
1063
+ profitWei = nativeProfitWei;
1064
+ console.log(`[V3 Buy] ERC20 利润转换: ${ethers.formatEther(calculateProfitAmount(totalFlowWei))} Token → ${ethers.formatEther(profitWei)} Native`);
1065
+ }
1066
+ else {
1067
+ console.log(`[V3 Buy] ERC20 利润报价失败,跳过利润提取`);
1068
+ profitWei = 0n;
1069
+ }
1052
1070
  }
1053
- // 利润交易
1054
- const profitWei = calculateProfitAmount(totalFlowWei);
1055
1071
  if (profitWei > 0n) {
1056
- const profitTx = await buildProfitTransaction(wallets[0], profitWei, nonces[0] + 1, gasPrice, chainId, txType);
1072
+ const profitTx = await buildProfitTransaction(wallets[maxFlowIndex], // 使用金额最大的钱包
1073
+ profitWei, nonces[maxFlowIndex] + 1, gasPrice, chainId, txType);
1057
1074
  signedTxs.push(profitTx);
1058
1075
  }
1059
1076
  return {
@@ -1108,58 +1125,30 @@ export async function directV3BatchSell(params) {
1108
1125
  const routerIface = new ethers.Interface(routerAbi);
1109
1126
  const approveIface = new ethers.Interface(ERC20_ABI);
1110
1127
  const outputToken = useNativeOutput ? wrappedNative : quoteToken;
1111
- const signedTxs = [];
1112
- let totalOutputEstimate = 0n;
1113
- const currentNonceOffset = new Array(wallets.length).fill(0);
1114
1128
  const deadline = getDeadline();
1115
- for (let i = 0; i < wallets.length; i++) {
1116
- const wallet = wallets[i];
1117
- const sellAmount = sellAmountsWei[i];
1118
- if (sellAmount <= 0n)
1119
- continue;
1120
- // 检查授权
1121
- if (!config.skipApprovalCheck) {
1122
- const allowance = await tokenContract.allowance(wallet.address, routerAddress);
1123
- if (allowance < sellAmount) {
1124
- const approveTx = {
1125
- to: tokenAddress,
1126
- data: approveIface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]),
1127
- value: 0n,
1128
- nonce: nonces[i] + currentNonceOffset[i],
1129
- gasLimit: 60000n,
1130
- gasPrice,
1131
- chainId,
1132
- type: txType,
1133
- };
1134
- signedTxs.push(await wallet.signTransaction(approveTx));
1135
- currentNonceOffset[i]++;
1136
- }
1137
- }
1138
- let txData;
1129
+ // 优化:构建卖出交易数据的辅助函数
1130
+ const buildV3SellTxData = (wallet, sellAmount) => {
1139
1131
  if (useLegacyRouter) {
1140
- // ✅ 旧版 SwapRouter: exactInputSingle 包含 deadline
1141
1132
  const swapParams = {
1142
1133
  tokenIn: tokenAddress,
1143
1134
  tokenOut: outputToken,
1144
1135
  fee: fee,
1145
1136
  recipient: useNativeOutput ? routerAddress : wallet.address,
1146
- deadline: deadline, // ✅ 旧版在 struct 内部
1137
+ deadline: deadline,
1147
1138
  amountIn: sellAmount,
1148
- amountOutMinimum: 0n, // 设为 0,依赖 MEV 保护
1139
+ amountOutMinimum: 0n,
1149
1140
  sqrtPriceLimitX96: 0n,
1150
1141
  };
1151
1142
  if (useNativeOutput) {
1152
- // 旧版 multicall 不带 deadline
1153
1143
  const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1154
1144
  const unwrapData = routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address]);
1155
- txData = routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, unwrapData]]);
1145
+ return routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, unwrapData]]);
1156
1146
  }
1157
1147
  else {
1158
- txData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1148
+ return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1159
1149
  }
1160
1150
  }
1161
1151
  else {
1162
- // ✅ SwapRouter02: exactInputSingle 不含 deadline
1163
1152
  const swapParams = {
1164
1153
  tokenIn: tokenAddress,
1165
1154
  tokenOut: outputToken,
@@ -1170,34 +1159,96 @@ export async function directV3BatchSell(params) {
1170
1159
  sqrtPriceLimitX96: 0n,
1171
1160
  };
1172
1161
  if (useNativeOutput) {
1173
- // SwapRouter02 的 multicall 带 deadline
1174
1162
  const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1175
1163
  const unwrapData = routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address]);
1176
- txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, unwrapData]]);
1164
+ return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, unwrapData]]);
1177
1165
  }
1178
1166
  else {
1179
- txData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1167
+ return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1180
1168
  }
1181
1169
  }
1182
- const sellTx = {
1170
+ };
1171
+ // ✅ 优化:并行检查所有授权
1172
+ let allowances = [];
1173
+ if (!config.skipApprovalCheck) {
1174
+ allowances = await Promise.all(wallets.map(w => tokenContract.allowance(w.address, routerAddress)));
1175
+ }
1176
+ // ✅ 优化:计算每个钱包的 nonce 偏移
1177
+ const currentNonceOffset = wallets.map((_, i) => {
1178
+ if (config.skipApprovalCheck || sellAmountsWei[i] <= 0n)
1179
+ return 0;
1180
+ return allowances[i] < sellAmountsWei[i] ? 1 : 0;
1181
+ });
1182
+ // ✅ 优化:并行签名所有授权交易
1183
+ const approvalTxPromises = wallets.map(async (wallet, i) => {
1184
+ if (config.skipApprovalCheck || sellAmountsWei[i] <= 0n)
1185
+ return null;
1186
+ if (allowances[i] >= sellAmountsWei[i])
1187
+ return null;
1188
+ return wallet.signTransaction({
1189
+ to: tokenAddress,
1190
+ data: approveIface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]),
1191
+ value: 0n,
1192
+ nonce: nonces[i],
1193
+ gasLimit: 60000n,
1194
+ gasPrice,
1195
+ chainId,
1196
+ type: txType,
1197
+ });
1198
+ });
1199
+ const approvalTxResults = await Promise.all(approvalTxPromises);
1200
+ // ✅ 优化:并行签名所有卖出交易
1201
+ const sellTxPromises = wallets.map(async (wallet, i) => {
1202
+ if (sellAmountsWei[i] <= 0n)
1203
+ return null;
1204
+ return wallet.signTransaction({
1183
1205
  to: routerAddress,
1184
- data: txData,
1206
+ data: buildV3SellTxData(wallet, sellAmountsWei[i]),
1185
1207
  value: 0n,
1186
1208
  nonce: nonces[i] + currentNonceOffset[i],
1187
1209
  gasLimit,
1188
1210
  gasPrice,
1189
1211
  chainId,
1190
1212
  type: txType,
1191
- };
1192
- signedTxs.push(await wallet.signTransaction(sellTx));
1193
- currentNonceOffset[i]++;
1194
- // 估算输出
1195
- totalOutputEstimate += sellAmount; // 简化处理,实际可调用 QuoterV2
1213
+ });
1214
+ });
1215
+ const sellTxResults = await Promise.all(sellTxPromises);
1216
+ // ✅ 按顺序组装签名交易:先授权,后卖出
1217
+ const signedTxs = [];
1218
+ const outputEstimates = [];
1219
+ for (let i = 0; i < wallets.length; i++) {
1220
+ const approvalTx = approvalTxResults[i];
1221
+ const sellTx = sellTxResults[i];
1222
+ if (approvalTx)
1223
+ signedTxs.push(approvalTx);
1224
+ if (sellTx) {
1225
+ signedTxs.push(sellTx);
1226
+ outputEstimates.push(sellAmountsWei[i]);
1227
+ currentNonceOffset[i]++;
1228
+ }
1229
+ else {
1230
+ outputEstimates.push(0n);
1231
+ }
1232
+ }
1233
+ const totalOutputEstimate = outputEstimates.reduce((sum, o) => sum + o, 0n);
1234
+ // ✅ 优化:选择输出最大的钱包支付利润
1235
+ const maxOutputIndex = findMaxFlowIndex(outputEstimates);
1236
+ let profitWei = calculateProfitAmount(totalOutputEstimate);
1237
+ // ✅ 修复:ERC20 输出时,将利润转换为原生代币
1238
+ if (!useNativeOutput && profitWei > 0n && quoteToken) {
1239
+ const nativeProfitWei = await getTokenToNativeQuote(provider, quoteToken, profitWei, chain);
1240
+ if (nativeProfitWei > 0n) {
1241
+ profitWei = nativeProfitWei;
1242
+ console.log(`[V3 Sell] ERC20 利润转换: ${ethers.formatEther(calculateProfitAmount(totalOutputEstimate))} Token → ${ethers.formatEther(profitWei)} Native`);
1243
+ }
1244
+ else {
1245
+ console.log(`[V3 Sell] ERC20 利润报价失败,跳过利润提取`);
1246
+ profitWei = 0n;
1247
+ }
1196
1248
  }
1197
- // 利润交易
1198
- const profitWei = calculateProfitAmount(totalOutputEstimate);
1199
1249
  if (profitWei > 0n && wallets.length > 0) {
1200
- const profitTx = await buildProfitTransaction(wallets[0], profitWei, nonces[0] + currentNonceOffset[0], gasPrice, chainId, txType);
1250
+ const profitTx = await buildProfitTransaction(wallets[maxOutputIndex], // 使用输出最大的钱包
1251
+ profitWei, nonces[maxOutputIndex] + currentNonceOffset[maxOutputIndex], gasPrice, chainId, txType);
1201
1252
  signedTxs.push(profitTx);
1202
1253
  }
1203
1254
  return {