From 1f1e3dfd262fe5c33fef3170babc324af633f140 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 24 Feb 2026 10:24:24 +0100 Subject: [PATCH 01/56] Update ENSApi Indexing Status API data model Include `config` field of `EnsApiPublicConfig` type. --- .../src/ensapi/api/indexing-status/deserialize.ts | 2 ++ .../ensnode-sdk/src/ensapi/api/indexing-status/response.ts | 2 ++ .../ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts | 2 ++ .../src/ensapi/api/indexing-status/serialized-response.ts | 4 +++- .../src/ensapi/api/indexing-status/zod-schemas.ts | 5 ++++- packages/ensnode-sdk/src/ensapi/config/deserialize.ts | 2 +- 6 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts index 08e927795c..9e58d06065 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts @@ -2,6 +2,7 @@ import { prettifyError } from "zod/v4"; import { buildUnvalidatedRealtimeIndexingStatusProjection } from "../../../indexing-status/deserialize/realtime-indexing-status-projection"; import type { Unvalidated } from "../../../shared/types"; +import { buildUnvalidatedEnsApiPublicConfig } from "../../config/deserialize"; import { type EnsApiIndexingStatusResponse, EnsApiIndexingStatusResponseCodes } from "./response"; import type { SerializedEnsApiIndexingStatusResponse } from "./serialized-response"; import { @@ -28,6 +29,7 @@ function buildUnvalidatedEnsApiIndexingStatusResponse( realtimeProjection: buildUnvalidatedRealtimeIndexingStatusProjection( serializedResponse.realtimeProjection, ), + config: buildUnvalidatedEnsApiPublicConfig(serializedResponse.config), }; } diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts index 14cd8b9ba5..2cb679a65d 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts @@ -1,4 +1,5 @@ import type { RealtimeIndexingStatusProjection } from "../../../indexing-status/realtime-indexing-status-projection"; +import type { EnsApiPublicConfig } from "../../config"; /** * A status code for indexing status responses. @@ -41,6 +42,7 @@ export type IndexingStatusResponseCode = EnsApiIndexingStatusResponseCode; export type EnsApiIndexingStatusResponseOk = { responseCode: typeof EnsApiIndexingStatusResponseCodes.Ok; realtimeProjection: RealtimeIndexingStatusProjection; + config: EnsApiPublicConfig; }; /** diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts index 9cc13d5d60..81626145ff 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts @@ -1,4 +1,5 @@ import { serializeRealtimeIndexingStatusProjection } from "../../../indexing-status/serialize/realtime-indexing-status-projection"; +import { serializeEnsApiPublicConfig } from "../../config/serialize"; import { type EnsApiIndexingStatusResponse, EnsApiIndexingStatusResponseCodes } from "./response"; import type { SerializedEnsApiIndexingStatusResponse, @@ -16,6 +17,7 @@ export function serializeEnsApiIndexingStatusResponse( return { responseCode: response.responseCode, realtimeProjection: serializeRealtimeIndexingStatusProjection(response.realtimeProjection), + config: serializeEnsApiPublicConfig(response.config), } satisfies SerializedEnsApiIndexingStatusResponseOk; case EnsApiIndexingStatusResponseCodes.Error: diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts index 6db56e7587..cfd47ddab9 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts @@ -1,4 +1,5 @@ import type { SerializedRealtimeIndexingStatusProjection } from "../../../indexing-status/serialize/realtime-indexing-status-projection"; +import type { SerializedEnsApiPublicConfig } from "../../config/serialized-types"; import type { EnsApiIndexingStatusResponse, EnsApiIndexingStatusResponseError, @@ -21,8 +22,9 @@ export type SerializedIndexingStatusResponseError = SerializedEnsApiIndexingStat * Serialized representation of {@link EnsApiIndexingStatusResponseOk}. */ export interface SerializedEnsApiIndexingStatusResponseOk - extends Omit { + extends Omit { realtimeProjection: SerializedRealtimeIndexingStatusProjection; + config: SerializedEnsApiPublicConfig; } /** diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts index 0123a6aedd..83df37e6b9 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts @@ -4,6 +4,7 @@ import { makeRealtimeIndexingStatusProjectionSchema, makeSerializedRealtimeIndexingStatusProjectionSchema, } from "../../../indexing-status/zod-schema/realtime-indexing-status-projection"; +import { makeEnsApiPublicConfigSchema, makeSerializedEnsApiPublicConfigSchema } from "../../config"; import { type EnsApiIndexingStatusResponse, EnsApiIndexingStatusResponseCodes, @@ -24,6 +25,7 @@ export const makeEnsApiIndexingStatusResponseOkSchema = ( z.strictObject({ responseCode: z.literal(EnsApiIndexingStatusResponseCodes.Ok), realtimeProjection: makeRealtimeIndexingStatusProjectionSchema(valueLabel), + config: makeEnsApiPublicConfigSchema(valueLabel), }); /** @@ -59,9 +61,10 @@ export const makeIndexingStatusResponseSchema = makeEnsApiIndexingStatusResponse export const makeSerializedEnsApiIndexingStatusResponseOkSchema = ( valueLabel: string = "Serialized Indexing Status Response OK", ) => - z.strictObject({ + z.object({ responseCode: z.literal(EnsApiIndexingStatusResponseCodes.Ok), realtimeProjection: makeSerializedRealtimeIndexingStatusProjectionSchema(valueLabel), + config: makeSerializedEnsApiPublicConfigSchema(valueLabel), }); /** diff --git a/packages/ensnode-sdk/src/ensapi/config/deserialize.ts b/packages/ensnode-sdk/src/ensapi/config/deserialize.ts index 78aa79412d..5c22c21809 100644 --- a/packages/ensnode-sdk/src/ensapi/config/deserialize.ts +++ b/packages/ensnode-sdk/src/ensapi/config/deserialize.ts @@ -16,7 +16,7 @@ import { * @param serializedPublicConfig - The serialized public config to build from. * @return An unvalidated {@link EnsApiPublicConfig} object. */ -function buildUnvalidatedEnsApiPublicConfig( +export function buildUnvalidatedEnsApiPublicConfig( serializedPublicConfig: SerializedEnsApiPublicConfig, ): Unvalidated { return { From e518a43a5e01e54bc1273dcf0282ab07ca3ef4a8 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 24 Feb 2026 10:27:11 +0100 Subject: [PATCH 02/56] Update HTTP handlers for ENSApi Merge `/api/config` endpoint into `/api/indexing-status`. The former endpoint got deleted, while the later includes the `config: EnsApiPublicConfig` object in the response. --- apps/ensapi/src/handlers/ensnode-api.ts | 52 +++++++------------------ 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/apps/ensapi/src/handlers/ensnode-api.ts b/apps/ensapi/src/handlers/ensnode-api.ts index 46d458dad4..f9e2ce07d8 100644 --- a/apps/ensapi/src/handlers/ensnode-api.ts +++ b/apps/ensapi/src/handlers/ensnode-api.ts @@ -3,16 +3,12 @@ import config from "@/config"; import { describeRoute, resolver as validationResolver } from "hono-openapi"; import { - IndexingStatusResponseCodes, - type IndexingStatusResponseError, - type IndexingStatusResponseOk, - serializeENSApiPublicConfig, - serializeIndexingStatusResponse, + EnsApiIndexingStatusResponseCodes, + type EnsApiIndexingStatusResponseError, + type EnsApiIndexingStatusResponseOk, + serializeEnsApiIndexingStatusResponse, } from "@ensnode/ensnode-sdk"; -import { - makeENSApiPublicConfigSchema, - makeIndexingStatusResponseSchema, -} from "@ensnode/ensnode-sdk/internal"; +import { makeIndexingStatusResponseSchema } from "@ensnode/ensnode-sdk/internal"; import { buildEnsApiPublicConfig } from "@/config/config.schema"; import { factory } from "@/lib/hono-factory"; @@ -24,29 +20,6 @@ import resolutionApi from "./resolution-api"; const app = factory.createApp(); -app.get( - "/config", - describeRoute({ - 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: validationResolver(makeENSApiPublicConfigSchema()), - }, - }, - }, - }, - }), - async (c) => { - const ensApiPublicConfig = buildEnsApiPublicConfig(config); - return c.json(serializeENSApiPublicConfig(ensApiPublicConfig)); - }, -); - app.get( "/indexing-status", describeRoute({ @@ -80,19 +53,22 @@ app.get( if (c.var.indexingStatus instanceof Error) { return c.json( - serializeIndexingStatusResponse({ - responseCode: IndexingStatusResponseCodes.Error, - } satisfies IndexingStatusResponseError), + serializeEnsApiIndexingStatusResponse({ + responseCode: EnsApiIndexingStatusResponseCodes.Error, + } satisfies EnsApiIndexingStatusResponseError), 503, ); } + const ensApiPublicConfig = buildEnsApiPublicConfig(config); + // return successful response using the indexing status projection from the middleware context return c.json( - serializeIndexingStatusResponse({ - responseCode: IndexingStatusResponseCodes.Ok, + serializeEnsApiIndexingStatusResponse({ + responseCode: EnsApiIndexingStatusResponseCodes.Ok, realtimeProjection: c.var.indexingStatus, - } satisfies IndexingStatusResponseOk), + config: ensApiPublicConfig, + } satisfies EnsApiIndexingStatusResponseOk), ); }, ); From ac2675097f3281c79cad4e292c6480d83c8fea12 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 24 Feb 2026 10:28:18 +0100 Subject: [PATCH 03/56] Update Indexing Status cache for ENSApi Use `EnsIndexerClient` to fetch indexing status. --- apps/ensapi/src/cache/indexing-status.cache.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/ensapi/src/cache/indexing-status.cache.ts b/apps/ensapi/src/cache/indexing-status.cache.ts index 4a4364e117..8e23c2513a 100644 --- a/apps/ensapi/src/cache/indexing-status.cache.ts +++ b/apps/ensapi/src/cache/indexing-status.cache.ts @@ -2,22 +2,22 @@ import config from "@/config"; import { type CrossChainIndexingStatusSnapshot, - ENSNodeClient, - IndexingStatusResponseCodes, + EnsIndexerClient, + EnsIndexerIndexingStatusResponseCodes, SWRCache, } from "@ensnode/ensnode-sdk"; import { makeLogger } from "@/lib/logger"; const logger = makeLogger("indexing-status.cache"); -const client = new ENSNodeClient({ url: config.ensIndexerUrl }); +const client = new EnsIndexerClient({ url: config.ensIndexerUrl }); export const indexingStatusCache = new SWRCache({ fn: async (_cachedResult) => client .indexingStatus() // fetch a new indexing status snapshot .then((response) => { - if (response.responseCode !== IndexingStatusResponseCodes.Ok) { + if (response.responseCode !== EnsIndexerIndexingStatusResponseCodes.Ok) { // An indexing status response was successfully fetched, but the response code contained within the response was not 'ok'. // Therefore, throw an error to trigger the subsequent `.catch` handler. throw new Error("Received Indexing Status response with responseCode other than 'ok'."); From 55414e5137e62a93f12a518a9a26633fa3d55dd3 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 24 Feb 2026 10:40:03 +0100 Subject: [PATCH 04/56] Rename `ENSNodeProvider*` types New names will match `EnsApiProvider*` pattern. --- packages/ensnode-react/README.md | 30 +++++++-------- packages/ensnode-react/src/context.ts | 8 ++-- packages/ensnode-react/src/provider.tsx | 44 ++++++++++----------- packages/ensnode-react/src/types.ts | 14 ++++--- packages/ensnode-react/src/utils/query.ts | 47 ++++++++--------------- 5 files changed, 66 insertions(+), 77 deletions(-) diff --git a/packages/ensnode-react/README.md b/packages/ensnode-react/README.md index 63b48c595b..98cd76cd56 100644 --- a/packages/ensnode-react/README.md +++ b/packages/ensnode-react/README.md @@ -16,18 +16,18 @@ Note: `@tanstack/react-query` is a peer dependency but you don't need to interac ### 1. Setup the Provider -Wrap your app with the `ENSNodeProvider`: +Wrap your app with the `EnsApiProvider`: ```tsx -import { ENSNodeProvider, createConfig } from "@ensnode/ensnode-react"; +import { EnsApiProvider, createEnsApiOptions } from "@ensnode/ensnode-react"; -const config = createConfig({ url: "https://api.alpha.ensnode.io" }); +const options = createEnsApiOptions({ url: "https://api.alpha.ensnode.io" }); function App() { return ( - + - + ); } ``` @@ -124,12 +124,12 @@ function DisplayPrimaryNames() { ## API Reference -### ENSNodeProvider +### EnsApiProvider The provider component that supplies ENSNode configuration to all child components. ```tsx -interface ENSNodeProviderProps { +interface EnsApiProviderProps { config: ENSNodeConfig; queryClient?: QueryClient; queryClientOptions?: QueryClientOptions; @@ -142,12 +142,12 @@ interface ENSNodeProviderProps { - `queryClient`: Optional TanStack Query client instance (requires manual QueryClientProvider setup) - `queryClientOptions`: Optional Custom options for auto-created QueryClient (only used when queryClient is not provided) -### createConfig +### createEnsApiOptions -Helper function to create ENSNode configuration with defaults. +Helper function to create ENSApi options with defaults. ```tsx -const config = createConfig({ +const options = createEnsApiOptions({ url: "https://api.alpha.ensnode.io", }); ``` @@ -230,11 +230,11 @@ const { data, isLoading, error, refetch } = usePrimaryNames({ ### Custom Query Configuration -The `ENSNodeProvider` automatically creates and manages a QueryClient for you. Cache keys include the ENSNode endpoint URL, so different endpoints (mainnet vs testnet) maintain separate caches. You can customize the QueryClient without importing TanStack Query: +The `EnsApiProvider` automatically creates and manages a QueryClient for you. Cache keys include the ENSNode endpoint URL, so different endpoints (mainnet vs testnet) maintain separate caches. You can customize the QueryClient without importing TanStack Query: ```tsx // Simple setup - no TanStack Query knowledge needed - - + ``` ### Advanced: Bring Your Own QueryClient @@ -268,9 +268,9 @@ const queryClient = new QueryClient({ }); - + - + ; ``` diff --git a/packages/ensnode-react/src/context.ts b/packages/ensnode-react/src/context.ts index 42474d2430..12a3b23f2d 100644 --- a/packages/ensnode-react/src/context.ts +++ b/packages/ensnode-react/src/context.ts @@ -1,13 +1,13 @@ import { createContext } from "react"; -import type { ENSNodeSDKConfig } from "./types"; +import type { EnsApiProviderOptions } from "./types"; /** - * React context for ENSNode configuration + * React context for ENSApi configuration */ -export const ENSNodeContext = createContext(undefined); +export const EnsApiContext = createContext(undefined); /** * Display name for debugging */ -ENSNodeContext.displayName = "ENSNodeContext"; +EnsApiContext.displayName = "EnsApiContext"; diff --git a/packages/ensnode-react/src/provider.tsx b/packages/ensnode-react/src/provider.tsx index 457db1319e..019402e6dc 100644 --- a/packages/ensnode-react/src/provider.tsx +++ b/packages/ensnode-react/src/provider.tsx @@ -4,18 +4,18 @@ import { QueryClient, QueryClientProvider, useQueryClient } from "@tanstack/react-query"; import { createElement, useMemo } from "react"; -import { ENSNodeClient } from "@ensnode/ensnode-sdk"; +import { EnsApiClient } from "@ensnode/ensnode-sdk"; -import { ENSNodeContext } from "./context"; -import type { ENSNodeSDKConfig } from "./types"; +import { EnsApiContext } from "./context"; +import type { EnsApiProviderOptions } from "./types"; -export interface ENSNodeProviderProps { - /** ENSNode configuration */ - config: ENSNodeSDKConfig; +export interface EnsApiProviderProps { + /** ENSApi options */ + options: EnsApiProviderOptions; /** * Optional QueryClient instance. If provided, you must wrap your app with QueryClientProvider yourself. - * If not provided, ENSNodeProvider will create and manage its own QueryClient internally. + * If not provided, EnsApiProvider will create and manage its own QueryClient internally. */ queryClient?: QueryClient; @@ -26,21 +26,21 @@ export interface ENSNodeProviderProps { queryClientOptions?: ConstructorParameters[0]; } -function ENSNodeInternalProvider({ +function EnsApiInternalProvider({ children, - config, + options, }: { children?: React.ReactNode; - config: ENSNodeSDKConfig; + options: EnsApiProviderOptions; }) { - // Memoize the config to prevent unnecessary re-renders - const memoizedConfig = useMemo(() => config, [config]); + // Memoize the options to prevent unnecessary re-renders + const memoizedOptions = useMemo(() => options, [options]); - return createElement(ENSNodeContext.Provider, { value: memoizedConfig }, children); + return createElement(EnsApiContext.Provider, { value: memoizedOptions }, children); } -export function ENSNodeProvider(parameters: React.PropsWithChildren) { - const { children, config, queryClient, queryClientOptions } = parameters; +export function EnsApiProvider(parameters: React.PropsWithChildren) { + const { children, options, queryClient, queryClientOptions } = parameters; // Check if we're already inside a QueryClientProvider let hasExistingQueryClient = false; @@ -59,12 +59,12 @@ export function ENSNodeProvider(parameters: React.PropsWithChildren { /** * Configuration parameter for hooks that need access to config */ -export interface WithSDKConfigParameter { - config?: TConfig | undefined; +export interface WithEnsApiProviderOptions< + TOptions extends EnsApiProviderOptions = EnsApiProviderOptions, +> { + options?: TOptions | undefined; } /** diff --git a/packages/ensnode-react/src/utils/query.ts b/packages/ensnode-react/src/utils/query.ts index fc9a5cb009..8a8696d4c4 100644 --- a/packages/ensnode-react/src/utils/query.ts +++ b/packages/ensnode-react/src/utils/query.ts @@ -3,7 +3,7 @@ import type { UndefinedInitialDataOptions } from "@tanstack/react-query"; import { - ENSNodeClient, + EnsApiClient, type NameTokensRequest, type RegistrarActionsRequest, type ResolvePrimaryNameRequest, @@ -12,7 +12,7 @@ import { type ResolverRecordsSelection, } from "@ensnode/ensnode-sdk"; -import type { ENSNodeSDKConfig } from "../types"; +import type { EnsApiProviderOptions } from "../types"; /** * Immutable query options for data that is assumed to be immutable and should only be fetched once per full page refresh per unique key. @@ -54,8 +54,6 @@ export const queryKeys = { primaryNames: (url: string, args: ResolvePrimaryNamesRequest) => [...queryKeys.resolve(url), "primary-names", args] as const, - config: (url: string) => [...queryKeys.base(url), "config"] as const, - indexingStatus: (url: string) => [...queryKeys.base(url), "indexing-status"] as const, registrarActions: (url: string, args: RegistrarActionsRequest) => @@ -69,14 +67,14 @@ export const queryKeys = { * Creates query options for Records Resolution */ export function createRecordsQueryOptions( - config: ENSNodeSDKConfig, + config: EnsApiProviderOptions, args: ResolveRecordsRequest, ) { return { enabled: true, queryKey: queryKeys.records(config.client.url.href, args), queryFn: async () => { - const client = new ENSNodeClient(config.client); + const client = new EnsApiClient(config.client); return client.resolveRecords(args.name, args.selection, args); }, }; @@ -86,14 +84,14 @@ export function createRecordsQueryOptions { - const client = new ENSNodeClient(config.client); + const client = new EnsApiClient(config.client); return client.resolvePrimaryName(args.address, args.chainId, args); }, }; @@ -103,42 +101,28 @@ export function createPrimaryNameQueryOptions( * Creates query options for Primary Name Resolution */ export function createPrimaryNamesQueryOptions( - config: ENSNodeSDKConfig, + config: EnsApiProviderOptions, args: ResolvePrimaryNamesRequest, ) { return { enabled: true, queryKey: queryKeys.primaryNames(config.client.url.href, args), queryFn: async () => { - const client = new ENSNodeClient(config.client); + const client = new EnsApiClient(config.client); return client.resolvePrimaryNames(args.address, args); }, }; } -/** - * Creates query options for ENSNode Config API - */ -export function createConfigQueryOptions(config: ENSNodeSDKConfig) { - return { - enabled: true, - queryKey: queryKeys.config(config.client.url.href), - queryFn: async () => { - const client = new ENSNodeClient(config.client); - return client.config(); - }, - }; -} - /** * Creates query options for ENSNode Indexing Status API */ -export function createIndexingStatusQueryOptions(config: ENSNodeSDKConfig) { +export function createIndexingStatusQueryOptions(config: EnsApiProviderOptions) { return { enabled: true, queryKey: queryKeys.indexingStatus(config.client.url.href), queryFn: async () => { - const client = new ENSNodeClient(config.client); + const client = new EnsApiClient(config.client); return client.indexingStatus(); }, }; @@ -148,14 +132,14 @@ export function createIndexingStatusQueryOptions(config: ENSNodeSDKConfig) { * Creates query options for ENSNode Registrar Actions API */ export function createRegistrarActionsQueryOptions( - config: ENSNodeSDKConfig, + config: EnsApiProviderOptions, args: RegistrarActionsRequest, ) { return { enabled: true, queryKey: queryKeys.registrarActions(config.client.url.href, args), queryFn: async () => { - const client = new ENSNodeClient(config.client); + const client = new EnsApiClient(config.client); return client.registrarActions(args); }, @@ -165,12 +149,15 @@ export function createRegistrarActionsQueryOptions( /** * Creates query options for Name Tokens API */ -export function createNameTokensQueryOptions(config: ENSNodeSDKConfig, args: NameTokensRequest) { +export function createNameTokensQueryOptions( + config: EnsApiProviderOptions, + args: NameTokensRequest, +) { return { enabled: true, queryKey: queryKeys.nameTokens(config.client.url.href, args), queryFn: async () => { - const client = new ENSNodeClient(config.client); + const client = new EnsApiClient(config.client); return client.nameTokens(args); }, From 2557708c05487c6c380ffff4ec28d9b8a26306e5 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 24 Feb 2026 10:41:20 +0100 Subject: [PATCH 05/56] Rename `useENSNodeSDKConfig` to `useEnsApiProviderOptions` --- .../src/hooks/useENSNodeSDKConfig.ts | 30 ------------------- .../src/hooks/useEnsApiProviderOptions.ts | 29 ++++++++++++++++++ 2 files changed, 29 insertions(+), 30 deletions(-) delete mode 100644 packages/ensnode-react/src/hooks/useENSNodeSDKConfig.ts create mode 100644 packages/ensnode-react/src/hooks/useEnsApiProviderOptions.ts diff --git a/packages/ensnode-react/src/hooks/useENSNodeSDKConfig.ts b/packages/ensnode-react/src/hooks/useENSNodeSDKConfig.ts deleted file mode 100644 index 2f7b1016aa..0000000000 --- a/packages/ensnode-react/src/hooks/useENSNodeSDKConfig.ts +++ /dev/null @@ -1,30 +0,0 @@ -"use client"; - -import { useContext } from "react"; - -import { ENSNodeContext } from "../context"; -import type { ENSNodeSDKConfig } from "../types"; - -/** - * Hook to access the ENSNodeSDKConfig from context or parameters. - * - * @param parameters - Optional config parameter that overrides context - * @returns The ENSNode configuration - * @throws Error if no config is available in context or parameters - */ -export function useENSNodeSDKConfig( - config: TConfig | undefined, -): TConfig { - const contextConfig = useContext(ENSNodeContext); - - // Use provided config or fall back to context - const resolvedConfig = config ?? contextConfig; - - if (!resolvedConfig) { - throw new Error( - "useENSNodeSDKConfig must be used within an ENSNodeProvider or you must pass a config parameter", - ); - } - - return resolvedConfig as TConfig; -} diff --git a/packages/ensnode-react/src/hooks/useEnsApiProviderOptions.ts b/packages/ensnode-react/src/hooks/useEnsApiProviderOptions.ts new file mode 100644 index 0000000000..90f4208423 --- /dev/null +++ b/packages/ensnode-react/src/hooks/useEnsApiProviderOptions.ts @@ -0,0 +1,29 @@ +"use client"; + +import { useContext } from "react"; + +import { EnsApiContext } from "../context"; +import type { EnsApiProviderOptions } from "../types"; + +/** + * Hook to access the EnsApiProviderOptions from context or parameters. + * + * @param options - Options parameter that overrides context + * @throws Error if no config is available in context or parameters + */ +export function useEnsApiProviderOptions< + ProviderOptionsType extends EnsApiProviderOptions = EnsApiProviderOptions, +>(options?: ProviderOptionsType): ProviderOptionsType { + const contextOptions = useContext(EnsApiContext); + + // Use provided options or fall back to context + const resolvedOptions = options ?? contextOptions; + + if (!resolvedOptions) { + throw new Error( + "useEnsApiProviderOptions must be used within an EnsApiProvider or you must pass the options parameter", + ); + } + + return resolvedOptions as ProviderOptionsType; +} From da220f38d45e0bb8e0474dce0ef4d58d22ba0960 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 24 Feb 2026 10:42:13 +0100 Subject: [PATCH 06/56] Remove `useENSNodeConfig` hook --- .../src/hooks/useENSNodeConfig.ts | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 packages/ensnode-react/src/hooks/useENSNodeConfig.ts diff --git a/packages/ensnode-react/src/hooks/useENSNodeConfig.ts b/packages/ensnode-react/src/hooks/useENSNodeConfig.ts deleted file mode 100644 index c246d61afd..0000000000 --- a/packages/ensnode-react/src/hooks/useENSNodeConfig.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; - -import type { ConfigResponse } from "@ensnode/ensnode-sdk"; - -import type { QueryParameter, WithSDKConfigParameter } from "../types"; -import { ASSUME_IMMUTABLE_QUERY, createConfigQueryOptions } from "../utils/query"; -import { useENSNodeSDKConfig } from "./useENSNodeSDKConfig"; - -type UseENSNodeConfigParameters = QueryParameter; - -export function useENSNodeConfig( - parameters: WithSDKConfigParameter & UseENSNodeConfigParameters = {}, -) { - const { config, query = {} } = parameters; - const _config = useENSNodeSDKConfig(config); - - const queryOptions = createConfigQueryOptions(_config); - - const options = { - ...queryOptions, - ...ASSUME_IMMUTABLE_QUERY, - ...query, - enabled: query.enabled ?? queryOptions.enabled, - }; - - return useQuery(options); -} From 098ff6e8e557cbdb6d5b65568ec3e35980df7cc3 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 24 Feb 2026 10:44:40 +0100 Subject: [PATCH 07/56] Update `useResolvedIdentity` to require `namespace` param This will address the usecase previously served by `useENSNodeConfig` (that got just removed). --- packages/ensnode-react/src/hooks/useResolvedIdentity.ts | 6 +----- packages/ensnode-react/src/types.ts | 2 ++ .../src/components/identity/ResolveAndDisplayIdentity.tsx | 1 + 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/ensnode-react/src/hooks/useResolvedIdentity.ts b/packages/ensnode-react/src/hooks/useResolvedIdentity.ts index 460ecaca6b..c7b69dac86 100644 --- a/packages/ensnode-react/src/hooks/useResolvedIdentity.ts +++ b/packages/ensnode-react/src/hooks/useResolvedIdentity.ts @@ -14,7 +14,6 @@ import { } from "@ensnode/ensnode-sdk"; import type { UseResolvedIdentityParameters } from "../types"; -import { useENSNodeConfig } from "./useENSNodeConfig"; import { usePrimaryName } from "./usePrimaryName"; /** @@ -39,10 +38,7 @@ import { usePrimaryName } from "./usePrimaryName"; * - All other properties from the underlying {@link usePrimaryName} query (e.g., `isLoading`, `error`, `refetch`, etc.) */ export function useResolvedIdentity(parameters: UseResolvedIdentityParameters) { - const { identity, accelerate, query: _query = {} } = parameters; - - const { data } = useENSNodeConfig(); - const namespace = data?.ensIndexerPublicConfig.namespace; + const { identity, namespace, accelerate, query: _query = {} } = parameters; const { data: primaryNameData, diff --git a/packages/ensnode-react/src/types.ts b/packages/ensnode-react/src/types.ts index 7bcc45362b..3c3a6db89a 100644 --- a/packages/ensnode-react/src/types.ts +++ b/packages/ensnode-react/src/types.ts @@ -2,6 +2,7 @@ import type { QueryObserverOptions } from "@tanstack/react-query"; import type { AcceleratableRequest, + ENSNamespaceId, EnsApiClientOptions, ResolvePrimaryNameRequest, ResolvePrimaryNameResponse, @@ -77,4 +78,5 @@ export interface UseResolvedIdentityParameters extends QueryParameter, AcceleratableRequest { identity: UnresolvedIdentity; + namespace?: ENSNamespaceId; } diff --git a/packages/namehash-ui/src/components/identity/ResolveAndDisplayIdentity.tsx b/packages/namehash-ui/src/components/identity/ResolveAndDisplayIdentity.tsx index b9d112f6cf..7df1498cd1 100644 --- a/packages/namehash-ui/src/components/identity/ResolveAndDisplayIdentity.tsx +++ b/packages/namehash-ui/src/components/identity/ResolveAndDisplayIdentity.tsx @@ -62,6 +62,7 @@ export function ResolveAndDisplayIdentity({ const { identity: identityResult } = useResolvedIdentity({ identity, accelerate, + namespace: namespaceId, }); return ( From f7603b851e0ace494774ff126dd10f692a981272 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 24 Feb 2026 10:46:48 +0100 Subject: [PATCH 08/56] Update ENSNode hooks to apply renamed functions and types --- packages/ensnode-react/src/hooks/index.ts | 3 +- .../src/hooks/useIndexingStatus.ts | 28 +++++++++---------- .../ensnode-react/src/hooks/useNameTokens.ts | 12 ++++---- .../ensnode-react/src/hooks/usePrimaryName.ts | 18 ++++++------ .../src/hooks/usePrimaryNames.ts | 18 ++++++------ .../ensnode-react/src/hooks/useRecords.ts | 16 +++++------ .../src/hooks/useRegistrarActions.ts | 18 ++++++------ 7 files changed, 52 insertions(+), 61 deletions(-) diff --git a/packages/ensnode-react/src/hooks/index.ts b/packages/ensnode-react/src/hooks/index.ts index 1a3e1c3cd3..5fbf4ebf6c 100644 --- a/packages/ensnode-react/src/hooks/index.ts +++ b/packages/ensnode-react/src/hooks/index.ts @@ -1,5 +1,4 @@ -export * from "./useENSNodeConfig"; -export * from "./useENSNodeSDKConfig"; +export * from "./useEnsApiProviderOptions"; export * from "./useIndexingStatus"; export * from "./useNameTokens"; export * from "./usePrimaryName"; diff --git a/packages/ensnode-react/src/hooks/useIndexingStatus.ts b/packages/ensnode-react/src/hooks/useIndexingStatus.ts index c56f3d6928..0f79779839 100644 --- a/packages/ensnode-react/src/hooks/useIndexingStatus.ts +++ b/packages/ensnode-react/src/hooks/useIndexingStatus.ts @@ -1,29 +1,29 @@ import { useQuery } from "@tanstack/react-query"; -import type { IndexingStatusRequest, IndexingStatusResponse } from "@ensnode/ensnode-sdk"; +import type { + EnsApiIndexingStatusRequest, + EnsApiIndexingStatusResponse, +} from "@ensnode/ensnode-sdk"; -import type { QueryParameter, WithSDKConfigParameter } from "../types"; +import type { QueryParameter, WithEnsApiProviderOptions } from "../types"; import { createIndexingStatusQueryOptions } from "../utils/query"; -import { useENSNodeSDKConfig } from "./useENSNodeSDKConfig"; +import { useEnsApiProviderOptions } from "./useEnsApiProviderOptions"; interface UseIndexingStatusParameters - extends IndexingStatusRequest, - QueryParameter {} + extends EnsApiIndexingStatusRequest, + QueryParameter {} export function useIndexingStatus( - parameters: WithSDKConfigParameter & UseIndexingStatusParameters = {}, + parameters: WithEnsApiProviderOptions & UseIndexingStatusParameters = {}, ) { - const { config, query = {} } = parameters; - const _config = useENSNodeSDKConfig(config); + const { options, query = {} } = parameters; + const providerOptions = useEnsApiProviderOptions(options); + const queryOptions = createIndexingStatusQueryOptions(providerOptions); - const queryOptions = createIndexingStatusQueryOptions(_config); - - const options = { + return useQuery({ ...queryOptions, refetchInterval: 10 * 1000, // 10 seconds - indexing status changes frequently ...query, enabled: query.enabled ?? queryOptions.enabled, - }; - - return useQuery(options); + }); } diff --git a/packages/ensnode-react/src/hooks/useNameTokens.ts b/packages/ensnode-react/src/hooks/useNameTokens.ts index 7eefdcbbcf..249efad4da 100644 --- a/packages/ensnode-react/src/hooks/useNameTokens.ts +++ b/packages/ensnode-react/src/hooks/useNameTokens.ts @@ -2,9 +2,9 @@ import { useQuery } from "@tanstack/react-query"; import type { NameTokensRequest, NameTokensResponse } from "@ensnode/ensnode-sdk"; -import type { QueryParameter, WithSDKConfigParameter } from "../types"; +import type { QueryParameter, WithEnsApiProviderOptions } from "../types"; import { createNameTokensQueryOptions } from "../utils/query"; -import { useENSNodeSDKConfig } from "./useENSNodeSDKConfig"; +import { useEnsApiProviderOptions } from "./useEnsApiProviderOptions"; type UseNameTokensParameters = NameTokensRequest & QueryParameter; @@ -13,11 +13,11 @@ type UseNameTokensParameters = NameTokensRequest & QueryParameter( - parameters: UseRecordsParameters & WithSDKConfigParameter, + parameters: UseRecordsParameters & WithEnsApiProviderOptions, ) { - const { config, query = {}, name, ...args } = parameters; - const _config = useENSNodeSDKConfig(config); + const { options, query = {}, name, ...args } = parameters; + const _config = useEnsApiProviderOptions(options); const canEnable = name !== null; @@ -62,11 +62,9 @@ export function useRecords( ? createRecordsQueryOptions(_config, { ...args, name }) : { enabled: false, queryKey: ["disabled"] as const }; - const options = { + return useQuery({ ...queryOptions, ...query, enabled: canEnable && (query.enabled ?? queryOptions.enabled), - }; - - return useQuery(options); + }); } diff --git a/packages/ensnode-react/src/hooks/useRegistrarActions.ts b/packages/ensnode-react/src/hooks/useRegistrarActions.ts index ed5a6ca0d2..e8ba548ed0 100644 --- a/packages/ensnode-react/src/hooks/useRegistrarActions.ts +++ b/packages/ensnode-react/src/hooks/useRegistrarActions.ts @@ -2,9 +2,9 @@ import { useQuery } from "@tanstack/react-query"; import type { RegistrarActionsRequest, RegistrarActionsResponse } from "@ensnode/ensnode-sdk"; -import type { QueryParameter, WithSDKConfigParameter } from "../types"; +import type { QueryParameter, WithEnsApiProviderOptions } from "../types"; import { createRegistrarActionsQueryOptions } from "../utils/query"; -import { useENSNodeSDKConfig } from "./useENSNodeSDKConfig"; +import { useEnsApiProviderOptions } from "./useEnsApiProviderOptions"; interface UseRegistrarActionsParameters extends RegistrarActionsRequest, @@ -16,19 +16,17 @@ interface UseRegistrarActionsParameters * Query ENSNode Registrar Actions API. */ export function useRegistrarActions( - parameters: WithSDKConfigParameter & UseRegistrarActionsParameters = {}, + parameters: WithEnsApiProviderOptions & UseRegistrarActionsParameters = {}, ) { - const { config, query = {} } = parameters; - const _config = useENSNodeSDKConfig(config); + const { options, query = {} } = parameters; + const providerOptions = useEnsApiProviderOptions(options); - const queryOptions = createRegistrarActionsQueryOptions(_config, parameters); + const queryOptions = createRegistrarActionsQueryOptions(providerOptions, parameters); - const options = { + return useQuery({ ...queryOptions, refetchInterval: 10 * 1000, // 10 seconds - latest registrar actions change frequently ...query, enabled: query.enabled ?? queryOptions.enabled, - }; - - return useQuery(options); + }); } From 1d4594074deddeb95d25d727913391c941bddf33 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 24 Feb 2026 10:50:55 +0100 Subject: [PATCH 09/56] Update `useIndexingStatusWithSwr` hook to also cache `config: EnsApiPublicConfig` value. --- .../use-indexing-status-with-swr.ts | 55 ++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts b/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts index ba9d80cc28..449138818c 100644 --- a/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts +++ b/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts @@ -7,40 +7,49 @@ import { useCallback, useMemo } from "react"; import { createIndexingStatusQueryOptions, QueryParameter, - useENSNodeSDKConfig, + useEnsApiProviderOptions, type useIndexingStatus, useSwrQuery, - WithSDKConfigParameter, + WithEnsApiProviderOptions, } from "@ensnode/ensnode-react"; import { CrossChainIndexingStatusSnapshotOmnichain, createRealtimeIndexingStatusProjection, Duration, - type IndexingStatusRequest, - IndexingStatusResponseCodes, - IndexingStatusResponseOk, + EnsApiIndexingStatusRequest, + EnsApiIndexingStatusResponseCodes, + EnsApiIndexingStatusResponseOk, + EnsApiPublicConfig, } from "@ensnode/ensnode-sdk"; const DEFAULT_REFETCH_INTERVAL = secondsToMilliseconds(10); const REALTIME_PROJECTION_REFRESH_RATE: Duration = 1; +interface CachableIndexingStatus { + crossChainIndexingStatusSnapshot: CrossChainIndexingStatusSnapshotOmnichain; + config: EnsApiPublicConfig; +} + interface UseIndexingStatusParameters - extends IndexingStatusRequest, - QueryParameter {} + extends EnsApiIndexingStatusRequest, + QueryParameter {} /** * A proxy hook for {@link useIndexingStatus} which applies * stale-while-revalidate cache for successful responses. */ export function useIndexingStatusWithSwr( - parameters: WithSDKConfigParameter & UseIndexingStatusParameters = {}, + parameters: WithEnsApiProviderOptions & UseIndexingStatusParameters = {}, ) { - const { config, query = {} } = parameters; - const _config = useENSNodeSDKConfig(config); + const { options, query = {} } = parameters; + const providerOptions = useEnsApiProviderOptions(options); const now = useNow({ timeToRefresh: REALTIME_PROJECTION_REFRESH_RATE }); - const queryOptions = useMemo(() => createIndexingStatusQueryOptions(_config), [_config]); + const queryOptions = useMemo( + () => createIndexingStatusQueryOptions(providerOptions), + [providerOptions], + ); const queryKey = useMemo(() => ["swr", ...queryOptions.queryKey], [queryOptions.queryKey]); const queryFn = useCallback( async () => @@ -48,17 +57,21 @@ export function useIndexingStatusWithSwr( // An indexing status response was successfully fetched, // but the response code contained within the response was not 'ok'. // Therefore, throw an error to avoid caching this response. - if (response.responseCode !== IndexingStatusResponseCodes.Ok) { + if (response.responseCode !== EnsApiIndexingStatusResponseCodes.Ok) { throw new Error( "Received Indexing Status response with responseCode other than 'ok' which will not be cached.", ); } - // The indexing status snapshot has been fetched and successfully validated for caching. + // The indexing status snapshot, including ENSApi public config, + // has been fetched and successfully validated for caching. // Therefore, return it so that query cache for `queryOptions.queryKey` will: // - Replace the currently cached value (if any) with this new value. // - Return this non-null value. - return response.realtimeProjection.snapshot; + return { + crossChainIndexingStatusSnapshot: response.realtimeProjection.snapshot, + config: response.config, + } satisfies CachableIndexingStatus; }), [queryOptions.queryFn], ); @@ -66,16 +79,20 @@ export function useIndexingStatusWithSwr( // Call select function to `createRealtimeIndexingStatusProjection` each time // `now` is updated. const select = useCallback( - (cachedSnapshot: CrossChainIndexingStatusSnapshotOmnichain): IndexingStatusResponseOk => { - const realtimeProjection = createRealtimeIndexingStatusProjection(cachedSnapshot, now); + (cachedResult: CachableIndexingStatus): EnsApiIndexingStatusResponseOk => { + const realtimeProjection = createRealtimeIndexingStatusProjection( + cachedResult.crossChainIndexingStatusSnapshot, + now, + ); - // Maintain the original response shape of `IndexingStatusResponse` + // Maintain the original response shape of `EnsApiIndexingStatusResponseOk` // for the consumers. Creating a new projection from the cached snapshot // each time `now` is updated should be implementation detail. return { - responseCode: IndexingStatusResponseCodes.Ok, + responseCode: EnsApiIndexingStatusResponseCodes.Ok, realtimeProjection, - } satisfies IndexingStatusResponseOk; + config: cachedResult.config, + } satisfies EnsApiIndexingStatusResponseOk; }, [now], ); From ba81822b8556a98fcc05629eb61303fb1b5835bd Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 24 Feb 2026 10:51:25 +0100 Subject: [PATCH 10/56] Create `useEnsApiConfig` for conveniet use of `EnsApiPublicConfig` value --- .../src/components/config/useEnsApiConfig.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 apps/ensadmin/src/components/config/useEnsApiConfig.ts diff --git a/apps/ensadmin/src/components/config/useEnsApiConfig.ts b/apps/ensadmin/src/components/config/useEnsApiConfig.ts new file mode 100644 index 0000000000..de5696ed9b --- /dev/null +++ b/apps/ensadmin/src/components/config/useEnsApiConfig.ts @@ -0,0 +1,16 @@ +import { useQuery } from "@tanstack/react-query"; + +import { useEnsApiProviderOptions } from "@ensnode/ensnode-react"; + +import { useIndexingStatusWithSwr } from "@/components/indexing-status"; + +export function useEnsApiConfig() { + const ensApiProviderOptions = useEnsApiProviderOptions(); + const indexingStatus = useIndexingStatusWithSwr(); + + return useQuery({ + enabled: indexingStatus.isSuccess, + queryKey: ["swr", ensApiProviderOptions.client.url.href, "config"], + queryFn: async () => indexingStatus.data?.config, // enabled flag ensures this is only called when indexingStatus.data is available + }); +} From 72d7ae71c6ae012971c4d7fc6710d3fd018e7f25 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 24 Feb 2026 10:52:49 +0100 Subject: [PATCH 11/56] Replace applications of removed `useENSNodeConfig` hook with `useEnsApiConfig` hook --- .../connection/cards/ensnode-info.tsx | 42 ++++++++++--------- .../connections/require-active-connection.tsx | 11 +++-- .../hooks/active/use-active-connection.tsx | 4 +- .../ensadmin/src/hooks/async/use-namespace.ts | 4 +- 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx index 29210bb111..b803baa93f 100644 --- a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx +++ b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx @@ -9,9 +9,9 @@ import { ChainIcon, getChainName } from "@namehash/namehash-ui"; import { History, Replace } from "lucide-react"; import { Fragment, ReactNode } from "react"; -import { useENSNodeConfig } from "@ensnode/ensnode-react"; -import { type ENSApiPublicConfig, getENSRootChainId } from "@ensnode/ensnode-sdk"; +import { type EnsApiPublicConfig, getENSRootChainId } from "@ensnode/ensnode-sdk"; +import { useEnsApiConfig } from "@/components/config/useEnsApiConfig"; import { ErrorInfo, type ErrorInfoProps } from "@/components/error-info"; import { ENSApiIcon } from "@/components/icons/ensnode-apps/ensapi-icon"; import { ENSDbIcon } from "@/components/icons/ensnode-apps/ensdb-icon"; @@ -96,7 +96,7 @@ function ENSNodeCardLoadingSkeleton() { * Props for ENSNodeConfigCardDisplay - display component that accepts props for testing/mocking */ export interface ENSNodeConfigCardDisplayProps { - ensApiPublicConfig: ENSApiPublicConfig; + ensApiPublicConfig: EnsApiPublicConfig; } /** @@ -114,7 +114,7 @@ export function ENSNodeConfigCardDisplay({ ensApiPublicConfig }: ENSNodeConfigCa * Props for ENSNodeConfigInfoView - internal component that accepts props for testing/mocking */ export interface ENSNodeConfigInfoViewProps { - ensApiPublicConfig?: ENSApiPublicConfig; + ensApiPublicConfig?: EnsApiPublicConfig; error?: ErrorInfoProps; isLoading?: boolean; } @@ -147,28 +147,30 @@ export function ENSNodeConfigInfoView({ * ENSNodeConfigInfo component - fetches and displays ENSNode configuration data */ export function ENSNodeConfigInfo() { - const ensNodeConfigQuery = useENSNodeConfig(); + const ensApiConfig = useEnsApiConfig(); - return ( - - ); + if (ensApiConfig.isPending) { + return ; + } + + if (ensApiConfig.isError) { + return ( + + ); + } + + return ; } function ENSNodeConfigCardContent({ ensApiPublicConfig, }: { - ensApiPublicConfig: ENSApiPublicConfig; + ensApiPublicConfig: EnsApiPublicConfig; }) { const cardItemValueStyles = "text-sm leading-6 font-normal text-black"; diff --git a/apps/ensadmin/src/components/connections/require-active-connection.tsx b/apps/ensadmin/src/components/connections/require-active-connection.tsx index e3d921cc96..d56c7709b5 100644 --- a/apps/ensadmin/src/components/connections/require-active-connection.tsx +++ b/apps/ensadmin/src/components/connections/require-active-connection.tsx @@ -2,8 +2,7 @@ import type { PropsWithChildren } from "react"; -import { useENSNodeConfig } from "@ensnode/ensnode-react"; - +import { useEnsApiConfig } from "@/components/config/useEnsApiConfig"; import { ErrorInfo } from "@/components/error-info"; import { LoadingSpinner } from "@/components/loading-spinner"; @@ -11,14 +10,14 @@ import { LoadingSpinner } from "@/components/loading-spinner"; * Allows consumers to use `useActiveConnection` by blocking rendering until it is available. */ export function RequireActiveConnection({ children }: PropsWithChildren) { - const { status, error } = useENSNodeConfig(); + const ensApiConfig = useEnsApiConfig(); - if (status === "pending") return ; + if (ensApiConfig.status === "pending") return ; - if (status === "error") { + if (ensApiConfig.status === "error") { return (
- +
); } diff --git a/apps/ensadmin/src/hooks/active/use-active-connection.tsx b/apps/ensadmin/src/hooks/active/use-active-connection.tsx index e1fa783caf..0e0f3affca 100644 --- a/apps/ensadmin/src/hooks/active/use-active-connection.tsx +++ b/apps/ensadmin/src/hooks/active/use-active-connection.tsx @@ -1,6 +1,6 @@ "use client"; -import { useENSNodeConfig } from "@ensnode/ensnode-react"; +import { useEnsApiConfig } from "@/components/config/useEnsApiConfig"; /** * Hook to get the currently active ENSNode connection synchronously. @@ -16,7 +16,7 @@ import { useENSNodeConfig } from "@ensnode/ensnode-react"; * @throws Error if no active ENSNode connection is available */ export function useActiveConnection() { - const { data } = useENSNodeConfig(); + const { data } = useEnsApiConfig(); if (data === undefined) { throw new Error(`Invariant(useActiveConnection): Expected an active ENSNode Config`); diff --git a/apps/ensadmin/src/hooks/async/use-namespace.ts b/apps/ensadmin/src/hooks/async/use-namespace.ts index be3a0925ed..a48acbd027 100644 --- a/apps/ensadmin/src/hooks/async/use-namespace.ts +++ b/apps/ensadmin/src/hooks/async/use-namespace.ts @@ -1,4 +1,4 @@ -import { useENSNodeConfig } from "@ensnode/ensnode-react"; +import { useEnsApiConfig } from "@/components/config/useEnsApiConfig"; /** * Hook to get the namespace ID from the active ENSNode connection. @@ -22,7 +22,7 @@ import { useENSNodeConfig } from "@ensnode/ensnode-react"; * ``` */ export function useNamespace() { - const query = useENSNodeConfig(); + const query = useEnsApiConfig(); return { ...query, From c3a15792c1d1222c2dde334c36f465f376927c6f Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 24 Feb 2026 10:58:08 +0100 Subject: [PATCH 12/56] Reduce use of `useENSNodeConfig` (removed) and `useIndexingStatusWithSwr` to just `useIndexingStatusWithSwr` `useIndexingStatusWithSwr` returns `data.config` object with is the cached `EnsApiPublicConfig` value. --- .../use-stateful-fetch-registrar-actions.ts | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/apps/ensadmin/src/components/registrar-actions/use-stateful-fetch-registrar-actions.ts b/apps/ensadmin/src/components/registrar-actions/use-stateful-fetch-registrar-actions.ts index 8ed5a1eee7..c7a2741b47 100644 --- a/apps/ensadmin/src/components/registrar-actions/use-stateful-fetch-registrar-actions.ts +++ b/apps/ensadmin/src/components/registrar-actions/use-stateful-fetch-registrar-actions.ts @@ -1,4 +1,4 @@ -import { useENSNodeConfig, useRegistrarActions } from "@ensnode/ensnode-react"; +import { useRegistrarActions } from "@ensnode/ensnode-react"; import { RegistrarActionsOrders, RegistrarActionsResponseCodes, @@ -38,18 +38,16 @@ const { export function useStatefulRegistrarActions({ itemsPerPage, }: UseStatefulRegistrarActionsProps): StatefulFetchRegistrarActions { - const ensNodeConfigQuery = useENSNodeConfig(); const indexingStatusQuery = useIndexingStatusWithSwr(); let isRegistrarActionsApiSupported = false; - if (ensNodeConfigQuery.isSuccess && indexingStatusQuery.isSuccess) { - const { ensIndexerPublicConfig } = ensNodeConfigQuery.data; - const { realtimeProjection } = indexingStatusQuery.data; + if (indexingStatusQuery.isSuccess) { + const { realtimeProjection, config } = indexingStatusQuery.data; const { omnichainSnapshot } = realtimeProjection.snapshot; isRegistrarActionsApiSupported = - hasEnsIndexerConfigSupport(ensIndexerPublicConfig) && + hasEnsIndexerConfigSupport(config.ensIndexerPublicConfig) && hasIndexingStatusSupport(omnichainSnapshot.omnichainStatus); } @@ -63,21 +61,13 @@ export function useStatefulRegistrarActions({ }, }); - // ENSNode config is not fetched yet, so wait in the initial status - if (ensNodeConfigQuery.isPending || indexingStatusQuery.isPending) { + // ENSApi config is not fetched yet, so wait in the initial status + if (indexingStatusQuery.isPending) { return { fetchStatus: StatefulFetchStatusIds.Connecting, } satisfies StatefulFetchRegistrarActionsConnecting; } - // ENSNode config fetched as error - if (!ensNodeConfigQuery.isSuccess) { - return { - fetchStatus: StatefulFetchStatusIds.Error, - reason: "ENSNode config could not be fetched successfully", - } satisfies StatefulFetchRegistrarActionsError; - } - // Indexing Status fetched as error if (!indexingStatusQuery.isSuccess) { return { @@ -86,7 +76,7 @@ export function useStatefulRegistrarActions({ } satisfies StatefulFetchRegistrarActionsError; } - const { ensIndexerPublicConfig } = ensNodeConfigQuery.data; + const { ensIndexerPublicConfig } = indexingStatusQuery.data.config; // fetching is indefinitely not possible due to unsupported ENSNode config if (!hasEnsIndexerConfigSupport(ensIndexerPublicConfig)) { From 352be15582a64a16919c36d3c2b90ca9ebbf49f2 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 24 Feb 2026 10:58:15 +0100 Subject: [PATCH 13/56] Replace applications of removed `SelectedENSNodeProvider` with `SelectedEnsApiProvider` --- .../src/components/layout-wrapper.tsx | 6 +++--- .../providers/selected-ensnode-provider.tsx | 19 ++++++++----------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/apps/ensadmin/src/components/layout-wrapper.tsx b/apps/ensadmin/src/components/layout-wrapper.tsx index d9c910753a..da1d3a10c4 100644 --- a/apps/ensadmin/src/components/layout-wrapper.tsx +++ b/apps/ensadmin/src/components/layout-wrapper.tsx @@ -7,7 +7,7 @@ import { AppSidebar } from "@/components/app-sidebar"; import { RequireActiveConnection } from "@/components/connections/require-active-connection"; import { RequireSelectedConnection } from "@/components/connections/require-selected-connection"; import { Header, HeaderActions, HeaderBreadcrumbs, HeaderNav } from "@/components/header"; -import { SelectedENSNodeProvider } from "@/components/providers/selected-ensnode-provider"; +import { SelectedEnsApiProvider } from "@/components/providers/selected-ensnode-provider"; import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; import { Skeleton } from "@/components/ui/skeleton"; @@ -54,7 +54,7 @@ export function LayoutWrapper({ - +
{breadcrumbs} @@ -62,7 +62,7 @@ export function LayoutWrapper({ {actions}
{children} -
+
diff --git a/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx b/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx index dff46ac856..dba4eafefc 100644 --- a/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx +++ b/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx @@ -2,32 +2,29 @@ import type { PropsWithChildren } from "react"; -import { ENSNodeProvider } from "@ensnode/ensnode-react"; +import { createEnsApiOptions, EnsApiProvider } from "@ensnode/ensnode-react"; import { useSelectedConnection } from "@/hooks/active/use-selected-connection"; /** - * Provider component that configures ENSNodeProvider with the currently + * Provider component that configures EnsApiProvider with the currently * selected ENSNode connection. * - * This component wraps the ENSNodeProvider from @ensnode/ensnode-react and + * This component wraps the EnsApiProvider from @ensnode/ensnode-react and * automatically configures it with the URL from the currently selected ENSNode * connection URL. It serves as a bridge between the connection management * system and the ENSNode React hooks. * * @param children - React children to render within the provider context */ -export function SelectedENSNodeProvider({ children }: PropsWithChildren) { +export function SelectedEnsApiProvider({ children }: PropsWithChildren) { const selectedConnection = useSelectedConnection(); if (selectedConnection.validatedSelectedConnection.isValid) { - return ( - - {children} - - ); + const options = createEnsApiOptions({ + url: selectedConnection.validatedSelectedConnection.url, + }); + return {children}; } else { // TODO: Logic here needs a deeper refactor to recognize the difference // between the selected connection being in a valid format or not. From 709443d4397f92afb3b9245384843671388ec0fb Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Mon, 2 Mar 2026 16:59:23 +0100 Subject: [PATCH 14/56] Fix references to updated ENSApi response data model --- packages/ensnode-sdk/src/ensapi/client.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ensnode-sdk/src/ensapi/client.test.ts b/packages/ensnode-sdk/src/ensapi/client.test.ts index 1c3c6dedc3..5f1508ad83 100644 --- a/packages/ensnode-sdk/src/ensapi/client.test.ts +++ b/packages/ensnode-sdk/src/ensapi/client.test.ts @@ -136,7 +136,7 @@ const EXAMPLE_INDEXING_STATUS_BACKFILL_RESPONSE = deserializeEnsApiIndexingStatu }, }, }, - + config: EXAMPLE_CONFIG_RESPONSE, responseCode: EnsApiIndexingStatusResponseCodes.Ok, } satisfies SerializedEnsApiIndexingStatusResponseOk); @@ -194,7 +194,7 @@ const _EXAMPLE_INDEXING_STATUS_FOLLOWING_RESPONSE: EnsApiIndexingStatusResponse } satisfies SerializedOmnichainIndexingStatusSnapshotFollowing, }, }, - + config: EXAMPLE_CONFIG_RESPONSE, responseCode: EnsApiIndexingStatusResponseCodes.Ok, }); From b768b3dc1caa89a76f94c901757d53bfd85cc46f Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Mon, 2 Mar 2026 16:59:32 +0100 Subject: [PATCH 15/56] Fix references to updated ENSIndexer response data model --- .../ponder/src/api/handlers/ensnode-api.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/ensindexer/ponder/src/api/handlers/ensnode-api.ts b/apps/ensindexer/ponder/src/api/handlers/ensnode-api.ts index 0cb27f9961..8dc3e84e74 100644 --- a/apps/ensindexer/ponder/src/api/handlers/ensnode-api.ts +++ b/apps/ensindexer/ponder/src/api/handlers/ensnode-api.ts @@ -6,11 +6,11 @@ import { Hono } from "hono"; import { buildCrossChainIndexingStatusSnapshotOmnichain, createRealtimeIndexingStatusProjection, - IndexingStatusResponseCodes, - type IndexingStatusResponseError, - type IndexingStatusResponseOk, - serializeENSIndexerPublicConfig, - serializeIndexingStatusResponse, + EnsIndexerIndexingStatusResponseCodes, + type EnsIndexerIndexingStatusResponseError, + type EnsIndexerIndexingStatusResponseOk, + serializeEnsIndexerIndexingStatusResponse, + serializeEnsIndexerPublicConfig, } from "@ensnode/ensnode-sdk"; import { buildENSIndexerPublicConfig } from "@/config/public"; @@ -26,7 +26,7 @@ app.get("/config", async (c) => { const publicConfig = await buildENSIndexerPublicConfig(config); // respond with the serialized public config object - return c.json(serializeENSIndexerPublicConfig(publicConfig)); + return c.json(serializeEnsIndexerPublicConfig(publicConfig)); }); app.get("/indexing-status", async (c) => { @@ -48,19 +48,19 @@ app.get("/indexing-status", async (c) => { ); return c.json( - serializeIndexingStatusResponse({ - responseCode: IndexingStatusResponseCodes.Ok, + serializeEnsIndexerIndexingStatusResponse({ + responseCode: EnsIndexerIndexingStatusResponseCodes.Ok, realtimeProjection, - } satisfies IndexingStatusResponseOk), + } satisfies EnsIndexerIndexingStatusResponseOk), ); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; console.error(`Omnichain snapshot is currently not available: ${errorMessage}`); return c.json( - serializeIndexingStatusResponse({ - responseCode: IndexingStatusResponseCodes.Error, - } satisfies IndexingStatusResponseError), + serializeEnsIndexerIndexingStatusResponse({ + responseCode: EnsIndexerIndexingStatusResponseCodes.Error, + } satisfies EnsIndexerIndexingStatusResponseError), 500, ); } From 2a2b40f4404b06ea732c336ebde1995a939610d4 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Mon, 2 Mar 2026 16:59:56 +0100 Subject: [PATCH 16/56] Fix references to updated Indexing Status data model in ENSAdmin --- apps/ensadmin/src/app/mock/config-api.mock.ts | 23 ++++++++++++++++--- .../src/app/mock/indexing-stats/page.tsx | 15 ++++++++---- .../src/app/mock/indexing-status-api.mock.ts | 6 +++++ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/apps/ensadmin/src/app/mock/config-api.mock.ts b/apps/ensadmin/src/app/mock/config-api.mock.ts index 8edbde34c3..7958e2dcc3 100644 --- a/apps/ensadmin/src/app/mock/config-api.mock.ts +++ b/apps/ensadmin/src/app/mock/config-api.mock.ts @@ -1,6 +1,10 @@ -import { deserializeENSIndexerPublicConfig } from "@ensnode/ensnode-sdk"; +import { + deserializeENSIndexerPublicConfig, + SerializedEnsApiConfigResponse, + SerializedEnsIndexerConfigResponse, +} from "@ensnode/ensnode-sdk"; -export const ensIndexerPublicConfig = deserializeENSIndexerPublicConfig({ +const serializedEnsIndexerPublicConfig = { labelSet: { labelSetId: "subgraph", labelSetVersion: 0, @@ -27,4 +31,17 @@ export const ensIndexerPublicConfig = deserializeENSIndexerPublicConfig({ ensRainbowSchema: 3, ensNormalize: "1.11.1", }, -}); +} satisfies SerializedEnsIndexerConfigResponse; + +export const ensIndexerPublicConfig = deserializeENSIndexerPublicConfig( + serializedEnsIndexerPublicConfig, +); + +export const serializedEnsApiPublicConfig = { + ensIndexerPublicConfig: serializedEnsIndexerPublicConfig, + theGraphFallback: { + canFallback: true, + url: "https://api.thegraph.com/subgraphs/name/ensdomains/ens", + }, + version: "0.35.0", +} satisfies SerializedEnsApiConfigResponse; diff --git a/apps/ensadmin/src/app/mock/indexing-stats/page.tsx b/apps/ensadmin/src/app/mock/indexing-stats/page.tsx index 1ceffccf85..218b79dcbb 100644 --- a/apps/ensadmin/src/app/mock/indexing-stats/page.tsx +++ b/apps/ensadmin/src/app/mock/indexing-stats/page.tsx @@ -7,6 +7,7 @@ import { useEffect, useState } from "react"; import { CrossChainIndexingStatusSnapshot, createRealtimeIndexingStatusProjection, + EnsApiIndexingStatusResponseOk, IndexingStatusResponseCodes, IndexingStatusResponseOk, OmnichainIndexingStatusIds, @@ -37,7 +38,7 @@ let loadingTimeoutId: number; async function fetchMockedIndexingStatus( selectedVariant: Variant, -): Promise { +): Promise { // always try clearing loading timeout when performing a mocked fetch // this way we get a fresh and very long request to observe the loading state if (loadingTimeoutId) { @@ -53,14 +54,14 @@ async function fetchMockedIndexingStatus( selectedVariant ] as IndexingStatusResponseOk; - return response.realtimeProjection.snapshot; + return response; } case "Error ResponseCode": throw new Error( "Received Indexing Status response with responseCode other than 'ok' which will not be cached.", ); case "Loading": - return new Promise((_resolve, reject) => { + return new Promise((_resolve, reject) => { loadingTimeoutId = +setTimeout(reject, 5 * 60 * 1_000); }); case "Loading Error": @@ -77,10 +78,14 @@ export default function MockIndexingStatusPage() { const mockedIndexingStatus = useQuery({ queryKey: ["mock", "useIndexingStatus", selectedVariant], queryFn: () => fetchMockedIndexingStatus(selectedVariant), - select: (cachedSnapshot) => { + select: (response) => { return { responseCode: IndexingStatusResponseCodes.Ok, - realtimeProjection: createRealtimeIndexingStatusProjection(cachedSnapshot, now), + realtimeProjection: createRealtimeIndexingStatusProjection( + response.realtimeProjection.snapshot, + now, + ), + config: response.config, } satisfies IndexingStatusResponseOk; }, retry: false, // allows loading error to be observed immediately diff --git a/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts b/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts index 914e5c329b..661c819337 100644 --- a/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts +++ b/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts @@ -18,6 +18,8 @@ import { type SerializedOmnichainIndexingStatusSnapshotUnstarted, } from "@ensnode/ensnode-sdk"; +import { serializedEnsApiPublicConfig } from "@/app/mock/config-api.mock"; + export const indexingStatusResponseError: IndexingStatusResponseError = { responseCode: IndexingStatusResponseCodes.Error, }; @@ -85,6 +87,7 @@ export const indexingStatusResponseOkOmnichain: Record< } satisfies SerializedOmnichainIndexingStatusSnapshotUnstarted, }, }, + config: serializedEnsApiPublicConfig, }), [OmnichainIndexingStatusIds.Backfill]: deserializeIndexingStatusResponse({ @@ -163,6 +166,7 @@ export const indexingStatusResponseOkOmnichain: Record< } satisfies SerializedOmnichainIndexingStatusSnapshotBackfill, }, }, + config: serializedEnsApiPublicConfig, }), [OmnichainIndexingStatusIds.Following]: deserializeIndexingStatusResponse({ @@ -256,6 +260,7 @@ export const indexingStatusResponseOkOmnichain: Record< } satisfies SerializedOmnichainIndexingStatusSnapshotFollowing, }, }, + config: serializedEnsApiPublicConfig, }), [OmnichainIndexingStatusIds.Completed]: deserializeIndexingStatusResponse({ @@ -293,5 +298,6 @@ export const indexingStatusResponseOkOmnichain: Record< } satisfies SerializedOmnichainIndexingStatusSnapshotCompleted, }, }, + config: serializedEnsApiPublicConfig, }), }; From 5edb86289eae5da9461898b7981e77004c02ffc7 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Mon, 2 Mar 2026 18:24:27 +0100 Subject: [PATCH 17/56] Remove config route definition from ENSApi routes --- .../ensapi/src/handlers/ensnode-api.routes.ts | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/apps/ensapi/src/handlers/ensnode-api.routes.ts b/apps/ensapi/src/handlers/ensnode-api.routes.ts index a8487d3856..0b7b9970dc 100644 --- a/apps/ensapi/src/handlers/ensnode-api.routes.ts +++ b/apps/ensapi/src/handlers/ensnode-api.routes.ts @@ -3,30 +3,10 @@ import { createRoute } from "@hono/zod-openapi"; import { makeEnsApiIndexingStatusResponseErrorSchema, makeSerializedEnsApiIndexingStatusResponseOkSchema, - makeSerializedEnsApiPublicConfigSchema, } from "@ensnode/ensnode-sdk/internal"; 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", - responses: { - 200: { - description: "Successfully retrieved ENSApi public config", - content: { - "application/json": { - schema: makeSerializedEnsApiPublicConfigSchema(), - }, - }, - }, - }, -}); - export const getIndexingStatusRoute = createRoute({ method: "get", path: "/indexing-status", @@ -54,4 +34,4 @@ export const getIndexingStatusRoute = createRoute({ }, }); -export const routes = [getConfigRoute, getIndexingStatusRoute]; +export const routes = [getIndexingStatusRoute]; From 073f642383100d81d39e4e52007c3e5517e02002 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Mon, 2 Mar 2026 18:35:13 +0100 Subject: [PATCH 18/56] Apply AI PR feedback Change memoization level for ENSApi options object --- .../providers/selected-ensnode-provider.tsx | 14 ++++++++++---- packages/ensnode-react/README.md | 10 +++++----- .../ensnode-react/src/hooks/useResolvedIdentity.ts | 2 +- packages/ensnode-react/src/provider.tsx | 5 +---- packages/ensnode-react/src/types.ts | 2 +- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx b/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx index dba4eafefc..f0284b5eee 100644 --- a/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx +++ b/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx @@ -1,6 +1,6 @@ "use client"; -import type { PropsWithChildren } from "react"; +import { type PropsWithChildren, useMemo } from "react"; import { createEnsApiOptions, EnsApiProvider } from "@ensnode/ensnode-react"; @@ -21,9 +21,15 @@ export function SelectedEnsApiProvider({ children }: PropsWithChildren) { const selectedConnection = useSelectedConnection(); if (selectedConnection.validatedSelectedConnection.isValid) { - const options = createEnsApiOptions({ - url: selectedConnection.validatedSelectedConnection.url, - }); + const selectedConenctionUrl = selectedConnection.validatedSelectedConnection.url; + const options = useMemo( + () => + createEnsApiOptions({ + url: selectedConenctionUrl, + }), + [selectedConenctionUrl], + ); + return {children}; } else { // TODO: Logic here needs a deeper refactor to recognize the difference diff --git a/packages/ensnode-react/README.md b/packages/ensnode-react/README.md index 98cd76cd56..ec01024b7c 100644 --- a/packages/ensnode-react/README.md +++ b/packages/ensnode-react/README.md @@ -126,11 +126,11 @@ function DisplayPrimaryNames() { ### EnsApiProvider -The provider component that supplies ENSNode configuration to all child components. +The provider component that supplies ENSApi options to all child components. ```tsx interface EnsApiProviderProps { - config: ENSNodeConfig; + options: EnsApiProviderOptions; queryClient?: QueryClient; queryClientOptions?: QueryClientOptions; } @@ -138,7 +138,7 @@ interface EnsApiProviderProps { #### Props -- `config`: ENSNode configuration object +- `options`: ENSApi options object - `queryClient`: Optional TanStack Query client instance (requires manual QueryClientProvider setup) - `queryClientOptions`: Optional Custom options for auto-created QueryClient (only used when queryClient is not provided) @@ -235,7 +235,7 @@ The `EnsApiProvider` automatically creates and manages a QueryClient for you. Ca ```tsx // Simple setup - no TanStack Query knowledge needed - + ; diff --git a/packages/ensnode-react/src/hooks/useResolvedIdentity.ts b/packages/ensnode-react/src/hooks/useResolvedIdentity.ts index c7b69dac86..e590f5aeb0 100644 --- a/packages/ensnode-react/src/hooks/useResolvedIdentity.ts +++ b/packages/ensnode-react/src/hooks/useResolvedIdentity.ts @@ -23,7 +23,7 @@ import { usePrimaryName } from "./usePrimaryName"; * @param parameters - Configuration object for the hook * @param parameters.identity - An {@link UnresolvedIdentity} containing the {@link DefaultableChainId} * and {@link Address} to resolve. - * @param parameters.namespaceId - The {@link ENSNamespaceId} that `identity.chainId` should be interpreted + * @param parameters.namespace - The {@link ENSNamespaceId} that `identity.chainId` should be interpreted * through (via {@link getResolvePrimaryNameChainIdParam}) to determine the literal * chainId that should be used for ENSIP-19 primary name resolution. * @param parameters.accelerate - Whether to attempt Protocol Acceleration (default: false) diff --git a/packages/ensnode-react/src/provider.tsx b/packages/ensnode-react/src/provider.tsx index 019402e6dc..f1f27fc9b5 100644 --- a/packages/ensnode-react/src/provider.tsx +++ b/packages/ensnode-react/src/provider.tsx @@ -33,10 +33,7 @@ function EnsApiInternalProvider({ children?: React.ReactNode; options: EnsApiProviderOptions; }) { - // Memoize the options to prevent unnecessary re-renders - const memoizedOptions = useMemo(() => options, [options]); - - return createElement(EnsApiContext.Provider, { value: memoizedOptions }, children); + return createElement(EnsApiContext.Provider, { value: options }, children); } export function EnsApiProvider(parameters: React.PropsWithChildren) { diff --git a/packages/ensnode-react/src/types.ts b/packages/ensnode-react/src/types.ts index 3c3a6db89a..66fc5a7236 100644 --- a/packages/ensnode-react/src/types.ts +++ b/packages/ensnode-react/src/types.ts @@ -15,7 +15,7 @@ import type { } from "@ensnode/ensnode-sdk"; /** - * Configuration options for the ENSNode provider + * Configuration options for the ENSApi provider */ export interface EnsApiProviderOptions { /** The ENSApi client configuration */ From e652446074634e91d4a2acd0403af6b6cc2b268e Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Mon, 13 Apr 2026 20:49:32 +0200 Subject: [PATCH 19/56] Update OpenAPI Spec --- docs/docs.ensnode.io/ensapi-openapi.json | 252 +++++++++++------------ 1 file changed, 119 insertions(+), 133 deletions(-) diff --git a/docs/docs.ensnode.io/ensapi-openapi.json b/docs/docs.ensnode.io/ensapi-openapi.json index edefe03597..d7e5de67dc 100644 --- a/docs/docs.ensnode.io/ensapi-openapi.json +++ b/docs/docs.ensnode.io/ensapi-openapi.json @@ -29,137 +29,6 @@ ], "components": { "schemas": {}, "parameters": {} }, "paths": { - "/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": { - "ensIndexerPublicConfig": { - "type": "object", - "properties": { - "ensIndexerSchemaName": { "type": "string", "minLength": 1 }, - "ensRainbowPublicConfig": { - "type": "object", - "properties": { - "version": { "type": "string", "minLength": 1 }, - "labelSet": { - "type": "object", - "properties": { - "labelSetId": { - "type": "string", - "minLength": 1, - "maxLength": 50, - "pattern": "^[a-z-]+$" - }, - "highestLabelSetVersion": { "type": ["number", "null"] } - }, - "required": ["labelSetId", "highestLabelSetVersion"] - }, - "recordsCount": { "type": "integer", "minimum": 0 } - }, - "required": ["version", "labelSet", "recordsCount"] - }, - "indexedChainIds": { - "type": "array", - "items": { "type": "integer", "exclusiveMinimum": 0 }, - "minItems": 1 - }, - "isSubgraphCompatible": { "type": "boolean" }, - "labelSet": { - "type": "object", - "properties": { - "labelSetId": { - "type": "string", - "minLength": 1, - "maxLength": 50, - "pattern": "^[a-z-]+$" - }, - "labelSetVersion": { "type": ["number", "null"] } - }, - "required": ["labelSetId", "labelSetVersion"] - }, - "namespace": { - "type": "string", - "enum": ["mainnet", "sepolia", "sepolia-v2", "ens-test-env"] - }, - "plugins": { - "type": "array", - "items": { "type": "string" }, - "minItems": 1 - }, - "versionInfo": { - "type": "object", - "properties": { - "ponder": { "type": "string", "minLength": 1 }, - "ensDb": { "type": "string", "minLength": 1 }, - "ensIndexer": { "type": "string", "minLength": 1 }, - "ensNormalize": { "type": "string", "minLength": 1 } - }, - "required": ["ponder", "ensDb", "ensIndexer", "ensNormalize"] - } - }, - "required": [ - "ensIndexerSchemaName", - "ensRainbowPublicConfig", - "indexedChainIds", - "isSubgraphCompatible", - "labelSet", - "namespace", - "plugins", - "versionInfo" - ] - }, - "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 - } - ] - }, - "versionInfo": { - "type": "object", - "properties": { - "ensApi": { "type": "string", "minLength": 1 }, - "ensNormalize": { "type": "string", "minLength": 1 } - }, - "required": ["ensApi", "ensNormalize"] - } - }, - "required": ["ensIndexerPublicConfig", "theGraphFallback", "versionInfo"] - } - } - } - } - } - } - }, "/api/indexing-status": { "get": { "operationId": "getIndexingStatus", @@ -909,10 +778,127 @@ "worstCaseDistance": { "type": "number" } }, "required": ["snapshot", "projectedAt", "worstCaseDistance"] + }, + "config": { + "type": "object", + "properties": { + "ensIndexerPublicConfig": { + "type": "object", + "properties": { + "ensIndexerSchemaName": { "type": "string", "minLength": 1 }, + "ensRainbowPublicConfig": { + "type": "object", + "properties": { + "version": { "type": "string", "minLength": 1 }, + "labelSet": { + "type": "object", + "properties": { + "labelSetId": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "pattern": "^[a-z-]+$" + }, + "highestLabelSetVersion": { "type": ["number", "null"] } + }, + "required": ["labelSetId", "highestLabelSetVersion"] + }, + "recordsCount": { "type": "integer", "minimum": 0 } + }, + "required": ["version", "labelSet", "recordsCount"] + }, + "indexedChainIds": { + "type": "array", + "items": { "type": "integer", "exclusiveMinimum": 0 }, + "minItems": 1 + }, + "isSubgraphCompatible": { "type": "boolean" }, + "labelSet": { + "type": "object", + "properties": { + "labelSetId": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "pattern": "^[a-z-]+$" + }, + "labelSetVersion": { "type": ["number", "null"] } + }, + "required": ["labelSetId", "labelSetVersion"] + }, + "namespace": { + "type": "string", + "enum": ["mainnet", "sepolia", "sepolia-v2", "ens-test-env"] + }, + "plugins": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "versionInfo": { + "type": "object", + "properties": { + "ponder": { "type": "string", "minLength": 1 }, + "ensDb": { "type": "string", "minLength": 1 }, + "ensIndexer": { "type": "string", "minLength": 1 }, + "ensNormalize": { "type": "string", "minLength": 1 } + }, + "required": ["ponder", "ensDb", "ensIndexer", "ensNormalize"] + } + }, + "required": [ + "ensIndexerSchemaName", + "ensRainbowPublicConfig", + "indexedChainIds", + "isSubgraphCompatible", + "labelSet", + "namespace", + "plugins", + "versionInfo" + ] + }, + "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 + } + ] + }, + "versionInfo": { + "type": "object", + "properties": { + "ensApi": { "type": "string", "minLength": 1 }, + "ensNormalize": { "type": "string", "minLength": 1 } + }, + "required": ["ensApi", "ensNormalize"] + } + }, + "required": ["ensIndexerPublicConfig", "theGraphFallback", "versionInfo"] } }, - "required": ["responseCode", "realtimeProjection"], - "additionalProperties": false + "required": ["responseCode", "realtimeProjection", "config"] } } } From c53228500f478eae38062753b84d8c12e16387de Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 10:56:37 +0200 Subject: [PATCH 20/56] Apply AI PR feedback Improve type casting for mocked data --- .../src/app/mock/indexing-stats/page.tsx | 4 +--- .../src/app/mock/indexing-status-api.mock.ts | 4 ++-- .../ensapi/api/indexing-status/deserialize.ts | 22 +++++++++++++++++-- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/apps/ensadmin/src/app/mock/indexing-stats/page.tsx b/apps/ensadmin/src/app/mock/indexing-stats/page.tsx index 218b79dcbb..5417bc3d08 100644 --- a/apps/ensadmin/src/app/mock/indexing-stats/page.tsx +++ b/apps/ensadmin/src/app/mock/indexing-stats/page.tsx @@ -50,9 +50,7 @@ async function fetchMockedIndexingStatus( case OmnichainIndexingStatusIds.Backfill: case OmnichainIndexingStatusIds.Following: case OmnichainIndexingStatusIds.Completed: { - const response = indexingStatusResponseOkOmnichain[ - selectedVariant - ] as IndexingStatusResponseOk; + const response = indexingStatusResponseOkOmnichain[selectedVariant]; return response; } diff --git a/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts b/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts index 661c819337..d38171870c 100644 --- a/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts +++ b/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts @@ -2,7 +2,7 @@ import { ChainIndexingStatusIds, CrossChainIndexingStrategyIds, deserializeIndexingStatusResponse, - type IndexingStatusResponse, + EnsApiIndexingStatusResponseOk, IndexingStatusResponseCodes, type IndexingStatusResponseError, type OmnichainIndexingStatusId, @@ -26,7 +26,7 @@ export const indexingStatusResponseError: IndexingStatusResponseError = { export const indexingStatusResponseOkOmnichain: Record< OmnichainIndexingStatusId, - IndexingStatusResponse + EnsApiIndexingStatusResponseOk > = { [OmnichainIndexingStatusIds.Unstarted]: deserializeIndexingStatusResponse({ responseCode: IndexingStatusResponseCodes.Ok, diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts index 9e58d06065..bbf44eafba 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts @@ -3,8 +3,17 @@ import { prettifyError } from "zod/v4"; import { buildUnvalidatedRealtimeIndexingStatusProjection } from "../../../indexing-status/deserialize/realtime-indexing-status-projection"; import type { Unvalidated } from "../../../shared/types"; import { buildUnvalidatedEnsApiPublicConfig } from "../../config/deserialize"; -import { type EnsApiIndexingStatusResponse, EnsApiIndexingStatusResponseCodes } from "./response"; -import type { SerializedEnsApiIndexingStatusResponse } from "./serialized-response"; +import { + type EnsApiIndexingStatusResponse, + EnsApiIndexingStatusResponseCodes, + type EnsApiIndexingStatusResponseError, + type EnsApiIndexingStatusResponseOk, +} from "./response"; +import type { + SerializedEnsApiIndexingStatusResponse, + SerializedEnsApiIndexingStatusResponseError, + SerializedEnsApiIndexingStatusResponseOk, +} from "./serialized-response"; import { makeEnsApiIndexingStatusResponseSchema, makeSerializedEnsApiIndexingStatusResponseSchema, @@ -36,6 +45,15 @@ function buildUnvalidatedEnsApiIndexingStatusResponse( /** * Deserialize a {@link EnsApiIndexingStatusResponse} object. */ +export function deserializeEnsApiIndexingStatusResponse( + maybeResponse: Unvalidated, +): EnsApiIndexingStatusResponseOk; +export function deserializeEnsApiIndexingStatusResponse( + maybeResponse: Unvalidated, +): EnsApiIndexingStatusResponseError; +export function deserializeEnsApiIndexingStatusResponse( + maybeResponse: Unvalidated, +): EnsApiIndexingStatusResponse; export function deserializeEnsApiIndexingStatusResponse( maybeResponse: Unvalidated, ): EnsApiIndexingStatusResponse { From 4d0703bda99499e602a80592e7b84ceb94bc1041 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 10:57:00 +0200 Subject: [PATCH 21/56] Fix conditional logic in react hooks usage --- .../providers/selected-ensnode-provider.tsx | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx b/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx index f0284b5eee..7f42f5e3bb 100644 --- a/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx +++ b/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx @@ -20,18 +20,17 @@ import { useSelectedConnection } from "@/hooks/active/use-selected-connection"; export function SelectedEnsApiProvider({ children }: PropsWithChildren) { const selectedConnection = useSelectedConnection(); - if (selectedConnection.validatedSelectedConnection.isValid) { - const selectedConenctionUrl = selectedConnection.validatedSelectedConnection.url; - const options = useMemo( - () => - createEnsApiOptions({ - url: selectedConenctionUrl, - }), - [selectedConenctionUrl], - ); + const options = useMemo(() => { + if (!selectedConnection.validatedSelectedConnection.isValid) { + return undefined; + } + + return createEnsApiOptions({ + url: selectedConnection.validatedSelectedConnection.url, + }); + }, [selectedConnection.validatedSelectedConnection]); - return {children}; - } else { + if (!selectedConnection.validatedSelectedConnection.isValid) { // TODO: Logic here needs a deeper refactor to recognize the difference // between the selected connection being in a valid format or not. // This logic will throw and an error and break if the selected connection @@ -44,4 +43,11 @@ export function SelectedEnsApiProvider({ children }: PropsWithChildren) { ); } + + // invariant to satisfy the type system - this is guaranteed by the logic above + if (!options) { + throw new Error("Options must be defined if the selected connection is valid"); + } + + return {children}; } From acf4e6bb838d5348702c5f74114dd3ee6cf7fbf1 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 11:36:05 +0200 Subject: [PATCH 22/56] Apply AI PR feedback Improve code docs and improve `useEnsApiConfig` hook enabled flag --- .../src/components/config/useEnsApiConfig.ts | 17 +++++++++++++++-- .../connection/cards/ensnode-info.tsx | 10 +++++----- .../use-indexing-status-with-swr.ts | 13 ++++++++----- .../src/hooks/useEnsApiProviderOptions.ts | 2 +- .../ensnode-react/src/hooks/useNameTokens.ts | 4 ++-- .../src/hooks/useRegistrarActions.ts | 4 ++-- packages/ensnode-react/src/types.ts | 2 +- 7 files changed, 34 insertions(+), 18 deletions(-) diff --git a/apps/ensadmin/src/components/config/useEnsApiConfig.ts b/apps/ensadmin/src/components/config/useEnsApiConfig.ts index de5696ed9b..f652d4456c 100644 --- a/apps/ensadmin/src/components/config/useEnsApiConfig.ts +++ b/apps/ensadmin/src/components/config/useEnsApiConfig.ts @@ -4,13 +4,26 @@ import { useEnsApiProviderOptions } from "@ensnode/ensnode-react"; import { useIndexingStatusWithSwr } from "@/components/indexing-status"; +/** + * Use the ENSApi Public Config for the currently selected connection. + * + * This hook combines the logic of fetching the indexing status (which includes + * the ENSApi public config) with React Query to provide an easy way to access + * the ENSApi config for the currently selected connection. + */ export function useEnsApiConfig() { const ensApiProviderOptions = useEnsApiProviderOptions(); const indexingStatus = useIndexingStatusWithSwr(); return useQuery({ - enabled: indexingStatus.isSuccess, + enabled: indexingStatus.isFetched, queryKey: ["swr", ensApiProviderOptions.client.url.href, "config"], - queryFn: async () => indexingStatus.data?.config, // enabled flag ensures this is only called when indexingStatus.data is available + queryFn: async () => { + if (!indexingStatus.data) { + throw new Error("Indexing status wasn't fetched successfully"); + } + + return indexingStatus.data.config; + }, }); } diff --git a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx index e8ed2bcd99..bb67b5078b 100644 --- a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx +++ b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx @@ -149,21 +149,21 @@ export function ENSNodeConfigInfoView({ export function ENSNodeConfigInfo() { const ensApiConfig = useEnsApiConfig(); - if (ensApiConfig.isPending) { - return ; - } - if (ensApiConfig.isError) { return ( ); } + if (ensApiConfig.isPending) { + return ; + } + return ; } diff --git a/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts b/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts index c344953162..6171b14d73 100644 --- a/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts +++ b/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts @@ -26,14 +26,17 @@ const DEFAULT_REFETCH_INTERVAL = secondsToMilliseconds(10); const REALTIME_PROJECTION_REFRESH_RATE: Duration = 1; -interface CachableIndexingStatus { +/** + * Data model for the object cached in SWR for indexing status query results. + */ +interface CacheableIndexingStatus { crossChainIndexingStatusSnapshot: CrossChainIndexingStatusSnapshotOmnichain; config: EnsApiPublicConfig; } interface UseIndexingStatusParameters extends EnsApiIndexingStatusRequest, - QueryParameter {} + QueryParameter {} /** * A proxy hook for {@link useIndexingStatus} which applies @@ -63,7 +66,7 @@ export function useIndexingStatusWithSwr( ); } - // The indexing status snapshot, including ENSApi public config, + // The object including the Indexing Status snapshot, and the ENSApi Public Config, // has been fetched and successfully validated for caching. // Therefore, return it so that query cache for `queryOptions.queryKey` will: // - Replace the currently cached value (if any) with this new value. @@ -71,7 +74,7 @@ export function useIndexingStatusWithSwr( return { crossChainIndexingStatusSnapshot: response.realtimeProjection.snapshot, config: response.config, - } satisfies CachableIndexingStatus; + } satisfies CacheableIndexingStatus; }), [queryOptions.queryFn], ); @@ -79,7 +82,7 @@ export function useIndexingStatusWithSwr( // Call select function to `createRealtimeIndexingStatusProjection` each time // `now` is updated. const select = useCallback( - (cachedResult: CachableIndexingStatus): EnsApiIndexingStatusResponseOk => { + (cachedResult: CacheableIndexingStatus): EnsApiIndexingStatusResponseOk => { const realtimeProjection = createRealtimeIndexingStatusProjection( cachedResult.crossChainIndexingStatusSnapshot, now, diff --git a/packages/ensnode-react/src/hooks/useEnsApiProviderOptions.ts b/packages/ensnode-react/src/hooks/useEnsApiProviderOptions.ts index 90f4208423..393c278431 100644 --- a/packages/ensnode-react/src/hooks/useEnsApiProviderOptions.ts +++ b/packages/ensnode-react/src/hooks/useEnsApiProviderOptions.ts @@ -9,7 +9,7 @@ import type { EnsApiProviderOptions } from "../types"; * Hook to access the EnsApiProviderOptions from context or parameters. * * @param options - Options parameter that overrides context - * @throws Error if no config is available in context or parameters + * @throws Error if no options are available in context or parameters */ export function useEnsApiProviderOptions< ProviderOptionsType extends EnsApiProviderOptions = EnsApiProviderOptions, diff --git a/packages/ensnode-react/src/hooks/useNameTokens.ts b/packages/ensnode-react/src/hooks/useNameTokens.ts index 249efad4da..cefca229a3 100644 --- a/packages/ensnode-react/src/hooks/useNameTokens.ts +++ b/packages/ensnode-react/src/hooks/useNameTokens.ts @@ -14,10 +14,10 @@ type UseNameTokensParameters = NameTokensRequest & QueryParameter, AcceleratableRequest { identity: UnresolvedIdentity; - namespace?: ENSNamespaceId; + namespace: ENSNamespaceId; } From fa06ece3d13507bf3e39ea1c50e49fa983df2685 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 11:49:00 +0200 Subject: [PATCH 23/56] Rename `EnsApiContext` to `EnsNodeContext` and `*EnsApiProvider*` to `*EnsNodeProvider*` --- .../src/components/config/useEnsApiConfig.ts | 6 ++-- .../use-indexing-status-with-swr.ts | 8 ++--- .../src/components/layout-wrapper.tsx | 6 ++-- .../providers/selected-ensnode-provider.tsx | 12 ++++---- packages/ensnode-react/README.md | 30 +++++++++---------- packages/ensnode-react/src/context.ts | 8 ++--- packages/ensnode-react/src/hooks/index.ts | 2 +- .../src/hooks/useEnsApiProviderOptions.ts | 29 ------------------ .../src/hooks/useEnsNodeProviderOptions.ts | 29 ++++++++++++++++++ .../src/hooks/useIndexingStatus.ts | 8 ++--- .../ensnode-react/src/hooks/useNameTokens.ts | 8 ++--- .../ensnode-react/src/hooks/usePrimaryName.ts | 8 ++--- .../src/hooks/usePrimaryNames.ts | 10 ++++--- .../ensnode-react/src/hooks/useRecords.ts | 8 ++--- .../src/hooks/useRegistrarActions.ts | 8 ++--- packages/ensnode-react/src/provider.tsx | 18 +++++------ packages/ensnode-react/src/types.ts | 6 ++-- packages/ensnode-react/src/utils/query.ts | 14 ++++----- 18 files changed, 110 insertions(+), 108 deletions(-) delete mode 100644 packages/ensnode-react/src/hooks/useEnsApiProviderOptions.ts create mode 100644 packages/ensnode-react/src/hooks/useEnsNodeProviderOptions.ts diff --git a/apps/ensadmin/src/components/config/useEnsApiConfig.ts b/apps/ensadmin/src/components/config/useEnsApiConfig.ts index f652d4456c..3092f80963 100644 --- a/apps/ensadmin/src/components/config/useEnsApiConfig.ts +++ b/apps/ensadmin/src/components/config/useEnsApiConfig.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { useEnsApiProviderOptions } from "@ensnode/ensnode-react"; +import { useEnsNodeProviderOptions } from "@ensnode/ensnode-react"; import { useIndexingStatusWithSwr } from "@/components/indexing-status"; @@ -12,12 +12,12 @@ import { useIndexingStatusWithSwr } from "@/components/indexing-status"; * the ENSApi config for the currently selected connection. */ export function useEnsApiConfig() { - const ensApiProviderOptions = useEnsApiProviderOptions(); + const EnsNodeProviderOptions = useEnsNodeProviderOptions(); const indexingStatus = useIndexingStatusWithSwr(); return useQuery({ enabled: indexingStatus.isFetched, - queryKey: ["swr", ensApiProviderOptions.client.url.href, "config"], + queryKey: ["swr", EnsNodeProviderOptions.client.url.href, "config"], queryFn: async () => { if (!indexingStatus.data) { throw new Error("Indexing status wasn't fetched successfully"); diff --git a/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts b/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts index 6171b14d73..db6fa1247f 100644 --- a/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts +++ b/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts @@ -8,10 +8,10 @@ import { useCallback, useMemo } from "react"; import { createIndexingStatusQueryOptions, QueryParameter, - useEnsApiProviderOptions, + useEnsNodeProviderOptions, type useIndexingStatus, useSwrQuery, - WithEnsApiProviderOptions, + WithEnsNodeProviderOptions, } from "@ensnode/ensnode-react"; import { CrossChainIndexingStatusSnapshotOmnichain, @@ -43,10 +43,10 @@ interface UseIndexingStatusParameters * stale-while-revalidate cache for successful responses. */ export function useIndexingStatusWithSwr( - parameters: WithEnsApiProviderOptions & UseIndexingStatusParameters = {}, + parameters: WithEnsNodeProviderOptions & UseIndexingStatusParameters = {}, ) { const { options, query = {} } = parameters; - const providerOptions = useEnsApiProviderOptions(options); + const providerOptions = useEnsNodeProviderOptions(options); const now = useNow({ timeToRefresh: REALTIME_PROJECTION_REFRESH_RATE }); const queryOptions = useMemo( diff --git a/apps/ensadmin/src/components/layout-wrapper.tsx b/apps/ensadmin/src/components/layout-wrapper.tsx index da1d3a10c4..638e212a10 100644 --- a/apps/ensadmin/src/components/layout-wrapper.tsx +++ b/apps/ensadmin/src/components/layout-wrapper.tsx @@ -7,7 +7,7 @@ import { AppSidebar } from "@/components/app-sidebar"; import { RequireActiveConnection } from "@/components/connections/require-active-connection"; import { RequireSelectedConnection } from "@/components/connections/require-selected-connection"; import { Header, HeaderActions, HeaderBreadcrumbs, HeaderNav } from "@/components/header"; -import { SelectedEnsApiProvider } from "@/components/providers/selected-ensnode-provider"; +import { SelectedEnsNodeProvider } from "@/components/providers/selected-ensnode-provider"; import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; import { Skeleton } from "@/components/ui/skeleton"; @@ -54,7 +54,7 @@ export function LayoutWrapper({ - +
{breadcrumbs} @@ -62,7 +62,7 @@ export function LayoutWrapper({ {actions}
{children} -
+
diff --git a/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx b/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx index 7f42f5e3bb..7f71927ce1 100644 --- a/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx +++ b/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx @@ -2,22 +2,22 @@ import { type PropsWithChildren, useMemo } from "react"; -import { createEnsApiOptions, EnsApiProvider } from "@ensnode/ensnode-react"; +import { createEnsNodeOptions, EnsNodeProvider } from "@ensnode/ensnode-react"; import { useSelectedConnection } from "@/hooks/active/use-selected-connection"; /** - * Provider component that configures EnsApiProvider with the currently + * Provider component that configures EnsNodeProvider with the currently * selected ENSNode connection. * - * This component wraps the EnsApiProvider from @ensnode/ensnode-react and + * This component wraps the EnsNodeProvider from @ensnode/ensnode-react and * automatically configures it with the URL from the currently selected ENSNode * connection URL. It serves as a bridge between the connection management * system and the ENSNode React hooks. * * @param children - React children to render within the provider context */ -export function SelectedEnsApiProvider({ children }: PropsWithChildren) { +export function SelectedEnsNodeProvider({ children }: PropsWithChildren) { const selectedConnection = useSelectedConnection(); const options = useMemo(() => { @@ -25,7 +25,7 @@ export function SelectedEnsApiProvider({ children }: PropsWithChildren) { return undefined; } - return createEnsApiOptions({ + return createEnsNodeOptions({ url: selectedConnection.validatedSelectedConnection.url, }); }, [selectedConnection.validatedSelectedConnection]); @@ -49,5 +49,5 @@ export function SelectedEnsApiProvider({ children }: PropsWithChildren) { throw new Error("Options must be defined if the selected connection is valid"); } - return {children}; + return {children}; } diff --git a/packages/ensnode-react/README.md b/packages/ensnode-react/README.md index fa689c380f..ebf842b5a7 100644 --- a/packages/ensnode-react/README.md +++ b/packages/ensnode-react/README.md @@ -16,18 +16,18 @@ Note: `@tanstack/react-query` is a peer dependency but you don't need to interac ### 1. Setup the Provider -Wrap your app with the `EnsApiProvider`: +Wrap your app with the `EnsNodeProvider`: ```tsx -import { EnsApiProvider, createEnsApiOptions } from "@ensnode/ensnode-react"; +import { EnsNodeProvider, createEnsNodeOptions } from "@ensnode/ensnode-react"; -const options = createEnsApiOptions({ url: "https://api.alpha.ensnode.io" }); +const options = createEnsNodeOptions({ url: "https://api.alpha.ensnode.io" }); function App() { return ( - + - + ); } ``` @@ -124,13 +124,13 @@ function DisplayPrimaryNames() { ## API Reference -### EnsApiProvider +### EnsNodeProvider The provider component that supplies ENSApi options to all child components. ```tsx -interface EnsApiProviderProps { - options: EnsApiProviderOptions; +interface EnsNodeProviderProps { + options: EnsNodeProviderOptions; queryClient?: QueryClient; queryClientOptions?: QueryClientOptions; } @@ -142,12 +142,12 @@ interface EnsApiProviderProps { - `queryClient`: Optional TanStack Query client instance (requires manual QueryClientProvider setup) - `queryClientOptions`: Optional Custom options for auto-created QueryClient (only used when queryClient is not provided) -### createEnsApiOptions +### createEnsNodeOptions Helper function to create ENSApi options with defaults. ```tsx -const options = createEnsApiOptions({ +const options = createEnsNodeOptions({ url: "https://api.alpha.ensnode.io", }); ``` @@ -230,11 +230,11 @@ const { data, isLoading, error, refetch } = usePrimaryNames({ ### Custom Query Configuration -The `EnsApiProvider` automatically creates and manages a QueryClient for you. Cache keys include the ENSNode endpoint URL, so different endpoints (mainnet vs testnet) maintain separate caches. You can customize the QueryClient without importing TanStack Query: +The `EnsNodeProvider` automatically creates and manages a QueryClient for you. Cache keys include the ENSNode endpoint URL, so different endpoints (mainnet vs testnet) maintain separate caches. You can customize the QueryClient without importing TanStack Query: ```tsx // Simple setup - no TanStack Query knowledge needed - - + ``` ### Advanced: Bring Your Own QueryClient @@ -268,9 +268,9 @@ const queryClient = new QueryClient({ }); - + - + ; ``` diff --git a/packages/ensnode-react/src/context.ts b/packages/ensnode-react/src/context.ts index 12a3b23f2d..eb5e2433af 100644 --- a/packages/ensnode-react/src/context.ts +++ b/packages/ensnode-react/src/context.ts @@ -1,13 +1,13 @@ import { createContext } from "react"; -import type { EnsApiProviderOptions } from "./types"; +import type { EnsNodeProviderOptions } from "./types"; /** - * React context for ENSApi configuration + * React context for ENSNodeProvider options */ -export const EnsApiContext = createContext(undefined); +export const EnsNodeContext = createContext(undefined); /** * Display name for debugging */ -EnsApiContext.displayName = "EnsApiContext"; +EnsNodeContext.displayName = "EnsNodeContext"; diff --git a/packages/ensnode-react/src/hooks/index.ts b/packages/ensnode-react/src/hooks/index.ts index 5fbf4ebf6c..f55864b962 100644 --- a/packages/ensnode-react/src/hooks/index.ts +++ b/packages/ensnode-react/src/hooks/index.ts @@ -1,4 +1,4 @@ -export * from "./useEnsApiProviderOptions"; +export * from "./useEnsNodeProviderOptions"; export * from "./useIndexingStatus"; export * from "./useNameTokens"; export * from "./usePrimaryName"; diff --git a/packages/ensnode-react/src/hooks/useEnsApiProviderOptions.ts b/packages/ensnode-react/src/hooks/useEnsApiProviderOptions.ts deleted file mode 100644 index 393c278431..0000000000 --- a/packages/ensnode-react/src/hooks/useEnsApiProviderOptions.ts +++ /dev/null @@ -1,29 +0,0 @@ -"use client"; - -import { useContext } from "react"; - -import { EnsApiContext } from "../context"; -import type { EnsApiProviderOptions } from "../types"; - -/** - * Hook to access the EnsApiProviderOptions from context or parameters. - * - * @param options - Options parameter that overrides context - * @throws Error if no options are available in context or parameters - */ -export function useEnsApiProviderOptions< - ProviderOptionsType extends EnsApiProviderOptions = EnsApiProviderOptions, ->(options?: ProviderOptionsType): ProviderOptionsType { - const contextOptions = useContext(EnsApiContext); - - // Use provided options or fall back to context - const resolvedOptions = options ?? contextOptions; - - if (!resolvedOptions) { - throw new Error( - "useEnsApiProviderOptions must be used within an EnsApiProvider or you must pass the options parameter", - ); - } - - return resolvedOptions as ProviderOptionsType; -} diff --git a/packages/ensnode-react/src/hooks/useEnsNodeProviderOptions.ts b/packages/ensnode-react/src/hooks/useEnsNodeProviderOptions.ts new file mode 100644 index 0000000000..98a63b83ef --- /dev/null +++ b/packages/ensnode-react/src/hooks/useEnsNodeProviderOptions.ts @@ -0,0 +1,29 @@ +"use client"; + +import { useContext } from "react"; + +import { EnsNodeContext } from "../context"; +import type { EnsNodeProviderOptions } from "../types"; + +/** + * Hook to access the {@link EnsNodeProviderOptions} from context or parameters. + * + * @param options - Options parameter that overrides context + * @throws Error if no options are available in context or parameters + */ +export function useEnsNodeProviderOptions< + ProviderOptionsType extends EnsNodeProviderOptions = EnsNodeProviderOptions, +>(options?: ProviderOptionsType): ProviderOptionsType { + const contextOptions = useContext(EnsNodeContext); + + // Use provided options or fall back to context + const resolvedOptions = options ?? contextOptions; + + if (!resolvedOptions) { + throw new Error( + "useEnsNodeProviderOptions must be used within an EnsNodeProvider or you must pass the options parameter", + ); + } + + return resolvedOptions as ProviderOptionsType; +} diff --git a/packages/ensnode-react/src/hooks/useIndexingStatus.ts b/packages/ensnode-react/src/hooks/useIndexingStatus.ts index 0f79779839..4457b01eeb 100644 --- a/packages/ensnode-react/src/hooks/useIndexingStatus.ts +++ b/packages/ensnode-react/src/hooks/useIndexingStatus.ts @@ -5,19 +5,19 @@ import type { EnsApiIndexingStatusResponse, } from "@ensnode/ensnode-sdk"; -import type { QueryParameter, WithEnsApiProviderOptions } from "../types"; +import type { QueryParameter, WithEnsNodeProviderOptions } from "../types"; import { createIndexingStatusQueryOptions } from "../utils/query"; -import { useEnsApiProviderOptions } from "./useEnsApiProviderOptions"; +import { useEnsNodeProviderOptions } from "./useEnsNodeProviderOptions"; interface UseIndexingStatusParameters extends EnsApiIndexingStatusRequest, QueryParameter {} export function useIndexingStatus( - parameters: WithEnsApiProviderOptions & UseIndexingStatusParameters = {}, + parameters: WithEnsNodeProviderOptions & UseIndexingStatusParameters = {}, ) { const { options, query = {} } = parameters; - const providerOptions = useEnsApiProviderOptions(options); + const providerOptions = useEnsNodeProviderOptions(options); const queryOptions = createIndexingStatusQueryOptions(providerOptions); return useQuery({ diff --git a/packages/ensnode-react/src/hooks/useNameTokens.ts b/packages/ensnode-react/src/hooks/useNameTokens.ts index cefca229a3..822ec42bac 100644 --- a/packages/ensnode-react/src/hooks/useNameTokens.ts +++ b/packages/ensnode-react/src/hooks/useNameTokens.ts @@ -2,9 +2,9 @@ import { useQuery } from "@tanstack/react-query"; import type { NameTokensRequest, NameTokensResponse } from "@ensnode/ensnode-sdk"; -import type { QueryParameter, WithEnsApiProviderOptions } from "../types"; +import type { QueryParameter, WithEnsNodeProviderOptions } from "../types"; import { createNameTokensQueryOptions } from "../utils/query"; -import { useEnsApiProviderOptions } from "./useEnsApiProviderOptions"; +import { useEnsNodeProviderOptions } from "./useEnsNodeProviderOptions"; type UseNameTokensParameters = NameTokensRequest & QueryParameter; @@ -13,9 +13,9 @@ type UseNameTokensParameters = NameTokensRequest & QueryParameter( - parameters: UseRecordsParameters & WithEnsApiProviderOptions, + parameters: UseRecordsParameters & WithEnsNodeProviderOptions, ) { const { options, query = {}, name, ...args } = parameters; - const _config = useEnsApiProviderOptions(options); + const _config = useEnsNodeProviderOptions(options); const canEnable = name !== null; diff --git a/packages/ensnode-react/src/hooks/useRegistrarActions.ts b/packages/ensnode-react/src/hooks/useRegistrarActions.ts index 46a6264987..51c7b2a05b 100644 --- a/packages/ensnode-react/src/hooks/useRegistrarActions.ts +++ b/packages/ensnode-react/src/hooks/useRegistrarActions.ts @@ -2,9 +2,9 @@ import { useQuery } from "@tanstack/react-query"; import type { RegistrarActionsRequest, RegistrarActionsResponse } from "@ensnode/ensnode-sdk"; -import type { QueryParameter, WithEnsApiProviderOptions } from "../types"; +import type { QueryParameter, WithEnsNodeProviderOptions } from "../types"; import { createRegistrarActionsQueryOptions } from "../utils/query"; -import { useEnsApiProviderOptions } from "./useEnsApiProviderOptions"; +import { useEnsNodeProviderOptions } from "./useEnsNodeProviderOptions"; interface UseRegistrarActionsParameters extends RegistrarActionsRequest, @@ -16,10 +16,10 @@ interface UseRegistrarActionsParameters * Query ENSNode Registrar Actions API. */ export function useRegistrarActions( - parameters: WithEnsApiProviderOptions & UseRegistrarActionsParameters = {}, + parameters: WithEnsNodeProviderOptions & UseRegistrarActionsParameters = {}, ) { const { options, query = {}, ...request } = parameters; - const providerOptions = useEnsApiProviderOptions(options); + const providerOptions = useEnsNodeProviderOptions(options); const queryOptions = createRegistrarActionsQueryOptions(providerOptions, request); diff --git a/packages/ensnode-react/src/provider.tsx b/packages/ensnode-react/src/provider.tsx index f1f27fc9b5..4a2717acd1 100644 --- a/packages/ensnode-react/src/provider.tsx +++ b/packages/ensnode-react/src/provider.tsx @@ -6,16 +6,16 @@ import { createElement, useMemo } from "react"; import { EnsApiClient } from "@ensnode/ensnode-sdk"; -import { EnsApiContext } from "./context"; -import type { EnsApiProviderOptions } from "./types"; +import { EnsNodeContext } from "./context"; +import type { EnsNodeProviderOptions } from "./types"; -export interface EnsApiProviderProps { +export interface EnsNodeProviderProps { /** ENSApi options */ - options: EnsApiProviderOptions; + options: EnsNodeProviderOptions; /** * Optional QueryClient instance. If provided, you must wrap your app with QueryClientProvider yourself. - * If not provided, EnsApiProvider will create and manage its own QueryClient internally. + * If not provided, EnsNodeProvider will create and manage its own QueryClient internally. */ queryClient?: QueryClient; @@ -31,12 +31,12 @@ function EnsApiInternalProvider({ options, }: { children?: React.ReactNode; - options: EnsApiProviderOptions; + options: EnsNodeProviderOptions; }) { - return createElement(EnsApiContext.Provider, { value: options }, children); + return createElement(EnsNodeContext.Provider, { value: options }, children); } -export function EnsApiProvider(parameters: React.PropsWithChildren) { +export function EnsNodeProvider(parameters: React.PropsWithChildren) { const { children, options, queryClient, queryClientOptions } = parameters; // Check if we're already inside a QueryClientProvider @@ -91,7 +91,7 @@ export function EnsApiProvider(parameters: React.PropsWithChildren { /** * Configuration parameter for hooks that need access to config */ -export interface WithEnsApiProviderOptions< - TOptions extends EnsApiProviderOptions = EnsApiProviderOptions, +export interface WithEnsNodeProviderOptions< + TOptions extends EnsNodeProviderOptions = EnsNodeProviderOptions, > { options?: TOptions | undefined; } diff --git a/packages/ensnode-react/src/utils/query.ts b/packages/ensnode-react/src/utils/query.ts index 8a8696d4c4..082019af21 100644 --- a/packages/ensnode-react/src/utils/query.ts +++ b/packages/ensnode-react/src/utils/query.ts @@ -12,7 +12,7 @@ import { type ResolverRecordsSelection, } from "@ensnode/ensnode-sdk"; -import type { EnsApiProviderOptions } from "../types"; +import type { EnsNodeProviderOptions } from "../types"; /** * Immutable query options for data that is assumed to be immutable and should only be fetched once per full page refresh per unique key. @@ -67,7 +67,7 @@ export const queryKeys = { * Creates query options for Records Resolution */ export function createRecordsQueryOptions( - config: EnsApiProviderOptions, + config: EnsNodeProviderOptions, args: ResolveRecordsRequest, ) { return { @@ -84,7 +84,7 @@ export function createRecordsQueryOptions Date: Fri, 17 Apr 2026 11:52:39 +0200 Subject: [PATCH 24/56] docs(changeset): Fixed ENS Namespace option for calling `useResolvedIdentity` hook from `ResolveAndDisplayIdentity` component. --- .changeset/young-phones-look.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/young-phones-look.md diff --git a/.changeset/young-phones-look.md b/.changeset/young-phones-look.md new file mode 100644 index 0000000000..063ef34a2e --- /dev/null +++ b/.changeset/young-phones-look.md @@ -0,0 +1,5 @@ +--- +"@namehash/namehash-ui": patch +--- + +Fixed ENS Namespace option for calling `useResolvedIdentity` hook from `ResolveAndDisplayIdentity` component. From 15f314c6163a41414c8be102dcfbe272cf133455 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 12:19:16 +0200 Subject: [PATCH 25/56] Rename `config` field to `ensApiPublicConfig` Updated `EnsApiIndexingStatusResponseOk` data model --- .../src/app/mock/indexing-stats/page.tsx | 2 +- .../src/app/mock/indexing-status-api.mock.ts | 65 ++++++++++++++++--- ...Config.ts => use-ens-api-public-config.ts} | 6 +- .../connection/cards/ensnode-info.tsx | 4 +- .../connections/require-active-connection.tsx | 4 +- .../use-indexing-status-with-swr.ts | 13 +++- .../active/use-active-ensnode-config.tsx | 6 +- .../hooks/active/use-ensadmin-features.tsx | 10 +-- .../ensadmin/src/hooks/async/use-namespace.ts | 4 +- .../src/handlers/api/meta/status-api.ts | 2 +- .../ensapi/api/indexing-status/deserialize.ts | 2 +- .../ensapi/api/indexing-status/response.ts | 2 +- .../ensapi/api/indexing-status/serialize.ts | 2 +- .../indexing-status/serialized-response.ts | 4 +- .../ensapi/api/indexing-status/zod-schemas.ts | 4 +- 15 files changed, 93 insertions(+), 37 deletions(-) rename apps/ensadmin/src/components/config/{useEnsApiConfig.ts => use-ens-api-public-config.ts} (82%) diff --git a/apps/ensadmin/src/app/mock/indexing-stats/page.tsx b/apps/ensadmin/src/app/mock/indexing-stats/page.tsx index 5417bc3d08..5e79132945 100644 --- a/apps/ensadmin/src/app/mock/indexing-stats/page.tsx +++ b/apps/ensadmin/src/app/mock/indexing-stats/page.tsx @@ -83,7 +83,7 @@ export default function MockIndexingStatusPage() { response.realtimeProjection.snapshot, now, ), - config: response.config, + ensApiPublicConfig: response.ensApiPublicConfig, } satisfies IndexingStatusResponseOk; }, retry: false, // allows loading error to be observed immediately diff --git a/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts b/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts index d38171870c..0fb2051230 100644 --- a/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts +++ b/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts @@ -1,6 +1,7 @@ import { ChainIndexingStatusIds, CrossChainIndexingStrategyIds, + deserializeEnsApiIndexingStatusResponse, deserializeIndexingStatusResponse, EnsApiIndexingStatusResponseOk, IndexingStatusResponseCodes, @@ -12,13 +13,59 @@ import { type SerializedChainIndexingStatusSnapshotCompleted, type SerializedChainIndexingStatusSnapshotFollowing, type SerializedChainIndexingStatusSnapshotQueued, + SerializedEnsApiPublicConfig, + SerializedEnsIndexerPublicConfig, type SerializedOmnichainIndexingStatusSnapshotBackfill, type SerializedOmnichainIndexingStatusSnapshotCompleted, type SerializedOmnichainIndexingStatusSnapshotFollowing, type SerializedOmnichainIndexingStatusSnapshotUnstarted, } from "@ensnode/ensnode-sdk"; -import { serializedEnsApiPublicConfig } from "@/app/mock/config-api.mock"; +const serializedEnsIndexerPublicConfig = { + labelSet: { + labelSetId: "subgraph", + labelSetVersion: 0, + }, + indexedChainIds: [1, 8453, 59144, 10, 42161, 534352, 567], + ensIndexerSchemaName: "alphaSchema0.34.0", + ensRainbowPublicConfig: { + version: "0.34.0", + labelSet: { + labelSetId: "subgraph", + highestLabelSetVersion: 0, + }, + recordsCount: 100, + }, + isSubgraphCompatible: false, + namespace: "mainnet", + plugins: [ + "subgraph", + "basenames", + "lineanames", + "threedns", + "protocol-acceleration", + "registrars", + "tokenscope", + ], + versionInfo: { + ponder: "0.11.43", + ensIndexer: "0.35.0", + ensDb: "0.35.0", + ensNormalize: "1.11.1", + }, +} satisfies SerializedEnsIndexerPublicConfig; + +export const serializedEnsApiPublicConfig = { + ensIndexerPublicConfig: serializedEnsIndexerPublicConfig, + theGraphFallback: { + canFallback: true, + url: "https://api.thegraph.com/subgraphs/name/ensdomains/ens", + }, + versionInfo: { + ensApi: "0.35.0", + ensNormalize: "1.11.1", + }, +} satisfies SerializedEnsApiPublicConfig; export const indexingStatusResponseError: IndexingStatusResponseError = { responseCode: IndexingStatusResponseCodes.Error, @@ -28,7 +75,7 @@ export const indexingStatusResponseOkOmnichain: Record< OmnichainIndexingStatusId, EnsApiIndexingStatusResponseOk > = { - [OmnichainIndexingStatusIds.Unstarted]: deserializeIndexingStatusResponse({ + [OmnichainIndexingStatusIds.Unstarted]: deserializeEnsApiIndexingStatusResponse({ responseCode: IndexingStatusResponseCodes.Ok, realtimeProjection: { projectedAt: 1759409669, @@ -87,10 +134,10 @@ export const indexingStatusResponseOkOmnichain: Record< } satisfies SerializedOmnichainIndexingStatusSnapshotUnstarted, }, }, - config: serializedEnsApiPublicConfig, + ensApiPublicConfig: serializedEnsApiPublicConfig, }), - [OmnichainIndexingStatusIds.Backfill]: deserializeIndexingStatusResponse({ + [OmnichainIndexingStatusIds.Backfill]: deserializeEnsApiIndexingStatusResponse({ responseCode: IndexingStatusResponseCodes.Ok, realtimeProjection: { projectedAt: 1759409670, @@ -166,10 +213,10 @@ export const indexingStatusResponseOkOmnichain: Record< } satisfies SerializedOmnichainIndexingStatusSnapshotBackfill, }, }, - config: serializedEnsApiPublicConfig, + ensApiPublicConfig: serializedEnsApiPublicConfig, }), - [OmnichainIndexingStatusIds.Following]: deserializeIndexingStatusResponse({ + [OmnichainIndexingStatusIds.Following]: deserializeEnsApiIndexingStatusResponse({ responseCode: IndexingStatusResponseCodes.Ok, realtimeProjection: { projectedAt: 1755667460, @@ -260,10 +307,10 @@ export const indexingStatusResponseOkOmnichain: Record< } satisfies SerializedOmnichainIndexingStatusSnapshotFollowing, }, }, - config: serializedEnsApiPublicConfig, + ensApiPublicConfig: serializedEnsApiPublicConfig, }), - [OmnichainIndexingStatusIds.Completed]: deserializeIndexingStatusResponse({ + [OmnichainIndexingStatusIds.Completed]: deserializeEnsApiIndexingStatusResponse({ responseCode: IndexingStatusResponseCodes.Ok, realtimeProjection: { projectedAt: 1689337668, @@ -298,6 +345,6 @@ export const indexingStatusResponseOkOmnichain: Record< } satisfies SerializedOmnichainIndexingStatusSnapshotCompleted, }, }, - config: serializedEnsApiPublicConfig, + ensApiPublicConfig: serializedEnsApiPublicConfig, }), }; diff --git a/apps/ensadmin/src/components/config/useEnsApiConfig.ts b/apps/ensadmin/src/components/config/use-ens-api-public-config.ts similarity index 82% rename from apps/ensadmin/src/components/config/useEnsApiConfig.ts rename to apps/ensadmin/src/components/config/use-ens-api-public-config.ts index 3092f80963..53edd46983 100644 --- a/apps/ensadmin/src/components/config/useEnsApiConfig.ts +++ b/apps/ensadmin/src/components/config/use-ens-api-public-config.ts @@ -11,19 +11,19 @@ import { useIndexingStatusWithSwr } from "@/components/indexing-status"; * the ENSApi public config) with React Query to provide an easy way to access * the ENSApi config for the currently selected connection. */ -export function useEnsApiConfig() { +export function useEnsApiPublicConfig() { const EnsNodeProviderOptions = useEnsNodeProviderOptions(); const indexingStatus = useIndexingStatusWithSwr(); return useQuery({ enabled: indexingStatus.isFetched, - queryKey: ["swr", EnsNodeProviderOptions.client.url.href, "config"], + queryKey: ["swr", EnsNodeProviderOptions.client.url.href, "ensApiPublicConfig"], queryFn: async () => { if (!indexingStatus.data) { throw new Error("Indexing status wasn't fetched successfully"); } - return indexingStatus.data.config; + return indexingStatus.data.ensApiPublicConfig; }, }); } diff --git a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx index bb67b5078b..7b81f2b5ac 100644 --- a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx +++ b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx @@ -11,7 +11,7 @@ import { Fragment, ReactNode } from "react"; import { type EnsApiPublicConfig, getENSRootChainId } from "@ensnode/ensnode-sdk"; -import { useEnsApiConfig } from "@/components/config/useEnsApiConfig"; +import { useEnsApiPublicConfig } from "@/components/config/use-ens-api-public-config"; import { ErrorInfo, type ErrorInfoProps } from "@/components/error-info"; import { ENSApiIcon } from "@/components/icons/ensnode-apps/ensapi-icon"; import { ENSDbIcon } from "@/components/icons/ensnode-apps/ensdb-icon"; @@ -147,7 +147,7 @@ export function ENSNodeConfigInfoView({ * ENSNodeConfigInfo component - fetches and displays ENSNode configuration data */ export function ENSNodeConfigInfo() { - const ensApiConfig = useEnsApiConfig(); + const ensApiConfig = useEnsApiPublicConfig(); if (ensApiConfig.isError) { return ( diff --git a/apps/ensadmin/src/components/connections/require-active-connection.tsx b/apps/ensadmin/src/components/connections/require-active-connection.tsx index d56c7709b5..ad2ee6f1e6 100644 --- a/apps/ensadmin/src/components/connections/require-active-connection.tsx +++ b/apps/ensadmin/src/components/connections/require-active-connection.tsx @@ -2,7 +2,7 @@ import type { PropsWithChildren } from "react"; -import { useEnsApiConfig } from "@/components/config/useEnsApiConfig"; +import { useEnsApiPublicConfig } from "@/components/config/use-ens-api-public-config"; import { ErrorInfo } from "@/components/error-info"; import { LoadingSpinner } from "@/components/loading-spinner"; @@ -10,7 +10,7 @@ import { LoadingSpinner } from "@/components/loading-spinner"; * Allows consumers to use `useActiveConnection` by blocking rendering until it is available. */ export function RequireActiveConnection({ children }: PropsWithChildren) { - const ensApiConfig = useEnsApiConfig(); + const ensApiConfig = useEnsApiPublicConfig(); if (ensApiConfig.status === "pending") return ; diff --git a/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts b/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts index db6fa1247f..9ad6d359a1 100644 --- a/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts +++ b/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts @@ -30,8 +30,15 @@ const REALTIME_PROJECTION_REFRESH_RATE: Duration = 1; * Data model for the object cached in SWR for indexing status query results. */ interface CacheableIndexingStatus { + /** + * The snapshot of the Cross-Chain Indexing Status. + */ crossChainIndexingStatusSnapshot: CrossChainIndexingStatusSnapshotOmnichain; - config: EnsApiPublicConfig; + + /** + * The Public Config for the connected ENSApi. + */ + ensApiPublicConfig: EnsApiPublicConfig; } interface UseIndexingStatusParameters @@ -73,7 +80,7 @@ export function useIndexingStatusWithSwr( // - Return this non-null value. return { crossChainIndexingStatusSnapshot: response.realtimeProjection.snapshot, - config: response.config, + ensApiPublicConfig: response.ensApiPublicConfig, } satisfies CacheableIndexingStatus; }), [queryOptions.queryFn], @@ -94,7 +101,7 @@ export function useIndexingStatusWithSwr( return { responseCode: EnsApiIndexingStatusResponseCodes.Ok, realtimeProjection, - config: cachedResult.config, + ensApiPublicConfig: cachedResult.ensApiPublicConfig, } satisfies EnsApiIndexingStatusResponseOk; }, [now], diff --git a/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx b/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx index 2913645a19..893f34041f 100644 --- a/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx +++ b/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx @@ -1,12 +1,12 @@ "use client"; -import { useEnsApiConfig } from "@/components/config/useEnsApiConfig"; +import { useEnsApiPublicConfig } from "@/components/config/use-ens-api-public-config"; /** * Hook to get the currently active ENSNode Config synchronously. * * This hook provides synchronous access to the active ENSNode connection. - * If no ENSNode connection is synchronouslly available, components using + * If no ENSNode connection is synchronously available, components using * this hook will throw. Components that use this hook should be a child of * `RequireActiveConnection` such that the connected ENSNode's config is synchronously * available during render. This simplifies state in components that only make sense @@ -16,7 +16,7 @@ import { useEnsApiConfig } from "@/components/config/useEnsApiConfig"; * @throws Error if no active ENSNode connection is available */ export function useActiveENSNodeConfig() { - const { data } = useEnsApiConfig(); + const { data } = useEnsApiPublicConfig(); if (data === undefined) { throw new Error(`Invariant(useActiveENSNodeConfig): Expected an active ENSNode Config`); diff --git a/apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx b/apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx index 028c3c0c75..c7113d8ca2 100644 --- a/apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx +++ b/apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx @@ -59,8 +59,10 @@ export function useENSAdminFeatures(): ENSAdminFeatures { if (indexingStatusQuery.status === "error") return INDEXING_STATUS_ERROR_STATUS; if (indexingStatusQuery.status === "pending") return CONNECTING_STATUS; - const { config } = indexingStatusQuery.data; - const configSupportResult = hasRegistrarActionsConfigSupport(config.ensIndexerPublicConfig); + const { ensApiPublicConfig } = indexingStatusQuery.data; + const configSupportResult = hasRegistrarActionsConfigSupport( + ensApiPublicConfig.ensIndexerPublicConfig, + ); if (!configSupportResult.supported) return prerequisiteResultToFeatureStatus(configSupportResult); @@ -79,7 +81,7 @@ export function useENSAdminFeatures(): ENSAdminFeatures { if (indexingStatusQuery.status === "error") return INDEXING_STATUS_ERROR_STATUS; if (indexingStatusQuery.status === "pending") return CONNECTING_STATUS; - const { ensIndexerPublicConfig } = indexingStatusQuery.data.config; + const { ensIndexerPublicConfig } = indexingStatusQuery.data.ensApiPublicConfig; return prerequisiteResultToFeatureStatus(hasSubgraphApiConfigSupport(ensIndexerPublicConfig)); }, [indexingStatusQuery]); @@ -87,7 +89,7 @@ export function useENSAdminFeatures(): ENSAdminFeatures { if (indexingStatusQuery.status === "error") return INDEXING_STATUS_ERROR_STATUS; if (indexingStatusQuery.status === "pending") return CONNECTING_STATUS; - const { ensIndexerPublicConfig } = indexingStatusQuery.data.config; + const { ensIndexerPublicConfig } = indexingStatusQuery.data.ensApiPublicConfig; return prerequisiteResultToFeatureStatus(hasOmnigraphApiConfigSupport(ensIndexerPublicConfig)); }, [indexingStatusQuery]); diff --git a/apps/ensadmin/src/hooks/async/use-namespace.ts b/apps/ensadmin/src/hooks/async/use-namespace.ts index a48acbd027..17ec932ada 100644 --- a/apps/ensadmin/src/hooks/async/use-namespace.ts +++ b/apps/ensadmin/src/hooks/async/use-namespace.ts @@ -1,4 +1,4 @@ -import { useEnsApiConfig } from "@/components/config/useEnsApiConfig"; +import { useEnsApiPublicConfig } from "@/components/config/use-ens-api-public-config"; /** * Hook to get the namespace ID from the active ENSNode connection. @@ -22,7 +22,7 @@ import { useEnsApiConfig } from "@/components/config/useEnsApiConfig"; * ``` */ export function useNamespace() { - const query = useEnsApiConfig(); + const query = useEnsApiPublicConfig(); return { ...query, diff --git a/apps/ensapi/src/handlers/api/meta/status-api.ts b/apps/ensapi/src/handlers/api/meta/status-api.ts index cffe687d26..5d51069659 100644 --- a/apps/ensapi/src/handlers/api/meta/status-api.ts +++ b/apps/ensapi/src/handlers/api/meta/status-api.ts @@ -32,7 +32,7 @@ app.openapi(getIndexingStatusRoute, async (c) => { serializeEnsApiIndexingStatusResponse({ responseCode: EnsApiIndexingStatusResponseCodes.Ok, realtimeProjection: c.var.indexingStatus, - config: ensApiPublicConfig, + ensApiPublicConfig: ensApiPublicConfig, } satisfies EnsApiIndexingStatusResponseOk), 200, ); diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts index bbf44eafba..acf788ec1a 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts @@ -38,7 +38,7 @@ function buildUnvalidatedEnsApiIndexingStatusResponse( realtimeProjection: buildUnvalidatedRealtimeIndexingStatusProjection( serializedResponse.realtimeProjection, ), - config: buildUnvalidatedEnsApiPublicConfig(serializedResponse.config), + ensApiPublicConfig: buildUnvalidatedEnsApiPublicConfig(serializedResponse.ensApiPublicConfig), }; } diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts index 2cb679a65d..5cd8092712 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts @@ -42,7 +42,7 @@ export type IndexingStatusResponseCode = EnsApiIndexingStatusResponseCode; export type EnsApiIndexingStatusResponseOk = { responseCode: typeof EnsApiIndexingStatusResponseCodes.Ok; realtimeProjection: RealtimeIndexingStatusProjection; - config: EnsApiPublicConfig; + ensApiPublicConfig: EnsApiPublicConfig; }; /** diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts index ac7fbfd9ea..1ddac600e0 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts @@ -32,7 +32,7 @@ export function serializeEnsApiIndexingStatusResponse( return { responseCode: response.responseCode, realtimeProjection: serializeRealtimeIndexingStatusProjection(response.realtimeProjection), - config: serializeEnsApiPublicConfig(response.config), + ensApiPublicConfig: serializeEnsApiPublicConfig(response.ensApiPublicConfig), } satisfies SerializedEnsApiIndexingStatusResponseOk; case EnsApiIndexingStatusResponseCodes.Error: diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts index cfd47ddab9..939bb6324c 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts @@ -22,9 +22,9 @@ export type SerializedIndexingStatusResponseError = SerializedEnsApiIndexingStat * Serialized representation of {@link EnsApiIndexingStatusResponseOk}. */ export interface SerializedEnsApiIndexingStatusResponseOk - extends Omit { + extends Omit { realtimeProjection: SerializedRealtimeIndexingStatusProjection; - config: SerializedEnsApiPublicConfig; + ensApiPublicConfig: SerializedEnsApiPublicConfig; } /** diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts index 83df37e6b9..30ce6795cd 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts @@ -25,7 +25,7 @@ export const makeEnsApiIndexingStatusResponseOkSchema = ( z.strictObject({ responseCode: z.literal(EnsApiIndexingStatusResponseCodes.Ok), realtimeProjection: makeRealtimeIndexingStatusProjectionSchema(valueLabel), - config: makeEnsApiPublicConfigSchema(valueLabel), + ensApiPublicConfig: makeEnsApiPublicConfigSchema(valueLabel), }); /** @@ -64,7 +64,7 @@ export const makeSerializedEnsApiIndexingStatusResponseOkSchema = ( z.object({ responseCode: z.literal(EnsApiIndexingStatusResponseCodes.Ok), realtimeProjection: makeSerializedRealtimeIndexingStatusProjectionSchema(valueLabel), - config: makeSerializedEnsApiPublicConfigSchema(valueLabel), + ensApiPublicConfig: makeSerializedEnsApiPublicConfigSchema(valueLabel), }); /** From a814284678ecf39f70a28f1b8329b2c9faa35400 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 12:21:25 +0200 Subject: [PATCH 26/56] Remove the `ConfigResponse` data model It was replaced by the `ensApiPublicConfig` field in the `EnsApiIndexingStatusResponseOk` data model --- apps/ensadmin/src/app/mock/config-api.mock.ts | 55 ------------------- .../src/ensapi/api/config/deserialize.ts | 20 ------- .../src/ensapi/api/config/index.ts | 4 -- .../src/ensapi/api/config/response.ts | 13 ----- .../src/ensapi/api/config/serialize.ts | 19 ------- .../ensapi/api/config/serialized-response.ts | 12 ---- packages/ensnode-sdk/src/ensapi/api/index.ts | 1 - .../ensnode-sdk/src/ensapi/client.test.ts | 32 +---------- packages/ensnode-sdk/src/ensapi/client.ts | 36 ------------ 9 files changed, 2 insertions(+), 190 deletions(-) delete mode 100644 apps/ensadmin/src/app/mock/config-api.mock.ts delete mode 100644 packages/ensnode-sdk/src/ensapi/api/config/deserialize.ts delete mode 100644 packages/ensnode-sdk/src/ensapi/api/config/index.ts delete mode 100644 packages/ensnode-sdk/src/ensapi/api/config/response.ts delete mode 100644 packages/ensnode-sdk/src/ensapi/api/config/serialize.ts delete mode 100644 packages/ensnode-sdk/src/ensapi/api/config/serialized-response.ts diff --git a/apps/ensadmin/src/app/mock/config-api.mock.ts b/apps/ensadmin/src/app/mock/config-api.mock.ts deleted file mode 100644 index e15ff8f2bc..0000000000 --- a/apps/ensadmin/src/app/mock/config-api.mock.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - deserializeENSIndexerPublicConfig, - SerializedEnsApiConfigResponse, - SerializedEnsIndexerConfigResponse, -} from "@ensnode/ensnode-sdk"; - -const serializedEnsIndexerPublicConfig = { - labelSet: { - labelSetId: "subgraph", - labelSetVersion: 0, - }, - indexedChainIds: [1, 8453, 59144, 10, 42161, 534352, 567], - ensIndexerSchemaName: "alphaSchema0.34.0", - ensRainbowPublicConfig: { - version: "0.34.0", - labelSet: { - labelSetId: "subgraph", - highestLabelSetVersion: 0, - }, - recordsCount: 100, - }, - isSubgraphCompatible: false, - namespace: "mainnet", - plugins: [ - "subgraph", - "basenames", - "lineanames", - "threedns", - "protocol-acceleration", - "registrars", - "tokenscope", - ], - versionInfo: { - ponder: "0.11.43", - ensIndexer: "0.35.0", - ensDb: "0.35.0", - ensNormalize: "1.11.1", - }, -} satisfies SerializedEnsIndexerConfigResponse; - -export const ensIndexerPublicConfig = deserializeENSIndexerPublicConfig( - serializedEnsIndexerPublicConfig, -); - -export const serializedEnsApiPublicConfig = { - ensIndexerPublicConfig: serializedEnsIndexerPublicConfig, - theGraphFallback: { - canFallback: true, - url: "https://api.thegraph.com/subgraphs/name/ensdomains/ens", - }, - versionInfo: { - ensApi: "0.35.0", - ensNormalize: "1.11.1", - }, -} satisfies SerializedEnsApiConfigResponse; diff --git a/packages/ensnode-sdk/src/ensapi/api/config/deserialize.ts b/packages/ensnode-sdk/src/ensapi/api/config/deserialize.ts deleted file mode 100644 index eabd780976..0000000000 --- a/packages/ensnode-sdk/src/ensapi/api/config/deserialize.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Unvalidated } from "../../../shared/types"; -import { deserializeEnsApiPublicConfig } from "../../config/deserialize"; -import type { EnsApiConfigResponse } from "./response"; -import type { SerializedEnsApiConfigResponse } from "./serialized-response"; - -/** - * Deserialize a {@link EnsApiConfigResponse} object. - */ -export function deserializeEnsApiConfigResponse( - maybeResponse: Unvalidated, -): EnsApiConfigResponse { - return deserializeEnsApiPublicConfig(maybeResponse); -} - -/** - * Deserialize a {@link EnsApiConfigResponse} object. - * - * @deprecated Use {@link deserializeEnsApiConfigResponse} instead. - */ -export const deserializeConfigResponse = deserializeEnsApiConfigResponse; diff --git a/packages/ensnode-sdk/src/ensapi/api/config/index.ts b/packages/ensnode-sdk/src/ensapi/api/config/index.ts deleted file mode 100644 index c33e8f8839..0000000000 --- a/packages/ensnode-sdk/src/ensapi/api/config/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./deserialize"; -export * from "./response"; -export * from "./serialize"; -export * from "./serialized-response"; diff --git a/packages/ensnode-sdk/src/ensapi/api/config/response.ts b/packages/ensnode-sdk/src/ensapi/api/config/response.ts deleted file mode 100644 index 62ea23bb87..0000000000 --- a/packages/ensnode-sdk/src/ensapi/api/config/response.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { EnsApiPublicConfig } from "../../config/types"; - -/** - * ENSApi Public Config Response - */ -export type EnsApiConfigResponse = EnsApiPublicConfig; - -/** - * ENSApi Config API Response - * - * @deprecated Use {@link EnsApiConfigResponse} instead. - */ -export type ConfigResponse = EnsApiConfigResponse; diff --git a/packages/ensnode-sdk/src/ensapi/api/config/serialize.ts b/packages/ensnode-sdk/src/ensapi/api/config/serialize.ts deleted file mode 100644 index 8b3dbe0cee..0000000000 --- a/packages/ensnode-sdk/src/ensapi/api/config/serialize.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { serializeEnsApiPublicConfig } from "../../config/serialize"; -import type { EnsApiConfigResponse } from "./response"; -import type { SerializedEnsApiConfigResponse } from "./serialized-response"; - -/** - * Serialize ENSApi Config API Response - */ -export function serializeEnsApiConfigResponse( - response: EnsApiConfigResponse, -): SerializedEnsApiConfigResponse { - return serializeEnsApiPublicConfig(response); -} - -/** - * Serialize ENSApi Config API Response - * - * @deprecated Use {@link serializeEnsApiConfigResponse} instead. - */ -export const serializeConfigResponse = serializeEnsApiConfigResponse; diff --git a/packages/ensnode-sdk/src/ensapi/api/config/serialized-response.ts b/packages/ensnode-sdk/src/ensapi/api/config/serialized-response.ts deleted file mode 100644 index 4a4487c655..0000000000 --- a/packages/ensnode-sdk/src/ensapi/api/config/serialized-response.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { SerializedEnsApiPublicConfig } from "../../config/serialized-types"; -import type { EnsApiConfigResponse } from "./response"; - -/** - * Serialized representation of {@link EnsApiConfigResponse} - */ -export type SerializedEnsApiConfigResponse = SerializedEnsApiPublicConfig; - -/** - * @deprecated Use {@link SerializedEnsApiConfigResponse} instead. - */ -export type SerializedConfigResponse = SerializedEnsApiConfigResponse; diff --git a/packages/ensnode-sdk/src/ensapi/api/index.ts b/packages/ensnode-sdk/src/ensapi/api/index.ts index 661de1ea75..306cc2cf3e 100644 --- a/packages/ensnode-sdk/src/ensapi/api/index.ts +++ b/packages/ensnode-sdk/src/ensapi/api/index.ts @@ -1,4 +1,3 @@ -export * from "./config"; export * from "./indexing-status"; export * from "./name-tokens"; export * from "./registrar-actions"; diff --git a/packages/ensnode-sdk/src/ensapi/client.test.ts b/packages/ensnode-sdk/src/ensapi/client.test.ts index dd522c4106..1460d054b9 100644 --- a/packages/ensnode-sdk/src/ensapi/client.test.ts +++ b/packages/ensnode-sdk/src/ensapi/client.test.ts @@ -23,7 +23,6 @@ import type { import type { ErrorResponse } from "./api/shared/errors/response"; import { EnsApiClient } from "./client"; import { ClientError } from "./client-error"; -import { deserializeEnsApiPublicConfig } from "./config/deserialize"; import type { SerializedEnsApiPublicConfig } from "./config/serialized-types"; import { DEFAULT_ENSNODE_API_URL_MAINNET, getDefaultEnsNodeUrl } from "./deployments"; @@ -141,7 +140,7 @@ const EXAMPLE_INDEXING_STATUS_BACKFILL_RESPONSE = deserializeEnsApiIndexingStatu }, }, }, - config: EXAMPLE_CONFIG_RESPONSE, + ensApiPublicConfig: EXAMPLE_CONFIG_RESPONSE, responseCode: EnsApiIndexingStatusResponseCodes.Ok, } satisfies SerializedEnsApiIndexingStatusResponseOk); @@ -199,7 +198,7 @@ const _EXAMPLE_INDEXING_STATUS_FOLLOWING_RESPONSE: EnsApiIndexingStatusResponse } satisfies SerializedOmnichainIndexingStatusSnapshotFollowing, }, }, - config: EXAMPLE_CONFIG_RESPONSE, + ensApiPublicConfig: EXAMPLE_CONFIG_RESPONSE, responseCode: EnsApiIndexingStatusResponseCodes.Ok, }); @@ -429,33 +428,6 @@ describe("EnsApiClient", () => { }); }); - describe("Config API", () => { - it("can fetch config object successfully", async () => { - // arrange - const requestUrl = new URL(`/api/config`, DEFAULT_ENSNODE_API_URL_MAINNET); - const serializedMockedResponse = EXAMPLE_CONFIG_RESPONSE; - const mockedResponse = deserializeEnsApiPublicConfig(serializedMockedResponse); - const client = new EnsApiClient(); - - mockFetch.mockResolvedValueOnce({ - ok: true, - json: async () => serializedMockedResponse, - }); - - // act & assert - await expect(client.config()).resolves.toStrictEqual(mockedResponse); - expect(mockFetch).toHaveBeenCalledWith(requestUrl); - }); - - it("should throw error when API returns error", async () => { - mockFetch.mockResolvedValueOnce({ ok: false, json: async () => EXAMPLE_ERROR_RESPONSE }); - - const client = new EnsApiClient(); - - await expect(client.config()).rejects.toThrow(/Fetching ENSApi Config Failed/i); - }); - }); - describe("Indexing Status API", () => { it("can fetch overall indexing 'backfill' status object successfully", async () => { // arrange diff --git a/packages/ensnode-sdk/src/ensapi/client.ts b/packages/ensnode-sdk/src/ensapi/client.ts index f8822f5193..b4116832ce 100644 --- a/packages/ensnode-sdk/src/ensapi/client.ts +++ b/packages/ensnode-sdk/src/ensapi/client.ts @@ -1,11 +1,9 @@ import type { ResolverRecordsSelection } from "../resolution"; import { deserializedNameTokensResponse, - deserializeEnsApiConfigResponse, deserializeEnsApiIndexingStatusResponse, deserializeErrorResponse, deserializeRegistrarActionsResponse, - type EnsApiConfigResponse, type EnsApiIndexingStatusResponse, type ErrorResponse, type NameTokensRequest, @@ -22,7 +20,6 @@ import { type ResolvePrimaryNamesResponse, type ResolveRecordsRequest, type ResolveRecordsResponse, - type SerializedEnsApiConfigResponse, type SerializedEnsApiIndexingStatusResponse, type SerializedNameTokensResponse, type SerializedRegistrarActionsResponse, @@ -310,39 +307,6 @@ export class EnsApiClient { return data as ResolvePrimaryNamesResponse; } - /** - * Fetch ENSApi Config - * - * Fetch the ENSApi's configuration. - * - * @returns {EnsApiConfigResponse} - * - * @throws if the ENSApi request fails - * @throws if the ENSApi returns a non-ok response - * @throws if the ENSApi response breaks required invariants - */ - async config(): Promise { - const url = new URL(`/api/config`, this.options.url); - - const response = await fetch(url); - - // ENSApi should always allow parsing a response as JSON object. - // If for some reason it's not the case, throw an error. - let responseData: unknown; - try { - responseData = await response.json(); - } catch { - throw new Error("Malformed response data: invalid JSON"); - } - - if (!response.ok) { - const errorResponse = deserializeErrorResponse(responseData); - throw new Error(`Fetching ENSApi Config Failed: ${errorResponse.message}`); - } - - return deserializeEnsApiConfigResponse(responseData as SerializedEnsApiConfigResponse); - } - /** * Fetch ENSApi Indexing Status * From 839bb4cc2e896c4fb3c21482bfb480201ebe59ab Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 12:22:02 +0200 Subject: [PATCH 27/56] Fix mocked data --- .../src/app/mock/config-info/data.json | 25 +++++++++++++++---- .../src/app/mock/registrar-actions/mocks.ts | 2 +- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/apps/ensadmin/src/app/mock/config-info/data.json b/apps/ensadmin/src/app/mock/config-info/data.json index 43de682f44..c339d9615a 100644 --- a/apps/ensadmin/src/app/mock/config-info/data.json +++ b/apps/ensadmin/src/app/mock/config-info/data.json @@ -1,6 +1,9 @@ { "Alpha Mainnet": { - "version": "0.35.0", + "versionInfo": { + "ensApi": "0.35.0", + "ensNormalize": "1.11.1" + }, "theGraphFallback": { "canFallback": false, "reason": "no-api-key" @@ -41,7 +44,10 @@ } }, "Alpha Sepolia": { - "version": "0.35.0", + "versionInfo": { + "ensApi": "0.35.0", + "ensNormalize": "1.11.1" + }, "theGraphFallback": { "canFallback": true, "url": "" @@ -81,7 +87,10 @@ } }, "Subgraph Mainnet": { - "version": "0.35.0", + "versionInfo": { + "ensApi": "0.35.0", + "ensNormalize": "1.11.1" + }, "theGraphFallback": { "canFallback": false, "reason": "no-api-key" @@ -114,7 +123,10 @@ } }, "Subgraph Sepolia": { - "version": "0.35.0", + "versionInfo": { + "ensApi": "0.35.0", + "ensNormalize": "1.11.1" + }, "theGraphFallback": { "canFallback": false, "reason": "no-api-key" @@ -147,7 +159,10 @@ } }, "Serialization Error": { - "version": "0.35.0", + "versionInfo": { + "ensApi": "0.35.0", + "ensNormalize": "1.11.1" + }, "theGraphFallback": { "canFallback": false, "reason": "no-api-key" diff --git a/apps/ensadmin/src/app/mock/registrar-actions/mocks.ts b/apps/ensadmin/src/app/mock/registrar-actions/mocks.ts index 41a7e4fe6f..5a3ece6aa9 100644 --- a/apps/ensadmin/src/app/mock/registrar-actions/mocks.ts +++ b/apps/ensadmin/src/app/mock/registrar-actions/mocks.ts @@ -212,7 +212,7 @@ function registrarActionWithUpdatedIncrementalDuration( return { ...registrarAction, action: { ...registrarAction.action, incrementalDuration }, - name: asInterpretedName(`incrementalDuration-${incrementalDuration}.${registrarAction.name}`), + name: asInterpretedName(`incremental-duration-${incrementalDuration}.${registrarAction.name}`), } satisfies NamedRegistrarAction; } From 26a2ff0e2128bd9e83a664e0e5d1a117dc0535b7 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 12:24:13 +0200 Subject: [PATCH 28/56] Update OpenAPI Spec --- docs/docs.ensnode.io/ensapi-openapi.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs.ensnode.io/ensapi-openapi.json b/docs/docs.ensnode.io/ensapi-openapi.json index 81ec6e9150..8854fcba0b 100644 --- a/docs/docs.ensnode.io/ensapi-openapi.json +++ b/docs/docs.ensnode.io/ensapi-openapi.json @@ -779,7 +779,7 @@ }, "required": ["snapshot", "projectedAt", "worstCaseDistance"] }, - "config": { + "ensApiPublicConfig": { "type": "object", "properties": { "ensIndexerPublicConfig": { @@ -898,7 +898,7 @@ "required": ["ensIndexerPublicConfig", "theGraphFallback", "versionInfo"] } }, - "required": ["responseCode", "realtimeProjection", "config"] + "required": ["responseCode", "realtimeProjection", "ensApiPublicConfig"] } } } From cdc1736eb6d4adbf5ca7a9ea54e9303d6e3d04b1 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 12:27:25 +0200 Subject: [PATCH 29/56] docs(changeset): Replaced the `EnsApiConfigResponse` data model by adding `ensApiPublicConfig` field to the `EnsApiIndexingStatusResponse` data model. --- .changeset/fancy-mails-fall.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fancy-mails-fall.md diff --git a/.changeset/fancy-mails-fall.md b/.changeset/fancy-mails-fall.md new file mode 100644 index 0000000000..c638cc3c15 --- /dev/null +++ b/.changeset/fancy-mails-fall.md @@ -0,0 +1,5 @@ +--- +"@ensnode/ensnode-sdk": minor +--- + +Replaced the `EnsApiConfigResponse` data model by adding `ensApiPublicConfig` field to the `EnsApiIndexingStatusResponseOk` data model. From 6f1d36347d40f072108dd04b67d061dc62f7fcb7 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 12:32:02 +0200 Subject: [PATCH 30/56] docs(changeset): **Breaking**: Replaced the `config()` method in the `EnsApiClient` class with the extended data model returned from the `indexingStatus()` method. --- .changeset/common-lines-smell.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/common-lines-smell.md diff --git a/.changeset/common-lines-smell.md b/.changeset/common-lines-smell.md new file mode 100644 index 0000000000..9be4789291 --- /dev/null +++ b/.changeset/common-lines-smell.md @@ -0,0 +1,5 @@ +--- +"@ensnode/ensnode-sdk": minor +--- + +**Breaking**: Replaced the `config()` method in the `EnsApiClient` class with the extended data model returned from the `indexingStatus()` method. From 288af776f132a1f324b1f428e11ce73d5db983ac Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 12:35:01 +0200 Subject: [PATCH 31/56] docs(changeset): **Breaking**: Replaced `useENSNodeSDKConfig` hook with `useEnsNodeProviderOptions` hook. --- .changeset/rare-dogs-sin.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rare-dogs-sin.md diff --git a/.changeset/rare-dogs-sin.md b/.changeset/rare-dogs-sin.md new file mode 100644 index 0000000000..9c7201403c --- /dev/null +++ b/.changeset/rare-dogs-sin.md @@ -0,0 +1,5 @@ +--- +"@ensnode/ensnode-react": minor +--- + +**Breaking**: Replaced `useENSNodeSDKConfig` hook with `useEnsNodeProviderOptions` hook. From 1ac516c547a8f07fb8b7db49a4b73491a26b841b Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 12:35:48 +0200 Subject: [PATCH 32/56] docs(changeset): **Breaking**: Removed `useENSNodeConfig` hook. --- .changeset/fiery-turtles-enter.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fiery-turtles-enter.md diff --git a/.changeset/fiery-turtles-enter.md b/.changeset/fiery-turtles-enter.md new file mode 100644 index 0000000000..70db1fa834 --- /dev/null +++ b/.changeset/fiery-turtles-enter.md @@ -0,0 +1,5 @@ +--- +"@ensnode/ensnode-react": minor +--- + +**Breaking**: Removed `useENSNodeConfig` hook. From dc147d0693bc439e137281971ea5382f45d94b5e Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 12:38:48 +0200 Subject: [PATCH 33/56] docs(changeset): **Breaking**: Removed Config API endpoint at `GET /api/config`. To get the ENSApi Public Config, call the `GET /api/indexing-status` endpoint and reference the `ensApiPublicConfig` field in the OK response. --- .changeset/thick-horses-prove.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/thick-horses-prove.md diff --git a/.changeset/thick-horses-prove.md b/.changeset/thick-horses-prove.md new file mode 100644 index 0000000000..49ab86e094 --- /dev/null +++ b/.changeset/thick-horses-prove.md @@ -0,0 +1,5 @@ +--- +"ensapi": minor +--- + +**Breaking**: Removed Config API endpoint at `GET /api/config`. To get the ENSApi Public Config, call the `GET /api/indexing-status` endpoint and reference the `ensApiPublicConfig` field in the OK response. From 2ce61f8b04fef20bed2e4045b72eca53dc518756 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 12:42:59 +0200 Subject: [PATCH 34/56] docs(changeset): Replaced the `useENSNodeConfig` with `useEnsApiPublicConfig` hook. The `useEnsApiPublicConfig` leverages the update data model returned from the `useIndexingStatusWithSwr` hook. --- .changeset/silent-adults-rest.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/silent-adults-rest.md diff --git a/.changeset/silent-adults-rest.md b/.changeset/silent-adults-rest.md new file mode 100644 index 0000000000..b8d894aabe --- /dev/null +++ b/.changeset/silent-adults-rest.md @@ -0,0 +1,5 @@ +--- +"ensadmin": minor +--- + +Replaced the `useENSNodeConfig` hook with `useEnsApiPublicConfig` hook. The `useEnsApiPublicConfig` hook leverages the updated data model returned from the `useIndexingStatusWithSwr` hook. From 0e7bd2a510339c1756241b33739b801034049fd4 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 18:16:24 +0200 Subject: [PATCH 35/56] Introduce `EnsNodeStackInfo` data model to ENSNode SDK --- .../ensapi/api/indexing-status/deserialize.ts | 12 ++--- .../ensapi/api/indexing-status/response.ts | 4 +- .../ensapi/api/indexing-status/serialize.ts | 4 +- .../indexing-status/serialized-response.ts | 6 +-- .../ensapi/api/indexing-status/zod-schemas.ts | 9 ++-- .../ensnode-sdk/src/ensapi/client.test.ts | 14 +++++- packages/ensnode-sdk/src/ensdb/config.ts | 9 ++++ packages/ensnode-sdk/src/ensdb/index.ts | 1 + .../src/ensdb/zod-schemas/config.ts | 14 ++++++ packages/ensnode-sdk/src/index.ts | 2 + .../deserialize/ensnode-stack-info.ts | 49 +++++++++++++++++++ .../src/stack-info/ensnode-stack-info.ts | 47 ++++++++++++++++++ packages/ensnode-sdk/src/stack-info/index.ts | 3 ++ .../serialize/ensnode-stack-info.ts | 26 ++++++++++ .../zod-schemas/ensnode-stack-info.ts | 34 +++++++++++++ 15 files changed, 216 insertions(+), 18 deletions(-) create mode 100644 packages/ensnode-sdk/src/ensdb/config.ts create mode 100644 packages/ensnode-sdk/src/ensdb/index.ts create mode 100644 packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts create mode 100644 packages/ensnode-sdk/src/stack-info/deserialize/ensnode-stack-info.ts create mode 100644 packages/ensnode-sdk/src/stack-info/ensnode-stack-info.ts create mode 100644 packages/ensnode-sdk/src/stack-info/index.ts create mode 100644 packages/ensnode-sdk/src/stack-info/serialize/ensnode-stack-info.ts create mode 100644 packages/ensnode-sdk/src/stack-info/zod-schemas/ensnode-stack-info.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts index acf788ec1a..06ab70914a 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts @@ -2,7 +2,7 @@ import { prettifyError } from "zod/v4"; import { buildUnvalidatedRealtimeIndexingStatusProjection } from "../../../indexing-status/deserialize/realtime-indexing-status-projection"; import type { Unvalidated } from "../../../shared/types"; -import { buildUnvalidatedEnsApiPublicConfig } from "../../config/deserialize"; +import { buildUnvalidatedEnsNodeStackInfo } from "../../../stack-info/deserialize/ensnode-stack-info"; import { type EnsApiIndexingStatusResponse, EnsApiIndexingStatusResponseCodes, @@ -33,12 +33,12 @@ function buildUnvalidatedEnsApiIndexingStatusResponse( return serializedResponse; } + const { realtimeProjection, stackInfo, ...rest } = serializedResponse; + return { - ...serializedResponse, - realtimeProjection: buildUnvalidatedRealtimeIndexingStatusProjection( - serializedResponse.realtimeProjection, - ), - ensApiPublicConfig: buildUnvalidatedEnsApiPublicConfig(serializedResponse.ensApiPublicConfig), + realtimeProjection: buildUnvalidatedRealtimeIndexingStatusProjection(realtimeProjection), + stackInfo: buildUnvalidatedEnsNodeStackInfo(stackInfo), + ...rest, }; } diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts index 5cd8092712..744a79311d 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts @@ -1,5 +1,5 @@ import type { RealtimeIndexingStatusProjection } from "../../../indexing-status/realtime-indexing-status-projection"; -import type { EnsApiPublicConfig } from "../../config"; +import type { EnsNodeStackInfo } from "../../../stack-info/ensnode-stack-info"; /** * A status code for indexing status responses. @@ -42,7 +42,7 @@ export type IndexingStatusResponseCode = EnsApiIndexingStatusResponseCode; export type EnsApiIndexingStatusResponseOk = { responseCode: typeof EnsApiIndexingStatusResponseCodes.Ok; realtimeProjection: RealtimeIndexingStatusProjection; - ensApiPublicConfig: EnsApiPublicConfig; + stackInfo: EnsNodeStackInfo; }; /** diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts index 1ddac600e0..bf2243e6fb 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts @@ -1,5 +1,5 @@ import { serializeRealtimeIndexingStatusProjection } from "../../../indexing-status/serialize/realtime-indexing-status-projection"; -import { serializeEnsApiPublicConfig } from "../../config/serialize"; +import { serializeEnsNodeStackInfo } from "../../../stack-info/serialize/ensnode-stack-info"; import { type EnsApiIndexingStatusResponse, EnsApiIndexingStatusResponseCodes, @@ -32,7 +32,7 @@ export function serializeEnsApiIndexingStatusResponse( return { responseCode: response.responseCode, realtimeProjection: serializeRealtimeIndexingStatusProjection(response.realtimeProjection), - ensApiPublicConfig: serializeEnsApiPublicConfig(response.ensApiPublicConfig), + stackInfo: serializeEnsNodeStackInfo(response.stackInfo), } satisfies SerializedEnsApiIndexingStatusResponseOk; case EnsApiIndexingStatusResponseCodes.Error: diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts index 939bb6324c..475173aed3 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts @@ -1,5 +1,5 @@ import type { SerializedRealtimeIndexingStatusProjection } from "../../../indexing-status/serialize/realtime-indexing-status-projection"; -import type { SerializedEnsApiPublicConfig } from "../../config/serialized-types"; +import type { SerializedEnsNodeStackInfo } from "../../../stack-info/serialize/ensnode-stack-info"; import type { EnsApiIndexingStatusResponse, EnsApiIndexingStatusResponseError, @@ -22,9 +22,9 @@ export type SerializedIndexingStatusResponseError = SerializedEnsApiIndexingStat * Serialized representation of {@link EnsApiIndexingStatusResponseOk}. */ export interface SerializedEnsApiIndexingStatusResponseOk - extends Omit { + extends Omit { realtimeProjection: SerializedRealtimeIndexingStatusProjection; - ensApiPublicConfig: SerializedEnsApiPublicConfig; + stackInfo: SerializedEnsNodeStackInfo; } /** diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts b/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts index 30ce6795cd..7adb03a98e 100644 --- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts +++ b/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts @@ -4,7 +4,10 @@ import { makeRealtimeIndexingStatusProjectionSchema, makeSerializedRealtimeIndexingStatusProjectionSchema, } from "../../../indexing-status/zod-schema/realtime-indexing-status-projection"; -import { makeEnsApiPublicConfigSchema, makeSerializedEnsApiPublicConfigSchema } from "../../config"; +import { + makeEnsNodeStackInfoSchema, + makeSerializedEnsNodeStackInfoSchema, +} from "../../../stack-info/zod-schemas/ensnode-stack-info"; import { type EnsApiIndexingStatusResponse, EnsApiIndexingStatusResponseCodes, @@ -25,7 +28,7 @@ export const makeEnsApiIndexingStatusResponseOkSchema = ( z.strictObject({ responseCode: z.literal(EnsApiIndexingStatusResponseCodes.Ok), realtimeProjection: makeRealtimeIndexingStatusProjectionSchema(valueLabel), - ensApiPublicConfig: makeEnsApiPublicConfigSchema(valueLabel), + stackInfo: makeEnsNodeStackInfoSchema(valueLabel), }); /** @@ -64,7 +67,7 @@ export const makeSerializedEnsApiIndexingStatusResponseOkSchema = ( z.object({ responseCode: z.literal(EnsApiIndexingStatusResponseCodes.Ok), realtimeProjection: makeSerializedRealtimeIndexingStatusProjectionSchema(valueLabel), - ensApiPublicConfig: makeSerializedEnsApiPublicConfigSchema(valueLabel), + stackInfo: makeSerializedEnsNodeStackInfoSchema(valueLabel), }); /** diff --git a/packages/ensnode-sdk/src/ensapi/client.test.ts b/packages/ensnode-sdk/src/ensapi/client.test.ts index 1460d054b9..4c84490d36 100644 --- a/packages/ensnode-sdk/src/ensapi/client.test.ts +++ b/packages/ensnode-sdk/src/ensapi/client.test.ts @@ -9,6 +9,7 @@ import { OmnichainIndexingStatusIds } from "../indexing-status/omnichain-indexin import type { SerializedOmnichainIndexingStatusSnapshotFollowing } from "../indexing-status/serialize/omnichain-indexing-status-snapshot"; import type { ResolverRecordsSelection } from "../resolution"; import { RangeTypeIds } from "../shared/blockrange"; +import type { SerializedEnsNodeStackInfo } from "../stack-info/serialize/ensnode-stack-info"; import { deserializeEnsApiIndexingStatusResponse } from "./api/indexing-status/deserialize"; import { type EnsApiIndexingStatusResponse, @@ -96,6 +97,15 @@ const EXAMPLE_CONFIG_RESPONSE = { }, } satisfies SerializedEnsApiPublicConfig; +const serializedStackInfo = { + ensApi: EXAMPLE_CONFIG_RESPONSE, + ensDb: { + postgreSqlVersion: "16", + }, + ensIndexer: EXAMPLE_CONFIG_RESPONSE.ensIndexerPublicConfig, + ensRainbow: EXAMPLE_CONFIG_RESPONSE.ensIndexerPublicConfig.ensRainbowPublicConfig, +} satisfies SerializedEnsNodeStackInfo; + const EXAMPLE_INDEXING_STATUS_BACKFILL_RESPONSE = deserializeEnsApiIndexingStatusResponse({ realtimeProjection: { projectedAt: 1755182604, @@ -140,7 +150,7 @@ const EXAMPLE_INDEXING_STATUS_BACKFILL_RESPONSE = deserializeEnsApiIndexingStatu }, }, }, - ensApiPublicConfig: EXAMPLE_CONFIG_RESPONSE, + stackInfo: serializedStackInfo, responseCode: EnsApiIndexingStatusResponseCodes.Ok, } satisfies SerializedEnsApiIndexingStatusResponseOk); @@ -198,7 +208,7 @@ const _EXAMPLE_INDEXING_STATUS_FOLLOWING_RESPONSE: EnsApiIndexingStatusResponse } satisfies SerializedOmnichainIndexingStatusSnapshotFollowing, }, }, - ensApiPublicConfig: EXAMPLE_CONFIG_RESPONSE, + stackInfo: serializedStackInfo, responseCode: EnsApiIndexingStatusResponseCodes.Ok, }); diff --git a/packages/ensnode-sdk/src/ensdb/config.ts b/packages/ensnode-sdk/src/ensdb/config.ts new file mode 100644 index 0000000000..8b046bc6db --- /dev/null +++ b/packages/ensnode-sdk/src/ensdb/config.ts @@ -0,0 +1,9 @@ +/** + * Complete public configuration object for ENSDb. + */ +export interface EnsDbPublicConfig { + /** + * Version of the PostgreSQL server hosting the ENSDb instance. + */ + postgreSqlVersion: string; +} diff --git a/packages/ensnode-sdk/src/ensdb/index.ts b/packages/ensnode-sdk/src/ensdb/index.ts new file mode 100644 index 0000000000..5c62e04f5e --- /dev/null +++ b/packages/ensnode-sdk/src/ensdb/index.ts @@ -0,0 +1 @@ +export * from "./config"; diff --git a/packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts b/packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts new file mode 100644 index 0000000000..da68a1030b --- /dev/null +++ b/packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts @@ -0,0 +1,14 @@ +import { z } from "zod/v4"; + +export const makeEnsDbPublicConfigSchema = (valueLabel?: string) => { + const label = valueLabel ?? "EnsDbPublicConfig"; + + return z + .object({ + postgreSqlVersion: z + .string() + .nonempty(`${label}.postgreSqlVersion must be a non-empty string`) + .describe("Version of the PostgreSQL server hosting the ENSDb instance."), + }) + .describe(label); +}; diff --git a/packages/ensnode-sdk/src/index.ts b/packages/ensnode-sdk/src/index.ts index ea5341ec7e..7491a05fd5 100644 --- a/packages/ensnode-sdk/src/index.ts +++ b/packages/ensnode-sdk/src/index.ts @@ -5,6 +5,7 @@ export { ENSNamespaceIds } from "@ensnode/datasources"; export * from "./ens"; export * from "./ensapi"; +export * from "./ensdb"; export * from "./ensindexer"; export * from "./ensrainbow"; export * from "./identity"; @@ -30,6 +31,7 @@ export * from "./shared/root-registry"; export * from "./shared/serialize"; export * from "./shared/types"; export * from "./shared/url"; +export * from "./stack-info"; export * from "./subgraph-api/prerequisites"; export * from "./tokenscope"; export * from "./tracing"; diff --git a/packages/ensnode-sdk/src/stack-info/deserialize/ensnode-stack-info.ts b/packages/ensnode-sdk/src/stack-info/deserialize/ensnode-stack-info.ts new file mode 100644 index 0000000000..07a2a87fa0 --- /dev/null +++ b/packages/ensnode-sdk/src/stack-info/deserialize/ensnode-stack-info.ts @@ -0,0 +1,49 @@ +import { prettifyError } from "zod/v4"; + +import { buildUnvalidatedEnsApiPublicConfig } from "../../ensapi/config/deserialize"; +import { buildUnvalidatedEnsIndexerPublicConfig } from "../../ensindexer/config/deserialize"; +import type { Unvalidated } from "../../shared/types"; +import type { EnsNodeStackInfo } from "../ensnode-stack-info"; +import type { SerializedEnsNodeStackInfo } from "../serialize/ensnode-stack-info"; +import { + makeEnsNodeStackInfoSchema, + makeSerializedEnsNodeStackInfoSchema, +} from "../zod-schemas/ensnode-stack-info"; + +/** + * Builds an unvalidated {@link EnsNodeStackInfo} object to be + * validated with {@link makeEnsNodeStackInfoSchema}. + * + * @param serializedStackInfo - The serialized stack info to build from. + * @return An unvalidated {@link EnsNodeStackInfo} object. + */ +export function buildUnvalidatedEnsNodeStackInfo( + serializedStackInfo: SerializedEnsNodeStackInfo, +): Unvalidated { + const { ensApi, ensIndexer, ...rest } = serializedStackInfo; + + return { + ...rest, + ensApi: buildUnvalidatedEnsApiPublicConfig(ensApi), + ensIndexer: buildUnvalidatedEnsIndexerPublicConfig(ensIndexer), + }; +} + +/** + * Deserialize value into {@link EnsNodeStackInfo} object. + */ +export function deserializeEnsNodeStackInfo( + maybeStackInfo: Unvalidated, + valueLabel?: string, +): EnsNodeStackInfo { + const parsed = makeSerializedEnsNodeStackInfoSchema(valueLabel) + .transform(buildUnvalidatedEnsNodeStackInfo) + .pipe(makeEnsNodeStackInfoSchema(valueLabel)) + .safeParse(maybeStackInfo); + + if (parsed.error) { + throw new Error(`Cannot deserialize EnsNodeStackInfo:\n${prettifyError(parsed.error)}\n`); + } + + return parsed.data; +} diff --git a/packages/ensnode-sdk/src/stack-info/ensnode-stack-info.ts b/packages/ensnode-sdk/src/stack-info/ensnode-stack-info.ts new file mode 100644 index 0000000000..36daa61487 --- /dev/null +++ b/packages/ensnode-sdk/src/stack-info/ensnode-stack-info.ts @@ -0,0 +1,47 @@ +import type { EnsApiPublicConfig } from "../ensapi/config/types"; +import type { EnsDbPublicConfig } from "../ensdb/config"; +import type { EnsIndexerPublicConfig } from "../ensindexer/config/types"; +import type { EnsRainbowPublicConfig } from "../ensrainbow/config"; + +/** + * Complete information about the ENSNode stack. + */ +export interface EnsNodeStackInfo { + /** + * ENSApi Public Config + */ + ensApi: EnsApiPublicConfig; + + /** + * ENSDb Public Config + */ + ensDb: EnsDbPublicConfig; + + /** + * ENSIndexer Public Config + */ + ensIndexer: EnsIndexerPublicConfig; + + /** + * ENSRainbow Public Config + * + * Note: ENSRainbow Public Config might not be available during cold starts. + */ + ensRainbow?: EnsRainbowPublicConfig; +} + +/** + * Build a complete {@link EnsNodeStackInfo} object from + * the given public configs of ENSApi and ENSDb. + */ +export function buildEnsNodeStackInfo( + ensApiPublicConfig: EnsApiPublicConfig, + ensDbPublicConfig: EnsDbPublicConfig, +): EnsNodeStackInfo { + return { + ensApi: ensApiPublicConfig, + ensDb: ensDbPublicConfig, + ensIndexer: ensApiPublicConfig.ensIndexerPublicConfig, + ensRainbow: ensApiPublicConfig.ensIndexerPublicConfig.ensRainbowPublicConfig, + }; +} diff --git a/packages/ensnode-sdk/src/stack-info/index.ts b/packages/ensnode-sdk/src/stack-info/index.ts new file mode 100644 index 0000000000..f3bf014196 --- /dev/null +++ b/packages/ensnode-sdk/src/stack-info/index.ts @@ -0,0 +1,3 @@ +export * from "./deserialize/ensnode-stack-info"; +export * from "./ensnode-stack-info"; +export * from "./serialize/ensnode-stack-info"; diff --git a/packages/ensnode-sdk/src/stack-info/serialize/ensnode-stack-info.ts b/packages/ensnode-sdk/src/stack-info/serialize/ensnode-stack-info.ts new file mode 100644 index 0000000000..31a5d979de --- /dev/null +++ b/packages/ensnode-sdk/src/stack-info/serialize/ensnode-stack-info.ts @@ -0,0 +1,26 @@ +import type { SerializedEnsApiPublicConfig } from "../../ensapi"; +import { serializeEnsApiPublicConfig } from "../../ensapi/config/serialize"; +import { serializeEnsIndexerPublicConfig } from "../../ensindexer"; +import type { SerializedEnsIndexerPublicConfig } from "../../ensindexer/config/serialized-types"; +import type { EnsNodeStackInfo } from "../ensnode-stack-info"; + +/** + * Serialized representation of {@link EnsNodeStackInfo}. + */ +export interface SerializedEnsNodeStackInfo + extends Omit { + ensApi: SerializedEnsApiPublicConfig; + ensIndexer: SerializedEnsIndexerPublicConfig; +} + +/** + * Serialize a {@link EnsNodeStackInfo} object. + */ +export function serializeEnsNodeStackInfo(stackInfo: EnsNodeStackInfo): SerializedEnsNodeStackInfo { + return { + ensApi: serializeEnsApiPublicConfig(stackInfo.ensApi), + ensDb: stackInfo.ensDb, + ensIndexer: serializeEnsIndexerPublicConfig(stackInfo.ensIndexer), + ensRainbow: stackInfo.ensRainbow, + }; +} diff --git a/packages/ensnode-sdk/src/stack-info/zod-schemas/ensnode-stack-info.ts b/packages/ensnode-sdk/src/stack-info/zod-schemas/ensnode-stack-info.ts new file mode 100644 index 0000000000..094a17d078 --- /dev/null +++ b/packages/ensnode-sdk/src/stack-info/zod-schemas/ensnode-stack-info.ts @@ -0,0 +1,34 @@ +import { z } from "zod/v4"; + +import { + makeEnsApiPublicConfigSchema, + makeSerializedEnsApiPublicConfigSchema, +} from "../../ensapi/config/zod-schemas"; +import { makeEnsDbPublicConfigSchema } from "../../ensdb/zod-schemas/config"; +import { + makeEnsIndexerPublicConfigSchema, + makeSerializedEnsIndexerPublicConfigSchema, +} from "../../ensindexer/config/zod-schemas"; +import { makeEnsRainbowPublicConfigSchema } from "../../ensrainbow/zod-schemas/config"; + +export function makeSerializedEnsNodeStackInfoSchema(valueLabel?: string) { + const label = valueLabel ?? "ENSNodeStackInfo"; + + return z.object({ + ensApi: makeSerializedEnsApiPublicConfigSchema(`${label}.ensApi`), + ensDb: makeEnsDbPublicConfigSchema(`${label}.ensDb`), + ensIndexer: makeSerializedEnsIndexerPublicConfigSchema(`${label}.ensIndexer`), + ensRainbow: makeEnsRainbowPublicConfigSchema(`${label}.ensRainbow`).optional(), + }); +} + +export function makeEnsNodeStackInfoSchema(valueLabel?: string) { + const label = valueLabel ?? "ENSNodeStackInfo"; + + return z.object({ + ensApi: makeEnsApiPublicConfigSchema(`${label}.ensApi`), + ensDb: makeEnsDbPublicConfigSchema(`${label}.ensDb`), + ensIndexer: makeEnsIndexerPublicConfigSchema(`${label}.ensIndexer`), + ensRainbow: makeEnsRainbowPublicConfigSchema(`${label}.ensRainbow`).optional(), + }); +} From ccc497ec77c7599afda825b698c728c0563fcc21 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 18:17:13 +0200 Subject: [PATCH 36/56] docs(changeset): Introduced `EnsNodeStackInfo` data model. --- .changeset/cute-news-begin.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cute-news-begin.md diff --git a/.changeset/cute-news-begin.md b/.changeset/cute-news-begin.md new file mode 100644 index 0000000000..f77e144298 --- /dev/null +++ b/.changeset/cute-news-begin.md @@ -0,0 +1,5 @@ +--- +"@ensnode/ensnode-sdk": minor +--- + +Introduced `EnsNodeStackInfo` data model. From cc131f494afa58164f748129bcda5ecddf3a43ed Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 18:31:19 +0200 Subject: [PATCH 37/56] Extend `EnsDbReader` class with `buildEnsDbPublicConfig()` method --- packages/ensdb-sdk/src/client/ensdb-reader.ts | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/packages/ensdb-sdk/src/client/ensdb-reader.ts b/packages/ensdb-sdk/src/client/ensdb-reader.ts index ac6b0b72e3..71b0dc6445 100644 --- a/packages/ensdb-sdk/src/client/ensdb-reader.ts +++ b/packages/ensdb-sdk/src/client/ensdb-reader.ts @@ -4,6 +4,7 @@ import { type CrossChainIndexingStatusSnapshot, deserializeCrossChainIndexingStatusSnapshot, deserializeEnsIndexerPublicConfig, + type EnsDbPublicConfig, type EnsIndexerPublicConfig, } from "@ensnode/ensnode-sdk"; @@ -156,6 +157,17 @@ export class EnsDbReader< return deserializeEnsIndexerPublicConfig(record); } + /** + * Build ENSDb Public Config + */ + async buildEnsDbPublicConfig(): Promise { + const postgreSqlVersion = await this.getPostgreSqlVersion(); + + return { + postgreSqlVersion, + }; + } + /** * Get Indexing Status Snapshot * @@ -208,4 +220,35 @@ export class EnsDbReader< `There must be exactly one ENSNodeMetadata record for ('${this.ensIndexerSchemaName}', '${metadata.key}') composite key`, ); } + + /** + * Get PostgreSQL version for the server hosting the ENSDb instance. + * + * @throws when the version cannot be retrieved or parsed from the query result. + */ + private async getPostgreSqlVersion(): Promise { + const result = await this.ensDb.execute<{ version: string }>("SELECT version();"); + + // result will be in the form of [{ version: "PostgreSQL 15.5 (Ubuntu 15.5-0ubuntu0.22.04.1) ..." }] + const versionString = result.rows[0]?.version; + + if (!versionString) { + throw new Error("Failed to get PostgreSQL version from ENSDb instance"); + } + + // extract the version number using regex + const match = versionString.match(/PostgreSQL (\d+\.\d+)/); + + if (!match) { + throw new Error(`Failed to parse PostgreSQL version from version string: '${versionString}'`); + } + + const parsedVersion = match[1]; + + if (typeof parsedVersion !== "string") { + throw new Error(`Parsed PostgreSQL version is not a string: '${parsedVersion}'`); + } + + return parsedVersion; + } } From 4658d538c2c9c9e0a750db4ecb371165b4f10451 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 18:33:51 +0200 Subject: [PATCH 38/56] Rename symbols exported from ENSNode React package --- .changeset/rare-dogs-sin.md | 4 +++- packages/ensnode-react/README.md | 16 ++++++++-------- packages/ensnode-react/src/provider.tsx | 8 +++++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.changeset/rare-dogs-sin.md b/.changeset/rare-dogs-sin.md index 9c7201403c..1b2bb0ff0d 100644 --- a/.changeset/rare-dogs-sin.md +++ b/.changeset/rare-dogs-sin.md @@ -2,4 +2,6 @@ "@ensnode/ensnode-react": minor --- -**Breaking**: Replaced `useENSNodeSDKConfig` hook with `useEnsNodeProviderOptions` hook. +**Breaking**: +- Replaced `useENSNodeSDKConfig` hook with `useEnsNodeProviderOptions` hook. +- Renamed `createEnsNodeOptions` function to `createEnsNodeProviderOptions`. diff --git a/packages/ensnode-react/README.md b/packages/ensnode-react/README.md index ebf842b5a7..e6d2dc34c1 100644 --- a/packages/ensnode-react/README.md +++ b/packages/ensnode-react/README.md @@ -19,9 +19,9 @@ Note: `@tanstack/react-query` is a peer dependency but you don't need to interac Wrap your app with the `EnsNodeProvider`: ```tsx -import { EnsNodeProvider, createEnsNodeOptions } from "@ensnode/ensnode-react"; +import { EnsNodeProvider, createEnsNodeProviderOptions } from "@ensnode/ensnode-react"; -const options = createEnsNodeOptions({ url: "https://api.alpha.ensnode.io" }); +const options = createEnsNodeProviderOptions({ url: "https://api.alpha.ensnode.io" }); function App() { return ( @@ -126,7 +126,7 @@ function DisplayPrimaryNames() { ### EnsNodeProvider -The provider component that supplies ENSApi options to all child components. +The provider component that supplies ENSApi Provider Options to all child components. ```tsx interface EnsNodeProviderProps { @@ -138,16 +138,16 @@ interface EnsNodeProviderProps { #### Props -- `options`: ENSApi options object +- `options`: ENSNode Provider Options object - `queryClient`: Optional TanStack Query client instance (requires manual QueryClientProvider setup) - `queryClientOptions`: Optional Custom options for auto-created QueryClient (only used when queryClient is not provided) -### createEnsNodeOptions +### createEnsNodeProviderOptions -Helper function to create ENSApi options with defaults. +Helper function to create ENSNode Provider Options with defaults. ```tsx -const options = createEnsNodeOptions({ +const options = createEnsNodeProviderOptions({ url: "https://api.alpha.ensnode.io", }); ``` @@ -230,7 +230,7 @@ const { data, isLoading, error, refetch } = usePrimaryNames({ ### Custom Query Configuration -The `EnsNodeProvider` automatically creates and manages a QueryClient for you. Cache keys include the ENSNode endpoint URL, so different endpoints (mainnet vs testnet) maintain separate caches. You can customize the QueryClient without importing TanStack Query: +The `EnsNodeProvider` automatically creates and manages a QueryClient for you. Cache keys include the ENSNode endpoint URL, so different ENSNode endpoints that may have different configurations (ex: mainnet vs sepolia) maintain separate caches. You can customize the QueryClient without importing TanStack Query: ```tsx // Simple setup - no TanStack Query knowledge needed diff --git a/packages/ensnode-react/src/provider.tsx b/packages/ensnode-react/src/provider.tsx index 4a2717acd1..a495875ee2 100644 --- a/packages/ensnode-react/src/provider.tsx +++ b/packages/ensnode-react/src/provider.tsx @@ -10,7 +10,7 @@ import { EnsNodeContext } from "./context"; import type { EnsNodeProviderOptions } from "./types"; export interface EnsNodeProviderProps { - /** ENSApi options */ + /** ENSNode Provider Options */ options: EnsNodeProviderOptions; /** @@ -89,9 +89,11 @@ export function EnsNodeProvider(parameters: React.PropsWithChildren Date: Fri, 17 Apr 2026 18:36:04 +0200 Subject: [PATCH 39/56] Return `stackInfo` field from Indexing Status API endpoint This field is set based on the middleware contexet from an immutable `stackInfoCache` instance. --- apps/ensapi/src/cache/stack-info.cache.ts | 69 +++++++ .../src/handlers/api/meta/status-api.ts | 10 +- apps/ensapi/src/lib/hono-factory.ts | 4 +- .../src/middleware/stack-info.middleware.ts | 35 ++++ docs/docs.ensnode.io/ensapi-openapi.json | 184 ++++++++++++++---- 5 files changed, 259 insertions(+), 43 deletions(-) create mode 100644 apps/ensapi/src/cache/stack-info.cache.ts create mode 100644 apps/ensapi/src/middleware/stack-info.middleware.ts diff --git a/apps/ensapi/src/cache/stack-info.cache.ts b/apps/ensapi/src/cache/stack-info.cache.ts new file mode 100644 index 0000000000..d95739521c --- /dev/null +++ b/apps/ensapi/src/cache/stack-info.cache.ts @@ -0,0 +1,69 @@ +import config from "@/config"; + +import { minutesToSeconds } from "date-fns"; + +import { + buildEnsNodeStackInfo, + type CachedResult, + type EnsNodeStackInfo, + SWRCache, +} from "@ensnode/ensnode-sdk"; + +import { buildEnsApiPublicConfig } from "@/config/config.schema"; +import { ensDbClient } from "@/lib/ensdb/singleton"; +import { lazyProxy } from "@/lib/lazy"; + +/** + * Loads the ENS Node stack info, either from cache if available, + * or by building it from the public configs of ENSApi and ENSDb. + * + * The ENSNode Stack Info object is considered immutable, so once + * it is successfully loaded, it will be cached indefinitely. + */ +async function loadEnsNodeStackInfo( + cachedResult?: CachedResult, +): Promise { + if (cachedResult && !(cachedResult.result instanceof Error)) { + return cachedResult.result; + } + + const ensApiPublicConfig = buildEnsApiPublicConfig(config); + const ensDbPublicConfig = await ensDbClient.buildEnsDbPublicConfig(); + + return buildEnsNodeStackInfo(ensApiPublicConfig, ensDbPublicConfig); +} + +// lazyProxy defers construction until first use so that this module can be +// imported without env vars being present (e.g. during OpenAPI generation). +// SWRCache with proactivelyInitialize:true starts background polling immediately +// on construction, which would trigger ensDbClient before env vars are available. +/** + * Cache for ENS Node stack info + * Once successfully loaded, the ENSNode Stack Info is cached indefinitely and + * never revalidated. This ensures the JSON is only fetched once during + * the application lifecycle. + * + * Configuration: + * - ttl: Infinity - Never expires once cached + * - errorTtl: 1 minute - If loading fails, retry on next access after 1 minute + * - proactiveRevalidationInterval: undefined - No proactive revalidation + * - proactivelyInitialize: true - Load immediately on startup + */ +export const stackInfoCache = lazyProxy( + () => + /** + * Cache for ENS Node stack info + * + * Once initialized successfully, this cache will always return + * the same stack info for the lifecycle of the ENSApi instance. + * + * If initialization fails, it will keep retrying on access until it succeeds, which is desirable because the stack info is critical for the functioning of the application and we want to recover from transient initialization failures without requiring a restart. + */ + new SWRCache({ + fn: loadEnsNodeStackInfo, + ttl: Number.POSITIVE_INFINITY, + errorTtl: minutesToSeconds(1), + proactiveRevalidationInterval: undefined, + proactivelyInitialize: true, + }), +); diff --git a/apps/ensapi/src/handlers/api/meta/status-api.ts b/apps/ensapi/src/handlers/api/meta/status-api.ts index 5d51069659..960755c28b 100644 --- a/apps/ensapi/src/handlers/api/meta/status-api.ts +++ b/apps/ensapi/src/handlers/api/meta/status-api.ts @@ -1,5 +1,3 @@ -import config from "@/config"; - import { EnsApiIndexingStatusResponseCodes, type EnsApiIndexingStatusResponseError, @@ -7,13 +5,13 @@ import { serializeEnsApiIndexingStatusResponse, } from "@ensnode/ensnode-sdk"; -import { buildEnsApiPublicConfig } from "@/config/config.schema"; import { createApp } from "@/lib/hono-factory"; import { indexingStatusMiddleware } from "@/middleware/indexing-status.middleware"; +import { stackInfoMiddleware } from "@/middleware/stack-info.middleware"; import { getIndexingStatusRoute } from "./status-api.routes"; -const app = createApp({ middlewares: [indexingStatusMiddleware] }); +const app = createApp({ middlewares: [stackInfoMiddleware, indexingStatusMiddleware] }); app.openapi(getIndexingStatusRoute, async (c) => { if (c.var.indexingStatus instanceof Error) { @@ -25,14 +23,12 @@ app.openapi(getIndexingStatusRoute, async (c) => { ); } - const ensApiPublicConfig = buildEnsApiPublicConfig(config); - // return successful response using the indexing status projection from the middleware context return c.json( serializeEnsApiIndexingStatusResponse({ responseCode: EnsApiIndexingStatusResponseCodes.Ok, realtimeProjection: c.var.indexingStatus, - ensApiPublicConfig: ensApiPublicConfig, + stackInfo: c.var.stackInfo, } satisfies EnsApiIndexingStatusResponseOk), 200, ); diff --git a/apps/ensapi/src/lib/hono-factory.ts b/apps/ensapi/src/lib/hono-factory.ts index c7a25cf001..c7d4f0012d 100644 --- a/apps/ensapi/src/lib/hono-factory.ts +++ b/apps/ensapi/src/lib/hono-factory.ts @@ -8,12 +8,14 @@ import type { IndexingStatusMiddlewareVariables } from "@/middleware/indexing-st import type { IsRealtimeMiddlewareVariables } from "@/middleware/is-realtime.middleware"; import type { ReferralLeaderboardEditionsCachesMiddlewareVariables } from "@/middleware/referral-leaderboard-editions-caches.middleware"; import type { ReferralProgramEditionConfigSetMiddlewareVariables } from "@/middleware/referral-program-edition-set.middleware"; +import type { StackInfoMiddlewareVariables } from "@/middleware/stack-info.middleware"; export type MiddlewareVariables = IndexingStatusMiddlewareVariables & IsRealtimeMiddlewareVariables & CanAccelerateMiddlewareVariables & ReferralProgramEditionConfigSetMiddlewareVariables & - ReferralLeaderboardEditionsCachesMiddlewareVariables; + ReferralLeaderboardEditionsCachesMiddlewareVariables & + StackInfoMiddlewareVariables; type AppEnv = { Variables: Partial }; diff --git a/apps/ensapi/src/middleware/stack-info.middleware.ts b/apps/ensapi/src/middleware/stack-info.middleware.ts new file mode 100644 index 0000000000..82ddb02712 --- /dev/null +++ b/apps/ensapi/src/middleware/stack-info.middleware.ts @@ -0,0 +1,35 @@ +import type { EnsNodeStackInfo } from "@ensnode/ensnode-sdk"; + +import { stackInfoCache } from "@/cache/stack-info.cache"; +import { factory, producing } from "@/lib/hono-factory"; +import logger from "@/lib/logger"; + +export interface StackInfoMiddlewareVariables { + /** + * ENSNode Stack Info + */ + stackInfo: EnsNodeStackInfo; +} + +/** + * Makes the ENSNode Stack Info available in the middleware context as `c.var.stackInfo`. + * + * If the stack info cannot be retrieved, for example, if ENSDb instance does not respond, + * we return a 503 Service Unavailable and an error message. + */ +export const stackInfoMiddleware = producing( + ["stackInfo"], + factory.createMiddleware(async (c, next) => { + const stackInfo = await stackInfoCache.read(); + + if (stackInfo instanceof Error) { + logger.error({ stackInfo }, "Failed to retrieve ENSNode Stack Info"); + + return c.json({ error: "Service Unavailable" }, 503); + } + + c.set("stackInfo", stackInfo); + + await next(); + }), +); diff --git a/docs/docs.ensnode.io/ensapi-openapi.json b/docs/docs.ensnode.io/ensapi-openapi.json index 8854fcba0b..db5c3b4616 100644 --- a/docs/docs.ensnode.io/ensapi-openapi.json +++ b/docs/docs.ensnode.io/ensapi-openapi.json @@ -779,10 +779,140 @@ }, "required": ["snapshot", "projectedAt", "worstCaseDistance"] }, - "ensApiPublicConfig": { + "stackInfo": { "type": "object", "properties": { - "ensIndexerPublicConfig": { + "ensApi": { + "type": "object", + "properties": { + "ensIndexerPublicConfig": { + "type": "object", + "properties": { + "ensIndexerSchemaName": { "type": "string", "minLength": 1 }, + "ensRainbowPublicConfig": { + "type": "object", + "properties": { + "version": { "type": "string", "minLength": 1 }, + "labelSet": { + "type": "object", + "properties": { + "labelSetId": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "pattern": "^[a-z-]+$" + }, + "highestLabelSetVersion": { "type": ["number", "null"] } + }, + "required": ["labelSetId", "highestLabelSetVersion"] + }, + "recordsCount": { "type": "integer", "minimum": 0 } + }, + "required": ["version", "labelSet", "recordsCount"] + }, + "indexedChainIds": { + "type": "array", + "items": { "type": "integer", "exclusiveMinimum": 0 }, + "minItems": 1 + }, + "isSubgraphCompatible": { "type": "boolean" }, + "labelSet": { + "type": "object", + "properties": { + "labelSetId": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "pattern": "^[a-z-]+$" + }, + "labelSetVersion": { "type": ["number", "null"] } + }, + "required": ["labelSetId", "labelSetVersion"] + }, + "namespace": { + "type": "string", + "enum": ["mainnet", "sepolia", "sepolia-v2", "ens-test-env"] + }, + "plugins": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "versionInfo": { + "type": "object", + "properties": { + "ponder": { "type": "string", "minLength": 1 }, + "ensDb": { "type": "string", "minLength": 1 }, + "ensIndexer": { "type": "string", "minLength": 1 }, + "ensNormalize": { "type": "string", "minLength": 1 } + }, + "required": ["ponder", "ensDb", "ensIndexer", "ensNormalize"] + } + }, + "required": [ + "ensIndexerSchemaName", + "ensRainbowPublicConfig", + "indexedChainIds", + "isSubgraphCompatible", + "labelSet", + "namespace", + "plugins", + "versionInfo" + ] + }, + "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 + } + ] + }, + "versionInfo": { + "type": "object", + "properties": { + "ensApi": { "type": "string", "minLength": 1 }, + "ensNormalize": { "type": "string", "minLength": 1 } + }, + "required": ["ensApi", "ensNormalize"] + } + }, + "required": ["ensIndexerPublicConfig", "theGraphFallback", "versionInfo"] + }, + "ensDb": { + "type": "object", + "properties": { + "postgreSqlVersion": { + "type": "string", + "minLength": 1, + "description": "Version of the PostgreSQL server hosting the ENSDb instance." + } + }, + "required": ["postgreSqlVersion"], + "description": "Serialized Indexing Status Response OK.ensDb" + }, + "ensIndexer": { "type": "object", "properties": { "ensIndexerSchemaName": { "type": "string", "minLength": 1 }, @@ -857,48 +987,32 @@ "versionInfo" ] }, - "theGraphFallback": { - "oneOf": [ - { - "type": "object", - "properties": { - "canFallback": { "type": "boolean", "enum": [true] }, - "url": { "type": "string" } - }, - "required": ["canFallback", "url"], - "additionalProperties": false - }, - { + "ensRainbow": { + "type": "object", + "properties": { + "version": { "type": "string", "minLength": 1 }, + "labelSet": { "type": "object", "properties": { - "canFallback": { "type": "boolean", "enum": [false] }, - "reason": { + "labelSetId": { "type": "string", - "enum": [ - "not-subgraph-compatible", - "no-api-key", - "no-subgraph-url" - ] - } + "minLength": 1, + "maxLength": 50, + "pattern": "^[a-z-]+$" + }, + "highestLabelSetVersion": { "type": ["number", "null"] } }, - "required": ["canFallback", "reason"], - "additionalProperties": false - } - ] - }, - "versionInfo": { - "type": "object", - "properties": { - "ensApi": { "type": "string", "minLength": 1 }, - "ensNormalize": { "type": "string", "minLength": 1 } + "required": ["labelSetId", "highestLabelSetVersion"] + }, + "recordsCount": { "type": "integer", "minimum": 0 } }, - "required": ["ensApi", "ensNormalize"] + "required": ["version", "labelSet", "recordsCount"] } }, - "required": ["ensIndexerPublicConfig", "theGraphFallback", "versionInfo"] + "required": ["ensApi", "ensDb", "ensIndexer"] } }, - "required": ["responseCode", "realtimeProjection", "ensApiPublicConfig"] + "required": ["responseCode", "realtimeProjection", "stackInfo"] } } } From 774a1055ccbd71dbc47ec13a8897b76d79396bd9 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 18:37:50 +0200 Subject: [PATCH 40/56] Replace `useEnsApiPublicConfig` hook with `useEnsNodeStackInfo` hook --- .../config/use-ens-api-public-config.ts | 29 ------------------- .../stack-info/use-ensnode-stack-info.ts | 17 +++++++++++ 2 files changed, 17 insertions(+), 29 deletions(-) delete mode 100644 apps/ensadmin/src/components/config/use-ens-api-public-config.ts create mode 100644 apps/ensadmin/src/components/stack-info/use-ensnode-stack-info.ts diff --git a/apps/ensadmin/src/components/config/use-ens-api-public-config.ts b/apps/ensadmin/src/components/config/use-ens-api-public-config.ts deleted file mode 100644 index 53edd46983..0000000000 --- a/apps/ensadmin/src/components/config/use-ens-api-public-config.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; - -import { useEnsNodeProviderOptions } from "@ensnode/ensnode-react"; - -import { useIndexingStatusWithSwr } from "@/components/indexing-status"; - -/** - * Use the ENSApi Public Config for the currently selected connection. - * - * This hook combines the logic of fetching the indexing status (which includes - * the ENSApi public config) with React Query to provide an easy way to access - * the ENSApi config for the currently selected connection. - */ -export function useEnsApiPublicConfig() { - const EnsNodeProviderOptions = useEnsNodeProviderOptions(); - const indexingStatus = useIndexingStatusWithSwr(); - - return useQuery({ - enabled: indexingStatus.isFetched, - queryKey: ["swr", EnsNodeProviderOptions.client.url.href, "ensApiPublicConfig"], - queryFn: async () => { - if (!indexingStatus.data) { - throw new Error("Indexing status wasn't fetched successfully"); - } - - return indexingStatus.data.ensApiPublicConfig; - }, - }); -} diff --git a/apps/ensadmin/src/components/stack-info/use-ensnode-stack-info.ts b/apps/ensadmin/src/components/stack-info/use-ensnode-stack-info.ts new file mode 100644 index 0000000000..2723e0a499 --- /dev/null +++ b/apps/ensadmin/src/components/stack-info/use-ensnode-stack-info.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; + +import { useEnsNodeProviderOptions } from "@ensnode/ensnode-react"; + +import { useIndexingStatusWithSwr } from "@/components/indexing-status"; + +/** + * Use the ENSNode Stack Info for the currently selected connection. + * + * This is a convenience hook that abstracts away the details of + * extracting the ENSNode Stack Info from the Indexing Status query. + */ +export function useEnsNodeStackInfo() { + const indexingStatusSwr = useIndexingStatusWithSwr(); + + return { ...indexingStatusSwr, data: indexingStatusSwr.data?.stackInfo }; +} From ac94d4204bf890a4034f7768926056231a866670 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 18:41:27 +0200 Subject: [PATCH 41/56] Rename `ensApiPublicConfig` field integrations with `stackInfo` field --- .../connection/cards/ensnode-info.tsx | 50 +++++++++++-------- .../connections/require-active-connection.tsx | 13 +++-- .../use-indexing-status-with-swr.ts | 9 ++-- .../providers/selected-ensnode-provider.tsx | 8 +-- .../active/use-active-ensnode-config.tsx | 4 +- .../src/hooks/active/use-active-namespace.ts | 2 +- .../hooks/active/use-ensadmin-features.tsx | 10 ++-- .../ensadmin/src/hooks/async/use-namespace.ts | 6 +-- 8 files changed, 55 insertions(+), 47 deletions(-) diff --git a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx index 7b81f2b5ac..202d64bb44 100644 --- a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx +++ b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx @@ -9,9 +9,8 @@ import { ChainIcon, getChainName } from "@namehash/namehash-ui"; import { History, Replace } from "lucide-react"; import { Fragment, ReactNode } from "react"; -import { type EnsApiPublicConfig, getENSRootChainId } from "@ensnode/ensnode-sdk"; +import { EnsNodeStackInfo, getENSRootChainId } from "@ensnode/ensnode-sdk"; -import { useEnsApiPublicConfig } from "@/components/config/use-ens-api-public-config"; import { ErrorInfo, type ErrorInfoProps } from "@/components/error-info"; import { ENSApiIcon } from "@/components/icons/ensnode-apps/ensapi-icon"; import { ENSDbIcon } from "@/components/icons/ensnode-apps/ensdb-icon"; @@ -22,6 +21,7 @@ import { IconGraphNetwork } from "@/components/icons/graph-network"; import { HealIcon } from "@/components/icons/HealIcon"; import { IndexAdditionalRecordsIcon } from "@/components/icons/IndexAdditionalRecordsIcon"; import { ExternalLinkWithIcon } from "@/components/link"; +import { useEnsNodeStackInfo } from "@/components/stack-info/use-ensnode-stack-info"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; @@ -96,16 +96,16 @@ function ENSNodeCardLoadingSkeleton() { * Props for ENSNodeConfigCardDisplay - display component that accepts props for testing/mocking */ export interface ENSNodeConfigCardDisplayProps { - ensApiPublicConfig: EnsApiPublicConfig; + ensNodeStackInfo: EnsNodeStackInfo; } /** * Display component that receives props - used for reusable/mockable presentation */ -export function ENSNodeConfigCardDisplay({ ensApiPublicConfig }: ENSNodeConfigCardDisplayProps) { +export function ENSNodeConfigCardDisplay({ ensNodeStackInfo }: ENSNodeConfigCardDisplayProps) { return ( - + ); } @@ -114,7 +114,7 @@ export function ENSNodeConfigCardDisplay({ ensApiPublicConfig }: ENSNodeConfigCa * Props for ENSNodeConfigInfoView - internal component that accepts props for testing/mocking */ export interface ENSNodeConfigInfoViewProps { - ensApiPublicConfig?: EnsApiPublicConfig; + ensNodeStackInfo?: EnsNodeStackInfo; error?: ErrorInfoProps; isLoading?: boolean; } @@ -123,7 +123,7 @@ export interface ENSNodeConfigInfoViewProps { * Internal view component that accepts props - used by both the main component and mock pages */ export function ENSNodeConfigInfoView({ - ensApiPublicConfig, + ensNodeStackInfo, error, isLoading = false, }: ENSNodeConfigInfoViewProps) { @@ -132,7 +132,7 @@ export function ENSNodeConfigInfoView({ } // Show ENSNode card - shell with skeleton while loading, or content when ready - if (isLoading || !ensApiPublicConfig) { + if (isLoading || !ensNodeStackInfo) { return ( @@ -140,41 +140,42 @@ export function ENSNodeConfigInfoView({ ); } - return ; + return ; } /** * ENSNodeConfigInfo component - fetches and displays ENSNode configuration data */ export function ENSNodeConfigInfo() { - const ensApiConfig = useEnsApiPublicConfig(); + const ensNodeStackInfo = useEnsNodeStackInfo(); - if (ensApiConfig.isError) { + if (ensNodeStackInfo.isError) { return ( ); } - if (ensApiConfig.isPending) { + if (ensNodeStackInfo.isPending) { return ; } - return ; + return ; } -function ENSNodeConfigCardContent({ - ensApiPublicConfig, -}: { - ensApiPublicConfig: EnsApiPublicConfig; -}) { +function ENSNodeConfigCardContent({ ensNodeStackInfo }: { ensNodeStackInfo: EnsNodeStackInfo }) { const cardItemValueStyles = "text-sm leading-6 font-normal text-black"; - const { ensIndexerPublicConfig } = ensApiPublicConfig; + const { + ensApi: ensApiPublicConfig, + ensIndexer: ensIndexerPublicConfig, + ensDb: ensDbPublicConfig, + ensRainbow: ensRainbowPublicConfig, + } = ensNodeStackInfo; const healReverseAddressesActivated = !ensIndexerPublicConfig.isSubgraphCompatible; const indexAdditionalRecordsActivated = !ensIndexerPublicConfig.isSubgraphCompatible; @@ -401,7 +402,12 @@ function ENSNodeConfigCardContent({ docsLink={new URL("https://ensnode.io/ensdb")} > - Postgres

} /> + Postgres {ensDbPublicConfig.postgreSqlVersion}

+ } + /> ; + if (ensNodeStackInfo.status === "pending") return ; - if (ensApiConfig.status === "error") { + if (ensNodeStackInfo.status === "error") { return (
- +
); } diff --git a/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts b/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts index 9ad6d359a1..978391aa5c 100644 --- a/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts +++ b/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts @@ -20,6 +20,7 @@ import { EnsApiIndexingStatusResponseCodes, EnsApiIndexingStatusResponseOk, EnsApiPublicConfig, + EnsNodeStackInfo, } from "@ensnode/ensnode-sdk"; const DEFAULT_REFETCH_INTERVAL = secondsToMilliseconds(10); @@ -36,9 +37,9 @@ interface CacheableIndexingStatus { crossChainIndexingStatusSnapshot: CrossChainIndexingStatusSnapshotOmnichain; /** - * The Public Config for the connected ENSApi. + * Stack info of the connected ENSNode. */ - ensApiPublicConfig: EnsApiPublicConfig; + stackInfo: EnsNodeStackInfo; } interface UseIndexingStatusParameters @@ -80,7 +81,7 @@ export function useIndexingStatusWithSwr( // - Return this non-null value. return { crossChainIndexingStatusSnapshot: response.realtimeProjection.snapshot, - ensApiPublicConfig: response.ensApiPublicConfig, + stackInfo: response.stackInfo, } satisfies CacheableIndexingStatus; }), [queryOptions.queryFn], @@ -101,7 +102,7 @@ export function useIndexingStatusWithSwr( return { responseCode: EnsApiIndexingStatusResponseCodes.Ok, realtimeProjection, - ensApiPublicConfig: cachedResult.ensApiPublicConfig, + stackInfo: cachedResult.stackInfo, } satisfies EnsApiIndexingStatusResponseOk; }, [now], diff --git a/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx b/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx index 7f71927ce1..b5277ca460 100644 --- a/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx +++ b/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx @@ -2,7 +2,7 @@ import { type PropsWithChildren, useMemo } from "react"; -import { createEnsNodeOptions, EnsNodeProvider } from "@ensnode/ensnode-react"; +import { createEnsNodeProviderOptions, EnsNodeProvider } from "@ensnode/ensnode-react"; import { useSelectedConnection } from "@/hooks/active/use-selected-connection"; @@ -12,8 +12,8 @@ import { useSelectedConnection } from "@/hooks/active/use-selected-connection"; * * This component wraps the EnsNodeProvider from @ensnode/ensnode-react and * automatically configures it with the URL from the currently selected ENSNode - * connection URL. It serves as a bridge between the connection management - * system and the ENSNode React hooks. + * connection URL. It serves as a bridge between the ENSAdmin connection + * management system and the ENSNode React hooks. * * @param children - React children to render within the provider context */ @@ -25,7 +25,7 @@ export function SelectedEnsNodeProvider({ children }: PropsWithChildren) { return undefined; } - return createEnsNodeOptions({ + return createEnsNodeProviderOptions({ url: selectedConnection.validatedSelectedConnection.url, }); }, [selectedConnection.validatedSelectedConnection]); diff --git a/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx b/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx index 893f34041f..36fa826937 100644 --- a/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx +++ b/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEnsApiPublicConfig } from "@/components/config/use-ens-api-public-config"; +import { useEnsNodeStackInfo } from "@/components/stack-info/use-ensnode-stack-info"; /** * Hook to get the currently active ENSNode Config synchronously. @@ -16,7 +16,7 @@ import { useEnsApiPublicConfig } from "@/components/config/use-ens-api-public-co * @throws Error if no active ENSNode connection is available */ export function useActiveENSNodeConfig() { - const { data } = useEnsApiPublicConfig(); + const { data } = useEnsNodeStackInfo(); if (data === undefined) { throw new Error(`Invariant(useActiveENSNodeConfig): Expected an active ENSNode Config`); diff --git a/apps/ensadmin/src/hooks/active/use-active-namespace.ts b/apps/ensadmin/src/hooks/active/use-active-namespace.ts index 5dab6e5fe7..1d842d5f99 100644 --- a/apps/ensadmin/src/hooks/active/use-active-namespace.ts +++ b/apps/ensadmin/src/hooks/active/use-active-namespace.ts @@ -13,4 +13,4 @@ import { useActiveENSNodeConfig } from "./use-active-ensnode-config"; * @returns The namespace from the active ENSNode configuration * @throws Error if no active ENSNode Config is available */ -export const useActiveNamespace = () => useActiveENSNodeConfig().ensIndexerPublicConfig.namespace; +export const useActiveNamespace = () => useActiveENSNodeConfig().ensIndexer.namespace; diff --git a/apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx b/apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx index c7113d8ca2..7d6ec90c9c 100644 --- a/apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx +++ b/apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx @@ -59,10 +59,8 @@ export function useENSAdminFeatures(): ENSAdminFeatures { if (indexingStatusQuery.status === "error") return INDEXING_STATUS_ERROR_STATUS; if (indexingStatusQuery.status === "pending") return CONNECTING_STATUS; - const { ensApiPublicConfig } = indexingStatusQuery.data; - const configSupportResult = hasRegistrarActionsConfigSupport( - ensApiPublicConfig.ensIndexerPublicConfig, - ); + const { ensIndexer: ensIndexerPublicConfig } = indexingStatusQuery.data.stackInfo; + const configSupportResult = hasRegistrarActionsConfigSupport(ensIndexerPublicConfig); if (!configSupportResult.supported) return prerequisiteResultToFeatureStatus(configSupportResult); @@ -81,7 +79,7 @@ export function useENSAdminFeatures(): ENSAdminFeatures { if (indexingStatusQuery.status === "error") return INDEXING_STATUS_ERROR_STATUS; if (indexingStatusQuery.status === "pending") return CONNECTING_STATUS; - const { ensIndexerPublicConfig } = indexingStatusQuery.data.ensApiPublicConfig; + const { ensIndexer: ensIndexerPublicConfig } = indexingStatusQuery.data.stackInfo; return prerequisiteResultToFeatureStatus(hasSubgraphApiConfigSupport(ensIndexerPublicConfig)); }, [indexingStatusQuery]); @@ -89,7 +87,7 @@ export function useENSAdminFeatures(): ENSAdminFeatures { if (indexingStatusQuery.status === "error") return INDEXING_STATUS_ERROR_STATUS; if (indexingStatusQuery.status === "pending") return CONNECTING_STATUS; - const { ensIndexerPublicConfig } = indexingStatusQuery.data.ensApiPublicConfig; + const { ensIndexer: ensIndexerPublicConfig } = indexingStatusQuery.data.stackInfo; return prerequisiteResultToFeatureStatus(hasOmnigraphApiConfigSupport(ensIndexerPublicConfig)); }, [indexingStatusQuery]); diff --git a/apps/ensadmin/src/hooks/async/use-namespace.ts b/apps/ensadmin/src/hooks/async/use-namespace.ts index 17ec932ada..2d920ef18e 100644 --- a/apps/ensadmin/src/hooks/async/use-namespace.ts +++ b/apps/ensadmin/src/hooks/async/use-namespace.ts @@ -1,4 +1,4 @@ -import { useEnsApiPublicConfig } from "@/components/config/use-ens-api-public-config"; +import { useEnsNodeStackInfo } from "@/components/stack-info/use-ensnode-stack-info"; /** * Hook to get the namespace ID from the active ENSNode connection. @@ -22,10 +22,10 @@ import { useEnsApiPublicConfig } from "@/components/config/use-ens-api-public-co * ``` */ export function useNamespace() { - const query = useEnsApiPublicConfig(); + const query = useEnsNodeStackInfo(); return { ...query, - data: query.data?.ensIndexerPublicConfig.namespace ?? null, + data: query.data?.ensIndexer.namespace ?? null, }; } From 8086ad946ca4098ec86de44fe585574157705907 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 18:41:45 +0200 Subject: [PATCH 42/56] Update mocks --- .../src/app/mock/config-info/page.tsx | 14 ++++++++--- .../src/app/mock/indexing-stats/page.tsx | 8 +++---- .../src/app/mock/indexing-status-api.mock.ts | 23 +++++++++++++------ 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/apps/ensadmin/src/app/mock/config-info/page.tsx b/apps/ensadmin/src/app/mock/config-info/page.tsx index be3d5e14c8..5724789e23 100644 --- a/apps/ensadmin/src/app/mock/config-info/page.tsx +++ b/apps/ensadmin/src/app/mock/config-info/page.tsx @@ -2,7 +2,11 @@ import { useMemo, useState } from "react"; -import { deserializeENSApiPublicConfig, SerializedENSApiPublicConfig } from "@ensnode/ensnode-sdk"; +import { + buildEnsNodeStackInfo, + deserializeENSApiPublicConfig, + SerializedENSApiPublicConfig, +} from "@ensnode/ensnode-sdk"; import { ENSNodeConfigInfoView, @@ -36,8 +40,12 @@ export default function MockConfigPage() { default: try { - const config = deserializeENSApiPublicConfig(mockConfigData[selectedConfig]); - return { ensApiPublicConfig: config }; + const ensApiPublicConfig = deserializeENSApiPublicConfig(mockConfigData[selectedConfig]); + return { + ensNodeStackInfo: buildEnsNodeStackInfo(ensApiPublicConfig, { + postgreSqlVersion: "18.1", + }), + } satisfies ENSNodeConfigInfoViewProps; } catch (error) { const errorMessage = error instanceof Error diff --git a/apps/ensadmin/src/app/mock/indexing-stats/page.tsx b/apps/ensadmin/src/app/mock/indexing-stats/page.tsx index 5e79132945..6f68cb01cf 100644 --- a/apps/ensadmin/src/app/mock/indexing-stats/page.tsx +++ b/apps/ensadmin/src/app/mock/indexing-stats/page.tsx @@ -76,14 +76,14 @@ export default function MockIndexingStatusPage() { const mockedIndexingStatus = useQuery({ queryKey: ["mock", "useIndexingStatus", selectedVariant], queryFn: () => fetchMockedIndexingStatus(selectedVariant), - select: (response) => { + select: ({ responseCode, realtimeProjection, stackInfo }) => { return { - responseCode: IndexingStatusResponseCodes.Ok, + responseCode, realtimeProjection: createRealtimeIndexingStatusProjection( - response.realtimeProjection.snapshot, + realtimeProjection.snapshot, now, ), - ensApiPublicConfig: response.ensApiPublicConfig, + stackInfo, } satisfies IndexingStatusResponseOk; }, retry: false, // allows loading error to be observed immediately diff --git a/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts b/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts index 0fb2051230..da399eb25a 100644 --- a/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts +++ b/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts @@ -2,7 +2,6 @@ import { ChainIndexingStatusIds, CrossChainIndexingStrategyIds, deserializeEnsApiIndexingStatusResponse, - deserializeIndexingStatusResponse, EnsApiIndexingStatusResponseOk, IndexingStatusResponseCodes, type IndexingStatusResponseError, @@ -13,8 +12,9 @@ import { type SerializedChainIndexingStatusSnapshotCompleted, type SerializedChainIndexingStatusSnapshotFollowing, type SerializedChainIndexingStatusSnapshotQueued, - SerializedEnsApiPublicConfig, - SerializedEnsIndexerPublicConfig, + type SerializedEnsApiPublicConfig, + type SerializedEnsIndexerPublicConfig, + type SerializedEnsNodeStackInfo, type SerializedOmnichainIndexingStatusSnapshotBackfill, type SerializedOmnichainIndexingStatusSnapshotCompleted, type SerializedOmnichainIndexingStatusSnapshotFollowing, @@ -67,6 +67,15 @@ export const serializedEnsApiPublicConfig = { }, } satisfies SerializedEnsApiPublicConfig; +const serializedStackInfo = { + ensApi: serializedEnsApiPublicConfig, + ensDb: { + postgreSqlVersion: "16", + }, + ensIndexer: serializedEnsIndexerPublicConfig, + ensRainbow: serializedEnsIndexerPublicConfig.ensRainbowPublicConfig, +} satisfies SerializedEnsNodeStackInfo; + export const indexingStatusResponseError: IndexingStatusResponseError = { responseCode: IndexingStatusResponseCodes.Error, }; @@ -134,7 +143,7 @@ export const indexingStatusResponseOkOmnichain: Record< } satisfies SerializedOmnichainIndexingStatusSnapshotUnstarted, }, }, - ensApiPublicConfig: serializedEnsApiPublicConfig, + stackInfo: serializedStackInfo, }), [OmnichainIndexingStatusIds.Backfill]: deserializeEnsApiIndexingStatusResponse({ @@ -213,7 +222,7 @@ export const indexingStatusResponseOkOmnichain: Record< } satisfies SerializedOmnichainIndexingStatusSnapshotBackfill, }, }, - ensApiPublicConfig: serializedEnsApiPublicConfig, + stackInfo: serializedStackInfo, }), [OmnichainIndexingStatusIds.Following]: deserializeEnsApiIndexingStatusResponse({ @@ -307,7 +316,7 @@ export const indexingStatusResponseOkOmnichain: Record< } satisfies SerializedOmnichainIndexingStatusSnapshotFollowing, }, }, - ensApiPublicConfig: serializedEnsApiPublicConfig, + stackInfo: serializedStackInfo, }), [OmnichainIndexingStatusIds.Completed]: deserializeEnsApiIndexingStatusResponse({ @@ -345,6 +354,6 @@ export const indexingStatusResponseOkOmnichain: Record< } satisfies SerializedOmnichainIndexingStatusSnapshotCompleted, }, }, - ensApiPublicConfig: serializedEnsApiPublicConfig, + stackInfo: serializedStackInfo, }), }; From 3ca4b1a5384d27a1bbe3bc0ee1ea033acb13866a Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 18:41:55 +0200 Subject: [PATCH 43/56] Update changesets --- .changeset/fancy-mails-fall.md | 2 +- .changeset/silent-adults-rest.md | 2 +- .changeset/thick-horses-prove.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/fancy-mails-fall.md b/.changeset/fancy-mails-fall.md index c638cc3c15..f8dc4d4373 100644 --- a/.changeset/fancy-mails-fall.md +++ b/.changeset/fancy-mails-fall.md @@ -2,4 +2,4 @@ "@ensnode/ensnode-sdk": minor --- -Replaced the `EnsApiConfigResponse` data model by adding `ensApiPublicConfig` field to the `EnsApiIndexingStatusResponseOk` data model. +Replaced the `EnsApiConfigResponse` data model by adding `stackInfo` field to the `EnsApiIndexingStatusResponseOk` data model. diff --git a/.changeset/silent-adults-rest.md b/.changeset/silent-adults-rest.md index b8d894aabe..295d65a590 100644 --- a/.changeset/silent-adults-rest.md +++ b/.changeset/silent-adults-rest.md @@ -2,4 +2,4 @@ "ensadmin": minor --- -Replaced the `useENSNodeConfig` hook with `useEnsApiPublicConfig` hook. The `useEnsApiPublicConfig` hook leverages the updated data model returned from the `useIndexingStatusWithSwr` hook. +Replaced the `useENSNodeConfig` hook with `useEnsNodeStackInfo` hook. The `useEnsNodeStackInfo` hook leverages the updated data model returned from the `useIndexingStatusWithSwr` hook. diff --git a/.changeset/thick-horses-prove.md b/.changeset/thick-horses-prove.md index 49ab86e094..e2c82b3b03 100644 --- a/.changeset/thick-horses-prove.md +++ b/.changeset/thick-horses-prove.md @@ -2,4 +2,4 @@ "ensapi": minor --- -**Breaking**: Removed Config API endpoint at `GET /api/config`. To get the ENSApi Public Config, call the `GET /api/indexing-status` endpoint and reference the `ensApiPublicConfig` field in the OK response. +**Breaking**: Removed Config API endpoint at `GET /api/config`. To get the ENSApi Public Config, call the `GET /api/indexing-status` endpoint and reference the `stackInfo.ensApi` field in the OK response. From 3aad5fdf31b80c82a8511ea8c551ce324a6ceb31 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 18:47:57 +0200 Subject: [PATCH 44/56] Move `useEnsNodeStackInfo` hook into the hooks dir --- apps/ensadmin/src/components/connection/cards/ensnode-info.tsx | 2 +- .../src/components/connections/require-active-connection.tsx | 2 +- apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx | 2 +- apps/ensadmin/src/hooks/async/use-namespace.ts | 2 +- .../{components/stack-info => hooks}/use-ensnode-stack-info.ts | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename apps/ensadmin/src/{components/stack-info => hooks}/use-ensnode-stack-info.ts (100%) diff --git a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx index 202d64bb44..4762b23f7d 100644 --- a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx +++ b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx @@ -21,10 +21,10 @@ import { IconGraphNetwork } from "@/components/icons/graph-network"; import { HealIcon } from "@/components/icons/HealIcon"; import { IndexAdditionalRecordsIcon } from "@/components/icons/IndexAdditionalRecordsIcon"; import { ExternalLinkWithIcon } from "@/components/link"; -import { useEnsNodeStackInfo } from "@/components/stack-info/use-ensnode-stack-info"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { useEnsNodeStackInfo } from "@/hooks/use-ensnode-stack-info"; import { cn } from "@/lib/utils"; import { diff --git a/apps/ensadmin/src/components/connections/require-active-connection.tsx b/apps/ensadmin/src/components/connections/require-active-connection.tsx index 5021ccbed3..5e1c522203 100644 --- a/apps/ensadmin/src/components/connections/require-active-connection.tsx +++ b/apps/ensadmin/src/components/connections/require-active-connection.tsx @@ -4,7 +4,7 @@ import type { PropsWithChildren } from "react"; import { ErrorInfo } from "@/components/error-info"; import { LoadingSpinner } from "@/components/loading-spinner"; -import { useEnsNodeStackInfo } from "@/components/stack-info/use-ensnode-stack-info"; +import { useEnsNodeStackInfo } from "@/hooks/use-ensnode-stack-info"; /** * Allows consumers to use `useActiveConnection` by blocking rendering until it is available. diff --git a/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx b/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx index 36fa826937..879e1269aa 100644 --- a/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx +++ b/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEnsNodeStackInfo } from "@/components/stack-info/use-ensnode-stack-info"; +import { useEnsNodeStackInfo } from "@/hooks/use-ensnode-stack-info"; /** * Hook to get the currently active ENSNode Config synchronously. diff --git a/apps/ensadmin/src/hooks/async/use-namespace.ts b/apps/ensadmin/src/hooks/async/use-namespace.ts index 2d920ef18e..1f2c658927 100644 --- a/apps/ensadmin/src/hooks/async/use-namespace.ts +++ b/apps/ensadmin/src/hooks/async/use-namespace.ts @@ -1,4 +1,4 @@ -import { useEnsNodeStackInfo } from "@/components/stack-info/use-ensnode-stack-info"; +import { useEnsNodeStackInfo } from "@/hooks/use-ensnode-stack-info"; /** * Hook to get the namespace ID from the active ENSNode connection. diff --git a/apps/ensadmin/src/components/stack-info/use-ensnode-stack-info.ts b/apps/ensadmin/src/hooks/use-ensnode-stack-info.ts similarity index 100% rename from apps/ensadmin/src/components/stack-info/use-ensnode-stack-info.ts rename to apps/ensadmin/src/hooks/use-ensnode-stack-info.ts From 8911aace0ba574553f0be1cd0353e3d2513cccb5 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 19:12:46 +0200 Subject: [PATCH 45/56] Rename `EnsApiClient*` symbols to `EnsNodeClient*` This consolidates the branding improvements for boosting the ENSNode brand and avoiding mixing it up with ENSApi term which is primarliy used to reference an app within the ENSNode stack. --- .changeset/common-lines-smell.md | 2 +- .changeset/cool-hotels-dress.md | 2 +- .changeset/two-rice-flash.md | 2 +- .../ensrainbow/concepts/unknown-labels.mdx | 2 +- packages/ensnode-react/src/provider.tsx | 6 +-- packages/ensnode-react/src/types.ts | 4 +- packages/ensnode-react/src/utils/query.ts | 14 +++--- packages/ensnode-sdk/README.md | 10 ++--- packages/ensnode-sdk/src/ensapi/index.ts | 8 ---- .../src/{ensapi => ensnode}/api/index.ts | 0 .../api/indexing-status/deserialize.ts | 0 .../api/indexing-status/index.ts | 0 .../api/indexing-status/request.ts | 0 .../api/indexing-status/response.ts | 0 .../api/indexing-status/serialize.ts | 0 .../indexing-status/serialized-response.ts | 0 .../api/indexing-status/zod-schemas.ts | 0 .../api/name-tokens/deserialize.ts | 0 .../api/name-tokens/index.ts | 0 .../api/name-tokens/prerequisites.ts | 0 .../api/name-tokens/request.ts | 0 .../api/name-tokens/response.ts | 0 .../api/name-tokens/serialize.ts | 0 .../api/name-tokens/serialized-response.ts | 0 .../api/name-tokens/zod-schemas.test.ts | 0 .../api/name-tokens/zod-schemas.ts | 0 .../api/registrar-actions/deserialize.ts | 0 .../api/registrar-actions/filters.ts | 0 .../api/registrar-actions/index.ts | 0 .../api/registrar-actions/prerequisites.ts | 0 .../api/registrar-actions/request.ts | 0 .../api/registrar-actions/response.ts | 0 .../api/registrar-actions/serialize.ts | 0 .../registrar-actions/serialized-response.ts | 0 .../api/registrar-actions/zod-schemas.test.ts | 0 .../api/registrar-actions/zod-schemas.ts | 0 .../api/resolution/index.ts | 0 .../api/resolution/types.ts | 0 .../api/resolution/zod-schemas.ts | 0 .../api/shared/errors/deserialize.ts | 0 .../api/shared/errors/index.ts | 0 .../api/shared/errors/response.ts | 0 .../api/shared/errors/zod-schemas.ts | 0 .../{ensapi => ensnode}/api/shared/index.ts | 0 .../shared/pagination/build-page-context.ts | 0 .../api/shared/pagination/index.ts | 0 .../api/shared/pagination/request.ts | 0 .../api/shared/pagination/response.ts | 0 .../api/shared/pagination/zod-schemas.ts | 0 .../src/{ensapi => ensnode}/client-error.ts | 0 .../src/{ensapi => ensnode}/client.test.ts | 40 ++++++++--------- .../src/{ensapi => ensnode}/client.ts | 44 +++++++++---------- .../{ensapi => ensnode}/deployments.test.ts | 0 .../src/{ensapi => ensnode}/deployments.ts | 0 packages/ensnode-sdk/src/ensnode/index.ts | 8 ++++ packages/ensnode-sdk/src/index.ts | 1 + packages/ensnode-sdk/src/internal.ts | 12 ++--- 57 files changed, 78 insertions(+), 77 deletions(-) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/index.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/indexing-status/deserialize.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/indexing-status/index.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/indexing-status/request.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/indexing-status/response.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/indexing-status/serialize.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/indexing-status/serialized-response.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/indexing-status/zod-schemas.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/name-tokens/deserialize.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/name-tokens/index.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/name-tokens/prerequisites.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/name-tokens/request.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/name-tokens/response.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/name-tokens/serialize.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/name-tokens/serialized-response.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/name-tokens/zod-schemas.test.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/name-tokens/zod-schemas.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/registrar-actions/deserialize.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/registrar-actions/filters.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/registrar-actions/index.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/registrar-actions/prerequisites.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/registrar-actions/request.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/registrar-actions/response.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/registrar-actions/serialize.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/registrar-actions/serialized-response.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/registrar-actions/zod-schemas.test.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/registrar-actions/zod-schemas.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/resolution/index.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/resolution/types.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/resolution/zod-schemas.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/shared/errors/deserialize.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/shared/errors/index.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/shared/errors/response.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/shared/errors/zod-schemas.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/shared/index.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/shared/pagination/build-page-context.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/shared/pagination/index.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/shared/pagination/request.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/shared/pagination/response.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/api/shared/pagination/zod-schemas.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/client-error.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/client.test.ts (94%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/client.ts (95%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/deployments.test.ts (100%) rename packages/ensnode-sdk/src/{ensapi => ensnode}/deployments.ts (100%) create mode 100644 packages/ensnode-sdk/src/ensnode/index.ts diff --git a/.changeset/common-lines-smell.md b/.changeset/common-lines-smell.md index 9be4789291..404f7917e3 100644 --- a/.changeset/common-lines-smell.md +++ b/.changeset/common-lines-smell.md @@ -2,4 +2,4 @@ "@ensnode/ensnode-sdk": minor --- -**Breaking**: Replaced the `config()` method in the `EnsApiClient` class with the extended data model returned from the `indexingStatus()` method. +**Breaking**: Replaced the `config()` method in the `EnsNodeClient` class with the extended data model returned from the `indexingStatus()` method. diff --git a/.changeset/cool-hotels-dress.md b/.changeset/cool-hotels-dress.md index cc95d775cd..3922333769 100644 --- a/.changeset/cool-hotels-dress.md +++ b/.changeset/cool-hotels-dress.md @@ -2,4 +2,4 @@ "@ensnode/ensnode-sdk": minor --- -Fully removed the deprecated `ENSNodeClient` (use `EnsApiClient` instead). +Fully removed the deprecated `ENSNodeClient` (use `EnsNodeClient` instead). diff --git a/.changeset/two-rice-flash.md b/.changeset/two-rice-flash.md index 45bf39db68..3ccb64f806 100644 --- a/.changeset/two-rice-flash.md +++ b/.changeset/two-rice-flash.md @@ -2,4 +2,4 @@ "@ensnode/ensnode-react": minor --- -Replaced references to the deprecated and removed `ENSNodeClient` with `EnsApiClient`. +Replaced references to the deprecated and removed `ENSNodeClient` with `EnsNodeClient`. diff --git a/docs/ensnode.io/src/content/docs/ensrainbow/concepts/unknown-labels.mdx b/docs/ensnode.io/src/content/docs/ensrainbow/concepts/unknown-labels.mdx index 93abad6cb7..8c666c81d3 100644 --- a/docs/ensnode.io/src/content/docs/ensrainbow/concepts/unknown-labels.mdx +++ b/docs/ensnode.io/src/content/docs/ensrainbow/concepts/unknown-labels.mdx @@ -160,7 +160,7 @@ When querying ENSNode's Subgraph-compatible GraphQL API or the legacy ENS Subgra **ENSNode's Resolution API:** -ENSNode's Resolution API (accessed via the `EnsApiClient` SDK) accepts names directly without requiring you to compute the node (namehash) yourself. However, you must still normalize the name before passing it to the API. The key difference is that you don't need to manually compute the namehash - you can call methods like `resolveRecords(normalizedName, selection)` directly with the name string. +ENSNode's Resolution API (accessed via the `EnsNodeClient` SDK) accepts names directly without requiring you to compute the node (namehash) yourself. However, you must still normalize the name before passing it to the API. The key difference is that you don't need to manually compute the namehash - you can call methods like `resolveRecords(normalizedName, selection)` directly with the name string. ## The Solution: How ENSRainbow Works diff --git a/packages/ensnode-react/src/provider.tsx b/packages/ensnode-react/src/provider.tsx index a495875ee2..8ee9566526 100644 --- a/packages/ensnode-react/src/provider.tsx +++ b/packages/ensnode-react/src/provider.tsx @@ -4,7 +4,7 @@ import { QueryClient, QueryClientProvider, useQueryClient } from "@tanstack/react-query"; import { createElement, useMemo } from "react"; -import { EnsApiClient } from "@ensnode/ensnode-sdk"; +import { EnsNodeClient } from "@ensnode/ensnode-sdk"; import { EnsNodeContext } from "./context"; import type { EnsNodeProviderOptions } from "./types"; @@ -94,11 +94,11 @@ export function EnsNodeProvider(parameters: React.PropsWithChildren { - const client = new EnsApiClient(config.client); + const client = new EnsNodeClient(config.client); return client.resolveRecords(args.name, args.selection, args); }, }; @@ -91,7 +91,7 @@ export function createPrimaryNameQueryOptions( enabled: true, queryKey: queryKeys.primaryName(config.client.url.href, args), queryFn: async () => { - const client = new EnsApiClient(config.client); + const client = new EnsNodeClient(config.client); return client.resolvePrimaryName(args.address, args.chainId, args); }, }; @@ -108,7 +108,7 @@ export function createPrimaryNamesQueryOptions( enabled: true, queryKey: queryKeys.primaryNames(config.client.url.href, args), queryFn: async () => { - const client = new EnsApiClient(config.client); + const client = new EnsNodeClient(config.client); return client.resolvePrimaryNames(args.address, args); }, }; @@ -122,7 +122,7 @@ export function createIndexingStatusQueryOptions(config: EnsNodeProviderOptions) enabled: true, queryKey: queryKeys.indexingStatus(config.client.url.href), queryFn: async () => { - const client = new EnsApiClient(config.client); + const client = new EnsNodeClient(config.client); return client.indexingStatus(); }, }; @@ -139,7 +139,7 @@ export function createRegistrarActionsQueryOptions( enabled: true, queryKey: queryKeys.registrarActions(config.client.url.href, args), queryFn: async () => { - const client = new EnsApiClient(config.client); + const client = new EnsNodeClient(config.client); return client.registrarActions(args); }, @@ -157,7 +157,7 @@ export function createNameTokensQueryOptions( enabled: true, queryKey: queryKeys.nameTokens(config.client.url.href, args), queryFn: async () => { - const client = new EnsApiClient(config.client); + const client = new EnsNodeClient(config.client); return client.nameTokens(args); }, diff --git a/packages/ensnode-sdk/README.md b/packages/ensnode-sdk/README.md index 346c3c8487..7a2314d489 100644 --- a/packages/ensnode-sdk/README.md +++ b/packages/ensnode-sdk/README.md @@ -10,9 +10,9 @@ Learn more about [ENSNode](https://ensnode.io/) from [the ENSNode docs](https:// npm install @ensnode/ensnode-sdk ``` -## EnsApiClient +## EnsNodeClient -The `EnsApiClient` provides a unified interface for the ENSApi REST APIs: +The `EnsNodeClient` provides a unified interface for the ENSApi REST APIs: - Resolution API (Protocol Accelerated Forward/Reverse Resolution) - Indexing Status API - Configuration API @@ -20,10 +20,10 @@ The `EnsApiClient` provides a unified interface for the ENSApi REST APIs: ### Basic Usage ```typescript -import { EnsApiClient, evmChainIdToCoinType } from "@ensnode/ensnode-sdk"; +import { EnsNodeClient, evmChainIdToCoinType } from "@ensnode/ensnode-sdk"; import { mainnet } from "viem/chains"; -const client = new EnsApiClient(); +const client = new EnsNodeClient(); // Resolution API: Records Resolution const { records } = await client.resolveRecords("jesse.base.eth", { @@ -186,7 +186,7 @@ console.log(status); ### Configuration ```typescript -const client = new EnsApiClient({ +const client = new EnsNodeClient({ url: new URL("https://my-ensnode-instance.com"), }); ``` diff --git a/packages/ensnode-sdk/src/ensapi/index.ts b/packages/ensnode-sdk/src/ensapi/index.ts index c31054877e..5c62e04f5e 100644 --- a/packages/ensnode-sdk/src/ensapi/index.ts +++ b/packages/ensnode-sdk/src/ensapi/index.ts @@ -1,9 +1 @@ -export * from "./api"; -export { - type ClientOptions, - EnsApiClient, - type EnsApiClientOptions, -} from "./client"; -export * from "./client-error"; export * from "./config"; -export * from "./deployments"; diff --git a/packages/ensnode-sdk/src/ensapi/api/index.ts b/packages/ensnode-sdk/src/ensnode/api/index.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/index.ts rename to packages/ensnode-sdk/src/ensnode/api/index.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts b/packages/ensnode-sdk/src/ensnode/api/indexing-status/deserialize.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts rename to packages/ensnode-sdk/src/ensnode/api/indexing-status/deserialize.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/index.ts b/packages/ensnode-sdk/src/ensnode/api/indexing-status/index.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/indexing-status/index.ts rename to packages/ensnode-sdk/src/ensnode/api/indexing-status/index.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/request.ts b/packages/ensnode-sdk/src/ensnode/api/indexing-status/request.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/indexing-status/request.ts rename to packages/ensnode-sdk/src/ensnode/api/indexing-status/request.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts b/packages/ensnode-sdk/src/ensnode/api/indexing-status/response.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts rename to packages/ensnode-sdk/src/ensnode/api/indexing-status/response.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts b/packages/ensnode-sdk/src/ensnode/api/indexing-status/serialize.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts rename to packages/ensnode-sdk/src/ensnode/api/indexing-status/serialize.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts b/packages/ensnode-sdk/src/ensnode/api/indexing-status/serialized-response.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts rename to packages/ensnode-sdk/src/ensnode/api/indexing-status/serialized-response.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts b/packages/ensnode-sdk/src/ensnode/api/indexing-status/zod-schemas.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts rename to packages/ensnode-sdk/src/ensnode/api/indexing-status/zod-schemas.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/name-tokens/deserialize.ts b/packages/ensnode-sdk/src/ensnode/api/name-tokens/deserialize.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/name-tokens/deserialize.ts rename to packages/ensnode-sdk/src/ensnode/api/name-tokens/deserialize.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/name-tokens/index.ts b/packages/ensnode-sdk/src/ensnode/api/name-tokens/index.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/name-tokens/index.ts rename to packages/ensnode-sdk/src/ensnode/api/name-tokens/index.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/name-tokens/prerequisites.ts b/packages/ensnode-sdk/src/ensnode/api/name-tokens/prerequisites.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/name-tokens/prerequisites.ts rename to packages/ensnode-sdk/src/ensnode/api/name-tokens/prerequisites.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/name-tokens/request.ts b/packages/ensnode-sdk/src/ensnode/api/name-tokens/request.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/name-tokens/request.ts rename to packages/ensnode-sdk/src/ensnode/api/name-tokens/request.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/name-tokens/response.ts b/packages/ensnode-sdk/src/ensnode/api/name-tokens/response.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/name-tokens/response.ts rename to packages/ensnode-sdk/src/ensnode/api/name-tokens/response.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/name-tokens/serialize.ts b/packages/ensnode-sdk/src/ensnode/api/name-tokens/serialize.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/name-tokens/serialize.ts rename to packages/ensnode-sdk/src/ensnode/api/name-tokens/serialize.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/name-tokens/serialized-response.ts b/packages/ensnode-sdk/src/ensnode/api/name-tokens/serialized-response.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/name-tokens/serialized-response.ts rename to packages/ensnode-sdk/src/ensnode/api/name-tokens/serialized-response.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/name-tokens/zod-schemas.test.ts b/packages/ensnode-sdk/src/ensnode/api/name-tokens/zod-schemas.test.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/name-tokens/zod-schemas.test.ts rename to packages/ensnode-sdk/src/ensnode/api/name-tokens/zod-schemas.test.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/name-tokens/zod-schemas.ts b/packages/ensnode-sdk/src/ensnode/api/name-tokens/zod-schemas.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/name-tokens/zod-schemas.ts rename to packages/ensnode-sdk/src/ensnode/api/name-tokens/zod-schemas.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/registrar-actions/deserialize.ts b/packages/ensnode-sdk/src/ensnode/api/registrar-actions/deserialize.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/registrar-actions/deserialize.ts rename to packages/ensnode-sdk/src/ensnode/api/registrar-actions/deserialize.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/registrar-actions/filters.ts b/packages/ensnode-sdk/src/ensnode/api/registrar-actions/filters.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/registrar-actions/filters.ts rename to packages/ensnode-sdk/src/ensnode/api/registrar-actions/filters.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/registrar-actions/index.ts b/packages/ensnode-sdk/src/ensnode/api/registrar-actions/index.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/registrar-actions/index.ts rename to packages/ensnode-sdk/src/ensnode/api/registrar-actions/index.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/registrar-actions/prerequisites.ts b/packages/ensnode-sdk/src/ensnode/api/registrar-actions/prerequisites.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/registrar-actions/prerequisites.ts rename to packages/ensnode-sdk/src/ensnode/api/registrar-actions/prerequisites.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/registrar-actions/request.ts b/packages/ensnode-sdk/src/ensnode/api/registrar-actions/request.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/registrar-actions/request.ts rename to packages/ensnode-sdk/src/ensnode/api/registrar-actions/request.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/registrar-actions/response.ts b/packages/ensnode-sdk/src/ensnode/api/registrar-actions/response.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/registrar-actions/response.ts rename to packages/ensnode-sdk/src/ensnode/api/registrar-actions/response.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/registrar-actions/serialize.ts b/packages/ensnode-sdk/src/ensnode/api/registrar-actions/serialize.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/registrar-actions/serialize.ts rename to packages/ensnode-sdk/src/ensnode/api/registrar-actions/serialize.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/registrar-actions/serialized-response.ts b/packages/ensnode-sdk/src/ensnode/api/registrar-actions/serialized-response.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/registrar-actions/serialized-response.ts rename to packages/ensnode-sdk/src/ensnode/api/registrar-actions/serialized-response.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/registrar-actions/zod-schemas.test.ts b/packages/ensnode-sdk/src/ensnode/api/registrar-actions/zod-schemas.test.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/registrar-actions/zod-schemas.test.ts rename to packages/ensnode-sdk/src/ensnode/api/registrar-actions/zod-schemas.test.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/registrar-actions/zod-schemas.ts b/packages/ensnode-sdk/src/ensnode/api/registrar-actions/zod-schemas.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/registrar-actions/zod-schemas.ts rename to packages/ensnode-sdk/src/ensnode/api/registrar-actions/zod-schemas.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/resolution/index.ts b/packages/ensnode-sdk/src/ensnode/api/resolution/index.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/resolution/index.ts rename to packages/ensnode-sdk/src/ensnode/api/resolution/index.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/resolution/types.ts b/packages/ensnode-sdk/src/ensnode/api/resolution/types.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/resolution/types.ts rename to packages/ensnode-sdk/src/ensnode/api/resolution/types.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/resolution/zod-schemas.ts b/packages/ensnode-sdk/src/ensnode/api/resolution/zod-schemas.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/resolution/zod-schemas.ts rename to packages/ensnode-sdk/src/ensnode/api/resolution/zod-schemas.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/shared/errors/deserialize.ts b/packages/ensnode-sdk/src/ensnode/api/shared/errors/deserialize.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/shared/errors/deserialize.ts rename to packages/ensnode-sdk/src/ensnode/api/shared/errors/deserialize.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/shared/errors/index.ts b/packages/ensnode-sdk/src/ensnode/api/shared/errors/index.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/shared/errors/index.ts rename to packages/ensnode-sdk/src/ensnode/api/shared/errors/index.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/shared/errors/response.ts b/packages/ensnode-sdk/src/ensnode/api/shared/errors/response.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/shared/errors/response.ts rename to packages/ensnode-sdk/src/ensnode/api/shared/errors/response.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/shared/errors/zod-schemas.ts b/packages/ensnode-sdk/src/ensnode/api/shared/errors/zod-schemas.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/shared/errors/zod-schemas.ts rename to packages/ensnode-sdk/src/ensnode/api/shared/errors/zod-schemas.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/shared/index.ts b/packages/ensnode-sdk/src/ensnode/api/shared/index.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/shared/index.ts rename to packages/ensnode-sdk/src/ensnode/api/shared/index.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/shared/pagination/build-page-context.ts b/packages/ensnode-sdk/src/ensnode/api/shared/pagination/build-page-context.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/shared/pagination/build-page-context.ts rename to packages/ensnode-sdk/src/ensnode/api/shared/pagination/build-page-context.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/shared/pagination/index.ts b/packages/ensnode-sdk/src/ensnode/api/shared/pagination/index.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/shared/pagination/index.ts rename to packages/ensnode-sdk/src/ensnode/api/shared/pagination/index.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/shared/pagination/request.ts b/packages/ensnode-sdk/src/ensnode/api/shared/pagination/request.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/shared/pagination/request.ts rename to packages/ensnode-sdk/src/ensnode/api/shared/pagination/request.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/shared/pagination/response.ts b/packages/ensnode-sdk/src/ensnode/api/shared/pagination/response.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/shared/pagination/response.ts rename to packages/ensnode-sdk/src/ensnode/api/shared/pagination/response.ts diff --git a/packages/ensnode-sdk/src/ensapi/api/shared/pagination/zod-schemas.ts b/packages/ensnode-sdk/src/ensnode/api/shared/pagination/zod-schemas.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/api/shared/pagination/zod-schemas.ts rename to packages/ensnode-sdk/src/ensnode/api/shared/pagination/zod-schemas.ts diff --git a/packages/ensnode-sdk/src/ensapi/client-error.ts b/packages/ensnode-sdk/src/ensnode/client-error.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/client-error.ts rename to packages/ensnode-sdk/src/ensnode/client-error.ts diff --git a/packages/ensnode-sdk/src/ensapi/client.test.ts b/packages/ensnode-sdk/src/ensnode/client.test.ts similarity index 94% rename from packages/ensnode-sdk/src/ensapi/client.test.ts rename to packages/ensnode-sdk/src/ensnode/client.test.ts index 4c84490d36..b7c531801d 100644 --- a/packages/ensnode-sdk/src/ensapi/client.test.ts +++ b/packages/ensnode-sdk/src/ensnode/client.test.ts @@ -2,6 +2,7 @@ import type { Address, Name } from "enssdk"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { ENSNamespaceIds } from "../ens"; +import type { SerializedEnsApiPublicConfig } from "../ensapi/config/serialized-types"; import { PluginName } from "../ensindexer/config/types"; import { ChainIndexingStatusIds } from "../indexing-status/chain-indexing-status-snapshot"; import { CrossChainIndexingStrategyIds } from "../indexing-status/cross-chain-indexing-status-snapshot"; @@ -22,9 +23,8 @@ import type { ResolvePrimaryNamesResponse, } from "./api/resolution/types"; import type { ErrorResponse } from "./api/shared/errors/response"; -import { EnsApiClient } from "./client"; +import { EnsNodeClient } from "./client"; import { ClientError } from "./client-error"; -import type { SerializedEnsApiPublicConfig } from "./config/serialized-types"; import { DEFAULT_ENSNODE_API_URL_MAINNET, getDefaultEnsNodeUrl } from "./deployments"; const EXAMPLE_NAME: Name = "example.eth"; @@ -216,14 +216,14 @@ const _EXAMPLE_INDEXING_STATUS_FOLLOWING_RESPONSE: EnsApiIndexingStatusResponse const mockFetch = vi.fn(); vi.stubGlobal("fetch", mockFetch); -describe("EnsApiClient", () => { +describe("EnsNodeClient", () => { beforeEach(() => { mockFetch.mockClear(); }); describe("constructor and options", () => { it("should use default options when none provided", () => { - const client = new EnsApiClient(); + const client = new EnsNodeClient(); const options = client.getOptions(); expect(options).toEqual({ url: getDefaultEnsNodeUrl(ENSNamespaceIds.Mainnet) }); @@ -231,14 +231,14 @@ describe("EnsApiClient", () => { it("should merge provided options with defaults", () => { const customUrl = new URL("https://custom.api.com"); - const client = new EnsApiClient({ url: customUrl }); + const client = new EnsNodeClient({ url: customUrl }); const options = client.getOptions(); expect(options).toEqual({ url: customUrl }); }); it("should return frozen options object", () => { - const client = new EnsApiClient(); + const client = new EnsNodeClient(); const options = client.getOptions(); expect(Object.isFrozen(options)).toBe(true); @@ -251,7 +251,7 @@ describe("EnsApiClient", () => { const mockResponse = { records: EXAMPLE_RECORDS_RESPONSE }; mockFetch.mockResolvedValueOnce({ ok: true, json: async () => mockResponse }); - const client = new EnsApiClient(); + const client = new EnsNodeClient(); const response = await client.resolveRecords(EXAMPLE_NAME, EXAMPLE_SELECTION); const expectedUrl = new URL( @@ -269,7 +269,7 @@ describe("EnsApiClient", () => { const mockResponse = { records: EXAMPLE_RECORDS_RESPONSE, trace: [] }; mockFetch.mockResolvedValueOnce({ ok: true, json: async () => mockResponse }); - const client = new EnsApiClient(); + const client = new EnsNodeClient(); const response = await client.resolveRecords(EXAMPLE_NAME, EXAMPLE_SELECTION, { trace: true, }); @@ -289,7 +289,7 @@ describe("EnsApiClient", () => { it("should throw error when API returns error", async () => { mockFetch.mockResolvedValueOnce({ ok: false, json: async () => EXAMPLE_ERROR_RESPONSE }); - const client = new EnsApiClient(); + const client = new EnsNodeClient(); await expect(client.resolveRecords(EXAMPLE_NAME, EXAMPLE_SELECTION)).rejects.toThrowError( ClientError, ); @@ -303,7 +303,7 @@ describe("EnsApiClient", () => { json: async () => EXAMPLE_PRIMARY_NAME_RESPONSE, }); - const client = new EnsApiClient(); + const client = new EnsNodeClient(); const response = await client.resolvePrimaryName(EXAMPLE_ADDRESS, 1); const expectedUrl = new URL( @@ -319,7 +319,7 @@ describe("EnsApiClient", () => { const mockResponse = { name: EXAMPLE_NAME, trace: [] }; mockFetch.mockResolvedValueOnce({ ok: true, json: async () => mockResponse }); - const client = new EnsApiClient(); + const client = new EnsNodeClient(); const response = await client.resolvePrimaryName(EXAMPLE_ADDRESS, 1, { trace: true }); const expectedUrl = new URL( @@ -338,7 +338,7 @@ describe("EnsApiClient", () => { json: async () => EXAMPLE_PRIMARY_NAME_RESPONSE, }); - const client = new EnsApiClient(); + const client = new EnsNodeClient(); await client.resolvePrimaryName(EXAMPLE_ADDRESS, 1, { accelerate: true }); const expectedUrl = new URL( @@ -353,7 +353,7 @@ describe("EnsApiClient", () => { it("should throw error when API returns error", async () => { mockFetch.mockResolvedValueOnce({ ok: false, json: async () => EXAMPLE_ERROR_RESPONSE }); - const client = new EnsApiClient(); + const client = new EnsNodeClient(); await expect(client.resolvePrimaryName(EXAMPLE_ADDRESS, 1)).rejects.toThrowError(ClientError); }); }); @@ -365,7 +365,7 @@ describe("EnsApiClient", () => { json: async () => EXAMPLE_PRIMARY_NAMES_RESPONSE, }); - const client = new EnsApiClient(); + const client = new EnsNodeClient(); const response = await client.resolvePrimaryNames(EXAMPLE_ADDRESS); const expectedUrl = new URL( @@ -383,7 +383,7 @@ describe("EnsApiClient", () => { json: async () => EXAMPLE_PRIMARY_NAMES_RESPONSE, }); - const client = new EnsApiClient(); + const client = new EnsNodeClient(); await client.resolvePrimaryNames(EXAMPLE_ADDRESS, { chainIds: [1, 10] }); const expectedUrl = new URL( @@ -399,7 +399,7 @@ describe("EnsApiClient", () => { const mockResponse = { ...EXAMPLE_PRIMARY_NAMES_RESPONSE, trace: [] }; mockFetch.mockResolvedValueOnce({ ok: true, json: async () => mockResponse }); - const client = new EnsApiClient(); + const client = new EnsNodeClient(); const response = await client.resolvePrimaryNames(EXAMPLE_ADDRESS, { trace: true }); const expectedUrl = new URL( @@ -418,7 +418,7 @@ describe("EnsApiClient", () => { json: async () => EXAMPLE_PRIMARY_NAMES_RESPONSE, }); - const client = new EnsApiClient(); + const client = new EnsNodeClient(); await client.resolvePrimaryNames(EXAMPLE_ADDRESS, { accelerate: true }); const expectedUrl = new URL( @@ -433,7 +433,7 @@ describe("EnsApiClient", () => { it("should throw error when API returns error", async () => { mockFetch.mockResolvedValueOnce({ ok: false, json: async () => EXAMPLE_ERROR_RESPONSE }); - const client = new EnsApiClient(); + const client = new EnsNodeClient(); await expect(client.resolvePrimaryNames(EXAMPLE_ADDRESS)).rejects.toThrowError(ClientError); }); }); @@ -444,7 +444,7 @@ describe("EnsApiClient", () => { const requestUrl = new URL(`/api/indexing-status`, DEFAULT_ENSNODE_API_URL_MAINNET); const mockedResponse = EXAMPLE_INDEXING_STATUS_BACKFILL_RESPONSE; - const client = new EnsApiClient(); + const client = new EnsNodeClient(); mockFetch.mockResolvedValueOnce({ ok: true, @@ -458,7 +458,7 @@ describe("EnsApiClient", () => { it("should throw error when API returns error other than 503 error", async () => { // arrange - const client = new EnsApiClient(); + const client = new EnsNodeClient(); mockFetch.mockResolvedValueOnce({ ok: false, json: async () => EXAMPLE_ERROR_RESPONSE }); diff --git a/packages/ensnode-sdk/src/ensapi/client.ts b/packages/ensnode-sdk/src/ensnode/client.ts similarity index 95% rename from packages/ensnode-sdk/src/ensapi/client.ts rename to packages/ensnode-sdk/src/ensnode/client.ts index b4116832ce..09c8fbf814 100644 --- a/packages/ensnode-sdk/src/ensapi/client.ts +++ b/packages/ensnode-sdk/src/ensnode/client.ts @@ -30,7 +30,7 @@ import { getDefaultEnsNodeUrl } from "./deployments"; /** * Configuration options for ENSNode API client */ -export interface EnsApiClientOptions { +export interface EnsNodeClientOptions { /** The ENSNode API URL */ url: URL; } @@ -38,12 +38,12 @@ export interface EnsApiClientOptions { /** * Configuration options for ENSNode API client * - * @deprecated Use {@link EnsApiClientOptions} instead. + * @deprecated Use {@link EnsNodeClientOptions} instead. */ -export type ClientOptions = EnsApiClientOptions; +export type ClientOptions = EnsNodeClientOptions; /** - * EnsApi Client + * ENSNode API Client * * Provides access to the following ENSNode APIs: * - Resolution API @@ -54,10 +54,10 @@ export type ClientOptions = EnsApiClientOptions; * * @example * ```typescript - * import { EnsApiClient } from "@ensnode/ensnode-sdk"; + * import { EnsNodeClient } from "@ensnode/ensnode-sdk"; * * // Create client with default options - * const client = new EnsApiClient(); + * const client = new EnsNodeClient(); * * // Use resolution methods * const { records } = await client.resolveRecords("jesse.base.eth", { @@ -68,51 +68,51 @@ export type ClientOptions = EnsApiClientOptions; * * @example * ```typescript - * import { ENSNamespaceIds, EnsApiClient, getDefaultEnsNodeUrl } from "@ensnode/ensnode-sdk"; + * import { ENSNamespaceIds, EnsNodeClient, getDefaultEnsNodeUrl } from "@ensnode/ensnode-sdk"; * * // Use default ENSNode API URL for Mainnet - * const client = new EnsApiClient({ + * const client = new EnsNodeClient({ * url: getDefaultEnsNodeUrl(ENSNamespaceIds.Mainnet), * }); * ``` * * @example * ```typescript - * import { ENSNamespaceIds, EnsApiClient, getDefaultEnsNodeUrl } from "@ensnode/ensnode-sdk"; + * import { ENSNamespaceIds, EnsNodeClient, getDefaultEnsNodeUrl } from "@ensnode/ensnode-sdk"; * * // Use default ENSNode API URL for Sepolia - * const client = new EnsApiClient({ + * const client = new EnsNodeClient({ * url: getDefaultEnsNodeUrl(ENSNamespaceIds.Sepolia), * }); * ``` * * @example * ```typescript - * import { EnsApiClient } from "@ensnode/ensnode-sdk"; + * import { EnsNodeClient } from "@ensnode/ensnode-sdk"; * * // Custom configuration - * const client = new EnsApiClient({ + * const client = new EnsNodeClient({ * url: new URL("https://my-ensnode-instance.com"), * }); * ``` */ -export class EnsApiClient { - private readonly options: EnsApiClientOptions; +export class EnsNodeClient { + private readonly options: EnsNodeClientOptions; - static defaultOptions(): EnsApiClientOptions { + static defaultOptions(): EnsNodeClientOptions { return { url: getDefaultEnsNodeUrl(), }; } - constructor(options: Partial = {}) { + constructor(options: Partial = {}) { this.options = { - ...EnsApiClient.defaultOptions(), + ...EnsNodeClient.defaultOptions(), ...options, }; } - getOptions(): Readonly { + getOptions(): Readonly { return Object.freeze({ url: new URL(this.options.url.href), }); @@ -372,13 +372,13 @@ export class EnsApiClient { * ```ts * import { * registrarActionsFilter, - * EnsApiClient, + * EnsNodeClient, * } from "@ensnode/ensnode-sdk"; * import { ETH_NODE, namehashInterpretedName, asInterpretedName } from "enssdk"; * * const BASE_NODE = namehashInterpretedName(asInterpretedName("base.eth")); * - * const client: EnsApiClient; + * const client: EnsNodeClient; * * // Get first page with default page size (10 records) * const response = await client.registrarActions(); @@ -586,14 +586,14 @@ export class EnsApiClient { * @example * ```ts * import { - * EnsApiClient, + * EnsNodeClient, * } from "@ensnode/ensnode-sdk"; * import { namehashInterpretedName, asInterpretedName } from "enssdk"; * * const VITALIK_NAME = asInterpretedName("vitalik.eth"); * const VITALIK_DOMAIN_ID = namehashInterpretedName(VITALIK_NAME); * - * const client: EnsApiClient; + * const client: EnsNodeClient; * * // get latest name token records from the indexed subregistry based on the requested name * const response = await client.nameTokens({ diff --git a/packages/ensnode-sdk/src/ensapi/deployments.test.ts b/packages/ensnode-sdk/src/ensnode/deployments.test.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/deployments.test.ts rename to packages/ensnode-sdk/src/ensnode/deployments.test.ts diff --git a/packages/ensnode-sdk/src/ensapi/deployments.ts b/packages/ensnode-sdk/src/ensnode/deployments.ts similarity index 100% rename from packages/ensnode-sdk/src/ensapi/deployments.ts rename to packages/ensnode-sdk/src/ensnode/deployments.ts diff --git a/packages/ensnode-sdk/src/ensnode/index.ts b/packages/ensnode-sdk/src/ensnode/index.ts new file mode 100644 index 0000000000..92d57bf888 --- /dev/null +++ b/packages/ensnode-sdk/src/ensnode/index.ts @@ -0,0 +1,8 @@ +export * from "./api"; +export { + type ClientOptions, + EnsNodeClient, + type EnsNodeClientOptions, +} from "./client"; +export * from "./client-error"; +export * from "./deployments"; diff --git a/packages/ensnode-sdk/src/index.ts b/packages/ensnode-sdk/src/index.ts index 7491a05fd5..06ca0096f8 100644 --- a/packages/ensnode-sdk/src/index.ts +++ b/packages/ensnode-sdk/src/index.ts @@ -7,6 +7,7 @@ export * from "./ens"; export * from "./ensapi"; export * from "./ensdb"; export * from "./ensindexer"; +export * from "./ensnode"; export * from "./ensrainbow"; export * from "./identity"; export * from "./indexing-status"; diff --git a/packages/ensnode-sdk/src/internal.ts b/packages/ensnode-sdk/src/internal.ts index 8029f1db3d..e810988665 100644 --- a/packages/ensnode-sdk/src/internal.ts +++ b/packages/ensnode-sdk/src/internal.ts @@ -12,14 +12,14 @@ * app/package in the monorepo which requires `@ensnode/ensnode-sdk` dependency. */ -export * from "./ensapi/api/indexing-status/zod-schemas"; -export * from "./ensapi/api/name-tokens/zod-schemas"; -export * from "./ensapi/api/registrar-actions/zod-schemas"; -export * from "./ensapi/api/resolution/zod-schemas"; -export * from "./ensapi/api/shared/errors/zod-schemas"; -export * from "./ensapi/api/shared/pagination/zod-schemas"; export * from "./ensapi/config/zod-schemas"; export * from "./ensindexer/config/zod-schemas"; +export * from "./ensnode/api/indexing-status/zod-schemas"; +export * from "./ensnode/api/name-tokens/zod-schemas"; +export * from "./ensnode/api/registrar-actions/zod-schemas"; +export * from "./ensnode/api/resolution/zod-schemas"; +export * from "./ensnode/api/shared/errors/zod-schemas"; +export * from "./ensnode/api/shared/pagination/zod-schemas"; export * from "./omnigraph-api/example-queries"; export * from "./registrars/zod-schemas"; export * from "./rpc"; From 2e8a4761be8985a9663a833970aa04cbf9ffc4af Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 17 Apr 2026 19:26:56 +0200 Subject: [PATCH 46/56] Apply AI PR feedback --- .changeset/rare-dogs-sin.md | 2 +- .../use-indexing-status-with-swr.ts | 9 +++---- .../src/hooks/use-ensnode-stack-info.ts | 4 --- .../src/handlers/api/meta/status-api.ts | 2 +- .../src/middleware/stack-info.middleware.ts | 25 +++++++++++++------ 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/.changeset/rare-dogs-sin.md b/.changeset/rare-dogs-sin.md index 1b2bb0ff0d..7321bf5048 100644 --- a/.changeset/rare-dogs-sin.md +++ b/.changeset/rare-dogs-sin.md @@ -4,4 +4,4 @@ **Breaking**: - Replaced `useENSNodeSDKConfig` hook with `useEnsNodeProviderOptions` hook. -- Renamed `createEnsNodeOptions` function to `createEnsNodeProviderOptions`. +- Renamed `createConfig` function to `createEnsNodeProviderOptions`. diff --git a/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts b/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts index 978391aa5c..8d26cfb7db 100644 --- a/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts +++ b/apps/ensadmin/src/components/indexing-status/use-indexing-status-with-swr.ts @@ -14,13 +14,12 @@ import { WithEnsNodeProviderOptions, } from "@ensnode/ensnode-react"; import { - CrossChainIndexingStatusSnapshotOmnichain, + type CrossChainIndexingStatusSnapshotOmnichain, createRealtimeIndexingStatusProjection, - EnsApiIndexingStatusRequest, + type EnsApiIndexingStatusRequest, EnsApiIndexingStatusResponseCodes, - EnsApiIndexingStatusResponseOk, - EnsApiPublicConfig, - EnsNodeStackInfo, + type EnsApiIndexingStatusResponseOk, + type EnsNodeStackInfo, } from "@ensnode/ensnode-sdk"; const DEFAULT_REFETCH_INTERVAL = secondsToMilliseconds(10); diff --git a/apps/ensadmin/src/hooks/use-ensnode-stack-info.ts b/apps/ensadmin/src/hooks/use-ensnode-stack-info.ts index 2723e0a499..360d8c6e58 100644 --- a/apps/ensadmin/src/hooks/use-ensnode-stack-info.ts +++ b/apps/ensadmin/src/hooks/use-ensnode-stack-info.ts @@ -1,7 +1,3 @@ -import { useQuery } from "@tanstack/react-query"; - -import { useEnsNodeProviderOptions } from "@ensnode/ensnode-react"; - import { useIndexingStatusWithSwr } from "@/components/indexing-status"; /** diff --git a/apps/ensapi/src/handlers/api/meta/status-api.ts b/apps/ensapi/src/handlers/api/meta/status-api.ts index 960755c28b..f2db1b4515 100644 --- a/apps/ensapi/src/handlers/api/meta/status-api.ts +++ b/apps/ensapi/src/handlers/api/meta/status-api.ts @@ -14,7 +14,7 @@ import { getIndexingStatusRoute } from "./status-api.routes"; const app = createApp({ middlewares: [stackInfoMiddleware, indexingStatusMiddleware] }); app.openapi(getIndexingStatusRoute, async (c) => { - if (c.var.indexingStatus instanceof Error) { + if (c.var.indexingStatus instanceof Error || c.var.stackInfo instanceof Error) { return c.json( serializeEnsApiIndexingStatusResponse({ responseCode: EnsApiIndexingStatusResponseCodes.Error, diff --git a/apps/ensapi/src/middleware/stack-info.middleware.ts b/apps/ensapi/src/middleware/stack-info.middleware.ts index 82ddb02712..03c3e27736 100644 --- a/apps/ensapi/src/middleware/stack-info.middleware.ts +++ b/apps/ensapi/src/middleware/stack-info.middleware.ts @@ -2,20 +2,28 @@ import type { EnsNodeStackInfo } from "@ensnode/ensnode-sdk"; import { stackInfoCache } from "@/cache/stack-info.cache"; import { factory, producing } from "@/lib/hono-factory"; -import logger from "@/lib/logger"; +import { makeLogger } from "@/lib/logger"; + +const logger = makeLogger("stack-info.middleware"); export interface StackInfoMiddlewareVariables { /** * ENSNode Stack Info */ - stackInfo: EnsNodeStackInfo; + stackInfo: EnsNodeStackInfo | Error; } /** - * Makes the ENSNode Stack Info available in the middleware context as `c.var.stackInfo`. + * Makes the ENSNode Stack Info cached in {@link stackInfoCache} available + * in the Hono context as `c.var.stackInfo`. + * + * If the ENSNode Stack Info cannot be retrieved, `c.var.stackInfo` will be set to + * the `Error` encountered while attempting to retrieve the Stack Info, so that + * downstream middlewares and handlers can handle the error appropriately. * - * If the stack info cannot be retrieved, for example, if ENSDb instance does not respond, - * we return a 503 Service Unavailable and an error message. + * This middleware should be used in routes that require the ENSNode Stack Info, + * such as the Indexing Status API route, to avoid redundant retrieval of + * the Stack Info in multiple middlewares and handlers within the same request. */ export const stackInfoMiddleware = producing( ["stackInfo"], @@ -23,9 +31,10 @@ export const stackInfoMiddleware = producing( const stackInfo = await stackInfoCache.read(); if (stackInfo instanceof Error) { - logger.error({ stackInfo }, "Failed to retrieve ENSNode Stack Info"); - - return c.json({ error: "Service Unavailable" }, 503); + logger.error( + { error: stackInfo }, + "Failed to retrieve ENSNode Stack Info in stackInfoMiddleware", + ); } c.set("stackInfo", stackInfo); From 713261160d57186c3710024e9734b1b4388c5dcc Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 21 Apr 2026 11:26:43 +0200 Subject: [PATCH 47/56] Update `SerializedEnsNodeStackInfo` data model Explicitly define types for all fields in the data model. Uses new type aliases `SerializedEnsDbPublicConfig` and `SerializedEnsRainbowPublicConfig`. --- packages/ensnode-sdk/src/ensdb/index.ts | 1 + packages/ensnode-sdk/src/ensdb/serialize/config.ts | 6 ++++++ packages/ensnode-sdk/src/ensrainbow/index.ts | 1 + .../ensnode-sdk/src/ensrainbow/serialize/config.ts | 6 ++++++ .../src/stack-info/serialize/ensnode-stack-info.ts | 11 +++++++---- 5 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 packages/ensnode-sdk/src/ensdb/serialize/config.ts create mode 100644 packages/ensnode-sdk/src/ensrainbow/serialize/config.ts diff --git a/packages/ensnode-sdk/src/ensdb/index.ts b/packages/ensnode-sdk/src/ensdb/index.ts index 5c62e04f5e..938f4f05c3 100644 --- a/packages/ensnode-sdk/src/ensdb/index.ts +++ b/packages/ensnode-sdk/src/ensdb/index.ts @@ -1 +1,2 @@ export * from "./config"; +export * from "./serialize/config"; diff --git a/packages/ensnode-sdk/src/ensdb/serialize/config.ts b/packages/ensnode-sdk/src/ensdb/serialize/config.ts new file mode 100644 index 0000000000..20e4de6031 --- /dev/null +++ b/packages/ensnode-sdk/src/ensdb/serialize/config.ts @@ -0,0 +1,6 @@ +import type { EnsDbPublicConfig } from "../config"; + +/** + * Serialized representation of {@link EnsDbPublicConfig} + */ +export type SerializedEnsDbPublicConfig = EnsDbPublicConfig; diff --git a/packages/ensnode-sdk/src/ensrainbow/index.ts b/packages/ensnode-sdk/src/ensrainbow/index.ts index b4fb84ab29..d56f25f6aa 100644 --- a/packages/ensnode-sdk/src/ensrainbow/index.ts +++ b/packages/ensnode-sdk/src/ensrainbow/index.ts @@ -1,2 +1,3 @@ export * from "./config"; +export * from "./serialize/config"; export * from "./types"; diff --git a/packages/ensnode-sdk/src/ensrainbow/serialize/config.ts b/packages/ensnode-sdk/src/ensrainbow/serialize/config.ts new file mode 100644 index 0000000000..5586611976 --- /dev/null +++ b/packages/ensnode-sdk/src/ensrainbow/serialize/config.ts @@ -0,0 +1,6 @@ +import type { EnsRainbowPublicConfig } from "../config"; + +/** + * Serialized representation of {@link EnsRainbowPublicConfig} + */ +export type SerializedEnsRainbowPublicConfig = EnsRainbowPublicConfig; diff --git a/packages/ensnode-sdk/src/stack-info/serialize/ensnode-stack-info.ts b/packages/ensnode-sdk/src/stack-info/serialize/ensnode-stack-info.ts index 31a5d979de..1c1f2f2638 100644 --- a/packages/ensnode-sdk/src/stack-info/serialize/ensnode-stack-info.ts +++ b/packages/ensnode-sdk/src/stack-info/serialize/ensnode-stack-info.ts @@ -1,16 +1,19 @@ -import type { SerializedEnsApiPublicConfig } from "../../ensapi"; import { serializeEnsApiPublicConfig } from "../../ensapi/config/serialize"; -import { serializeEnsIndexerPublicConfig } from "../../ensindexer"; +import type { SerializedEnsApiPublicConfig } from "../../ensapi/config/serialized-types"; +import type { SerializedEnsDbPublicConfig } from "../../ensdb/serialize/config"; +import { serializeEnsIndexerPublicConfig } from "../../ensindexer/config/serialize"; import type { SerializedEnsIndexerPublicConfig } from "../../ensindexer/config/serialized-types"; +import type { SerializedEnsRainbowPublicConfig } from "../../ensrainbow/serialize/config"; import type { EnsNodeStackInfo } from "../ensnode-stack-info"; /** * Serialized representation of {@link EnsNodeStackInfo}. */ -export interface SerializedEnsNodeStackInfo - extends Omit { +export interface SerializedEnsNodeStackInfo { ensApi: SerializedEnsApiPublicConfig; + ensDb: SerializedEnsDbPublicConfig; ensIndexer: SerializedEnsIndexerPublicConfig; + ensRainbow?: SerializedEnsRainbowPublicConfig; } /** From 446be2ece5031188223b6839a83a4b9c552f2035 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 21 Apr 2026 11:28:57 +0200 Subject: [PATCH 48/56] Add `versionInfo` field to `EnsDbPublicConfig` data model --- .../src/app/mock/config-info/page.tsx | 9 ++- .../src/app/mock/indexing-status-api.mock.ts | 17 ++++-- .../connection/cards/ensnode-info.tsx | 4 +- packages/ensdb-sdk/src/client/ensdb-reader.ts | 21 ++++++- packages/ensnode-sdk/src/ensdb/config.ts | 14 ++++- .../src/ensdb/zod-schemas/config.ts | 18 ++++-- .../ensnode-sdk/src/ensnode/client.test.ts | 57 ++++++++++++++++--- 7 files changed, 116 insertions(+), 24 deletions(-) diff --git a/apps/ensadmin/src/app/mock/config-info/page.tsx b/apps/ensadmin/src/app/mock/config-info/page.tsx index 5724789e23..d4493533a4 100644 --- a/apps/ensadmin/src/app/mock/config-info/page.tsx +++ b/apps/ensadmin/src/app/mock/config-info/page.tsx @@ -41,10 +41,13 @@ export default function MockConfigPage() { default: try { const ensApiPublicConfig = deserializeENSApiPublicConfig(mockConfigData[selectedConfig]); + const ensDbPublicConfig = { + versionInfo: { + postgresql: "18.1", + }, + }; return { - ensNodeStackInfo: buildEnsNodeStackInfo(ensApiPublicConfig, { - postgreSqlVersion: "18.1", - }), + ensNodeStackInfo: buildEnsNodeStackInfo(ensApiPublicConfig, ensDbPublicConfig), } satisfies ENSNodeConfigInfoViewProps; } catch (error) { const errorMessage = diff --git a/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts b/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts index da399eb25a..b23533fbc5 100644 --- a/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts +++ b/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts @@ -13,8 +13,10 @@ import { type SerializedChainIndexingStatusSnapshotFollowing, type SerializedChainIndexingStatusSnapshotQueued, type SerializedEnsApiPublicConfig, + type SerializedEnsDbPublicConfig, type SerializedEnsIndexerPublicConfig, type SerializedEnsNodeStackInfo, + SerializedEnsRainbowPublicConfig, type SerializedOmnichainIndexingStatusSnapshotBackfill, type SerializedOmnichainIndexingStatusSnapshotCompleted, type SerializedOmnichainIndexingStatusSnapshotFollowing, @@ -67,13 +69,20 @@ export const serializedEnsApiPublicConfig = { }, } satisfies SerializedEnsApiPublicConfig; +const serializedEnsDbPublicConfig = { + versionInfo: { + postgresql: "18.1", + }, +} satisfies SerializedEnsDbPublicConfig; + +const serializedEnsRainbowPublicConfig = + serializedEnsIndexerPublicConfig.ensRainbowPublicConfig satisfies SerializedEnsRainbowPublicConfig; + const serializedStackInfo = { ensApi: serializedEnsApiPublicConfig, - ensDb: { - postgreSqlVersion: "16", - }, + ensDb: serializedEnsDbPublicConfig, ensIndexer: serializedEnsIndexerPublicConfig, - ensRainbow: serializedEnsIndexerPublicConfig.ensRainbowPublicConfig, + ensRainbow: serializedEnsRainbowPublicConfig, } satisfies SerializedEnsNodeStackInfo; export const indexingStatusResponseError: IndexingStatusResponseError = { diff --git a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx index 4762b23f7d..47baa1022f 100644 --- a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx +++ b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx @@ -405,7 +405,9 @@ function ENSNodeConfigCardContent({ ensNodeStackInfo }: { ensNodeStackInfo: EnsN Postgres {ensDbPublicConfig.postgreSqlVersion}

+

+ Postgres {ensDbPublicConfig.versionInfo.postgresql} +

} /> { - const postgreSqlVersion = await this.getPostgreSqlVersion(); + const versionInfo = await this.buildEnsDbVersionInfo(); return { - postgreSqlVersion, + versionInfo, }; } @@ -226,7 +227,7 @@ export class EnsDbReader< * * @throws when the version cannot be retrieved or parsed from the query result. */ - private async getPostgreSqlVersion(): Promise { + private async getPostgresVersion(): Promise { const result = await this.ensDb.execute<{ version: string }>("SELECT version();"); // result will be in the form of [{ version: "PostgreSQL 15.5 (Ubuntu 15.5-0ubuntu0.22.04.1) ..." }] @@ -251,4 +252,18 @@ export class EnsDbReader< return parsedVersion; } + + /** + * Build ENSDb version info. + * + * @throws when version info cannot be retrieved or parsed from + * the ENSDb instance. + */ + private async buildEnsDbVersionInfo(): Promise { + const postgresVersion = await this.getPostgresVersion(); + + return { + postgresql: postgresVersion, + }; + } } diff --git a/packages/ensnode-sdk/src/ensdb/config.ts b/packages/ensnode-sdk/src/ensdb/config.ts index 8b046bc6db..7aef05275a 100644 --- a/packages/ensnode-sdk/src/ensdb/config.ts +++ b/packages/ensnode-sdk/src/ensdb/config.ts @@ -1,9 +1,19 @@ +/** + * Version info about ENSIndexer and its dependencies. + */ +export interface EnsDbVersionInfo { + /** + * Version of the PostgreSQL server hosting the ENSDb instance. + */ + postgresql: string; +} + /** * Complete public configuration object for ENSDb. */ export interface EnsDbPublicConfig { /** - * Version of the PostgreSQL server hosting the ENSDb instance. + * Version info about ENSDb. */ - postgreSqlVersion: string; + versionInfo: EnsDbVersionInfo; } diff --git a/packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts b/packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts index da68a1030b..1d9db9ed3b 100644 --- a/packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts +++ b/packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts @@ -1,14 +1,24 @@ import { z } from "zod/v4"; -export const makeEnsDbPublicConfigSchema = (valueLabel?: string) => { - const label = valueLabel ?? "EnsDbPublicConfig"; +const makeEnsDbVersionInfoSchema = (valueLabel?: string) => { + const label = valueLabel ?? "EnsDbVersionInfo"; return z .object({ - postgreSqlVersion: z + postgresql: z .string() - .nonempty(`${label}.postgreSqlVersion must be a non-empty string`) + .nonempty(`${label}.postgresql must be a non-empty string`) .describe("Version of the PostgreSQL server hosting the ENSDb instance."), }) .describe(label); }; + +export const makeEnsDbPublicConfigSchema = (valueLabel?: string) => { + const label = valueLabel ?? "EnsDbPublicConfig"; + + return z + .object({ + versionInfo: makeEnsDbVersionInfoSchema(`${label}.versionInfo`), + }) + .describe(label); +}; diff --git a/packages/ensnode-sdk/src/ensnode/client.test.ts b/packages/ensnode-sdk/src/ensnode/client.test.ts index b7c531801d..8d2b99cc59 100644 --- a/packages/ensnode-sdk/src/ensnode/client.test.ts +++ b/packages/ensnode-sdk/src/ensnode/client.test.ts @@ -3,7 +3,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { ENSNamespaceIds } from "../ens"; import type { SerializedEnsApiPublicConfig } from "../ensapi/config/serialized-types"; +import type { SerializedEnsDbPublicConfig } from "../ensdb/serialize/config"; +import type { SerializedEnsIndexerPublicConfig } from "../ensindexer/config/serialized-types"; import { PluginName } from "../ensindexer/config/types"; +import type { SerializedEnsRainbowPublicConfig } from "../ensrainbow/serialize/config"; import { ChainIndexingStatusIds } from "../indexing-status/chain-indexing-status-snapshot"; import { CrossChainIndexingStrategyIds } from "../indexing-status/cross-chain-indexing-status-snapshot"; import { OmnichainIndexingStatusIds } from "../indexing-status/omnichain-indexing-status-snapshot"; @@ -57,7 +60,7 @@ const EXAMPLE_PRIMARY_NAMES_RESPONSE = { const EXAMPLE_ERROR_RESPONSE: ErrorResponse = { message: "error" }; -const EXAMPLE_CONFIG_RESPONSE = { +const EXAMPLE_ENSAPI_CONFIG_RESPONSE = { versionInfo: { ensApi: "1.9.0", ensNormalize: "1.11.1", @@ -97,13 +100,53 @@ const EXAMPLE_CONFIG_RESPONSE = { }, } satisfies SerializedEnsApiPublicConfig; -const serializedStackInfo = { - ensApi: EXAMPLE_CONFIG_RESPONSE, - ensDb: { - postgreSqlVersion: "16", +const EXAMPLE_ENSDB_PUBLIC_RESPONSE = { + versionInfo: { + postgresql: "18.1", + }, +} satisfies SerializedEnsDbPublicConfig; + +const EXAMPLE_ENSINDEXER_PUBLIC_CONFIG = { + ensRainbowPublicConfig: { + version: "0.31.0", + labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + recordsCount: 100, + }, + labelSet: { + labelSetId: "subgraph", + labelSetVersion: 0, }, - ensIndexer: EXAMPLE_CONFIG_RESPONSE.ensIndexerPublicConfig, - ensRainbow: EXAMPLE_CONFIG_RESPONSE.ensIndexerPublicConfig.ensRainbowPublicConfig, + indexedChainIds: [1, 8453, 59144, 10, 42161, 534352], + ensIndexerSchemaName: "alphaSchema0.31.0", + isSubgraphCompatible: false, + namespace: "mainnet", + plugins: [ + PluginName.Subgraph, + PluginName.Basenames, + PluginName.Lineanames, + PluginName.ThreeDNS, + PluginName.ProtocolAcceleration, + PluginName.Registrars, + ], + versionInfo: { + ponder: "0.11.43", + ensDb: "0.32.0", + ensIndexer: "0.32.0", + ensNormalize: "1.11.1", + }, +} satisfies SerializedEnsIndexerPublicConfig; + +const EXAMPLE_ENSRAINBOW_PUBLIC_CONFIG = { + version: "0.31.0", + labelSet: { labelSetId: "subgraph", highestLabelSetVersion: 0 }, + recordsCount: 100, +} satisfies SerializedEnsRainbowPublicConfig; + +const serializedStackInfo = { + ensApi: EXAMPLE_ENSAPI_CONFIG_RESPONSE, + ensDb: EXAMPLE_ENSDB_PUBLIC_RESPONSE, + ensIndexer: EXAMPLE_ENSINDEXER_PUBLIC_CONFIG, + ensRainbow: EXAMPLE_ENSRAINBOW_PUBLIC_CONFIG, } satisfies SerializedEnsNodeStackInfo; const EXAMPLE_INDEXING_STATUS_BACKFILL_RESPONSE = deserializeEnsApiIndexingStatusResponse({ From 940b4012fdf1cd1c4bcd9acdca8f9f6c21a68b8c Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 21 Apr 2026 11:35:31 +0200 Subject: [PATCH 49/56] Apply JSDoc updates --- .changeset/cool-hotels-dress.md | 2 +- .../connections/require-active-connection.tsx | 2 +- apps/ensapi/src/cache/stack-info.cache.ts | 11 ++++++----- apps/ensapi/src/middleware/stack-info.middleware.ts | 10 ++++++++++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.changeset/cool-hotels-dress.md b/.changeset/cool-hotels-dress.md index 3922333769..56e1b104e8 100644 --- a/.changeset/cool-hotels-dress.md +++ b/.changeset/cool-hotels-dress.md @@ -2,4 +2,4 @@ "@ensnode/ensnode-sdk": minor --- -Fully removed the deprecated `ENSNodeClient` (use `EnsNodeClient` instead). +**Breaking**: Renamed `ENSNodeClient` to `EnsNodeClient`. diff --git a/apps/ensadmin/src/components/connections/require-active-connection.tsx b/apps/ensadmin/src/components/connections/require-active-connection.tsx index 5e1c522203..cf48ccbef1 100644 --- a/apps/ensadmin/src/components/connections/require-active-connection.tsx +++ b/apps/ensadmin/src/components/connections/require-active-connection.tsx @@ -18,7 +18,7 @@ export function RequireActiveConnection({ children }: PropsWithChildren) { return (
diff --git a/apps/ensapi/src/cache/stack-info.cache.ts b/apps/ensapi/src/cache/stack-info.cache.ts index d95739521c..7bc864bf6f 100644 --- a/apps/ensapi/src/cache/stack-info.cache.ts +++ b/apps/ensapi/src/cache/stack-info.cache.ts @@ -14,11 +14,12 @@ import { ensDbClient } from "@/lib/ensdb/singleton"; import { lazyProxy } from "@/lib/lazy"; /** - * Loads the ENS Node stack info, either from cache if available, + * Loads the ENSNode stack info, either from cache if available, * or by building it from the public configs of ENSApi and ENSDb. * - * The ENSNode Stack Info object is considered immutable, so once - * it is successfully loaded, it will be cached indefinitely. + * The ENSNode Stack Info object is considered immutable for + * the lifecycle of an ENSApi process instance, so once it is successfully + * loaded, it will be cached indefinitely. */ async function loadEnsNodeStackInfo( cachedResult?: CachedResult, @@ -38,7 +39,7 @@ async function loadEnsNodeStackInfo( // SWRCache with proactivelyInitialize:true starts background polling immediately // on construction, which would trigger ensDbClient before env vars are available. /** - * Cache for ENS Node stack info + * Cache for ENSNode stack info * Once successfully loaded, the ENSNode Stack Info is cached indefinitely and * never revalidated. This ensures the JSON is only fetched once during * the application lifecycle. @@ -52,7 +53,7 @@ async function loadEnsNodeStackInfo( export const stackInfoCache = lazyProxy( () => /** - * Cache for ENS Node stack info + * Cache for ENSNode stack info * * Once initialized successfully, this cache will always return * the same stack info for the lifecycle of the ENSApi instance. diff --git a/apps/ensapi/src/middleware/stack-info.middleware.ts b/apps/ensapi/src/middleware/stack-info.middleware.ts index 03c3e27736..7011ff6386 100644 --- a/apps/ensapi/src/middleware/stack-info.middleware.ts +++ b/apps/ensapi/src/middleware/stack-info.middleware.ts @@ -9,6 +9,16 @@ const logger = makeLogger("stack-info.middleware"); export interface StackInfoMiddlewareVariables { /** * ENSNode Stack Info + * + * If there was an issue retrieving the Stack Info, it will be set to + * an `Error` so that downstream middlewares and handlers can handle + * the error appropriately. + * + * In the case of a successful retrieval, this will be + * the ENSNode Stack Info object, which is considered immutable for + * the lifecycle of the ENSApi instance. Therefore, once successfully loaded, + * the same Stack Info object will be returned for every request and + * cached indefinitely. */ stackInfo: EnsNodeStackInfo | Error; } From 74685bde1759cb375296605931f8b1f68be1911b Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 21 Apr 2026 11:37:53 +0200 Subject: [PATCH 50/56] Fix OpenAPI Spec output for `highestLabelSetVersion` field --- apps/ensindexer/src/config/config.test.ts | 2 +- docs/docs.ensnode.io/ensapi-openapi.json | 26 +++++++++++++------ .../ensindexer/config/labelset-utils.test.ts | 8 ++++-- .../src/ensindexer/config/labelset-utils.ts | 4 +-- .../src/ensindexer/config/zod-schemas.test.ts | 2 +- .../src/ensindexer/config/zod-schemas.ts | 4 +-- .../src/ensrainbow/zod-schemas/config.ts | 17 ++++++++---- 7 files changed, 42 insertions(+), 21 deletions(-) diff --git a/apps/ensindexer/src/config/config.test.ts b/apps/ensindexer/src/config/config.test.ts index 3052b4b87b..40a21dfbb9 100644 --- a/apps/ensindexer/src/config/config.test.ts +++ b/apps/ensindexer/src/config/config.test.ts @@ -650,7 +650,7 @@ describe("config (with base env)", () => { it("throws an error when LABEL_SET_VERSION is not a number", async () => { vi.stubEnv("LABEL_SET_VERSION", "not-a-number"); - await expect(getConfig()).rejects.toThrow(/LABEL_SET_VERSION must be an integer/); + await expect(getConfig()).rejects.toThrow(/LABEL_SET_VERSION must be a non-negative integer/); }); it("accepts zero as a valid LABEL_SET_VERSION", async () => { diff --git a/docs/docs.ensnode.io/ensapi-openapi.json b/docs/docs.ensnode.io/ensapi-openapi.json index db5c3b4616..e5b16253a1 100644 --- a/docs/docs.ensnode.io/ensapi-openapi.json +++ b/docs/docs.ensnode.io/ensapi-openapi.json @@ -802,7 +802,10 @@ "maxLength": 50, "pattern": "^[a-z-]+$" }, - "highestLabelSetVersion": { "type": ["number", "null"] } + "highestLabelSetVersion": { + "type": "integer", + "minimum": 0 + } }, "required": ["labelSetId", "highestLabelSetVersion"] }, @@ -903,13 +906,20 @@ "ensDb": { "type": "object", "properties": { - "postgreSqlVersion": { - "type": "string", - "minLength": 1, - "description": "Version of the PostgreSQL server hosting the ENSDb instance." + "versionInfo": { + "type": "object", + "properties": { + "postgresql": { + "type": "string", + "minLength": 1, + "description": "Version of the PostgreSQL server hosting the ENSDb instance." + } + }, + "required": ["postgresql"], + "description": "Serialized Indexing Status Response OK.ensDb.versionInfo" } }, - "required": ["postgreSqlVersion"], + "required": ["versionInfo"], "description": "Serialized Indexing Status Response OK.ensDb" }, "ensIndexer": { @@ -929,7 +939,7 @@ "maxLength": 50, "pattern": "^[a-z-]+$" }, - "highestLabelSetVersion": { "type": ["number", "null"] } + "highestLabelSetVersion": { "type": "integer", "minimum": 0 } }, "required": ["labelSetId", "highestLabelSetVersion"] }, @@ -1000,7 +1010,7 @@ "maxLength": 50, "pattern": "^[a-z-]+$" }, - "highestLabelSetVersion": { "type": ["number", "null"] } + "highestLabelSetVersion": { "type": "integer", "minimum": 0 } }, "required": ["labelSetId", "highestLabelSetVersion"] }, diff --git a/packages/ensnode-sdk/src/ensindexer/config/labelset-utils.test.ts b/packages/ensnode-sdk/src/ensindexer/config/labelset-utils.test.ts index 198bc2836f..218779ad42 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/labelset-utils.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/labelset-utils.test.ts @@ -62,11 +62,15 @@ describe("buildLabelSetVersion", () => { }); it("should throw an error for NaN", () => { - expect(() => buildLabelSetVersion(NaN)).toThrow("LabelSetVersion must be an integer"); + expect(() => buildLabelSetVersion(NaN)).toThrow( + "LabelSetVersion must be a non-negative integer", + ); }); it("should throw an error for Infinity", () => { - expect(() => buildLabelSetVersion(Infinity)).toThrow("LabelSetVersion must be an integer"); + expect(() => buildLabelSetVersion(Infinity)).toThrow( + "LabelSetVersion must be a non-negative integer", + ); }); it("should return a valid label set version for a string input", () => { diff --git a/packages/ensnode-sdk/src/ensindexer/config/labelset-utils.ts b/packages/ensnode-sdk/src/ensindexer/config/labelset-utils.ts index 5661d211c6..9f4dad7880 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/labelset-utils.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/labelset-utils.ts @@ -6,7 +6,7 @@ import type { } from "../../ensrainbow"; import { makeLabelSetIdSchema, - makeLabelSetVersionSchema, + makeLabelSetVersionStringSchema, } from "../../ensrainbow/zod-schemas/config"; /** @@ -26,7 +26,7 @@ export function buildLabelSetId(maybeLabelSetId: string): LabelSetId { * @throws If the input is not a valid LabelSetVersion. */ export function buildLabelSetVersion(maybeLabelSetVersion: number | string): LabelSetVersion { - return makeLabelSetVersionSchema("LabelSetVersion").parse(maybeLabelSetVersion); + return makeLabelSetVersionStringSchema("LabelSetVersion").parse(maybeLabelSetVersion); } /** diff --git a/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.test.ts b/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.test.ts index 3d24cfa9eb..fcc9adab74 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.test.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.test.ts @@ -245,7 +245,7 @@ describe("ENSIndexer: Config", () => { }), ), ), - ).toContain("labelSet.labelSetVersion must be an integer"); + ).toContain("labelSet.labelSetVersion must be a non-negative integer"); }); }); diff --git a/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.ts b/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.ts index 3563087181..68f32f9987 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/zod-schemas.ts @@ -12,7 +12,7 @@ import type { EnsRainbowClientLabelSet, EnsRainbowServerLabelSet } from "../../e import { makeEnsRainbowPublicConfigSchema, makeLabelSetIdSchema, - makeLabelSetVersionSchema, + makeLabelSetVersionStringSchema, } from "../../ensrainbow/zod-schemas/config"; import { uniq } from "../../shared/collections"; import { makeChainIdSchema, makeENSNamespaceIdSchema } from "../../shared/zod-schemas"; @@ -88,7 +88,7 @@ export const makeFullyPinnedLabelSetSchema = (valueLabel: string = "Label set") } return z.object({ labelSetId: makeLabelSetIdSchema(valueLabelLabelSetId), - labelSetVersion: makeLabelSetVersionSchema(valueLabelLabelSetVersion), + labelSetVersion: makeLabelSetVersionStringSchema(valueLabelLabelSetVersion), }); }; diff --git a/packages/ensnode-sdk/src/ensrainbow/zod-schemas/config.ts b/packages/ensnode-sdk/src/ensrainbow/zod-schemas/config.ts index d0022f3103..b8aff5a513 100644 --- a/packages/ensnode-sdk/src/ensrainbow/zod-schemas/config.ts +++ b/packages/ensnode-sdk/src/ensrainbow/zod-schemas/config.ts @@ -27,11 +27,18 @@ export const makeLabelSetIdSchema = (valueLabel: string = "Label set ID") => { * * @param valueLabel - The label to use in error messages (e.g., "Label set version", "LABEL_SET_VERSION") */ -export const makeLabelSetVersionSchema = (valueLabel: string = "Label set version") => { - return z.coerce - .number({ error: `${valueLabel} must be an integer.` }) - .pipe(makeNonNegativeIntegerSchema(valueLabel)); -}; +export const makeLabelSetVersionSchema = (valueLabel: string = "Label set version") => + makeNonNegativeIntegerSchema(valueLabel); + +/** + * Makes a schema for parsing a label set version string. + * + * @param valueLabel - The label to use in error messages (e.g., "Label set version", "LABEL_SET_VERSION") + */ +export const makeLabelSetVersionStringSchema = (valueLabel: string = "Label set version") => + z.coerce + .number({ error: `${valueLabel} must be a non-negative integer` }) + .pipe(makeLabelSetVersionSchema(valueLabel)); /** * Makes a schema for parsing the EnsRainbowPublicConfig object. From ec4d4a5fd635c2edafd8ac818f021c18fc202101 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 21 Apr 2026 12:14:08 +0200 Subject: [PATCH 51/56] Cleanup teminology around "ENSNode Client" --- packages/ens-referrals/README.md | 2 +- packages/ens-referrals/src/client.ts | 8 +++---- packages/ensnode-react/src/types.ts | 4 ++-- packages/ensnode-sdk/README.md | 23 +++---------------- .../ensnode-sdk/src/ensnode/client.test.ts | 22 +++++++++--------- packages/ensnode-sdk/src/ensnode/client.ts | 18 ++++----------- .../src/ensnode/deployments.test.ts | 10 ++++---- .../ensnode-sdk/src/ensnode/deployments.ts | 8 +++---- packages/ensnode-sdk/src/ensnode/index.ts | 1 - .../deserialize/ensnode-stack-info.ts | 4 ++++ .../src/stack-info/ensnode-stack-info.ts | 5 ++-- 11 files changed, 42 insertions(+), 63 deletions(-) diff --git a/packages/ens-referrals/README.md b/packages/ens-referrals/README.md index 394acad876..8a2a3111a8 100644 --- a/packages/ens-referrals/README.md +++ b/packages/ens-referrals/README.md @@ -17,7 +17,7 @@ npm install @namehash/ens-referrals viem ```typescript import { ENSReferralsClient } from "@namehash/ens-referrals"; -// Create a client with the default ENSNode API URL +// Create a client with the default ENSNode URL const client = new ENSReferralsClient(); ``` diff --git a/packages/ens-referrals/src/client.ts b/packages/ens-referrals/src/client.ts index f22225f46d..262b34c360 100644 --- a/packages/ens-referrals/src/client.ts +++ b/packages/ens-referrals/src/client.ts @@ -18,20 +18,20 @@ import { } from "./edition"; /** - * Default ENSNode API endpoint URL + * Default ENSNode endpoint URL */ export const DEFAULT_ENSNODE_API_URL = "https://api.alpha.ensnode.io" as const; /** - * Configuration options for ENS Referrals API client + * Configuration options for an ENS Referrals client */ export interface ClientOptions { - /** The ENSNode API URL */ + /** The ENSNode URL */ url: URL; } /** - * ENS Referrals API Client + * ENS Referrals Client * * Provides access to ENS Referrals data and leaderboard information. * diff --git a/packages/ensnode-react/src/types.ts b/packages/ensnode-react/src/types.ts index 2038febab5..0171376e60 100644 --- a/packages/ensnode-react/src/types.ts +++ b/packages/ensnode-react/src/types.ts @@ -15,10 +15,10 @@ import type { } from "@ensnode/ensnode-sdk"; /** - * Configuration options for the ENSApi provider + * Configuration options for the ENSNode provider */ export interface EnsNodeProviderOptions { - /** The ENSApi client configuration */ + /** The ENSNode client configuration */ client: EnsNodeClientOptions; } diff --git a/packages/ensnode-sdk/README.md b/packages/ensnode-sdk/README.md index 7a2314d489..b926e7deb2 100644 --- a/packages/ensnode-sdk/README.md +++ b/packages/ensnode-sdk/README.md @@ -12,10 +12,9 @@ npm install @ensnode/ensnode-sdk ## EnsNodeClient -The `EnsNodeClient` provides a unified interface for the ENSApi REST APIs: +The `EnsNodeClient` provides a unified interface for the ENSNode REST APIs: - Resolution API (Protocol Accelerated Forward/Reverse Resolution) - Indexing Status API -- Configuration API ### Basic Usage @@ -152,21 +151,6 @@ console.log(names); // } ``` -#### Configuration API - -##### `config()` - -Fetches the ENSNode's configuration. - -- Returns: `ConfigResponse` - The ENSNode configuration data -- Throws: Error if the request fails or the ENSNode API returns an error response - -```ts -const config = await client.config(); -console.log(config); -// Returns the ENSNode configuration including indexed chains, etc. -``` - #### Indexing Status API ##### `indexingStatus()` @@ -178,9 +162,8 @@ Fetches the ENSNode's multichain indexing status. ```ts // Get current indexing status -const status = await client.indexingStatus(); -console.log(status); -// Returns indexing status for all indexed chains +const indexingStatusResponse = await client.indexingStatus(); +console.log(indexingStatusResponse); // Includes indexing status for all indexed chains ``` ### Configuration diff --git a/packages/ensnode-sdk/src/ensnode/client.test.ts b/packages/ensnode-sdk/src/ensnode/client.test.ts index 8d2b99cc59..d23e7a9ce0 100644 --- a/packages/ensnode-sdk/src/ensnode/client.test.ts +++ b/packages/ensnode-sdk/src/ensnode/client.test.ts @@ -28,7 +28,7 @@ import type { import type { ErrorResponse } from "./api/shared/errors/response"; import { EnsNodeClient } from "./client"; import { ClientError } from "./client-error"; -import { DEFAULT_ENSNODE_API_URL_MAINNET, getDefaultEnsNodeUrl } from "./deployments"; +import { DEFAULT_ENSNODE_URL_MAINNET, getDefaultEnsNodeUrl } from "./deployments"; const EXAMPLE_NAME: Name = "example.eth"; const EXAMPLE_ADDRESS: Address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; @@ -299,7 +299,7 @@ describe("EnsNodeClient", () => { const expectedUrl = new URL( `/api/resolve/records/${EXAMPLE_NAME}`, - DEFAULT_ENSNODE_API_URL_MAINNET, + DEFAULT_ENSNODE_URL_MAINNET, ); expectedUrl.searchParams.set("addresses", EXAMPLE_SELECTION.addresses.join(",")); expectedUrl.searchParams.set("texts", EXAMPLE_SELECTION.texts.join(",")); @@ -319,7 +319,7 @@ describe("EnsNodeClient", () => { const expectedUrl = new URL( `/api/resolve/records/${EXAMPLE_NAME}`, - DEFAULT_ENSNODE_API_URL_MAINNET, + DEFAULT_ENSNODE_URL_MAINNET, ); expectedUrl.searchParams.set("addresses", EXAMPLE_SELECTION.addresses.join(",")); expectedUrl.searchParams.set("texts", EXAMPLE_SELECTION.texts.join(",")); @@ -351,7 +351,7 @@ describe("EnsNodeClient", () => { const expectedUrl = new URL( `/api/resolve/primary-name/${EXAMPLE_ADDRESS}/1`, - DEFAULT_ENSNODE_API_URL_MAINNET, + DEFAULT_ENSNODE_URL_MAINNET, ); expect(mockFetch).toHaveBeenCalledWith(expectedUrl); @@ -367,7 +367,7 @@ describe("EnsNodeClient", () => { const expectedUrl = new URL( `/api/resolve/primary-name/${EXAMPLE_ADDRESS}/1`, - DEFAULT_ENSNODE_API_URL_MAINNET, + DEFAULT_ENSNODE_URL_MAINNET, ); expectedUrl.searchParams.set("trace", "true"); @@ -386,7 +386,7 @@ describe("EnsNodeClient", () => { const expectedUrl = new URL( `/api/resolve/primary-name/${EXAMPLE_ADDRESS}/1`, - DEFAULT_ENSNODE_API_URL_MAINNET, + DEFAULT_ENSNODE_URL_MAINNET, ); expectedUrl.searchParams.set("accelerate", "true"); @@ -413,7 +413,7 @@ describe("EnsNodeClient", () => { const expectedUrl = new URL( `/api/resolve/primary-names/${EXAMPLE_ADDRESS}`, - DEFAULT_ENSNODE_API_URL_MAINNET, + DEFAULT_ENSNODE_URL_MAINNET, ); expect(mockFetch).toHaveBeenCalledWith(expectedUrl); @@ -431,7 +431,7 @@ describe("EnsNodeClient", () => { const expectedUrl = new URL( `/api/resolve/primary-names/${EXAMPLE_ADDRESS}`, - DEFAULT_ENSNODE_API_URL_MAINNET, + DEFAULT_ENSNODE_URL_MAINNET, ); expectedUrl.searchParams.set("chainIds", "1,10"); @@ -447,7 +447,7 @@ describe("EnsNodeClient", () => { const expectedUrl = new URL( `/api/resolve/primary-names/${EXAMPLE_ADDRESS}`, - DEFAULT_ENSNODE_API_URL_MAINNET, + DEFAULT_ENSNODE_URL_MAINNET, ); expectedUrl.searchParams.set("trace", "true"); @@ -466,7 +466,7 @@ describe("EnsNodeClient", () => { const expectedUrl = new URL( `/api/resolve/primary-names/${EXAMPLE_ADDRESS}`, - DEFAULT_ENSNODE_API_URL_MAINNET, + DEFAULT_ENSNODE_URL_MAINNET, ); expectedUrl.searchParams.set("accelerate", "true"); @@ -484,7 +484,7 @@ describe("EnsNodeClient", () => { describe("Indexing Status API", () => { it("can fetch overall indexing 'backfill' status object successfully", async () => { // arrange - const requestUrl = new URL(`/api/indexing-status`, DEFAULT_ENSNODE_API_URL_MAINNET); + const requestUrl = new URL(`/api/indexing-status`, DEFAULT_ENSNODE_URL_MAINNET); const mockedResponse = EXAMPLE_INDEXING_STATUS_BACKFILL_RESPONSE; const client = new EnsNodeClient(); diff --git a/packages/ensnode-sdk/src/ensnode/client.ts b/packages/ensnode-sdk/src/ensnode/client.ts index 09c8fbf814..eccef4fed1 100644 --- a/packages/ensnode-sdk/src/ensnode/client.ts +++ b/packages/ensnode-sdk/src/ensnode/client.ts @@ -28,26 +28,18 @@ import { ClientError } from "./client-error"; import { getDefaultEnsNodeUrl } from "./deployments"; /** - * Configuration options for ENSNode API client + * Configuration options for an ENSNode client */ export interface EnsNodeClientOptions { - /** The ENSNode API URL */ + /** The ENSNode URL */ url: URL; } /** - * Configuration options for ENSNode API client - * - * @deprecated Use {@link EnsNodeClientOptions} instead. - */ -export type ClientOptions = EnsNodeClientOptions; - -/** - * ENSNode API Client + * ENSNode Client * * Provides access to the following ENSNode APIs: * - Resolution API - * - Configuration API * - Indexing Status API * - Registrar Actions API * - Name Tokens API @@ -70,7 +62,7 @@ export type ClientOptions = EnsNodeClientOptions; * ```typescript * import { ENSNamespaceIds, EnsNodeClient, getDefaultEnsNodeUrl } from "@ensnode/ensnode-sdk"; * - * // Use default ENSNode API URL for Mainnet + * // Use default ENSNode URL for Mainnet * const client = new EnsNodeClient({ * url: getDefaultEnsNodeUrl(ENSNamespaceIds.Mainnet), * }); @@ -80,7 +72,7 @@ export type ClientOptions = EnsNodeClientOptions; * ```typescript * import { ENSNamespaceIds, EnsNodeClient, getDefaultEnsNodeUrl } from "@ensnode/ensnode-sdk"; * - * // Use default ENSNode API URL for Sepolia + * // Use default ENSNode URL for Sepolia * const client = new EnsNodeClient({ * url: getDefaultEnsNodeUrl(ENSNamespaceIds.Sepolia), * }); diff --git a/packages/ensnode-sdk/src/ensnode/deployments.test.ts b/packages/ensnode-sdk/src/ensnode/deployments.test.ts index 8578adb2fc..83d2671661 100644 --- a/packages/ensnode-sdk/src/ensnode/deployments.test.ts +++ b/packages/ensnode-sdk/src/ensnode/deployments.test.ts @@ -2,8 +2,8 @@ import { describe, expect, it } from "vitest"; import { ENSNamespaceIds } from "../ens"; import { - DEFAULT_ENSNODE_API_URL_MAINNET, - DEFAULT_ENSNODE_API_URL_SEPOLIA, + DEFAULT_ENSNODE_URL_MAINNET, + DEFAULT_ENSNODE_URL_SEPOLIA, getDefaultEnsNodeUrl, } from "./deployments"; @@ -11,19 +11,19 @@ describe("getDefaultEnsNodeUrl", () => { it("returns the mainnet default URL when no namespace is provided", () => { const url = getDefaultEnsNodeUrl(); - expect(url.href).toBe(`${DEFAULT_ENSNODE_API_URL_MAINNET}/`); + expect(url.href).toBe(`${DEFAULT_ENSNODE_URL_MAINNET}/`); }); it("returns the mainnet default URL", () => { const url = getDefaultEnsNodeUrl(ENSNamespaceIds.Mainnet); - expect(url.href).toBe(`${DEFAULT_ENSNODE_API_URL_MAINNET}/`); + expect(url.href).toBe(`${DEFAULT_ENSNODE_URL_MAINNET}/`); }); it("returns the sepolia default URL", () => { const url = getDefaultEnsNodeUrl(ENSNamespaceIds.Sepolia); - expect(url.href).toBe(`${DEFAULT_ENSNODE_API_URL_SEPOLIA}/`); + expect(url.href).toBe(`${DEFAULT_ENSNODE_URL_SEPOLIA}/`); }); it("throws for unsupported namespaces", () => { diff --git a/packages/ensnode-sdk/src/ensnode/deployments.ts b/packages/ensnode-sdk/src/ensnode/deployments.ts index 74a0138021..c889ae29e0 100644 --- a/packages/ensnode-sdk/src/ensnode/deployments.ts +++ b/packages/ensnode-sdk/src/ensnode/deployments.ts @@ -3,12 +3,12 @@ import { type ENSNamespaceId, ENSNamespaceIds } from "@ensnode/datasources"; /** * Default ENSNode API endpoint URL for Mainnet */ -export const DEFAULT_ENSNODE_API_URL_MAINNET = "https://api.alpha.ensnode.io" as const; +export const DEFAULT_ENSNODE_URL_MAINNET = "https://api.alpha.ensnode.io" as const; /** * Default ENSNode API endpoint URL for Sepolia */ -export const DEFAULT_ENSNODE_API_URL_SEPOLIA = "https://api.alpha-sepolia.ensnode.io" as const; +export const DEFAULT_ENSNODE_URL_SEPOLIA = "https://api.alpha-sepolia.ensnode.io" as const; /** * Gets the default ENSNode URL for the provided ENSNamespaceId. @@ -23,9 +23,9 @@ export const getDefaultEnsNodeUrl = (namespace?: ENSNamespaceId): URL => { const effectiveNamespace = namespace ?? ENSNamespaceIds.Mainnet; switch (effectiveNamespace) { case ENSNamespaceIds.Mainnet: - return new URL(DEFAULT_ENSNODE_API_URL_MAINNET); + return new URL(DEFAULT_ENSNODE_URL_MAINNET); case ENSNamespaceIds.Sepolia: - return new URL(DEFAULT_ENSNODE_API_URL_SEPOLIA); + return new URL(DEFAULT_ENSNODE_URL_SEPOLIA); default: throw new Error( `ENSNamespaceId ${effectiveNamespace} does not have a default ENSNode URL defined`, diff --git a/packages/ensnode-sdk/src/ensnode/index.ts b/packages/ensnode-sdk/src/ensnode/index.ts index 92d57bf888..227ed19cdb 100644 --- a/packages/ensnode-sdk/src/ensnode/index.ts +++ b/packages/ensnode-sdk/src/ensnode/index.ts @@ -1,6 +1,5 @@ export * from "./api"; export { - type ClientOptions, EnsNodeClient, type EnsNodeClientOptions, } from "./client"; diff --git a/packages/ensnode-sdk/src/stack-info/deserialize/ensnode-stack-info.ts b/packages/ensnode-sdk/src/stack-info/deserialize/ensnode-stack-info.ts index 07a2a87fa0..40ee912e5a 100644 --- a/packages/ensnode-sdk/src/stack-info/deserialize/ensnode-stack-info.ts +++ b/packages/ensnode-sdk/src/stack-info/deserialize/ensnode-stack-info.ts @@ -20,6 +20,10 @@ import { export function buildUnvalidatedEnsNodeStackInfo( serializedStackInfo: SerializedEnsNodeStackInfo, ): Unvalidated { + // Stack info for ENSApi and ENSIndexer requires deserialization, + // so we handle them separately here before returning + // the final stack info object. Stack info for ENSDb and ENSRainbow can be + // passed through directly since they don't require deserialization. const { ensApi, ensIndexer, ...rest } = serializedStackInfo; return { diff --git a/packages/ensnode-sdk/src/stack-info/ensnode-stack-info.ts b/packages/ensnode-sdk/src/stack-info/ensnode-stack-info.ts index 36daa61487..94f39d1955 100644 --- a/packages/ensnode-sdk/src/stack-info/ensnode-stack-info.ts +++ b/packages/ensnode-sdk/src/stack-info/ensnode-stack-info.ts @@ -4,7 +4,7 @@ import type { EnsIndexerPublicConfig } from "../ensindexer/config/types"; import type { EnsRainbowPublicConfig } from "../ensrainbow/config"; /** - * Complete information about the ENSNode stack. + * Information about the stack of services inside an ENSNode instance. */ export interface EnsNodeStackInfo { /** @@ -25,7 +25,8 @@ export interface EnsNodeStackInfo { /** * ENSRainbow Public Config * - * Note: ENSRainbow Public Config might not be available during cold starts. + * If undefined, represents that ENSRainbow is currently undergoing + * a cold start and may take up to an hour to become ready. */ ensRainbow?: EnsRainbowPublicConfig; } From ac957d580101f6d69df1b0575ffd39e952fbf38e Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 21 Apr 2026 12:22:28 +0200 Subject: [PATCH 52/56] Add tests for parsing pg version info string --- packages/ensdb-sdk/src/client/ensdb-reader.ts | 23 ++---- .../src/lib/parse-pg-version-info.test.ts | 77 +++++++++++++++++++ .../src/lib/parse-pg-version-info.ts | 29 +++++++ 3 files changed, 112 insertions(+), 17 deletions(-) create mode 100644 packages/ensdb-sdk/src/lib/parse-pg-version-info.test.ts create mode 100644 packages/ensdb-sdk/src/lib/parse-pg-version-info.ts diff --git a/packages/ensdb-sdk/src/client/ensdb-reader.ts b/packages/ensdb-sdk/src/client/ensdb-reader.ts index 71c7a6250d..49d61d3ee1 100644 --- a/packages/ensdb-sdk/src/client/ensdb-reader.ts +++ b/packages/ensdb-sdk/src/client/ensdb-reader.ts @@ -16,6 +16,7 @@ import { type EnsDbDrizzleClient, type EnsNodeSchema, } from "../lib/drizzle"; +import { parsePgVersionInfo } from "../lib/parse-pg-version-info"; import { EnsNodeMetadataKeys } from "./ensnode-metadata"; import type { SerializedEnsNodeMetadata, @@ -233,24 +234,12 @@ export class EnsDbReader< // result will be in the form of [{ version: "PostgreSQL 15.5 (Ubuntu 15.5-0ubuntu0.22.04.1) ..." }] const versionString = result.rows[0]?.version; - if (!versionString) { - throw new Error("Failed to get PostgreSQL version from ENSDb instance"); + try { + return parsePgVersionInfo(versionString); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : "Unknown error"; + throw new Error(`Failed to get PostgreSQL version for the ENSDb instance: ${errorMessage}`); } - - // extract the version number using regex - const match = versionString.match(/PostgreSQL (\d+\.\d+)/); - - if (!match) { - throw new Error(`Failed to parse PostgreSQL version from version string: '${versionString}'`); - } - - const parsedVersion = match[1]; - - if (typeof parsedVersion !== "string") { - throw new Error(`Parsed PostgreSQL version is not a string: '${parsedVersion}'`); - } - - return parsedVersion; } /** diff --git a/packages/ensdb-sdk/src/lib/parse-pg-version-info.test.ts b/packages/ensdb-sdk/src/lib/parse-pg-version-info.test.ts new file mode 100644 index 0000000000..508126ab55 --- /dev/null +++ b/packages/ensdb-sdk/src/lib/parse-pg-version-info.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, it } from "vitest"; + +import { parsePgVersionInfo } from "./parse-pg-version-info"; + +describe("parsePgVersionInfo", () => { + it("parses standard PostgreSQL version string", () => { + const versionString = "PostgreSQL 15.5 (Ubuntu 15.5-0ubuntu0.22.04.1) on x86_64-pc-linux-gnu"; + + expect(parsePgVersionInfo(versionString)).toBe("15.5"); + }); + + it("parses PostgreSQL version string with different version numbers", () => { + const versionString = "PostgreSQL 14.2 on x86_64-pc-linux-gnu"; + + expect(parsePgVersionInfo(versionString)).toBe("14.2"); + }); + + it("parses PostgreSQL version string with single digit minor version", () => { + const versionString = "PostgreSQL 16.1 (Debian 16.1-1.pgdg120+1)"; + + expect(parsePgVersionInfo(versionString)).toBe("16.1"); + }); + + it("parses minimal PostgreSQL version string", () => { + const versionString = "PostgreSQL 17.0"; + + expect(parsePgVersionInfo(versionString)).toBe("17.0"); + }); + + it("throws error for empty string", () => { + expect(() => parsePgVersionInfo("")).toThrow( + "Failed to parse PostgreSQL version from version string: ''", + ); + }); + + it("throws error for null", () => { + expect(() => parsePgVersionInfo(null as unknown as string)).toThrow( + "PostgreSQL version string must be a string", + ); + }); + + it("throws error for undefined", () => { + expect(() => parsePgVersionInfo(undefined as unknown as string)).toThrow( + "PostgreSQL version string must be a string", + ); + }); + + it("throws error when PostgreSQL prefix is missing", () => { + const versionString = "MySQL 8.0.32 on x86_64"; + + expect(() => parsePgVersionInfo(versionString)).toThrow( + `Failed to parse PostgreSQL version from version string: '${versionString}'`, + ); + }); + + it("throws error when version format is invalid", () => { + const versionString = "PostgreSQL 15 on x86_64"; + + expect(() => parsePgVersionInfo(versionString)).toThrow( + `Failed to parse PostgreSQL version from version string: '${versionString}'`, + ); + }); + + it("throws error for completely unrelated string", () => { + const versionString = "random text without version info"; + + expect(() => parsePgVersionInfo(versionString)).toThrow( + `Failed to parse PostgreSQL version from version string: '${versionString}'`, + ); + }); + + it("parses version string with extra content after version", () => { + const versionString = "PostgreSQL 13.14 compiled by Visual C++ build 1914, 64-bit with SSL"; + + expect(parsePgVersionInfo(versionString)).toBe("13.14"); + }); +}); diff --git a/packages/ensdb-sdk/src/lib/parse-pg-version-info.ts b/packages/ensdb-sdk/src/lib/parse-pg-version-info.ts new file mode 100644 index 0000000000..7d948def4c --- /dev/null +++ b/packages/ensdb-sdk/src/lib/parse-pg-version-info.ts @@ -0,0 +1,29 @@ +/** + * Parse PostgreSQL version information from a version string. + * + * @param versionString The version string is expected to be in the format + * returned by the PostgreSQL `version()` function, + * which typically looks like: + * "PostgreSQL 15.5 (Ubuntu 15.5-0ubuntu0.22.04.1) ..." + * @returns The parsed PostgreSQL version as a string. + */ +export function parsePgVersionInfo(versionString: string | undefined): string { + if (typeof versionString !== "string") { + throw new Error("PostgreSQL version string must be a string"); + } + + // extract the version number using regex + const match = versionString.match(/PostgreSQL (\d+\.\d+)/); + + if (!match) { + throw new Error(`Failed to parse PostgreSQL version from version string: '${versionString}'`); + } + + const parsedVersion = match[1]; + + if (typeof parsedVersion !== "string") { + throw new Error(`Parsed PostgreSQL version is not a string: '${parsedVersion}'`); + } + + return parsedVersion; +} From 87523e16f63ffd4ee315a23245c1927a51789f07 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 21 Apr 2026 15:30:06 +0200 Subject: [PATCH 53/56] Rename `useActiveENSNodeConfig` hook to `useActiveEnsNodeStackInfo` Also, created an issue to evaluate if we need the `useActiveEnsNodeStackInfo` hook at all. More details here: https://github.com/namehash/ensnode/issues/1971 --- .../active/use-active-ensnode-config.tsx | 26 ----------------- .../active/use-active-ensnode-stack-info.tsx | 28 +++++++++++++++++++ .../src/hooks/active/use-active-namespace.ts | 4 +-- 3 files changed, 30 insertions(+), 28 deletions(-) delete mode 100644 apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx create mode 100644 apps/ensadmin/src/hooks/active/use-active-ensnode-stack-info.tsx diff --git a/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx b/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx deleted file mode 100644 index 879e1269aa..0000000000 --- a/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client"; - -import { useEnsNodeStackInfo } from "@/hooks/use-ensnode-stack-info"; - -/** - * Hook to get the currently active ENSNode Config synchronously. - * - * This hook provides synchronous access to the active ENSNode connection. - * If no ENSNode connection is synchronously available, components using - * this hook will throw. Components that use this hook should be a child of - * `RequireActiveConnection` such that the connected ENSNode's config is synchronously - * available during render. This simplifies state in components that only make sense - * within the context of an actively connected ENSNode. - * - * @returns The active ENSNode connection (currently only the ENSIndexer config) - * @throws Error if no active ENSNode connection is available - */ -export function useActiveENSNodeConfig() { - const { data } = useEnsNodeStackInfo(); - - if (data === undefined) { - throw new Error(`Invariant(useActiveENSNodeConfig): Expected an active ENSNode Config`); - } - - return data; -} diff --git a/apps/ensadmin/src/hooks/active/use-active-ensnode-stack-info.tsx b/apps/ensadmin/src/hooks/active/use-active-ensnode-stack-info.tsx new file mode 100644 index 0000000000..5ec4aa4fa6 --- /dev/null +++ b/apps/ensadmin/src/hooks/active/use-active-ensnode-stack-info.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { useEnsNodeStackInfo } from "@/hooks/use-ensnode-stack-info"; + +/** + * Hook to synchronously get the ENSNode Stack Info for the active ENSNode connection. + * + * This hook provides synchronous access to the active ENSNode Stack Info. + * If no ENSNode connection is synchronously available, components using + * this hook will throw. Components that use this hook should be a child of + * `RequireActiveConnection` such that the connected ENSNode's Stack Info is synchronously + * available during render. This simplifies state in components that only make sense + * within the context of an actively connected ENSNode. + * + * @returns The ENSNode Stack Info for the active ENSNode connection + * @throws Error if no active ENSNode connection is available + */ +export function useActiveEnsNodeStackInfo() { + const { data } = useEnsNodeStackInfo(); + + if (data === undefined) { + throw new Error( + `Invariant(useActiveEnsNodeStackInfo): Expected ENSNode Stack Info to be available synchronously, but it is not. Ensure that this component is a child of RequireActiveConnection.`, + ); + } + + return data; +} diff --git a/apps/ensadmin/src/hooks/active/use-active-namespace.ts b/apps/ensadmin/src/hooks/active/use-active-namespace.ts index 1d842d5f99..a34000a4d0 100644 --- a/apps/ensadmin/src/hooks/active/use-active-namespace.ts +++ b/apps/ensadmin/src/hooks/active/use-active-namespace.ts @@ -1,4 +1,4 @@ -import { useActiveENSNodeConfig } from "./use-active-ensnode-config"; +import { useActiveEnsNodeStackInfo } from "./use-active-ensnode-stack-info"; /** * Hook to get the namespace from the currently active ENSNode configuration synchronously. @@ -13,4 +13,4 @@ import { useActiveENSNodeConfig } from "./use-active-ensnode-config"; * @returns The namespace from the active ENSNode configuration * @throws Error if no active ENSNode Config is available */ -export const useActiveNamespace = () => useActiveENSNodeConfig().ensIndexer.namespace; +export const useActiveNamespace = () => useActiveEnsNodeStackInfo().ensIndexer.namespace; From 23495d059574e2169076051af01b90251f0bf257 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 21 Apr 2026 15:44:28 +0200 Subject: [PATCH 54/56] Apply AI PR feedback --- packages/ensnode-react/README.md | 2 +- packages/ensnode-react/src/provider.tsx | 8 ++++---- packages/ensnode-sdk/src/ensdb/config.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/ensnode-react/README.md b/packages/ensnode-react/README.md index e6d2dc34c1..be1bd48a99 100644 --- a/packages/ensnode-react/README.md +++ b/packages/ensnode-react/README.md @@ -126,7 +126,7 @@ function DisplayPrimaryNames() { ### EnsNodeProvider -The provider component that supplies ENSApi Provider Options to all child components. +The provider component that supplies ENSNode Provider Options to all child components. ```tsx interface EnsNodeProviderProps { diff --git a/packages/ensnode-react/src/provider.tsx b/packages/ensnode-react/src/provider.tsx index 8ee9566526..4280373d3f 100644 --- a/packages/ensnode-react/src/provider.tsx +++ b/packages/ensnode-react/src/provider.tsx @@ -26,7 +26,7 @@ export interface EnsNodeProviderProps { queryClientOptions?: ConstructorParameters[0]; } -function EnsApiInternalProvider({ +function EnsNodeInternalProvider({ children, options, }: { @@ -56,12 +56,12 @@ export function EnsNodeProvider(parameters: React.PropsWithChildren Date: Tue, 21 Apr 2026 17:55:37 +0200 Subject: [PATCH 55/56] Apply PR feedback Replaced `useActiveNamespace` hook with direct use of `useActiveEnsNodeStackInfo` hook --- apps/ensadmin/src/app/api/omnigraph/page.tsx | 4 ++-- .../src/app/inspect/primary-name/page.tsx | 4 ++-- .../src/app/inspect/primary-names/page.tsx | 4 ++-- apps/ensadmin/src/app/inspect/records/page.tsx | 4 ++-- apps/ensadmin/src/app/mock/config-info/page.tsx | 3 ++- .../name/_components/NameDetailPageContent.tsx | 4 ++-- apps/ensadmin/src/app/name/page.tsx | 4 ++-- .../latest-registrar-actions.tsx | 6 +++--- .../active/use-active-ensnode-stack-info.tsx | 14 ++++++++------ .../src/hooks/active/use-active-namespace.ts | 16 ---------------- packages/ensnode-sdk/src/ensdb/config.ts | 2 +- packages/ensnode-sdk/src/ensnode/deployments.ts | 4 ++-- 12 files changed, 28 insertions(+), 41 deletions(-) delete mode 100644 apps/ensadmin/src/hooks/active/use-active-namespace.ts diff --git a/apps/ensadmin/src/app/api/omnigraph/page.tsx b/apps/ensadmin/src/app/api/omnigraph/page.tsx index 82caf52222..a3810312ee 100644 --- a/apps/ensadmin/src/app/api/omnigraph/page.tsx +++ b/apps/ensadmin/src/app/api/omnigraph/page.tsx @@ -8,7 +8,7 @@ import { GRAPHQL_API_EXAMPLE_QUERIES } from "@ensnode/ensnode-sdk/internal"; import { GraphiQLEditor } from "@/components/graphiql-editor"; import { RequireENSAdminFeature } from "@/components/require-ensadmin-feature"; -import { useActiveNamespace } from "@/hooks/active/use-active-namespace"; +import { useActiveEnsNodeStackInfo } from "@/hooks/active/use-active-ensnode-stack-info"; import { useValidatedSelectedConnection } from "@/hooks/active/use-selected-connection"; function GraphQLPage() { @@ -16,7 +16,7 @@ function GraphQLPage() { const initialQuery = searchParams.get("query"); const initialVariables = searchParams.get("variables"); - const namespace = useActiveNamespace(); + const { namespace } = useActiveEnsNodeStackInfo().ensIndexer; const selectedConnection = useValidatedSelectedConnection(); const url = useMemo( () => new URL(`/api/omnigraph`, selectedConnection).toString(), diff --git a/apps/ensadmin/src/app/inspect/primary-name/page.tsx b/apps/ensadmin/src/app/inspect/primary-name/page.tsx index 0705f52d86..2fbbd6d7e3 100644 --- a/apps/ensadmin/src/app/inspect/primary-name/page.tsx +++ b/apps/ensadmin/src/app/inspect/primary-name/page.tsx @@ -26,7 +26,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { useActiveNamespace } from "@/hooks/active/use-active-namespace"; +import { useActiveEnsNodeStackInfo } from "@/hooks/active/use-active-ensnode-stack-info"; import { useRawConnectionUrlParam } from "@/hooks/use-connection-url-param"; import { getENSIP19SupportedChainIds } from "@/lib/get-ensip19-supported-chain-ids"; @@ -42,7 +42,7 @@ export default function ResolvePrimaryNameInspector() { const searchParams = useSearchParams(); const { retainCurrentRawConnectionUrlParam } = useRawConnectionUrlParam(); - const namespace = useActiveNamespace(); + const { namespace } = useActiveEnsNodeStackInfo().ensIndexer; const exampleAddresses = useMemo( () => getNamespaceSpecificValue(namespace, EXAMPLE_ADDRESSES), [namespace], diff --git a/apps/ensadmin/src/app/inspect/primary-names/page.tsx b/apps/ensadmin/src/app/inspect/primary-names/page.tsx index b1a3e59369..5c0c449ac7 100644 --- a/apps/ensadmin/src/app/inspect/primary-names/page.tsx +++ b/apps/ensadmin/src/app/inspect/primary-names/page.tsx @@ -16,7 +16,7 @@ import { Pill } from "@/components/pill"; import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { useActiveNamespace } from "@/hooks/active/use-active-namespace"; +import { useActiveEnsNodeStackInfo } from "@/hooks/active/use-active-ensnode-stack-info"; import { useRawConnectionUrlParam } from "@/hooks/use-connection-url-param"; import { EXAMPLE_ADDRESSES } from "../_lib/example-addresses"; @@ -29,7 +29,7 @@ export default function ResolvePrimaryNameInspector() { const searchParams = useSearchParams(); const { retainCurrentRawConnectionUrlParam } = useRawConnectionUrlParam(); - const namespace = useActiveNamespace(); + const { namespace } = useActiveEnsNodeStackInfo().ensIndexer; const exampleAddresses = useMemo( () => getNamespaceSpecificValue(namespace, EXAMPLE_ADDRESSES), [namespace], diff --git a/apps/ensadmin/src/app/inspect/records/page.tsx b/apps/ensadmin/src/app/inspect/records/page.tsx index 775cfb2ff2..3df356af23 100644 --- a/apps/ensadmin/src/app/inspect/records/page.tsx +++ b/apps/ensadmin/src/app/inspect/records/page.tsx @@ -20,7 +20,7 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { useActiveNamespace } from "@/hooks/active/use-active-namespace"; +import { useActiveEnsNodeStackInfo } from "@/hooks/active/use-active-ensnode-stack-info"; import { useRawConnectionUrlParam } from "@/hooks/use-connection-url-param"; import { DefaultRecordsSelection } from "@/lib/default-records-selection"; @@ -33,7 +33,7 @@ export default function ResolveRecordsInspector() { const searchParams = useSearchParams(); const nameFromQuery = (searchParams.get("name")?.trim() || null) as Name | null; - const namespace = useActiveNamespace(); + const { namespace } = useActiveEnsNodeStackInfo().ensIndexer; const exampleNames = useMemo( () => getNamespaceSpecificValue(namespace, EXAMPLE_NAMES), [namespace], diff --git a/apps/ensadmin/src/app/mock/config-info/page.tsx b/apps/ensadmin/src/app/mock/config-info/page.tsx index d4493533a4..bc6edc3527 100644 --- a/apps/ensadmin/src/app/mock/config-info/page.tsx +++ b/apps/ensadmin/src/app/mock/config-info/page.tsx @@ -5,6 +5,7 @@ import { useMemo, useState } from "react"; import { buildEnsNodeStackInfo, deserializeENSApiPublicConfig, + type EnsDbPublicConfig, SerializedENSApiPublicConfig, } from "@ensnode/ensnode-sdk"; @@ -45,7 +46,7 @@ export default function MockConfigPage() { versionInfo: { postgresql: "18.1", }, - }; + } satisfies EnsDbPublicConfig; return { ensNodeStackInfo: buildEnsNodeStackInfo(ensApiPublicConfig, ensDbPublicConfig), } satisfies ENSNodeConfigInfoViewProps; diff --git a/apps/ensadmin/src/app/name/_components/NameDetailPageContent.tsx b/apps/ensadmin/src/app/name/_components/NameDetailPageContent.tsx index b46147ae90..2ed9b7ddd2 100644 --- a/apps/ensadmin/src/app/name/_components/NameDetailPageContent.tsx +++ b/apps/ensadmin/src/app/name/_components/NameDetailPageContent.tsx @@ -6,7 +6,7 @@ import { ASSUME_IMMUTABLE_QUERY, useRecords } from "@ensnode/ensnode-react"; import type { ResolverRecordsSelection } from "@ensnode/ensnode-sdk"; import { Card, CardContent } from "@/components/ui/card"; -import { useActiveNamespace } from "@/hooks/active/use-active-namespace"; +import { useActiveEnsNodeStackInfo } from "@/hooks/active/use-active-ensnode-stack-info"; import { getCommonCoinTypes } from "@/lib/default-records-selection"; import { AdditionalRecords } from "./AdditionalRecords"; @@ -45,7 +45,7 @@ interface NameDetailPageContentProps { } export function NameDetailPageContent({ name }: NameDetailPageContentProps) { - const namespace = useActiveNamespace(); + const { namespace } = useActiveEnsNodeStackInfo().ensIndexer; const selection = { addresses: getCommonCoinTypes(namespace), diff --git a/apps/ensadmin/src/app/name/page.tsx b/apps/ensadmin/src/app/name/page.tsx index 221d18d4c1..3536137c8b 100644 --- a/apps/ensadmin/src/app/name/page.tsx +++ b/apps/ensadmin/src/app/name/page.tsx @@ -12,7 +12,7 @@ import { getNameDetailsRelativePath, NameLink } from "@/components/name-links"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; -import { useActiveNamespace } from "@/hooks/active/use-active-namespace"; +import { useActiveEnsNodeStackInfo } from "@/hooks/active/use-active-ensnode-stack-info"; import { useRawConnectionUrlParam } from "@/hooks/use-connection-url-param"; import { NameDetailPageContent } from "./_components/NameDetailPageContent"; @@ -62,7 +62,7 @@ export default function ExploreNamesPage() { const nameFromQuery = searchParams.get("name"); const [rawInputName, setRawInputName] = useState(""); - const namespace = useActiveNamespace(); + const { namespace } = useActiveEnsNodeStackInfo().ensIndexer; const exampleNames = useMemo( () => getNamespaceSpecificValue(namespace, EXAMPLE_NAMES), [namespace], diff --git a/apps/ensadmin/src/components/registrar-actions/latest-registrar-actions.tsx b/apps/ensadmin/src/components/registrar-actions/latest-registrar-actions.tsx index 69c25bf3a4..e5f0ad63f3 100644 --- a/apps/ensadmin/src/components/registrar-actions/latest-registrar-actions.tsx +++ b/apps/ensadmin/src/components/registrar-actions/latest-registrar-actions.tsx @@ -2,7 +2,7 @@ import { useRegistrarActions } from "@ensnode/ensnode-react"; import { RegistrarActionsOrders, RegistrarActionsResponseCodes } from "@ensnode/ensnode-sdk"; import { ErrorInfo } from "@/components/error-info"; -import { useActiveNamespace } from "@/hooks/active/use-active-namespace"; +import { useActiveEnsNodeStackInfo } from "@/hooks/active/use-active-ensnode-stack-info"; import { DisplayRegistrarActionsList, @@ -19,7 +19,7 @@ interface LatestRegistrarActionsProps { * Fetches the latest Registrar Actions and displays them. */ export function LatestRegistrarActions({ recordsPerPage }: LatestRegistrarActionsProps) { - const namespaceId = useActiveNamespace(); + const { namespace } = useActiveEnsNodeStackInfo().ensIndexer; const query = useRegistrarActions({ order: RegistrarActionsOrders.LatestRegistrarActions, recordsPerPage, @@ -40,7 +40,7 @@ export function LatestRegistrarActions({ recordsPerPage }: LatestRegistrarAction return ( ); diff --git a/apps/ensadmin/src/hooks/active/use-active-ensnode-stack-info.tsx b/apps/ensadmin/src/hooks/active/use-active-ensnode-stack-info.tsx index 5ec4aa4fa6..3121c74ced 100644 --- a/apps/ensadmin/src/hooks/active/use-active-ensnode-stack-info.tsx +++ b/apps/ensadmin/src/hooks/active/use-active-ensnode-stack-info.tsx @@ -1,26 +1,28 @@ "use client"; +import { EnsNodeStackInfo } from "@ensnode/ensnode-sdk"; + import { useEnsNodeStackInfo } from "@/hooks/use-ensnode-stack-info"; /** - * Hook to synchronously get the ENSNode Stack Info for the active ENSNode connection. + * Hook to synchronously get the {@link EnsNodeStackInfo} for the active ENSNode connection. * - * This hook provides synchronous access to the active ENSNode Stack Info. + * This hook provides synchronous access to the active {@link EnsNodeStackInfo}. * If no ENSNode connection is synchronously available, components using * this hook will throw. Components that use this hook should be a child of - * `RequireActiveConnection` such that the connected ENSNode's Stack Info is synchronously + * `RequireActiveConnection` such that {@link EnsNodeStackInfo} is synchronously * available during render. This simplifies state in components that only make sense * within the context of an actively connected ENSNode. * - * @returns The ENSNode Stack Info for the active ENSNode connection + * @returns The {@link EnsNodeStackInfo} for the active ENSNode connection * @throws Error if no active ENSNode connection is available */ -export function useActiveEnsNodeStackInfo() { +export function useActiveEnsNodeStackInfo(): EnsNodeStackInfo { const { data } = useEnsNodeStackInfo(); if (data === undefined) { throw new Error( - `Invariant(useActiveEnsNodeStackInfo): Expected ENSNode Stack Info to be available synchronously, but it is not. Ensure that this component is a child of RequireActiveConnection.`, + `Invariant(useActiveEnsNodeStackInfo): Expected 'EnsNodeStackInfo' to be available synchronously, but it is not. Ensure that this component is a child of RequireActiveConnection.`, ); } diff --git a/apps/ensadmin/src/hooks/active/use-active-namespace.ts b/apps/ensadmin/src/hooks/active/use-active-namespace.ts deleted file mode 100644 index a34000a4d0..0000000000 --- a/apps/ensadmin/src/hooks/active/use-active-namespace.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useActiveEnsNodeStackInfo } from "./use-active-ensnode-stack-info"; - -/** - * Hook to get the namespace from the currently active ENSNode configuration synchronously. - * - * This is a helper hook for accessing the active ENSNode's Config's Namespace. - * If the connected ENSNode's Config is not synchronously available, components using - * this hook will throw. Components that use this hook should be a child of - * `RequireActiveConnection` such that the connected ENSNode's config is synchronously - * available during render. This simplifies state in components that only make sense - * within the context of a connected ENSNode. - * - * @returns The namespace from the active ENSNode configuration - * @throws Error if no active ENSNode Config is available - */ -export const useActiveNamespace = () => useActiveEnsNodeStackInfo().ensIndexer.namespace; diff --git a/packages/ensnode-sdk/src/ensdb/config.ts b/packages/ensnode-sdk/src/ensdb/config.ts index 6eeaea44ac..bb2d2d0b7e 100644 --- a/packages/ensnode-sdk/src/ensdb/config.ts +++ b/packages/ensnode-sdk/src/ensdb/config.ts @@ -9,7 +9,7 @@ export interface EnsDbVersionInfo { } /** - * Complete public configuration object for ENSDb. + * The public configuration of an ENSDb instance. */ export interface EnsDbPublicConfig { /** diff --git a/packages/ensnode-sdk/src/ensnode/deployments.ts b/packages/ensnode-sdk/src/ensnode/deployments.ts index c889ae29e0..35deafe4e6 100644 --- a/packages/ensnode-sdk/src/ensnode/deployments.ts +++ b/packages/ensnode-sdk/src/ensnode/deployments.ts @@ -1,12 +1,12 @@ import { type ENSNamespaceId, ENSNamespaceIds } from "@ensnode/datasources"; /** - * Default ENSNode API endpoint URL for Mainnet + * Default ENSNode URL for Mainnet */ export const DEFAULT_ENSNODE_URL_MAINNET = "https://api.alpha.ensnode.io" as const; /** - * Default ENSNode API endpoint URL for Sepolia + * Default ENSNode URL for Sepolia */ export const DEFAULT_ENSNODE_URL_SEPOLIA = "https://api.alpha-sepolia.ensnode.io" as const; From 5913d2d01305e7ef1a24d23aa27019c2d74d13b9 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 21 Apr 2026 18:00:15 +0200 Subject: [PATCH 56/56] Refine examples for `EnsNodeClient` --- packages/ensnode-sdk/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/ensnode-sdk/README.md b/packages/ensnode-sdk/README.md index b926e7deb2..01e1836e9b 100644 --- a/packages/ensnode-sdk/README.md +++ b/packages/ensnode-sdk/README.md @@ -155,7 +155,7 @@ console.log(names); ##### `indexingStatus()` -Fetches the ENSNode's multichain indexing status. +Fetches the ENSNode's omnichain indexing status. - Returns: `IndexingStatusResponse` - The indexing status data for all indexed chains - Throws: Error if the request fails or the ENSNode API returns an error response @@ -163,7 +163,14 @@ Fetches the ENSNode's multichain indexing status. ```ts // Get current indexing status const indexingStatusResponse = await client.indexingStatus(); -console.log(indexingStatusResponse); // Includes indexing status for all indexed chains + +if (indexingStatusResponse.responseCode === EnsApiIndexingStatusResponseCodes.Ok) { + const { realtimeProjection, stackInfo } = indexingStatusResponse; + console.log("RealtimeIndexingStatusProjection:", realtimeProjection); + console.log("EnsNodeStackInfo:", stackInfo); +} else { + console.error("Error while fetching Indexing Status"); +} ``` ### Configuration