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
@@ -7,38 +7,12 @@
7
7
  * - ERC20 → 原生代币转换报价
8
8
  */
9
9
  import { ethers, Contract } from 'ethers';
10
+ import { V2_ROUTER_QUOTE_ABI, V3_QUOTER_ABI } from '../abis/common.js';
10
11
  // ============================================================================
11
12
  // 常量配置
12
13
  // ============================================================================
13
- /** V2 Router ABI(用于报价) */
14
- const V2_ROUTER_ABI = [
15
- 'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
16
- ];
17
- /** V3 QuoterV2 ABI(用于报价)- 使用结构体参数 */
18
- const V3_QUOTER_ABI = [
19
- {
20
- "inputs": [{
21
- "components": [
22
- { "name": "tokenIn", "type": "address" },
23
- { "name": "tokenOut", "type": "address" },
24
- { "name": "amountIn", "type": "uint256" },
25
- { "name": "fee", "type": "uint24" },
26
- { "name": "sqrtPriceLimitX96", "type": "uint160" }
27
- ],
28
- "name": "params",
29
- "type": "tuple"
30
- }],
31
- "name": "quoteExactInputSingle",
32
- "outputs": [
33
- { "name": "amountOut", "type": "uint256" },
34
- { "name": "sqrtPriceX96After", "type": "uint160" },
35
- { "name": "initializedTicksCrossed", "type": "uint32" },
36
- { "name": "gasEstimate", "type": "uint256" }
37
- ],
38
- "stateMutability": "nonpayable",
39
- "type": "function"
40
- }
41
- ];
14
+ // V2_ROUTER_ABI V3_QUOTER_ABI 从公共模块导入
15
+ const V2_ROUTER_ABI = V2_ROUTER_QUOTE_ABI;
42
16
  /** V3 常用费率档位 */
43
17
  export const V3_FEE_TIERS = [100, 500, 2500, 10000]; // 0.01%, 0.05%, 0.25%, 1%
44
18
  /** 各链的报价配置 */
@@ -3,12 +3,7 @@
3
3
  */
4
4
  import { ethers, Contract } from 'ethers';
5
5
  import { batchCheckAllowances } from './erc20.js';
6
- const ERC20_ABI = [
7
- 'function approve(address spender, uint256 amount) returns (bool)',
8
- 'function allowance(address owner, address spender) view returns (uint256)',
9
- 'function balanceOf(address account) view returns (uint256)',
10
- 'function decimals() view returns (uint8)'
11
- ];
6
+ import { ERC20_ABI } from '../abis/common.js';
12
7
  const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
13
8
  /**
14
9
  * 获取 Gas Limit(授权专用)
@@ -9,19 +9,6 @@ export type PrivateKeyValidation = {
9
9
  normalized?: string;
10
10
  error?: string;
11
11
  };
12
- /**
13
- * 批量生成指定数量的钱包地址与私钥。
14
- * 注意:仅用于开发/测试。生产环境请安全存储私钥。
15
- */
16
- export declare function generateWallets(count: number): GeneratedWallet[];
17
- /**
18
- * 批量校验私钥数组是否合规。
19
- * 规则:
20
- * - 必须为 0x 开头的 32 字节十六进制串(64 十六进制字符)
21
- * - 能够被 ethers.Wallet 正常构造(包含椭圆曲线有效性)
22
- * 返回每个私钥的校验结果与可选地址/错误信息。
23
- */
24
- export declare function validatePrivateKeys(privateKeys: string[]): PrivateKeyValidation[];
25
12
  export type MulticallResult = {
26
13
  address: string;
27
14
  balance: bigint;
@@ -36,6 +23,14 @@ export type MultiTokenBalancesResult = {
36
23
  }>;
37
24
  success: boolean;
38
25
  };
