four-flap-meme-sdk 1.4.2 → 1.4.4

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,31 @@ 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
+ // ✅ 方案 B:提前计算利润并并行获取 ERC20 报价
850
+ const totalOutputEstimate = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
851
+ const baseProfitWei = calculateProfitAmount(totalOutputEstimate);
852
+ // ERC20 报价(如果需要)- 可以在签名同时进行
853
+ const nativeProfitPromise = (!useNativeOutput && baseProfitWei > 0n && quoteToken)
854
+ ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
855
+ : Promise.resolve(baseProfitWei);
856
+ // 构建路径和 Router
858
857
  const outputToken = useNativeOutput ? wrappedNative : quoteToken;
859
858
  const path = [tokenAddress, outputToken];
860
- // ✅ 判断是否是 SwapRouter02 或 DYORSwap
861
859
  const useSwapRouter02 = isSwapRouter02(chain, routerAddress);
862
860
  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
- // ✅ 优化:构建卖出交易数据的辅助函数(同步)
861
+ const routerIface = useSwapRouter02 ? new ethers.Interface(SWAP_ROUTER02_V2_ABI) : new ethers.Interface(V2_ROUTER_ABI);
862
+ // 构建卖出交易数据的辅助函数
868
863
  const buildSellTxData = (wallet, sellAmount) => {
869
864
  if (useDYORSwap) {
870
865
  const DYORSWAP_FACTORY = DIRECT_ROUTERS.XLAYER.DYORSWAP_FACTORY;
871
866
  const ADDRESS_THIS = 2n;
872
867
  const multicallData = [];
873
868
  multicallData.push(encodeDYORSwapExactTokensForTokens(sellAmount, 0n, path, [DYORSWAP_FACTORY], ADDRESS_THIS, 1n, DYORSWAP_FACTORY));
874
- if (useNativeOutput) {
869
+ if (useNativeOutput)
875
870
  multicallData.push(encodeDYORUnwrapWETH9(0n, wallet.address, '0x'));
876
- }
877
871
  return encodeDYORMulticall(BigInt(deadline), multicallData);
878
872
  }
879
873
  else if (useSwapRouter02) {
@@ -886,8 +880,7 @@ export async function directV2BatchSell(params) {
886
880
  return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, multicallData]);
887
881
  }
888
882
  else {
889
- const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [sellAmount, 0n, path, wallet.address]);
890
- return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData]]);
883
+ return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [routerIface.encodeFunctionData('swapExactTokensForTokens', [sellAmount, 0n, path, wallet.address])]]);
891
884
  }
892
885
  }
893
886
  else if (useNativeOutput) {
@@ -897,26 +890,22 @@ export async function directV2BatchSell(params) {
897
890
  return routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [sellAmount, 0n, path, wallet.address, deadline]);
898
891
  }
899
892
  };
900
- // ✅ 性能优化:直接用 sellAmountsWei 找最大输出钱包(跳过 N 次 getAmountsOut RPC 调用)
901
- // 因为卖出金额越大,输出也越大(假设线性关系),无需额外报价
902
893
  const maxOutputIndex = findMaxFlowIndex(sellAmountsWei);
903
- // ✅ 计算贿赂金额(仅 BSC 链)
904
894
  const bribeWei = getBribeAmount(config, chain);
905
895
  const hasBribe = bribeWei > 0n && wallets.length > 0;
906
- // ✅ 计算 nonce 偏移(仅贿赂,已移除授权)
907
896
  const nonceOffsets = wallets.map((_, i) => i === maxOutputIndex && hasBribe ? 1 : 0);
908
- // ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
909
- const bribeTxs = [];
897
+ // ✅ 方案 A:并行签名所有交易(贿赂、卖出、利润)+ 并行获取 ERC20 报价
898
+ const signPromises = [];
899
+ // 贿赂交易
910
900
  if (hasBribe) {
911
- const bribeTx = await buildBribeTransaction(wallets[maxOutputIndex], bribeWei, nonces[maxOutputIndex], // 使用原始 nonce
912
- gasPrice, chainId, txType);
913
- bribeTxs.push(bribeTx);
901
+ signPromises.push(buildBribeTransaction(wallets[maxOutputIndex], bribeWei, nonces[maxOutputIndex], gasPrice, chainId, txType)
902
+ .then(tx => ({ type: 'bribe', index: 0, tx })));
914
903
  }
