@w3lib/solana-batch-call 0.0.0-stage → 0.1.0

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 (49) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +169 -2
  4. package/dist/address.d.ts +14 -0
  5. package/dist/address.d.ts.map +1 -0
  6. package/dist/address.js +81 -0
  7. package/dist/address.js.map +1 -0
  8. package/dist/client.d.ts +96 -0
  9. package/dist/client.d.ts.map +1 -0
  10. package/dist/client.js +575 -0
  11. package/dist/client.js.map +1 -0
  12. package/dist/constants.d.ts +21 -0
  13. package/dist/constants.d.ts.map +1 -0
  14. package/dist/constants.js +35 -0
  15. package/dist/constants.js.map +1 -0
  16. package/dist/index.d.ts +9 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +9 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/layout.d.ts +55 -0
  21. package/dist/layout.d.ts.map +1 -0
  22. package/dist/layout.js +177 -0
  23. package/dist/layout.js.map +1 -0
  24. package/dist/rpc.d.ts +59 -0
  25. package/dist/rpc.d.ts.map +1 -0
  26. package/dist/rpc.js +295 -0
  27. package/dist/rpc.js.map +1 -0
  28. package/dist/shortcuts.d.ts +60 -0
  29. package/dist/shortcuts.d.ts.map +1 -0
  30. package/dist/shortcuts.js +136 -0
  31. package/dist/shortcuts.js.map +1 -0
  32. package/dist/source.d.ts +35 -0
  33. package/dist/source.d.ts.map +1 -0
  34. package/dist/source.js +126 -0
  35. package/dist/source.js.map +1 -0
  36. package/dist/types.d.ts +104 -0
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/types.js +2 -0
  39. package/dist/types.js.map +1 -0
  40. package/package.json +56 -3
  41. package/src/address.ts +92 -0
  42. package/src/client.ts +753 -0
  43. package/src/constants.ts +41 -0
  44. package/src/index.ts +54 -0
  45. package/src/layout.ts +238 -0
  46. package/src/rpc.ts +349 -0
  47. package/src/shortcuts.ts +173 -0
  48. package/src/source.ts +151 -0
  49. package/src/types.ts +108 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ 首个版本:
