Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions apps/ensapi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
"@ensnode/ponder-subgraph": "workspace:*",
"@hono/node-server": "^1.19.5",
"@hono/otel": "^0.2.2",
"@hono/standard-validator": "^0.2.2",
"@hono/zod-openapi": "^1.2.2",
"@namehash/ens-referrals": "workspace:*",
Comment on lines 27 to 31
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pnpm-lock.yaml still lists @hono/standard-validator and hono-openapi under the apps/ensapi importer (so a CI run with --frozen-lockfile will fail). Please regenerate and commit the lockfile after removing these deps from apps/ensapi/package.json.

Copilot uses AI. Check for mistakes.
"@opentelemetry/api": "^1.9.0",
Expand All @@ -52,7 +51,6 @@
"graphql": "^16.11.0",
"graphql-yoga": "^5.16.0",
"hono": "catalog:",
"hono-openapi": "^1.1.2",
"p-memoize": "^8.0.0",
"p-retry": "^7.1.0",
"pg-connection-string": "catalog:",
Expand Down
5 changes: 4 additions & 1 deletion apps/ensapi/src/handlers/amirealtime-api.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const AMIREALTIME_DEFAULT_MAX_WORST_CASE_DISTANCE: Duration = minutesToSe
export const amIRealtimeGetMeta = createRoute({
method: "get",
path: "/",
operationId: "isRealtime",
tags: ["Meta"],
summary: "Check indexing progress",
description:
Expand All @@ -26,7 +27,9 @@ export const amIRealtimeGetMeta = createRoute({
.default(AMIREALTIME_DEFAULT_MAX_WORST_CASE_DISTANCE)
.pipe(
z.coerce
.number({ error: "maxWorstCaseDistance query param must be a number" })
.number({
error: "maxWorstCaseDistance query param must be a number",
})
.pipe(makeDurationSchema("maxWorstCaseDistance query param")),
)
.describe("Maximum acceptable worst-case indexing distance in seconds"),
Expand Down
3 changes: 3 additions & 0 deletions apps/ensapi/src/handlers/ensanalytics-api-v1.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const editionsQuerySchema = z.object({
export const getReferralLeaderboardRoute = createRoute({
method: "get",
path: "/referral-leaderboard",
operationId: "getReferralLeaderboard_v1",
tags: ["ENSAwards"],
summary: "Get Referrer Leaderboard (v1)",
description: "Returns a paginated page from the referrer leaderboard for a specific edition",
Expand All @@ -78,6 +79,7 @@ export const getReferralLeaderboardRoute = createRoute({
export const getReferrerDetailRoute = createRoute({
method: "get",
path: "/referrer/{referrer}",
operationId: "getReferrerDetail_v1",
tags: ["ENSAwards"],
summary: "Get Referrer Detail for Editions (v1)",
description: `Returns detailed information for a specific referrer for the requested editions. Requires 1-${MAX_EDITIONS_PER_REQUEST} distinct edition slugs. All requested editions must be recognized and have cached data, or the request fails.`,
Expand Down Expand Up @@ -107,6 +109,7 @@ export const getReferrerDetailRoute = createRoute({
export const getEditionsRoute = createRoute({
method: "get",
path: "/editions",
operationId: "getEditions_v1",
tags: ["ENSAwards"],
summary: "Get Edition Config Set (v1)",
description:
Expand Down
2 changes: 2 additions & 0 deletions apps/ensapi/src/handlers/ensanalytics-api.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const referrerAddressSchema = z.object({
export const getReferrerLeaderboardRoute = createRoute({
method: "get",
path: "/referrers",
operationId: "getReferrerLeaderboard",
tags: ["ENSAwards"],
summary: "Get Referrer Leaderboard",
description: "Returns a paginated page from the referrer leaderboard",
Expand All @@ -52,6 +53,7 @@ export const getReferrerLeaderboardRoute = createRoute({
export const getReferrerDetailRoute = createRoute({
method: "get",
path: "/referrers/{referrer}",
operationId: "getReferrerDetail",
tags: ["ENSAwards"],
summary: "Get Referrer Detail",
description: "Returns detailed information for a specific referrer by address",
Expand Down
2 changes: 2 additions & 0 deletions apps/ensapi/src/handlers/ensnode-api.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const basePath = "/api";
export const getConfigRoute = createRoute({
method: "get",
path: "/config",
operationId: "getConfig",
tags: ["Meta"],
summary: "Get ENSApi Public Config",
description: "Gets the public config of the ENSApi instance",
Expand All @@ -29,6 +30,7 @@ export const getConfigRoute = createRoute({
export const getIndexingStatusRoute = createRoute({
method: "get",
path: "/indexing-status",
operationId: "getIndexingStatus",
tags: ["Meta"],
summary: "Get ENSIndexer Indexing Status",
description: "Returns the indexing status snapshot most recently captured from ENSIndexer",
Expand Down
1 change: 1 addition & 0 deletions apps/ensapi/src/handlers/name-tokens-api.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export type NameTokensQuery = z.output<typeof nameTokensQuerySchema>;
export const getNameTokensRoute = createRoute({
method: "get",
path: "/",
operationId: "getNameTokens",
tags: ["Explore"],
summary: "Get Name Tokens",
description: "Returns name tokens for the requested identifier (domainId or name)",
Expand Down
2 changes: 2 additions & 0 deletions apps/ensapi/src/handlers/registrar-actions-api.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export type RegistrarActionsQuery = z.output<typeof registrarActionsQuerySchema>
export const getRegistrarActionsRoute = createRoute({
method: "get",
path: "/",
operationId: "getRegistrarActions",
tags: ["Explore"],
summary: "Get Registrar Actions",
description: "Returns all registrar actions with optional filtering and pagination",
Expand All @@ -101,6 +102,7 @@ export const getRegistrarActionsRoute = createRoute({
export const getRegistrarActionsByParentNodeRoute = createRoute({
method: "get",
path: "/{parentNode}",
operationId: "getRegistrarActionsByParentNode",
tags: ["Explore"],
summary: "Get Registrar Actions by Parent Node",
description:
Expand Down
26 changes: 25 additions & 1 deletion apps/ensapi/src/handlers/resolution-api.routes.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { createRoute } from "@hono/zod-openapi";
import { z } from "zod/v4";

import {
makeResolvePrimaryNameResponseSchema,
makeResolvePrimaryNamesResponseSchema,
makeResolveRecordsResponseSchema,
} from "@ensnode/ensnode-sdk/internal";

import { params } from "@/lib/handlers/params.schema";

export const basePath = "/resolve";
export const basePath = "/api/resolve";

export const resolveRecordsRoute = createRoute({
method: "get",
path: "/records/{name}",
operationId: "resolveRecords",
tags: ["Resolution"],
summary: "Resolve ENS Records",
description: "Resolves ENS records for a given name",
Expand All @@ -30,13 +37,19 @@ export const resolveRecordsRoute = createRoute({
responses: {
200: {
description: "Successfully resolved records",
content: {
"application/json": {
schema: makeResolveRecordsResponseSchema(),
},
},
},
},
});

export const resolvePrimaryNameRoute = createRoute({
method: "get",
path: "/primary-name/{address}/{chainId}",
operationId: "resolvePrimaryName",
tags: ["Resolution"],
summary: "Resolve Primary Name",
description: "Resolves a primary name for a given `address` and `chainId`",
Expand All @@ -53,13 +66,19 @@ export const resolvePrimaryNameRoute = createRoute({
responses: {
200: {
description: "Successfully resolved name",
content: {
"application/json": {
schema: makeResolvePrimaryNameResponseSchema(),
},
},
},
},
});

export const resolvePrimaryNamesRoute = createRoute({
method: "get",
path: "/primary-names/{address}",
operationId: "resolvePrimaryNames",
tags: ["Resolution"],
summary: "Resolve Primary Names",
description: "Resolves all primary names for a given address across multiple chains",
Expand All @@ -76,6 +95,11 @@ export const resolvePrimaryNamesRoute = createRoute({
responses: {
200: {
description: "Successfully resolved records",
content: {
"application/json": {
schema: makeResolvePrimaryNamesResponseSchema(),
},
},
},
},
});
Expand Down
50 changes: 8 additions & 42 deletions apps/ensapi/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { serve } from "@hono/node-server";
import { otel } from "@hono/otel";
import { cors } from "hono/cors";
import { html } from "hono/html";
import { openAPIRouteHandler } from "hono-openapi";

import { indexingStatusCache } from "@/cache/indexing-status.cache";
import { getReferralLeaderboardEditionsCaches } from "@/cache/referral-leaderboard-editions.cache";
Expand All @@ -17,6 +16,7 @@ import { factory } from "@/lib/hono-factory";
import { sdk } from "@/lib/instrumentation";
import logger from "@/lib/logger";
import { indexingStatusMiddleware } from "@/middleware/indexing-status.middleware";
import { generateOpenApi31Document } from "@/openapi-document";

import amIRealtimeApi from "./handlers/amirealtime-api";
import ensanalyticsApi from "./handlers/ensanalytics-api";
Expand Down Expand Up @@ -74,47 +74,13 @@ app.route("/v1/ensanalytics", ensanalyticsApiV1);
// use Am I Realtime API at /amirealtime
app.route("/amirealtime", amIRealtimeApi);

// use OpenAPI Schema
app.get(
"/openapi.json",
openAPIRouteHandler(app, {
documentation: {
info: {
title: "ENSApi APIs",
version: packageJson.version,
description:
"APIs for ENS resolution, navigating the ENS nameforest, and metadata about an ENSNode",
},
servers: [
{ url: "https://api.alpha.ensnode.io", description: "ENSNode Alpha (Mainnet)" },
{
url: "https://api.alpha-sepolia.ensnode.io",
description: "ENSNode Alpha (Sepolia Testnet)",
},
{ url: `http://localhost:${config.port}`, description: "Local Development" },
],
tags: [
{
name: "Resolution",
description: "APIs for resolving ENS names and addresses",
},
{
name: "Meta",
description: "APIs for indexing status, configuration, and realtime monitoring",
},
{
name: "Explore",
description:
"APIs for exploring the indexed state of ENS, including name tokens and registrar actions",
},
{
name: "ENSAwards",
description: "APIs for ENSAwards functionality, including referrer data",
},
],
},
}),
);
// serve pre-generated OpenAPI 3.1 document
const openApi31Document = generateOpenApi31Document([
{ url: `http://localhost:${config.port}`, description: "Local Development" },
]);
app.get("/openapi.json", (c) => {
return c.json(openApi31Document);
});

// will automatically 503 if config is not available due to ensIndexerPublicConfigMiddleware
app.get("/health", async (c) => {
Expand Down
29 changes: 0 additions & 29 deletions apps/ensapi/src/lib/handlers/validate.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { OpenAPIHono } from "@hono/zod-openapi";

import { openapiMeta } from "@/openapi-meta";

import * as amIRealtimeRoutes from "./handlers/amirealtime-api.routes";
import * as ensanalyticsRoutes from "./handlers/ensanalytics-api.routes";
import * as ensanalyticsV1Routes from "./handlers/ensanalytics-api-v1.routes";
Expand All @@ -8,24 +10,24 @@ import * as nameTokensRoutes from "./handlers/name-tokens-api.routes";
import * as registrarActionsRoutes from "./handlers/registrar-actions-api.routes";
import * as resolutionRoutes from "./handlers/resolution-api.routes";

const routeGroups = [
amIRealtimeRoutes,
ensnodeRoutes,
ensanalyticsV1Routes,
ensanalyticsRoutes,
nameTokensRoutes,
registrarActionsRoutes,
resolutionRoutes,
];

/**
* Creates an OpenAPIHono app with all route definitions registered using stub handlers.
* This allows generating the OpenAPI spec without importing any handler code that
* depends on config/env vars.
* Creates an OpenAPIHono app with all route definitions registered using stub
* handlers. This allows generating the OpenAPI spec without importing any
* handler code that depends on config/env vars.
*/
export function createStubRoutesForSpec() {
function createStubRoutesForSpec() {
const app = new OpenAPIHono();

const routeGroups = [
amIRealtimeRoutes,
ensnodeRoutes,
ensanalyticsV1Routes,
ensanalyticsRoutes,
nameTokensRoutes,
registrarActionsRoutes,
resolutionRoutes,
];

for (const group of routeGroups) {
for (const route of group.routes) {
const path = route.path === "/" ? group.basePath : `${group.basePath}${route.path}`;
Expand All @@ -39,3 +41,17 @@ export function createStubRoutesForSpec() {

return app;
}

/**
* Generates an OpenAPI 3.1 document from stub route definitions.
* Accepts optional additional servers (e.g. a localhost entry derived from
* runtime config) so that the core generation has no runtime dependencies.
*/
export function generateOpenApi31Document(
additionalServers: { url: string; description: string }[] = [],
): ReturnType<OpenAPIHono["getOpenAPI31Document"]> {
return createStubRoutesForSpec().getOpenAPI31Document({
...openapiMeta,
servers: [...openapiMeta.servers, ...additionalServers],
});
}
4 changes: 3 additions & 1 deletion apps/ensapi/src/openapi-meta.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import packageJson from "@/../package.json" with { type: "json" };

export const openapiMeta = {
openapi: "3.1.0" as const,
info: {
title: "ENSApi APIs",
version: "0.0.0",
version: packageJson.version,
description:
"APIs for ENS resolution, navigating the ENS nameforest, and metadata about an ENSNode",
},
Expand Down
Loading