26
+ /**
27
+ * 批量生成钱包(并行优化)
28
+ */
29
+ export declare function generateWallets(count: number): GeneratedWallet[];
30
+ /**
31
+ * 批量校验私钥(并行处理)
32
+ */
33
+ export declare function validatePrivateKeys(privateKeys: string[]): PrivateKeyValidation[];
39
34
  export declare function getTokenBalancesWithMulticall(rpcUrl: string, token: string, holders: string[]): Promise<MulticallResult[]>;
40
35
  export declare function getTokenBalancesWithMulticall(rpcUrl: string, multicall3: string, token: string, holders: string[]): Promise<MulticallResult[]>;
41
36
  export declare function getTokenBalancesWithMulticall(rpcUrl: string, chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'ARBITRUM_ONE' | 'MONAD', tokenAddresses: string[], holders: string[]): Promise<MultiTokenBalancesResult[]>;
@@ -1,24 +1,36 @@
1
1
  import { Wallet, Interface, JsonRpcProvider, formatEther, formatUnits, isHexString } from 'ethers';
2
- import { CHAIN } from './constants.js';
2
+ import { CHAIN, ADDRESSES } from './constants.js';
3
+ import { ERC20_ABI, MULTICALL3_ABI } from '../abis/common.js';
4
+ // ============================================================================
5
+ // 常量
6
+ // ============================================================================
7
+ const DEFAULT_MULTICALL3 = ADDRESSES.BSC.Multicall3;
8
+ const MULTICALL3_BY_CHAIN = {
9
+ 1: DEFAULT_MULTICALL3, // Ethereum
10
+ 56: DEFAULT_MULTICALL3, // BSC Mainnet
11
+ 97: DEFAULT_MULTICALL3, // BSC Testnet
12
+ 8453: DEFAULT_MULTICALL3, // Base
13
+ 42161: DEFAULT_MULTICALL3, // Arbitrum One
14
+ 196: DEFAULT_MULTICALL3, // X Layer
15
+ 2818: DEFAULT_MULTICALL3, // Morph
16
+ 143: DEFAULT_MULTICALL3, // Monad
17
+ };
18
+ // ============================================================================
19
+ // 钱包生成与验证
20
+ // ============================================================================
3
21
  /**
4
- * 批量生成指定数量的钱包地址与私钥。
5
- * 注意:仅用于开发/测试。生产环境请安全存储私钥。
22
+ * 批量生成钱包(并行优化)
6
23
  */
7
24
  export function generateWallets(count) {
8
25
  const n = Math.max(0, Math.floor(count));
9
- const wallets = [];
10
- for (let i = 0; i < n; i++) {
26
+ // 使用 Array.from 并行生成
27
+ return Array.from({ length: n }, () => {
11
28
  const w = Wallet.createRandom();
12
- wallets.push({ address: w.address, privateKey: w.privateKey });
13
- }
14
- return wallets;
29
+ return { address: w.address, privateKey: w.privateKey };
30
+ });
15
31
  }
16
32
  /**
17
- * 批量校验私钥数组是否合规。
18
- * 规则:
19
- * - 必须为 0x 开头的 32 字节十六进制串(64 十六进制字符)
20
- * - 能够被 ethers.Wallet 正常构造(包含椭圆曲线有效性)
21
- * 返回每个私钥的校验结果与可选地址/错误信息。
33
+ * 批量校验私钥(并行处理)
22
34
  */
23
35
  export function validatePrivateKeys(privateKeys) {
24
36
  if (!Array.isArray(privateKeys))
@@ -36,359 +48,114 @@ export function validatePrivateKeys(privateKeys) {
36
48
  }
37
49
  });
38
50
  }
