@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/dist/rpc.js ADDED
@@ -0,0 +1,295 @@
1
+ /** 节点返回的 JSON-RPC 错误 */
2
+ export class RpcError extends Error {
3
+ code;
4
+ data;
5
+ /** 是否是节点自身的限制(限频、需要 API Key、禁止访问等),换节点可能成功 */
6
+ nodeFault;
7
+ constructor(message, code, data) {
8
+ super(message);
9
+ this.name = 'RpcError';
10
+ this.code = code;
11
+ this.data = data;
12
+ this.nodeFault =
13
+ code === 403 ||
14
+ code === 429 ||
15
+ code === -32005 ||
16
+ /forbidden|blocked|personal token|api.?key|rate.?limit|too many|not allowed|unauthori[sz]ed|limit exceeded|disabled|not available|not supported/i.test(message);
17
+ }
18
+ }
19
+ /** HTTP 层错误(非 2xx、网络错误、超时) */
20
+ export class HttpError extends Error {
21
+ status;
22
+ constructor(message, status) {
23
+ super(message);
24
+ this.name = 'HttpError';
25
+ this.status = status;
26
+ }
27
+ }
28
+ /** 是否是节点问题(应换节点重试):除了确定性的 JSON-RPC 错误(如参数错误),其余都算 */
29
+ export function isNodeFault(err) {
30
+ if (err instanceof RpcError) {
31
+ return err.nodeFault;
32
+ }
33
+ return true;
34
+ }
35
+ let nextId = 1;
36
+ /** 批量请求被拒绝、可以降级为逐条请求的 HTTP 状态 */
37
+ const BATCH_REJECTED_STATUS = new Set([400, 404, 405, 415, 422]);
38
+ function isBatchLimitError(message) {
39
+ return !!message && /batch/i.test(message) && /more than \d+|too (large|many|big)|not (allowed|supported)|exceed/i.test(message);
40
+ }
41
+ /**
42
+ * 基于 fetch 的 JSON-RPC 传输:同一收集窗口内的调用合并成一个 JSON-RPC 批量请求。
43
+ * 节点不支持批量(返回非数组)或限制批量大小(如 drpc 免费档最多 3 条)时自动降级。
44
+ */
45
+ export class HttpRpc {
46
+ url;
47
+ #timeout;
48
+ #headers;
49
+ #batchWait;
50
+ #retries;
51
+ #fetch;
52
+ #maxBatchSize;
53
+ #batch;
54
+ #queue = [];
55
+ #timer = null;
56
+ constructor(url, options = {}) {
57
+ this.url = url;
58
+ this.#timeout = options.timeout ?? 10_000;
59
+ this.#headers = { 'content-type': 'application/json', ...options.headers };
60
+ this.#maxBatchSize = Math.max(1, options.maxBatchSize ?? 20);
61
+ this.#batch = options.batch ?? true;
62
+ this.#batchWait = options.batchWait ?? 0;
63
+ this.#retries = Math.max(0, options.retries ?? 2);
64
+ this.#fetch = options.fetch ?? ((...args) => fetch(...args));
65
+ }
66
+ request(method, params = []) {
67
+ return new Promise((resolve, reject) => {
68
+ this.#queue.push({ method, params, resolve: resolve, reject });
69
+ if (!this.#batch || this.#queue.length >= this.#maxBatchSize) {
70
+ this.#flush();
71
+ }
72
+ else if (!this.#timer) {
73
+ this.#timer = setTimeout(() => this.#flush(), this.#batchWait);
74
+ }
75
+ });
76
+ }
77
+ #flush() {
78
+ if (this.#timer) {
79
+ clearTimeout(this.#timer);
80
+ this.#timer = null;
81
+ }
82
+ const queue = this.#queue;
83
+ this.#queue = [];
84
+ for (let i = 0; i < queue.length; i += this.#maxBatchSize) {
85
+ void this.#dispatch(queue.slice(i, i + this.#maxBatchSize));
86
+ }
87
+ }
88
+ async #dispatch(items) {
89
+ if (items.length === 1 || !this.#batch) {
90
+ await Promise.all(items.map((item) => this.#single(item)));
91
+ return;
92
+ }
93
+ const ids = items.map(() => nextId++);
94
+ let body;
95
+ try {
96
+ body = await this.#post(items.map((item, i) => ({ jsonrpc: '2.0', id: ids[i], method: item.method, params: item.params })));
97
+ }
98
+ catch (err) {
99
+ if (err instanceof HttpError && err.status === 413 && items.length > 1) {
100
+ // 请求体太大:批量减半后重发
101
+ this.#maxBatchSize = Math.max(1, Math.floor(items.length / 2));
102
+ await this.#redispatch(items);
103
+ return;
104
+ }
105
+ // 400 / 404 / 405 / 415 / 422 等:节点多半不接受批量请求,降级为逐条请求
106
+ // (401 / 403 / 429 / 5xx 是鉴权、限频或节点本身的问题,降级没有用,原样报错)
107
+ if (err instanceof HttpError && err.status !== undefined && BATCH_REJECTED_STATUS.has(err.status)) {
108
+ this.#disableBatch();
109
+ await Promise.all(items.map((item) => this.#single(item)));
110
+ return;
111
+ }
112
+ items.forEach((item) => item.reject(err));
113
+ return;
114
+ }
115
+ if (!Array.isArray(body)) {
116
+ const error = body?.error;
117
+ const rpcError = new RpcError(error?.message ?? 'Invalid JSON-RPC batch response', error?.code, error?.data);
118
+ // 限频 / 鉴权等节点问题:原样报错,不降级(降级成逐条请求只会让限频更严重)
119
+ if (rpcError.nodeFault) {
120
+ items.forEach((item) => item.reject(rpcError));
121
+ return;
122
+ }
123
+ // 其他(如 -32600 invalid request):节点不支持批量,降级为逐条请求
124
+ this.#disableBatch();
125
+ await Promise.all(items.map((item) => this.#single(item)));
126
+ return;
127
+ }
128
+ const byId = new Map(body.map((res) => [res.id, res]));
129
+ const limited = body.find((res) => isBatchLimitError(res.error?.message));
130
+ if (limited) {
131
+ // 如 “Batch of more than 3 requests are not allowed”:按限制缩小批量后重发;
132
+ // 限制不小于当前批量(节点的上限判断有偏差)或解析不出数字时,降级为逐条请求,避免无限重发
133
+ const max = Number(/more than (\d+)/i.exec(limited.error?.message ?? '')?.[1]);
134
+ if (Number.isFinite(max) && max > 1 && max < items.length) {
135
+ this.#maxBatchSize = max;
136
+ await this.#redispatch(items);
137
+ }
138
+ else {
139
+ this.#disableBatch();
140
+ await Promise.all(items.map((item) => this.#single(item)));
141
+ }
142
+ return;
143
+ }
144
+ // 批量里个别调用被限频(JSON-RPC 429):只把这些调用退避后单独重试
145
+ const limitedItems = [];
146
+ items.forEach((item, i) => {
147
+ const response = byId.get(ids[i]);
148
+ if (response?.error?.code === 429 && this.#retries > 0) {
149
+ limitedItems.push(item);
150
+ }
151
+ else {
152
+ settle(item, response);
153
+ }
154
+ });
155
+ if (limitedItems.length) {
156
+ await new Promise((r) => setTimeout(r, 500));
157
+ await Promise.all(limitedItems.map((item) => this.#single(item)));
158
+ }
159
+ }
160
+ /** 按当前的 maxBatchSize 重新分批发送(每批都比原来小,不会无限循环) */
161
+ async #redispatch(items) {
162
+ const size = this.#maxBatchSize;
163
+ const chunks = [];
164
+ for (let i = 0; i < items.length; i += size) {
165
+ chunks.push(items.slice(i, i + size));
166
+ }
167
+ await Promise.all(chunks.map((chunk) => this.#dispatch(chunk)));
168
+ }
169
+ async #single(item) {
170
+ try {
171
+ const body = (await this.#post({ jsonrpc: '2.0', id: nextId++, method: item.method, params: item.params }));
172
+ settle(item, body);
173
+ }
174
+ catch (err) {
175
+ item.reject(err);
176
+ }
177
+ }
178
+ #disableBatch() {
179
+ this.#batch = false;
180
+ }
181
+ async #post(payload) {
182
+ for (let attempt = 0;; attempt++) {
183
+ let response;
184
+ try {
185
+ response = await this.#fetch(this.url, {
186
+ method: 'POST',
187
+ headers: this.#headers,
188
+ body: JSON.stringify(payload),
189
+ signal: this.#timeout > 0 && Number.isFinite(this.#timeout) ? AbortSignal.timeout(this.#timeout) : undefined,
190
+ });
191
+ }
192
+ catch (err) {
193
+ throw new HttpError(`Request to ${this.url} failed: ${err?.message ?? err}`);
194
+ }
195
+ if (response.status === 429 && attempt < this.#retries) {
196
+ await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
197
+ continue;
198
+ }
199
+ const text = await response.text();
200
+ let body;
201
+ try {
202
+ body = parseJson(text);
203
+ }
204
+ catch {
205
+ throw new HttpError(`Invalid JSON from ${this.url} (HTTP ${response.status})`, response.status);
206
+ }
207
+ // 非 2xx 但返回了 JSON-RPC 结构(如 403 Access forbidden、drpc 的 500 + 批量错误数组):交给上层解析
208
+ if (!response.ok && !Array.isArray(body) && !body?.error) {
209
+ throw new HttpError(`HTTP ${response.status} from ${this.url}`, response.status);
210
+ }
211
+ // 有的节点限频时返回 HTTP 200 + JSON-RPC 错误码 429:同样退避重试
212
+ if (attempt < this.#retries && isRateLimited(body)) {
213
+ await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
214
+ continue;
215
+ }
216
+ return body;
217
+ }
218
+ }
219
+ }
220
+ // lamports 等 u64 以 JSON 数字返回,超过 2^53(约 900 万 SOL)时 JSON.parse 会丢精度:
221
+ // 支持 JSON.parse source text access 的环境(Node 21+、新版浏览器)直接取原文;否则先把超长整数改成字符串再解析
222
+ let sourceAccess;
223
+ function parseJson(text) {
224
+ sourceAccess ??= (() => {
225
+ let supported = false;
226
+ JSON.parse('1', (_key, value, context) => {
227
+ supported = typeof context?.source === 'string';
228
+ return value;
229
+ });
230
+ return supported;
231
+ })();
232
+ if (sourceAccess) {
233
+ return JSON.parse(text, (_key, value, context) => typeof value === 'number' && !Number.isSafeInteger(value) && Number.isInteger(value) && context?.source ? context.source : value);
234
+ }
235
+ return JSON.parse(text.replace(/([:[,]\s*)(-?\d{16,})(?=\s*[,}\]])/g, '$1"$2"'));
236
+ }
237
+ function isRateLimited(body) {
238
+ const responses = (Array.isArray(body) ? body : [body]);
239
+ return responses.length > 0 && responses.every((res) => res?.error?.code === 429);
240
+ }
241
+ function settle(item, response) {
242
+ if (!response) {
243
+ item.reject(new HttpError('Missing response in JSON-RPC batch'));
244
+ }
245
+ else if (response.error) {
246
+ item.reject(new RpcError(response.error.message ?? 'JSON-RPC error', response.error.code, response.error.data));
247
+ }
248
+ else {
249
+ item.resolve(response.result);
250
+ }
251
+ }
252
+ /**
253
+ * 多节点故障切换:按顺序使用,节点问题(网络、超时、限频、需要 Key、403 等)换下一个;
254
+ * 参数错误等确定性错误直接抛出。出错的节点在冷却期内排到最后。
255
+ */
256
+ export class FallbackRpc {
257
+ nodes;
258
+ #cooldown;
259
+ #failedAt;
260
+ constructor(nodes, options = {}) {
261
+ if (!nodes.length) {
262
+ throw new Error('FallbackRpc requires at least one node');
263
+ }
264
+ this.nodes = nodes;
265
+ this.#cooldown = options.cooldown ?? 30_000;
266
+ this.#failedAt = nodes.map(() => 0);
267
+ }
268
+ async request(method, params) {
269
+ let lastError;
270
+ for (const index of this.#order()) {
271
+ try {
272
+ const result = await this.nodes[index].request(method, params);
273
+ this.#failedAt[index] = 0;
274
+ return result;
275
+ }
276
+ catch (err) {
277
+ if (!isNodeFault(err)) {
278
+ throw err;
279
+ }
280
+ this.#failedAt[index] = Date.now();
281
+ lastError = err;
282
+ }
283
+ }
284
+ throw lastError;
285
+ }
286
+ #order() {
287
+ const now = Date.now();
288
+ const healthy = [];
289
+ const cooling = [];
290
+ this.#failedAt.forEach((at, i) => (at && now - at < this.#cooldown ? cooling : healthy).push(i));
291
+ cooling.sort((a, b) => this.#failedAt[a] - this.#failedAt[b]);
292
+ return [...healthy, ...cooling];
293
+ }
294
+ }
295
+ //# sourceMappingURL=rpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import { SolanaClient, type BalancesOptions, type ClientConfig, type OwnerTokensOptions, type TokensOptions } from './client.js';
2
+ import type { RpcSource } from './source.js';
3
+ import type { DefaultTokenField, NftDetails, NftOwner, OwnedToken, SolBalance, TokenBalance, TokenDetails, TokenField } from './types.js';
4
+ /** 各函数共用的节点参数 */
5
+ export interface ShortcutOptions extends ClientConfig {
6
+ /**
7
+ * 节点:RPC URL、URL 数组(主节点 + 备用节点)、web3.js Connection、自定义传输(request(method, params))。
8
+ * 不传则使用 cluster(默认 mainnet)的内置公共节点
9
+ */
10
+ provider?: RpcSource | readonly RpcSource[];
11
+ }
12
+ export interface GetBalancesOptions extends BalancesOptions, ShortcutOptions {
13
+ }
14
+ /**
15
+ * 批量查余额(SOL + SPL Token + Token-2022),返回原始余额、decimals 和换算后的数值。
16
+ *
17
+ * ```ts
18
+ * import { NATIVE_MINT, getBalances } from '@w3lib/solana-batch-call'
19
+ *
20
+ * await getBalances(owner, [NATIVE_MINT, USDC]) // 指定代币:免费公共节点也能用
21
+ * await getBalances(owner, [NATIVE_MINT, USDC], { symbol: true })
22
+ * await getBalances(owner, undefined, { provider: 'https://my-rpc…' }) // 全部持仓:需要支持 getTokenAccountsByOwner 的节点
23
+ * ```
24
+ */
25
+ export declare function getBalances(owner: string, mints?: readonly string[], options?: GetBalancesOptions): Promise<TokenBalance[]>;
26
+ /** 批量查多个地址的 SOL 余额(一次 getMultipleAccounts) */
27
+ export declare function getSolBalances(addresses: readonly string[], options?: ShortcutOptions): Promise<SolBalance[]>;
28
+ export interface GetTokensOptions<F extends TokenField = DefaultTokenField> extends TokensOptions<F>, ShortcutOptions {
29
+ }
30
+ /**
31
+ * 批量查代币详情,字段可选(默认 name / symbol / decimals)。
32
+ *
33
+ * ```ts
34
+ * await getTokens([USDC, PYUSD], { fields: ['name', 'symbol', 'decimals', 'supply', 'tokenProgram'] })
35
+ * ```
36
+ */
37
+ export declare function getTokens<const F extends TokenField = DefaultTokenField>(mints: readonly string[], options?: GetTokensOptions<F>): Promise<TokenDetails<F>[]>;
38
+ /** 批量查 NFT 元数据(名称、uri、所属集合、版税、创作者等) */
39
+ export declare function getNfts(mints: readonly string[], options?: ShortcutOptions): Promise<NftDetails[]>;
40
+ /** 批量查 NFT 持有人 */
41
+ export declare function getNftOwners(mints: readonly string[], options?: ShortcutOptions): Promise<NftOwner[]>;
42
+ /** 查某地址持有的全部 NFT(需要支持 getTokenAccountsByOwner 的节点) */
43
+ export declare function getOwnerNfts(owner: string, options?: ShortcutOptions): Promise<NftDetails[]>;
44
+ export interface GetOwnerTokensOptions extends OwnerTokensOptions, ShortcutOptions {
45
+ }
46
+ /**
47
+ * 列出持有人拥有的全部代币(SOL 在第一位),带 name / symbol,适合做资产列表。
48
+ * 需要支持 getTokenAccountsByOwner 的节点。
49
+ *
50
+ * ```ts
51
+ * await getOwnerTokens(owner, { provider: 'https://my-rpc…' })
52
+ * // [{ token: NATIVE_MINT, native: true, name: 'Solana', symbol: 'SOL', formatted: '1.5', … }, { token: USDC, name: 'USD Coin', … }]
53
+ * ```
54
+ */
55
+ export declare function getOwnerTokens(owner: string, options?: GetOwnerTokensOptions): Promise<OwnedToken[]>;
56
+ /** 测试用:按参数取(或创建)客户端,用于验证缓存复用(不在包的公开导出里) */
57
+ export declare function resolveClientForTest(options: ShortcutOptions): SolanaClient;
58
+ /** 测试用:清空客户端缓存 */
59
+ export declare function resetClientCache(): void;
60
+ //# sourceMappingURL=shortcuts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shortcuts.d.ts","sourceRoot":"","sources":["../src/shortcuts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AAChI,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEzI,iBAAiB;AACjB,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,SAAS,EAAE,CAAA;CAC5C;AAqFD,MAAM,WAAW,kBAAmB,SAAQ,eAAe,EAAE,eAAe;CAAG;AAE/E;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAG/H;AAED,8CAA8C;AAC9C,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAEjH;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,UAAU,GAAG,iBAAiB,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC,EAAE,eAAe;CAAG;AAExH;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,CAAC,CAAC,SAAS,UAAU,GAAG,iBAAiB,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,gBAAgB,CAAC,CAAC,CAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAGjK;AAED,uCAAuC;AACvC,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAEtG;AAED,kBAAkB;AAClB,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAEzG;AAED,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAEhG;AAED,MAAM,WAAW,qBAAsB,SAAQ,kBAAkB,EAAE,eAAe;CAAG;AAErF;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAGxG;AAED,2CAA2C;AAC3C,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,eAAe,GAAG,YAAY,CAE3E;AAED,kBAAkB;AAClB,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
@@ -0,0 +1,136 @@
1
+ import { SolanaClient } from './client.js';
2
+ // 复用客户端:多次调用共享连接、批量合并与故障切换状态。按 “配置内容” 分组,组内 URL 按字符串、对象按引用(WeakMap)。
3
+ // 配置里有函数(如自定义 fetch)或节点数组含对象时不缓存。
4
+ const MAX_CACHED = 32;
5
+ const clientCache = new Map();
6
+ function setBounded(map, key, value) {
7
+ map.set(key, value);
8
+ if (map.size > MAX_CACHED) {
9
+ map.delete(map.keys().next().value);
10
+ }
11
+ }
12
+ function configKey(config) {
13
+ try {
14
+ return JSON.stringify(config, (_key, value) => {
15
+ if (typeof value === 'function') {
16
+ throw new Error('not comparable');
17
+ }
18
+ return value;
19
+ });
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ function sourceKey(source) {
26
+ if (source === undefined) {
27
+ return 'default';
28
+ }
29
+ if (typeof source === 'string') {
30
+ return `url:${source}`;
31
+ }
32
+ if (Array.isArray(source)) {
33
+ return source.every((item) => typeof item === 'string') ? `urls:${source.join('\n')}` : null;
34
+ }
35
+ return source;
36
+ }
37
+ /** 拆出节点参数和函数自己的参数(ownKeys),其余作为客户端配置;值为 undefined 的参数忽略 */
38
+ function resolve(options, ownKeys = []) {
39
+ const { provider, ...others } = options;
40
+ const own = {};
41
+ const config = {};
42
+ for (const [key, value] of Object.entries(others)) {
43
+ if (value !== undefined) {
44
+ ;
45
+ (ownKeys.includes(key) ? own : config)[key] = value;
46
+ }
47
+ }
48
+ return { client: getClient(provider, config), own: own };
49
+ }
50
+ function getClient(source, config) {
51
+ const cfg = configKey(config);
52
+ const key = sourceKey(source);
53
+ if (cfg === null || key === null) {
54
+ return new SolanaClient(source, config);
55
+ }
56
+ let group = clientCache.get(cfg);
57
+ if (!group) {
58
+ group = { byString: new Map(), byObject: new WeakMap() };
59
+ setBounded(clientCache, cfg, group);
60
+ }
61
+ let client = typeof key === 'string' ? group.byString.get(key) : group.byObject.get(key);
62
+ if (!client) {
63
+ client = new SolanaClient(source, config);
64
+ if (typeof key === 'string') {
65
+ setBounded(group.byString, key, client);
66
+ }
67
+ else {
68
+ group.byObject.set(key, client);
69
+ }
70
+ }
71
+ return client;
72
+ }
73
+ /**
74
+ * 批量查余额(SOL + SPL Token + Token-2022),返回原始余额、decimals 和换算后的数值。
75
+ *
76
+ * ```ts
77
+ * import { NATIVE_MINT, getBalances } from '@w3lib/solana-batch-call'
78
+ *
79
+ * await getBalances(owner, [NATIVE_MINT, USDC]) // 指定代币:免费公共节点也能用
80
+ * await getBalances(owner, [NATIVE_MINT, USDC], { symbol: true })
81
+ * await getBalances(owner, undefined, { provider: 'https://my-rpc…' }) // 全部持仓:需要支持 getTokenAccountsByOwner 的节点
82
+ * ```
83
+ */
84
+ export function getBalances(owner, mints, options = {}) {
85
+ const { client, own } = resolve(options, ['symbol', 'scan']);
86
+ return client.balances(owner, mints, own);
87
+ }
88
+ /** 批量查多个地址的 SOL 余额(一次 getMultipleAccounts) */
89
+ export function getSolBalances(addresses, options = {}) {
90
+ return resolve(options).client.solBalances(addresses);
91
+ }
92
+ /**
93
+ * 批量查代币详情,字段可选(默认 name / symbol / decimals)。
94
+ *
95
+ * ```ts
96
+ * await getTokens([USDC, PYUSD], { fields: ['name', 'symbol', 'decimals', 'supply', 'tokenProgram'] })
97
+ * ```
98
+ */
99
+ export function getTokens(mints, options = {}) {
100
+ const { client, own } = resolve(options, ['fields']);
101
+ return client.tokens(mints, { fields: own.fields });
102
+ }
103
+ /** 批量查 NFT 元数据(名称、uri、所属集合、版税、创作者等) */
104
+ export function getNfts(mints, options = {}) {
105
+ return resolve(options).client.nfts(mints);
106
+ }
107
+ /** 批量查 NFT 持有人 */
108
+ export function getNftOwners(mints, options = {}) {
109
+ return resolve(options).client.nftOwners(mints);
110
+ }
111
+ /** 查某地址持有的全部 NFT(需要支持 getTokenAccountsByOwner 的节点) */
112
+ export function getOwnerNfts(owner, options = {}) {
113
+ return resolve(options).client.ownerNfts(owner);
114
+ }
115
+ /**
116
+ * 列出持有人拥有的全部代币(SOL 在第一位),带 name / symbol,适合做资产列表。
117
+ * 需要支持 getTokenAccountsByOwner 的节点。
118
+ *
119
+ * ```ts
120
+ * await getOwnerTokens(owner, { provider: 'https://my-rpc…' })
121
+ * // [{ token: NATIVE_MINT, native: true, name: 'Solana', symbol: 'SOL', formatted: '1.5', … }, { token: USDC, name: 'USD Coin', … }]
122
+ * ```
123
+ */
124
+ export function getOwnerTokens(owner, options = {}) {
125
+ const { client, own } = resolve(options, ['metadata', 'includeNative', 'includeZero', 'includeNfts']);
126
+ return client.ownerTokens(owner, own);
127
+ }
128
+ /** 测试用:按参数取(或创建)客户端,用于验证缓存复用(不在包的公开导出里) */
129
+ export function resolveClientForTest(options) {
130
+ return resolve(options).client;
131
+ }
132
+ /** 测试用:清空客户端缓存 */
133
+ export function resetClientCache() {
134
+ clientCache.clear();
135
+ }
136
+ //# sourceMappingURL=shortcuts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shortcuts.js","sourceRoot":"","sources":["../src/shortcuts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import { type Cluster } from './constants.js';
2
+ import { type FallbackOptions, type HttpRpcOptions, type RpcTransport } from './rpc.js';
3
+ /**
4
+ * 节点来源(也可以传数组:主节点 + 备用节点):
5
+ * - RPC URL 字符串
6
+ * - @solana/web3.js 的 Connection(使用其 rpcEndpoint)
7
+ * - 自定义传输:实现 `request(method, params)` 的对象(如包一层 @solana/kit 的 rpc)
8
+ */
9
+ export type RpcSource = string | RpcTransport | {
10
+ rpcEndpoint: string;
11
+ };
12
+ export interface SourceOptions extends HttpRpcOptions, FallbackOptions {
13
+ }
14
+ export declare function genesisOf(transport: RpcTransport): Promise<string>;
15
+ export declare class NetworkMismatchError extends Error {
16
+ constructor(expected: string, actual: string);
17
+ }
18
+ /**
19
+ * 校验节点所在的网络(创世区块哈希),避免把 devnet 的数据当成 mainnet 的返回。
20
+ * 校验与请求并行发出,不增加延迟;结果缓存,每个节点只多一次请求;识别失败时不拦截,并在一段时间内不再重试。
21
+ */
22
+ export declare class NetworkCheckedRpc implements RpcTransport {
23
+ #private;
24
+ readonly inner: RpcTransport;
25
+ readonly expected: () => Promise<string>;
26
+ constructor(inner: RpcTransport, expected: () => Promise<string>);
27
+ request<T = unknown>(method: string, params?: readonly unknown[]): Promise<T>;
28
+ }
29
+ export interface ResolvedSource {
30
+ transport: RpcTransport;
31
+ /** 期望的创世区块哈希:指定了 cluster 时取对应值,否则取第一个响应节点的 */
32
+ genesis: () => Promise<string>;
33
+ }
34
+ export declare function resolveSource(source: RpcSource | readonly RpcSource[] | undefined, cluster: Cluster | undefined, options?: SourceOptions): ResolvedSource;
35
+ //# sourceMappingURL=source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC/E,OAAO,EAAwB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAA;AAE7G;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvE,MAAM,WAAW,aAAc,SAAQ,cAAc,EAAE,eAAe;CAAG;AAoBzE,wBAAgB,SAAS,CAAC,SAAS,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAalE;AAKD,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAI7C;AAED;;;GAGG;AACH,qBAAa,iBAAkB,YAAW,YAAY;;IAIlD,QAAQ,CAAC,KAAK,EAAE,YAAY;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC;gBAD/B,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC;IAqBpC,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC;CAUpF;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,YAAY,CAAA;IACvB,8CAA8C;IAC9C,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CAC/B;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,SAAS,GAAG,SAAS,SAAS,EAAE,GAAG,SAAS,EACpD,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,OAAO,GAAE,aAAkB,GAC1B,cAAc,CA0ChB"}