import * as FundingCatalog from '../../../internal/funding/Catalog.js' import * as FundingProvider from '../../../internal/funding/Provider.js' import * as Across from '../../../internal/funding/providers/across.js' import * as Relay from '../../../internal/funding/providers/relay.js' import * as Rhino from '../../../internal/funding/providers/rhino.js' import * as Squid from '../../../internal/funding/providers/squid.js' import * as Symbiosis from '../../../internal/funding/providers/symbiosis.js' import * as Store from '../../../internal/Store.js' import * as TestApp from '../../../../test/App.js' import * as TestFunding from '../../../../test/Funding.js' import * as Funding from './providers.js' const db = TestApp.database() const providers = [ Across.across({ apiKey: 'across-api-key', integratorId: '0xdead', userAddress: '0x1111111111111111111111111111111111111111', }), Relay.relay({ userAddresses: TestFunding.relayQuoteUsers }), Rhino.rhino(), Squid.squid({ integratorId: 'squid-integrator-id', userAddress: '0x1111111111111111111111111111111111111111', }), Symbiosis.symbiosis({ userAddress: '0x1111111111111111111111111111111111111111' }), ] as const beforeAll(() => TestFunding.publish(db)) function create(options: TestApp.create.Options = {}) { return TestApp.create({ ...options, db }) } describe('GET /funding/providers', () => { test('lists supplied quote providers', async () => { const app = create({ providers: [relayProvider()] }) const response = await app.request('/v1/funding/providers', { headers: TestApp.auth.headers, }) expect(response.status).toBe(200) const body = await TestApp.json(response, Funding.schema.getFundingProviders.Response) expect(body.data).toEqual([ { id: 'relay', name: 'Relay', }, ]) }) test('isolates cached inventories between apps', async () => { const store = Store.memory() const relayApp = create({ cache: { store }, providers: [relayProvider()] }) const acrossApp = create({ cache: { store }, providers: [acrossProvider()] }) const relayResponse = await relayApp.request('/v1/funding/providers', { headers: TestApp.auth.headers, }) const acrossResponse = await acrossApp.request('/v1/funding/providers', { headers: TestApp.auth.headers, }) const relayBody = await TestApp.json(relayResponse, Funding.schema.getFundingProviders.Response) const acrossBody = await TestApp.json( acrossResponse, Funding.schema.getFundingProviders.Response, ) expect(relayBody.data.map((provider) => provider.id)).toEqual(['relay']) expect(acrossBody.data.map((provider) => provider.id)).toEqual(['across']) }) test('isolates cached inventories between database sources', async () => { const first = TestApp.database() const second = TestApp.database() const store = Store.memory() await TestFunding.publish(first) await FundingCatalog.publish(second, { ...TestFunding.data, routes: [] }) const firstApp = TestApp.create({ cache: { store }, db: first, providers: [relayProvider()] }) const secondApp = TestApp.create({ cache: { store }, db: second, providers: [relayProvider()] }) const firstResponse = await firstApp.request('/v1/funding/providers', { headers: TestApp.auth.headers, }) const secondResponse = await secondApp.request('/v1/funding/providers', { headers: TestApp.auth.headers, }) const firstBody = await TestApp.json(firstResponse, Funding.schema.getFundingProviders.Response) const secondBody = await TestApp.json( secondResponse, Funding.schema.getFundingProviders.Response, ) expect(firstBody.data.map((provider) => provider.id)).toEqual(['relay']) expect(secondBody.data).toEqual([]) }) test('does not expose the removed generic provider endpoint', async () => { const response = await create().request('/v1/providers', { headers: TestApp.auth.headers, }) expect(response.status).toBe(404) }) }) function relayProvider(onGetQuote: () => void = () => {}) { return withGetQuote('relay', async (input, signal) => { onGetQuote() expect(signal.aborted).toBe(false) expect(input.candidate.id).toBe('base-usdc-tempo-usdce-relay') return { destinationAmountMin: '980000', destinationAmount: '990000', quality: { estimatedSeconds: 4, liquiditySource: 'providerQuote', sourceDetail: 'relay:quote-v2', tier: 'liquid', }, sampledAt: '2026-01-01T00:00:00Z', status: 'available', } }) } function acrossProvider(onGetQuote: () => void = () => {}) { return withGetQuote('across', async (input, signal) => { onGetQuote() expect(signal.aborted).toBe(false) expect(input.candidate.id).toBe('base-usdc-tempo-usdce-across') return { expiresAt: '2026-01-01T00:01:00Z', destinationAmountMin: '988000', destinationAmount: '990000', quality: { estimatedSeconds: 2, liquiditySource: 'providerQuote', sourceDetail: 'across:swap-approval', tier: 'liquid', }, sampledAt: '2026-01-01T00:00:00Z', status: 'available', } }) } function withGetQuote( id: FundingProvider.ProviderId, getQuote: FundingProvider.Provider['getQuote'], ) { const provider = providers.find((entry) => entry.id === id) if (!provider) throw new Error(`Missing ${id} provider`) return FundingProvider.from({ ...provider, getQuote }) }