915
- // ✅ 优化:并行签名所有卖出交易(已移除授权交易)
916
- const swapTxs = await Promise.all(wallets.map(async (wallet, i) => {
904
+ // 卖出交易(并行签名)
905
+ wallets.forEach((wallet, i) => {
917
906
  if (sellAmountsWei[i] <= 0n)
918
- return null;
919
- return wallet.signTransaction({
907
+ return;
908
+ signPromises.push(wallet.signTransaction({
920
909
  to: routerAddress,
921
910
  data: buildSellTxData(wallet, sellAmountsWei[i]),
922
911
  value: 0n,
@@ -925,31 +914,25 @@ export async function directV2BatchSell(params) {
925
914
  gasPrice,
926
915
  chainId,
927
916
  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 = [];
917
+ }).then(tx => ({ type: 'swap', index: i, tx })));
918
+ });
919
+ // ✅ 并行执行:签名 + ERC20 报价
920
+ const [signedResults, nativeProfitWei] = await Promise.all([
921
+ Promise.all(signPromises),
922
+ nativeProfitPromise
923
+ ]);
924
+ const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
925
+ // 利润交易(需要等 ERC20 报价完成才能确定金额)
926
+ let profitTx = null;
945
927
  if (profitWei > 0n && wallets.length > 0) {
946
- // 利润交易 nonce = 原始 nonce + 贿赂偏移 + 1(卖出交易)
947
928
  const profitNonce = nonces[maxOutputIndex] + nonceOffsets[maxOutputIndex] + 1;
948
- const profitTx = await buildProfitTransaction(wallets[maxOutputIndex], // 使用输出最大的钱包
949
- profitWei, profitNonce, gasPrice, chainId, txType);
950
- profitTxs.push(profitTx);
929
+ profitTx = await buildProfitTransaction(wallets[maxOutputIndex], profitWei, profitNonce, gasPrice, chainId, txType);
951
930
  }
952
- // ✅ 组装最终交易列表:贿赂 → 卖出 → 利润
931
+ // 按类型分组并按顺序组装
932
+ const validResults = signedResults.filter((r) => r !== null);
933
+ const bribeTxs = validResults.filter(r => r.type === 'bribe').map(r => r.tx);
934
+ const swapTxs = validResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
935
+ const profitTxs = profitTx ? [profitTx] : [];
953
936
  const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
954
937
  return {
955
938
  signedTransactions: signedTxs,
@@ -976,121 +959,79 @@ export async function directV3BatchBuy(params) {
976
959
  throw new Error('privateKeys 和 buyAmounts 长度必须相同');
977
960
  }
978
961
  const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
979
- const provider = new JsonRpcProvider(config.rpcUrl, { chainId, name: chain });
962
+ // 方案 C:使用缓存的 Provider
963
+ const provider = getCachedProvider(config.rpcUrl, chainId, chain);
980
964
  const useNative = isNativeToken(quoteToken);
981
965
  const wrappedNative = getWrappedNative(chain);
982
- // ✅ 判断是否使用旧版 SwapRouter(Monad Uniswap V3)
983
966
  const useLegacyRouter = isLegacySwapRouter(chain, routerAddress);
984
967
  const routerAbi = useLegacyRouter ? V3_ROUTER_LEGACY_ABI : V3_ROUTER02_ABI;
985
968
  const wallets = privateKeys.map(pk => new Wallet(pk, provider));
986
- // ✅ 性能优化:如果前端已传入 nonces,则跳过 RPC 获取
987
- const [nonces, gasPrice] = await Promise.all([
969
+ // ✅ 预先计算所有金额(同步操作,无 RPC
970
+ const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
971
+ const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
972
+ const baseProfitWei = calculateProfitAmount(totalFlowWei);
973
+ // ✅ 方案 B:并行获取 nonces、gasPrice 和 ERC20 报价
974
+ const [nonces, gasPrice, nativeProfitWei] = await Promise.all([
988
975
  startNonces && startNonces.length === wallets.length
989
976
  ? Promise.resolve(startNonces)
990
977
  : new NonceManager(provider).getNextNoncesForWallets(wallets),
991
- getGasPrice(provider, config)
978
+ getGasPrice(provider, config),
979
+ (!useNative && baseProfitWei > 0n && quoteToken)
980
+ ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
981
+ : Promise.resolve(baseProfitWei)
992
982
  ]);
983
+ const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
993
984
  const gasLimit = getGasLimit(config, 300000);
994
985
  const txType = config.txType ?? 0;
995
986
  const routerIface = new ethers.Interface(routerAbi);
996
987
  const inputToken = useNative ? wrappedNative : quoteToken;
997
988
  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
- // ✅ 优化:构建交易数据的辅助函数
989
+ // 构建交易数据的辅助函数
1002
990
  const buildV3BuyTxData = (wallet, amountWei) => {
1003
991
  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
- };
992
+ const swapParams = { tokenIn: inputToken, tokenOut: tokenAddress, fee, recipient: wallet.address, deadline, amountIn: amountWei, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
1014
993
  if (useNative) {
1015
994
  const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1016
995
  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 };
996
+ return { txData: routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, refundData]]), txValue: amountWei };
1024
997
  }
998
+ return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
1025
999
  }
1026
1000
  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
- };
1001
+ const swapParams = { tokenIn: inputToken, tokenOut: tokenAddress, fee, recipient: wallet.address, amountIn: amountWei, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
1036
1002
  if (useNative) {
1037
1003
  const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1038
1004
  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 };
1005
+ return { txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, refundData]]), txValue: amountWei };
1046
1006
  }
1007
+ return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
1047
1008
  }
