four-flap-meme-sdk 1.5.55 → 1.5.56
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/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/sol/constants.d.ts +150 -0
- package/dist/sol/constants.js +188 -0
- package/dist/sol/dex/blockrazor/client.d.ts +51 -0
- package/dist/sol/dex/blockrazor/client.js +96 -0
- package/dist/sol/dex/blockrazor/constants.d.ts +34 -0
- package/dist/sol/dex/blockrazor/constants.js +55 -0
- package/dist/sol/dex/blockrazor/geyser.d.ts +128 -0
- package/dist/sol/dex/blockrazor/geyser.js +530 -0
- package/dist/sol/dex/blockrazor/index.d.ts +18 -0
- package/dist/sol/dex/blockrazor/index.js +23 -0
- package/dist/sol/dex/blockrazor/send.d.ts +135 -0
- package/dist/sol/dex/blockrazor/send.js +254 -0
- package/dist/sol/dex/blockrazor/types.d.ts +191 -0
- package/dist/sol/dex/blockrazor/types.js +5 -0
- package/dist/sol/dex/index.d.ts +10 -0
- package/dist/sol/dex/index.js +16 -0
- package/dist/sol/dex/jup/client.d.ts +33 -0
- package/dist/sol/dex/jup/client.js +110 -0
- package/dist/sol/dex/jup/index.d.ts +16 -0
- package/dist/sol/dex/jup/index.js +148 -0
- package/dist/sol/dex/jup/legacy.d.ts +623 -0
- package/dist/sol/dex/jup/legacy.js +416 -0
- package/dist/sol/dex/jup/lend.d.ts +640 -0
- package/dist/sol/dex/jup/lend.js +603 -0
- package/dist/sol/dex/jup/portfolio.d.ts +362 -0
- package/dist/sol/dex/jup/portfolio.js +367 -0
- package/dist/sol/dex/jup/price.d.ts +173 -0
- package/dist/sol/dex/jup/price.js +220 -0
- package/dist/sol/dex/jup/recurring.d.ts +437 -0
- package/dist/sol/dex/jup/recurring.js +320 -0
- package/dist/sol/dex/jup/send.d.ts +282 -0
- package/dist/sol/dex/jup/send.js +295 -0
- package/dist/sol/dex/jup/studio.d.ts +457 -0
- package/dist/sol/dex/jup/studio.js +488 -0
- package/dist/sol/dex/jup/tokens.d.ts +767 -0
- package/dist/sol/dex/jup/tokens.js +697 -0
- package/dist/sol/dex/jup/trigger.d.ts +511 -0
- package/dist/sol/dex/jup/trigger.js +397 -0
- package/dist/sol/dex/jup/types.d.ts +433 -0
- package/dist/sol/dex/jup/types.js +5 -0
- package/dist/sol/dex/jup/ultra.d.ts +646 -0
- package/dist/sol/dex/jup/ultra.js +853 -0
- package/dist/sol/dex/meteora/client.d.ts +76 -0
- package/dist/sol/dex/meteora/client.js +219 -0
- package/dist/sol/dex/meteora/damm-v1-bundle.d.ts +61 -0
- package/dist/sol/dex/meteora/damm-v1-bundle.js +112 -0
- package/dist/sol/dex/meteora/damm-v1.d.ts +118 -0
- package/dist/sol/dex/meteora/damm-v1.js +315 -0
- package/dist/sol/dex/meteora/damm-v2-bundle.d.ts +82 -0
- package/dist/sol/dex/meteora/damm-v2-bundle.js +242 -0
- package/dist/sol/dex/meteora/damm-v2.d.ts +172 -0
- package/dist/sol/dex/meteora/damm-v2.js +632 -0
- package/dist/sol/dex/meteora/dbc-bundle.d.ts +123 -0
- package/dist/sol/dex/meteora/dbc-bundle.js +304 -0
- package/dist/sol/dex/meteora/dbc.d.ts +192 -0
- package/dist/sol/dex/meteora/dbc.js +619 -0
- package/dist/sol/dex/meteora/dlmm-bundle.d.ts +39 -0
- package/dist/sol/dex/meteora/dlmm-bundle.js +189 -0
- package/dist/sol/dex/meteora/dlmm.d.ts +157 -0
- package/dist/sol/dex/meteora/dlmm.js +671 -0
- package/dist/sol/dex/meteora/index.d.ts +25 -0
- package/dist/sol/dex/meteora/index.js +65 -0
- package/dist/sol/dex/meteora/types.d.ts +787 -0
- package/dist/sol/dex/meteora/types.js +110 -0
- package/dist/sol/dex/orca/index.d.ts +10 -0
- package/dist/sol/dex/orca/index.js +16 -0
- package/dist/sol/dex/orca/orca-bundle.d.ts +41 -0
- package/dist/sol/dex/orca/orca-bundle.js +173 -0
- package/dist/sol/dex/orca/orca.d.ts +65 -0
- package/dist/sol/dex/orca/orca.js +474 -0
- package/dist/sol/dex/orca/types.d.ts +263 -0
- package/dist/sol/dex/orca/types.js +38 -0
- package/dist/sol/dex/orca/wavebreak-bundle.d.ts +34 -0
- package/dist/sol/dex/orca/wavebreak-bundle.js +198 -0
- package/dist/sol/dex/orca/wavebreak-types.d.ts +227 -0
- package/dist/sol/dex/orca/wavebreak-types.js +23 -0
- package/dist/sol/dex/orca/wavebreak.d.ts +78 -0
- package/dist/sol/dex/orca/wavebreak.js +497 -0
- package/dist/sol/dex/pump/index.d.ts +9 -0
- package/dist/sol/dex/pump/index.js +14 -0
- package/dist/sol/dex/pump/pump-bundle.d.ts +92 -0
- package/dist/sol/dex/pump/pump-bundle.js +383 -0
- package/dist/sol/dex/pump/pump-swap-bundle.d.ts +103 -0
- package/dist/sol/dex/pump/pump-swap-bundle.js +380 -0
- package/dist/sol/dex/pump/pump-swap.d.ts +46 -0
- package/dist/sol/dex/pump/pump-swap.js +199 -0
- package/dist/sol/dex/pump/pump.d.ts +35 -0
- package/dist/sol/dex/pump/pump.js +352 -0
- package/dist/sol/dex/pump/types.d.ts +215 -0
- package/dist/sol/dex/pump/types.js +5 -0
- package/dist/sol/dex/raydium/index.d.ts +8 -0
- package/dist/sol/dex/raydium/index.js +12 -0
- package/dist/sol/dex/raydium/launchlab.d.ts +68 -0
- package/dist/sol/dex/raydium/launchlab.js +210 -0
- package/dist/sol/dex/raydium/raydium-bundle.d.ts +64 -0
- package/dist/sol/dex/raydium/raydium-bundle.js +324 -0
- package/dist/sol/dex/raydium/raydium.d.ts +40 -0
- package/dist/sol/dex/raydium/raydium.js +366 -0
- package/dist/sol/dex/raydium/types.d.ts +240 -0
- package/dist/sol/dex/raydium/types.js +5 -0
- package/dist/sol/index.d.ts +11 -0
- package/dist/sol/index.js +18 -0
- package/dist/sol/jito/bundle.d.ts +90 -0
- package/dist/sol/jito/bundle.js +263 -0
- package/dist/sol/jito/index.d.ts +7 -0
- package/dist/sol/jito/index.js +7 -0
- package/dist/sol/jito/tip.d.ts +51 -0
- package/dist/sol/jito/tip.js +83 -0
- package/dist/sol/jito/types.d.ts +100 -0
- package/dist/sol/jito/types.js +5 -0
- package/dist/sol/nozomi/client.d.ts +63 -0
- package/dist/sol/nozomi/client.js +222 -0
- package/dist/sol/nozomi/index.d.ts +8 -0
- package/dist/sol/nozomi/index.js +8 -0
- package/dist/sol/nozomi/tip.d.ts +50 -0
- package/dist/sol/nozomi/tip.js +80 -0
- package/dist/sol/nozomi/types.d.ts +96 -0
- package/dist/sol/nozomi/types.js +5 -0
- package/dist/sol/token/create-complete.d.ts +115 -0
- package/dist/sol/token/create-complete.js +235 -0
- package/dist/sol/token/create-token.d.ts +57 -0
- package/dist/sol/token/create-token.js +230 -0
- package/dist/sol/token/index.d.ts +9 -0
- package/dist/sol/token/index.js +14 -0
- package/dist/sol/token/metadata-upload.d.ts +86 -0
- package/dist/sol/token/metadata-upload.js +173 -0
- package/dist/sol/token/metadata.d.ts +92 -0
- package/dist/sol/token/metadata.js +274 -0
- package/dist/sol/token/types.d.ts +153 -0
- package/dist/sol/token/types.js +5 -0
- package/dist/sol/types.d.ts +176 -0
- package/dist/sol/types.js +7 -0
- package/dist/sol/utils/balance.d.ts +160 -0
- package/dist/sol/utils/balance.js +638 -0
- package/dist/sol/utils/connection.d.ts +78 -0
- package/dist/sol/utils/connection.js +168 -0
- package/dist/sol/utils/index.d.ts +9 -0
- package/dist/sol/utils/index.js +9 -0
- package/dist/sol/utils/lp-inspect.d.ts +75 -0
- package/dist/sol/utils/lp-inspect.js +235 -0
- package/dist/sol/utils/transfer.d.ts +196 -0
- package/dist/sol/utils/transfer.js +307 -0
- package/dist/sol/utils/wallet.d.ts +107 -0
- package/dist/sol/utils/wallet.js +210 -0
- package/package.json +44 -5
- package/README.zh-CN.pdf +0 -0
- package/dist/flap/portal-bundle-merkle/encryption.d.ts +0 -16
- package/dist/flap/portal-bundle-merkle/encryption.js +0 -146
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solana 余额查询功能
|
|
3
|
+
* 使用批量 RPC 调用优化,减少限流风险
|
|
4
|
+
* @module sol/utils/balance
|
|
5
|
+
*/
|
|
6
|
+
import { PublicKey, } from '@solana/web3.js';
|
|
7
|
+
import { getAccount, getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, } from '@solana/spl-token';
|
|
8
|
+
import { lamportsToSol } from '../constants.js';
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Decimals 缓存(减少重复查询)
|
|
11
|
+
// ============================================================================
|
|
12
|
+
const decimalsCache = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* 获取代币精度(带缓存)
|
|
15
|
+
*/
|
|
16
|
+
async function getTokenDecimals(connection, mint) {
|
|
17
|
+
const cached = decimalsCache.get(mint);
|
|
18
|
+
if (cached !== undefined)
|
|
19
|
+
return cached;
|
|
20
|
+
try {
|
|
21
|
+
const mintPubkey = new PublicKey(mint);
|
|
22
|
+
const mintInfo = await connection.getParsedAccountInfo(mintPubkey);
|
|
23
|
+
if (mintInfo.value?.data && 'parsed' in mintInfo.value.data) {
|
|
24
|
+
const decimals = mintInfo.value.data.parsed.info.decimals;
|
|
25
|
+
decimalsCache.set(mint, decimals);
|
|
26
|
+
return decimals;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// 默认值
|
|
31
|
+
}
|
|
32
|
+
return 9;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 批量获取代币精度(一次 RPC 调用)
|
|
36
|
+
*/
|
|
37
|
+
async function batchGetTokenDecimals(connection, mints) {
|
|
38
|
+
const result = new Map();
|
|
39
|
+
const uncachedMints = [];
|
|
40
|
+
const uncachedIndexes = [];
|
|
41
|
+
// 检查缓存
|
|
42
|
+
for (let i = 0; i < mints.length; i++) {
|
|
43
|
+
const mint = mints[i];
|
|
44
|
+
const cached = decimalsCache.get(mint);
|
|
45
|
+
if (cached !== undefined) {
|
|
46
|
+
result.set(mint, cached);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
uncachedMints.push(mint);
|
|
50
|
+
uncachedIndexes.push(i);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (uncachedMints.length === 0)
|
|
54
|
+
return result;
|
|
55
|
+
// 批量查询未缓存的
|
|
56
|
+
try {
|
|
57
|
+
const mintPubkeys = uncachedMints.map(m => new PublicKey(m));
|
|
58
|
+
const mintInfos = await connection.getMultipleParsedAccounts(mintPubkeys);
|
|
59
|
+
for (let i = 0; i < uncachedMints.length; i++) {
|
|
60
|
+
const mint = uncachedMints[i];
|
|
61
|
+
const info = mintInfos.value[i];
|
|
62
|
+
let decimals = 9;
|
|
63
|
+
if (info?.data && 'parsed' in info.data) {
|
|
64
|
+
decimals = info.data.parsed.info.decimals;
|
|
65
|
+
}
|
|
66
|
+
decimalsCache.set(mint, decimals);
|
|
67
|
+
result.set(mint, decimals);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// 使用默认值
|
|
72
|
+
for (const mint of uncachedMints) {
|
|
73
|
+
result.set(mint, 9);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
// ==================== SOL 余额 ====================
|
|
79
|
+
/**
|
|
80
|
+
* 查询单个地址的 SOL 余额
|
|
81
|
+
* @param connection Solana 连接
|
|
82
|
+
* @param address 钱包地址
|
|
83
|
+
*/
|
|
84
|
+
export async function getSolBalance(connection, address) {
|
|
85
|
+
try {
|
|
86
|
+
const publicKey = new PublicKey(address);
|
|
87
|
+
const balance = await connection.getBalance(publicKey);
|
|
88
|
+
const lamports = BigInt(balance);
|
|
89
|
+
return {
|
|
90
|
+
address,
|
|
91
|
+
lamports,
|
|
92
|
+
sol: lamportsToSol(lamports),
|
|
93
|
+
success: true,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return {
|
|
98
|
+
address,
|
|
99
|
+
lamports: BigInt(0),
|
|
100
|
+
sol: 0,
|
|
101
|
+
success: false,
|
|
102
|
+
error: error.message,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 批量查询 SOL 余额
|
|
108
|
+
* @param connection Solana 连接
|
|
109
|
+
* @param addresses 钱包地址列表
|
|
110
|
+
* @param batchSize 每批查询数量(默认 100)
|
|
111
|
+
*/
|
|
112
|
+
export async function batchGetSolBalance(connection, addresses, batchSize = 100) {
|
|
113
|
+
const results = [];
|
|
114
|
+
let successCount = 0;
|
|
115
|
+
let failedCount = 0;
|
|
116
|
+
// 分批查询
|
|
117
|
+
for (let i = 0; i < addresses.length; i += batchSize) {
|
|
118
|
+
const batch = addresses.slice(i, i + batchSize);
|
|
119
|
+
// 使用 getMultipleAccountsInfo 批量查询
|
|
120
|
+
try {
|
|
121
|
+
const publicKeys = batch.map(addr => new PublicKey(addr));
|
|
122
|
+
const accounts = await connection.getMultipleAccountsInfo(publicKeys);
|
|
123
|
+
for (let j = 0; j < batch.length; j++) {
|
|
124
|
+
const account = accounts[j];
|
|
125
|
+
const address = batch[j];
|
|
126
|
+
if (account) {
|
|
127
|
+
const lamports = BigInt(account.lamports);
|
|
128
|
+
results.push({
|
|
129
|
+
address,
|
|
130
|
+
lamports,
|
|
131
|
+
sol: lamportsToSol(lamports),
|
|
132
|
+
success: true,
|
|
133
|
+
});
|
|
134
|
+
successCount++;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// 账户不存在,余额为 0
|
|
138
|
+
results.push({
|
|
139
|
+
address,
|
|
140
|
+
lamports: BigInt(0),
|
|
141
|
+
sol: 0,
|
|
142
|
+
success: true,
|
|
143
|
+
});
|
|
144
|
+
successCount++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
// 如果批量查询失败,逐个查询
|
|
150
|
+
for (const address of batch) {
|
|
151
|
+
const result = await getSolBalance(connection, address);
|
|
152
|
+
results.push(result);
|
|
153
|
+
if (result.success) {
|
|
154
|
+
successCount++;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
failedCount++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// 批次间延迟
|
|
162
|
+
if (i + batchSize < addresses.length) {
|
|
163
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
results,
|
|
168
|
+
successCount,
|
|
169
|
+
failedCount,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// ==================== 代币余额 ====================
|
|
173
|
+
/**
|
|
174
|
+
* 查询单个地址的代币余额
|
|
175
|
+
* @param connection Solana 连接
|
|
176
|
+
* @param walletAddress 钱包地址
|
|
177
|
+
* @param tokenMint 代币 Mint 地址
|
|
178
|
+
* @param useToken2022 是否使用 Token-2022 程序(默认 false)
|
|
179
|
+
*/
|
|
180
|
+
export async function getTokenBalance(connection, walletAddress, tokenMint, useToken2022 = false) {
|
|
181
|
+
try {
|
|
182
|
+
const wallet = new PublicKey(walletAddress);
|
|
183
|
+
const mint = new PublicKey(tokenMint);
|
|
184
|
+
const programId = useToken2022 ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
|
|
185
|
+
// 使用同步方法获取 ATA 地址(避免 RPC 调用)
|
|
186
|
+
const ata = getAssociatedTokenAddressSync(mint, wallet, false, programId);
|
|
187
|
+
// 获取 decimals(带缓存)
|
|
188
|
+
const decimals = await getTokenDecimals(connection, tokenMint);
|
|
189
|
+
try {
|
|
190
|
+
const account = await getAccount(connection, ata, 'confirmed', programId);
|
|
191
|
+
const balance = BigInt(account.amount.toString());
|
|
192
|
+
return {
|
|
193
|
+
address: walletAddress,
|
|
194
|
+
tokenAddress: tokenMint,
|
|
195
|
+
balance,
|
|
196
|
+
decimals,
|
|
197
|
+
uiBalance: Number(balance) / Math.pow(10, decimals),
|
|
198
|
+
success: true,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// ATA 不存在,余额为 0
|
|
203
|
+
return {
|
|
204
|
+
address: walletAddress,
|
|
205
|
+
tokenAddress: tokenMint,
|
|
206
|
+
balance: BigInt(0),
|
|
207
|
+
decimals,
|
|
208
|
+
uiBalance: 0,
|
|
209
|
+
success: true,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
return {
|
|
215
|
+
address: walletAddress,
|
|
216
|
+
tokenAddress: tokenMint,
|
|
217
|
+
balance: BigInt(0),
|
|
218
|
+
decimals: 0,
|
|
219
|
+
uiBalance: 0,
|
|
220
|
+
success: false,
|
|
221
|
+
error: error.message,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* 批量查询代币余额(多个钱包,同一代币)
|
|
227
|
+
* 使用 getMultipleAccountsInfo 优化,单次 RPC 调用查询多个账户
|
|
228
|
+
*
|
|
229
|
+
* @param connection Solana 连接
|
|
230
|
+
* @param walletAddresses 钱包地址列表
|
|
231
|
+
* @param tokenMint 代币 Mint 地址
|
|
232
|
+
* @param useToken2022 是否使用 Token-2022 程序
|
|
233
|
+
*/
|
|
234
|
+
export async function batchGetTokenBalance(connection, walletAddresses, tokenMint, useToken2022 = false) {
|
|
235
|
+
const results = [];
|
|
236
|
+
let successCount = 0;
|
|
237
|
+
let failedCount = 0;
|
|
238
|
+
// 获取 decimals(带缓存,只查一次)
|
|
239
|
+
const decimals = await getTokenDecimals(connection, tokenMint);
|
|
240
|
+
const programId = useToken2022 ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
|
|
241
|
+
const mint = new PublicKey(tokenMint);
|
|
242
|
+
// 使用同步方法批量获取 ATA 地址(无 RPC 调用)
|
|
243
|
+
const atas = walletAddresses.map((addr) => {
|
|
244
|
+
const wallet = new PublicKey(addr);
|
|
245
|
+
return getAssociatedTokenAddressSync(mint, wallet, false, programId);
|
|
246
|
+
});
|
|
247
|
+
// 批量查询账户(Solana 单次最多 100 个账户)
|
|
248
|
+
const batchSize = 100;
|
|
249
|
+
for (let i = 0; i < atas.length; i += batchSize) {
|
|
250
|
+
const batchAtas = atas.slice(i, i + batchSize);
|
|
251
|
+
const batchAddresses = walletAddresses.slice(i, i + batchSize);
|
|
252
|
+
try {
|
|
253
|
+
const accounts = await connection.getMultipleAccountsInfo(batchAtas);
|
|
254
|
+
for (let j = 0; j < batchAtas.length; j++) {
|
|
255
|
+
const account = accounts[j];
|
|
256
|
+
const address = batchAddresses[j];
|
|
257
|
+
if (account && account.data.length > 0) {
|
|
258
|
+
// 解析代币账户数据
|
|
259
|
+
// Token 账户结构:前 64 字节是 mint 和 owner,接下来 8 字节是 amount
|
|
260
|
+
const data = account.data;
|
|
261
|
+
let balance = BigInt(0);
|
|
262
|
+
if (data.length >= 72) {
|
|
263
|
+
// 读取 amount(offset 64,8 字节,little-endian)
|
|
264
|
+
const amountBytes = data.slice(64, 72);
|
|
265
|
+
for (let k = 7; k >= 0; k--) {
|
|
266
|
+
balance = (balance << BigInt(8)) + BigInt(amountBytes[k]);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
results.push({
|
|
270
|
+
address,
|
|
271
|
+
tokenAddress: tokenMint,
|
|
272
|
+
balance,
|
|
273
|
+
decimals,
|
|
274
|
+
uiBalance: Number(balance) / Math.pow(10, decimals),
|
|
275
|
+
success: true,
|
|
276
|
+
});
|
|
277
|
+
successCount++;
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// 账户不存在
|
|
281
|
+
results.push({
|
|
282
|
+
address,
|
|
283
|
+
tokenAddress: tokenMint,
|
|
284
|
+
balance: BigInt(0),
|
|
285
|
+
decimals,
|
|
286
|
+
uiBalance: 0,
|
|
287
|
+
success: true,
|
|
288
|
+
});
|
|
289
|
+
successCount++;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
// 批量失败,标记所有为失败
|
|
295
|
+
for (const address of batchAddresses) {
|
|
296
|
+
results.push({
|
|
297
|
+
address,
|
|
298
|
+
tokenAddress: tokenMint,
|
|
299
|
+
balance: BigInt(0),
|
|
300
|
+
decimals,
|
|
301
|
+
uiBalance: 0,
|
|
302
|
+
success: false,
|
|
303
|
+
error: error.message,
|
|
304
|
+
});
|
|
305
|
+
failedCount++;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// 批次间延迟(减少限流)
|
|
309
|
+
if (i + batchSize < atas.length) {
|
|
310
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
results,
|
|
315
|
+
successCount,
|
|
316
|
+
failedCount,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* 批量查询多个代币余额(单个钱包,多个代币)
|
|
321
|
+
*
|
|
322
|
+
* @param connection Solana 连接
|
|
323
|
+
* @param walletAddress 钱包地址
|
|
324
|
+
* @param tokenMints 代币 Mint 地址列表
|
|
325
|
+
* @param useToken2022 是否使用 Token-2022 程序
|
|
326
|
+
*/
|
|
327
|
+
export async function batchGetMultiTokenBalance(connection, walletAddress, tokenMints, useToken2022 = false) {
|
|
328
|
+
const results = [];
|
|
329
|
+
let successCount = 0;
|
|
330
|
+
let failedCount = 0;
|
|
331
|
+
const programId = useToken2022 ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
|
|
332
|
+
const wallet = new PublicKey(walletAddress);
|
|
333
|
+
// 批量获取 decimals(一次 RPC 调用)
|
|
334
|
+
const decimalsMap = await batchGetTokenDecimals(connection, tokenMints);
|
|
335
|
+
// 使用同步方法批量获取 ATA 地址(无 RPC 调用)
|
|
336
|
+
const atas = tokenMints.map((mintStr) => {
|
|
337
|
+
const mint = new PublicKey(mintStr);
|
|
338
|
+
return getAssociatedTokenAddressSync(mint, wallet, false, programId);
|
|
339
|
+
});
|
|
340
|
+
// 批量查询账户
|
|
341
|
+
const batchSize = 100;
|
|
342
|
+
for (let i = 0; i < atas.length; i += batchSize) {
|
|
343
|
+
const batchAtas = atas.slice(i, i + batchSize);
|
|
344
|
+
const batchMints = tokenMints.slice(i, i + batchSize);
|
|
345
|
+
try {
|
|
346
|
+
const accounts = await connection.getMultipleAccountsInfo(batchAtas);
|
|
347
|
+
for (let j = 0; j < batchAtas.length; j++) {
|
|
348
|
+
const account = accounts[j];
|
|
349
|
+
const tokenMint = batchMints[j];
|
|
350
|
+
const decimals = decimalsMap.get(tokenMint) || 9;
|
|
351
|
+
if (account && account.data.length > 0) {
|
|
352
|
+
const data = account.data;
|
|
353
|
+
let balance = BigInt(0);
|
|
354
|
+
if (data.length >= 72) {
|
|
355
|
+
const amountBytes = data.slice(64, 72);
|
|
356
|
+
for (let k = 7; k >= 0; k--) {
|
|
357
|
+
balance = (balance << BigInt(8)) + BigInt(amountBytes[k]);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
results.push({
|
|
361
|
+
address: walletAddress,
|
|
362
|
+
tokenAddress: tokenMint,
|
|
363
|
+
balance,
|
|
364
|
+
decimals,
|
|
365
|
+
uiBalance: Number(balance) / Math.pow(10, decimals),
|
|
366
|
+
success: true,
|
|
367
|
+
});
|
|
368
|
+
successCount++;
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
results.push({
|
|
372
|
+
address: walletAddress,
|
|
373
|
+
tokenAddress: tokenMint,
|
|
374
|
+
balance: BigInt(0),
|
|
375
|
+
decimals,
|
|
376
|
+
uiBalance: 0,
|
|
377
|
+
success: true,
|
|
378
|
+
});
|
|
379
|
+
successCount++;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
for (const tokenMint of batchMints) {
|
|
385
|
+
results.push({
|
|
386
|
+
address: walletAddress,
|
|
387
|
+
tokenAddress: tokenMint,
|
|
388
|
+
balance: BigInt(0),
|
|
389
|
+
decimals: decimalsMap.get(tokenMint) || 9,
|
|
390
|
+
uiBalance: 0,
|
|
391
|
+
success: false,
|
|
392
|
+
error: error.message,
|
|
393
|
+
});
|
|
394
|
+
failedCount++;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (i + batchSize < atas.length) {
|
|
398
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
results,
|
|
403
|
+
successCount,
|
|
404
|
+
failedCount,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* 获取钱包持有的所有代币
|
|
409
|
+
* @param connection Solana 连接
|
|
410
|
+
* @param walletAddress 钱包地址
|
|
411
|
+
* @param includeToken2022 是否包含 Token-2022 代币
|
|
412
|
+
*/
|
|
413
|
+
export async function getAllTokenBalances(connection, walletAddress, includeToken2022 = true) {
|
|
414
|
+
const wallet = new PublicKey(walletAddress);
|
|
415
|
+
// 获取 SOL 余额
|
|
416
|
+
const solResult = await getSolBalance(connection, walletAddress);
|
|
417
|
+
const tokens = [];
|
|
418
|
+
// 查询 SPL Token 账户
|
|
419
|
+
try {
|
|
420
|
+
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(wallet, {
|
|
421
|
+
programId: TOKEN_PROGRAM_ID,
|
|
422
|
+
});
|
|
423
|
+
for (const { account } of tokenAccounts.value) {
|
|
424
|
+
const data = account.data;
|
|
425
|
+
if ('parsed' in data) {
|
|
426
|
+
const info = data.parsed.info;
|
|
427
|
+
const balance = BigInt(info.tokenAmount.amount);
|
|
428
|
+
if (balance > 0) {
|
|
429
|
+
tokens.push({
|
|
430
|
+
mint: info.mint,
|
|
431
|
+
balance,
|
|
432
|
+
decimals: info.tokenAmount.decimals,
|
|
433
|
+
uiBalance: info.tokenAmount.uiAmount || 0,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
// 忽略错误
|
|
441
|
+
}
|
|
442
|
+
// 查询 Token-2022 账户
|
|
443
|
+
if (includeToken2022) {
|
|
444
|
+
try {
|
|
445
|
+
const token2022Accounts = await connection.getParsedTokenAccountsByOwner(wallet, {
|
|
446
|
+
programId: TOKEN_2022_PROGRAM_ID,
|
|
447
|
+
});
|
|
448
|
+
for (const { account } of token2022Accounts.value) {
|
|
449
|
+
const data = account.data;
|
|
450
|
+
if ('parsed' in data) {
|
|
451
|
+
const info = data.parsed.info;
|
|
452
|
+
const balance = BigInt(info.tokenAmount.amount);
|
|
453
|
+
if (balance > 0) {
|
|
454
|
+
tokens.push({
|
|
455
|
+
mint: info.mint,
|
|
456
|
+
balance,
|
|
457
|
+
decimals: info.tokenAmount.decimals,
|
|
458
|
+
uiBalance: info.tokenAmount.uiAmount || 0,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
// 忽略错误
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
address: walletAddress,
|
|
470
|
+
sol: {
|
|
471
|
+
lamports: solResult.lamports,
|
|
472
|
+
balance: solResult.sol,
|
|
473
|
+
},
|
|
474
|
+
tokens,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* 批量获取钱包完整余额信息
|
|
479
|
+
* @param connection Solana 连接
|
|
480
|
+
* @param walletAddresses 钱包地址列表
|
|
481
|
+
*/
|
|
482
|
+
export async function batchGetAllBalances(connection, walletAddresses) {
|
|
483
|
+
const results = [];
|
|
484
|
+
// 并行查询,但限制并发数
|
|
485
|
+
const concurrency = 5;
|
|
486
|
+
for (let i = 0; i < walletAddresses.length; i += concurrency) {
|
|
487
|
+
const batch = walletAddresses.slice(i, i + concurrency);
|
|
488
|
+
const batchResults = await Promise.all(batch.map(addr => getAllTokenBalances(connection, addr)));
|
|
489
|
+
results.push(...batchResults);
|
|
490
|
+
// 批次间延迟
|
|
491
|
+
if (i + concurrency < walletAddresses.length) {
|
|
492
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return results;
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* 批量查询多个钱包的多个代币余额(矩阵查询)
|
|
499
|
+
* 优化 RPC 调用次数,适合大批量查询
|
|
500
|
+
*
|
|
501
|
+
* @param connection Solana 连接
|
|
502
|
+
* @param walletAddresses 钱包地址列表
|
|
503
|
+
* @param tokenMints 代币 Mint 地址列表
|
|
504
|
+
* @param useToken2022 是否使用 Token-2022 程序
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* ```typescript
|
|
508
|
+
* const result = await batchGetMatrixBalance(
|
|
509
|
+
* connection,
|
|
510
|
+
* ['wallet1', 'wallet2', 'wallet3'],
|
|
511
|
+
* ['tokenA', 'tokenB'],
|
|
512
|
+
* )
|
|
513
|
+
*
|
|
514
|
+
* // 获取某个钱包的某个代币余额
|
|
515
|
+
* const balance = result.byWallet.get('wallet1')?.get('tokenA')
|
|
516
|
+
* ```
|
|
517
|
+
*/
|
|
518
|
+
export async function batchGetMatrixBalance(connection, walletAddresses, tokenMints, useToken2022 = false) {
|
|
519
|
+
const byWallet = new Map();
|
|
520
|
+
const byToken = new Map();
|
|
521
|
+
const results = [];
|
|
522
|
+
let successCount = 0;
|
|
523
|
+
let failedCount = 0;
|
|
524
|
+
let rpcCalls = 0;
|
|
525
|
+
const programId = useToken2022 ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
|
|
526
|
+
// 1. 批量获取所有代币的 decimals(一次 RPC 调用)
|
|
527
|
+
const decimalsMap = await batchGetTokenDecimals(connection, tokenMints);
|
|
528
|
+
rpcCalls++;
|
|
529
|
+
// 2. 构建所有 ATA 地址(无 RPC 调用)
|
|
530
|
+
const ataInfos = [];
|
|
531
|
+
for (const walletAddress of walletAddresses) {
|
|
532
|
+
const wallet = new PublicKey(walletAddress);
|
|
533
|
+
byWallet.set(walletAddress, new Map());
|
|
534
|
+
for (const mintStr of tokenMints) {
|
|
535
|
+
const mint = new PublicKey(mintStr);
|
|
536
|
+
const ata = getAssociatedTokenAddressSync(mint, wallet, false, programId);
|
|
537
|
+
const decimals = decimalsMap.get(mintStr) || 9;
|
|
538
|
+
ataInfos.push({ ata, wallet: walletAddress, mint: mintStr, decimals });
|
|
539
|
+
if (!byToken.has(mintStr)) {
|
|
540
|
+
byToken.set(mintStr, new Map());
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
// 3. 批量查询所有 ATA 账户
|
|
545
|
+
const batchSize = 100;
|
|
546
|
+
for (let i = 0; i < ataInfos.length; i += batchSize) {
|
|
547
|
+
const batch = ataInfos.slice(i, i + batchSize);
|
|
548
|
+
const ataPubkeys = batch.map(info => info.ata);
|
|
549
|
+
try {
|
|
550
|
+
const accounts = await connection.getMultipleAccountsInfo(ataPubkeys);
|
|
551
|
+
rpcCalls++;
|
|
552
|
+
for (let j = 0; j < batch.length; j++) {
|
|
553
|
+
const info = batch[j];
|
|
554
|
+
const account = accounts[j];
|
|
555
|
+
let balance = BigInt(0);
|
|
556
|
+
if (account && account.data.length >= 72) {
|
|
557
|
+
const amountBytes = account.data.slice(64, 72);
|
|
558
|
+
for (let k = 7; k >= 0; k--) {
|
|
559
|
+
balance = (balance << BigInt(8)) + BigInt(amountBytes[k]);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const result = {
|
|
563
|
+
address: info.wallet,
|
|
564
|
+
tokenAddress: info.mint,
|
|
565
|
+
balance,
|
|
566
|
+
decimals: info.decimals,
|
|
567
|
+
uiBalance: Number(balance) / Math.pow(10, info.decimals),
|
|
568
|
+
success: true,
|
|
569
|
+
};
|
|
570
|
+
results.push(result);
|
|
571
|
+
byWallet.get(info.wallet).set(info.mint, result);
|
|
572
|
+
byToken.get(info.mint).set(info.wallet, result);
|
|
573
|
+
successCount++;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
for (const info of batch) {
|
|
578
|
+
const result = {
|
|
579
|
+
address: info.wallet,
|
|
580
|
+
tokenAddress: info.mint,
|
|
581
|
+
balance: BigInt(0),
|
|
582
|
+
decimals: info.decimals,
|
|
583
|
+
uiBalance: 0,
|
|
584
|
+
success: false,
|
|
585
|
+
error: error.message,
|
|
586
|
+
};
|
|
587
|
+
results.push(result);
|
|
588
|
+
byWallet.get(info.wallet).set(info.mint, result);
|
|
589
|
+
byToken.get(info.mint).set(info.wallet, result);
|
|
590
|
+
failedCount++;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
// 批次间短暂延迟
|
|
594
|
+
if (i + batchSize < ataInfos.length) {
|
|
595
|
+
await new Promise(resolve => setTimeout(resolve, 30));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return {
|
|
599
|
+
byWallet,
|
|
600
|
+
byToken,
|
|
601
|
+
results,
|
|
602
|
+
stats: {
|
|
603
|
+
totalQueries: walletAddresses.length * tokenMints.length,
|
|
604
|
+
successCount,
|
|
605
|
+
failedCount,
|
|
606
|
+
rpcCalls,
|
|
607
|
+
},
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
// ============================================================================
|
|
611
|
+
// 缓存管理
|
|
612
|
+
// ============================================================================
|
|
613
|
+
/**
|
|
614
|
+
* 清除 decimals 缓存
|
|
615
|
+
* @param mint 可选,指定代币地址;不传则清除全部
|
|
616
|
+
*/
|
|
617
|
+
export function clearDecimalsCache(mint) {
|
|
618
|
+
if (mint) {
|
|
619
|
+
decimalsCache.delete(mint);
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
decimalsCache.clear();
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* 获取缓存的 decimals 数量
|
|
627
|
+
*/
|
|
628
|
+
export function getDecimalsCacheSize() {
|
|
629
|
+
return decimalsCache.size;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* 预热 decimals 缓存
|
|
633
|
+
* @param connection Solana 连接
|
|
634
|
+
* @param mints 代币 Mint 地址列表
|
|
635
|
+
*/
|
|
636
|
+
export async function warmupDecimalsCache(connection, mints) {
|
|
637
|
+
await batchGetTokenDecimals(connection, mints);
|
|
638
|
+
}
|