diff --git a/apps/ensapi/src/handlers/amirealtime-api.routes.ts b/apps/ensapi/src/handlers/amirealtime-api.routes.ts index 3d379aa83..328b94167 100644 --- a/apps/ensapi/src/handlers/amirealtime-api.routes.ts +++ b/apps/ensapi/src/handlers/amirealtime-api.routes.ts @@ -1,6 +1,5 @@ -import { createRoute } from "@hono/zod-openapi"; +import { createRoute, z } from "@hono/zod-openapi"; import { minutesToSeconds } from "date-fns"; -import { z } from "zod/v4"; import type { Duration } from "@ensnode/ensnode-sdk"; import { makeDurationSchema } from "@ensnode/ensnode-sdk/internal"; @@ -32,6 +31,7 @@ export const amIRealtimeGetMeta = createRoute({ }) .pipe(makeDurationSchema("maxWorstCaseDistance query param")), ) + .openapi({ type: "integer", minimum: 0 }) .describe("Maximum acceptable worst-case indexing distance in seconds"), }), }, diff --git a/apps/ensapi/src/handlers/ensanalytics-api-v1.routes.ts b/apps/ensapi/src/handlers/ensanalytics-api-v1.routes.ts index 492495075..ceef7fc5e 100644 --- a/apps/ensapi/src/handlers/ensanalytics-api-v1.routes.ts +++ b/apps/ensapi/src/handlers/ensanalytics-api-v1.routes.ts @@ -1,4 +1,4 @@ -import { createRoute } from "@hono/zod-openapi"; +import { createRoute, z } from "@hono/zod-openapi"; import { MAX_EDITIONS_PER_REQUEST, REFERRERS_PER_LEADERBOARD_PAGE_MAX, @@ -7,7 +7,6 @@ import { makeReferralProgramEditionSlugSchema, makeReferrerMetricsEditionsArraySchema, } from "@namehash/ens-referrals/v1/internal"; -import { z } from "zod/v4"; import { makeLowercaseAddressSchema } from "@ensnode/ensnode-sdk/internal"; @@ -21,6 +20,7 @@ const referrerLeaderboardPageQuerySchema = z.object({ edition: makeReferralProgramEditionSlugSchema("edition"), page: z .optional(z.coerce.number().int().min(1, "Page must be a positive integer")) + .openapi({ type: "integer", minimum: 1 }) .describe("Page number for pagination"), recordsPerPage: z .optional( @@ -33,6 +33,7 @@ const referrerLeaderboardPageQuerySchema = z.object({ `Records per page must not exceed ${REFERRERS_PER_LEADERBOARD_PAGE_MAX}`, ), ) + .openapi({ type: "integer", minimum: 1, maximum: REFERRERS_PER_LEADERBOARD_PAGE_MAX }) .describe("Number of referrers per page"), }); diff --git a/apps/ensapi/src/handlers/ensanalytics-api.routes.ts b/apps/ensapi/src/handlers/ensanalytics-api.routes.ts index e331f635b..cb14bac2b 100644 --- a/apps/ensapi/src/handlers/ensanalytics-api.routes.ts +++ b/apps/ensapi/src/handlers/ensanalytics-api.routes.ts @@ -1,6 +1,5 @@ -import { createRoute } from "@hono/zod-openapi"; +import { createRoute, z } from "@hono/zod-openapi"; import { REFERRERS_PER_LEADERBOARD_PAGE_MAX } from "@namehash/ens-referrals"; -import { z } from "zod/v4"; import { makeLowercaseAddressSchema } from "@ensnode/ensnode-sdk/internal"; @@ -10,6 +9,7 @@ export const basePath = "/ensanalytics"; const paginationQuerySchema = z.object({ page: z .optional(z.coerce.number().int().min(1, "Page must be a positive integer")) + .openapi({ type: "integer", minimum: 1 }) .describe("Page number for pagination"), recordsPerPage: z .optional( @@ -22,6 +22,7 @@ const paginationQuerySchema = z.object({ `Records per page must not exceed ${REFERRERS_PER_LEADERBOARD_PAGE_MAX}`, ), ) + .openapi({ type: "integer", minimum: 1, maximum: REFERRERS_PER_LEADERBOARD_PAGE_MAX }) .describe("Number of referrers per page"), }); diff --git a/apps/ensapi/src/handlers/name-tokens-api.routes.ts b/apps/ensapi/src/handlers/name-tokens-api.routes.ts index e15d9a4fa..01a1c773f 100644 --- a/apps/ensapi/src/handlers/name-tokens-api.routes.ts +++ b/apps/ensapi/src/handlers/name-tokens-api.routes.ts @@ -1,5 +1,4 @@ -import { createRoute } from "@hono/zod-openapi"; -import { z } from "zod/v4"; +import { createRoute, z } from "@hono/zod-openapi"; import { ErrorResponseSchema, diff --git a/apps/ensapi/src/handlers/registrar-actions-api.routes.ts b/apps/ensapi/src/handlers/registrar-actions-api.routes.ts index 3b28b2aec..b9f70c983 100644 --- a/apps/ensapi/src/handlers/registrar-actions-api.routes.ts +++ b/apps/ensapi/src/handlers/registrar-actions-api.routes.ts @@ -1,5 +1,4 @@ -import { createRoute } from "@hono/zod-openapi"; -import { z } from "zod/v4"; +import { createRoute, z } from "@hono/zod-openapi"; import { RECORDS_PER_PAGE_DEFAULT, @@ -30,6 +29,7 @@ export const registrarActionsQuerySchema = z .default(1) .pipe(z.coerce.number()) .pipe(makePositiveIntegerSchema("page")) + .openapi({ type: "integer", minimum: 1, default: 1 }) .describe("Page number for pagination"), recordsPerPage: params.queryParam @@ -37,12 +37,19 @@ export const registrarActionsQuerySchema = z .default(RECORDS_PER_PAGE_DEFAULT) .pipe(z.coerce.number()) .pipe(makePositiveIntegerSchema("recordsPerPage").max(RECORDS_PER_PAGE_MAX)) + .openapi({ + type: "integer", + minimum: 1, + maximum: RECORDS_PER_PAGE_MAX, + default: RECORDS_PER_PAGE_DEFAULT, + }) .describe("Number of records per page"), withReferral: params.boolstring .optional() .default(false) - .describe("Filter to only include actions with referrals"), + .describe("Filter to only include actions with referrals") + .openapi({ default: false }), decodedReferrer: makeLowercaseAddressSchema("decodedReferrer") .optional() diff --git a/apps/ensapi/src/handlers/resolution-api.routes.ts b/apps/ensapi/src/handlers/resolution-api.routes.ts index 1af335aeb..c6eadb165 100644 --- a/apps/ensapi/src/handlers/resolution-api.routes.ts +++ b/apps/ensapi/src/handlers/resolution-api.routes.ts @@ -1,5 +1,4 @@ -import { createRoute } from "@hono/zod-openapi"; -import { z } from "zod/v4"; +import { createRoute, z } from "@hono/zod-openapi"; import { makeResolvePrimaryNameResponseSchema, diff --git a/apps/ensapi/src/index.ts b/apps/ensapi/src/index.ts index 87fdcb2a1..778ab816c 100644 --- a/apps/ensapi/src/index.ts +++ b/apps/ensapi/src/index.ts @@ -75,9 +75,7 @@ app.route("/v1/ensanalytics", ensanalyticsApiV1); app.route("/amirealtime", amIRealtimeApi); // serve pre-generated OpenAPI 3.1 document -const openApi31Document = generateOpenApi31Document([ - { url: `http://localhost:${config.port}`, description: "Local Development" }, -]); +const openApi31Document = generateOpenApi31Document(); app.get("/openapi.json", (c) => { return c.json(openApi31Document); }); diff --git a/apps/ensapi/src/lib/handlers/params.schema.ts b/apps/ensapi/src/lib/handlers/params.schema.ts index 948d7476a..00d83c22d 100644 --- a/apps/ensapi/src/lib/handlers/params.schema.ts +++ b/apps/ensapi/src/lib/handlers/params.schema.ts @@ -1,4 +1,4 @@ -import { z } from "zod/v4"; +import { z } from "@hono/zod-openapi"; import { DEFAULT_EVM_CHAIN_ID, @@ -23,7 +23,8 @@ const excludingDefaultChainId = z const boolstring = z .string() .pipe(z.enum(["true", "false"])) - .transform((val) => val === "true"); + .transform((val) => val === "true") + .openapi({ type: "boolean" }); const stringarray = z .string() @@ -38,8 +39,8 @@ const name = z .refine(isNormalizedName, "Must be normalized, see https://docs.ens.domains/resolution/names/") .transform((val) => val as Name); -const trace = z.optional(boolstring).default(false); -const accelerate = z.optional(boolstring).default(false); +const trace = z.optional(boolstring).default(false).openapi({ default: false }); +const accelerate = z.optional(boolstring).default(false).openapi({ default: false }); const address = makeLowercaseAddressSchema(); const defaultableChainId = makeDefaultableChainIdStringSchema(); const coinType = makeCoinTypeStringSchema(); diff --git a/apps/ensapi/src/openapi-document.ts b/apps/ensapi/src/openapi-document.ts index e1e26dc7c..7a45968e0 100644 --- a/apps/ensapi/src/openapi-document.ts +++ b/apps/ensapi/src/openapi-document.ts @@ -44,14 +44,7 @@ function createStubRoutesForSpec() { /** * 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 { - return createStubRoutesForSpec().getOpenAPI31Document({ - ...openapiMeta, - servers: [...openapiMeta.servers, ...additionalServers], - }); +export function generateOpenApi31Document(): ReturnType { + return createStubRoutesForSpec().getOpenAPI31Document(openapiMeta); } diff --git a/docs/docs.ensnode.io/README.md b/docs/docs.ensnode.io/README.md index de8f99ab7..850b18bda 100644 --- a/docs/docs.ensnode.io/README.md +++ b/docs/docs.ensnode.io/README.md @@ -2,29 +2,25 @@ [docs.ensnode.io](https://docs.ensnode.io) runs on [Mintlify](https://mintlify.com). -## Local Development - -### Getting Started +Learn more about [ENSNode](https://ensnode.io) from [the "Starlight" ENSNode docs](https://ensnode.io/docs/). Everything from these "Starlight" docs is planned to be transitioned into these Mintlify docs soon. -1. Clone the repository: - - ```bash - git clone https://github.com/namehash/ensnode.git - ``` +## Local Development -2. Navigate to the docs directory: +1. `git clone https://github.com/namehash/ensnode.git` +2. `cd ensnode` +3. `cd docs/docs.ensnode.io` +4. `pnpm mint dev` +5. Open [http://localhost:3000](http://localhost:3000) in your browser - ```bash - cd ensnode/docs/docs.ensnode.io - ``` +### Regenerating the OpenAPI Spec -3. Start the local development server: +The ENSApi OpenAPI spec (`ensapi-openapi.json`) is generated from the route definitions in `apps/ensapi`. To regenerate it after making changes to route schemas: - ```bash - pnpm mint dev - ``` +```sh +pnpm generate:openapi +``` -4. Open [http://localhost:3000](http://localhost:3000) in your browser +This runs from the repo root and outputs the formatted spec to `docs/docs.ensnode.io/ensapi-openapi.json`. ### Troubleshooting @@ -38,3 +34,4 @@ Changes pushed to the main branch are automatically deployed to production. ## Resources - [Mintlify documentation](https://mintlify.com/docs) +- [ENSNode "Starlight" docs](https://ensnode.io/docs/) diff --git a/docs/docs.ensnode.io/docs.json b/docs/docs.ensnode.io/docs.json index 8f4554902..5502080c8 100644 --- a/docs/docs.ensnode.io/docs.json +++ b/docs/docs.ensnode.io/docs.json @@ -24,7 +24,13 @@ }, { "group": "API Reference", - "openapi": "https://gist.githubusercontent.com/notrab/94b637e77468cbddd895d7933ce88f64/raw/12cb5ed183558a9bdda5d1c7004db6c794dbd13e/green-ensnode-openapi.json" + "openapi": "https://api.alpha.ensnode.io/openapi.json" + }, + { + "group": "Preview", + "pages": ["ensapi/preview"], + "openapi": "ensapi-openapi.json", + "hidden": true } ] } diff --git a/docs/docs.ensnode.io/ensapi-openapi.json b/docs/docs.ensnode.io/ensapi-openapi.json new file mode 100644 index 000000000..7630b76de --- /dev/null +++ b/docs/docs.ensnode.io/ensapi-openapi.json @@ -0,0 +1,2076 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "ENSApi APIs", + "version": "1.5.1", + "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)" + } + ], + "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" + } + ], + "components": { "schemas": {}, "parameters": {} }, + "paths": { + "/amirealtime": { + "get": { + "operationId": "isRealtime", + "tags": ["Meta"], + "summary": "Check indexing progress", + "description": "Checks if the indexing progress is guaranteed to be within a requested worst-case distance of realtime", + "parameters": [ + { + "schema": { + "type": "integer", + "minimum": 0, + "description": "Maximum acceptable worst-case indexing distance in seconds" + }, + "required": false, + "description": "Maximum acceptable worst-case indexing distance in seconds", + "name": "maxWorstCaseDistance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Indexing progress is guaranteed to be within the requested distance of realtime" + }, + "503": { + "description": "Indexing progress is not guaranteed to be within the requested distance of realtime or indexing status unavailable" + } + } + } + }, + "/api/config": { + "get": { + "operationId": "getConfig", + "tags": ["Meta"], + "summary": "Get ENSApi Public Config", + "description": "Gets the public config of the ENSApi instance", + "responses": { + "200": { + "description": "Successfully retrieved ENSApi public config", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": { "type": "string", "minLength": 1 }, + "theGraphFallback": { + "oneOf": [ + { + "type": "object", + "properties": { + "canFallback": { "type": "boolean", "enum": [true] }, + "url": { "type": "string" } + }, + "required": ["canFallback", "url"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "canFallback": { "type": "boolean", "enum": [false] }, + "reason": { + "type": "string", + "enum": ["not-subgraph-compatible", "no-api-key", "no-subgraph-url"] + } + }, + "required": ["canFallback", "reason"], + "additionalProperties": false + } + ] + }, + "ensIndexerPublicConfig": { + "type": "object", + "properties": { + "labelSet": { + "type": "object", + "properties": { + "labelSetId": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "pattern": "^[a-z-]+$" + }, + "labelSetVersion": { "type": ["number", "null"] } + }, + "required": ["labelSetId", "labelSetVersion"] + }, + "indexedChainIds": { + "type": "array", + "items": { "type": "integer", "exclusiveMinimum": 0 }, + "minItems": 1 + }, + "isSubgraphCompatible": { "type": "boolean" }, + "namespace": { + "type": "string", + "enum": ["mainnet", "sepolia", "sepolia-v2", "ens-test-env"] + }, + "plugins": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "databaseSchemaName": { "type": "string", "minLength": 1 }, + "versionInfo": { + "type": "object", + "properties": { + "nodejs": { "type": "string", "minLength": 1 }, + "ponder": { "type": "string", "minLength": 1 }, + "ensDb": { "type": "string", "minLength": 1 }, + "ensIndexer": { "type": "string", "minLength": 1 }, + "ensNormalize": { "type": "string", "minLength": 1 }, + "ensRainbow": { "type": "string", "minLength": 1 }, + "ensRainbowSchema": { "type": "integer", "exclusiveMinimum": 0 } + }, + "required": [ + "nodejs", + "ponder", + "ensDb", + "ensIndexer", + "ensNormalize", + "ensRainbow", + "ensRainbowSchema" + ], + "additionalProperties": false + } + }, + "required": [ + "labelSet", + "indexedChainIds", + "isSubgraphCompatible", + "namespace", + "plugins", + "databaseSchemaName", + "versionInfo" + ] + } + }, + "required": ["version", "theGraphFallback", "ensIndexerPublicConfig"] + } + } + } + } + } + } + }, + "/api/indexing-status": { + "get": { + "operationId": "getIndexingStatus", + "tags": ["Meta"], + "summary": "Get ENSIndexer Indexing Status", + "description": "Returns the indexing status snapshot most recently captured from ENSIndexer", + "responses": { + "200": { + "description": "Successfully retrieved indexing status", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "responseCode": { "type": "string", "enum": ["ok"] }, + "realtimeProjection": { + "type": "object", + "properties": { + "snapshot": { + "type": "object", + "properties": { + "strategy": { "type": "string", "enum": ["omnichain"] }, + "slowestChainIndexingCursor": { "type": "integer" }, + "snapshotTime": { "type": "integer" }, + "omnichainSnapshot": { + "oneOf": [ + { + "type": "object", + "properties": { + "omnichainStatus": { + "type": "string", + "enum": ["omnichain-unstarted"] + }, + "chains": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "object", + "properties": { + "chainStatus": { + "type": "string", + "enum": ["chain-queued"] + }, + "config": { + "oneOf": [ + { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["left-bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": ["rangeType", "startBlock"] + }, + { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + }, + "endBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": [ + "rangeType", + "startBlock", + "endBlock" + ] + } + ] + } + }, + "required": ["chainStatus", "config"] + } + ] + } + }, + "omnichainIndexingCursor": { "type": "integer" } + }, + "required": [ + "omnichainStatus", + "chains", + "omnichainIndexingCursor" + ] + }, + { + "type": "object", + "properties": { + "omnichainStatus": { + "type": "string", + "enum": ["omnichain-backfill"] + }, + "chains": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "object", + "properties": { + "chainStatus": { + "type": "string", + "enum": ["chain-queued"] + }, + "config": { + "oneOf": [ + { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["left-bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": ["rangeType", "startBlock"] + }, + { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + }, + "endBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": [ + "rangeType", + "startBlock", + "endBlock" + ] + } + ] + } + }, + "required": ["chainStatus", "config"] + }, + { + "type": "object", + "properties": { + "chainStatus": { + "type": "string", + "enum": ["chain-backfill"] + }, + "config": { + "oneOf": [ + { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["left-bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": ["rangeType", "startBlock"] + }, + { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + }, + "endBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": [ + "rangeType", + "startBlock", + "endBlock" + ] + } + ] + }, + "latestIndexedBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + }, + "backfillEndBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": [ + "chainStatus", + "config", + "latestIndexedBlock", + "backfillEndBlock" + ] + }, + { + "type": "object", + "properties": { + "chainStatus": { + "type": "string", + "enum": ["chain-completed"] + }, + "config": { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + }, + "endBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": ["rangeType", "startBlock", "endBlock"] + }, + "latestIndexedBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": [ + "chainStatus", + "config", + "latestIndexedBlock" + ] + } + ] + } + }, + "omnichainIndexingCursor": { "type": "integer" } + }, + "required": [ + "omnichainStatus", + "chains", + "omnichainIndexingCursor" + ] + }, + { + "type": "object", + "properties": { + "omnichainStatus": { + "type": "string", + "enum": ["omnichain-completed"] + }, + "chains": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "object", + "properties": { + "chainStatus": { + "type": "string", + "enum": ["chain-completed"] + }, + "config": { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + }, + "endBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": ["rangeType", "startBlock", "endBlock"] + }, + "latestIndexedBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": [ + "chainStatus", + "config", + "latestIndexedBlock" + ] + } + ] + } + }, + "omnichainIndexingCursor": { "type": "integer" } + }, + "required": [ + "omnichainStatus", + "chains", + "omnichainIndexingCursor" + ] + }, + { + "type": "object", + "properties": { + "omnichainStatus": { + "type": "string", + "enum": ["omnichain-following"] + }, + "chains": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "object", + "properties": { + "chainStatus": { + "type": "string", + "enum": ["chain-queued"] + }, + "config": { + "oneOf": [ + { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["left-bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": ["rangeType", "startBlock"] + }, + { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + }, + "endBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": [ + "rangeType", + "startBlock", + "endBlock" + ] + } + ] + } + }, + "required": ["chainStatus", "config"] + }, + { + "type": "object", + "properties": { + "chainStatus": { + "type": "string", + "enum": ["chain-backfill"] + }, + "config": { + "oneOf": [ + { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["left-bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": ["rangeType", "startBlock"] + }, + { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + }, + "endBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": [ + "rangeType", + "startBlock", + "endBlock" + ] + } + ] + }, + "latestIndexedBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + }, + "backfillEndBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": [ + "chainStatus", + "config", + "latestIndexedBlock", + "backfillEndBlock" + ] + }, + { + "type": "object", + "properties": { + "chainStatus": { + "type": "string", + "enum": ["chain-following"] + }, + "config": { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["left-bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": ["rangeType", "startBlock"] + }, + "latestIndexedBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + }, + "latestKnownBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": [ + "chainStatus", + "config", + "latestIndexedBlock", + "latestKnownBlock" + ] + }, + { + "type": "object", + "properties": { + "chainStatus": { + "type": "string", + "enum": ["chain-completed"] + }, + "config": { + "type": "object", + "properties": { + "rangeType": { + "type": "string", + "enum": ["bounded"] + }, + "startBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + }, + "endBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": ["rangeType", "startBlock", "endBlock"] + }, + "latestIndexedBlock": { + "type": "object", + "properties": { + "timestamp": { "type": "integer" }, + "number": { "type": "integer", "minimum": 0 } + }, + "required": ["timestamp", "number"], + "additionalProperties": false + } + }, + "required": [ + "chainStatus", + "config", + "latestIndexedBlock" + ] + } + ] + } + }, + "omnichainIndexingCursor": { "type": "integer" } + }, + "required": [ + "omnichainStatus", + "chains", + "omnichainIndexingCursor" + ] + } + ] + } + }, + "required": [ + "strategy", + "slowestChainIndexingCursor", + "snapshotTime", + "omnichainSnapshot" + ] + }, + "projectedAt": { "type": "integer" }, + "worstCaseDistance": { "type": "number" } + }, + "required": ["snapshot", "projectedAt", "worstCaseDistance"] + } + }, + "required": ["responseCode", "realtimeProjection"], + "additionalProperties": false + } + } + } + }, + "503": { + "description": "Indexing status snapshot unavailable", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { "responseCode": { "type": "string", "enum": ["error"] } }, + "required": ["responseCode"], + "additionalProperties": false + } + } + } + } + } + } + }, + "/v1/ensanalytics/referral-leaderboard": { + "get": { + "operationId": "getReferralLeaderboard_v1", + "tags": ["ENSAwards"], + "summary": "Get Referrer Leaderboard (v1)", + "description": "Returns a paginated page from the referrer leaderboard for a specific edition", + "parameters": [ + { + "schema": { "type": "string", "minLength": 1, "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" }, + "required": true, + "name": "edition", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "description": "Page number for pagination" + }, + "required": false, + "description": "Page number for pagination", + "name": "page", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "description": "Number of referrers per page" + }, + "required": false, + "description": "Number of referrers per page", + "name": "recordsPerPage", + "in": "query" + } + ], + "responses": { + "200": { "description": "Successfully retrieved referrer leaderboard page" }, + "404": { "description": "Unknown edition slug" }, + "500": { "description": "Internal server error" }, + "503": { "description": "Service unavailable" } + } + } + }, + "/v1/ensanalytics/referrer/{referrer}": { + "get": { + "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-20 distinct edition slugs. All requested editions must be recognized and have cached data, or the request fails.", + "parameters": [ + { + "schema": { "type": "string", "description": "Referrer Ethereum address" }, + "required": true, + "description": "Referrer Ethereum address", + "name": "referrer", + "in": "path" + }, + { + "schema": { "type": "string", "description": "Comma-separated list of edition slugs" }, + "required": true, + "description": "Comma-separated list of edition slugs", + "name": "editions", + "in": "query" + } + ], + "responses": { + "200": { "description": "Successfully retrieved referrer detail for requested editions" }, + "400": { "description": "Invalid request" }, + "404": { "description": "Unknown edition slug" }, + "500": { "description": "Internal server error" }, + "503": { "description": "Service unavailable" } + } + } + }, + "/v1/ensanalytics/editions": { + "get": { + "operationId": "getEditions_v1", + "tags": ["ENSAwards"], + "summary": "Get Edition Config Set (v1)", + "description": "Returns the currently configured referral program edition config set. Editions are sorted in descending order by start timestamp (most recent first).", + "responses": { + "200": { "description": "Successfully retrieved edition config set" }, + "500": { "description": "Internal server error" }, + "503": { "description": "Service unavailable" } + } + } + }, + "/ensanalytics/referrers": { + "get": { + "operationId": "getReferrerLeaderboard", + "tags": ["ENSAwards"], + "summary": "Get Referrer Leaderboard", + "description": "Returns a paginated page from the referrer leaderboard", + "parameters": [ + { + "schema": { + "type": "integer", + "minimum": 1, + "description": "Page number for pagination" + }, + "required": false, + "description": "Page number for pagination", + "name": "page", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "description": "Number of referrers per page" + }, + "required": false, + "description": "Number of referrers per page", + "name": "recordsPerPage", + "in": "query" + } + ], + "responses": { + "200": { "description": "Successfully retrieved referrer leaderboard page" }, + "500": { "description": "Internal server error" } + } + } + }, + "/ensanalytics/referrers/{referrer}": { + "get": { + "operationId": "getReferrerDetail", + "tags": ["ENSAwards"], + "summary": "Get Referrer Detail", + "description": "Returns detailed information for a specific referrer by address", + "parameters": [ + { + "schema": { "type": "string", "description": "Referrer Ethereum address" }, + "required": true, + "description": "Referrer Ethereum address", + "name": "referrer", + "in": "path" + } + ], + "responses": { + "200": { "description": "Successfully retrieved referrer detail" }, + "500": { "description": "Internal server error" }, + "503": { "description": "Service unavailable - referrer leaderboard data not yet cached" } + } + } + }, + "/api/name-tokens": { + "get": { + "operationId": "getNameTokens", + "tags": ["Explore"], + "summary": "Get Name Tokens", + "description": "Returns name tokens for the requested identifier (domainId or name)", + "parameters": [ + { + "schema": { "type": "string", "description": "Domain node hash identifier" }, + "required": false, + "description": "Domain node hash identifier", + "name": "domainId", + "in": "query" + }, + { + "schema": { "type": "string", "description": "ENS name to look up tokens for" }, + "required": false, + "description": "ENS name to look up tokens for", + "name": "name", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Name tokens known", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "responseCode": { "type": "string", "enum": ["ok"] }, + "registeredNameTokens": { + "type": "object", + "properties": { + "domainId": { "type": "string" }, + "name": { "type": "string" }, + "tokens": { + "type": "array", + "items": { + "type": "object", + "properties": { + "token": { + "type": "object", + "properties": { + "assetNamespace": { + "type": "string", + "enum": ["erc721", "erc1155"] + }, + "contract": { + "type": "object", + "properties": { + "chainId": { "type": "integer", "exclusiveMinimum": 0 }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + }, + "tokenId": { "type": "string" } + }, + "required": ["assetNamespace", "contract", "tokenId"] + }, + "ownership": { + "oneOf": [ + { + "type": "object", + "properties": { + "ownershipType": { + "type": "string", + "enum": ["namewrapper"] + }, + "owner": { + "type": "object", + "properties": { + "chainId": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + } + }, + "required": ["ownershipType", "owner"] + }, + { + "type": "object", + "properties": { + "ownershipType": { + "type": "string", + "enum": ["fully-onchain"] + }, + "owner": { + "type": "object", + "properties": { + "chainId": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + } + }, + "required": ["ownershipType", "owner"] + }, + { + "type": "object", + "properties": { + "ownershipType": { "type": "string", "enum": ["burned"] }, + "owner": { + "type": "object", + "properties": { + "chainId": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + } + }, + "required": ["ownershipType", "owner"] + }, + { + "type": "object", + "properties": { + "ownershipType": { + "type": "string", + "enum": ["unknown"] + }, + "owner": { + "type": "object", + "properties": { + "chainId": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + } + }, + "required": ["ownershipType", "owner"] + } + ] + }, + "mintStatus": { "type": "string", "enum": ["minted", "burned"] } + }, + "required": ["token", "ownership", "mintStatus"] + }, + "minItems": 1 + }, + "expiresAt": { "type": "integer" }, + "accurateAsOf": { "type": "integer" } + }, + "required": ["domainId", "name", "tokens", "expiresAt", "accurateAsOf"] + } + }, + "required": ["responseCode", "registeredNameTokens"], + "additionalProperties": false + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "responseCode": { "type": "string", "enum": ["error"] }, + "errorCode": { "type": "string", "enum": ["name-tokens-not-indexed"] }, + "error": { + "type": "object", + "properties": { "message": { "type": "string" }, "details": {} }, + "required": ["message"] + } + }, + "required": ["responseCode", "errorCode", "error"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "responseCode": { "type": "string", "enum": ["error"] }, + "errorCode": { + "type": "string", + "enum": ["unsupported-ensindexer-config"] + }, + "error": { + "type": "object", + "properties": { "message": { "type": "string" }, "details": {} }, + "required": ["message"] + } + }, + "required": ["responseCode", "errorCode", "error"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "responseCode": { "type": "string", "enum": ["error"] }, + "errorCode": { + "type": "string", + "enum": ["unsupported-indexing-status"] + }, + "error": { + "type": "object", + "properties": { "message": { "type": "string" }, "details": {} }, + "required": ["message"] + } + }, + "required": ["responseCode", "errorCode", "error"], + "additionalProperties": false + } + ] + } + ] + } + } + } + }, + "400": { + "description": "Invalid input", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { "message": { "type": "string" }, "details": {} }, + "required": ["message"] + } + } + } + }, + "404": { + "description": "Name tokens not indexed", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "responseCode": { "type": "string", "enum": ["ok"] }, + "registeredNameTokens": { + "type": "object", + "properties": { + "domainId": { "type": "string" }, + "name": { "type": "string" }, + "tokens": { + "type": "array", + "items": { + "type": "object", + "properties": { + "token": { + "type": "object", + "properties": { + "assetNamespace": { + "type": "string", + "enum": ["erc721", "erc1155"] + }, + "contract": { + "type": "object", + "properties": { + "chainId": { "type": "integer", "exclusiveMinimum": 0 }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + }, + "tokenId": { "type": "string" } + }, + "required": ["assetNamespace", "contract", "tokenId"] + }, + "ownership": { + "oneOf": [ + { + "type": "object", + "properties": { + "ownershipType": { + "type": "string", + "enum": ["namewrapper"] + }, + "owner": { + "type": "object", + "properties": { + "chainId": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + } + }, + "required": ["ownershipType", "owner"] + }, + { + "type": "object", + "properties": { + "ownershipType": { + "type": "string", + "enum": ["fully-onchain"] + }, + "owner": { + "type": "object", + "properties": { + "chainId": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + } + }, + "required": ["ownershipType", "owner"] + }, + { + "type": "object", + "properties": { + "ownershipType": { "type": "string", "enum": ["burned"] }, + "owner": { + "type": "object", + "properties": { + "chainId": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + } + }, + "required": ["ownershipType", "owner"] + }, + { + "type": "object", + "properties": { + "ownershipType": { + "type": "string", + "enum": ["unknown"] + }, + "owner": { + "type": "object", + "properties": { + "chainId": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + } + }, + "required": ["ownershipType", "owner"] + } + ] + }, + "mintStatus": { "type": "string", "enum": ["minted", "burned"] } + }, + "required": ["token", "ownership", "mintStatus"] + }, + "minItems": 1 + }, + "expiresAt": { "type": "integer" }, + "accurateAsOf": { "type": "integer" } + }, + "required": ["domainId", "name", "tokens", "expiresAt", "accurateAsOf"] + } + }, + "required": ["responseCode", "registeredNameTokens"], + "additionalProperties": false + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "responseCode": { "type": "string", "enum": ["error"] }, + "errorCode": { "type": "string", "enum": ["name-tokens-not-indexed"] }, + "error": { + "type": "object", + "properties": { "message": { "type": "string" }, "details": {} }, + "required": ["message"] + } + }, + "required": ["responseCode", "errorCode", "error"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "responseCode": { "type": "string", "enum": ["error"] }, + "errorCode": { + "type": "string", + "enum": ["unsupported-ensindexer-config"] + }, + "error": { + "type": "object", + "properties": { "message": { "type": "string" }, "details": {} }, + "required": ["message"] + } + }, + "required": ["responseCode", "errorCode", "error"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "responseCode": { "type": "string", "enum": ["error"] }, + "errorCode": { + "type": "string", + "enum": ["unsupported-indexing-status"] + }, + "error": { + "type": "object", + "properties": { "message": { "type": "string" }, "details": {} }, + "required": ["message"] + } + }, + "required": ["responseCode", "errorCode", "error"], + "additionalProperties": false + } + ] + } + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { "message": { "type": "string" }, "details": {} }, + "required": ["message"] + } + } + } + }, + "503": { + "description": "Service unavailable - Name Tokens API prerequisites not met (indexing status not ready or required plugins not activated)", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "responseCode": { "type": "string", "enum": ["ok"] }, + "registeredNameTokens": { + "type": "object", + "properties": { + "domainId": { "type": "string" }, + "name": { "type": "string" }, + "tokens": { + "type": "array", + "items": { + "type": "object", + "properties": { + "token": { + "type": "object", + "properties": { + "assetNamespace": { + "type": "string", + "enum": ["erc721", "erc1155"] + }, + "contract": { + "type": "object", + "properties": { + "chainId": { "type": "integer", "exclusiveMinimum": 0 }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + }, + "tokenId": { "type": "string" } + }, + "required": ["assetNamespace", "contract", "tokenId"] + }, + "ownership": { + "oneOf": [ + { + "type": "object", + "properties": { + "ownershipType": { + "type": "string", + "enum": ["namewrapper"] + }, + "owner": { + "type": "object", + "properties": { + "chainId": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + } + }, + "required": ["ownershipType", "owner"] + }, + { + "type": "object", + "properties": { + "ownershipType": { + "type": "string", + "enum": ["fully-onchain"] + }, + "owner": { + "type": "object", + "properties": { + "chainId": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + } + }, + "required": ["ownershipType", "owner"] + }, + { + "type": "object", + "properties": { + "ownershipType": { "type": "string", "enum": ["burned"] }, + "owner": { + "type": "object", + "properties": { + "chainId": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + } + }, + "required": ["ownershipType", "owner"] + }, + { + "type": "object", + "properties": { + "ownershipType": { + "type": "string", + "enum": ["unknown"] + }, + "owner": { + "type": "object", + "properties": { + "chainId": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "address": { "type": "string" } + }, + "required": ["chainId", "address"], + "additionalProperties": false + } + }, + "required": ["ownershipType", "owner"] + } + ] + }, + "mintStatus": { "type": "string", "enum": ["minted", "burned"] } + }, + "required": ["token", "ownership", "mintStatus"] + }, + "minItems": 1 + }, + "expiresAt": { "type": "integer" }, + "accurateAsOf": { "type": "integer" } + }, + "required": ["domainId", "name", "tokens", "expiresAt", "accurateAsOf"] + } + }, + "required": ["responseCode", "registeredNameTokens"], + "additionalProperties": false + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "responseCode": { "type": "string", "enum": ["error"] }, + "errorCode": { "type": "string", "enum": ["name-tokens-not-indexed"] }, + "error": { + "type": "object", + "properties": { "message": { "type": "string" }, "details": {} }, + "required": ["message"] + } + }, + "required": ["responseCode", "errorCode", "error"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "responseCode": { "type": "string", "enum": ["error"] }, + "errorCode": { + "type": "string", + "enum": ["unsupported-ensindexer-config"] + }, + "error": { + "type": "object", + "properties": { "message": { "type": "string" }, "details": {} }, + "required": ["message"] + } + }, + "required": ["responseCode", "errorCode", "error"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "responseCode": { "type": "string", "enum": ["error"] }, + "errorCode": { + "type": "string", + "enum": ["unsupported-indexing-status"] + }, + "error": { + "type": "object", + "properties": { "message": { "type": "string" }, "details": {} }, + "required": ["message"] + } + }, + "required": ["responseCode", "errorCode", "error"], + "additionalProperties": false + } + ] + } + ] + } + } + } + } + } + } + }, + "/api/registrar-actions": { + "get": { + "operationId": "getRegistrarActions", + "tags": ["Explore"], + "summary": "Get Registrar Actions", + "description": "Returns all registrar actions with optional filtering and pagination", + "parameters": [ + { + "schema": { + "type": "string", + "enum": ["orderBy[timestamp]=desc"], + "default": "orderBy[timestamp]=desc", + "description": "Order of results" + }, + "required": false, + "description": "Order of results", + "name": "orderBy", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "description": "Page number for pagination" + }, + "required": false, + "description": "Page number for pagination", + "name": "page", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 10, + "description": "Number of records per page" + }, + "required": false, + "description": "Number of records per page", + "name": "recordsPerPage", + "in": "query" + }, + { + "schema": { + "type": "boolean", + "description": "Filter to only include actions with referrals", + "default": false + }, + "required": false, + "description": "Filter to only include actions with referrals", + "name": "withReferral", + "in": "query" + }, + { + "schema": { "type": "string", "description": "Filter by decoded referrer address" }, + "required": false, + "description": "Filter by decoded referrer address", + "name": "decodedReferrer", + "in": "query" + }, + { + "schema": { "description": "Filter actions at or after this Unix timestamp" }, + "required": false, + "description": "Filter actions at or after this Unix timestamp", + "name": "beginTimestamp", + "in": "query" + }, + { + "schema": { "description": "Filter actions at or before this Unix timestamp" }, + "required": false, + "description": "Filter actions at or before this Unix timestamp", + "name": "endTimestamp", + "in": "query" + } + ], + "responses": { + "200": { "description": "Successfully retrieved registrar actions" }, + "400": { "description": "Invalid query" }, + "500": { "description": "Internal server error" } + } + } + }, + "/api/registrar-actions/{parentNode}": { + "get": { + "operationId": "getRegistrarActionsByParentNode", + "tags": ["Explore"], + "summary": "Get Registrar Actions by Parent Node", + "description": "Returns registrar actions filtered by parent node hash with optional additional filtering and pagination", + "parameters": [ + { + "schema": { + "type": "string", + "description": "Parent node to filter registrar actions" + }, + "required": true, + "description": "Parent node to filter registrar actions", + "name": "parentNode", + "in": "path" + }, + { + "schema": { + "type": "string", + "enum": ["orderBy[timestamp]=desc"], + "default": "orderBy[timestamp]=desc", + "description": "Order of results" + }, + "required": false, + "description": "Order of results", + "name": "orderBy", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "description": "Page number for pagination" + }, + "required": false, + "description": "Page number for pagination", + "name": "page", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 10, + "description": "Number of records per page" + }, + "required": false, + "description": "Number of records per page", + "name": "recordsPerPage", + "in": "query" + }, + { + "schema": { + "type": "boolean", + "description": "Filter to only include actions with referrals", + "default": false + }, + "required": false, + "description": "Filter to only include actions with referrals", + "name": "withReferral", + "in": "query" + }, + { + "schema": { "type": "string", "description": "Filter by decoded referrer address" }, + "required": false, + "description": "Filter by decoded referrer address", + "name": "decodedReferrer", + "in": "query" + }, + { + "schema": { "description": "Filter actions at or after this Unix timestamp" }, + "required": false, + "description": "Filter actions at or after this Unix timestamp", + "name": "beginTimestamp", + "in": "query" + }, + { + "schema": { "description": "Filter actions at or before this Unix timestamp" }, + "required": false, + "description": "Filter actions at or before this Unix timestamp", + "name": "endTimestamp", + "in": "query" + } + ], + "responses": { + "200": { "description": "Successfully retrieved registrar actions" }, + "400": { "description": "Invalid input" }, + "500": { "description": "Internal server error" } + } + } + }, + "/api/resolve/records/{name}": { + "get": { + "operationId": "resolveRecords", + "tags": ["Resolution"], + "summary": "Resolve ENS Records", + "description": "Resolves ENS records for a given name", + "parameters": [ + { "schema": { "type": "string" }, "required": true, "name": "name", "in": "path" }, + { "schema": { "type": "string" }, "required": false, "name": "name", "in": "query" }, + { "schema": { "type": "string" }, "required": false, "name": "addresses", "in": "query" }, + { "schema": { "type": "string" }, "required": false, "name": "texts", "in": "query" }, + { + "schema": { "type": "boolean", "default": false }, + "required": false, + "name": "trace", + "in": "query" + }, + { + "schema": { "type": "boolean", "default": false }, + "required": false, + "name": "accelerate", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successfully resolved records", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "records": { + "type": "object", + "properties": { + "name": { "type": ["string", "null"] }, + "addresses": { + "type": "object", + "additionalProperties": { "type": ["string", "null"] } + }, + "texts": { + "type": "object", + "additionalProperties": { "type": ["string", "null"] } + } + } + }, + "accelerationRequested": { "type": "boolean" }, + "accelerationAttempted": { "type": "boolean" }, + "trace": { "type": "array", "items": {} } + }, + "required": ["records", "accelerationRequested", "accelerationAttempted"] + } + } + } + } + } + } + }, + "/api/resolve/primary-name/{address}/{chainId}": { + "get": { + "operationId": "resolvePrimaryName", + "tags": ["Resolution"], + "summary": "Resolve Primary Name", + "description": "Resolves a primary name for a given `address` and `chainId`", + "parameters": [ + { "schema": { "type": "string" }, "required": true, "name": "address", "in": "path" }, + { "schema": { "type": "string" }, "required": true, "name": "chainId", "in": "path" }, + { + "schema": { "type": "boolean", "default": false }, + "required": false, + "name": "trace", + "in": "query" + }, + { + "schema": { "type": "boolean", "default": false }, + "required": false, + "name": "accelerate", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successfully resolved name", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { "type": ["string", "null"] }, + "accelerationRequested": { "type": "boolean" }, + "accelerationAttempted": { "type": "boolean" }, + "trace": { "type": "array", "items": {} } + }, + "required": ["name", "accelerationRequested", "accelerationAttempted"] + } + } + } + } + } + } + }, + "/api/resolve/primary-names/{address}": { + "get": { + "operationId": "resolvePrimaryNames", + "tags": ["Resolution"], + "summary": "Resolve Primary Names", + "description": "Resolves all primary names for a given address across multiple chains", + "parameters": [ + { "schema": { "type": "string" }, "required": true, "name": "address", "in": "path" }, + { "schema": { "type": "string" }, "required": false, "name": "chainIds", "in": "query" }, + { + "schema": { "type": "boolean", "default": false }, + "required": false, + "name": "trace", + "in": "query" + }, + { + "schema": { "type": "boolean", "default": false }, + "required": false, + "name": "accelerate", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successfully resolved records", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "names": { + "type": "object", + "additionalProperties": { "type": ["string", "null"] } + }, + "accelerationRequested": { "type": "boolean" }, + "accelerationAttempted": { "type": "boolean" }, + "trace": { "type": "array", "items": {} } + }, + "required": ["names", "accelerationRequested", "accelerationAttempted"] + } + } + } + } + } + } + } + }, + "webhooks": {} +} diff --git a/docs/docs.ensnode.io/ensapi/preview.mdx b/docs/docs.ensnode.io/ensapi/preview.mdx new file mode 100644 index 000000000..ea65c2e29 --- /dev/null +++ b/docs/docs.ensnode.io/ensapi/preview.mdx @@ -0,0 +1,9 @@ +--- +title: API Preview +sidebarTitle: Preview +description: Preview upcoming API changes from the source branch. +--- + +This page provides a preview of the committed OpenAPI specification from the branch this docs site was built from (for pull requests, the PR's head branch), which may include unreleased API changes. + +For the production API documentation, see the [Production API Reference](/ensapi). diff --git a/docs/docs.ensnode.io/package.json b/docs/docs.ensnode.io/package.json index d77269424..3fd14f2af 100644 --- a/docs/docs.ensnode.io/package.json +++ b/docs/docs.ensnode.io/package.json @@ -12,7 +12,8 @@ }, "homepage": "https://github.com/namehash/ensnode/tree/main/docs/docs.ensnode.io", "scripts": { - "mint": "pnpm dlx mint@^4.1.0" + "mint": "pnpm dlx mint@^4.1.0", + "generate:openapi": "pnpm --filter ensapi exec tsx --tsconfig tsconfig.json ../../scripts/generate-ensapi-openapi.ts" }, "packageManager": "pnpm@10.28.0" } diff --git a/package.json b/package.json index 83f2da0c0..1379f117e 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "docker:build:ensadmin": "docker build -f apps/ensadmin/Dockerfile -t ghcr.io/namehash/ensnode/ensadmin:latest .", "docker:build:ensrainbow": "docker build -f apps/ensrainbow/Dockerfile -t ghcr.io/namehash/ensnode/ensrainbow:latest .", "docker:build:ensapi": "docker build -f apps/ensapi/Dockerfile -t ghcr.io/namehash/ensnode/ensapi:latest .", - "otel-desktop-viewer": "docker run -p 8000:8000 -p 4317:4317 -p 4318:4318 davetron5000/otel-desktop-viewer:alpine-3" + "otel-desktop-viewer": "docker run -p 8000:8000 -p 4317:4317 -p 4318:4318 davetron5000/otel-desktop-viewer:alpine-3", + "generate:openapi": "pnpm -r --if-present generate:openapi" }, "devDependencies": { "@biomejs/biome": "^2.3.1", diff --git a/scripts/generate-ensapi-openapi.ts b/scripts/generate-ensapi-openapi.ts new file mode 100644 index 000000000..597653e17 --- /dev/null +++ b/scripts/generate-ensapi-openapi.ts @@ -0,0 +1,53 @@ +/** + * Generates a static OpenAPI 3.1 JSON document for ENSApi. + * + * Usage: pnpm generate:openapi + * + * Output: docs/docs.ensnode.io/ensapi-openapi.json + * + * This script has no runtime dependencies — it calls generateOpenApi31Document() + * which uses only stub route handlers and static metadata. + */ + +import { execFileSync } from "node:child_process"; +import { mkdirSync, writeFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { generateOpenApi31Document } from "@/openapi-document"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const outputPath = resolve(__dirname, "..", "docs", "docs.ensnode.io", "ensapi-openapi.json"); + +// Generate the document (no additional servers for the static spec) +const document = generateOpenApi31Document(); + +// Write JSON (Biome handles formatting) +mkdirSync(dirname(outputPath), { recursive: true }); +writeFileSync(outputPath, JSON.stringify(document)); + +console.log(`OpenAPI spec written to ${outputPath}`); + +// Format with Biome for consistency +console.log("Formatting with Biome..."); +try { + execFileSync("pnpm", ["-w", "exec", "biome", "format", "--write", outputPath], { + stdio: "inherit", + }); +} catch (error) { + console.error("Error: Failed to format with Biome."); + if (error instanceof Error) { + const err = error as NodeJS.ErrnoException & { status?: number }; + if (err.code === "ENOENT") { + console.error("'pnpm' is not available on your PATH."); + } else if (err.status !== undefined) { + console.error(`Biome exited with code ${err.status}.`); + console.error( + `Try running 'pnpm -w exec biome format --write ${outputPath}' manually to debug.`, + ); + } else if (err.message) { + console.error(err.message); + } + } + process.exit(1); +}