four-flap-meme-sdk 1.4.24 → 1.4.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/abis/common.d.ts +85 -0
  2. package/dist/abis/common.js +242 -0
  3. package/dist/abis/index.d.ts +1 -0
  4. package/dist/abis/index.js +2 -0
  5. package/dist/contracts/tm-bundle-merkle/approve-tokenmanager.js +1 -6
  6. package/dist/contracts/tm-bundle-merkle/core.js +9 -16
  7. package/dist/contracts/tm-bundle-merkle/internal.js +6 -8
  8. package/dist/contracts/tm-bundle-merkle/pancake-proxy.d.ts +12 -6
  9. package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +224 -166
  10. package/dist/contracts/tm-bundle-merkle/private.js +6 -19
  11. package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +2 -7
  12. package/dist/contracts/tm-bundle-merkle/swap-internal.d.ts +1 -1
  13. package/dist/contracts/tm-bundle-merkle/swap-internal.js +9 -2
  14. package/dist/contracts/tm-bundle-merkle/swap.js +1 -3
  15. package/dist/contracts/tm-bundle-merkle/types.d.ts +20 -0
  16. package/dist/contracts/tm-bundle-merkle/utils.js +164 -175
  17. package/dist/dex/direct-router.d.ts +2 -1
  18. package/dist/dex/direct-router.js +25 -140
  19. package/dist/flap/constants.d.ts +2 -1
  20. package/dist/flap/constants.js +2 -1
  21. package/dist/flap/meta.js +6 -4
  22. package/dist/flap/portal-bundle-merkle/config.js +6 -12
  23. package/dist/flap/portal-bundle-merkle/core.js +8 -11
  24. package/dist/flap/portal-bundle-merkle/pancake-proxy.d.ts +12 -10
  25. package/dist/flap/portal-bundle-merkle/pancake-proxy.js +307 -370
  26. package/dist/flap/portal-bundle-merkle/private.js +1 -1
  27. package/dist/flap/portal-bundle-merkle/swap-buy-first.js +12 -30
  28. package/dist/flap/portal-bundle-merkle/swap.js +13 -26
  29. package/dist/flap/portal-bundle-merkle/types.d.ts +22 -2
  30. package/dist/flap/portal-bundle-merkle/utils.js +11 -16
  31. package/dist/index.d.ts +3 -2
  32. package/dist/index.js +9 -2
  33. package/dist/pancake/bundle-buy-first.js +56 -38
  34. package/dist/pancake/bundle-swap.js +114 -61
  35. package/dist/utils/bundle-helpers.d.ts +28 -0
  36. package/dist/utils/bundle-helpers.js +64 -0
  37. package/dist/utils/constants.d.ts +23 -1
  38. package/dist/utils/constants.js +37 -7
  39. package/dist/utils/erc20.js +17 -25
  40. package/dist/utils/lp-inspect.js +9 -20
  41. package/dist/utils/private-sale.js +1 -2
  42. package/dist/utils/quote-helpers.js +3 -29
  43. package/dist/utils/swap-helpers.js +1 -6
  44. package/dist/utils/wallet.d.ts +8 -13
  45. package/dist/utils/wallet.js +154 -342
  46. package/package.json +1 -1
@@ -1,32 +1,19 @@
1
1
  import { ethers, Wallet } from 'ethers';
2
2
  import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
3
- import { ADDRESSES } from '../../utils/constants.js';
4
- import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount // ✅ 添加贿赂金额获取函数
5
- } from './config.js';
6
- // ✅ BlockRazor Builder EOA 地址(用于贿赂)
7
- const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
8
- // ✅ 已移除授权检查(前端负责确保授权)
3
+ import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA } from '../../utils/constants.js';
4
+ import { TM2_ABI } from '../../abis/common.js';
5
+ import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount } from './config.js';
9
6
  import { trySell } from '../tm.js';
10
7
  const CHAIN_ID = 56;
11
8
  const DEFAULT_GAS_LIMIT = 800000;
12
- // ==================== TokenManager2 ABI(仅需要的方法)====================
13
- const TM2_ABI = [
14
- 'function buyTokenAMAP(uint256 origin, address token, address to, uint256 funds, uint256 minAmount) payable',
15
- 'function sellToken(uint256 origin, address token, uint256 amount, uint256 minFunds)'
16
- ];
17
- // ==================== ERC20 ABI ====================
18
- const ERC20_ABI = [
19
- 'function approve(address spender, uint256 amount) returns (bool)',
20
- 'function allowance(address owner, address spender) view returns (uint256)',
21
- 'function decimals() view returns (uint8)'
22
- ];
9
+ // TM2_ABI 从公共模块导入
10
+ // 本地 getGasLimit(FourAnyConfig 支持 bigint gasLimit)
23
11
  function getGasLimit(config, defaultGas = DEFAULT_GAS_LIMIT) {
24
12
  if (config.gasLimit !== undefined) {
25
13
  return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
26
14
  }
27
15
  const multiplier = config.gasLimitMultiplier ?? 1.0;
28
- const calculatedGas = Math.ceil(defaultGas * multiplier);
29
- return BigInt(calculatedGas);
16
+ return BigInt(Math.ceil(defaultGas * multiplier));
30
17
  }