1048
1009
  };
1049
- // ✅ 优化:选择金额最大的钱包支付贿赂和利润
1050
1010
  const maxFlowIndex = findMaxFlowIndex(flowAmounts);
1051
- // ✅ 计算贿赂金额(仅 BSC 链)
1052
1011
  const bribeWei = getBribeAmount(config, chain);
1053
1012
  const hasBribe = bribeWei > 0n && wallets.length > 0;
1054
- // 计算 nonce 偏移
1013
+ const hasProfit = profitWei > 0n;
1055
1014
  const nonceOffsets = wallets.map((_, i) => i === maxFlowIndex && hasBribe ? 1 : 0);
1056
- // ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
1057
- const bribeTxs = [];
1015
+ // ✅ 方案 A:并行签名所有交易(贿赂、主交易、利润)
1016
+ const signPromises = [];
1058
1017
  if (hasBribe) {
1059
- const bribeTx = await buildBribeTransaction(wallets[maxFlowIndex], bribeWei, nonces[maxFlowIndex], gasPrice, chainId, txType);
1060
- bribeTxs.push(bribeTx);
1018
+ signPromises.push(buildBribeTransaction(wallets[maxFlowIndex], bribeWei, nonces[maxFlowIndex], gasPrice, chainId, txType)
1019
+ .then(tx => ({ type: 'bribe', index: 0, tx })));
1061
1020
  }
1062
- // 优化:并行签名所有交易
1063
- const swapTxs = await Promise.all(wallets.map(async (wallet, i) => {
1021
+ wallets.forEach((wallet, i) => {
1064
1022
  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
- }
1086
- }
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);
1023
+ signPromises.push(wallet.signTransaction({ to: routerAddress, data: txData, value: txValue, nonce: nonces[i] + nonceOffsets[i], gasLimit, gasPrice, chainId, type: txType })
1024
+ .then(tx => ({ type: 'swap', index: i, tx })));
1025
+ });
1026
+ if (hasProfit) {
1027
+ const profitNonce = nonces[maxFlowIndex] + nonceOffsets[maxFlowIndex] + 1;
1028
+ signPromises.push(buildProfitTransaction(wallets[maxFlowIndex], profitWei, profitNonce, gasPrice, chainId, txType)
1029
+ .then(tx => ({ type: 'profit', index: 0, tx })));
1092
1030
  }
1093
- // 组装最终交易列表:贿赂 交易 → 利润
1031
+ const signedResults = await Promise.all(signPromises);
1032
+ const bribeTxs = signedResults.filter(r => r.type === 'bribe').map(r => r.tx);
1033
+ const swapTxs = signedResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
1034
+ const profitTxs = signedResults.filter(r => r.type === 'profit').map(r => r.tx);
1094
1035
  const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
