import type { Context } from 'hono' import * as TestApp from '../../../test/App.js' import type * as App from '../../App.js' import * as Db from '../../db/Db.js' import * as core_Catalog from '../../db/tables/fundingCatalog.js' import * as Catalog from './Catalog.js' const data = { chains: [ { aliases: ['eth'], id: 'eip155:1', name: 'Ethereum', parentChainId: null, rpcUrls: ['https://ethereum.example'], slug: 'ethereum', }, { aliases: [], id: 'eip155:4217', name: 'Tempo', parentChainId: null, rpcUrls: ['https://tempo.example'], slug: 'tempo', }, ], chainTokens: [ { address: '0xA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48', chainId: 'eip155:1', decimals: 6, name: null, standard: 'ERC-20', tokenId: 'usdc', }, { address: '0x20C000000000000000000000B9537D11C60E8B50', chainId: 'eip155:4217', decimals: 6, name: null, standard: 'TIP-20', tokenId: 'usdce', }, ], routes: [ { destinationChainId: 'eip155:4217', destinationTokenId: 'usdce', providerId: 'relay', sourceChainId: 'eip155:1', sourceTokenId: 'usdc', }, ], tokens: [ { currency: 'USD', id: 'usdc', name: 'USD Coin', symbol: 'USDC', }, { currency: 'USD', id: 'usdce', name: 'Bridged USDC (Stargate)', symbol: 'USDC.e', }, ], } as const satisfies Catalog.publish.Input function rows(options: Partial = {}): core_Catalog.Rows { return { catalog: { id: 'default', updatedAt: '2026-01-01T00:00:00.000Z', version: 'test', }, ...data, ...options, } } function context(db: Db.Db): Context { return { get: (key: string) => (key === 'dbCached' ? db : undefined), } as unknown as Context } describe('compile', () => { test('hydrates and indexes catalog routes', () => { const snapshot = Catalog.compile(rows()) expect({ chain: snapshot.chainsByKey.get('eth')?.id, destination: snapshot.routes[0]?.route.destination.address, rpcUrls: snapshot.chainsByKey.get('eth')?.rpcUrls, source: snapshot.routes[0]?.route.source.address, token: snapshot.tokensById.get('usdc')?.symbol, }).toMatchInlineSnapshot(` { "chain": "eip155:1", "destination": "0x20c000000000000000000000b9537d11c60e8b50", "rpcUrls": [ "https://ethereum.example", ], "source": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "token": "USDC", } `) }) test('compiles an empty database to an empty catalog', () => { const snapshot = Catalog.compile({ catalog: undefined, chainTokens: [], chains: [], routes: [], tokens: [], }) expect({ routes: snapshot.routes, version: snapshot.version }).toMatchInlineSnapshot(` { "routes": [], "version": "empty", } `) }) test('rejects duplicate chain aliases', () => { expect(() => Catalog.compile( rows({ chains: [ ...data.chains, { aliases: ['ETH'], id: 'eip155:8453', name: 'Base', parentChainId: null, rpcUrls: ['https://base.example'], slug: 'base', }, ], }), ), ).toThrowErrorMatchingInlineSnapshot( `[Funding.Catalog.InvalidCatalogError: Duplicate chain key "ETH".]`, ) }) test('rejects dangling route placements', () => { expect(() => Catalog.compile( rows({ routes: [ { ...data.routes[0], destinationTokenId: 'missing', }, ], }), ), ).toThrowErrorMatchingInlineSnapshot( `[Funding.Catalog.InvalidCatalogError: Unknown route token "missing".]`, ) }) test('rejects malformed chain ids', () => { expect(() => Catalog.compile( rows({ chains: [ { aliases: [], id: 'eip155:not-a-number', name: 'Invalid', parentChainId: null, rpcUrls: [], slug: 'invalid', }, ], }), ), ).toThrowErrorMatchingInlineSnapshot(` [$ZodError: [ { "origin": "string", "code": "invalid_format", "format": "regex", "pattern": "/^(?:eip155:\\\\d+|solana:[^:]+|tron:[^:]+)$/", "path": [ "chains", 0, "id" ], "message": "Invalid string: must match pattern /^(?:eip155:\\\\d+|solana:[^:]+|tron:[^:]+)$/" } ]] `) }) }) describe('snapshot', () => { test('cold-loads a repository publish', async () => { const db = TestApp.database() const catalog = await core_Catalog.publish(db, data) const snapshot = await Catalog.snapshot(context(db)) expect(snapshot.version).toBe(catalog.version) }) test('isolates snapshots by database source', async () => { const first = TestApp.database() const second = TestApp.database() await Catalog.publish(first, data) await Catalog.publish(second, { ...data, routes: [], }) expect((await Catalog.snapshot(context(first))).routes).toHaveLength(1) expect((await Catalog.snapshot(context(second))).routes).toHaveLength(0) }) }) describe('publish', () => { test('round-trips and primes the current isolate', async () => { const db = TestApp.database() const published = await Catalog.publish(db, data) const current = await Catalog.snapshot(context(db)) expect(current).toBe(published) expect((await Catalog.read(db)).version).toBe(published.version) }) test('primes the same route order returned by database reads', async () => { const db = TestApp.database() const published = await Catalog.publish(db, { ...data, routes: [ data.routes[0], { ...data.routes[0], providerId: 'across', }, ], }) expect(published.routes.map((entry) => entry.providerId)).toEqual(['across', 'relay']) expect((await Catalog.read(db)).routes.map((entry) => entry.providerId)).toEqual([ 'across', 'relay', ]) }) test('advances the catalog version', async () => { const db = TestApp.database() const first = await Catalog.publish(db, data) const second = await Catalog.publish(db, { chainTokens: [], chains: [], routes: [], tokens: [], }) expect(second.version > first.version).toBe(true) }) }) describe('route capabilities', () => { const capabilities = { transfer: { modes: ['exactSource'] }, } as const test('carries capabilities into hydrated provider routes', () => { const snapshot = Catalog.compile(rows({ routes: [{ ...data.routes[0]!, capabilities }] })) expect(snapshot.routes[0]?.capabilities).toStrictEqual(capabilities) }) test('omits capabilities for indicative-only routes', () => { const absent = Catalog.compile(rows()) const nulled = Catalog.compile(rows({ routes: [{ ...data.routes[0]!, capabilities: null }] })) expect('capabilities' in absent.routes[0]!).toBe(false) expect('capabilities' in nulled.routes[0]!).toBe(false) }) test('publishes capabilities through the write boundary', async () => { const db = TestApp.database() const snapshot = await Catalog.publish(db, { ...data, routes: [{ ...data.routes[0]!, capabilities }], }) expect(snapshot.routes[0]?.capabilities).toStrictEqual(capabilities) }) test('rejects invalid capability entries', () => { expect(() => Catalog.compile(rows({ routes: [{ ...data.routes[0]!, capabilities: {} }] })), ).toThrow() expect(() => Catalog.compile( rows({ routes: [{ ...data.routes[0]!, capabilities: { transfer: undefined } }] }), ), ).toThrow() expect(() => Catalog.compile( rows({ routes: [{ ...data.routes[0]!, capabilities: { transfer: { modes: [] } } }] }), // prettier-ignore ), ).toThrow() expect(() => Catalog.compile( rows({ routes: [{ ...data.routes[0]!, capabilities: { depositAddress: false as never } }] }), // prettier-ignore ), ).toThrow() }) }) describe('route configuration', () => { const configuration = { poolAddress: `0x${'11'.repeat(20)}` } test('carries configuration into hydrated provider routes', () => { const snapshot = Catalog.compile(rows({ routes: [{ ...data.routes[0]!, configuration }] })) expect(snapshot.routes[0]?.configuration).toStrictEqual(configuration) expect(snapshot.routesByProvider.get('relay')?.[0]?.configuration).toStrictEqual(configuration) }) test('omits configuration when it is absent or null', () => { const absent = Catalog.compile(rows()) const nulled = Catalog.compile(rows({ routes: [{ ...data.routes[0]!, configuration: null }] })) expect('configuration' in absent.routes[0]!).toBe(false) expect('configuration' in nulled.routes[0]!).toBe(false) }) test('publishes configuration through the write boundary', async () => { const db = TestApp.database() const snapshot = await Catalog.publish(db, { ...data, routes: [{ ...data.routes[0]!, configuration }], }) expect(snapshot.routes[0]?.configuration).toStrictEqual(configuration) }) test('rejects non-object configuration', () => { expect(() => Catalog.compile( rows({ routes: [{ ...data.routes[0]!, configuration: 'invalid' as never }] }), ), ).toThrow() }) })