import { ZoneRpcAuthentication } from 'ox/tempo' import { defineChain, LimitExceededRpcError, publicActions, RpcRequestError, type Chain, } from 'viem' import { tempoTestnet } from 'viem/tempo/chains' import * as TestRuntime from '../../test/runtime.js' import * as TestViem from '../../test/Viem.js' import type * as Auth from './Auth.js' import * as Viem from './Viem.js' /** Anonymous (public) request principal. */ const publicPrincipal: Auth.Principal = { id: 'pub', type: 'public' } /** Authenticated API-key request principal. */ const apiKeyPrincipal: Auth.Principal = { apiKey: {} as never, environment: 'production', id: 'key_test', orgId: 'org_test', type: 'api_key', } const zone = defineChain({ ...tempoTestnet, contracts: { ...tempoTestnet.contracts, messenger: { address: '0x0000000000000000000000000000000000000001' }, portal: { address: '0x0000000000000000000000000000000000000002' }, }, id: 421_700_001, name: 'Zone E', rpcUrls: { default: { http: ['https://zone.rpc.test'] }, internal: { http: ['https://internal.zone.rpc.test'] }, }, sourceId: tempoTestnet.id, }) satisfies Chain describe('isMainnet', () => { test('is true only for the mainnet chain id', () => { expect(Viem.isMainnet(Viem.chainId.mainnet)).toBe(true) expect(Viem.isMainnet(Viem.chainId.testnet)).toBe(false) expect(Viem.isMainnet(31337)).toBe(false) }) test('mainnet is the default chain', () => { expect(Viem.defaultChainId).toBe(Viem.chainId.mainnet) }) }) describe('configuredChainIds', () => { test('returns ids from object and serialized URL maps', () => { expect(Viem.configuredChainIds({ 31_318: 'https://nextfork.tidx.test' })).toEqual([31_318]) expect( Viem.configuredChainIds( JSON.stringify({ 31_318: 'https://nextfork.tidx.test', 421_700_002: 'https://zone.tidx.test', }), ), ).toEqual([31_318, 421_700_002]) }) test('returns no ids for shared URLs and dynamic resolvers', () => { expect(Viem.configuredChainIds('https://tidx.test')).toEqual([]) expect(Viem.configuredChainIds(() => 'https://tidx.test')).toEqual([]) }) test('rejects malformed chain-id maps', () => { for (const chainId of ['-1', '0', '031318', '0x7a56', '1e3', '9007199254740992', 'devnet']) expect(() => Viem.configuredChainIds(JSON.stringify({ [chainId]: 'https://tidx.test' })), ).toThrow('Expected a URL or a chain-id-to-URL JSON object.') }) }) describe('requestFailure', () => { test('returns bounded JSON-RPC diagnostics', () => { const apiKey = `tempo:sk:${'ab'.repeat(28)}` expect( Viem.requestFailure( new RpcRequestError({ body: { method: 'eth_call' }, error: { code: -32_000, message: `${apiKey}${'x'.repeat(500)}` }, url: 'https://credential.example.com', }), ), ).toEqual({ code: 'RpcRequestError:-32000', failure: 'query', message: `tempo:sk:…${'x'.repeat(490)}`, }) }) test('attributes EVM client failures to their chain', async () => { const client = Viem.createEvmClient({ chainId: 8453, fetch: async () => new Response( JSON.stringify({ error: { code: -32_000, message: 'upstream failure' }, id: 1, jsonrpc: '2.0', }), { headers: { 'content-type': 'application/json' } }, ), urls: ['https://base.rpc.test'], }).extend(publicActions) const cause = await client.getChainId().catch((error: unknown) => error) expect(Viem.requestFailure(cause)).toEqual({ chainId: 8453, code: 'InvalidInputRpcError:-32000', failure: 'query', message: 'upstream failure', }) }) test.each([ { cause: new LimitExceededRpcError( new RpcRequestError({ body: { method: 'eth_call' }, error: { code: -32_005, message: 'rate limited' }, url: 'https://base.rpc.test', }), ), code: 'LimitExceededRpcError:-32005', }, { cause: new RpcRequestError({ body: { method: 'eth_call' }, error: { code: 429, message: 'rate limited' }, url: 'https://base.rpc.test', }), code: 'RpcRequestError:429', }, ])('classifies JSON-RPC limit errors as rate limits %#', ({ cause, code }) => { expect(Viem.requestFailure(cause)).toEqual({ code, failure: 'rate_limit', message: 'rate limited', }) }) }) describe('resolveRpc', () => { test('never infers a public endpoint from Zone metadata', () => { for (const rpc of [undefined, {}, { publicZoneUrl: {} }, () => ({})]) expect(Viem.resolveRpc(rpc, { chainId: zone.id, principal: publicPrincipal, zone })) .toMatchInlineSnapshot(` { "basicAuth": undefined, "url": "https://internal.zone.rpc.test", } `) }) test('rejects public URLs that identify a configured or metadata internal endpoint', () => { for (const publicZoneUrl of [ 'https://internal.zone.rpc.test', 'https://INTERNAL.ZONE.RPC.TEST:443/?public=true#rpc', 'https://internal.zone.rpc.test/public', 'https://override.zone.rpc.test', 'https://override.zone.rpc.test/', 'https://user:secret@public.zone.rpc.test', 'not a URL', 'file:///rpc', ]) expect( Viem.resolveRpc( { publicZoneUrl, url: 'https://override.zone.rpc.test' }, { chainId: zone.id, principal: publicPrincipal, zone }, ).publicZoneUrl, ).toBeUndefined() }) test('canonicalizes internal hostnames before comparing public endpoints', () => { for (const [url, publicZoneUrl] of [ ['https://internal.zone.rpc.test', 'https://internal.zone.rpc.test.'], ['https://internal.zone.rpc.test.', 'https://internal.zone.rpc.test'], ['https://internal.zone.rpc.test:8443', 'https://INTERNAL.ZONE.RPC.TEST.:8443/public'], ['https://internal.zone.rpc.test', 'https://internal.zone.rpc.test.:443'], ['https://override.zone.rpc.test.', 'http://override.zone.rpc.test:443'], ]) expect( Viem.resolveRpc( { publicZoneUrl, url }, { chainId: zone.id, principal: publicPrincipal, zone }, ).publicZoneUrl, ).toBeUndefined() expect( Viem.resolveRpc( { publicZoneUrl: 'https://internal.zone.rpc.test.:8443' }, { chainId: zone.id, principal: publicPrincipal, zone }, ).publicZoneUrl, ).toBe('https://internal.zone.rpc.test.:8443') }) test('compares effective ports across public and internal RPC schemes', () => { for (const [url, publicZoneUrl, allowed] of [ ['http://shared.zone.test', 'https://shared.zone.test', true], ['https://shared.zone.test', 'http://shared.zone.test', true], ['http://shared.zone.test:80', 'https://shared.zone.test:443', true], ['http://shared.zone.test', 'https://shared.zone.test:80', false], ['https://shared.zone.test:80', 'http://shared.zone.test', false], ['https://shared.zone.test', 'http://shared.zone.test:443', false], ['http://shared.zone.test:443', 'https://shared.zone.test', false], ['http://shared.zone.test.', 'http://shared.zone.test:80', false], ['https://shared.zone.test.', 'https://shared.zone.test:443', false], ] as const) expect( Viem.resolveRpc( { publicZoneUrl, url }, { chainId: zone.id, principal: publicPrincipal, zone }, ).publicZoneUrl, ).toBe(allowed ? publicZoneUrl : undefined) }) test('defaults to the per-chain RPC URL', () => { expect(Viem.resolveRpc(undefined, { chainId: 4217, principal: null })).toMatchInlineSnapshot(` { "basicAuth": undefined, "url": "https://rpc.tempo.xyz", } `) expect(Viem.resolveRpc(undefined, { chainId: 42431, principal: null })).toMatchInlineSnapshot(` { "basicAuth": undefined, "url": "https://rpc.testnet.tempo.xyz", } `) }) test('uses a static config override', () => { expect( Viem.resolveRpc( { auth: 'user:pass', url: (chainId) => `https://${chainId}.rpc.test` }, { chainId: 4217, principal: null }, ), ).toMatchInlineSnapshot(` { "basicAuth": "user:pass", "url": "https://4217.rpc.test", } `) }) test('selects RPC URLs by chain id', () => { const rpc = { publicZoneUrl: JSON.stringify({ 421_700_001: 'https://public.zone.rpc.test' }), url: JSON.stringify({ 4217: 'https://mainnet.rpc.test' }), } expect(Viem.resolveRpc(rpc, { chainId: 4217, principal: null })).toMatchInlineSnapshot(` { "basicAuth": undefined, "url": "https://mainnet.rpc.test", } `) expect(Viem.resolveRpc(rpc, { chainId: zone.id, principal: null, zone })) .toMatchInlineSnapshot(` { "basicAuth": undefined, "publicZoneUrl": "https://public.zone.rpc.test", "url": "https://internal.zone.rpc.test", } `) }) test('selects auth and scheme by chain id', () => { const rpc = { auth: { 4217: 'mainnet:secret', 42431: 'testnet-token' } } expect(Viem.resolveRpc(rpc, { chainId: 4217, principal: null }).basicAuth).toBe( 'mainnet:secret', ) expect(Viem.resolveRpc(rpc, { chainId: 42431, principal: null }).bearerAuth).toBe( 'testnet-token', ) expect( Viem.resolveRpc(rpc, { chainId: 421_700_001, principal: null }).basicAuth, ).toBeUndefined() }) test('parses serialized Basic auth by chain id', () => { const rpc = { auth: JSON.stringify({ 4217: 'mainnet:secret' }) } expect(Viem.resolveRpc(rpc, { chainId: 4217, principal: null }).basicAuth).toBe( 'mainnet:secret', ) expect(Viem.resolveRpc(rpc, { chainId: 42431, principal: null }).basicAuth).toBeUndefined() }) test('the static form ignores the principal', () => { const rpc = { auth: 'user:pass', url: () => 'https://internal.rpc' } expect(Viem.resolveRpc(rpc, { chainId: 4217, principal: publicPrincipal })) .toMatchInlineSnapshot(` { "basicAuth": "user:pass", "url": "https://internal.rpc", } `) expect(Viem.resolveRpc(rpc, { chainId: 4217, principal: apiKeyPrincipal })) .toMatchInlineSnapshot(` { "basicAuth": "user:pass", "url": "https://internal.rpc", } `) }) test('the resolver form routes anonymous callers to a separate upstream', () => { const rpc = ({ principal }: Viem.resolveRpc.Context) => principal?.type === 'public' ? { url: 'https://rpc.tempo.xyz' } : { auth: 'user:pass', url: 'https://internal.rpc' } expect(Viem.resolveRpc(rpc, { chainId: 4217, principal: publicPrincipal })) .toMatchInlineSnapshot(` { "basicAuth": undefined, "url": "https://rpc.tempo.xyz", } `) expect(Viem.resolveRpc(rpc, { chainId: 4217, principal: apiKeyPrincipal })) .toMatchInlineSnapshot(` { "basicAuth": "user:pass", "url": "https://internal.rpc", } `) }) test('a null principal (trusted poller) takes the non-public branch', () => { const rpc = ({ principal }: Viem.resolveRpc.Context) => principal?.type === 'public' ? { url: 'https://rpc.tempo.xyz' } : { auth: 'user:pass', url: 'https://internal.rpc' } expect(Viem.resolveRpc(rpc, { chainId: 4217, principal: null })).toMatchInlineSnapshot(` { "basicAuth": "user:pass", "url": "https://internal.rpc", } `) }) test('a resolver that omits url falls back to the built-in public host', () => { const rpc = () => ({}) expect(Viem.resolveRpc(rpc, { chainId: 42431, principal: publicPrincipal })) .toMatchInlineSnapshot(` { "basicAuth": undefined, "url": "https://rpc.testnet.tempo.xyz", } `) }) test('a resolver that omits url falls back to the Zone internal host', () => { expect(Viem.resolveRpc(() => ({}), { chainId: zone.id, principal: publicPrincipal, zone })) .toMatchInlineSnapshot(` { "basicAuth": undefined, "url": "https://internal.zone.rpc.test", } `) }) }) describe('getClient', () => { test('rejects a caller token without a public endpoint before constructing a transport', () => { expect(() => Viem.getClient({ chainId: zone.id, rpc: { auth: 'internal:secret' }, zone, zoneToken: 'unvalidated-token', }), ).toThrowErrorMatchingInlineSnapshot( `[Viem.PublicZoneRpcUnavailableError: Public Zone RPC is unavailable.]`, ) }) test('disables CCIP-Read', () => { expect(Viem.getClient({ chainId: 4217 }).ccipRead).toBe(false) }) test('disables multicall for Zones', () => { const l1Client = Viem.getClient({ chainId: 4217 }) const zoneClient = Viem.getClient({ chainId: zone.id, zone }) expect(l1Client.batch?.multicall).toEqual({ deployless: true }) expect(zoneClient.batch?.multicall).toBe(false) }) test('sends inferred Bearer auth', async () => { const fetch = vi .spyOn(globalThis, 'fetch') .mockResolvedValue(Response.json({ id: 1, jsonrpc: '2.0', result: '0x1079' })) try { const client = Viem.getClient({ chainId: 4217, rpc: { auth: 'rpc-token', url: 'https://internal.rpc.test' }, }) await client.getChainId() const [input, init] = fetch.mock.calls[0]! const upstream = new Request(input, init) expect(upstream.headers.get('authorization')).toBe('Bearer rpc-token') } finally { fetch.mockRestore() } }) test('does not send RPC auth to a public Zone URL', async () => { const fetch = vi .spyOn(globalThis, 'fetch') .mockResolvedValue(Response.json({ id: 1, jsonrpc: '2.0', result: '0x1079' })) try { const client = Viem.getClient({ chainId: zone.id, rpc: { auth: 'rpc:secret', publicZoneUrl: 'https://public.zone.rpc.test', url: 'https://internal.rpc.test', }, zone, zoneToken: 'zone-token', }) await client.getChainId() const [input, init] = fetch.mock.calls[0]! const upstream = new Request(input, init) expect(upstream.url).toBe('https://public.zone.rpc.test/') expect(upstream.headers.get('authorization')).toBeNull() expect(upstream.headers.get(ZoneRpcAuthentication.headerName)).toBe('zone-token') } finally { fetch.mockRestore() } }) test('sends Zone headers to both internal and public Zone URLs', async () => { const fetch = vi .spyOn(globalThis, 'fetch') .mockResolvedValue(Response.json({ id: 1, jsonrpc: '2.0', result: '0x1079' })) try { const client = Viem.getClient({ chainId: zone.id, rpc: { publicZoneUrl: 'https://public.zone.rpc.test', zoneHeaders: { 'CF-Access-Client-Id': 'client-id', 'CF-Access-Client-Secret': 'client-secret', }, }, zone, zoneToken: 'zone-token', }) await client.getChainId() const [input, init] = fetch.mock.calls[0]! const upstream = new Request(input, init) expect(upstream.headers.get('CF-Access-Client-Id')).toBe('client-id') expect(upstream.headers.get('CF-Access-Client-Secret')).toBe('client-secret') } finally { fetch.mockRestore() } }) }) describe('createBatchClient', () => { test('preserves caller cancellation without cancelling unrelated requests', async () => { const client = Viem.createBatchClient({ chainId: TestRuntime.get().chainId, rpc: TestViem.rpc, }) const controller = new AbortController() controller.abort() const [cancelled, active] = await Promise.allSettled([ client.request({ method: 'eth_blockNumber' }, { retryCount: 0, signal: controller.signal }), client.request({ method: 'eth_blockNumber' }), ]) expect({ active: active.status, cancelled: cancelled.status }).toMatchInlineSnapshot(` { "active": "fulfilled", "cancelled": "rejected", } `) if (cancelled.status === 'rejected') expect(cancelled.reason).toHaveProperty('name', 'AbortError') }) test('rejects caller tokens when the public endpoint resolves to the internal node', () => { expect(() => Viem.createBatchClient({ chainId: zone.id, rpc: { publicZoneUrl: 'https://internal.zone.rpc.test' }, zone, zoneToken: 'unvalidated-token', }), ).toThrowErrorMatchingInlineSnapshot( `[Viem.PublicZoneRpcUnavailableError: Public Zone RPC is unavailable.]`, ) }) }) describe('createGetClient', () => { test('memoizes one client per resolved upstream', () => { const getClient = Viem.createGetClient({ rpc: ({ principal }) => principal?.type === 'public' ? { url: 'https://rpc.tempo.xyz' } : { auth: 'user:pass', url: 'https://internal.rpc' }, }) expect(getClient(4217, apiKeyPrincipal)).toBe(getClient(4217, apiKeyPrincipal)) expect(getClient(4217, publicPrincipal)).toBe(getClient(4217, publicPrincipal)) expect(getClient(4217, publicPrincipal)).not.toBe(getClient(4217, apiKeyPrincipal)) expect(getClient(4217, apiKeyPrincipal)).not.toBe(getClient(42431, apiKeyPrincipal)) }) test('shares a client across principals when they resolve identically', () => { const getClient = Viem.createGetClient() expect(getClient(4217, publicPrincipal)).toBe(getClient(4217, apiKeyPrincipal)) expect(getClient(4217, null)).toBe(getClient(4217, publicPrincipal)) }) test('consults the resolver on cache hits', () => { const seen: (string | undefined)[] = [] const getClient = Viem.createGetClient({ rpc: ({ principal }) => { seen.push(principal?.type) return { url: 'https://internal.rpc' } }, }) const first = getClient(4217, apiKeyPrincipal) const second = getClient(4217, apiKeyPrincipal) getClient(4217, publicPrincipal) expect(seen).toMatchInlineSnapshot(` [ "api_key", "api_key", "public", ] `) expect(first).toBe(second) }) })