31
18
  /**
32
19
  * 私有购买(单笔)(仅签名版本 - 不依赖 Merkle)
@@ -5,16 +5,11 @@
5
5
  */
6
6
  import { ethers, Contract, Wallet } from 'ethers';
7
7
  import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
8
- import { ADDRESSES } from '../../utils/constants.js';
8
+ import { ADDRESSES, PROFIT_CONFIG, BLOCKRAZOR_BUILDER_EOA } from '../../utils/constants.js';
9
9
  import { TM_ABI, HELPER3_ABI, TM_ADDRESS } from './swap-internal.js';
10
10
  import { getTxType, getGasPriceConfig, getProfitRecipient, getBribeAmount } from './config.js';
11
- import { PROFIT_CONFIG } from '../../utils/constants.js';
12
11
  import { trySell } from '../tm.js';
13
- // ✅ BlockRazor Builder EOA 地址(用于贿赂)
14
- const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
15
- /**
16
- * 获取 Gas Limit(支持 FourAnyConfig)
17
- */
12
+ // ✅ 本地 getGasLimit(FourAnyConfig 支持 bigint gasLimit)
18
13
  function getGasLimit(config, defaultGas = 800000) {
19
14
  if (config.gasLimit !== undefined) {
20
15
  return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
@@ -1,3 +1,3 @@
1
1
  export declare const TM_ABI: string[];
2
2
  export declare const HELPER3_ABI: string[];
3
- export declare const TM_ADDRESS = "0x5c952063c7fc8610FFDB798152D69F0B9550762b";
3
+ export declare const TM_ADDRESS: "0x342399a59943B5815849657Aa0e06D7058D9d5C6";
@@ -1,10 +1,17 @@
1
- // 共享的 Swap 内部常量与 ABI,避免在多个文件重复定义
1
+ // 共享的 Swap 内部常量与 ABI
2
+ // ✅ 从公共模块重新导出
3
+ import { TM2_ABI, HELPER3_ABI as _HELPER3_ABI } from '../../abis/common.js';
4
+ import { ADDRESSES } from '../../utils/constants.js';
5
+ // ✅ Four.meme 内盘 ABI(与公共 TM2_ABI 扩展)
2
6
  export const TM_ABI = [
7
+ ...TM2_ABI,
3
8
  'function sellToken(uint256 origin, address token, uint256 amount, uint256 minFunds) returns (uint256)',
4
9
  'function buyTokenAMAP(uint256 origin, address token, address to, uint256 funds, uint256 minAmount) payable returns (uint256)'
5
10
  ];
11
+ // ✅ Helper3 ABI(与公共 HELPER3_ABI 扩展)
6
12
  export const HELPER3_ABI = [
13
+ ..._HELPER3_ABI,
7
14
  'function tryBuy(address token, uint256 amount, uint256 funds) view returns (address tokenManager, address quote, uint256 estimatedAmount, uint256 estimatedCost, uint256 estimatedFee, uint256 amountMsgValue, uint256 amountApproval, uint256 amountFunds)',
8
15
  'function trySell(address token, uint256 amount) view returns (address tokenManager, address quote, uint256 funds, uint256 fee)'
9
16
  ];
10
- export const TM_ADDRESS = '0x5c952063c7fc8610FFDB798152D69F0B9550762b';
17
+ export const TM_ADDRESS = ADDRESSES.BSC.TokenManagerV2Proxy;
@@ -6,11 +6,9 @@
6
6
  import { ethers, Contract, Wallet } from 'ethers';
7
7
  import { calculateSellAmount } from '../../utils/swap-helpers.js';
8
8
  import { NonceManager, getOptimizedGasPrice, getGasLimit, getGasPriceConfig, getTxType } from '../../utils/bundle-helpers.js';
9
- import { ADDRESSES, PROFIT_CONFIG } from '../../utils/constants.js';
9
+ import { ADDRESSES, PROFIT_CONFIG, BLOCKRAZOR_BUILDER_EOA } from '../../utils/constants.js';
10
10
  import { TM_ABI, HELPER3_ABI, TM_ADDRESS } from './swap-internal.js';
11
11
  import { getBribeAmount } from './config.js';
12
- // ✅ BlockRazor Builder EOA 地址(用于贿赂)
13
- const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
14
12
  /**
15
13
  * Four内盘捆绑换手
16
14
  */
@@ -334,6 +334,10 @@ export type FourPancakeProxyBatchBuyParams = {
334
334
  v3Fee?: number;
335
335
  v3LpAddresses?: string[];
336
336
  v3ExactTokenIn?: string;
337
+ /** V3 多跳:代币路径 [tokenIn, tokenMid1, ..., tokenOut] */
338
+ v3Tokens?: string[];
339
+ /** V3 多跳:费率路径 [fee0, fee1, ...] - 长度 = v3Tokens.length - 1 */
340
+ v3Fees?: number[];
337
341
  minOutputAmounts?: (string | bigint)[];
338
342
  config: FourBundleMerkleConfig;
339
343
  };
@@ -348,6 +352,10 @@ export type FourPancakeProxyBatchBuySignParams = {
348
352
  v3Fee?: number;
349
353
  v3LpAddresses?: string[];
350
354
  v3ExactTokenIn?: string;
355
+ /** V3 多跳:代币路径 [tokenIn, tokenMid1, ..., tokenOut] */
356
+ v3Tokens?: string[];
357
+ /** V3 多跳:费率路径 [fee0, fee1, ...] - 长度 = v3Tokens.length - 1 */
358
+ v3Fees?: number[];
351
359
  minOutputAmounts?: (string | bigint)[];
352
360
  config: FourSignConfig;
353
361
  };
@@ -364,6 +372,10 @@ export type FourPancakeProxyBatchSellParams = {
364
372
  v3Fee?: number;
365
373
  v3LpAddresses?: string[];
366
374
  v3ExactTokenIn?: string;
375
+ /** V3 多跳:代币路径 [tokenIn, tokenMid1, ..., tokenOut] */
376
+ v3Tokens?: string[];
377
+ /** V3 多跳:费率路径 [fee0, fee1, ...] - 长度 = v3Tokens.length - 1 */
378
+ v3Fees?: number[];
367
379
  minOutputAmounts?: (string | bigint)[];
368
380
  config: FourBundleMerkleConfig;
369
381
  };
@@ -378,6 +390,10 @@ export type FourPancakeProxyBatchSellSignParams = {
378
390
  v3Fee?: number;
379
391
  v3LpAddresses?: string[];
380
392
  v3ExactTokenIn?: string;
393
+ /** V3 多跳:代币路径 [tokenIn, tokenMid1, ..., tokenOut] */
394
+ v3Tokens?: string[];
395
+ /** V3 多跳:费率路径 [fee0, fee1, ...] - 长度 = v3Tokens.length - 1 */
396
+ v3Fees?: number[];
381
397
  minOutputAmounts?: (string | bigint)[];
382
398
  config: FourSignConfig;
383
399
  };
@@ -388,12 +404,16 @@ export type FourPancakeProxyApprovalParams = {
388
404
  tokenAddress: string;
389
405
  amount: string | 'max';
390
406
  rpcUrl: string;
407
+ /** 路由类型,用于确定授权目标(V2 或 V3 Router) */
408
+ routeType?: 'v2' | 'v3-single' | 'v3-multi';
391
409
  };
392
410
  export type FourPancakeProxyApprovalBatchParams = {
393
411
  privateKeys: string[];
394
412
  tokenAddress: string;
395
413
  amounts: (string | 'max')[];
396
414
  config: FourSignConfig;
415
+ /** 路由类型,用于确定授权目标(V2 或 V3 Router) */
416
+ routeType?: 'v2' | 'v3-single' | 'v3-multi';
397
417
  };
398
418
  /** ✅ PancakeProxy 批量授权结果 */
399
419
  export type FourPancakeProxyApprovalBatchResult = {
@@ -1,6 +1,6 @@
1
1
  import { ethers, Wallet } from 'ethers';
2
2
  import { getOptimizedGasPrice, NonceManager } from '../../utils/bundle-helpers.js';
3
- import { PROFIT_CONFIG } from '../../utils/constants.js';
3
+ import { PROFIT_CONFIG, ZERO_ADDRESS } from '../../utils/constants.js';
4
4
  import { getTxType, getGasPriceConfig, shouldExtractProfit, getProfitRecipient } from './config.js';
5
5
  import { getErc20DecimalsMerkle as _getErc20DecimalsMerkle, generateHopWallets as _generateHopWallets, normalizeAmounts as _normalizeAmounts, batchGetBalances as _batchGetBalances, calculateGasLimit as _calculateGasLimit, isNativeTokenAddress as _isNativeTokenAddress } from './internal.js';
6
6
  // ==================== 本地利润计算(万分之三)====================
@@ -56,8 +56,7 @@ async function getFourInnerQuote(rpcUrl, tokenAddress, tokenAmount) {
56
56
  return 0n;
57
57
  }
58
58
  }
59
- /** 零地址(原生代币) */
60
- const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
59
+ // ZERO_ADDRESS 从公共模块导入
61
60
  /**
62
61
  * 获取 FLAP 内盘代币 → 原生代币的报价
63
62
  * ✅ 与 portal-bundle-merkle/core.ts 使用相同的 quoteExactInput 方法
@@ -893,7 +892,7 @@ export async function sweepWithBundleMerkle(params) {
893
892
  }
894
893
  else {
895
894
  // ========== 有多跳:构建跳转链归集 ==========
896
- // 分离有跳转和无跳转的地址(不需要 RPC)
895
+ // 优化版:批量计算 + 批量获取 nonce + 并行签名
897
896
  const sourceWallets = actualKeys.map(pk => new Wallet(pk, provider));
898
897
  const withHopIndexes = [];
899
898
  const withoutHopIndexes = [];
@@ -906,7 +905,7 @@ export async function sweepWithBundleMerkle(params) {
906
905
  }
907
906
  }
908
907
  const sourceAddresses = sourceWallets.map(w => w.address);
909
- // ✅ 优化:并行获取 gasPrice、decimals、余额(传入 chainIdNum 避免 NETWORK_ERROR)
908
+ // ✅ 优化:并行获取 gasPrice、decimals、余额
910
909
  const [gasPrice, decimals, balances, bnbBalances] = await Promise.all([
911
910
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
912
911
  isNative ? Promise.resolve(18) : Promise.resolve(tokenDecimals ?? await _getErc20DecimalsMerkle(provider, tokenAddress, chainIdNum)),
@@ -916,122 +915,107 @@ export async function sweepWithBundleMerkle(params) {
916
915
  const iface = isNative ? null : new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
917
916
  const gasFeePerHop = finalGasLimit * gasPrice;
918
917
  const nonceManager = new NonceManager(provider);
919
- // ✅ 用于记录每个钱包的归集金额(用于计算利润)
918
+ // ✅ 用于记录每个钱包的归集金额
920
919
  const sweepAmounts = new Array(sourceWallets.length).fill(0n);
921
- // 处理无跳转的地址(批量)
922
- if (withoutHopIndexes.length > 0) {
923
- for (let idx = 0; idx < withoutHopIndexes.length; idx++) {
924
- const i = withoutHopIndexes[idx];
925
- const sourceWallet = sourceWallets[i];
926
- const bal = balances[i];
927
- let toSend = 0n;
928
- if (isNative) {
929
- const gasCost = nativeGasLimit * gasPrice;
930
- const ratioForI = (() => {
931
- if (sources && sources[i] && sources[i].ratioPct !== undefined)
932
- return clamp(sources[i].ratioPct);
933
- if (ratios && ratios[i] !== undefined)
934
- return clamp(ratios[i]);
935
- return ratio;
936
- })();
937
- const amountStrForI = (() => {
938
- if (sources && sources[i] && sources[i].amount !== undefined)
939
- return String(sources[i].amount);
940
- if (amounts && amounts[i] !== undefined)
941
- return String(amounts[i]);
942
- return amount !== undefined ? String(amount) : undefined;
943
- })();
944
- if (ratioForI !== undefined) {
945
- const want = (bal * BigInt(ratioForI)) / 100n;
946
- const maxSendable = bal > gasCost ? (bal - gasCost) : 0n;
947
- toSend = want > maxSendable ? maxSendable : want;
948
- }
949
- else if (amountStrForI && amountStrForI.trim().length > 0) {
950
- const amt = ethers.parseEther(amountStrForI);
951
- const need = amt + gasCost;
952
- if (!skipIfInsufficient || bal >= need)
953
- toSend = amt;
954
- }
955
- if (toSend > 0n) {
956
- sweepAmounts[i] = toSend; // ✅ 记录归集金额
957
- totalAmountBeforeProfit += toSend;
958
- const nonce = await nonceManager.getNextNonce(sourceWallet);
959
- const tx = await sourceWallet.signTransaction({
960
- to: target,
961
- value: toSend,
962
- nonce,
963
- gasPrice,
964
- gasLimit: nativeGasLimit,
965
- chainId: chainIdNum,
966
- type: txType
967
- });
968
- signedTxs.push(tx);
969
- }
920
+ // ✅ 辅助函数:获取比例和金额
921
+ const getRatioForI = (i) => {
922
+ if (sources && sources[i] && sources[i].ratioPct !== undefined)
923
+ return clamp(sources[i].ratioPct);
924
+ if (ratios && ratios[i] !== undefined)
925
+ return clamp(ratios[i]);
926
+ return ratio;
927
+ };
928
+ const getAmountStrForI = (i) => {
929
+ if (sources && sources[i] && sources[i].amount !== undefined)
930
+ return String(sources[i].amount);
931
+ if (amounts && amounts[i] !== undefined)
932
+ return String(amounts[i]);
933
+ return amount !== undefined ? String(amount) : undefined;
934
+ };
935
+ const noHopTxDataList = [];
936
+ for (const i of withoutHopIndexes) {
937
+ const bal = balances[i];
938
+ let toSend = 0n;
939
+ const ratioForI = getRatioForI(i);
940
+ const amountStrForI = getAmountStrForI(i);
941
+ if (isNative) {
942
+ const gasCost = nativeGasLimit * gasPrice;
943
+ if (ratioForI !== undefined) {
944
+ const want = (bal * BigInt(ratioForI)) / 100n;
945
+ const maxSendable = bal > gasCost ? (bal - gasCost) : 0n;
946
+ toSend = want > maxSendable ? maxSendable : want;
970
947
  }
971
- else {
972
- const ratioForI = (() => {
973
- if (sources && sources[i] && sources[i].ratioPct !== undefined)
974
- return clamp(sources[i].ratioPct);
975
- if (ratios && ratios[i] !== undefined)
976
- return clamp(ratios[i]);
977
- return ratio;
978
- })();
979
- const amountStrForI = (() => {
980
- if (sources && sources[i] && sources[i].amount !== undefined)
981
- return String(sources[i].amount);
982
- if (amounts && amounts[i] !== undefined)
983
- return String(amounts[i]);
984
- return amount !== undefined ? String(amount) : undefined;
985
- })();
986
- if (ratioForI !== undefined) {
987
- toSend = (bal * BigInt(ratioForI)) / 100n;
988
- }
989
- else if (amountStrForI && amountStrForI.trim().length > 0) {
990
- toSend = ethers.parseUnits(amountStrForI, decimals);
991
- }
992
- if (toSend > 0n && (!skipIfInsufficient || bal >= toSend)) {
993
- sweepAmounts[i] = toSend; // ✅ 记录归集金额
994
- totalAmountBeforeProfit += toSend;
995
- const nonce = await nonceManager.getNextNonce(sourceWallet);
996
- const data = iface.encodeFunctionData('transfer', [target, toSend]);
997
- const tx = await sourceWallet.signTransaction({
998
- to: tokenAddress,
999
- data,
1000
- value: 0n,
1001
- nonce,
1002
- gasPrice,
1003
- gasLimit: finalGasLimit,
1004
- chainId: chainIdNum,
1005
- type: txType
1006
- });
1007
- signedTxs.push(tx);
1008
- }
948
+ else if (amountStrForI && amountStrForI.trim().length > 0) {
949
+ const amt = ethers.parseEther(amountStrForI);
950
+ const need = amt + gasCost;
951
+ if (!skipIfInsufficient || bal >= need)
952
+ toSend = amt;
1009
953
  }
1010
954
  }
955
+ else {
956
+ if (ratioForI !== undefined) {
957
+ toSend = (bal * BigInt(ratioForI)) / 100n;
958
+ }
959
+ else if (amountStrForI && amountStrForI.trim().length > 0) {
960
+ toSend = ethers.parseUnits(amountStrForI, decimals);
961
+ }
962
+ if (skipIfInsufficient && bal < toSend)
963
+ toSend = 0n;
964
+ }
965
+ if (toSend > 0n) {
966
+ sweepAmounts[i] = toSend;
967
+ totalAmountBeforeProfit += toSend;
968
+ noHopTxDataList.push({ index: i, wallet: sourceWallets[i], toSend });
969
+ }
970
+ }
971
+ // ========== 第2步:批量获取无跳转钱包的 nonces ==========
972
+ const noHopWallets = noHopTxDataList.map(d => d.wallet);
973
+ const noHopNonces = noHopWallets.length > 0
974
+ ? await nonceManager.getNextNoncesForWallets(noHopWallets)
975
+ : [];
976
+ // ========== 第3步:并行签名无跳转交易 ==========
977
+ const noHopTxPromises = noHopTxDataList.map((data, idx) => {
978
+ const { wallet, toSend } = data;
979
+ const nonce = noHopNonces[idx];
980
+ if (isNative) {
981
+ return wallet.signTransaction({
982
+ to: target,
983
+ value: toSend,
984
+ nonce,
985
+ gasPrice,
986
+ gasLimit: nativeGasLimit,
987
+ chainId: chainIdNum,
988
+ type: txType
989
+ });
990
+ }
991
+ else {
992
+ const txData = iface.encodeFunctionData('transfer', [target, toSend]);
993
+ return wallet.signTransaction({
994
+ to: tokenAddress,
995
+ data: txData,
996
+ value: 0n,
997
+ nonce,
998
+ gasPrice,
999
+ gasLimit: finalGasLimit,
1000
+ chainId: chainIdNum,
1001
+ type: txType
1002
+ });
1003
+ }
1004
+ });
1005
+ if (noHopTxPromises.length > 0) {
1006
+ const noHopSignedTxs = await Promise.all(noHopTxPromises);
1007
+ signedTxs.push(...noHopSignedTxs);
1011
1008
  }
1012
- // 处理有跳转的地址
1009
+ const hopTxDataList = [];
1013
1010
  for (const i of withHopIndexes) {
1014
1011
  const sourceWallet = sourceWallets[i];
1015
1012
  const hopChain = preparedHops[i];
1016
- // 计算要归集的金额
1017
- let toSend = 0n;
1018
1013
  const bal = balances[i];
1014
+ let toSend = 0n;
1015
+ const ratioForI = getRatioForI(i);
1016
+ const amountStrForI = getAmountStrForI(i);
1019
1017
  if (isNative) {
1020
1018
  const totalGasCost = finalGasLimit * gasPrice * BigInt(hopChain.length + 1);
1021
- const ratioForI = (() => {
1022
- if (sources && sources[i] && sources[i].ratioPct !== undefined)
1023
- return clamp(sources[i].ratioPct);
1024
- if (ratios && ratios[i] !== undefined)
1025
- return clamp(ratios[i]);
1026
- return ratio;
1027
- })();
1028
- const amountStrForI = (() => {
1029
- if (sources && sources[i] && sources[i].amount !== undefined)
1030
- return String(sources[i].amount);
1031
- if (amounts && amounts[i] !== undefined)
1032
- return String(amounts[i]);
1033
- return amount !== undefined ? String(amount) : undefined;
1034
- })();
1035
1019
  if (ratioForI !== undefined) {
1036
1020
  const want = (bal * BigInt(ratioForI)) / 100n;
1037
1021
  const maxSendable = bal > totalGasCost ? (bal - totalGasCost) : 0n;
@@ -1045,25 +1029,10 @@ export async function sweepWithBundleMerkle(params) {
1045
1029
  }
1046
1030
  }
1047
1031
  else {
1048
- // ERC20:检查 BNB 余额是否足够支付 gas
1049
1032
  const bnbBal = bnbBalances[i];
1050
1033
  const bnbNeeded = (gasFeePerHop * BigInt(hopChain.length)) + (finalGasLimit * gasPrice);
1051
1034
  if (bnbBal < bnbNeeded && skipIfInsufficient)
1052
1035
  continue;
1053
- const ratioForI = (() => {
1054
- if (sources && sources[i] && sources[i].ratioPct !== undefined)
1055
- return clamp(sources[i].ratioPct);
1056
- if (ratios && ratios[i] !== undefined)
1057
- return clamp(ratios[i]);
1058
- return ratio;
1059
- })();
1060
- const amountStrForI = (() => {
1061
- if (sources && sources[i] && sources[i].amount !== undefined)
1062
- return String(sources[i].amount);
1063
- if (amounts && amounts[i] !== undefined)
1064
- return String(amounts[i]);
1065
- return amount !== undefined ? String(amount) : undefined;
1066
- })();
1067
1036
  if (ratioForI !== undefined) {
1068
1037
  toSend = (bal * BigInt(ratioForI)) / 100n;
1069
1038
  }
@@ -1075,76 +1044,96 @@ export async function sweepWithBundleMerkle(params) {
1075
1044
  }
1076
1045
  if (toSend <= 0n)
1077
1046
  continue;
1078
- // ✅ 记录归集金额
1079
1047
  sweepAmounts[i] = toSend;
1080
1048
  totalAmountBeforeProfit += toSend;
1081
- // 构建跳转链: 子钱包 -> 中转1 -> 中转2 -> ... -> 目标地址
1082
1049
  const fullChain = [sourceWallet, ...hopChain.map(pk => new Wallet(pk, provider))];
1083
1050
  const addresses = [...fullChain.map(w => w.address), target];
1051
+ hopTxDataList.push({ index: i, sourceWallet, toSend, hopChain, fullChain, addresses });
1052
+ }
1053
+ // ========== 第5步:计算每个有跳转钱包需要的 nonce 数量并批量获取 ==========
1054
+ // 每个源钱包需要的 nonce 数量:
1055
+ // - ERC20: hopChain.length (gas费交易) + 1 (主转账)
1056
+ // - Native: 1 (主转账)
1057
+ const hopNonceNeeds = hopTxDataList.map(d => isNative ? 1 : d.hopChain.length + 1);
1058
+ const hopSourceWallets = hopTxDataList.map(d => d.sourceWallet);
1059
+ // ✅ 批量获取所有有跳转钱包的 nonces
1060
+ const hopNoncesFlat = [];
1061
+ if (hopSourceWallets.length > 0) {
1062
+ const batchNoncePromises = hopSourceWallets.map((w, idx) => nonceManager.getNextNonceBatch(w, hopNonceNeeds[idx]));
1063
+ const batchNonces = await Promise.all(batchNoncePromises);
1064
+ batchNonces.forEach(nonces => hopNoncesFlat.push(...nonces));
1065
+ }
1066
+ const hopTxsToSign = [];
1067
+ let nonceOffset = 0;
1068
+ for (let dataIdx = 0; dataIdx < hopTxDataList.length; dataIdx++) {
1069
+ const { sourceWallet, toSend, hopChain, fullChain, addresses } = hopTxDataList[dataIdx];
1070
+ const nonceCount = hopNonceNeeds[dataIdx];
1071
+ const nonces = hopNoncesFlat.slice(nonceOffset, nonceOffset + nonceCount);
1072
+ nonceOffset += nonceCount;
1073
+ let nonceIdx = 0;
1084
1074
  // ERC20 多跳:先给中转钱包转 gas 费
1085
1075
  if (!isNative) {
1086
- const gasNonces = await nonceManager.getNextNonceBatch(sourceWallet, hopChain.length);
1087
- const gasTxs = [];
1088
1076
  for (let j = 0; j < hopChain.length; j++) {
1089
- const tx = await sourceWallet.signTransaction({
1090
- to: fullChain[j + 1].address,
1091
- value: gasFeePerHop,
1092
- nonce: gasNonces[j],
1093
- gasPrice,
1094
- gasLimit: nativeGasLimit,
1095
- chainId: chainIdNum,
1096
- type: txType
1077
+ hopTxsToSign.push({
1078
+ wallet: sourceWallet,
1079
+ tx: {
1080
+ to: fullChain[j + 1].address,
1081
+ value: gasFeePerHop,
1082
+ nonce: nonces[nonceIdx++],
1083
+ gasPrice,
1084
+ gasLimit: nativeGasLimit,
1085
+ chainId: chainIdNum,
1086
+ type: txType
1087
+ }
1097
1088
  });
1098
- gasTxs.push(tx);
1099
1089
  }
1100
- signedTxs.push(...gasTxs);
1101
1090
  }
1102
1091
  // 执行主要的归集链
1103
1092
  for (let j = 0; j < addresses.length - 1; j++) {
1104
1093
  const fromWallet = fullChain[j];
1105
1094
  const toAddress = addresses[j + 1];
1106
- // 使用 NonceManager:源钱包查询,中转钱包从 0 开始
1107
- const nonce = j === 0
1108
- ? await nonceManager.getNextNonce(sourceWallet)
1109
- : 0;
1095
+ const nonce = j === 0 ? nonces[nonceIdx++] : 0;
1110
1096
  if (isNative) {
1111
- // 原生币归集:每一跳需要附加后续跳数的gas费
1112
- // toSend = 最终主钱包收到的净金额
1113
- // 剩余跳数 = 还需要几跳才能到目标地址
1114
1097
  const remainingHops = addresses.length - 2 - j;
1115
1098
  const additionalGas = gasFeePerHop * BigInt(remainingHops);
1116
1099
  const valueToTransfer = toSend + additionalGas;
1117
- // 示例:hopCount=1(2跳),toSend=0.002874
1118
- // j=0: 子钱包->中转1,剩余1跳,转 0.002874 + 1*gas = 0.002937
1119
- // j=1: 中转1->主钱包,剩余0跳,转 0.002874 + 0 = 0.002874
1120
- const tx = await fromWallet.signTransaction({
1121
- to: toAddress,
1122
- value: valueToTransfer,
1123
- nonce,
1124
- gasPrice,
1125
- gasLimit: finalGasLimit,
1126
- chainId: chainIdNum,
1127
- type: txType
1100
+ hopTxsToSign.push({
1101
+ wallet: fromWallet,
1102
+ tx: {
1103
+ to: toAddress,
1104
+ value: valueToTransfer,
1105
+ nonce,
1106
+ gasPrice,
1107
+ gasLimit: finalGasLimit,
1108
+ chainId: chainIdNum,
1109
+ type: txType
1110
+ }
1128
1111
  });
1129
- signedTxs.push(tx);
1130
1112
  }
1131
1113
  else {
1132
1114
  const data = iface.encodeFunctionData('transfer', [toAddress, toSend]);
1133
- const tx = await fromWallet.signTransaction({
1134
- to: tokenAddress,
1135
- data,
1136
- value: 0n,
1137
- nonce,
1138
- gasPrice,
1139
- gasLimit: finalGasLimit,
1140
- chainId: chainIdNum,
1141
- type: txType
1115
+ hopTxsToSign.push({
1116
+ wallet: fromWallet,
1117
+ tx: {
1118
+ to: tokenAddress,
1119
+ data,
1120
+ value: 0n,
1121
+ nonce,
1122
+ gasPrice,
1123
+ gasLimit: finalGasLimit,
1124
+ chainId: chainIdNum,
1125
+ type: txType
1126
+ }
1142
1127
  });
1143
- signedTxs.push(tx);
1144
1128
  }
1145
1129
  }
1146
1130
  }
1147
- // ✅ 多跳模式:计算利润并添加利润转账
1131
+ // ✅ 并行签名所有有跳转的交易
1132
+ if (hopTxsToSign.length > 0) {
1133
+ const hopSignedTxs = await Promise.all(hopTxsToSign.map(({ wallet, tx }) => wallet.signTransaction(tx)));
1134
+ signedTxs.push(...hopSignedTxs);
1135
+ }
1136
+ // ========== 第7步:计算利润并添加利润转账 ==========
1148
1137
  if (extractProfit && totalAmountBeforeProfit > 0n) {
1149
1138
  // 找出归集金额最大的钱包作为支付者
1150
1139
  let maxSweepIndex = -1;
@@ -1161,26 +1150,26 @@ export async function sweepWithBundleMerkle(params) {
1161
1150
  if (sweepAmounts[i] > 0n) {
1162
1151
  const { profit } = calculateProfit(sweepAmounts[i]);
1163
1152
  if (isNative) {
1164
- totalProfit += profit; // 原生币直接累加
1153
+ totalProfit += profit;
1165
1154
  }
1166
1155
  else {
1167
- totalTokenProfit += profit; // ERC20 累计代币利润
1156
+ totalTokenProfit += profit;
1168
1157
  }
1169
1158
  }
1170
1159
  }
1171
- // ✅ ERC20 多跳归集:获取代币利润等值的原生代币报价
1160
+ // ✅ ERC20:获取代币利润等值的原生代币报价
1172
1161
  let nativeProfitAmount = isNative ? totalProfit : 0n;
1173
1162
  if (!isNative && totalTokenProfit > 0n) {
1174
1163
  nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
1175
- totalProfit = nativeProfitAmount; // 更新为原生代币利润
1164
+ totalProfit = nativeProfitAmount;
1176
1165
  }
1177
- // 由归集金额最大的钱包支付利润(转等值原生代币)
1166
+ // 由归集金额最大的钱包支付利润
1178
1167
  if (nativeProfitAmount > 0n && maxSweepIndex >= 0) {
1179
1168
  const payerWallet = sourceWallets[maxSweepIndex];
1180
1169
  const profitNonce = await nonceManager.getNextNonce(payerWallet);
1181
1170
  const profitTx = await payerWallet.signTransaction({
1182
1171
  to: getProfitRecipient(),
1183
- value: nativeProfitAmount, // ✅ 转等值原生代币
1172
+ value: nativeProfitAmount,
1184
1173
  nonce: profitNonce,
1185
1174
  gasPrice,
1186
1175
  gasLimit: 21000n,
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * 收费方式:交易末尾附加利润提取交易(和内盘一致)
9
9
  */
10
- /** Router 地址配置 */
10
+ /** Router 地址配置 - ✅ 使用公共模块中的地址 */
11
11
  export declare const DIRECT_ROUTERS: {
12
12
  readonly BSC: {
13
13
  readonly PANCAKESWAP_V2: "0x10ED43C718714eb63d5aA57B78B54704E256024E";
@@ -31,6 +31,7 @@ export declare const DIRECT_ROUTERS: {
31
31
  readonly WOKB: "0xe538905cf8410324e03a5a23c1c177a474d59b2b";
32
32
  };
33
33
  };
34
+ /** ERC20 ABI - ✅ 从公共模块导入 */
34
35
  export interface DirectRouterSignConfig {
35
36
  rpcUrl: string;
36
37
  chainId?: number;