four-flap-meme-sdk 1.3.81 → 1.3.83

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 (111) hide show
  1. package/dist/contracts/tm-bundle.js +224 -113
  2. package/dist/flap/portal-bundle-merkle/private.js +87 -107
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +1 -0
  5. package/dist/sol/constants.d.ts +136 -0
  6. package/dist/sol/constants.js +156 -0
  7. package/dist/sol/dex/index.d.ts +8 -0
  8. package/dist/sol/dex/index.js +12 -0
  9. package/dist/sol/dex/meteora/client.d.ts +75 -0
  10. package/dist/sol/dex/meteora/client.js +218 -0
  11. package/dist/sol/dex/meteora/damm-v1-bundle.d.ts +61 -0
  12. package/dist/sol/dex/meteora/damm-v1-bundle.js +112 -0
  13. package/dist/sol/dex/meteora/damm-v1.d.ts +118 -0
  14. package/dist/sol/dex/meteora/damm-v1.js +315 -0
  15. package/dist/sol/dex/meteora/damm-v2-bundle.d.ts +82 -0
  16. package/dist/sol/dex/meteora/damm-v2-bundle.js +242 -0
  17. package/dist/sol/dex/meteora/damm-v2.d.ts +172 -0
  18. package/dist/sol/dex/meteora/damm-v2.js +632 -0
  19. package/dist/sol/dex/meteora/dbc-bundle.d.ts +123 -0
  20. package/dist/sol/dex/meteora/dbc-bundle.js +304 -0
  21. package/dist/sol/dex/meteora/dbc.d.ts +192 -0
  22. package/dist/sol/dex/meteora/dbc.js +619 -0
  23. package/dist/sol/dex/meteora/dlmm-bundle.d.ts +39 -0
  24. package/dist/sol/dex/meteora/dlmm-bundle.js +189 -0
  25. package/dist/sol/dex/meteora/dlmm.d.ts +146 -0
  26. package/dist/sol/dex/meteora/dlmm.js +593 -0
  27. package/dist/sol/dex/meteora/index.d.ts +25 -0
  28. package/dist/sol/dex/meteora/index.js +65 -0
  29. package/dist/sol/dex/meteora/types.d.ts +787 -0
  30. package/dist/sol/dex/meteora/types.js +110 -0
  31. package/dist/sol/dex/orca/index.d.ts +10 -0
  32. package/dist/sol/dex/orca/index.js +16 -0
  33. package/dist/sol/dex/orca/orca-bundle.d.ts +41 -0
  34. package/dist/sol/dex/orca/orca-bundle.js +140 -0
  35. package/dist/sol/dex/orca/orca.d.ts +65 -0
  36. package/dist/sol/dex/orca/orca.js +426 -0
  37. package/dist/sol/dex/orca/types.d.ts +263 -0
  38. package/dist/sol/dex/orca/types.js +38 -0
  39. package/dist/sol/dex/orca/wavebreak-bundle.d.ts +34 -0
  40. package/dist/sol/dex/orca/wavebreak-bundle.js +189 -0
  41. package/dist/sol/dex/orca/wavebreak-types.d.ts +227 -0
  42. package/dist/sol/dex/orca/wavebreak-types.js +23 -0
  43. package/dist/sol/dex/orca/wavebreak.d.ts +78 -0
  44. package/dist/sol/dex/orca/wavebreak.js +469 -0
  45. package/dist/sol/dex/pump/index.d.ts +9 -0
  46. package/dist/sol/dex/pump/index.js +14 -0
  47. package/dist/sol/dex/pump/pump-bundle.d.ts +92 -0
  48. package/dist/sol/dex/pump/pump-bundle.js +383 -0
  49. package/dist/sol/dex/pump/pump-swap-bundle.d.ts +103 -0
  50. package/dist/sol/dex/pump/pump-swap-bundle.js +380 -0
  51. package/dist/sol/dex/pump/pump-swap.d.ts +46 -0
  52. package/dist/sol/dex/pump/pump-swap.js +199 -0
  53. package/dist/sol/dex/pump/pump.d.ts +35 -0
  54. package/dist/sol/dex/pump/pump.js +352 -0
  55. package/dist/sol/dex/pump/types.d.ts +215 -0
  56. package/dist/sol/dex/pump/types.js +5 -0
  57. package/dist/sol/dex/raydium/index.d.ts +8 -0
  58. package/dist/sol/dex/raydium/index.js +12 -0
  59. package/dist/sol/dex/raydium/launchlab.d.ts +68 -0
  60. package/dist/sol/dex/raydium/launchlab.js +210 -0
  61. package/dist/sol/dex/raydium/raydium-bundle.d.ts +64 -0
  62. package/dist/sol/dex/raydium/raydium-bundle.js +324 -0
  63. package/dist/sol/dex/raydium/raydium.d.ts +40 -0
  64. package/dist/sol/dex/raydium/raydium.js +366 -0
  65. package/dist/sol/dex/raydium/types.d.ts +240 -0
  66. package/dist/sol/dex/raydium/types.js +5 -0
  67. package/dist/sol/index.d.ts +10 -0
  68. package/dist/sol/index.js +16 -0
  69. package/dist/sol/jito/bundle.d.ts +90 -0
  70. package/dist/sol/jito/bundle.js +263 -0
  71. package/dist/sol/jito/index.d.ts +7 -0
  72. package/dist/sol/jito/index.js +7 -0
  73. package/dist/sol/jito/tip.d.ts +51 -0
  74. package/dist/sol/jito/tip.js +83 -0
  75. package/dist/sol/jito/types.d.ts +100 -0
  76. package/dist/sol/jito/types.js +5 -0
  77. package/dist/sol/token/create-complete.d.ts +115 -0
  78. package/dist/sol/token/create-complete.js +235 -0
  79. package/dist/sol/token/create-token.d.ts +57 -0
  80. package/dist/sol/token/create-token.js +230 -0
  81. package/dist/sol/token/index.d.ts +9 -0
  82. package/dist/sol/token/index.js +14 -0
  83. package/dist/sol/token/metadata-upload.d.ts +86 -0
  84. package/dist/sol/token/metadata-upload.js +173 -0
  85. package/dist/sol/token/metadata.d.ts +92 -0
  86. package/dist/sol/token/metadata.js +274 -0
  87. package/dist/sol/token/types.d.ts +153 -0
  88. package/dist/sol/token/types.js +5 -0
  89. package/dist/sol/types.d.ts +176 -0
  90. package/dist/sol/types.js +7 -0
  91. package/dist/sol/utils/balance.d.ts +160 -0
  92. package/dist/sol/utils/balance.js +638 -0
  93. package/dist/sol/utils/connection.d.ts +69 -0
  94. package/dist/sol/utils/connection.js +156 -0
  95. package/dist/sol/utils/index.d.ts +9 -0
  96. package/dist/sol/utils/index.js +9 -0
  97. package/dist/sol/utils/lp-inspect.d.ts +129 -0
  98. package/dist/sol/utils/lp-inspect.js +515 -0
  99. package/dist/sol/utils/transfer.d.ts +125 -0
  100. package/dist/sol/utils/transfer.js +220 -0
  101. package/dist/sol/utils/wallet.d.ts +107 -0
  102. package/dist/sol/utils/wallet.js +210 -0
  103. package/dist/utils/airdrop-sweep.js +86 -76
  104. package/dist/utils/erc20.d.ts +1 -0
  105. package/dist/utils/erc20.js +22 -20
  106. package/dist/utils/lp-inspect.js +77 -65
  107. package/dist/utils/stealth-transfer.js +34 -37
  108. package/dist/utils/swap-helpers.js +8 -4
  109. package/package.json +23 -3
  110. package/dist/flap/portal-bundle-merkle/encryption.d.ts +0 -16
  111. package/dist/flap/portal-bundle-merkle/encryption.js +0 -146