1095
1036
  return {
1096
1037
  signedTransactions: signedTxs,
@@ -1111,24 +1052,20 @@ export async function directV3BatchBuy(params) {
1111
1052
  export async function directV3BatchSell(params) {
1112
1053
  const { chain, privateKeys, sellPercentages, sellAmounts, tokenAddress, tokenDecimals = 18, routerAddress, fee, quoteToken, startNonces, config, } = params;
1113
1054
  const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
1114
- const provider = new JsonRpcProvider(config.rpcUrl, { chainId, name: chain });
1055
+ // 方案 C:使用缓存的 Provider
1056
+ const provider = getCachedProvider(config.rpcUrl, chainId, chain);
1115
1057
  const useNativeOutput = isNativeToken(quoteToken);
1116
1058
  const wrappedNative = getWrappedNative(chain);
1117
- // ✅ 判断是否使用旧版 SwapRouter(Monad Uniswap V3)
1118
1059
  const useLegacyRouter = isLegacySwapRouter(chain, routerAddress);
1119
1060
  const routerAbi = useLegacyRouter ? V3_ROUTER_LEGACY_ABI : V3_ROUTER02_ABI;
1120
1061
  const wallets = privateKeys.map(pk => new Wallet(pk, provider));
1121
1062
  const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
1122
- // ✅ 性能优化:并行获取所有独立的 RPC 数据
1123
- // ✅ 如果前端已传入 nonces,则跳过 RPC 获取
1063
+ // ✅ 并行获取所有 RPC 数据
1124
1064
  const [balances, nonces, gasPrice] = await Promise.all([
1125
- // 1. 批量获取余额
1126
1065
  Promise.all(wallets.map(w => tokenContract.balanceOf(w.address))),
1127
- // 2. 批量获取 nonce(如果前端已传入则跳过)
1128
1066
  startNonces && startNonces.length === wallets.length
1129
1067
  ? Promise.resolve(startNonces)
1130
1068
  : new NonceManager(provider).getNextNoncesForWallets(wallets),
1131
- // 3. 获取 gas price
1132
1069
  getGasPrice(provider, config)
1133
1070
  ]);
1134
1071
  const gasLimit = getGasLimit(config, 350000);
@@ -1147,71 +1084,46 @@ export async function directV3BatchSell(params) {
1147
1084
  sellAmountsWei.push(balances[i]);
1148
1085
  }
1149
1086
  }
1087
+ // ✅ 方案 B:提前计算利润并并行获取 ERC20 报价
1088
+ const totalOutputEstimate = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
1089
+ const baseProfitWei = calculateProfitAmount(totalOutputEstimate);
1090
+ const nativeProfitPromise = (!useNativeOutput && baseProfitWei > 0n && quoteToken)
1091
+ ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
1092
+ : Promise.resolve(baseProfitWei);
1150
1093
  const routerIface = new ethers.Interface(routerAbi);
1151
1094
  const outputToken = useNativeOutput ? wrappedNative : quoteToken;
1152
1095
  const deadline = getDeadline();
1153
- // ✅ 优化:构建卖出交易数据的辅助函数
1096
+ // 构建卖出交易数据的辅助函数
1154
1097
  const buildV3SellTxData = (wallet, sellAmount) => {
1155
1098
  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
- };
1099
+ const swapParams = { tokenIn: tokenAddress, tokenOut: outputToken, fee, recipient: useNativeOutput ? routerAddress : wallet.address, deadline, amountIn: sellAmount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
1166
1100
  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]);
1101
+ return routerIface.encodeFunctionData('multicall(bytes[])', [[routerIface.encodeFunctionData('exactInputSingle', [swapParams]), routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address])]]);
1173
1102
  }
1103
+ return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1174
1104
  }
1175
1105
  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
- };
1106
+ const swapParams = { tokenIn: tokenAddress, tokenOut: outputToken, fee, recipient: useNativeOutput ? routerAddress : wallet.address, amountIn: sellAmount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
1185
1107
  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]);
