tapimo 0.4.1 → 0.4.3
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 +2451 -1533
- 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/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 +1354 -984
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.d.ts +94 -5
- package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.js +196 -5
- 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/exchanges.d.ts +453 -453
- package/dist/apps/data/routes/fee-amm.d.ts +101 -101
- package/dist/apps/data/routes/funding.d.ts +730 -138
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +234 -0
- 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/transactions.d.ts +281 -281
- package/dist/apps/data/routes/transfers.d.ts +58 -58
- package/dist/apps/data/routes/verified-tokens.d.ts +81 -81
- package/dist/apps/management/App.d.ts +1156 -615
- 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 +106 -90
- 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 +278 -146
- 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/me.d.ts +6 -6
- 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 +130 -70
- 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 +137 -48
- 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 +35 -3
- 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/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 +13 -18
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +78 -58
- 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/FundingRoutes.d.ts +170 -0
- package/dist/internal/FundingRoutes.d.ts.map +1 -1
- package/dist/internal/FundingRoutes.js +68 -6
- 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/package.json +2 -2
- package/src/ApiKey.ts +2 -3
- package/src/ApiKeys.ts +95 -17
- package/src/App.test.ts +44 -2
- 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 +64 -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 +204 -0
- package/src/apps/data/FundingRouteProviders.ts +294 -9
- package/src/apps/data/routes/funding.test.ts +90 -23
- package/src/apps/data/routes/funding.ts +315 -0
- 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 +25 -1
- package/src/apps/mcp.ts +43 -3
- package/src/apps/relay/App.test.ts +23 -18
- package/src/apps/relay/App.ts +35 -14
- 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 +174 -7
- package/src/internal/Auth.ts +90 -62
- 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 +26 -0
- package/src/internal/FundingRoutes.ts +187 -18
- package/src/internal/Log.test.ts +41 -3
- package/src/internal/Log.ts +23 -4
|
@@ -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
|
|
|
@@ -93,6 +93,160 @@ describe('FundingRouteProviders', () => {
|
|
|
93
93
|
})
|
|
94
94
|
})
|
|
95
95
|
|
|
96
|
+
test('builds an authenticated exact-input Across quote request', async () => {
|
|
97
|
+
const route = await staticRoute('across')
|
|
98
|
+
const request = FundingRouteProviders.createAcrossQuoteRequest({
|
|
99
|
+
apiKey: 'across-api-key',
|
|
100
|
+
baseUrl: 'https://across.test/api/',
|
|
101
|
+
depositor: '0x1111111111111111111111111111111111111111',
|
|
102
|
+
integratorId: '0xdead',
|
|
103
|
+
recipient: '0x2222222222222222222222222222222222222222',
|
|
104
|
+
route,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
expect(request.url.pathname).toBe('/api/swap/approval')
|
|
108
|
+
expect(request.url.searchParams.get('originChainId')).toBe('8453')
|
|
109
|
+
expect(request.url.searchParams.get('destinationChainId')).toBe('4217')
|
|
110
|
+
expect(request.url.searchParams.get('amount')).toBe('1000000')
|
|
111
|
+
expect(request.url.searchParams.get('integratorId')).toBe('0xdead')
|
|
112
|
+
expect(request.url.searchParams.get('tradeType')).toBe('exactInput')
|
|
113
|
+
expect(request.url.searchParams.get('skipOriginTxEstimation')).toBe('true')
|
|
114
|
+
expect(request.url.searchParams.get('strictTradeType')).toBe('true')
|
|
115
|
+
expect(request.url.searchParams.get('depositor')).toBe(
|
|
116
|
+
'0x1111111111111111111111111111111111111111',
|
|
117
|
+
)
|
|
118
|
+
expect(request.url.searchParams.get('recipient')).toBe(
|
|
119
|
+
'0x2222222222222222222222222222222222222222',
|
|
120
|
+
)
|
|
121
|
+
expect(new Headers(request.headers).get('authorization')).toBe('Bearer across-api-key')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('calls the runtime fetch method with its global receiver', async () => {
|
|
125
|
+
const route = await staticRoute('across')
|
|
126
|
+
vi.useFakeTimers()
|
|
127
|
+
vi.setSystemTime(now())
|
|
128
|
+
const fetch = vi.fn(function (this: unknown) {
|
|
129
|
+
expect(this).toBe(globalThis)
|
|
130
|
+
return Promise.resolve(jsonResponse(acrossQuoteResponse(route)))
|
|
131
|
+
})
|
|
132
|
+
vi.stubGlobal('fetch', fetch)
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const quote = FundingRouteProviders.acrossQuote({
|
|
136
|
+
apiKey: 'across-api-key',
|
|
137
|
+
integratorId: '0xdead',
|
|
138
|
+
})
|
|
139
|
+
const result = await quote(
|
|
140
|
+
{ depositor: '0x1111111111111111111111111111111111111111', route },
|
|
141
|
+
new AbortController().signal,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
expect(result.outputAmount).toBe('990000')
|
|
145
|
+
expect(fetch).toHaveBeenCalledOnce()
|
|
146
|
+
} finally {
|
|
147
|
+
vi.unstubAllGlobals()
|
|
148
|
+
vi.useRealTimers()
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test('normalizes an Across quote payload', async () => {
|
|
153
|
+
const route = await staticRoute('across')
|
|
154
|
+
const result = FundingRouteProviders.parseAcrossQuote({
|
|
155
|
+
body: acrossQuoteResponse(route),
|
|
156
|
+
now: now(),
|
|
157
|
+
route,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
expect(result).toMatchObject({
|
|
161
|
+
estimatedSeconds: 2,
|
|
162
|
+
expiresAt: '2026-01-01T00:01:00.000Z',
|
|
163
|
+
fees: {
|
|
164
|
+
total: {
|
|
165
|
+
amount: '1200',
|
|
166
|
+
amountUsd: '0.0012',
|
|
167
|
+
bps: 12,
|
|
168
|
+
token: { chainId: 'eip155:8453', symbol: 'USDC' },
|
|
169
|
+
},
|
|
170
|
+
totalMax: {
|
|
171
|
+
amount: '1800',
|
|
172
|
+
amountUsd: '0.0018',
|
|
173
|
+
bps: 18,
|
|
174
|
+
token: { chainId: 'eip155:8453', symbol: 'USDC' },
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
minimumOutputAmount: '988000',
|
|
178
|
+
outputAmount: '990000',
|
|
179
|
+
sampledAt: '2026-01-01T00:00:00.000Z',
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
test('maps Across HTTP errors into stable provider errors', () => {
|
|
184
|
+
expect(
|
|
185
|
+
FundingRouteProviders.mapAcrossQuoteError({ body: { code: 'RATE_LIMITED' }, status: 429 }),
|
|
186
|
+
).toBeInstanceOf(FundingRoutes.ProviderRateLimitError)
|
|
187
|
+
expect(
|
|
188
|
+
FundingRouteProviders.mapAcrossQuoteError({ body: { code: 'NO_ROUTE' }, status: 400 }),
|
|
189
|
+
).toBeInstanceOf(FundingRoutes.ProviderQuoteUnavailableError)
|
|
190
|
+
const error = FundingRouteProviders.mapAcrossQuoteError({
|
|
191
|
+
body: { code: 'NOT_FOUND' },
|
|
192
|
+
status: 404,
|
|
193
|
+
})
|
|
194
|
+
expect(error).toMatchObject({ name: 'FundingRouteProviders.ProviderResponseError' })
|
|
195
|
+
expect(FundingRouteProviders.acrossQuoteFailure({ cause: error })).toEqual({
|
|
196
|
+
code: 'NOT_FOUND',
|
|
197
|
+
failure: 'http',
|
|
198
|
+
id: 'across',
|
|
199
|
+
operation: 'swap/approval',
|
|
200
|
+
status: 404,
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test('rejects invalid Across configuration during adapter construction', () => {
|
|
205
|
+
expect(() =>
|
|
206
|
+
FundingRouteProviders.acrossQuote({ apiKey: 'across-api-key', integratorId: 'tempo' }),
|
|
207
|
+
).toThrowErrorMatchingInlineSnapshot(`[FundingRouteProviders.ProviderConfigurationError]`)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test('rejects malformed Across quote payloads', async () => {
|
|
211
|
+
const route = await staticRoute('across')
|
|
212
|
+
const failure = (() => {
|
|
213
|
+
try {
|
|
214
|
+
FundingRouteProviders.parseAcrossQuote({
|
|
215
|
+
body: { expectedOutputAmount: '990000' },
|
|
216
|
+
now: now(),
|
|
217
|
+
route,
|
|
218
|
+
})
|
|
219
|
+
throw new Error('Expected malformed payload to fail')
|
|
220
|
+
} catch (cause) {
|
|
221
|
+
return cause
|
|
222
|
+
}
|
|
223
|
+
})()
|
|
224
|
+
expect(failure).toMatchObject({ name: 'FundingRouteProviders.ProviderPayloadError' })
|
|
225
|
+
expect(FundingRouteProviders.acrossQuoteFailure({ cause: failure })).toEqual({
|
|
226
|
+
failure: 'payload',
|
|
227
|
+
id: 'across',
|
|
228
|
+
operation: 'swap/approval',
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
test('rejects inconsistent Across fee summaries', async () => {
|
|
233
|
+
const route = await staticRoute('across')
|
|
234
|
+
const body = acrossQuoteResponse(route)
|
|
235
|
+
body.fees.totalMax.amount = '1000'
|
|
236
|
+
expect(() => FundingRouteProviders.parseAcrossQuote({ body, now: now(), route })).toThrow(
|
|
237
|
+
expect.objectContaining({ name: 'FundingRouteProviders.ProviderPayloadError' }),
|
|
238
|
+
)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
test('rejects mismatched Across fee tokens', async () => {
|
|
242
|
+
const route = await staticRoute('across')
|
|
243
|
+
const body = acrossQuoteResponse(route)
|
|
244
|
+
body.fees.total.token.address = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
|
|
245
|
+
expect(() => FundingRouteProviders.parseAcrossQuote({ body, now: now(), route })).toThrow(
|
|
246
|
+
expect.objectContaining({ name: 'FundingRouteProviders.ProviderPayloadError' }),
|
|
247
|
+
)
|
|
248
|
+
})
|
|
249
|
+
|
|
96
250
|
test('maps Relay liquidity and quote responses into quote probes', async () => {
|
|
97
251
|
const requests: RecordedRequest[] = []
|
|
98
252
|
const route = await staticRoute('relay')
|
|
@@ -252,6 +406,56 @@ function probeInputFromRoute(
|
|
|
252
406
|
}
|
|
253
407
|
}
|
|
254
408
|
|
|
409
|
+
function acrossQuoteResponse(route: FundingRoutes.QuoteProbeInput['route']) {
|
|
410
|
+
return {
|
|
411
|
+
amountType: 'exactInput',
|
|
412
|
+
approvalTxns: [{ data: '0x1234', to: '0x2222222222222222222222222222222222222222' }],
|
|
413
|
+
checks: { balance: { actual: '1000000', expected: '1000000' } },
|
|
414
|
+
expectedFillTime: 2,
|
|
415
|
+
expectedOutputAmount: '990000',
|
|
416
|
+
fees: {
|
|
417
|
+
total: {
|
|
418
|
+
amount: '1200',
|
|
419
|
+
amountUsd: '0.0012',
|
|
420
|
+
pct: '1200000000000000',
|
|
421
|
+
token: {
|
|
422
|
+
address: route.sourceToken.address,
|
|
423
|
+
chainId: 8453,
|
|
424
|
+
decimals: 6,
|
|
425
|
+
symbol: 'USDC',
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
totalMax: {
|
|
429
|
+
amount: '1800',
|
|
430
|
+
amountUsd: '0.0018',
|
|
431
|
+
pct: '1800000000000000',
|
|
432
|
+
token: {
|
|
433
|
+
address: route.sourceToken.address,
|
|
434
|
+
chainId: 8453,
|
|
435
|
+
decimals: 6,
|
|
436
|
+
symbol: 'USDC',
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
inputAmount: '1000000',
|
|
441
|
+
inputToken: {
|
|
442
|
+
address: route.sourceToken.address,
|
|
443
|
+
chainId: 8453,
|
|
444
|
+
decimals: 6,
|
|
445
|
+
symbol: 'USDC',
|
|
446
|
+
},
|
|
447
|
+
minOutputAmount: '988000',
|
|
448
|
+
outputToken: {
|
|
449
|
+
address: route.destinationToken.address,
|
|
450
|
+
chainId: 4217,
|
|
451
|
+
decimals: 6,
|
|
452
|
+
symbol: 'USDC.e',
|
|
453
|
+
},
|
|
454
|
+
quoteExpiryTimestamp: 1767225660,
|
|
455
|
+
swapTx: { data: '0xabcd', to: '0x3333333333333333333333333333333333333333' },
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
255
459
|
function recordingFetch(
|
|
256
460
|
requests: RecordedRequest[],
|
|
257
461
|
respond: (request: RecordedRequest) => Response | Promise<Response>,
|