four-flap-meme-sdk 1.3.81 → 1.3.82

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.
@@ -103,18 +103,15 @@ export async function getFactoryFromRouter(routerAddress, rpcUrl, routerType = '
103
103
  else {
104
104
  // V3 SwapRouter02 可能同时有 V2 和 V3 Factory
105
105
  const router = new Contract(routerAddress, V3_ROUTER_ABI, provider);
106
- // 尝试获取 V3 Factory
107
- try {
108
- result.v3Factory = await router.factory();
109
- }
110
- catch {
111
- }
112
- // 尝试获取 V2 Factory
113
- try {
114
- result.v2Factory = await router.factoryV2();
115
- }
116
- catch {
117
- }
106
+ // 并行获取 V3 和 V2 Factory
107
+ const [v3Factory, v2Factory] = await Promise.all([
108
+ router.factory().catch(() => undefined),
109
+ router.factoryV2().catch(() => undefined)
110
+ ]);
111
+ if (v3Factory)
112
+ result.v3Factory = v3Factory;
113
+ if (v2Factory)
114
+ result.v2Factory = v2Factory;
118
115
  }
119
116
  }
120
117
  catch (error) {
@@ -590,35 +587,42 @@ async function queryV2Pairs(token, factoryAddress, quoteTokens, provider, multic
590
587
  return results;
591
588
  }
592
589
  async function queryV2PairsFallback(token, factoryAddress, quoteTokens, provider, tokenDecimals, shouldFormat) {
593
- const results = [];
594
590
  const factory = new Contract(factoryAddress, I_UNIV2_FACTORY_ABI, provider);
595
- for (const qt of quoteTokens) {
591
+ // 并行查询所有 quoteToken 的交易对
592
+ const pairResults = await Promise.all(quoteTokens.map(async (qt) => {
596
593
  try {
597
594
  const pairAddress = await factory.getPair(token, qt.address);
598
- if (pairAddress && pairAddress.toLowerCase() !== ZERO_ADDRESS) {
599
- const pair = new Contract(pairAddress, I_UNIV2_PAIR_ABI, provider);
600
- const [token0, token1] = await Promise.all([pair.token0(), pair.token1()]);
601
- const [r0, r1] = await pair.getReserves();
602
- const isToken0 = token0.toLowerCase() === token.toLowerCase();
603
- const reserveToken = isToken0 ? r0 : r1;
604
- const reserveQuote = isToken0 ? r1 : r0;
605
- results.push({
606
- quoteToken: qt.address,
607
- quoteSymbol: qt.symbol,
608
- quoteDecimals: qt.decimals,
609
- pairAddress,
610
- reserveToken: formatBalance(reserveToken, shouldFormat, tokenDecimals),
611
- reserveQuote: formatBalance(reserveQuote, shouldFormat, qt.decimals),
612
- reserveQuoteRaw: reserveQuote,
613
- reserveTokenRaw: reserveToken, // ✅ 新增
614
- });
595
+ if (!pairAddress || pairAddress.toLowerCase() === ZERO_ADDRESS) {
596
+ return null;
615
597
  }
598
+ const pair = new Contract(pairAddress, I_UNIV2_PAIR_ABI, provider);
599
+ // ✅ 并行获取 token0、token1 和 reserves
600
+ const [token0, token1, reserves] = await Promise.all([
601
+ pair.token0(),
602
+ pair.token1(),
603
+ pair.getReserves()
604
+ ]);
605
+ const [r0, r1] = reserves;
606
+ const isToken0 = token0.toLowerCase() === token.toLowerCase();
607
+ const reserveToken = isToken0 ? r0 : r1;
608
+ const reserveQuote = isToken0 ? r1 : r0;
609
+ return {
610
+ quoteToken: qt.address,
611
+ quoteSymbol: qt.symbol,
612
+ quoteDecimals: qt.decimals,
613
+ pairAddress,
614
+ reserveToken: formatBalance(reserveToken, shouldFormat, tokenDecimals),
615
+ reserveQuote: formatBalance(reserveQuote, shouldFormat, qt.decimals),
616
+ reserveQuoteRaw: reserveQuote,
617
+ reserveTokenRaw: reserveToken,
618
+ };
616
619
  }
617
620
  catch {
618
- // 跳过失败的查询
621
+ return null;
619
622
  }
620
- }
621
- return results;
623
+ }));
624
+ // 过滤掉 null 结果
625
+ return pairResults.filter((r) => r !== null);
622
626
  }
623
627
  // ============================================================================
624
628
  // V3 批量查询
@@ -715,43 +719,51 @@ async function queryV3Pools(token, factoryAddress, quoteTokens, feeTiers, provid
715
719
  return results;
716
720
  }
717
721
  async function queryV3PoolsFallback(token, factoryAddress, quoteTokens, feeTiers, provider, tokenDecimals, shouldFormat) {
718
- const results = [];
719
722
  const factory = new Contract(factoryAddress, I_UNIV3_FACTORY_ABI, provider);
720
723
  const tokenContract = new Contract(token, I_ERC20_ABI, provider);
724
+ // ✅ 构建所有查询组合
725
+ const queries = [];
721
726
  for (const qt of quoteTokens) {
722
- const quoteContract = new Contract(qt.address, I_ERC20_ABI, provider);
727
+ const tokenLower = token.toLowerCase();
728
+ const quoteLower = qt.address.toLowerCase();
729
+ const [token0, token1] = tokenLower < quoteLower
730
+ ? [tokenLower, quoteLower]
731
+ : [quoteLower, tokenLower];
723
732
  for (const fee of feeTiers) {
724
- try {
725
- const tokenLower = token.toLowerCase();
726
- const quoteLower = qt.address.toLowerCase();
727
- const [token0, token1] = tokenLower < quoteLower
728
- ? [tokenLower, quoteLower]
729
- : [quoteLower, tokenLower];
730
- const poolAddress = await factory.getPool(token0, token1, fee);
731
- if (poolAddress && poolAddress.toLowerCase() !== ZERO_ADDRESS) {
732
- const [tokenBalance, quoteBalance] = await Promise.all([
733
- tokenContract.balanceOf(poolAddress),
734
- quoteContract.balanceOf(poolAddress),
735
- ]);
736
- results.push({
737
- quoteToken: qt.address,
738
- quoteSymbol: qt.symbol,
739
- quoteDecimals: qt.decimals,
740
- pairAddress: poolAddress,
741
- reserveToken: formatBalance(tokenBalance, shouldFormat, tokenDecimals),
742
- reserveQuote: formatBalance(quoteBalance, shouldFormat, qt.decimals),
743
- reserveQuoteRaw: quoteBalance,
744
- reserveTokenRaw: tokenBalance, // ✅ 新增
745
- fee,
746
- });
747
- }
748
- }
749
- catch {
750
- // 跳过失败的查询
751
- }
733
+ queries.push({ qt, fee, token0, token1 });
752
734
  }
753
735
  }
754
- return results;
736
+ // ✅ 并行查询所有池子
737
+ const poolResults = await Promise.all(queries.map(async ({ qt, fee, token0, token1 }) => {
738
+ try {
739
+ const poolAddress = await factory.getPool(token0, token1, fee);
740
+ if (!poolAddress || poolAddress.toLowerCase() === ZERO_ADDRESS) {
741
+ return null;
742
+ }
743
+ const quoteContract = new Contract(qt.address, I_ERC20_ABI, provider);
744
+ // ✅ 并行获取余额
745
+ const [tokenBalance, quoteBalance] = await Promise.all([
746
+ tokenContract.balanceOf(poolAddress),
747
+ quoteContract.balanceOf(poolAddress),
748
+ ]);
749
+ return {
750
+ quoteToken: qt.address,
751
+ quoteSymbol: qt.symbol,
752
+ quoteDecimals: qt.decimals,
753
+ pairAddress: poolAddress,
754
+ reserveToken: formatBalance(tokenBalance, shouldFormat, tokenDecimals),
755
+ reserveQuote: formatBalance(quoteBalance, shouldFormat, qt.decimals),
756
+ reserveQuoteRaw: quoteBalance,
757
+ reserveTokenRaw: tokenBalance,
758
+ fee,
759
+ };
760
+ }
761
+ catch {
762
+ return null;
763
+ }
764
+ }));
765
+ // 过滤掉 null 结果
766
+ return poolResults.filter((r) => r !== null);
755
767
  }
756
768
  // ============================================================================
757
769
  // 平台类型判断
@@ -52,12 +52,9 @@ async function _stealthTransferInternal(params) {
52
52
  if (rootBal < needed) {
53
53
  throw new Error(`insufficient balance: need ${needed.toString()}, have ${rootBal.toString()}`);
54
54
  }
55
- // 预取 nonce(pending)
56
- const nonces = [];
57
- nonces.push(await provider.getTransactionCount(rootAddr, 'pending'));
58
- for (const w of hops) {
59
- nonces.push(await provider.getTransactionCount(w.address, 'pending'));
60
- }
55
+ // 并行预取所有 nonce
56
+ const allAddresses = [rootAddr, ...hops.map(w => w.address)];
57
+ const nonces = await Promise.all(allAddresses.map(addr => provider.getTransactionCount(addr, 'pending')));
61
58
  const signed = [];
62
59
  const hopAddresses = [];
63
60
  if (mode !== 'erc20') {
@@ -73,14 +70,14 @@ async function _stealthTransferInternal(params) {
73
70
  type: 0,
74
71
  });
75
72
  signed.push(fundTx);
76
- // 2) 每一跳按顺序转发
77
- for (let i = 0; i < hops.length; i++) {
73
+ // 2) ✅ 并行签名所有跳转交易
74
+ const hopTxs = await Promise.all(hops.map(async (hop, i) => {
78
75
  const isLast = i === hops.length - 1;
79
76
  const nextTo = isLast ? finalTo : hops[i + 1].address;
80
77
  const remain = BigInt(hops.length - 1 - i);
81
78
  const value = isLast ? amountWei : amountWei + perHopFee * remain;
82
- hopAddresses.push(hops[i].address);
83
- const tx = await hops[i].signTransaction({
79
+ hopAddresses.push(hop.address);
80
+ return hop.signTransaction({
84
81
  to: nextTo,
85
82
  value,
86
83
  nonce: nonces[i + 1],
@@ -89,61 +86,61 @@ async function _stealthTransferInternal(params) {
89
86
  chainId,
90
87
  type: 0,
91
88
  });
92
- signed.push(tx);
93
- }
89
+ }));
90
+ signed.push(...hopTxs);
94
91
  }
95
92
  else {
96
93
  // ============== ERC20 代币多跳路径 ==============
97
- // 为每个 hop 预先注入 gas 费(root -> hop[i]),每个 hop 只需要发送一笔 ERC20 transfer
98
- let currNonce = nonces[0];
99
- for (let i = 0; i < hops.length; i++) {
100
- hopAddresses.push(hops[i].address);
101
- const fund = await root.signTransaction({
102
- to: hops[i].address,
103
- value: (erc20?.transferGasLimit ?? 65000n) * gasPrice, // 覆盖该 hop 的代币转账 gas
104
- nonce: currNonce++,
105
- gasPrice,
106
- gasLimit,
107
- chainId,
108
- type: 0,
109
- });
110
- signed.push(fund);
111
- }
94
+ const erc20Iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
95
+ const transferGas = erc20?.transferGasLimit ?? 65000n;
96
+ // 填充 hopAddresses
97
+ hops.forEach(hop => hopAddresses.push(hop.address));
98
+ // 并行签名所有 gas 注资交易(root -> hop[i])
99
+ const fundTxs = await Promise.all(hops.map((hop, i) => root.signTransaction({
100
+ to: hop.address,
101
+ value: transferGas * gasPrice,
102
+ nonce: nonces[0] + i,
103
+ gasPrice,
104
+ gasLimit,
105
+ chainId,
106
+ type: 0,
107
+ })));
108
+ signed.push(...fundTxs);
112
109
  // 代币持有者:可选独立私钥,未提供则用 root
113
110
  const tokenOwner = (erc20?.holderPrivateKey && erc20.holderPrivateKey.length > 0)
114
111
  ? new ethers.Wallet(erc20.holderPrivateKey, provider)
115
112
  : root;
116
- // tokenOwner -> hop0: ERC20 transfer 全量 token(amountWei 解释为代币数量)
117
- const erc20Iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
113
+ // tokenOwner -> hop0: ERC20 transfer 全量 token
118
114
  const callDataRoot = erc20Iface.encodeFunctionData('transfer', [hops[0].address, amountWei]);
115
+ const rootTokenNonce = nonces[0] + hops.length;
119
116
  const erc20TxRoot = await tokenOwner.signTransaction({
120
117
  to: erc20.address,
121
118
  data: callDataRoot,
122
119
  value: 0n,
123
- nonce: currNonce++,
120
+ nonce: rootTokenNonce,
124
121
  gasPrice,
125
- gasLimit: erc20?.transferGasLimit ?? 65000n,
122
+ gasLimit: transferGas,
126
123
  chainId,
127
124
  type: 0,
128
125
  });
129
126
  signed.push(erc20TxRoot);
130
- // hop[i] -> next: ERC20 transfer,最后一跳 -> finalTo
131
- for (let i = 0; i < hops.length; i++) {
127
+ // ✅ 并行签名所有 hop ERC20 transfer
128
+ const hopTokenTxs = await Promise.all(hops.map((hop, i) => {
132
129
  const isLast = i === hops.length - 1;
133
130
  const nextTo = isLast ? finalTo : hops[i + 1].address;
134
131
  const callData = erc20Iface.encodeFunctionData('transfer', [nextTo, amountWei]);
135
- const tx = await hops[i].signTransaction({
132
+ return hop.signTransaction({
136
133
  to: erc20.address,
137
134
  data: callData,
138
135
  value: 0n,
139
136
  nonce: nonces[i + 1],
140
137
  gasPrice,
141
- gasLimit: erc20?.transferGasLimit ?? 65000n,
138
+ gasLimit: transferGas,
142
139
  chainId,
143
140
  type: 0,
144
141
  });
145
- signed.push(tx);
146
- }
142
+ }));
143
+ signed.push(...hopTokenTxs);
147
144
  }
148
145
  // 3) Bundle 提交
149
146
  const club48 = new Club48Client({ endpoint: bundleEndpoint });
@@ -112,20 +112,24 @@ export async function calculateSellAmount(provider, tokenAddress, walletAddress,
112
112
  if (sellAmount && sellPercentage) {
113
113
  throw new Error('sellAmount 和 sellPercentage 不能同时指定');
114
114
  }
115
- const decimals = await getTokenDecimals(provider, tokenAddress);
116
- // 精确数量
115
+ // 精确数量:只需要 decimals
117
116
  if (sellAmount) {
117
+ const decimals = await getTokenDecimals(provider, tokenAddress);
118
118
  return {
119
119
  amount: ethers.parseUnits(sellAmount, decimals),
120
120
  decimals
121
121
  };
122
122
  }
123
- // 百分比
123
+ // 百分比:需要 decimals 和 balance
124
124
  if (sellPercentage !== undefined) {
125
125
  if (sellPercentage <= 0 || sellPercentage > 100) {
126
126
  throw new Error('sellPercentage 必须在 0-100 之间');
127
127
  }
128
- const balance = await getTokenBalance(provider, tokenAddress, walletAddress);
128
+ // 并行获取 decimals balance
129
+ const [decimals, balance] = await Promise.all([
130
+ getTokenDecimals(provider, tokenAddress),
131
+ getTokenBalance(provider, tokenAddress, walletAddress)
132
+ ]);
129
133
  const amount = (balance * BigInt(Math.floor(sellPercentage * 100))) / 10000n;
130
134
  return { amount, decimals };
131
135
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.3.81",
3
+ "version": "1.3.82",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",