tapimo 0.4.1 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/ApiKey.d.ts +1 -9
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +2 -3
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +53 -27
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +32 -17
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +1990 -1442
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +23 -9
- package/dist/App.js.map +1 -1
- package/dist/Assets.d.ts +10 -0
- package/dist/Assets.d.ts.map +1 -1
- package/dist/Assets.js +4 -0
- package/dist/Assets.js.map +1 -1
- package/dist/Auth.d.ts +2 -1
- package/dist/Auth.d.ts.map +1 -1
- package/dist/Auth.js +2 -1
- package/dist/Auth.js.map +1 -1
- package/dist/Scope.d.ts +40 -15
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +44 -13
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +560 -9
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +38 -6
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +10 -24
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +10 -3
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +833 -0
- package/dist/admin/apps/organizations.d.ts.map +1 -0
- package/dist/admin/apps/organizations.js +733 -0
- package/dist/admin/apps/organizations.js.map +1 -0
- package/dist/admin/apps/scopes.d.ts +8 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +57 -57
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +53 -13
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +10 -0
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +8 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +9 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +14 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +1304 -1304
- package/dist/apps/data/FundingRouteProviders.d.ts +4 -22
- package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.js +10 -120
- package/dist/apps/data/FundingRouteProviders.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +38 -38
- package/dist/apps/data/routes/addresses.d.ts +24 -24
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/fee-amm.d.ts +20 -20
- package/dist/apps/data/routes/funding.js +7 -7
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/tokenlist.d.ts +12 -12
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/management/App.d.ts +808 -267
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +2 -2
- package/dist/apps/management/Billing.d.ts +3 -0
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +7 -2
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +116 -100
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +41 -30
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +182 -50
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +14 -14
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +93 -20
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +12 -15
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +89 -18
- package/dist/apps/management/routes/members.d.ts.map +1 -1
- package/dist/apps/management/routes/members.js +10 -10
- package/dist/apps/management/routes/members.js.map +1 -1
- package/dist/apps/management/routes/orgs.d.ts +100 -40
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +19 -19
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts +122 -33
- package/dist/apps/management/routes/projects.d.ts.map +1 -1
- package/dist/apps/management/routes/projects.js +12 -13
- package/dist/apps/management/routes/projects.js.map +1 -1
- package/dist/apps/management/routes/scopes.d.ts +4 -18
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +6 -7
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +63 -19
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +9 -10
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +38 -4
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/relay/App.d.ts +26 -4
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +17 -14
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/cloudflare.d.ts +2 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +4 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +5 -3
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +52 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +13 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +16 -1
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/adminAuditLogs.d.ts +27 -0
- package/dist/db/tables/adminAuditLogs.d.ts.map +1 -0
- package/dist/db/tables/adminAuditLogs.js +19 -0
- package/dist/db/tables/adminAuditLogs.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -1
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/memberships.d.ts +32 -4
- package/dist/db/tables/memberships.d.ts.map +1 -1
- package/dist/db/tables/memberships.js +31 -0
- package/dist/db/tables/memberships.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +43 -1
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +103 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.d.ts +25 -0
- package/dist/db/tables/projects.d.ts.map +1 -1
- package/dist/db/tables/projects.js +22 -0
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/webhookDeliveries.d.ts +26 -0
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
- package/dist/db/tables/webhookDeliveries.js +37 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -1
- package/dist/internal/Auth.d.ts +46 -40
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +118 -69
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cursor.d.ts +1 -1
- package/dist/internal/Cursor.d.ts.map +1 -1
- package/dist/internal/Cursor.js +4 -0
- package/dist/internal/Cursor.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +2 -0
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/Errors.d.ts +6 -0
- package/dist/internal/Errors.d.ts.map +1 -0
- package/dist/internal/Errors.js +16 -0
- package/dist/internal/Errors.js.map +1 -0
- package/dist/internal/FundingRoutes.d.ts +15 -43
- package/dist/internal/FundingRoutes.d.ts.map +1 -1
- package/dist/internal/FundingRoutes.js +8 -17
- package/dist/internal/FundingRoutes.js.map +1 -1
- package/dist/internal/Log.d.ts +16 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +5 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/Store.d.ts +61 -16
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +72 -17
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/dist/internal/index.d.ts +27 -0
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +27 -0
- package/dist/internal/index.js.map +1 -0
- package/package.json +8 -3
- package/src/ApiKey.ts +2 -3
- package/src/ApiKeys.ts +95 -17
- package/src/App.test.ts +104 -6
- package/src/App.ts +29 -9
- package/src/Assets.ts +8 -0
- package/src/Auth.test-d.ts +36 -0
- package/src/Auth.ts +20 -1
- package/src/Client.test-d.ts +17 -17
- package/src/Scope.test.ts +34 -0
- package/src/Scope.ts +53 -17
- package/src/admin/App.test.ts +33 -3
- package/src/admin/App.ts +49 -14
- package/src/admin/apps/api-keys.ts +10 -3
- package/src/admin/apps/organizations.test.ts +455 -0
- package/src/admin/apps/organizations.ts +953 -0
- package/src/admin/apps/scopes.test.ts +10 -0
- package/src/admin/ui/plugin.ts +1 -1
- package/src/admin/ui/src/components/MobileProfile.tsx +2 -0
- package/src/admin/ui/src/components/ThemeToggle.tsx +20 -0
- package/src/admin/ui/src/lib/api-keys.ts +36 -19
- package/src/admin/ui/src/lib/config.ts +2 -3
- package/src/admin/ui/src/lib/organizations.test.ts +89 -0
- package/src/admin/ui/src/lib/organizations.ts +262 -0
- package/src/admin/ui/src/lib/query-client.ts +3 -1
- package/src/admin/ui/src/lib/theme.ts +57 -0
- package/src/admin/ui/src/routeTree.gen.ts +47 -4
- package/src/admin/ui/src/routes/__root.tsx +18 -3
- package/src/admin/ui/src/routes/api-keys.tsx +52 -7
- package/src/admin/ui/src/routes/index.tsx +1 -1
- package/src/admin/ui/src/routes/organizations.tsx +158 -0
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +710 -0
- package/src/admin/ui/src/start.ts +6 -0
- package/src/admin/ui.generated.ts +63 -13
- package/src/analytics/Analytics.ts +25 -0
- package/src/analytics/tables/requestEvents.ts +33 -0
- package/src/apps/composition.test.ts +82 -2
- package/src/apps/data/FundingRouteProviders.test.ts +37 -93
- package/src/apps/data/FundingRouteProviders.ts +14 -161
- package/src/apps/data/routes/funding.test.ts +7 -42
- package/src/apps/data/routes/funding.ts +8 -8
- package/src/apps/data/routes/indexer.test.ts +2 -2
- package/src/apps/data/routes/webhooks.test.ts +2 -2
- package/src/apps/management/App.ts +2 -2
- package/src/apps/management/Billing.ts +10 -2
- package/src/apps/management/routes/api-keys.test.ts +137 -1
- package/src/apps/management/routes/api-keys.ts +63 -38
- package/src/apps/management/routes/billing.ts +14 -14
- package/src/apps/management/routes/invitations.test.ts +102 -1
- package/src/apps/management/routes/invitations.ts +13 -15
- package/src/apps/management/routes/members.ts +10 -10
- package/src/apps/management/routes/orgs.test.ts +78 -16
- package/src/apps/management/routes/orgs.ts +21 -20
- package/src/apps/management/routes/projects.ts +12 -13
- package/src/apps/management/routes/scopes.test.ts +16 -0
- package/src/apps/management/routes/scopes.ts +6 -7
- package/src/apps/management/routes/usage.ts +9 -14
- package/src/apps/mcp.test.ts +72 -7
- package/src/apps/mcp.ts +46 -4
- package/src/apps/relay/App.test.ts +23 -18
- package/src/apps/relay/App.ts +35 -14
- package/src/cloudflare.ts +5 -0
- package/src/db/Db.ts +59 -3
- package/src/db/Schema.ts +20 -1
- package/src/db/tables/adminAuditLogs.ts +47 -0
- package/src/db/tables/invitations.ts +1 -1
- package/src/db/tables/memberships.ts +59 -3
- package/src/db/tables/organizations.ts +148 -1
- package/src/db/tables/projects.ts +47 -0
- package/src/db/tables/webhookDeliveries.ts +83 -0
- package/src/internal/Auth.test.ts +571 -23
- package/src/internal/Auth.ts +166 -94
- package/src/internal/Cursor.test.ts +10 -0
- package/src/internal/Cursor.ts +5 -1
- package/src/internal/EdgeCache.test.ts +11 -0
- package/src/internal/EdgeCache.ts +2 -0
- package/src/internal/FundingRoutes.test.ts +8 -8
- package/src/internal/FundingRoutes.ts +25 -29
- package/src/internal/Log.test.ts +41 -3
- package/src/internal/Log.ts +23 -4
- package/src/internal/Store.test-d.ts +15 -0
- package/src/internal/Store.test.ts +153 -2
- package/src/internal/Store.ts +157 -41
- package/src/internal/index.test.ts +34 -0
- package/src/internal/index.ts +26 -0
|
@@ -8,6 +8,31 @@ export type BillableCount = RequestEvents.BillableCount
|
|
|
8
8
|
/** Options for {@link readBillableRequestCounts}. */
|
|
9
9
|
export type BillableCountsOptions = RequestEvents.BillableCountsOptions
|
|
10
10
|
|
|
11
|
+
/** One organization's request usage and error breakdown. */
|
|
12
|
+
export type ProjectUsage = RequestEvents.ProjectUsage
|
|
13
|
+
|
|
14
|
+
/** Options for reading one organization's request usage. */
|
|
15
|
+
export type ProjectUsageOptions = RequestEvents.ProjectUsageOptions
|
|
16
|
+
|
|
17
|
+
/** API errors grouped by their stable error code. */
|
|
18
|
+
export type ErrorBreakdown = RequestEvents.ErrorBreakdown
|
|
19
|
+
|
|
20
|
+
/** Reads stable API error-code counts for one organization attribution window. */
|
|
21
|
+
export function readErrorBreakdown(
|
|
22
|
+
source: Source,
|
|
23
|
+
options: ProjectUsageOptions,
|
|
24
|
+
): Promise<ErrorBreakdown[]> {
|
|
25
|
+
return RequestEvents.readErrorBreakdown(get(source), options)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Reads request usage for an organization and optional attribution terms. */
|
|
29
|
+
export function readProjectUsage(
|
|
30
|
+
source: Source,
|
|
31
|
+
options: ProjectUsageOptions,
|
|
32
|
+
): Promise<ProjectUsage> {
|
|
33
|
+
return RequestEvents.readProjectUsage(get(source), options)
|
|
34
|
+
}
|
|
35
|
+
|
|
11
36
|
/**
|
|
12
37
|
* Reads deduped billable request counts grouped by `(org, UTC hour)` from
|
|
13
38
|
* `request_events` — the request-metered billing source. Resolves the source
|
|
@@ -59,6 +59,14 @@ export type Attribution = {
|
|
|
59
59
|
/** API-key environment for request usage. */
|
|
60
60
|
export type Environment = 'production' | 'sandbox'
|
|
61
61
|
|
|
62
|
+
/** API errors grouped by their stable error code. */
|
|
63
|
+
export type ErrorBreakdown = {
|
|
64
|
+
/** Stable API error code, or `unknown` when none was recorded. */
|
|
65
|
+
code: string
|
|
66
|
+
/** Requests returning this error. */
|
|
67
|
+
requests: number
|
|
68
|
+
}
|
|
69
|
+
|
|
62
70
|
/** API key usage grouped for one project. */
|
|
63
71
|
export type KeyBreakdown = {
|
|
64
72
|
/** API key id (`key_…`). */
|
|
@@ -331,6 +339,26 @@ export async function readProjectUsage(
|
|
|
331
339
|
}
|
|
332
340
|
}
|
|
333
341
|
|
|
342
|
+
/** Reads stable API error-code counts for one organization attribution window. */
|
|
343
|
+
export async function readErrorBreakdown(
|
|
344
|
+
analytics: Analytics.Analytics,
|
|
345
|
+
options: ProjectUsageOptions,
|
|
346
|
+
): Promise<ErrorBreakdown[]> {
|
|
347
|
+
const rows = await analytics.query<ErrorRow>(
|
|
348
|
+
`
|
|
349
|
+
SELECT
|
|
350
|
+
coalesce(error_code, 'unknown') AS code,
|
|
351
|
+
count() AS requests
|
|
352
|
+
FROM ${table}
|
|
353
|
+
WHERE ${usageWhere(options)} AND status >= 400
|
|
354
|
+
GROUP BY error_code
|
|
355
|
+
ORDER BY requests DESC, code ASC
|
|
356
|
+
LIMIT 25
|
|
357
|
+
`,
|
|
358
|
+
)
|
|
359
|
+
return rows.map((row) => ({ code: String(row.code), requests: number(row.requests) }))
|
|
360
|
+
}
|
|
361
|
+
|
|
334
362
|
/** One org's deduped billable request count for a single UTC hour. */
|
|
335
363
|
export type BillableCount = {
|
|
336
364
|
/** UTC hour start (ISO 8601). */
|
|
@@ -406,6 +434,11 @@ type KeyRow = {
|
|
|
406
434
|
requests: number | string
|
|
407
435
|
}
|
|
408
436
|
|
|
437
|
+
type ErrorRow = {
|
|
438
|
+
code: string
|
|
439
|
+
requests: number | string
|
|
440
|
+
}
|
|
441
|
+
|
|
409
442
|
type RouteRow = {
|
|
410
443
|
averageDurationMs: number | string
|
|
411
444
|
errors: number | string
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { App, Store } from 'tapimo'
|
|
2
2
|
import { data, management, relay } from 'tapimo/apps'
|
|
3
|
+
import { Hono } from 'hono'
|
|
4
|
+
import { describeRoute } from 'hono-openapi'
|
|
3
5
|
import * as TestApp from '../../test/App.js'
|
|
4
6
|
|
|
5
7
|
// Each test mounts one group and asserts its routes exist while omitted groups
|
|
@@ -10,7 +12,10 @@ import * as TestApp from '../../test/App.js'
|
|
|
10
12
|
async function document(app: { request: (path: string) => Promise<Response> | Response }) {
|
|
11
13
|
const res = await app.request('/openapi.json')
|
|
12
14
|
return (await res.json()) as {
|
|
15
|
+
components: { securitySchemes: Record<string, unknown> }
|
|
13
16
|
paths: Record<string, unknown>
|
|
17
|
+
tags: readonly { description: string; name: string }[]
|
|
18
|
+
webhooks?: Record<string, unknown> | undefined
|
|
14
19
|
'x-tagGroups'?: readonly { name: string; tags: readonly string[] }[] | undefined
|
|
15
20
|
}
|
|
16
21
|
}
|
|
@@ -20,6 +25,77 @@ async function paths(app: { request: (path: string) => Promise<Response> | Respo
|
|
|
20
25
|
return Object.keys((await document(app)).paths)
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
describe('App.from()', () => {
|
|
29
|
+
test('contributes external route-group metadata', async () => {
|
|
30
|
+
const group = new Hono().get(
|
|
31
|
+
'/v1/external/webhooks',
|
|
32
|
+
describeRoute({
|
|
33
|
+
responses: { 200: { description: 'External webhook status.' } },
|
|
34
|
+
summary: 'Get webhook status',
|
|
35
|
+
tags: ['External'],
|
|
36
|
+
}),
|
|
37
|
+
(c) => c.json({ status: 'ok' }, 200),
|
|
38
|
+
)
|
|
39
|
+
const external = App.from(group, {
|
|
40
|
+
docs: ({ spec }) =>
|
|
41
|
+
new Hono().get('/external-docs', async (c) => c.json({ paths: (await spec()).paths })),
|
|
42
|
+
securitySchemes: {
|
|
43
|
+
externalKey: { in: 'header', name: 'external-key', type: 'apiKey' },
|
|
44
|
+
},
|
|
45
|
+
tags: [{ description: 'Externally defined routes.', name: 'External' }],
|
|
46
|
+
webhooks: () => ({ externalEvent: { post: { responses: { 200: { description: 'OK' } } } } }),
|
|
47
|
+
'x-tagGroups': [{ name: 'External API', tags: ['External'] }],
|
|
48
|
+
})
|
|
49
|
+
expectTypeOf(external).toEqualTypeOf<typeof group>()
|
|
50
|
+
|
|
51
|
+
const app = App.create({ auth: false, db: TestApp.database() }).route('/', external)
|
|
52
|
+
const spec = await document(app)
|
|
53
|
+
|
|
54
|
+
expect({
|
|
55
|
+
securityScheme: spec.components.securitySchemes['externalKey'],
|
|
56
|
+
tags: spec.tags,
|
|
57
|
+
tagGroups: spec['x-tagGroups'],
|
|
58
|
+
webhooks: spec.webhooks,
|
|
59
|
+
}).toMatchInlineSnapshot(`
|
|
60
|
+
{
|
|
61
|
+
"securityScheme": {
|
|
62
|
+
"in": "header",
|
|
63
|
+
"name": "external-key",
|
|
64
|
+
"type": "apiKey",
|
|
65
|
+
},
|
|
66
|
+
"tagGroups": [
|
|
67
|
+
{
|
|
68
|
+
"name": "External API",
|
|
69
|
+
"tags": [
|
|
70
|
+
"External",
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
"tags": [
|
|
75
|
+
{
|
|
76
|
+
"description": "Externally defined routes.",
|
|
77
|
+
"name": "External",
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
"webhooks": {
|
|
81
|
+
"externalEvent": {
|
|
82
|
+
"post": {
|
|
83
|
+
"responses": {
|
|
84
|
+
"200": {
|
|
85
|
+
"description": "OK",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
`)
|
|
93
|
+
|
|
94
|
+
const docs = await app.request('/external-docs')
|
|
95
|
+
expect((await docs.json()).paths['/v1/external/webhooks']).toBeDefined()
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
23
99
|
describe('data()', () => {
|
|
24
100
|
test('mounts data routes and omits management routes', async () => {
|
|
25
101
|
const app = App.create({ db: TestApp.database() }).route('/', data())
|
|
@@ -42,13 +118,17 @@ describe('relay()', () => {
|
|
|
42
118
|
|
|
43
119
|
// Mounted but default-closed and hidden: unauthenticated requests are
|
|
44
120
|
// refused, and the path never appears in the OpenAPI document.
|
|
45
|
-
const
|
|
46
|
-
|
|
121
|
+
const relayMounted = await composed.request('/rpc/relay')
|
|
122
|
+
const sponsorMounted = await composed.request('/rpc/sponsor')
|
|
123
|
+
expect(relayMounted.status).toBe(401)
|
|
124
|
+
expect(sponsorMounted.status).toBe(401)
|
|
47
125
|
expect((await paths(composed)).some((path) => path.startsWith('/rpc/relay'))).toBe(false)
|
|
126
|
+
expect((await paths(composed)).some((path) => path.startsWith('/rpc/sponsor'))).toBe(false)
|
|
48
127
|
|
|
49
128
|
// Without the relay group, the data group no longer serves the path.
|
|
50
129
|
const dataOnly = App.create({ db: TestApp.database() }).route('/', data())
|
|
51
130
|
expect((await dataOnly.request('/rpc/relay')).status).toBe(404)
|
|
131
|
+
expect((await dataOnly.request('/rpc/sponsor')).status).toBe(404)
|
|
52
132
|
})
|
|
53
133
|
})
|
|
54
134
|
|
|
@@ -4,95 +4,6 @@ import * as FundingRouteProviders from './FundingRouteProviders.js'
|
|
|
4
4
|
const now = () => new Date('2026-01-01T00:00:00.000Z')
|
|
5
5
|
|
|
6
6
|
describe('FundingRouteProviders', () => {
|
|
7
|
-
test('maps Across suggested-fees responses into quote probes', async () => {
|
|
8
|
-
const requests: RecordedRequest[] = []
|
|
9
|
-
const fetch = recordingFetch(requests, async () =>
|
|
10
|
-
jsonResponse({
|
|
11
|
-
estimatedFillTimeSec: 2,
|
|
12
|
-
limits: {
|
|
13
|
-
maxDeposit: '2000000',
|
|
14
|
-
maxDepositInstant: '1500000',
|
|
15
|
-
minDeposit: '500000',
|
|
16
|
-
},
|
|
17
|
-
outputAmount: '990000',
|
|
18
|
-
relayFeePct: '1200000000000000',
|
|
19
|
-
timestamp: '1767225600',
|
|
20
|
-
}),
|
|
21
|
-
)
|
|
22
|
-
const adapter = FundingRouteProviders.across({
|
|
23
|
-
apiKey: 'across-api-key',
|
|
24
|
-
baseUrl: 'https://across.test/api/',
|
|
25
|
-
fetch,
|
|
26
|
-
integratorId: 'tempo',
|
|
27
|
-
now,
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
const result = await adapter.probeQuote(
|
|
31
|
-
await probeInput('across'),
|
|
32
|
-
new AbortController().signal,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
expect(requests).toHaveLength(1)
|
|
36
|
-
expect(requests[0]?.url.pathname).toBe('/api/suggested-fees')
|
|
37
|
-
expect(requests[0]?.url.searchParams.get('originChainId')).toBe('8453')
|
|
38
|
-
expect(requests[0]?.url.searchParams.get('destinationChainId')).toBe('4217')
|
|
39
|
-
expect(requests[0]?.url.searchParams.get('amount')).toBe('1000000')
|
|
40
|
-
expect(requests[0]?.url.searchParams.get('integratorId')).toBe('tempo')
|
|
41
|
-
expect(requests[0]?.url.searchParams.get('allowUnmatchedDecimals')).toBe('true')
|
|
42
|
-
expect(new Headers(requests[0]?.init?.headers).get('authorization')).toBe(
|
|
43
|
-
'Bearer across-api-key',
|
|
44
|
-
)
|
|
45
|
-
expect(result).toMatchObject({
|
|
46
|
-
expiresAt: '2026-01-01T00:10:00.000Z',
|
|
47
|
-
limits: {
|
|
48
|
-
instantSourceAmount: '1500000',
|
|
49
|
-
maxSourceAmount: '2000000',
|
|
50
|
-
minSourceAmount: '500000',
|
|
51
|
-
},
|
|
52
|
-
outputAmount: '990000',
|
|
53
|
-
outputAmountFormatted: '0.99',
|
|
54
|
-
quality: {
|
|
55
|
-
availableVolumeUsd: '2',
|
|
56
|
-
estimatedSeconds: 2,
|
|
57
|
-
feeBps: 12,
|
|
58
|
-
instantVolumeUsd: '1.5',
|
|
59
|
-
liquiditySource: 'providerLimit',
|
|
60
|
-
sourceDetail: 'across:suggested-fees',
|
|
61
|
-
tier: 'liquid',
|
|
62
|
-
},
|
|
63
|
-
sampledAt: '2026-01-01T00:00:00.000Z',
|
|
64
|
-
status: 'available',
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
test('maps recognized Across quote errors to unavailable probes', async () => {
|
|
69
|
-
const fetch = recordingFetch([], async () =>
|
|
70
|
-
jsonResponse({ code: 'AMOUNT_TOO_HIGH', message: 'Amount exceeds max deposit' }, 400),
|
|
71
|
-
)
|
|
72
|
-
const adapter = FundingRouteProviders.across({
|
|
73
|
-
apiKey: 'across-api-key',
|
|
74
|
-
fetch,
|
|
75
|
-
integratorId: 'tempo',
|
|
76
|
-
now,
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
const result = await adapter.probeQuote(
|
|
80
|
-
await probeInput('across'),
|
|
81
|
-
new AbortController().signal,
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
expect(result).toMatchObject({
|
|
85
|
-
quality: {
|
|
86
|
-
liquiditySource: 'providerLimit',
|
|
87
|
-
score: 0,
|
|
88
|
-
sourceDetail: 'across:suggested-fees:AMOUNT_TOO_HIGH',
|
|
89
|
-
tier: 'unavailable',
|
|
90
|
-
},
|
|
91
|
-
sampledAt: '2026-01-01T00:00:00.000Z',
|
|
92
|
-
status: 'unavailable',
|
|
93
|
-
})
|
|
94
|
-
})
|
|
95
|
-
|
|
96
7
|
test('maps Relay liquidity and quote responses into quote probes', async () => {
|
|
97
8
|
const requests: RecordedRequest[] = []
|
|
98
9
|
const route = await staticRoute('relay')
|
|
@@ -165,6 +76,43 @@ describe('FundingRouteProviders', () => {
|
|
|
165
76
|
})
|
|
166
77
|
})
|
|
167
78
|
|
|
79
|
+
test('calls the runtime fetch method with its global receiver', async () => {
|
|
80
|
+
const route = await staticRoute('relay')
|
|
81
|
+
const fetch = vi.fn(function (this: unknown, input: Parameters<typeof globalThis.fetch>[0]) {
|
|
82
|
+
expect(this).toBe(globalThis)
|
|
83
|
+
const url = toUrl(input)
|
|
84
|
+
return Promise.resolve(
|
|
85
|
+
jsonResponse(
|
|
86
|
+
url.pathname === '/quote/v2'
|
|
87
|
+
? {
|
|
88
|
+
details: {
|
|
89
|
+
currencyOut: { amount: '990000', minimumAmount: '980000' },
|
|
90
|
+
timeEstimate: 15,
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
: { liquidity: [] },
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
})
|
|
97
|
+
vi.stubGlobal('fetch', fetch)
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const adapter = FundingRouteProviders.relay({
|
|
101
|
+
now,
|
|
102
|
+
userAddress: '0x1111111111111111111111111111111111111111',
|
|
103
|
+
})
|
|
104
|
+
const result = await adapter.probeQuote(
|
|
105
|
+
probeInputFromRoute(route),
|
|
106
|
+
new AbortController().signal,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
expect(result.outputAmount).toBe('990000')
|
|
110
|
+
expect(fetch).toHaveBeenCalledTimes(2)
|
|
111
|
+
} finally {
|
|
112
|
+
vi.unstubAllGlobals()
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
168
116
|
test('keeps Relay quotes when the liquidity probe fails', async () => {
|
|
169
117
|
const route = await staticRoute('relay')
|
|
170
118
|
const fetch = recordingFetch([], async (request) => {
|
|
@@ -224,10 +172,6 @@ async function staticRoute(
|
|
|
224
172
|
return route as FundingRoutes.QuoteProbeInput['route']
|
|
225
173
|
}
|
|
226
174
|
|
|
227
|
-
async function probeInput(provider: FundingRoutes.ProviderId) {
|
|
228
|
-
return probeInputFromRoute(await staticRoute(provider))
|
|
229
|
-
}
|
|
230
|
-
|
|
231
175
|
function probeInputFromRoute(
|
|
232
176
|
route: FundingRoutes.QuoteProbeInput['route'],
|
|
233
177
|
): FundingRoutes.QuoteProbeInput {
|
|
@@ -1,90 +1,30 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as FundingRoutes from '../../internal/FundingRoutes.js'
|
|
2
2
|
|
|
3
3
|
type Fetch = typeof globalThis.fetch
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const defaultFetch: Fetch = (input, init) => globalThis.fetch(input, init)
|
|
6
|
+
|
|
6
7
|
const defaultRelayBaseUrl = 'https://api.relay.link'
|
|
7
8
|
|
|
8
9
|
/** Creates the live funding route adapters configured for this deployment. */
|
|
9
10
|
export function createAdapters(options: createAdapters.Options = {}) {
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} satisfies FundingRoutes.ProviderAdapters
|
|
11
|
+
return (
|
|
12
|
+
options.relay ? { relay: relay(options.relay) } : {}
|
|
13
|
+
) satisfies FundingRoutes.ProviderAdapters
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export declare namespace createAdapters {
|
|
17
17
|
/** Provider adapter factory options. Omit a provider to keep its routes static. */
|
|
18
18
|
type Options = {
|
|
19
|
-
/** Across quote adapter options. */
|
|
20
|
-
across?: across.Options | false | undefined
|
|
21
19
|
/** Relay quote adapter options. */
|
|
22
20
|
relay?: relay.Options | false | undefined
|
|
23
21
|
}
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
/** Creates an Across quote adapter backed by `/suggested-fees`. */
|
|
27
|
-
export function across(options: across.Options): FundingRoutes.ProviderAdapter {
|
|
28
|
-
const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultAcrossBaseUrl)
|
|
29
|
-
const fetcher = options.fetch ?? globalThis.fetch
|
|
30
|
-
const now = options.now ?? (() => new Date())
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
async probeQuote({ route }, signal) {
|
|
34
|
-
const sourceChainId = providerChainId(route.sourceChain, 'across')
|
|
35
|
-
const destinationChainId = eip155ChainId(route.destinationChain)
|
|
36
|
-
const url = new URL(`${baseUrl}/suggested-fees`)
|
|
37
|
-
url.search = new URLSearchParams({
|
|
38
|
-
allowUnmatchedDecimals: 'true',
|
|
39
|
-
amount: route.sourceAmount.amount,
|
|
40
|
-
destinationChainId,
|
|
41
|
-
inputToken: route.sourceToken.address,
|
|
42
|
-
integratorId: options.integratorId,
|
|
43
|
-
originChainId: sourceChainId,
|
|
44
|
-
outputToken: route.destinationToken.address,
|
|
45
|
-
}).toString()
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
const body = await requestJson(url, {
|
|
49
|
-
fetcher,
|
|
50
|
-
headers: { Authorization: `Bearer ${options.apiKey}` },
|
|
51
|
-
signal,
|
|
52
|
-
})
|
|
53
|
-
return formatAcrossResult(route, body, now())
|
|
54
|
-
} catch (cause) {
|
|
55
|
-
const unavailable = providerUnavailable(cause, ['AMOUNT_TOO_HIGH', 'AMOUNT_TOO_LOW'])
|
|
56
|
-
if (!unavailable) throw cause
|
|
57
|
-
return unavailableResult({
|
|
58
|
-
detail: 'across:suggested-fees',
|
|
59
|
-
message: unavailable,
|
|
60
|
-
now: now(),
|
|
61
|
-
source: 'providerLimit',
|
|
62
|
-
})
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export declare namespace across {
|
|
69
|
-
/** Across quote adapter options. */
|
|
70
|
-
type Options = {
|
|
71
|
-
/** Across API key sent as a bearer token. */
|
|
72
|
-
apiKey: string
|
|
73
|
-
/** Across integrator id sent as the required query parameter. */
|
|
74
|
-
integratorId: string
|
|
75
|
-
/** Override for tests or non-production Across environments. */
|
|
76
|
-
baseUrl?: string | undefined
|
|
77
|
-
/** Fetch implementation override for tests. */
|
|
78
|
-
fetch?: Fetch | undefined
|
|
79
|
-
/** Clock override for deterministic tests. */
|
|
80
|
-
now?: (() => Date) | undefined
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
24
|
/** Creates a Relay quote adapter backed by `/chains/liquidity` and `/quote/v2`. */
|
|
85
25
|
export function relay(options: relay.Options): FundingRoutes.ProviderAdapter {
|
|
86
26
|
const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultRelayBaseUrl)
|
|
87
|
-
const fetcher = options.fetch ??
|
|
27
|
+
const fetcher = options.fetch ?? defaultFetch
|
|
88
28
|
const now = options.now ?? (() => new Date())
|
|
89
29
|
|
|
90
30
|
return {
|
|
@@ -152,45 +92,6 @@ export declare namespace relay {
|
|
|
152
92
|
}
|
|
153
93
|
}
|
|
154
94
|
|
|
155
|
-
function formatAcrossResult(
|
|
156
|
-
route: FundingRoutes.RouteCandidate,
|
|
157
|
-
body: unknown,
|
|
158
|
-
now: Date,
|
|
159
|
-
): FundingRoutes.QuoteProbeResult {
|
|
160
|
-
const response = record(body)
|
|
161
|
-
const timestamp = integerString(response['timestamp'])
|
|
162
|
-
const sampledAt = timestamp ? isoFromUnixSeconds(timestamp) : now.toISOString()
|
|
163
|
-
const limits = acrossLimits(response['limits'])
|
|
164
|
-
const amountStatus = statusFromLimits(route.sourceAmount.amount, limits)
|
|
165
|
-
const status = response['isAmountTooLow'] === true ? 'unavailable' : amountStatus.status
|
|
166
|
-
const tier = response['isAmountTooLow'] === true ? 'unavailable' : amountStatus.tier
|
|
167
|
-
const outputAmount = decimalString(response['outputAmount'])
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
expiresAt: timestamp ? isoFromUnixSeconds((BigInt(timestamp) + 600n).toString()) : null,
|
|
171
|
-
limits,
|
|
172
|
-
outputAmount,
|
|
173
|
-
outputAmountFormatted: outputAmount
|
|
174
|
-
? decimalFromBaseUnits(outputAmount, route.destinationToken.decimals)
|
|
175
|
-
: null,
|
|
176
|
-
quality: {
|
|
177
|
-
availableVolumeUsd: stablecoinUsdCapacity(route, limits?.maxSourceAmount),
|
|
178
|
-
estimatedSeconds: nonnegativeInteger(response['estimatedFillTimeSec']),
|
|
179
|
-
feeBps: scaledPctToBps(
|
|
180
|
-
nestedString(response['totalRelayFee'], 'pct') ?? decimalString(response['relayFeePct']),
|
|
181
|
-
),
|
|
182
|
-
historicalVolumeUsd24h: null,
|
|
183
|
-
instantVolumeUsd: stablecoinUsdCapacity(route, limits?.instantSourceAmount),
|
|
184
|
-
liquiditySource: 'providerLimit',
|
|
185
|
-
score: scoreFor(tier),
|
|
186
|
-
sourceDetail: 'across:suggested-fees',
|
|
187
|
-
tier,
|
|
188
|
-
},
|
|
189
|
-
sampledAt,
|
|
190
|
-
status,
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
95
|
function formatRelayResult(
|
|
195
96
|
route: FundingRoutes.RouteCandidate,
|
|
196
97
|
quote: unknown,
|
|
@@ -262,31 +163,6 @@ function unavailableResult(options: {
|
|
|
262
163
|
}
|
|
263
164
|
}
|
|
264
165
|
|
|
265
|
-
function acrossLimits(input: unknown): FundingRoutes.QuoteProbeLimits | null {
|
|
266
|
-
const limits = record(input)
|
|
267
|
-
const minSourceAmount = decimalString(limits['minDeposit'])
|
|
268
|
-
const maxSourceAmount = decimalString(limits['maxDeposit'])
|
|
269
|
-
const instantSourceAmount =
|
|
270
|
-
decimalString(limits['maxDepositInstant']) ?? decimalString(limits['recommendedDepositInstant'])
|
|
271
|
-
return minSourceAmount || maxSourceAmount || instantSourceAmount
|
|
272
|
-
? { instantSourceAmount, maxSourceAmount, minSourceAmount }
|
|
273
|
-
: null
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function statusFromLimits(
|
|
277
|
-
amount: string,
|
|
278
|
-
limits: FundingRoutes.QuoteProbeLimits | null,
|
|
279
|
-
): { status: FundingRoutes.RouteStatus; tier: FundingRoutes.QualityTier } {
|
|
280
|
-
if (!limits) return { status: 'available', tier: 'liquid' }
|
|
281
|
-
if (limits.minSourceAmount && compareIntegerStrings(amount, limits.minSourceAmount) < 0)
|
|
282
|
-
return { status: 'unavailable', tier: 'unavailable' }
|
|
283
|
-
if (limits.maxSourceAmount && compareIntegerStrings(amount, limits.maxSourceAmount) > 0)
|
|
284
|
-
return { status: 'unavailable', tier: 'unavailable' }
|
|
285
|
-
if (limits.instantSourceAmount && compareIntegerStrings(amount, limits.instantSourceAmount) > 0)
|
|
286
|
-
return { status: 'thin', tier: 'thin' }
|
|
287
|
-
return { status: 'available', tier: 'liquid' }
|
|
288
|
-
}
|
|
289
|
-
|
|
290
166
|
function relayLiquidity(input: unknown, route: FundingRoutes.RouteCandidate) {
|
|
291
167
|
const response = record(input)
|
|
292
168
|
const rows = Array.isArray(response['liquidity']) ? response['liquidity'] : []
|
|
@@ -333,12 +209,17 @@ function relayHeaders(apiKey: string | undefined): Record<string, string> | unde
|
|
|
333
209
|
function providerUnavailable(cause: unknown, codes: readonly string[]) {
|
|
334
210
|
if (!(cause instanceof ProviderResponseError) || cause.status < 400 || cause.status >= 500)
|
|
335
211
|
return undefined
|
|
336
|
-
const
|
|
337
|
-
const code = stringValue(body['code']) ?? stringValue(body['errorCode'])
|
|
212
|
+
const code = providerErrorCode(cause.body)
|
|
338
213
|
if (code && codes.includes(code)) return code
|
|
339
214
|
return undefined
|
|
340
215
|
}
|
|
341
216
|
|
|
217
|
+
function providerErrorCode(input: unknown) {
|
|
218
|
+
const body = record(input)
|
|
219
|
+
const code = stringValue(body['code']) ?? stringValue(body['errorCode'])
|
|
220
|
+
return code && /^[A-Za-z0-9_.:-]{1,64}$/.test(code) ? code : undefined
|
|
221
|
+
}
|
|
222
|
+
|
|
342
223
|
class ProviderResponseError extends Error {
|
|
343
224
|
override name = 'FundingRouteProviders.ProviderResponseError'
|
|
344
225
|
|
|
@@ -406,10 +287,6 @@ function integerString(input: unknown) {
|
|
|
406
287
|
return undefined
|
|
407
288
|
}
|
|
408
289
|
|
|
409
|
-
function nestedString(input: unknown, key: string) {
|
|
410
|
-
return decimalString(record(input)[key])
|
|
411
|
-
}
|
|
412
|
-
|
|
413
290
|
function nonnegativeInteger(input: unknown) {
|
|
414
291
|
if (typeof input === 'number' && Number.isSafeInteger(input) && input >= 0) return input
|
|
415
292
|
if (typeof input === 'string' && /^\d+$/.test(input)) return Number(input)
|
|
@@ -427,30 +304,6 @@ function compareIntegerStrings(left: string, right: string) {
|
|
|
427
304
|
return BigInt(left) === BigInt(right) ? 0 : BigInt(left) > BigInt(right) ? 1 : -1
|
|
428
305
|
}
|
|
429
306
|
|
|
430
|
-
function isoFromUnixSeconds(input: string) {
|
|
431
|
-
return new Date(Number(input) * 1_000).toISOString()
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
function stablecoinUsdCapacity(
|
|
435
|
-
route: FundingRoutes.RouteCandidate,
|
|
436
|
-
baseUnits: string | null | undefined,
|
|
437
|
-
) {
|
|
438
|
-
if (!baseUnits || !isDollarStablecoin(route.sourceToken.symbol)) return null
|
|
439
|
-
return decimalFromBaseUnits(baseUnits, route.sourceToken.decimals)
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function isDollarStablecoin(symbol: string) {
|
|
443
|
-
return ['DAI', 'FRXUSD', 'PYUSD', 'USDC', 'USDC.E', 'USDT', 'USDT0'].includes(
|
|
444
|
-
symbol.toUpperCase(),
|
|
445
|
-
)
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function scaledPctToBps(input: string | undefined) {
|
|
449
|
-
if (!input || !/^\d+$/.test(input)) return null
|
|
450
|
-
const denominator = 1_000_000_000_000_000_000n
|
|
451
|
-
return Number((BigInt(input) * 10_000n + denominator / 2n) / denominator)
|
|
452
|
-
}
|
|
453
|
-
|
|
454
307
|
function scoreFor(tier: FundingRoutes.QualityTier) {
|
|
455
308
|
if (tier === 'liquid') return 90
|
|
456
309
|
if (tier === 'thin') return 65
|
|
@@ -19,16 +19,11 @@ describe('GET /funding/routes', () => {
|
|
|
19
19
|
expect(response.status).toBe(200)
|
|
20
20
|
const body = await TestApp.json(response, Funding.schema.listFundingRoutes.Response)
|
|
21
21
|
|
|
22
|
-
expect(body.data.map((route) => route.provider.id)).toEqual([
|
|
23
|
-
'across',
|
|
24
|
-
'relay',
|
|
25
|
-
'bridge',
|
|
26
|
-
'bungee',
|
|
27
|
-
])
|
|
22
|
+
expect(body.data.map((route) => route.provider.id)).toEqual(['relay', 'bridge', 'bungee'])
|
|
28
23
|
expect(body.data[0]).toMatchObject({
|
|
29
24
|
destinationChain: { id: 'eip155:4217', name: 'Tempo' },
|
|
30
25
|
destinationToken: { symbol: 'USDC.e' },
|
|
31
|
-
id: 'base-usdc-tempo-usdce-
|
|
26
|
+
id: 'base-usdc-tempo-usdce-relay',
|
|
32
27
|
quality: { liquiditySource: 'staticInventory', tier: 'unknown' },
|
|
33
28
|
sourceAmount: { amount: '1000000', formatted: '1' },
|
|
34
29
|
sourceChain: { id: 'eip155:8453', name: 'Base' },
|
|
@@ -41,12 +36,12 @@ describe('GET /funding/routes', () => {
|
|
|
41
36
|
const app = TestApp.create({
|
|
42
37
|
fundingRoutes: {
|
|
43
38
|
adapters: {
|
|
44
|
-
|
|
39
|
+
relay: {
|
|
45
40
|
async probeQuote(input, signal) {
|
|
46
41
|
expect(signal.aborted).toBe(false)
|
|
47
42
|
expect(input.cache.bypass).toBe(true)
|
|
48
43
|
expect(input.include).toEqual(['quotes'])
|
|
49
|
-
expect(input.route.id).toBe('base-usdc-tempo-usdce-
|
|
44
|
+
expect(input.route.id).toBe('base-usdc-tempo-usdce-relay')
|
|
50
45
|
expect(input.sourceAmount.amount).toBe('1000000')
|
|
51
46
|
expect(input.sourceChain.id).toBe('eip155:8453')
|
|
52
47
|
expect(input.sourceToken.symbol).toBe('USDC')
|
|
@@ -81,7 +76,7 @@ describe('GET /funding/routes', () => {
|
|
|
81
76
|
})
|
|
82
77
|
|
|
83
78
|
const response = await app.request(
|
|
84
|
-
'/v1/funding/routes?sourceChain=base&sourceToken=USDC&sourceAmount=1000000&provider=
|
|
79
|
+
'/v1/funding/routes?sourceChain=base&sourceToken=USDC&sourceAmount=1000000&provider=relay&include=quotes',
|
|
85
80
|
{ headers: { ...TestApp.auth.headers, 'Cache-Control': 'no-cache' } },
|
|
86
81
|
)
|
|
87
82
|
expect(response.status).toBe(200)
|
|
@@ -90,7 +85,7 @@ describe('GET /funding/routes', () => {
|
|
|
90
85
|
expect(body.data).toHaveLength(1)
|
|
91
86
|
expect(body.data[0]).toMatchObject({
|
|
92
87
|
errorCode: null,
|
|
93
|
-
id: 'base-usdc-tempo-usdce-
|
|
88
|
+
id: 'base-usdc-tempo-usdce-relay',
|
|
94
89
|
quality: {
|
|
95
90
|
availableVolumeUsd: '1000.00',
|
|
96
91
|
liquiditySource: 'providerQuote',
|
|
@@ -261,13 +256,7 @@ describe('GET /providers', () => {
|
|
|
261
256
|
expect(response.status).toBe(200)
|
|
262
257
|
const body = await TestApp.json(response, Funding.schema.listProviders.Response)
|
|
263
258
|
|
|
264
|
-
expect(body.data.map((provider) => provider.id)).toEqual([
|
|
265
|
-
'across',
|
|
266
|
-
'relay',
|
|
267
|
-
'bridge',
|
|
268
|
-
'bungee',
|
|
269
|
-
'lifi',
|
|
270
|
-
])
|
|
259
|
+
expect(body.data.map((provider) => provider.id)).toEqual(['relay', 'bridge', 'bungee', 'lifi'])
|
|
271
260
|
expect(body.data.every((provider) => provider.capabilities.includes('fundingRoutes'))).toBe(
|
|
272
261
|
true,
|
|
273
262
|
)
|
|
@@ -275,34 +264,10 @@ describe('GET /providers', () => {
|
|
|
275
264
|
'inventoryOnly',
|
|
276
265
|
'inventoryOnly',
|
|
277
266
|
'inventoryOnly',
|
|
278
|
-
'inventoryOnly',
|
|
279
267
|
'planned',
|
|
280
268
|
])
|
|
281
269
|
})
|
|
282
270
|
|
|
283
|
-
test('marks configured live provider adapters', async () => {
|
|
284
|
-
const app = TestApp.create({
|
|
285
|
-
fundingRoutes: {
|
|
286
|
-
adapters: {
|
|
287
|
-
across: {
|
|
288
|
-
async probeQuote() {
|
|
289
|
-
throw new Error('not used')
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
},
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
const response = await app.request('/v1/providers?capability=fundingRoutes', {
|
|
297
|
-
headers: TestApp.auth.headers,
|
|
298
|
-
})
|
|
299
|
-
expect(response.status).toBe(200)
|
|
300
|
-
const body = await TestApp.json(response, Funding.schema.listProviders.Response)
|
|
301
|
-
|
|
302
|
-
expect(body.data.find((provider) => provider.id === 'across')?.status).toBe('configured')
|
|
303
|
-
expect(body.data.find((provider) => provider.id === 'relay')?.status).toBe('inventoryOnly')
|
|
304
|
-
})
|
|
305
|
-
|
|
306
271
|
test('rejects unsupported provider capabilities', async () => {
|
|
307
272
|
const response = await TestApp.create().request('/v1/providers?capability=settlement', {
|
|
308
273
|
headers: TestApp.auth.headers,
|