From f915673a54a2edb460ba0144cfbf57192b085f38 Mon Sep 17 00:00:00 2001 From: Sergo Date: Thu, 21 May 2026 15:16:31 +0100 Subject: [PATCH 1/3] fix: improve tests --- .../use-idl-from-anchor-program-seed.test.ts | 65 +++++++++++++++++++ .../model/use-idl-from-anchor-program-seed.ts | 10 +-- 2 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 app/entities/idl/model/__tests__/use-idl-from-anchor-program-seed.test.ts diff --git a/app/entities/idl/model/__tests__/use-idl-from-anchor-program-seed.test.ts b/app/entities/idl/model/__tests__/use-idl-from-anchor-program-seed.test.ts new file mode 100644 index 000000000..00daa96e9 --- /dev/null +++ b/app/entities/idl/model/__tests__/use-idl-from-anchor-program-seed.test.ts @@ -0,0 +1,65 @@ +import { Program } from '@coral-xyz/anchor'; +import { PublicKey } from '@solana/web3.js'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { Cluster } from '@/app/utils/cluster'; + +import { useIdlFromAnchorProgramSeed } from '../use-idl-from-anchor-program-seed'; + +describe('useIdlFromAnchorProgramSeed', () => { + const url = 'https://any.rpc.address'; + + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('should not fire duplicate /api/anchor requests for the same key while the request is in flight', () => { + const fetchMock = vi.fn(() => new Promise(() => {})); + vi.stubGlobal('fetch', fetchMock); + + const programAddress = PublicKey.unique().toBase58(); + + let firstThrown: unknown; + let secondThrown: unknown; + + try { + useIdlFromAnchorProgramSeed(programAddress, url, Cluster.MainnetBeta); + } catch (e) { + firstThrown = e; + } + try { + useIdlFromAnchorProgramSeed(programAddress, url, Cluster.MainnetBeta); + } catch (e) { + secondThrown = e; + } + + expect(firstThrown).toBeInstanceOf(Promise); + expect(secondThrown).toBe(firstThrown); + expect(fetchMock).toHaveBeenCalledTimes(1); + }); + + it('should not call Program.fetchIdl twice for the same key while the request is in flight on a Custom cluster', () => { + const fetchIdlSpy = vi.spyOn(Program, 'fetchIdl').mockReturnValue(new Promise(() => {})); + + const programAddress = PublicKey.unique().toBase58(); + + let firstThrown: unknown; + let secondThrown: unknown; + + try { + useIdlFromAnchorProgramSeed(programAddress, url, Cluster.Custom); + } catch (e) { + firstThrown = e; + } + try { + useIdlFromAnchorProgramSeed(programAddress, url, Cluster.Custom); + } catch (e) { + secondThrown = e; + } + + expect(firstThrown).toBeInstanceOf(Promise); + expect(secondThrown).toBe(firstThrown); + expect(fetchIdlSpy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/app/entities/idl/model/use-idl-from-anchor-program-seed.ts b/app/entities/idl/model/use-idl-from-anchor-program-seed.ts index 68ad42f21..1f9f34f07 100644 --- a/app/entities/idl/model/use-idl-from-anchor-program-seed.ts +++ b/app/entities/idl/model/use-idl-from-anchor-program-seed.ts @@ -15,6 +15,10 @@ export function getProvider(url: string) { return new AnchorProvider(new Connection(url), new NodeWallet(Keypair.generate()), {}); } +function recordInFlight(key: string, promise: Promise) { + cachedAnchorProgramPromises[key] = { __type: 'promise', promise }; +} + export function useIdlFromAnchorProgramSeed(programAddress: string, url: string, cluster?: Cluster): Idl | null { const key = `${programAddress}-${url}`; const cacheEntry = cachedAnchorProgramPromises[key]; @@ -43,6 +47,7 @@ export function useIdlFromAnchorProgramSeed(programAddress: string, url: string, .catch(_ => { cachedAnchorProgramPromises[key] = { __type: 'result', result: null }; }); + recordInFlight(key, promise); } else { const programId = new PublicKey(programAddress); promise = Program.fetchIdl(programId, getProvider(url)) @@ -59,10 +64,7 @@ export function useIdlFromAnchorProgramSeed(programAddress: string, url: string, .catch(_ => { cachedAnchorProgramPromises[key] = { __type: 'result', result: null }; }); - cachedAnchorProgramPromises[key] = { - __type: 'promise', - promise, - }; + recordInFlight(key, promise); } throw promise; } else if (cacheEntry.__type === 'promise') { From b5cfb30a8a42d213632661c2a1795edcae0681f5 Mon Sep 17 00:00:00 2001 From: Sergo Date: Thu, 21 May 2026 15:31:28 +0100 Subject: [PATCH 2/3] chore: collect build info --- bench/BUILD.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bench/BUILD.md b/bench/BUILD.md index 3e225d467..f524143f0 100644 --- a/bench/BUILD.md +++ b/bench/BUILD.md @@ -4,19 +4,19 @@ |------|-------|------|---------------| | Static | `/` | 20 kB | 1.24 MB | | Static | `/_not-found` | 370 B | 190 kB | -| Dynamic | `/address/[address]` | 20 kB | 1.16 MB | +| Dynamic | `/address/[address]` | 20 kB | 1.30 MB | | Dynamic | `/address/[address]/anchor-account` | 10 kB | 1.22 MB | | Dynamic | `/address/[address]/anchor-program` | 380 B | 1.07 MB | | Dynamic | `/address/[address]/attestation` | 10 kB | 1.18 MB | | Dynamic | `/address/[address]/attributes` | 10 kB | 1.14 MB | -| Dynamic | `/address/[address]/blockhashes` | 10 kB | 1.13 MB | +| Dynamic | `/address/[address]/blockhashes` | 10 kB | 1.14 MB | | Dynamic | `/address/[address]/compression` | 10 kB | 1.17 MB | | Dynamic | `/address/[address]/concurrent-merkle-tree` | 10 kB | 1.17 MB | -| Dynamic | `/address/[address]/domains` | 10 kB | 1.15 MB | +| Dynamic | `/address/[address]/domains` | 10 kB | 1.16 MB | | Dynamic | `/address/[address]/entries` | 10 kB | 1.16 MB | | Dynamic | `/address/[address]/feature-gate` | 380 B | 1.07 MB | | Dynamic | `/address/[address]/idl` | 160 kB | 1.47 MB | -| Dynamic | `/address/[address]/instructions` | 10 kB | 1.25 MB | +| Dynamic | `/address/[address]/instructions` | 10 kB | 1.26 MB | | Dynamic | `/address/[address]/metadata` | 10 kB | 1.15 MB | | Dynamic | `/address/[address]/nftoken-collection-nfts` | 10 kB | 1.21 MB | | Dynamic | `/address/[address]/program-multisig` | 10 kB | 1.21 MB | @@ -25,13 +25,13 @@ | Dynamic | `/address/[address]/slot-hashes` | 10 kB | 1.14 MB | | Dynamic | `/address/[address]/stake-history` | 10 kB | 1.14 MB | | Dynamic | `/address/[address]/token-extensions` | 20 kB | 1.22 MB | -| Dynamic | `/address/[address]/tokens` | 20 kB | 1.35 MB | +| Dynamic | `/address/[address]/tokens` | 20 kB | 1.34 MB | | Dynamic | `/address/[address]/transfers` | 10 kB | 1.28 MB | | Dynamic | `/address/[address]/verified-build` | 10 kB | 1.21 MB | | Dynamic | `/address/[address]/vote-history` | 10 kB | 1.14 MB | | Dynamic | `/api/anchor` | 370 B | 190 kB | | Dynamic | `/api/ans-domains/[address]` | 370 B | 190 kB | -| Dynamic | `/api/domain-info/[domain]` | 10 kB | 1.15 MB | +| Dynamic | `/api/domain-info/[domain]` | 10 kB | 1.16 MB | | Dynamic | `/api/geo-location` | 370 B | 190 kB | | Dynamic | `/api/metadata/proxy` | 370 B | 190 kB | | Dynamic | `/api/ping/[network]` | 370 B | 190 kB | @@ -46,7 +46,7 @@ | Dynamic | `/api/verified-programs/list/[page]` | 370 B | 190 kB | | Dynamic | `/api/verified-programs/metadata/[programId]` | 370 B | 190 kB | | Dynamic | `/block/[slot]` | 10 kB | 1.23 MB | -| Dynamic | `/block/[slot]/accounts` | 10 kB | 1.15 MB | +| Dynamic | `/block/[slot]/accounts` | 10 kB | 1.16 MB | | Dynamic | `/block/[slot]/programs` | 10 kB | 1.16 MB | | Dynamic | `/block/[slot]/rewards` | 10 kB | 1.16 MB | | Dynamic | `/epoch/[epoch]` | 10 kB | 290 kB | @@ -57,6 +57,6 @@ | Static | `/supply` | 10 kB | 1.16 MB | | Static | `/tos` | 370 B | 190 kB | | Dynamic | `/tx/[signature]` | 70 kB | 1.68 MB | -| Dynamic | `/tx/[signature]/inspect` | 620 B | 1.44 MB | +| Dynamic | `/tx/[signature]/inspect` | 610 B | 1.44 MB | | Static | `/tx/inspector` | 600 B | 1.44 MB | | Static | `/verified-programs` | 10 kB | 200 kB | \ No newline at end of file From 1332cb2bffed460da442f638940ce25edf5544a4 Mon Sep 17 00:00:00 2001 From: Sergo Date: Wed, 27 May 2026 18:54:41 +0100 Subject: [PATCH 3/3] fix cache issue --- .../__tests__/use-idl-from-anchor-program-seed.test.ts | 3 ++- app/entities/idl/model/use-idl-from-anchor-program-seed.ts | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/entities/idl/model/__tests__/use-idl-from-anchor-program-seed.test.ts b/app/entities/idl/model/__tests__/use-idl-from-anchor-program-seed.test.ts index 00daa96e9..2cfdcb7f1 100644 --- a/app/entities/idl/model/__tests__/use-idl-from-anchor-program-seed.test.ts +++ b/app/entities/idl/model/__tests__/use-idl-from-anchor-program-seed.test.ts @@ -4,7 +4,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { Cluster } from '@/app/utils/cluster'; -import { useIdlFromAnchorProgramSeed } from '../use-idl-from-anchor-program-seed'; +import { resetCacheForTesting, useIdlFromAnchorProgramSeed } from '../use-idl-from-anchor-program-seed'; describe('useIdlFromAnchorProgramSeed', () => { const url = 'https://any.rpc.address'; @@ -12,6 +12,7 @@ describe('useIdlFromAnchorProgramSeed', () => { afterEach(() => { vi.unstubAllGlobals(); vi.restoreAllMocks(); + resetCacheForTesting(); }); it('should not fire duplicate /api/anchor requests for the same key while the request is in flight', () => { diff --git a/app/entities/idl/model/use-idl-from-anchor-program-seed.ts b/app/entities/idl/model/use-idl-from-anchor-program-seed.ts index 1f9f34f07..734bcde43 100644 --- a/app/entities/idl/model/use-idl-from-anchor-program-seed.ts +++ b/app/entities/idl/model/use-idl-from-anchor-program-seed.ts @@ -19,6 +19,12 @@ function recordInFlight(key: string, promise: Promise) { cachedAnchorProgramPromises[key] = { __type: 'promise', promise }; } +export function resetCacheForTesting() { + for (const k of Object.keys(cachedAnchorProgramPromises)) { + delete cachedAnchorProgramPromises[k]; + } +} + export function useIdlFromAnchorProgramSeed(programAddress: string, url: string, cluster?: Cluster): Idl | null { const key = `${programAddress}-${url}`; const cacheEntry = cachedAnchorProgramPromises[key];