1108
+ return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [routerIface.encodeFunctionData('exactInputSingle', [swapParams]), routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address])]]);
1192
1109
  }
1110
+ return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
1193
1111
  }
1194
1112
  };
1195
- // ✅ 先找出输出最大的钱包索引(用于支付贿赂和利润)
1196
- // 对于 V3 卖出,使用卖出金额作为输出估计
1197
1113
  const maxOutputIndex = findMaxFlowIndex(sellAmountsWei);
1198
- // ✅ 计算贿赂金额(仅 BSC 链)
1199
1114
  const bribeWei = getBribeAmount(config, chain);
1200
1115
  const hasBribe = bribeWei > 0n && wallets.length > 0;
1201
- // ✅ 计算 nonce 偏移(仅贿赂,已移除授权)
1202
1116
  const nonceOffsets = wallets.map((_, i) => i === maxOutputIndex && hasBribe ? 1 : 0);
1203
- // ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
1204
- const bribeTxs = [];
1117
+ // ✅ 方案 A:并行签名所有交易(贿赂、卖出)+ 并行获取 ERC20 报价
1118
+ const signPromises = [];
1205
1119
  if (hasBribe) {
1206
- const bribeTx = await buildBribeTransaction(wallets[maxOutputIndex], bribeWei, nonces[maxOutputIndex], // 使用原始 nonce
1207
- gasPrice, chainId, txType);
1208
- bribeTxs.push(bribeTx);
1120
+ signPromises.push(buildBribeTransaction(wallets[maxOutputIndex], bribeWei, nonces[maxOutputIndex], gasPrice, chainId, txType)
1121
+ .then(tx => ({ type: 'bribe', index: 0, tx })));
1209
1122
  }
