four-flap-meme-sdk 1.4.25 → 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.
- package/dist/abis/common.d.ts +85 -0
- package/dist/abis/common.js +242 -0
- package/dist/abis/index.d.ts +1 -0
- package/dist/abis/index.js +2 -0
- package/dist/contracts/tm-bundle-merkle/approve-tokenmanager.js +1 -6
- package/dist/contracts/tm-bundle-merkle/core.js +9 -16
- package/dist/contracts/tm-bundle-merkle/internal.js +6 -8
- package/dist/contracts/tm-bundle-merkle/pancake-proxy.d.ts +12 -6
- package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +224 -166
- package/dist/contracts/tm-bundle-merkle/private.js +6 -19
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +2 -7
- package/dist/contracts/tm-bundle-merkle/swap-internal.d.ts +1 -1
- package/dist/contracts/tm-bundle-merkle/swap-internal.js +9 -2
- package/dist/contracts/tm-bundle-merkle/swap.js +1 -3
- package/dist/contracts/tm-bundle-merkle/types.d.ts +20 -0
- package/dist/contracts/tm-bundle-merkle/utils.js +2 -3
- package/dist/dex/direct-router.d.ts +2 -1
- package/dist/dex/direct-router.js +25 -140
- package/dist/flap/constants.d.ts +2 -1
- package/dist/flap/constants.js +2 -1
- package/dist/flap/meta.js +6 -4
- package/dist/flap/portal-bundle-merkle/config.js +6 -12
- package/dist/flap/portal-bundle-merkle/core.js +8 -11
- package/dist/flap/portal-bundle-merkle/pancake-proxy.d.ts +12 -10
- package/dist/flap/portal-bundle-merkle/pancake-proxy.js +307 -370
- package/dist/flap/portal-bundle-merkle/private.js +1 -1
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +12 -30
- package/dist/flap/portal-bundle-merkle/swap.js +13 -26
- package/dist/flap/portal-bundle-merkle/types.d.ts +22 -2
- package/dist/flap/portal-bundle-merkle/utils.js +11 -16
- package/dist/index.d.ts +3 -2
- package/dist/index.js +9 -2
- package/dist/pancake/bundle-buy-first.js +56 -38
- package/dist/pancake/bundle-swap.js +114 -61
- package/dist/utils/bundle-helpers.d.ts +28 -0
- package/dist/utils/bundle-helpers.js +64 -0
- package/dist/utils/constants.d.ts +23 -1
- package/dist/utils/constants.js +37 -7
- package/dist/utils/erc20.js +17 -25
- package/dist/utils/lp-inspect.js +9 -20
- package/dist/utils/private-sale.js +1 -2
- package/dist/utils/quote-helpers.js +3 -29
- package/dist/utils/swap-helpers.js +1 -6
- package/dist/utils/wallet.d.ts +8 -13
- package/dist/utils/wallet.js +154 -342
- 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
|
-
|
|
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
|
-
|
|
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(授权专用)
|
package/dist/utils/wallet.d.ts
CHANGED
|
@@ -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[]>;
|
package/dist/utils/wallet.js
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
26
|
+
// ✅ 使用 Array.from 并行生成
|
|
27
|
+
return Array.from({ length: n }, () => {
|
|
11
28
|
const w = Wallet.createRandom();
|
|
12
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
*
|
|
84
|
-
* @param rpcUrl JSON-RPC 端点
|
|
85
|
-
* @param multicall3 Multicall3 合约地址
|
|
86
|
-
* @param token ERC20 代币地址
|
|
87
|
-
* @param holders 地址数组
|
|
68
|
+
* ✅ 内部:批量查询多代币余额(统一逻辑)
|
|
88
69
|
*/
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
//
|
|
195
|
-
|
|
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:
|
|
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
|
-
//
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
109
|
+
catch {
|
|
110
|
+
outputs[i].native = '0';
|
|
111
|
+
outputs[i].success = false;
|
|
314
112
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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 =
|
|
324
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
holders = b;
|
|
132
|
+
// 解码 decimals
|
|
133
|
+
const tokenDecimals = new Map();
|
|
134
|
+
for (const token of tokenAddresses) {
|
|
377
135
|
try {
|
|
378
|
-
const
|
|
379
|
-
|
|
136
|
+
const decoded = erc20Iface.decodeFunctionResult('decimals', returnData[idx]);
|
|
137
|
+
tokenDecimals.set(token, Number(decoded[0]));
|
|
380
138
|
}
|
|
381
139
|
catch {
|
|
382
|
-
|
|
140
|
+
tokenDecimals.set(token, 18);
|
|
383
141
|
}
|
|
142
|
+
idx++;
|
|
384
143
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
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
|
|
402
|
-
|
|
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
|
-
|
|
172
|
+
return { address: addr, balance: decoded[0], success: true };
|
|
408
173
|
}
|
|
409
174
|
catch {
|
|
410
|
-
|
|
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
|
-
|
|
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
|
}
|