@w3lib/solana-batch-call 0.2.0 → 0.2.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"shortcuts.d.ts","sourceRoot":"","sources":["../src/shortcuts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shortcuts.d.ts","sourceRoot":"","sources":["../src/shortcuts.ts"],"names":[],"mappings":"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"}
package/dist/shortcuts.js CHANGED
@@ -9,11 +9,23 @@ function setBounded(map, key, value) {
9
9
  map.delete(map.keys().next().value);
10
10
  }
11
11
  }
12
+ // 配置里的函数(如自定义 fetch)按对象编号参与缓存键:同一个函数对象复用同一个客户端(WeakMap 不阻止回收)
13
+ const functionIds = new WeakMap();
14
+ let nextFunctionId = 0;
15
+ /** 配置的缓存键:对象的键按字母排序(写法顺序不同视为同一份配置),函数按对象编号;无法序列化时返回 null(不缓存) */
12
16
  function configKey(config) {
13
17
  try {
14
18
  return JSON.stringify(config, (_key, value) => {
15
19
  if (typeof value === 'function') {
16
- throw new Error('not comparable');
20
+ let id = functionIds.get(value);
21
+ if (id === undefined) {
22
+ id = ++nextFunctionId;
23
+ functionIds.set(value, id);
24
+ }
25
+ return `function#${id}`;
26
+ }
27
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
28
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
17
29
  }
18
30
  return value;
19
31
  });
@@ -1 +1 @@
1
- {"version":3,"file":"shortcuts.js","sourceRoot":"","sources":["../src/shortcuts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shortcuts.js","sourceRoot":"","sources":["../src/shortcuts.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@w3lib/solana-batch-call",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Batched Solana reads: SOL / SPL / Token-2022 balances, token metadata, NFTs. Auto-batching JSON-RPC, getMultipleAccounts chunking, RPC failover. No Solana SDK dependency.",
5
5
  "keywords": [
6
6
  "solana",
package/src/address.ts CHANGED
@@ -92,14 +92,24 @@ function concat(parts: Uint8Array[]): Uint8Array {
92
92
  return out
93
93
  }
94
94
 
95
+ const MAX_SEED_LENGTH = 32
96
+ const MAX_SEEDS = 16
97
+
95
98
  // PDA 推导是纯计算但要做多次 sha256 + 曲线判断,结果缓存
96
99
  const pdaCache = new Map<string, string>()
97
100
 
98
101
  /**
99
102
  * 推导 PDA(与 web3.js PublicKey.findProgramAddressSync 相同),返回地址。
100
- * seeds 每项不超过 32 字节。
103
+ * seeds 每项不超过 32 字节、最多 15 个(加上 bump 共 16 个,链上上限),超出时抛错。
101
104
  */
102
105
  export function findProgramAddress(seeds: Uint8Array[], programId: string): string {
106
+ // 与链上和 web3.js 一致:每个种子最多 32 字节;种子加上 bump 最多 16 个
107
+ if (seeds.some((seed) => seed.length > MAX_SEED_LENGTH)) {
108
+ throw new TypeError('Max seed length exceeded')
109
+ }
110
+ if (seeds.length + 1 > MAX_SEEDS) {
111
+ throw new TypeError('Max seeds exceeded')
112
+ }
103
113
  const key = `${programId}:${seeds.map((s) => encodeAddress(s)).join(',')}`
104
114
  const cached = pdaCache.get(key)
105
115
  if (cached) {
package/src/client.ts CHANGED
@@ -241,6 +241,8 @@ const TOKEN_STANDARDS: readonly TokenStandard[] = [
241
241
  interface TokenMeta {
242
242
  decimals?: number
243
243
  tokenProgram?: string
244
+ /** tokenProgram 来自导入的缓存、还没从链上确认过:ATA 模式第一次用时按程序未知处理(推导两个程序的 ATA、读 mint 确认) */
245
+ programUnverified?: boolean
244
246
  /** null:确认没有元数据 */
245
247
  name?: string | null
246
248
  symbol?: string | null
@@ -257,6 +259,29 @@ const NAME_TTL = 60 * 60 * 1000
257
259
  const MAX_CACHED_TOKENS = 50_000
258
260
  const tokenMetaCache = new Map<string, CachedMeta>()
259
261
 
262
+ // 已知数据为空的钱包账户(系统账户或不存在):ATA 模式下和代币账户放在同一个请求里读,不必单独带 dataSlice 多发一个请求。
263
+ // 按 scope + 地址记,最多 1 万个,超出淘汰最早的;读到不是系统账户(如程序账户)时移除
264
+ const MAX_SMALL_OWNERS = 10_000
265
+ const smallOwners = new Set<string>()
266
+
267
+ function isSmallOwner(scope: string, owner: string): boolean {
268
+ return smallOwners.has(`${scope}:${owner}`)
269
+ }
270
+
271
+ /** 根据只读 lamports 的结果判断钱包账户是否“数据为空”(dataSlice 读不到数据长度,按所属程序判断:系统账户的数据为空) */
272
+ function learnOwner(scope: string, owner: string, account: AccountInfo | null): void {
273
+ const key = `${scope}:${owner}`
274
+ if (account === null || account.owner === SYSTEM_PROGRAM_ID) {
275
+ smallOwners.delete(key)
276
+ smallOwners.add(key)
277
+ if (smallOwners.size > MAX_SMALL_OWNERS) {
278
+ smallOwners.delete(smallOwners.values().next().value as string)
279
+ }
280
+ } else {
281
+ smallOwners.delete(key)
282
+ }
283
+ }
284
+
260
285
  let clientIds = 0
261
286
 
262
287
  /** 缓存写入监听(persistTokenMetaCache 用) */
@@ -264,6 +289,7 @@ const cacheWriteListeners = new Set<() => void>()
264
289
 
265
290
  /** 测试用:清空代币信息缓存 */
266
291
  export function resetTokenMetaCache(): void {
292
+ smallOwners.clear()
267
293
  tokenMetaCache.clear()
268
294
  }
269
295
 
@@ -285,7 +311,8 @@ export function exportTokenMetaCache(maxEntries = Number.POSITIVE_INFINITY): Tok
285
311
  const entries: TokenMetaSnapshot['entries'] = []
286
312
  for (const [key, meta] of tokenMetaCache) {
287
313
  if (SHARED_SCOPES.has(key.slice(0, key.indexOf(':')))) {
288
- entries.push([key, { ...meta }])
314
+ const { programUnverified: _, ...exported } = meta
315
+ entries.push([key, exported])
289
316
  }
290
317
  }
291
318
  return { version: 1, entries: entries.slice(Math.max(0, entries.length - maxEntries)) }
@@ -319,11 +346,20 @@ function validMeta(entry: unknown): CachedMeta | null {
319
346
  }
320
347
  const meta: CachedMeta = {}
321
348
  if (typeof raw.decimals === 'number' && Number.isInteger(raw.decimals) && raw.decimals >= 0 && raw.decimals <= 255) meta.decimals = raw.decimals
322
- if (raw.tokenProgram === TOKEN_PROGRAM_ID || raw.tokenProgram === TOKEN_2022_PROGRAM_ID) meta.tokenProgram = raw.tokenProgram
323
- if (typeof raw.namesAt === 'number' && (typeof raw.name === 'string' || raw.name === null) && (typeof raw.symbol === 'string' || raw.symbol === null)) {
349
+ if (raw.tokenProgram === TOKEN_PROGRAM_ID || raw.tokenProgram === TOKEN_2022_PROGRAM_ID) {
350
+ meta.tokenProgram = raw.tokenProgram
351
+ meta.programUnverified = true
352
+ }
353
+ // 写入时间只接受有限值、且不晚于现在(留 1 分钟时钟误差):未来的时间会让 name / symbol 永不过期,截到现在
354
+ if (
355
+ typeof raw.namesAt === 'number' &&
356
+ Number.isFinite(raw.namesAt) &&
357
+ (typeof raw.name === 'string' || raw.name === null) &&
358
+ (typeof raw.symbol === 'string' || raw.symbol === null)
359
+ ) {
324
360
  meta.name = raw.name
325
361
  meta.symbol = raw.symbol
326
- meta.namesAt = raw.namesAt
362
+ meta.namesAt = Math.min(raw.namesAt, Date.now())
327
363
  }
328
364
  return Object.keys(meta).length ? meta : null
329
365
  }
@@ -395,7 +431,32 @@ interface Holding {
395
431
 
396
432
  interface ParsedTokenAccount {
397
433
  pubkey: string
398
- account: { owner: string; data: { parsed?: { info?: { mint?: string; owner?: string; tokenAmount?: { amount?: string; decimals?: number } } } } }
434
+ account: {
435
+ owner: string
436
+ // 节点解析不了的账户(如 Token-2022 的新扩展)会退回 base64:["…", "base64"]
437
+ data: { parsed?: { info?: { mint?: string; owner?: string; tokenAmount?: { amount?: string; decimals?: number } } } } | [string, string] | string
438
+ }
439
+ }
440
+
441
+ /** jsonParsed 结果里的一个代币账户:节点解析好的直接取;退回 base64 的自己解析(这时没有 decimals,要从 mint 账户补) */
442
+ function readTokenEntry(entry: ParsedTokenAccount): { mint: string; amount: bigint; decimals: number | undefined } | null {
443
+ const data = entry.account?.data
444
+ if (data && !Array.isArray(data) && typeof data === 'object') {
445
+ const info = data.parsed?.info
446
+ if (info?.mint && info.tokenAmount?.decimals !== undefined) {
447
+ return { mint: info.mint, amount: BigInt(info.tokenAmount.amount ?? '0'), decimals: info.tokenAmount.decimals }
448
+ }
449
+ }
450
+ const encoded = Array.isArray(data) ? (data[1] === 'base64' ? data[0] : undefined) : typeof data === 'string' ? data : undefined
451
+ if (encoded === undefined) {
452
+ return null
453
+ }
454
+ try {
455
+ const parsed = parseTokenAccount(base64.decode(encoded))
456
+ return { mint: parsed.mint, amount: parsed.amount, decimals: undefined }
457
+ } catch {
458
+ return null
459
+ }
399
460
  }
400
461
 
401
462
  /**
@@ -414,7 +475,8 @@ interface ParsedTokenAccount {
414
475
  */
415
476
  /** ATA 模式下单个代币的查询计划(multiBalances 时多个钱包共用一次 getMultipleAccounts) */
416
477
  type AtaItem =
417
- | { kind: 'native'; mint: string; owner: number }
478
+ /** owner:钱包地址(主币余额按地址从两组结果里取) */
479
+ | { kind: 'native'; mint: string; owner: string }
418
480
  | { kind: 'invalid'; mint: string }
419
481
  | { kind: 'excluded'; mint: string }
420
482
  | { kind: 'token'; mint: string; mintIndex: number; metadataIndex: number; atas: Array<{ program: string; index: number }> }
@@ -497,58 +559,87 @@ export class SolanaClient {
497
559
  }
498
560
 
499
561
  /**
500
- * getMultipleAccounts:去重、按 100 个一组,最多同时 3 组(避免大批量时触发节点限频)。
501
- * tolerant 为 true 时,某组因节点问题(网络、限频等)失败不抛错:该组标记为 FAILED,并且不再发后面的组(节点已经在限频了)。
562
+ * 只读 lamports(主币余额):getMultipleAccounts 带 dataSlice { offset: 0, length: 0 },不读回账户数据——
563
+ * 钱包可能是数据很大的程序账户(上百 KB),整份读回会拖慢甚至超时。结果与 addresses 一一对应,账户不存在或地址非法时为 null
502
564
  */
565
+ async #lamports(addresses: readonly string[], ctx: QueryContext): Promise<(bigint | null)[]> {
566
+ const [fetched] = await this.#fetchGroups([{ addresses, lamportsOnly: true }], false, ctx)
567
+ return addresses.map((address) => (fetched?.get(address) as AccountInfo | null | undefined)?.lamports ?? null)
568
+ }
569
+
570
+ /** getMultipleAccounts(读账户数据):见 #fetchGroups */
503
571
  async #fetchAccounts(addresses: readonly string[], tolerant: boolean, ctx: QueryContext): Promise<Map<string, AccountInfo | null | typeof FAILED>> {
504
- const unique = [...new Set(addresses.filter((a) => isAddress(a)))]
505
- const chunks: string[][] = []
506
- for (let i = 0; i < unique.length; i += MAX_ACCOUNTS_PER_REQUEST) {
507
- chunks.push(unique.slice(i, i + MAX_ACCOUNTS_PER_REQUEST))
508
- }
509
- const result = new Map<string, AccountInfo | null | typeof FAILED>()
572
+ const [fetched] = await this.#fetchGroups([{ addresses, lamportsOnly: false }], tolerant, ctx)
573
+ return fetched as Map<string, AccountInfo | null | typeof FAILED>
574
+ }
575
+
576
+ /**
577
+ * getMultipleAccounts:每组去重、按 100 个一组拆分,所有组的请求一起排队,最多同时 3 个(避免大批量时触发节点限频)。
578
+ * lamportsOnly 的组带 dataSlice(长度 0,对同一请求里的所有账户生效,所以只要 lamports 的账户要单独成组)。
579
+ * tolerant 为 true 时,某个请求因节点问题(网络、限频等)失败不抛错:该请求的账户标记为 FAILED,并且不再发后面的请求(节点已经在限频了);
580
+ * 否则第一个错误抛出,后面的请求也不再发。结果按组返回
581
+ */
582
+ async #fetchGroups(
583
+ groups: ReadonlyArray<{ addresses: readonly string[]; lamportsOnly: boolean }>,
584
+ tolerant: boolean,
585
+ ctx: QueryContext,
586
+ ): Promise<Array<Map<string, AccountInfo | null | typeof FAILED>>> {
587
+ const chunks: Array<{ group: number; addresses: string[]; lamportsOnly: boolean }> = []
588
+ groups.forEach(({ addresses, lamportsOnly }, group) => {
589
+ const unique = [...new Set(addresses.filter((a) => isAddress(a)))]
590
+ for (let i = 0; i < unique.length; i += MAX_ACCOUNTS_PER_REQUEST) {
591
+ chunks.push({ group, addresses: unique.slice(i, i + MAX_ACCOUNTS_PER_REQUEST), lamportsOnly })
592
+ }
593
+ })
594
+ const results = groups.map(() => new Map<string, AccountInfo | null | typeof FAILED>())
510
595
  let aborted = false
511
596
  for (let i = 0; i < chunks.length; i += MAX_PARALLEL_ACCOUNT_REQUESTS) {
512
597
  const wave = chunks.slice(i, i + MAX_PARALLEL_ACCOUNT_REQUESTS)
513
- // 已取消的查询不再发后面的组
598
+ // 已取消的查询不再发后面的请求
514
599
  if (ctx.signal?.aborted) {
515
600
  throw ctx.signal.reason
516
601
  }
517
602
  if (aborted) {
518
- wave.flat().forEach((address) => result.set(address, FAILED))
603
+ wave.forEach((chunk) => chunk.addresses.forEach((address) => results[chunk.group]?.set(address, FAILED)))
519
604
  continue
520
605
  }
521
- await Promise.all(
606
+ // 同一批里的请求同时发出(合并成一个 JSON-RPC 批量请求);一个失败时等这一批都结束再抛,后面的批次不再发
607
+ const settled = await Promise.allSettled(
522
608
  wave.map(async (chunk) => {
609
+ const result = results[chunk.group] as Map<string, AccountInfo | null | typeof FAILED>
523
610
  try {
524
611
  const res = await this.#call<{ value: Array<RawAccount | null> }>(ctx, 'getMultipleAccounts', [
525
- chunk,
526
- { encoding: 'base64', commitment: this.#commitment },
612
+ chunk.addresses,
613
+ { encoding: 'base64', commitment: this.#commitment, ...(chunk.lamportsOnly ? { dataSlice: { offset: 0, length: 0 } } : {}) },
527
614
  ])
528
- chunk.forEach((address, j) => result.set(address, toAccountInfo(address, res?.value?.[j] ?? null)))
615
+ chunk.addresses.forEach((address, j) => result.set(address, toAccountInfo(address, res?.value?.[j] ?? null)))
529
616
  } catch (err) {
530
617
  if (!tolerant || !isRecoverable(err)) {
531
618
  throw err
532
619
  }
533
620
  aborted = true
534
- chunk.forEach((address) => result.set(address, FAILED))
621
+ chunk.addresses.forEach((address) => result.set(address, FAILED))
535
622
  }
536
623
  }),
537
624
  )
625
+ const failed = settled.find((r): r is PromiseRejectedResult => r.status === 'rejected')
626
+ if (failed) {
627
+ throw failed.reason
628
+ }
538
629
  }
539
- return result
630
+ return results
540
631
  }
541
632
 
542
- /** 批量查多个地址的 SOL 余额(一次 getMultipleAccounts;账户不存在视为 0) */
633
+ /** 批量查多个地址的 SOL 余额(getMultipleAccounts 只读 lamports、不读回账户数据;账户不存在视为 0) */
543
634
  solBalances(addresses: readonly string[], options: SolBalancesOptions = {}): Promise<SolBalance[]> {
544
635
  return abortable(options.signal, async () => {
545
636
  const ctx = this.#context(options)
546
- const accounts = await this.#accounts(addresses, ctx)
637
+ const balances = await this.#lamports(addresses, ctx)
547
638
  const list = addresses.map((address, i): SolBalance => {
548
639
  if (!isAddress(address)) {
549
640
  return { address, balance: '0', formatted: '0', success: false, error: 'invalid-address' }
550
641
  }
551
- const lamports = accounts[i]?.lamports ?? 0n
642
+ const lamports = balances[i] ?? 0n
552
643
  return { address, balance: lamports.toString(), formatted: formatUnits(lamports, SOL_DECIMALS), success: true }
553
644
  })
554
645
  return this.#attachSlot(list, ctx, options)
@@ -562,7 +653,7 @@ export class SolanaClient {
562
653
  /**
563
654
  * 批量查余额(主币 SOL + SPL Token + Token-2022),返回原始余额、decimals 和换算后的数值。
564
655
  *
565
- * - 传 mints:本地推导 ATA,与主币账户、mint 账户一起用 getMultipleAccounts 一次读完,免费节点也能用
656
+ * - 传 mints:本地推导 ATA,与 mint 账户一起用 getMultipleAccounts 读完,免费节点也能用;主币只读 lamports(dataSlice),另成一组同时发出
566
657
  * - 传 mints 且 accounts: 'all':每个代币按 mint 过滤查 getTokenAccountsByOwner,统计全部代币账户(含非 ATA)
567
658
  * - 不传 mints(或 scan: true):用 getTokenAccountsByOwner 扫描全部代币账户,返回所有余额大于 0 的代币
568
659
  * - 失败项(success: false)带 error:invalid-address(mint 地址非法)/ not-found(mint 不存在)/ not-token(不是代币 mint)
@@ -621,26 +712,49 @@ export class SolanaClient {
621
712
  /** ATA 模式:所有钱包的 主币账户 / mint / 元数据 / ATA 一起读,结果按钱包分组 */
622
713
  async #ataBalances(ctx: QueryContext, queries: readonly BalanceQuery[], withSymbol: boolean, allowed: readonly string[]): Promise<TokenBalance[][]> {
623
714
  const scope = this.#scope()
715
+ // 要数据的账户(mint、元数据、ATA)一组;钱包账户只要 lamports:已知是普通钱包(系统账户 / 不存在,数据为空)的
716
+ // 放进同一组(一个请求、同一个快照),其余(可能是数据很大的程序账户)单独一组带 dataSlice——同时发出,合并在同一个 JSON-RPC 批量请求里
624
717
  const addresses: string[] = []
625
- const plans = queries.map(({ owner, mints }) => mints.map((mint) => this.#ataPlan(scope, owner, mint, withSymbol, allowed, addresses)))
626
- const accounts = addresses.length ? await this.#accounts(addresses, ctx) : []
627
- return plans.map((plan) => plan.flatMap((item) => this.#ataResult(scope, item, accounts, withSymbol, allowed)))
718
+ const owners: string[] = []
719
+ const plans = queries.map(({ owner, mints }) => mints.map((mint) => this.#ataPlan(scope, owner, mint, withSymbol, allowed, addresses, owners)))
720
+ const [dataMap, ownerMap] = await this.#fetchGroups(
721
+ [
722
+ { addresses, lamportsOnly: false },
723
+ { addresses: owners, lamportsOnly: true },
724
+ ],
725
+ false,
726
+ ctx,
727
+ )
728
+ // 记下钱包账户是否数据为空:单独读的按结果学习;已放进代币那组的,读到不再是系统账户(如被分配给程序)时移除,下次单独读
729
+ for (const owner of new Set(queries.map((q) => q.owner))) {
730
+ // null(账户不存在)也是结果,不能用 ?? 落到另一组
731
+ const account = ownerMap?.has(owner) ? ownerMap.get(owner) : dataMap?.get(owner)
732
+ if (account !== undefined && account !== FAILED) {
733
+ learnOwner(scope, owner, account)
734
+ }
735
+ }
736
+ const accounts = addresses.map((address) => (dataMap?.get(address) as AccountInfo | null | undefined) ?? null)
737
+ const lamportsOf = (owner: string) => ((ownerMap?.has(owner) ? ownerMap.get(owner) : dataMap?.get(owner)) as AccountInfo | null | undefined)?.lamports ?? 0n
738
+ return plans.map((plan) => plan.flatMap((item) => this.#ataResult(scope, item, accounts, lamportsOf, withSymbol, allowed)))
628
739
  }
629
740
 
630
- #ataPlan(scope: string, owner: string, mint: string, withSymbol: boolean, allowed: readonly string[], addresses: string[]): AtaItem {
741
+ #ataPlan(scope: string, owner: string, mint: string, withSymbol: boolean, allowed: readonly string[], addresses: string[], owners: string[]): AtaItem {
631
742
  if (this.#nativeMints.has(mint)) {
632
- return { kind: 'native', mint, owner: addresses.push(owner) - 1 }
743
+ ;(isSmallOwner(scope, owner) ? addresses : owners).push(owner)
744
+ return { kind: 'native', mint, owner }
633
745
  }
634
746
  if (!isAddress(mint)) {
635
747
  return { kind: 'invalid', mint }
636
748
  }
637
749
  const meta = getMeta(scope, mint)
638
- if (isExcluded(meta.tokenProgram, allowed)) {
750
+ // 导入的缓存里的所属程序没确认过:按未知处理(记错时不会让余额恒为 0)
751
+ const program = meta.programUnverified ? undefined : meta.tokenProgram
752
+ if (isExcluded(program, allowed)) {
639
753
  return { kind: 'excluded', mint }
640
754
  }
641
- const needMint = meta.decimals === undefined || meta.tokenProgram === undefined || (withSymbol && meta.symbol === undefined)
755
+ const needMint = meta.decimals === undefined || program === undefined || (withSymbol && meta.symbol === undefined)
642
756
  // 所属程序未知时只推导允许的程序的 ATA,读到 mint 账户后再确认
643
- const programs = meta.tokenProgram ? [meta.tokenProgram] : allowed
757
+ const programs = program ? [program] : allowed
644
758
  return {
645
759
  kind: 'token',
646
760
  mint,
@@ -650,9 +764,16 @@ export class SolanaClient {
650
764
  }
651
765
  }
652
766
 
653
- #ataResult(scope: string, item: AtaItem, accounts: readonly (AccountInfo | null)[], withSymbol: boolean, allowed: readonly string[]): TokenBalance[] {
767
+ #ataResult(
768
+ scope: string,
769
+ item: AtaItem,
770
+ accounts: readonly (AccountInfo | null)[],
771
+ lamportsOf: (owner: string) => bigint,
772
+ withSymbol: boolean,
773
+ allowed: readonly string[],
774
+ ): TokenBalance[] {
654
775
  if (item.kind === 'native') {
655
- const lamports = accounts[item.owner]?.lamports ?? 0n
776
+ const lamports = lamportsOf(item.owner)
656
777
  return [
657
778
  withSymbolField(
658
779
  { token: item.mint, native: true, balance: lamports.toString(), decimals: SOL_DECIMALS, formatted: formatUnits(lamports, SOL_DECIMALS), tokenProgram: null, success: true },
@@ -675,21 +796,24 @@ export class SolanaClient {
675
796
  item.metadataIndex === -1 ? null : (accounts[item.metadataIndex] ?? null),
676
797
  item.metadataIndex !== -1 && item.mintIndex !== -1,
677
798
  )
678
- // 代币账户由哪个程序持有就是哪个;mint 账户的 owner 也能确定程序
799
+ // 代币账户由哪个程序持有就是哪个;mint 账户的 owner 也能确定程序。
800
+ // 所属程序未知时推导了两个程序的 ATA,但一个 mint 只属于一个程序,另一个程序下不可能有这个 mint 的代币账户:找到就结束
679
801
  let amount = 0n
680
802
  for (const ata of item.atas) {
681
803
  const account = accounts[ata.index]
682
- if (account && account.owner === ata.program) {
683
- try {
684
- amount = parseTokenAccount(account.data).amount
685
- if (meta.tokenProgram === undefined) {
686
- setMeta(scope, item.mint, { tokenProgram: ata.program })
687
- meta.tokenProgram = ata.program
688
- }
689
- } catch {
690
- // 不是代币账户
691
- }
804
+ if (!account || account.owner !== ata.program) {
805
+ continue
692
806
  }
807
+ try {
808
+ amount = parseTokenAccount(account.data).amount
809
+ } catch {
810
+ continue // 不是代币账户
811
+ }
812
+ if (meta.tokenProgram === undefined) {
813
+ setMeta(scope, item.mint, { tokenProgram: ata.program })
814
+ meta.tokenProgram = ata.program
815
+ }
816
+ break
693
817
  }
694
818
  // 读到 mint 账户后才知道是不允许的程序
695
819
  if (isExcluded(meta.tokenProgram, allowed)) {
@@ -717,7 +841,12 @@ export class SolanaClient {
717
841
  }
718
842
 
719
843
  /** 扫描持有人的 SOL 余额和全部代币账户(默认 Token + Token-2022,只请求 programs 里的程序),同一代币的多个账户合计 */
720
- async #scanHoldings(ctx: QueryContext, owner: string, programs: readonly string[] = TOKEN_PROGRAMS): Promise<{ sol: bigint; held: Map<string, Holding> }> {
844
+ async #scanHoldings(
845
+ ctx: QueryContext,
846
+ owner: string,
847
+ programs: readonly string[] = TOKEN_PROGRAMS,
848
+ withSymbol = false,
849
+ ): Promise<{ sol: bigint; held: Map<string, Holding> }> {
721
850
  const scope = this.#scope()
722
851
  const [lamports, ...results] = await Promise.all([
723
852
  this.#call<{ value: number | string }>(ctx, 'getBalance', [owner, { commitment: this.#commitment }]),
@@ -725,22 +854,47 @@ export class SolanaClient {
725
854
  this.#call<{ value: ParsedTokenAccount[] }>(ctx, 'getTokenAccountsByOwner', [owner, { programId }, { encoding: 'jsonParsed', commitment: this.#commitment }]),
726
855
  ),
727
856
  ])
857
+ const entries = programs.flatMap((program, i) => (results[i]?.value ?? []).map((entry) => ({ program, entry })))
858
+ const held = await this.#collectHoldings(ctx, scope, entries, withSymbol)
859
+ return { sol: BigInt(lamports.value), held }
860
+ }
861
+
862
+ /**
863
+ * 把 getTokenAccountsByOwner(jsonParsed)的结果按 mint 汇总。节点解析不了、退回 base64 的账户自己解析,
864
+ * decimals 读 mint 账户补上(不丢弃,也不会把余额算成 0);mint 读不到 decimals 时才跳过
865
+ */
866
+ async #collectHoldings(
867
+ ctx: QueryContext,
868
+ scope: string,
869
+ entries: ReadonlyArray<{ program: string | undefined; entry: ParsedTokenAccount }>,
870
+ withSymbol = false,
871
+ ): Promise<Map<string, Holding>> {
872
+ const read = entries.flatMap(({ program, entry }) => {
873
+ const token = readTokenEntry(entry)
874
+ return token ? [{ ...token, program: entry.account?.owner || program }] : []
875
+ })
876
+ read.forEach(({ mint, decimals, program }) => decimals !== undefined && program !== undefined && setMeta(scope, mint, { decimals, tokenProgram: program }))
877
+ // 缺 decimals 或所属程序(节点没给账户 owner)的读 mint 账户补上;需要 symbol 时一起读,后面不必再读一遍
878
+ const unknown = [
879
+ ...new Set(read.filter((t) => getMeta(scope, t.mint).decimals === undefined || (t.program === undefined && getMeta(scope, t.mint).tokenProgram === undefined)).map((t) => t.mint)),
880
+ ]
881
+ if (unknown.length) {
882
+ // 补读 mint 不受 minContextSlot 约束、不计入扫描的 slot(decimals / 所属程序不会因为刚成交而变化);
883
+ // 节点问题时不让整次扫描失败,读不到的代币跳过
884
+ await this.#loadMeta({ signal: ctx.signal }, scope, unknown, withSymbol, true)
885
+ }
728
886
  const held = new Map<string, Holding>()
729
- programs.forEach((program, i) => {
730
- for (const entry of results[i]?.value ?? []) {
731
- const info = entry.account?.data?.parsed?.info
732
- const mint = info?.mint
733
- const decimals = info?.tokenAmount?.decimals
734
- if (!mint || decimals === undefined) {
735
- continue
736
- }
737
- const prev = held.get(mint)
738
- const amount = BigInt(info?.tokenAmount?.amount ?? '0')
739
- held.set(mint, { amount: (prev?.amount ?? 0n) + amount, decimals, program, accounts: (prev?.accounts ?? 0) + 1 })
740
- setMeta(scope, mint, { decimals, tokenProgram: program })
887
+ for (const { mint, amount, program: entryProgram } of read) {
888
+ const meta = getMeta(scope, mint)
889
+ const decimals = meta.decimals
890
+ const program = entryProgram ?? (meta.programUnverified ? undefined : meta.tokenProgram)
891
+ if (decimals === undefined || program === undefined) {
892
+ continue
741
893
  }
742
- })
743
- return { sol: BigInt(lamports.value), held }
894
+ const prev = held.get(mint)
895
+ held.set(mint, { amount: (prev?.amount ?? 0n) + amount, decimals, program, accounts: (prev?.accounts ?? 0) + 1 })
896
+ }
897
+ return held
744
898
  }
745
899
 
746
900
  /**
@@ -761,7 +915,7 @@ export class SolanaClient {
761
915
  const { metadata = true, includeNative = true, includeZero = false, includeNfts = false } = options
762
916
  const scope = this.#scope()
763
917
  const ctx = this.#context(options)
764
- const { sol, held } = await this.#scanHoldings(ctx, owner, resolvePrograms(options.tokenPrograms))
918
+ const { sol, held } = await this.#scanHoldings(ctx, owner, resolvePrograms(options.tokenPrograms), metadata)
765
919
  const scanSlot = ctx.slot
766
920
  // Wrapped SOL 是独立的代币账户(需要 unwrap 才是 SOL),资产列表里单独列出(native: false)
767
921
  const tokens = [...held].filter(([, h]) => (includeZero || h.amount > 0n) && (includeNfts || !isNftHolding(h)))
@@ -797,7 +951,7 @@ export class SolanaClient {
797
951
  async #scanBalances(ctx: QueryContext, owner: string, mints: readonly string[] | undefined, withSymbol: boolean, allowed: readonly string[]): Promise<TokenBalance[]> {
798
952
  const scope = this.#scope()
799
953
  // held 里只有允许的程序的代币
800
- const { sol, held } = await this.#scanHoldings(ctx, owner, allowed)
954
+ const { sol, held } = await this.#scanHoldings(ctx, owner, allowed, withSymbol)
801
955
 
802
956
  // Wrapped SOL(So111…112)默认按原生 SOL 处理:余额列表里只有一项 SOL,不再列出 wSOL 代币账户
803
957
  // 第一项是主币:用 nativeMints 里的地址(nativeMints 不含 So111…112 时 wSOL 作为普通代币列出,不能再拿它当 SOL)
@@ -834,7 +988,15 @@ export class SolanaClient {
834
988
  */
835
989
  async #mintAccountBalances(ctx: QueryContext, owner: string, mints: readonly string[], withSymbol: boolean, allowed: readonly string[]): Promise<TokenBalance[]> {
836
990
  const scope = this.#scope()
837
- const queried = [...new Set(mints.filter((mint) => !this.#nativeMints.has(mint) && isAddress(mint) && !isExcluded(getMeta(scope, mint).tokenProgram, allowed)))]
991
+ // 导入的、未经链上确认的所属程序不用来排除代币(可能记错):照常查询,从链上学到后再按程序过滤
992
+ const queried = [
993
+ ...new Set(
994
+ mints.filter((mint) => {
995
+ const meta = getMeta(scope, mint)
996
+ return !this.#nativeMints.has(mint) && isAddress(mint) && (meta.programUnverified || !isExcluded(meta.tokenProgram, allowed))
997
+ }),
998
+ ),
999
+ ]
838
1000
  const [lamports, ...results] = await Promise.all([
839
1001
  mints.some((mint) => this.#nativeMints.has(mint))
840
1002
  ? this.#call<{ value: number | string }>(ctx, 'getBalance', [owner, { commitment: this.#commitment }])
@@ -849,20 +1011,12 @@ export class SolanaClient {
849
1011
  ),
850
1012
  ),
851
1013
  ])
852
- const held = new Map<string, Holding>()
853
- queried.forEach((mint, i) => {
854
- for (const entry of results[i]?.value ?? []) {
855
- const decimals = entry.account?.data?.parsed?.info?.tokenAmount?.decimals
856
- const program = entry.account?.owner
857
- if (decimals === undefined || !program) {
858
- continue
859
- }
860
- const prev = held.get(mint)
861
- const amount = BigInt(entry.account.data.parsed?.info?.tokenAmount?.amount ?? '0')
862
- held.set(mint, { amount: (prev?.amount ?? 0n) + amount, decimals, program, accounts: (prev?.accounts ?? 0) + 1 })
863
- setMeta(scope, mint, { decimals, tokenProgram: program })
864
- }
865
- })
1014
+ const held = await this.#collectHoldings(
1015
+ ctx,
1016
+ scope,
1017
+ queried.flatMap((_, i) => (results[i]?.value ?? []).map((entry) => ({ program: entry.account?.owner, entry }))),
1018
+ withSymbol,
1019
+ )
866
1020
  // 没持有的代币补 decimals(同时确认 mint 是否存在),需要时补 symbol
867
1021
  const failures = await this.#loadMeta(ctx, scope, queried, withSymbol)
868
1022
  const sol = BigInt(lamports?.value ?? 0)
@@ -1241,7 +1395,9 @@ function safeParseMint(account: AccountInfo): MintInfo | null {
1241
1395
  return null
1242
1396
  }
1243
1397
  try {
1244
- return parseMint(account.data)
1398
+ const mint = parseMint(account.data)
1399
+ // 未初始化的 mint(创建和初始化分在两笔交易里,恰好在中间读到):decimals 还是 0,不能当真、更不能永久缓存
1400
+ return mint.isInitialized ? mint : null
1245
1401
  } catch {
1246
1402
  return null
1247
1403
  }
@@ -1278,6 +1434,9 @@ function setMeta(scope: string, mint: string, meta: TokenMeta): void {
1278
1434
  }
1279
1435
  const key = `${scope}:${mint}`
1280
1436
  const merged = { ...tokenMetaCache.get(key), ...defined }
1437
+ if ('tokenProgram' in defined && !defined.programUnverified) {
1438
+ delete merged.programUnverified // 从链上学到了所属程序
1439
+ }
1281
1440
  tokenMetaCache.delete(key) // 重新插入,保持“最近写入在后”的顺序
1282
1441
  tokenMetaCache.set(key, merged)
1283
1442
  if (tokenMetaCache.size > MAX_CACHED_TOKENS) {