39
- /**
40
- * 将任意形式的私钥规范化为 0x + 64 位十六进制。
41
- * 接受:
42
- * - 可含/不含 0x 前缀
43
- * - 奇数字符长度(自动左侧补 0)
44
- * - 少于 64 位的 hex(左侧补零至 64)
45
- * 拒绝:非 hex 字符或超过 64 位。
46
- */
47
51
  function normalizePrivateKey(input) {
48
52
  if (typeof input !== 'string')
49
53
  return null;
50
54
  let s = input.trim();
51
- // 去掉前导 0x/0X
52
55
  if (s.startsWith('0x') || s.startsWith('0X'))
53
56
  s = s.slice(2);
54
- if (s.length === 0)
55
- return null;
56
- // 仅允许 hex
57
- if (!/^[0-9a-fA-F]+$/.test(s))
57
+ if (s.length === 0 || !/^[0-9a-fA-F]+$/.test(s) || s.length > 64)
58
58
  return null;
59
- // 长度不能超过 64(32字节)
60
- if (s.length > 64)
61
- return null;
62
- // 若为奇数长度,左侧补 0
63
59
  if (s.length % 2 === 1)
64
60
  s = '0' + s;
65
- // 左侧补零到 64
66
- const padded = s.padStart(64, '0');
67
- const withPrefix = '0x' + padded;
68
- // 最终仍做一次严格校验
69
- if (!isHexString(withPrefix, 32))
70
- return null;
71
- return withPrefix;
61
+ const withPrefix = '0x' + s.padStart(64, '0');
62
+ return isHexString(withPrefix, 32) ? withPrefix : null;
72
63
  }
73
- // 简化版 ERC20 与 Multicall3 ABI
74
- const ERC20_ABI = [
75
- 'function balanceOf(address) view returns (uint256)',
76
- 'function decimals() view returns (uint8)'
77
- ];
78
- const MULTICALL3_ABI = [
79
- 'function aggregate((address target, bytes callData)[]) public returns (uint256 blockNumber, bytes[] returnData)',
80
- 'function getEthBalance(address addr) public returns (uint256 balance)'
81
- ];
64
+ // ============================================================================
65
+ // Multicall 核心逻辑(提取公共部分)
66
+ // ============================================================================
82
67
  /**
83
- * 使用 Multicall3 批量查询 ERC20 余额。
84
- * @param rpcUrl JSON-RPC 端点
85
- * @param multicall3 Multicall3 合约地址
86
- * @param token ERC20 代币地址
87
- * @param holders 地址数组
68
+ * 内部:批量查询多代币余额(统一逻辑)
88
69
  */
