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 TestApp from '../../../../test/App.js' import * as TestFunding from '../../../../test/Funding.js' import * as Funding from './chains.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('funding OpenAPI', () => { test('publishes generator-ready discovery contracts', async () => { const app = create({ auth: false }) const spec = await (await app.request('/openapi.json')).json() const operations = { chains: spec.paths['/v1/funding/chains'].get, providers: spec.paths['/v1/funding/providers'].get, quotes: spec.paths['/v1/funding/quotes'].get, } const summarize = (operation: (typeof operations)[keyof typeof operations]) => ({ errors: Object.fromEntries( [400, 401, 403, 429, 500, 502].map((status) => [ status, operation.responses[status].$ref ?? operation.responses[status].content?.['application/json']?.schema, ]), ), operationId: operation.operationId, response: operation.responses[200].content['application/json'].schema, }) expect({ components: [ 'FundingChain', 'FundingChainInventory', 'FundingChainList', 'FundingProvider', 'FundingProviderList', 'FundingQuote', 'FundingQuoteDetails', 'FundingQuoteList', 'FundingQuoteQuality', 'FundingToken', ].filter((name) => spec.components.schemas[name]), formats: { quoteExpiresAt: spec.components.schemas.FundingQuoteDetails.properties.expiresAt.format, quoteSampledAt: spec.components.schemas.FundingQuoteDetails.properties.sampledAt.format, }, models: { chainInventoryToken: spec.components.schemas.FundingChainInventory.properties.tokens.items, chainList: spec.components.schemas.FundingChainList.properties.data.items, providerList: spec.components.schemas.FundingProviderList.properties.data.items, quote: { destinationChain: spec.components.schemas.FundingQuote.properties.destinationChain, destinationToken: spec.components.schemas.FundingQuote.properties.destinationToken, provider: spec.components.schemas.FundingQuote.properties.provider, quality: spec.components.schemas.FundingQuote.properties.quality, quote: spec.components.schemas.FundingQuote.properties.quote, sourceChain: spec.components.schemas.FundingQuote.properties.sourceChain, sourceToken: spec.components.schemas.FundingQuote.properties.sourceToken, }, quoteList: spec.components.schemas.FundingQuoteList.properties.data.items, }, operations: { chains: summarize(operations.chains), providers: summarize(operations.providers), quotes: summarize(operations.quotes), }, sharedErrorCodes: { authentication: spec.components.schemas.AuthenticationError.properties.error.properties.code.enum, forbidden: spec.components.schemas.ForbiddenError.properties.error.properties.code.enum, malformedApiKey: spec.components.schemas.ApiKeyMalformedError.properties.error.properties.code.enum, malformedApiKeyOrInvalidQuery: spec.components.schemas.ApiKeyMalformedOrQueryInvalidError.properties.error.properties .code.enum, upstream: spec.components.schemas.UpstreamError.properties.error.properties.code.enum, }, }).toMatchInlineSnapshot(` { "components": [ "FundingChain", "FundingChainInventory", "FundingChainList", "FundingProvider", "FundingProviderList", "FundingQuote", "FundingQuoteDetails", "FundingQuoteList", "FundingQuoteQuality", "FundingToken", ], "formats": { "quoteExpiresAt": "date-time", "quoteSampledAt": "date-time", }, "models": { "chainInventoryToken": { "$ref": "#/components/schemas/FundingToken", }, "chainList": { "$ref": "#/components/schemas/FundingChainInventory", }, "providerList": { "$ref": "#/components/schemas/FundingProvider", }, "quote": { "destinationChain": { "$ref": "#/components/schemas/FundingChain", }, "destinationToken": { "$ref": "#/components/schemas/FundingToken", }, "provider": { "$ref": "#/components/schemas/FundingProvider", }, "quality": { "$ref": "#/components/schemas/FundingQuoteQuality", }, "quote": { "$ref": "#/components/schemas/FundingQuoteDetails", }, "sourceChain": { "$ref": "#/components/schemas/FundingChain", }, "sourceToken": { "$ref": "#/components/schemas/FundingToken", }, }, "quoteList": { "$ref": "#/components/schemas/FundingQuote", }, }, "operations": { "chains": { "errors": { "400": { "$ref": "#/components/schemas/ApiKeyMalformedError", }, "401": { "$ref": "#/components/schemas/AuthenticationError", }, "403": { "$ref": "#/components/schemas/ForbiddenError", }, "429": "#/components/responses/RateLimited", "500": "#/components/responses/InternalError", "502": { "$ref": "#/components/schemas/UpstreamError", }, }, "operationId": "getFundingChains", "response": { "$ref": "#/components/schemas/FundingChainList", }, }, "providers": { "errors": { "400": { "$ref": "#/components/schemas/ApiKeyMalformedError", }, "401": { "$ref": "#/components/schemas/AuthenticationError", }, "403": { "$ref": "#/components/schemas/ForbiddenError", }, "429": "#/components/responses/RateLimited", "500": "#/components/responses/InternalError", "502": { "$ref": "#/components/schemas/UpstreamError", }, }, "operationId": "getFundingProviders", "response": { "$ref": "#/components/schemas/FundingProviderList", }, }, "quotes": { "errors": { "400": { "$ref": "#/components/schemas/ApiKeyMalformedOrQueryInvalidError", }, "401": { "$ref": "#/components/schemas/AuthenticationError", }, "403": { "$ref": "#/components/schemas/ForbiddenError", }, "429": "#/components/responses/RateLimited", "500": "#/components/responses/InternalError", "502": { "$ref": "#/components/schemas/UpstreamError", }, }, "operationId": "quoteFunding", "response": { "$ref": "#/components/schemas/FundingQuoteList", }, }, }, "sharedErrorCodes": { "authentication": [ "api_key_invalid", "api_key_missing", "unauthorized", ], "forbidden": [ "api_key_forbidden", "api_key_ip_forbidden", "forbidden", ], "malformedApiKey": [ "api_key_malformed", ], "malformedApiKeyOrInvalidQuery": [ "api_key_malformed", "query_invalid", ], "upstream": [ "upstream_error", ], }, } `) }) }) describe('GET /funding/chains', () => { test('lists only inputs supported by quote providers', async () => { const response = await create({ providers }).request('/v1/funding/chains', { headers: TestApp.auth.headers, }) expect(response.status).toBe(200) const body = await TestApp.json(response, Funding.schema.getFundingChains.Response) expect(body.data.map((chain) => chain.id)).toEqual([ 'eip155:1', 'eip155:56', 'eip155:8453', 'eip155:137', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', 'tron:0x2b6653dc', ]) expect(body.data.flatMap((chain) => chain.tokens.map((token) => token.symbol))).toEqual( expect.arrayContaining(['USDC', 'USDT']), ) expect( body.data.flatMap((chain) => chain.tokens).every((token) => token.currency === 'USD'), ).toBe(true) }) })