6
+
7
+ - 批量余额(SOL / SPL Token / Token-2022),指定代币时用 ATA + `getMultipleAccounts`,免费节点可用;扫描模式统计全部代币账户
8
+ - 代币详情(字段可选),Metaplex 与 Token-2022 TokenMetadata 扩展
9
+ - 资产列表 `getOwnerTokens`:持有人的全部代币,带 name / symbol,多账户合计,元数据分批读取、失败不影响余额
10
+ - NFT 元数据、持有人、某地址持有的全部 NFT
11
+ - JSON-RPC 自动合并、批量降级、429 重试,多节点故障切换,按创世区块哈希校验网络
12
+ - 支持 URL、web3.js Connection、自定义传输
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 L
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,170 @@
1
- # Temporary Holding Version
1
+ # @w3lib/solana-batch-call
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Solana 批量读取库:余额(SOL / SPL Token / Token-2022)、代币详情、NFT。不依赖 Solana SDK,直接发 JSON-RPC。
4
+
5
+ ```bash
6
+ pnpm add @w3lib/solana-batch-call
7
+ ```
8
+
9
+ - **一次请求查一批**:账户读取用 `getMultipleAccounts`(自动去重、按 100 个一组拆分、最多同时 3 个请求);同一 tick 内发起的 RPC 调用合并成一个 JSON-RPC 批量请求
10
+ - **免费节点也能查余额**:指定代币时在本地推导关联代币账户(ATA)地址,和 SOL 账户、mint 账户一起一次读完,不需要 `getTokenAccountsByOwner` 这类索引方法
11
+ - **Token-2022**:余额、decimals、TokenMetadata 扩展里的 name / symbol / uri 都支持
12
+ - **多节点故障切换**:限频、403、需要 API Key、不支持某方法等节点问题自动换下一个节点;节点不支持批量请求或限制批量大小时自动降级
13
+ - **网络校验**:按创世区块哈希确认节点所在网络,不会把 devnet 的数据当成 mainnet 的返回
14
+ - 结果字段都是字符串 / 数字 / 布尔 / null,可以直接 `JSON.stringify`
15
+
16
+ ## 快速上手
17
+
18
+ ```ts
19
+ import { NATIVE_MINT, getBalances, getNftOwners, getNfts, getOwnerNfts, getOwnerTokens, getSolBalances, getTokens } from '@w3lib/solana-batch-call'
20
+
21
+ // 资产列表:持有人拥有的全部代币(SOL 在第一位,带 name / symbol;需要支持索引方法的节点)
22
+ await getOwnerTokens(owner, { provider: 'https://my-rpc.example' })
23
+ // [
24
+ // { token: NATIVE_MINT, native: true, name: 'Solana', symbol: 'SOL', formatted: '1.5', accounts: 1, … },
25
+ // { token: USDC, native: false, name: 'USD Coin', symbol: 'USDC', formatted: '1235', accounts: 2, … }, // 2 个代币账户合计
26
+ // ...
27
+ // ]
28
+
29
+ // 余额:SOL + 代币,带 decimals 换算(不传 provider 时使用内置的 mainnet 公共节点)
30
+ await getBalances(owner, [NATIVE_MINT, USDC, PYUSD], { symbol: true })
31
+ // [
32
+ // { token: NATIVE_MINT, native: true, balance: '1500000000', decimals: 9, formatted: '1.5', tokenProgram: null, symbol: 'SOL', success: true },
33
+ // { token: USDC, native: false, balance: '1234500000', decimals: 6, formatted: '1234.5', tokenProgram: 'Tokenkeg…', symbol: 'USDC', success: true },
34
+ // ...
35
+ // ]
36
+
37
+ // 多个地址的 SOL 余额
38
+ await getSolBalances([addressA, addressB])
39
+
40
+ // 代币详情,字段可选:name / symbol / uri / decimals / supply / tokenProgram / mintAuthority / freezeAuthority(默认 name、symbol、decimals)
41
+ await getTokens([USDC, PYUSD], { fields: ['name', 'symbol', 'decimals', 'supply'] })
42
+ // [{ address: USDC, native: false, name: 'USD Coin', symbol: 'USDC', decimals: 6, supply: '…', supplyFormatted: '…', success: true }, ...]
43
+
44
+ // NFT
45
+ await getNfts([mint]) // 名称、uri、所属集合(是否已认证)、版税、标准、创作者
46
+ await getNftOwners([mint]) // 持有人
47
+ await getOwnerNfts(owner) // 某地址持有的全部 NFT(需要支持索引方法的节点)
48
+ ```
49
+
50
+ | 函数 | SolanaClient 方法 | 说明 |
51
+ | --- | --- | --- |
52
+ | `getBalances(owner, mints?, opts)` | `balances` | SOL + SPL Token + Token-2022 余额;`symbol: true` 额外返回 symbol;不传 mints 返回全部持仓 |
53
+ | `getOwnerTokens(owner, opts)` | `ownerTokens` | 持有人的全部代币(资产列表),带 name / symbol;默认不含 NFT 和余额为 0 的账户 |
54
+ | `getSolBalances(addresses, opts)` | `solBalances` | 多个地址的 SOL 余额 |
55
+ | `getTokens(mints, opts)` | `tokens` | 代币详情,`fields` 选择字段,结果类型随之收窄 |
56
+ | `getNfts(mints, opts)` | `nfts` | NFT 元数据(Metaplex;Token-2022 NFT 取扩展) |
57
+ | `getNftOwners(mints, opts)` | `nftOwners` | NFT 持有人 |
58
+ | `getOwnerNfts(owner, opts)` | `ownerNfts` | 某地址持有的全部 NFT |
59
+ | — | `accounts(addresses)` | 批量读原始账户(`AccountInfo \| null`) |
60
+ | — | `request(method, params)` | 直接发 JSON-RPC(同样参与合并与故障切换) |
61
+
62
+ ## 资产列表
63
+
64
+ `getOwnerTokens(owner)` 扫描持有人的全部代币账户(Token + Token-2022),同一代币的多个账户合计,返回:
65
+
66
+ - 第一项是 SOL,之后是 SPL Token、Token-2022 的代币(各自按节点返回的顺序;库里没有价格信息,不按价值排序)
67
+ - 每项带 `accounts`(代币账户数量);默认还带 `name` / `symbol` / `metadataStatus`(`ok` 读到了、`missing` 确认没有元数据、`failed` 节点问题没读到),`metadata: false` 时不读元数据、不返回这三个字段
68
+ - Wrapped SOL 代币账户单独列出(`token` 同样是 `So111…112`,`native: false`),与原生 SOL 那一项区分
69
+ - 默认不含 NFT 和余额为 0 的代币账户(已清空的 ATA),用 `includeNfts` / `includeZero` 打开;`includeNative: false` 不返回 SOL。NFT 的判断按 mint 汇总:精度 0、合计数量 1(与 `getOwnerNfts` 一致)
70
+
71
+ 持仓很多时读元数据较慢:读取账户最多同时 3 个请求、每个 100 个地址,以避免限频(实测 4000 个代币在官方节点上约 80 秒,不读元数据约 4 秒)。可以先用 `metadata: false` 拿到列表,再对需要展示的代币调用 `getTokens`。元数据读取遇到限频等节点问题时不再继续请求,对应代币的 `metadataStatus` 为 `failed`、`name` / `symbol` 为 `null`,余额列表照常返回;之后可以对这些代币单独重查。
72
+
73
+ 需要支持 `getTokenAccountsByOwner` 的节点,内置的免费公共节点大多不支持。
74
+
75
+ ## 余额的两种模式
76
+
77
+ | | 指定 mints(默认) | 扫描(不传 mints,或 `scan: true`) |
78
+ | --- | --- | --- |
79
+ | 实现 | 本地推导 ATA + 一次 `getMultipleAccounts` | `getTokenAccountsByOwner`(Token / Token-2022 各一次) |
80
+ | 统计范围 | 只统计 **ATA** 里的余额 | 该地址的**全部**代币账户,同一代币多个账户合计 |
81
+ | 免费公共节点 | 可用 | 大多不支持,需要自己的节点(Helius、QuickNode、Triton 等) |
82
+
83
+ 普通钱包的代币都在 ATA 里,用默认模式即可。交易所、托管类地址常有非 ATA 的代币账户,默认模式会少算,需要 `scan: true`。
84
+
85
+ 其他规则:
86
+
87
+ - **主币**:`So111…112`(`NATIVE_MINT`,Wrapped SOL 的 mint)和 `11111…1`(System Program)按原生 SOL 查询(lamports,9 位精度);此时 Wrapped SOL 代币账户里的余额不单独列出。可用 `nativeMints` 配置,传 `[SYSTEM_PROGRAM_ID]` 时 `So111…112` 按普通代币(Wrapped SOL)查询
88
+ - **缓存**:decimals、所属程序永久缓存;name、symbol 来自可修改的元数据,缓存 1 小时(确认没有元数据的代币同样缓存,不会每次重查)。按网络共享(各函数共用);自定义节点且没指定 `cluster` 时只在该客户端内缓存。最多 5 万个代币,超出淘汰最早的
89
+ - **大数**:lamports 等 u64 超过 2⁵³ 时不会丢精度
90
+ - **失败**:mint 不存在、地址非法等只让该项 `success: false`;节点不可用等整个请求失败时才抛错
91
+
92
+ ## 节点
93
+
94
+ ```ts
95
+ import { SolanaClient } from '@w3lib/solana-batch-call'
96
+
97
+ new SolanaClient() // 内置 mainnet 公共节点
98
+ new SolanaClient(undefined, { cluster: 'devnet' }) // 内置 devnet 节点
99
+ new SolanaClient('https://my-rpc.example') // 自己的节点
100
+ new SolanaClient(['https://rpc-1.example', 'https://rpc-2.example']) // 主节点 + 备用节点
101
+ new SolanaClient(connection) // @solana/web3.js 的 Connection(使用其 rpcEndpoint)
102
+ new SolanaClient({ request: (method, params) => myRpc(method, params) }) // 自定义传输
103
+
104
+ // 独立函数的节点参数相同
105
+ await getBalances(owner, mints, { provider: 'https://my-rpc.example', cluster: 'mainnet' })
106
+ ```
107
+
108
+ **内置公共节点**(`DEFAULT_RPC_URLS`):mainnet 依次是 publicnode 和官方节点。2026-10 实测:
109
+
110
+ - publicnode:放行跨域、支持批量请求,但不支持 `getTokenAccountsByOwner`、`getTokenLargestAccounts` 等需要索引的方法
111
+ - 官方节点 `api.mainnet-beta.solana.com`:对带浏览器 `Origin` 的请求返回 403,只能在服务端使用;限频较严
112
+
113
+ 所以扫描持仓、`getNftOwners`、`getOwnerNfts` 在内置节点上可能不可用,生产环境建议传入自己的节点。
114
+
115
+ **传输层行为**(`HttpRpc`):
116
+
117
+ - 收集窗口(`batchWait`,默认 0 即同一 tick)内的调用合并成一个批量请求,最多 `maxBatchSize`(默认 20)条
118
+ - 节点不支持批量请求时自动改为逐条并发;返回 “Batch of more than N” 时按 N 缩小批量;请求体过大(413)时批量减半。批量请求被限频或拒绝访问时不会降级(降级只会让限频更严重)
119
+ - HTTP 429 和 JSON-RPC 错误码 429 都会退避重试(`retries`,默认 2;传入多个自定义节点时为 0,直接换节点;内置节点保留重试),批量里只重试被限频的那几条
120
+ - 单次请求超时 `timeout`,默认 10 秒
121
+
122
+ **故障切换**(多节点时):网络错误、超时、限频、403、需要 API Key、请求被拦截等节点问题换下一个节点,出错节点冷却 `cooldown`(默认 30 秒);参数错误等确定性错误直接抛出(`RpcError`,`isNodeFault(err)` 可判断)。
123
+
124
+ **网络校验**:传了 `cluster`,或传了多个节点时,会用 `getGenesisHash` 确认每个节点所在的网络,不一致时报 `NetworkMismatchError`(多节点时换下一个节点)。校验与请求并行发出,结果按节点缓存,每个节点只多一次请求;内置节点不校验。`await client.getCluster()` 返回节点所在网络。
125
+
126
+ ## 配置
127
+
128
+ ```ts
129
+ new SolanaClient(provider?, {
130
+ cluster: 'mainnet', // 'mainnet' | 'devnet' | 'testnet'
131
+ commitment: 'confirmed', // 'processed' | 'confirmed' | 'finalized'
132
+ nativeMints: [NATIVE_MINT, SYSTEM_PROGRAM_ID],
133
+ timeout: 10_000,
134
+ maxBatchSize: 20,
135
+ batchWait: 0,
136
+ batch: true, // false:不合并成批量请求
137
+ retries: 2,
138
+ cooldown: 30_000,
139
+ headers: { 'x-api-key': '…' },
140
+ fetch: customFetch, // 需要代理等场景
141
+ })
142
+ ```
143
+
144
+ 在 Node 里通过代理访问节点时:Node 自带的 `fetch` 默认不读 `HTTPS_PROXY`,Node 24+ 可以设置 `NODE_USE_ENV_PROXY=1`,或者通过 `fetch` 选项传入自定义实现。
145
+
146
+ ## 工具函数
147
+
148
+ ```ts
149
+ import {
150
+ getAssociatedTokenAddress, // ATA 地址
151
+ getMetadataAddress, // Metaplex 元数据账户地址
152
+ findProgramAddress, // 通用 PDA 推导(与 web3.js findProgramAddressSync 相同)
153
+ isAddress,
154
+ parseMint, parseTokenAccount, parseMetaplexMetadata, // 账户数据解析
155
+ formatAmount,
156
+ TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, NATIVE_MINT, GENESIS_HASHES,
157
+ } from '@w3lib/solana-batch-call'
158
+ ```
159
+
160
+ ## 开发
161
+
162
+ ```bash
163
+ pnpm test # 单元测试(模拟节点,不需要网络)
164
+ pnpm test:live # 连真实节点(SOLANA_RPC 指定支持索引方法的节点;需要代理时加 NODE_USE_ENV_PROXY=1)
165
+ pnpm build
166
+ ```
167
+
168
+ ## License
169
+
170
+ MIT
@@ -0,0 +1,14 @@
1
+ /** 是否是合法的 Solana 地址(base58,32 字节) */
2
+ export declare function isAddress(value: unknown): value is string;
3
+ export declare function decodeAddress(address: string): Uint8Array;
4
+ export declare function encodeAddress(bytes: Uint8Array): string;
5
+ /**
6
+ * 推导 PDA(与 web3.js PublicKey.findProgramAddressSync 相同),返回地址。
7
+ * seeds 每项不超过 32 字节。
8
+ */
9
+ export declare function findProgramAddress(seeds: Uint8Array[], programId: string): string;
10
+ /** 关联代币账户(ATA)地址 */
11
+ export declare function getAssociatedTokenAddress(owner: string, mint: string, tokenProgram: string): string;
12
+ /** Metaplex Token Metadata 账户地址 */
13
+ export declare function getMetadataAddress(mint: string): string;
14
+ //# sourceMappingURL=address.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address.d.ts","sourceRoot":"","sources":["../src/address.ts"],"names":[],"mappings":"AAMA,qCAAqC;AACrC,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CASzD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAMzD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAEvD;AA2BD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAmBjF;AAED,oBAAoB;AACpB,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEnG;AAED,mCAAmC;AACnC,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKvD"}
@@ -0,0 +1,81 @@
1
+ import { ed25519 } from '@noble/curves/ed25519.js';
2
+ import { sha256 } from '@noble/hashes/sha2.js';
3
+ import { base58 } from '@scure/base';
4
+ import { ASSOCIATED_TOKEN_PROGRAM_ID, METADATA_PROGRAM_ID } from './constants.js';
5
+ /** 是否是合法的 Solana 地址(base58,32 字节) */
6
+ export function isAddress(value) {
7
+ if (typeof value !== 'string' || value.length < 32 || value.length > 44) {
8
+ return false;
9
+ }
10
+ try {
11
+ return base58.decode(value).length === 32;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ export function decodeAddress(address) {
18
+ const bytes = base58.decode(address);
19
+ if (bytes.length !== 32) {
20
+ throw new Error(`Invalid Solana address: ${address}`);
21
+ }
22
+ return bytes;
23
+ }
24
+ export function encodeAddress(bytes) {
25
+ return base58.encode(bytes);
26
+ }
27
+ /** 32 字节是否在 ed25519 曲线上(PDA 必须不在曲线上) */
28
+ function isOnCurve(bytes) {
29
+ try {
30
+ ed25519.Point.fromBytes(bytes);
31
+ return true;
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
37
+ const PDA_MARKER = new TextEncoder().encode('ProgramDerivedAddress');
38
+ function concat(parts) {
39
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
40
+ let offset = 0;
41
+ for (const part of parts) {
42
+ out.set(part, offset);
43
+ offset += part.length;
44
+ }
45
+ return out;
46
+ }
47
+ // PDA 推导是纯计算但要做多次 sha256 + 曲线判断,结果缓存
48
+ const pdaCache = new Map();
49
+ /**
50
+ * 推导 PDA(与 web3.js PublicKey.findProgramAddressSync 相同),返回地址。
51
+ * seeds 每项不超过 32 字节。
52
+ */
53
+ export function findProgramAddress(seeds, programId) {
54
+ const key = `${programId}:${seeds.map((s) => encodeAddress(s)).join(',')}`;
55
+ const cached = pdaCache.get(key);
56
+ if (cached) {
57
+ return cached;
58
+ }
59
+ const program = decodeAddress(programId);
60
+ for (let bump = 255; bump >= 0; bump--) {
61
+ const hash = sha256(concat([...seeds, new Uint8Array([bump]), program, PDA_MARKER]));
62
+ if (!isOnCurve(hash)) {
63
+ const address = encodeAddress(hash);
64
+ if (pdaCache.size > 10_000) {
65
+ pdaCache.clear();
66
+ }
67
+ pdaCache.set(key, address);
68
+ return address;
69
+ }
70
+ }
71
+ throw new Error('Unable to find a viable program address bump seed');
72
+ }
73
+ /** 关联代币账户(ATA)地址 */
74
+ export function getAssociatedTokenAddress(owner, mint, tokenProgram) {
75
+ return findProgramAddress([decodeAddress(owner), decodeAddress(tokenProgram), decodeAddress(mint)], ASSOCIATED_TOKEN_PROGRAM_ID);
76
+ }
77
+ /** Metaplex Token Metadata 账户地址 */
78
+ export function getMetadataAddress(mint) {
79
+ return findProgramAddress([new TextEncoder().encode('metadata'), decodeAddress(METADATA_PROGRAM_ID), decodeAddress(mint)], METADATA_PROGRAM_ID);
80
+ }
81
+ //# sourceMappingURL=address.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address.js","sourceRoot":"","sources":["../src/address.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import { type Cluster } from './constants.js';
2
+ import { type AccountInfo } from './layout.js';
3
+ import { type RpcTransport } from './rpc.js';
4
+ import { type RpcSource, type SourceOptions } from './source.js';
5
+ import { type Commitment, type DefaultTokenField, type NftDetails, type NftOwner, type OwnedToken, type SolBalance, type TokenBalance, type TokenDetails, type TokenField } from './types.js';
6
+ export interface ClientConfig extends SourceOptions {
7
+ /** 网络。不传 provider 时用于选择内置节点(默认 mainnet);传了 provider 时用于校验节点所在网络 */
8
+ cluster?: Cluster;
9
+ /** 默认 'confirmed' */
10
+ commitment?: Commitment;
11
+ /** 视为原生 SOL 的 mint,默认 [So111…112(Wrapped SOL), 11111…1(System Program)] */
12
+ nativeMints?: readonly string[];
13
+ }
14
+ export interface BalancesOptions {
15
+ /** 同时返回 symbol(代币查一次后缓存,主币为 SOL)。默认 false */
16
+ symbol?: boolean;
17
+ /**
18
+ * 用 getTokenAccountsByOwner 扫描持有人的全部代币账户(含非 ATA 账户、同一代币的多个账户合计)。
19
+ * 不传 mints 时总是扫描。注意:免费公共节点大多不支持这个方法,需要自己的节点。
20
+ */
21
+ scan?: boolean;
22
+ }
23
+ export interface OwnerTokensOptions {
24
+ /** 返回 name / symbol(代币查一次后缓存)。默认 true */
25
+ metadata?: boolean;
26
+ /** 第一项返回 SOL。默认 true */
27
+ includeNative?: boolean;
28
+ /** 包含余额为 0 的代币账户(如已清空但未关闭的 ATA)。默认 false */
29
+ includeZero?: boolean;
30
+ /** 包含 NFT(精度 0 且数量 1)。默认 false */
31
+ includeNfts?: boolean;
32
+ }
33
+ export interface TokensOptions<F extends TokenField = DefaultTokenField> {
34
+ /** 要返回的字段,默认 ['name', 'symbol', 'decimals'] */
35
+ fields?: readonly F[];
36
+ }
37
+ /** 测试用:清空代币信息缓存 */
38
+ export declare function resetTokenMetaCache(): void;
39
+ /**
40
+ * Solana 批量读取客户端。
41
+ *
42
+ * ```ts
43
+ * const sol = new SolanaClient() // 内置公共节点(mainnet)
44
+ * const sol = new SolanaClient('https://my-rpc.example') // 自己的节点
45
+ * const sol = new SolanaClient([connection, 'https://backup…']) // web3.js Connection + 备用节点
46
+ *
47
+ * await sol.balances(owner, [NATIVE_MINT, USDC])
48
+ * await sol.tokens([USDC, PYUSD], { fields: ['name', 'symbol', 'decimals', 'supply'] })
49
+ * ```
50
+ *
51
+ * 同一 tick 内发起的 RPC 调用会合并成一个 JSON-RPC 批量请求;账户读取按 100 个一组用 getMultipleAccounts。
52
+ */
53
+ export declare class SolanaClient {
54
+ #private;
55
+ readonly transport: RpcTransport;
56
+ constructor(provider?: RpcSource | readonly RpcSource[], config?: ClientConfig);
57
+ /** 节点所在网络(按创世区块哈希识别);不是 mainnet / devnet / testnet 时为 null */
58
+ getCluster(): Promise<Cluster | null>;
59
+ /** 直接发 JSON-RPC 调用(同样参与自动合并与故障切换) */
60
+ request<T = unknown>(method: string, params?: readonly unknown[]): Promise<T>;
61
+ /**
62
+ * 批量读取账户(getMultipleAccounts,按 100 个一组,去重),结果与 addresses 一一对应。
63
+ * 账户不存在或地址非法时为 null。
64
+ */
65
+ accounts(addresses: readonly string[]): Promise<(AccountInfo | null)[]>;
66
+ /** 批量查多个地址的 SOL 余额(一次 getMultipleAccounts;账户不存在视为 0) */
67
+ solBalances(addresses: readonly string[]): Promise<SolBalance[]>;
68
+ /**
69
+ * 批量查余额(主币 SOL + SPL Token + Token-2022),返回原始余额、decimals 和换算后的数值。
70
+ *
71
+ * - 传 mints:本地推导 ATA,与主币账户、mint 账户一起用 getMultipleAccounts 一次读完,免费节点也能用
72
+ * - 不传 mints(或 scan: true):用 getTokenAccountsByOwner 扫描全部代币账户,返回所有余额大于 0 的代币
73
+ */
74
+ balances(owner: string, mints?: readonly string[], options?: BalancesOptions): Promise<TokenBalance[]>;
75
+ /**
76
+ * 列出持有人拥有的全部代币(SOL 在第一位),带 name / symbol,适合做资产列表。
77
+ * 需要支持 getTokenAccountsByOwner 的节点(免费公共节点大多不支持)。
78
+ *
79
+ * - 默认不含 NFT(精度 0 且数量 1)和余额为 0 的代币账户(如已清空的 ATA),可用 includeNfts / includeZero 打开
80
+ * - accounts 是该代币的代币账户数量;大于 1 时余额为所有账户的合计
81
+ * - 顺序:SOL,然后 SPL Token 的代币,再然后 Token-2022 的代币(各自按节点返回的顺序;没有价格信息,不按价值排序)
82
+ */
83
+ ownerTokens(owner: string, options?: OwnerTokensOptions): Promise<OwnedToken[]>;
84
+ /**
85
+ * 批量查代币详情(一次 getMultipleAccounts),可以选择返回哪些字段。
86
+ * name / symbol / uri 优先取 Token-2022 TokenMetadata 扩展,其次 Metaplex 元数据。
87
+ */
88
+ tokens<const F extends TokenField = DefaultTokenField>(mints: readonly string[], options?: TokensOptions<F>): Promise<TokenDetails<F>[]>;
89
+ /** 批量查 NFT 元数据(Metaplex;Token-2022 NFT 取 TokenMetadata 扩展),一次 getMultipleAccounts */
90
+ nfts(mints: readonly string[]): Promise<NftDetails[]>;
91
+ /** 批量查 NFT 持有人:getTokenLargestAccounts(合并成一个批量请求)+ 一次 getMultipleAccounts */
92
+ nftOwners(mints: readonly string[]): Promise<NftOwner[]>;
93
+ /** 查某地址持有的全部 NFT(扫描代币账户:数量 1、精度 0),再批量读元数据。需要支持 getTokenAccountsByOwner 的节点 */
94
+ ownerNfts(owner: string): Promise<NftDetails[]>;
95
+ }
96
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}