@@ -39,10 +39,10 @@ export async function disperseWithBundle(params) {
39
39
  const signedTxs = [];
40
40
  if (isNative) {
41
41
  const baseNonce = await provider.getTransactionCount(wallet.address, 'pending');
42
- for (let i = 0; i < recipients.length; i++) {
43
- const to = recipients[i];
42
+ // 并行签名所有交易
43
+ const signedTxList = await Promise.all(recipients.map(async (to, i) => {
44
44
  const amountWei = ethers.parseEther(effAmounts[i]);
45
- const tx = await wallet.signTransaction({
45
+ return wallet.signTransaction({
46
46
  to,
47
47
  value: amountWei,
48
48
  nonce: baseNonce + i,
@@ -51,18 +51,21 @@ export async function disperseWithBundle(params) {
51
51
  chainId,
52
52
  type: 0
53
53
  });
54
- signedTxs.push(tx);
55
- }
54
+ }));
55
+ signedTxs.push(...signedTxList);
56
56
  }
57
57
  else {
58
- const decimals = await getErc20Decimals(provider, tokenAddress);
58
+ // 并行获取 decimals nonce
59
+ const [decimals, baseNonce] = await Promise.all([
60
+ getErc20Decimals(provider, tokenAddress),
61
+ provider.getTransactionCount(wallet.address, 'pending')
62
+ ]);
59
63
  const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
60
- const baseNonce = await provider.getTransactionCount(wallet.address, 'pending');
61
- for (let i = 0; i < recipients.length; i++) {
62
- const to = recipients[i];
64
+ // 并行签名所有交易
65
+ const signedTxList = await Promise.all(recipients.map(async (to, i) => {
63
66
  const amountWei = ethers.parseUnits(effAmounts[i], decimals);
64
67
  const data = iface.encodeFunctionData('transfer', [to, amountWei]);
65
- const tx = await wallet.signTransaction({
68
+ return wallet.signTransaction({
66
69
  to: tokenAddress,
67
70
  data,
68
71
  value: 0n,
@@ -72,8 +75,8 @@ export async function disperseWithBundle(params) {
72
75
  chainId,
73
76
  type: 0
74
77
  });
75
- signedTxs.push(tx);
76
- }
78
+ }));
79
+ signedTxs.push(...signedTxList);
77
80
  }
78
81
  // ✅ 只返回签名交易,不提交到 Bundle
79
82
  return { signedTransactions: signedTxs };
@@ -104,89 +107,96 @@ export async function sweepWithBundle(params) {
104
107
  };
105
108
  const ratio = clampRatio(ratioPct);
106
109
  if (isNative) {
107
- for (let i = 0; i < sourcePrivateKeys.length; i++) {
108
- const w = new ethers.Wallet(sourcePrivateKeys[i], provider);
109
- let toSend = 0n;
110
- try {
111
- const bal = await provider.getBalance(w.address);
112
- const gasCost = nativeGasLimit * gasPrice;
113
- if (ratio !== undefined) {
114
- const want = (bal * BigInt(ratio)) / 100n;
115
- const maxSendable = bal > gasCost ? (bal - gasCost) : 0n;
116
- toSend = want > maxSendable ? maxSendable : want;
117
- }
118
- else if (amount && amount.trim().length > 0) {
119
- const amountWei = ethers.parseEther(amount);
120
- const needed = amountWei + gasCost;
121
- if (!skipIfInsufficient || bal >= needed)
122
- toSend = amountWei;
123
- }
110
+ // 并行处理所有源钱包
111
+ const wallets = sourcePrivateKeys.map(pk => new ethers.Wallet(pk, provider));
112
+ const gasCost = nativeGasLimit * gasPrice;
113
+ // 并行获取所有余额和 nonce
114
+ const [balances, nonces] = await Promise.all([
115
+ Promise.all(wallets.map(w => provider.getBalance(w.address).catch(() => 0n))),
116
+ Promise.all(wallets.map(w => provider.getTransactionCount(w.address, 'pending')))
117
+ ]);
118
+ // 计算每个钱包的发送金额
119
+ const sendAmounts = balances.map(bal => {
120
+ if (ratio !== undefined) {
121
+ const want = (bal * BigInt(ratio)) / 100n;
122
+ const maxSendable = bal > gasCost ? (bal - gasCost) : 0n;
123
+ return want > maxSendable ? maxSendable : want;
124
124
  }
125
- catch {
126
- // 读取失败:保守不发送
127
- toSend = 0n;
125
+ else if (amount && amount.trim().length > 0) {
126
+ const amountWei = ethers.parseEther(amount);
127
+ const needed = amountWei + gasCost;
128
+ if (!skipIfInsufficient || bal >= needed)
129
+ return amountWei;
128
130
  }
129
- if (toSend <= 0n)
130
- continue;
131
- const nonce = await provider.getTransactionCount(w.address, 'pending');
132
- const tx = await w.signTransaction({
133
- to: target,
134
- value: toSend,
135
- nonce,
136
- gasPrice,
137
- gasLimit: nativeGasLimit,
138
- chainId,
139
- type: 0
140
- });
141
- signedTxs.push(tx);
142
- }
131
+ return 0n;
132
+ });
133
+ // 并行签名所有有效交易
134
+ const validIndices = sendAmounts.map((amt, i) => amt > 0n ? i : -1).filter(i => i >= 0);
135
+ const signedTxList = await Promise.all(validIndices.map(i => wallets[i].signTransaction({
136
+ to: target,
137
+ value: sendAmounts[i],
138
+ nonce: nonces[i],
139
+ gasPrice,
140
+ gasLimit: nativeGasLimit,
141
+ chainId,
142
+ type: 0
143
+ })));
144
+ signedTxs.push(...signedTxList);
143
145
  }
144
146
  else {
145
- const decimals = await getErc20Decimals(provider, tokenAddress);
147
+ const wallets = sourcePrivateKeys.map(pk => new ethers.Wallet(pk, provider));
146
148
  const iface = new ethers.Interface(['function balanceOf(address) view returns (uint256)', 'function transfer(address,uint256) returns (bool)']);
147
- for (let i = 0; i < sourcePrivateKeys.length; i++) {
148
- const w = new ethers.Wallet(sourcePrivateKeys[i], provider);
149
- let toSend = 0n;
150
- try {
151
- const balData = iface.encodeFunctionData('balanceOf', [w.address]);
152
- const balRaw = await provider.call({ to: tokenAddress, data: balData });
153
- const [bal] = iface.decodeFunctionResult('balanceOf', balRaw);
154
- if (ratio !== undefined) {
155
- toSend = (bal * BigInt(ratio)) / 100n;
156
- }
157
- else if (amount && amount.trim().length > 0) {
158
- toSend = ethers.parseUnits(amount, decimals);
159
- }
160
- }
161
- catch {
162
- toSend = 0n;
163
- }
164
- if (toSend <= 0n)
165
- continue;
166
- if (skipIfInsufficient) {
149
+ // 并行获取 decimals、所有代币余额和 nonce
150
+ const [decimals, balanceResults, nonces] = await Promise.all([
151
+ getErc20Decimals(provider, tokenAddress),
152
+ Promise.all(wallets.map(async (w) => {
167
153
  try {
168
154
  const balData = iface.encodeFunctionData('balanceOf', [w.address]);
169
155
  const balRaw = await provider.call({ to: tokenAddress, data: balData });
170
156
  const [bal] = iface.decodeFunctionResult('balanceOf', balRaw);
171
- if (bal < toSend)
172
- continue;
157
+ return bal;
158
+ }
159
+ catch {
160
+ return 0n;
173
161
  }
174
- catch { }
162
+ })),
163
+ Promise.all(wallets.map(w => provider.getTransactionCount(w.address, 'pending')))
164
+ ]);
165
+ // 计算每个钱包的发送金额
166
+ const sendAmounts = balanceResults.map(bal => {
167
+ if (ratio !== undefined) {
168
+ return (bal * BigInt(ratio)) / 100n;
169
+ }
170
+ else if (amount && amount.trim().length > 0) {
171
+ const amountWei = ethers.parseUnits(amount, decimals);
172
+ if (!skipIfInsufficient || bal >= amountWei)
173
+ return amountWei;
175
174
  }
176
- const data = iface.encodeFunctionData('transfer', [target, toSend]);
177
- const nonce = await provider.getTransactionCount(w.address, 'pending');
178
- const tx = await w.signTransaction({
175
+ return 0n;
176
+ });
177
+ // 过滤余额不足的钱包
178
+ const validIndices = sendAmounts.map((amt, i) => {
179
+ if (amt <= 0n)
180
+ return -1;
181
+ if (skipIfInsufficient && balanceResults[i] < amt)
182
+ return -1;
183
+ return i;
184
+ }).filter(i => i >= 0);
185
+ // 并行签名所有有效交易
186
+ const signedTxList = await Promise.all(validIndices.map(i => {
187
+ const data = iface.encodeFunctionData('transfer', [target, sendAmounts[i]]);
188
+ return wallets[i].signTransaction({
179
189
  to: tokenAddress,
180
190
  data,
181
191
  value: 0n,
182
- nonce,
192
+ nonce: nonces[i],
183
193
  gasPrice,
184
194
  gasLimit: transferGasLimit,
185
195
  chainId,
186
196
  type: 0
187
197
  });
188
- signedTxs.push(tx);
189
- }
198
+ }));
199
+ signedTxs.push(...signedTxList);
190
200
  }
191
201
  // ✅ 只返回签名交易,不提交到 Bundle
192
202
  return { signedTransactions: signedTxs };
@@ -75,6 +75,7 @@ export declare function ensureFlapSellApproval(chain: 'BSC' | 'BASE' | 'XLAYER'
75
75
  export declare function ensureFlapSellApprovalBatch(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD', rpcUrl: string, privateKeys: string[], token: string, required?: bigint): Promise<EnsureAllowanceBatchItemResult[]>;
76
76
  /**
77
77
  * 批量检查 Flap Protocol 授权状态(默认按上限 2^256-1 判断)
78
+ * ✅ 使用 Multicall3 批量查询,减少 RPC 调用次数
78
79
  */
79
80
  export declare function checkFlapSellApprovalBatch(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD', rpcUrl: string, token: string, owners: string[], required?: bigint): Promise<Array<{
80
81
  owner: string;
@@ -216,18 +216,23 @@ export async function ensureFlapSellApproval(chain, rpcUrl, privateKey, token, o
216
216
  * @returns 每个地址的授权结果(包含 owner 与交易回执等信息)
217
217
  */
218
218
  export async function ensureFlapSellApprovalBatch(chain, rpcUrl, privateKeys, token, required = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')) {
219
- const results = [];
220
- for (const pk of privateKeys || []) {
219
+ if (!privateKeys || privateKeys.length === 0)
220
+ return [];
221
+ // ✅ 并行处理所有授权
222
+ const results = await Promise.all(privateKeys.map(async (pk) => {
221
223
  const owner = new Wallet(pk).address;
222
224
  const r = await ensureFlapSellApproval(chain, rpcUrl, pk, token, owner, required);
223
- results.push({ owner, ...r });
224
- }
225
+ return { owner, ...r };
226
+ }));
225
227
  return results;
226
228
  }
227
229
  /**
228
230
  * 批量检查 Flap Protocol 授权状态(默认按上限 2^256-1 判断)
231
+ * ✅ 使用 Multicall3 批量查询,减少 RPC 调用次数
229
232
  */
230
233
  export async function checkFlapSellApprovalBatch(chain, rpcUrl, token, owners, required = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')) {
234
+ if (!owners || owners.length === 0)
235
+ return [];
231
236
  const proxyAddresses = {
232
237
  BSC: ADDRESSES.BSC.FlapPortal,
233
238
  BASE: ADDRESSES.BASE.FlapPortal,
@@ -236,22 +241,19 @@ export async function checkFlapSellApprovalBatch(chain, rpcUrl, token, owners, r
236
241
  MONAD: ADDRESSES.MONAD.FlapPortal,
237
242
  };
238
243
  const provider = new JsonRpcProvider(rpcUrl);
239
- // ✅ 验证 token 和代理合约地址
240
- await validateContractAddress(provider, token, 'Token');
241
- await validateContractAddress(provider, proxyAddresses[chain], `Flap Portal (${chain})`);
242
- const erc20 = new Contract(token, ERC20_ABI, provider);
243
- const out = [];
244
- for (const owner of owners || []) {
245
- let current;
246
- try {
247
- current = await erc20.allowance(owner, proxyAddresses[chain]);
248
- }
249
- catch (error) {
250
- throw new Error(`❌ 调用 allowance 失败(Token 可能不是 ERC20): ${error.message}`);
251
- }
252
- out.push({ owner, isApproved: current >= required, currentAllowance: current, requiredAllowance: required });
253
- }
254
- return out;
244
+ // ✅ 并行验证 token 和代理合约地址
245
+ await Promise.all([
246
+ validateContractAddress(provider, token, 'Token'),
247
+ validateContractAddress(provider, proxyAddresses[chain], `Flap Portal (${chain})`)
248
+ ]);
249
+ // 使用 batchCheckAllowances 批量查询(Multicall3)
250
+ const allowances = await batchCheckAllowances(provider, token, owners, proxyAddresses[chain]);
251
+ return owners.map((owner, i) => ({
252
+ owner,
253
+ isApproved: allowances[i] >= required,
254
+ currentAllowance: allowances[i],
255
+ requiredAllowance: required
256
+ }));
255
257
  }
256
258
  /**
257
259
  * 使用 Multicall3 批量查询 ERC20 授权额度
@@ -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
  }