1210
- // 优化:并行签名所有卖出交易(已移除授权交易)
1211
- const swapTxs = await Promise.all(wallets.map(async (wallet, i) => {
1123
+ wallets.forEach((wallet, i) => {
1212
1124
  if (sellAmountsWei[i] <= 0n)
1213
- return null;
1214
- return wallet.signTransaction({
1125
+ return;
1126
+ signPromises.push(wallet.signTransaction({
1215
1127
  to: routerAddress,
1216
1128
  data: buildV3SellTxData(wallet, sellAmountsWei[i]),
1217
1129
  value: 0n,
@@ -1220,31 +1132,25 @@ export async function directV3BatchSell(params) {
1220
1132
  gasPrice,
1221
1133
  chainId,
1222
1134
  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 = [];
1135
+ }).then(tx => ({ type: 'swap', index: i, tx })));
1136
+ });
1137
+ // ✅ 并行执行:签名 + ERC20 报价
1138
+ const [signedResults, nativeProfitWei] = await Promise.all([
1139
+ Promise.all(signPromises),
1140
+ nativeProfitPromise
1141
+ ]);
1142
+ const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
1143
+ // 利润交易(需要等 ERC20 报价完成才能确定金额)
1144
+ let profitTx = null;
1240
1145
  if (profitWei > 0n && wallets.length > 0) {
1241
- // 利润交易 nonce = 原始 nonce + 贿赂偏移 + 1(卖出交易)
1242
1146
  const profitNonce = nonces[maxOutputIndex] + nonceOffsets[maxOutputIndex] + 1;
1243
- const profitTx = await buildProfitTransaction(wallets[maxOutputIndex], // 使用输出最大的钱包
1244
- profitWei, profitNonce, gasPrice, chainId, txType);
1245
- profitTxs.push(profitTx);
1147
+ profitTx = await buildProfitTransaction(wallets[maxOutputIndex], profitWei, profitNonce, gasPrice, chainId, txType);
1246
1148
  }
1247
- // ✅ 组装最终交易列表:贿赂 → 卖出 → 利润
1149
+ // 按类型分组并按顺序组装
1150
+ const validResults = signedResults.filter((r) => r !== null);
1151
+ const bribeTxs = validResults.filter(r => r.type === 'bribe').map(r => r.tx);
1152
+ const swapTxs = validResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
1153
+ const profitTxs = profitTx ? [profitTx] : [];
1248
1154
  const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
1249
1155
  return {
1250
1156
  signedTransactions: signedTxs,
@@ -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 {
@@ -1,4 +1,5 @@
1
1
  import { JsonRpcProvider } from 'ethers';
2
+ import { NonceManager } from './bundle-helpers.js';
2
3
  export type EnsureAllowanceResult = {
3
4
  alreadyApproved: boolean;
4
5
  currentAllowance: bigint;
@@ -190,6 +191,8 @@ export type ApproveTokenBatchRawParams = {
190
191
  gasLimit?: number;
191
192
  /** 链ID(可选,默认56=BSC,signOnly=true 时生效) */
192
193
  chainId?: number;
194
+ /** 外部 NonceManager(可选,用于同一钱包签多笔交易时共享 nonce 状态) */
195
+ nonceManager?: NonceManager;
193
196
  };
194
197
  /** signOnly=false 时的返回结果(直接发送交易) */
195
198
  export type ApproveTokenBatchResult = {
@@ -1,5 +1,6 @@
1
1
  import { Contract, Wallet, JsonRpcProvider, Interface, parseUnits } from 'ethers';
2
2
  import { ADDRESSES } from './constants.js';
3
+ import { NonceManager } from './bundle-helpers.js';
3
4
  const ERC20_ABI = [
4
5
  { "constant": false, "inputs": [{ "name": "spender", "type": "address" }, { "name": "value", "type": "uint256" }], "name": "approve", "outputs": [{ "name": "", "type": "bool" }], "type": "function" },
5
6
  { "constant": true, "inputs": [{ "name": "owner", "type": "address" }, { "name": "spender", "type": "address" }], "name": "allowance", "outputs": [{ "name": "", "type": "uint256" }], "type": "function" },
@@ -488,7 +489,7 @@ export async function approveTokenBatch(params) {
488
489
  return approveTokenBatchRaw({ rpcUrl, privateKeys, tokenAddress, spenderAddress, amounts, signOnly, gasPriceGwei, gasLimit, chainId });
489
490
  }
490
491
  export async function approveTokenBatchRaw(params) {
491
- const { rpcUrl, privateKeys, tokenAddress, spenderAddress, amounts, signOnly, gasPriceGwei, gasLimit, chainId = 56 } = params;
492
+ const { rpcUrl, privateKeys, tokenAddress, spenderAddress, amounts, signOnly, gasPriceGwei, gasLimit, chainId = 56, nonceManager: externalNonceManager } = params;
492
493
  if (privateKeys.length === 0 || amounts.length !== privateKeys.length) {
493
494
  throw new Error('❌ 私钥数量和授权数量必须匹配');
494
495
  }
@@ -507,10 +508,12 @@ export async function approveTokenBatchRaw(params) {
507
508
  : amount);
508
509
  // ==================== signOnly=true:只签名不提交 ====================
509
510
  if (signOnly) {
511
+ // ✅ 使用 NonceManager 管理 nonce(和买卖交易一样)
512
+ const nonceManager = externalNonceManager || new NonceManager(provider);
510
513
  // ✅ 并行获取:当前授权额度 + nonces + gasPrice
511
514
  const [currentAllowances, nonces, fetchedGasPrice] = await Promise.all([
512
515
  batchCheckAllowances(provider, normalizedToken, ownerAddresses, normalizedSpender),
513
- Promise.all(wallets.map(w => provider.getTransactionCount(w.address, 'latest'))),
516
+ nonceManager.getNextNoncesForWallets(wallets), // 使用 NonceManager 批量获取 nonce
514
517
  gasPriceGwei ? Promise.resolve(parseUnits(gasPriceGwei.toString(), 'gwei')) : provider.getFeeData().then(fee => fee.gasPrice || parseUnits('3', 'gwei'))
515
518
  ]);
516
519
  const finalGasPrice = fetchedGasPrice;
@@ -537,7 +540,7 @@ export async function approveTokenBatchRaw(params) {
537
540
  const signedTx = await wallet.signTransaction({
538
541
  to: normalizedToken,
539
542
  data: txData,
540
- nonce: nonces[i],
543
+ nonce: nonces[i], // ✅ NonceManager 已经处理好递增
541
544
  gasLimit: finalGasLimit,
542
545
  gasPrice: finalGasPrice,
543
546
  chainId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",