89
- // 常见链的 Multicall3 标准地址(ca11...ca11);未知链回退此地址
90
- const DEFAULT_MULTICALL3 = '0xca11bde05977b3631167028862be2a173976ca11';
91
- const MULTICALL3_BY_CHAIN = {
92
- 1: DEFAULT_MULTICALL3, // Ethereum
93
- 56: DEFAULT_MULTICALL3, // BSC Mainnet
94
- 97: DEFAULT_MULTICALL3, // BSC Testnet
95
- 8453: DEFAULT_MULTICALL3, // Base
96
- 42161: DEFAULT_MULTICALL3, // Arbitrum One
97
- 196: DEFAULT_MULTICALL3, // X Layer
98
- 2818: DEFAULT_MULTICALL3, // Morph
99
- 143: DEFAULT_MULTICALL3, // ✅ Monad
100
- };
101
- export async function getTokenBalancesWithMulticall(rpcUrl, a, b, c) {
102
- const provider = new JsonRpcProvider(rpcUrl);
103
- let multicallAddress = '';
104
- let token = '';
105
- let holders = [];
106
- // 新签名分支:rpcUrl, tokenAddresses[], holders[] (不传 chain)
107
- if (Array.isArray(a) && Array.isArray(b) && typeof c === 'undefined') {
108
- const tokenAddresses = a;
109
- holders = b;
110
- if (!holders?.length)
111
- return [];
112
- try {
113
- const net = await provider.getNetwork();
114
- multicallAddress = MULTICALL3_BY_CHAIN[Number(net.chainId)] || DEFAULT_MULTICALL3;
115
- }
116
- catch {
117
- multicallAddress = DEFAULT_MULTICALL3;
118
- }
119
- const erc20Iface = new Interface(ERC20_ABI);
120
- const multiIface = new Interface(MULTICALL3_ABI);
121
- const calls = [];
122
- // 原生余额
123
- for (const addr of holders) {
124
- calls.push({ target: multicallAddress, callData: multiIface.encodeFunctionData('getEthBalance', [addr]) });
125
- }
126
- // ERC20 余额
127
- for (const t of tokenAddresses) {
128
- for (const addr of holders) {
129
- calls.push({ target: t, callData: erc20Iface.encodeFunctionData('balanceOf', [addr]) });
130
- }
131
- }
132
- // ERC20 decimals(每个 token 一次)
133
- for (const t of tokenAddresses) {
134
- calls.push({ target: t, callData: erc20Iface.encodeFunctionData('decimals', []) });
135
- }
136
- const callData = multiIface.encodeFunctionData('aggregate', [calls]);
137
- const result = await provider.call({ to: multicallAddress, data: callData });
138
- const decodedAggregate = multiIface.decodeFunctionResult('aggregate', result);
139
- const returnData = decodedAggregate[1];
140
- const outputs = holders.map((addr) => ({ address: addr, native: '0', tokens: [], success: true }));
141
- // 解码原生余额
142
- for (let i = 0; i < holders.length; i++) {
143
- try {
144
- const decoded = multiIface.decodeFunctionResult('getEthBalance', returnData[i]);
145
- outputs[i].native = formatEther(decoded[0]);
146
- }
147
- catch {
148
- outputs[i].native = '0';
149
- outputs[i].success = false;
150
- }
151
- }
152
- // 解码 ERC20 余额
153
- let idx = holders.length;
154
- for (const t of tokenAddresses) {
155
- for (let i = 0; i < holders.length; i++) {
156
- try {
157
- const decoded = erc20Iface.decodeFunctionResult('balanceOf', returnData[idx]);
158
- outputs[i].tokens.push({ token: t, balance: '0' }); // 占位,稍后格式化
159
- outputs[i]._raw = outputs[i]._raw || {};
160
- outputs[i]._raw[t] = decoded[0];
161
- }
162
- catch {
163
- outputs[i].tokens.push({ token: t, balance: '0' });
164
- outputs[i].success = false;
165
- }
166
- idx++;
167
- }
168
- }
169
- // 读取 decimals
170
- const tokenToDecimals = {};
171
- for (const t of tokenAddresses) {
172
- try {
173
- const decoded = erc20Iface.decodeFunctionResult('decimals', returnData[idx]);
174
- tokenToDecimals[t] = Number(decoded[0]);
175
- }
176
- catch {
177
- tokenToDecimals[t] = 18; // 回退 18
178
- }
179
- idx++;
180
- }
181
- // 使用 decimals 将 _raw 格式化为字符串
182
- for (let i = 0; i < holders.length; i++) {
183
- const raw = outputs[i]._raw;
184
- if (!raw)
185
- continue;
186
- outputs[i].tokens = outputs[i].tokens.map(({ token }) => ({
187
- token,
188
- balance: formatUnits(raw[token] ?? 0n, tokenToDecimals[token] ?? 18)
189
- }));
190
- delete outputs[i]._raw;
191
- }
192
- return outputs;
70
+ async function _queryMultiTokenBalances(provider, multicallAddress, tokenAddresses, holders) {
71
+ if (!holders?.length)
72
+ return [];
73
+ const erc20Iface = new Interface(ERC20_ABI);
74
+ const multiIface = new Interface(MULTICALL3_ABI);
75
+ // 一次性构建所有调用
76
+ const calls = [];
77
+ // 1. 原生余额查询
78
+ for (const addr of holders) {
79
+ calls.push({ target: multicallAddress, callData: multiIface.encodeFunctionData('getEthBalance', [addr]) });
193
80
  }
194
- // 新签名分支:rpcUrl, chain, tokenAddresses[], holders[]
195
- if (Array.isArray(b) && Array.isArray(c)) {
196
- const chain = a;
197
- const tokenAddresses = b;
198
- holders = c;
199
- if (!holders?.length)
200
- return [];
201
- const chainId = CHAIN[chain]?.chainId;
202
- if (chainId && MULTICALL3_BY_CHAIN[Number(chainId)]) {
203
- multicallAddress = MULTICALL3_BY_CHAIN[Number(chainId)];
204
- }
205
- else {
206
- // 尝试从 provider 读取,失败回退默认
207
- try {
208
- const net = await provider.getNetwork();
209
- multicallAddress = MULTICALL3_BY_CHAIN[Number(net.chainId)] || DEFAULT_MULTICALL3;
210
- }
211
- catch {
212
- multicallAddress = DEFAULT_MULTICALL3;
213
- }
214
- }
215
- const erc20Iface = new Interface(ERC20_ABI);
216
- const multiIface = new Interface(MULTICALL3_ABI);
217
- const calls = [];
218
- // 原生余额(使用 Multicall3.getEthBalance)
81
+ // 2. ERC20 余额查询(批量)
82
+ for (const token of tokenAddresses) {
219
83
  for (const addr of holders) {
220
- calls.push({ target: multicallAddress, callData: multiIface.encodeFunctionData('getEthBalance', [addr]) });
84
+ calls.push({ target: token, callData: erc20Iface.encodeFunctionData('balanceOf', [addr]) });
221
85
  }
222
- // ERC20 余额
223
- for (const t of tokenAddresses) {
224
- for (const addr of holders) {
225
- calls.push({ target: t, callData: erc20Iface.encodeFunctionData('balanceOf', [addr]) });
226
- }
227
- }
228
- // ERC20 decimals(每个 token 一次)
229
- for (const t of tokenAddresses) {
230
- calls.push({ target: t, callData: erc20Iface.encodeFunctionData('decimals', []) });
231
- }
232
- const callData = multiIface.encodeFunctionData('aggregate', [calls]);
233
- const result = await provider.call({ to: multicallAddress, data: callData });
234
- const decodedAggregate = multiIface.decodeFunctionResult('aggregate', result);
235
- const returnData = decodedAggregate[1];
236
- const outputs = holders.map((addr) => ({ address: addr, native: '0', tokens: [], success: true }));
237
- // 解码原生余额
238
- for (let i = 0; i < holders.length; i++) {
239
- try {
240
- const decoded = multiIface.decodeFunctionResult('getEthBalance', returnData[i]);
241
- outputs[i].native = formatEther(decoded[0]);
242
- }
243
- catch {
244
- outputs[i].native = '0';
245
- outputs[i].success = false;
246
- }
247
- }
248
- // 解码 ERC20 余额
249
- let idx = holders.length;
250
- for (const t of tokenAddresses) {
251
- for (let i = 0; i < holders.length; i++) {
252
- try {
253
- const decoded = erc20Iface.decodeFunctionResult('balanceOf', returnData[idx]);
254
- outputs[i].tokens.push({ token: t, balance: '0' });
255
- outputs[i]._raw = outputs[i]._raw || {};
256
- outputs[i]._raw[t] = decoded[0];
257
- }
258
- catch {
259
- outputs[i].tokens.push({ token: t, balance: '0' });
260
- outputs[i].success = false;
261
- }
262
- idx++;
263
- }
264
- }
265
- // 读取 decimals
266
- const tokenToDecimals = {};
267
- for (const t of tokenAddresses) {
268
- try {
269
- const decoded = erc20Iface.decodeFunctionResult('decimals', returnData[idx]);
270
- tokenToDecimals[t] = Number(decoded[0]);
271
- }
272
- catch {
273
- tokenToDecimals[t] = 18;
274
- }
275
- idx++;
276
- }
277
- // 应用 decimals 格式化
278
- for (let i = 0; i < holders.length; i++) {
279
- const raw = outputs[i]._raw;
280
- if (!raw)
281
- continue;
282
- outputs[i].tokens = outputs[i].tokens.map(({ token }) => ({
283
- token,
284
- balance: formatUnits(raw[token] ?? 0n, tokenToDecimals[token] ?? 18)
285
- }));
286
- delete outputs[i]._raw;
287
- }
288
- return outputs;
289
86
  }
290
- // 新签名分支:rpcUrl, chainId(number), tokenAddresses[], holders[]
291
- if (typeof a === 'number' && Array.isArray(b) && Array.isArray(c)) {
292
- const chainId = a;
293
- const tokenAddresses = b;
294
- holders = c;
295
- if (!holders?.length)
296
- return [];
297
- multicallAddress = MULTICALL3_BY_CHAIN[Number(chainId)] || DEFAULT_MULTICALL3;
298
- const erc20Iface = new Interface(ERC20_ABI);
299
- const multiIface = new Interface(MULTICALL3_ABI);
300
- const calls = [];
301
- // 原生余额
302
- for (const addr of holders) {
303
- calls.push({ target: multicallAddress, callData: multiIface.encodeFunctionData('getEthBalance', [addr]) });
304
- }
305
- // ERC20 余额
306
- for (const t of tokenAddresses) {
307
- for (const addr of holders) {
308
- calls.push({ target: t, callData: erc20Iface.encodeFunctionData('balanceOf', [addr]) });
309
- }
87
+ // 3. ERC20 decimals 查询(每个 token 一次)
88
+ for (const token of tokenAddresses) {
89
+ calls.push({ target: token, callData: erc20Iface.encodeFunctionData('decimals', []) });
90
+ }
91
+ // 单次 multicall 获取所有数据
92
+ const callData = multiIface.encodeFunctionData('aggregate', [calls]);
93
+ const result = await provider.call({ to: multicallAddress, data: callData });
94
+ const [, returnData] = multiIface.decodeFunctionResult('aggregate', result);
95
+ // 并行解码结果
96
+ const outputs = holders.map((addr) => ({
97
+ address: addr,
98
+ native: '0',
99
+ tokens: [],
100
+ success: true
101
+ }));
102
+ // 解码原生余额
103
+ let idx = 0;
104
+ for (let i = 0; i < holders.length; i++, idx++) {
105
+ try {
106
+ const decoded = multiIface.decodeFunctionResult('getEthBalance', returnData[idx]);
107
+ outputs[i].native = formatEther(decoded[0]);
310
108
  }
311
- // ERC20 decimals(每个 token 一次)
312
- for (const t of tokenAddresses) {
313
- calls.push({ target: t, callData: erc20Iface.encodeFunctionData('decimals', []) });
109
+ catch {
110
+ outputs[i].native = '0';
111
+ outputs[i].success = false;
314
112
  }
315
- const callData = multiIface.encodeFunctionData('aggregate', [calls]);
316
- const result = await provider.call({ to: multicallAddress, data: callData });
317
- const decodedAggregate = multiIface.decodeFunctionResult('aggregate', result);
318
- const returnData = decodedAggregate[1];
319
- const outputs = holders.map((addr) => ({ address: addr, native: '0', tokens: [], success: true }));
320
- // 解码原生余额
321
- for (let i = 0; i < holders.length; i++) {
113
+ }
114
+ // 解码 ERC20 余额(先缓存原始值)
115
+ const rawBalances = new Map();
116
+ for (const token of tokenAddresses) {
117
+ for (let i = 0; i < holders.length; i++, idx++) {
322
118
  try {
323
- const decoded = multiIface.decodeFunctionResult('getEthBalance', returnData[i]);
324
- outputs[i].native = formatEther(decoded[0]);
119
+ const decoded = erc20Iface.decodeFunctionResult('balanceOf', returnData[idx]);
120
+ if (!rawBalances.has(i))
121
+ rawBalances.set(i, new Map());
122
+ rawBalances.get(i).set(token, decoded[0]);
325
123
  }
326
124
  catch {
327
- outputs[i].native = '0';
125
+ if (!rawBalances.has(i))
126
+ rawBalances.set(i, new Map());
127
+ rawBalances.get(i).set(token, 0n);
328
128
  outputs[i].success = false;
329
129
  }
330
130
  }
331
- // 解码 ERC20 余额(先缓存 raw,再按 decimals 格式化)
332
- let idx = holders.length;
333
- for (const t of tokenAddresses) {
334
- for (let i = 0; i < holders.length; i++) {
335
- try {
336
- const decoded = erc20Iface.decodeFunctionResult('balanceOf', returnData[idx]);
337
- outputs[i].tokens.push({ token: t, balance: '0' });
338
- outputs[i]._raw = outputs[i]._raw || {};
339
- outputs[i]._raw[t] = decoded[0];
340
- }
341
- catch {
342
- outputs[i].tokens.push({ token: t, balance: '0' });
343
- outputs[i].success = false;
344
- }
345
- idx++;
346
- }
347
- }
348
- // 读取 decimals
349
- const tokenToDecimals = {};
350
- for (const t of tokenAddresses) {
351
- try {
352
- const decoded = erc20Iface.decodeFunctionResult('decimals', returnData[idx]);
353
- tokenToDecimals[t] = Number(decoded[0]);
354
- }
355
- catch {
356
- tokenToDecimals[t] = 18;
357
- }
358
- idx++;
359
- }
360
- // 应用 decimals 格式化
361
- for (let i = 0; i < holders.length; i++) {
362
- const raw = outputs[i]._raw;
363
- if (!raw)
364
- continue;
365
- outputs[i].tokens = outputs[i].tokens.map(({ token }) => ({
366
- token,
367
- balance: formatUnits(raw[token] ?? 0n, tokenToDecimals[token] ?? 18)
368
- }));
369
- delete outputs[i]._raw;
370
- }
371
- return outputs;
372
131
  }
373
- // 旧签名分支 1:rpcUrl, token, holders
374
- if (Array.isArray(b) && typeof c === 'undefined') {
375
- token = a;
376
- holders = b;
132
+ // 解码 decimals
133
+ const tokenDecimals = new Map();
134
+ for (const token of tokenAddresses) {
377
135
  try {
378
- const net = await provider.getNetwork();
379
- multicallAddress = MULTICALL3_BY_CHAIN[Number(net.chainId)] || DEFAULT_MULTICALL3;
136
+ const decoded = erc20Iface.decodeFunctionResult('decimals', returnData[idx]);
137
+ tokenDecimals.set(token, Number(decoded[0]));
380
138
  }
381
139
  catch {
382
- multicallAddress = DEFAULT_MULTICALL3;
140
+ tokenDecimals.set(token, 18);
383
141
  }
142
+ idx++;
384
143
  }
385
- else {
386
- // 旧签名分支 2:rpcUrl, multicall3, token, holders
387
- multicallAddress = a;
388
- token = b;
389
- holders = c;
144
+ // ✅ 格式化最终结果
145
+ for (let i = 0; i < holders.length; i++) {
146
+ const raw = rawBalances.get(i);
147
+ outputs[i].tokens = tokenAddresses.map(token => ({
148
+ token,
149
+ balance: formatUnits(raw?.get(token) ?? 0n, tokenDecimals.get(token) ?? 18)
150
+ }));
390
151
  }
391
- if (!holders || holders.length === 0)
152
+ return outputs;
153
+ }
154
+ /**
155
+ * ✅ 内部:查询单代币余额(旧接口兼容)
156
+ */
157
+ async function _querySingleTokenBalances(provider, multicallAddress, token, holders) {
158
+ if (!holders?.length)
392
159
  return [];
393
160
  const erc20Iface = new Interface(ERC20_ABI);
394
161
  const multiIface = new Interface(MULTICALL3_ABI);
@@ -398,17 +165,62 @@ export async function getTokenBalancesWithMulticall(rpcUrl, a, b, c) {
398
165
  }));
399
166
  const callData = multiIface.encodeFunctionData('aggregate', [calls]);
400
167
  const result = await provider.call({ to: multicallAddress, data: callData });
401
- const decodedAggregate = multiIface.decodeFunctionResult('aggregate', result);
402
- const returnData = decodedAggregate[1];
403
- const outputs = [];
404
- for (let i = 0; i < holders.length; i++) {
168
+ const [, returnData] = multiIface.decodeFunctionResult('aggregate', result);
169
+ return holders.map((addr, i) => {
405
170
  try {
406
171
  const decoded = erc20Iface.decodeFunctionResult('balanceOf', returnData[i]);
407
- outputs.push({ address: holders[i], balance: decoded[0], success: true });
172
+ return { address: addr, balance: decoded[0], success: true };
408
173
  }
409
174
  catch {
410
- outputs.push({ address: holders[i], balance: 0n, success: false });
175
+ return { address: addr, balance: 0n, success: false };
411
176
  }
177
+ });
178
+ }
179
+ /**
180
+ * ✅ 获取 Multicall3 地址(自动检测链)
181
+ */
182
+ async function getMulticallAddress(provider, chainId) {
183
+ if (chainId && MULTICALL3_BY_CHAIN[chainId]) {
184
+ return MULTICALL3_BY_CHAIN[chainId];
412
185
  }
413
- return outputs;
186
+ try {
187
+ const net = await provider.getNetwork();
188
+ return MULTICALL3_BY_CHAIN[Number(net.chainId)] || DEFAULT_MULTICALL3;
189
+ }
190
+ catch {
191
+ return DEFAULT_MULTICALL3;
192
+ }
193
+ }
194
+ /**
195
+ * ✅ 统一实现(精简版,复用内部函数)
196
+ */
197
+ export async function getTokenBalancesWithMulticall(rpcUrl, a, b, c) {
198
+ const provider = new JsonRpcProvider(rpcUrl);
199
+ // ✅ 分支 1:rpcUrl, tokenAddresses[], holders[]
200
+ if (Array.isArray(a) && Array.isArray(b) && typeof c === 'undefined') {
201
+ const multicallAddress = await getMulticallAddress(provider);
202
+ return _queryMultiTokenBalances(provider, multicallAddress, a, b);
203
+ }
204
+ // ✅ 分支 2:rpcUrl, chain/chainId, tokenAddresses[], holders[]
205
+ if (Array.isArray(b) && Array.isArray(c)) {
206
+ let chainId;
207
+ if (typeof a === 'number') {
208
+ chainId = a;
209
+ }
210
+ else if (typeof a === 'string') {
211
+ chainId = CHAIN[a]?.chainId;
212
+ }
213
+ const multicallAddress = await getMulticallAddress(provider, chainId);
214
+ return _queryMultiTokenBalances(provider, multicallAddress, b, c);
215
+ }
216
+ // ✅ 分支 3:rpcUrl, token, holders(旧接口)
217
+ if (typeof a === 'string' && Array.isArray(b) && typeof c === 'undefined') {
218
+ const multicallAddress = await getMulticallAddress(provider);
219
+ return _querySingleTokenBalances(provider, multicallAddress, a, b);
220
+ }
221
+ // ✅ 分支 4:rpcUrl, multicall3, token, holders(旧接口兼容)
222
+ if (typeof a === 'string' && typeof b === 'string' && Array.isArray(c)) {
223
+ return _querySingleTokenBalances(provider, a, b, c);
224
+ }
225
+ return [];
414
226
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.24",
3
+ "version": "1.4.26",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",