diff --git a/.changeset/common-lines-smell.md b/.changeset/common-lines-smell.md
new file mode 100644
index 0000000000..404f7917e3
--- /dev/null
+++ b/.changeset/common-lines-smell.md
@@ -0,0 +1,5 @@
+---
+"@ensnode/ensnode-sdk": minor
+---
+
+**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..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 `EnsApiClient` instead).
+**Breaking**: Renamed `ENSNodeClient` to `EnsNodeClient`.
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.
diff --git a/.changeset/fancy-mails-fall.md b/.changeset/fancy-mails-fall.md
new file mode 100644
index 0000000000..f8dc4d4373
--- /dev/null
+++ b/.changeset/fancy-mails-fall.md
@@ -0,0 +1,5 @@
+---
+"@ensnode/ensnode-sdk": minor
+---
+
+Replaced the `EnsApiConfigResponse` data model by adding `stackInfo` field to the `EnsApiIndexingStatusResponseOk` data model.
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.
diff --git a/.changeset/rare-dogs-sin.md b/.changeset/rare-dogs-sin.md
new file mode 100644
index 0000000000..7321bf5048
--- /dev/null
+++ b/.changeset/rare-dogs-sin.md
@@ -0,0 +1,7 @@
+---
+"@ensnode/ensnode-react": minor
+---
+
+**Breaking**:
+- Replaced `useENSNodeSDKConfig` hook with `useEnsNodeProviderOptions` hook.
+- Renamed `createConfig` function to `createEnsNodeProviderOptions`.
diff --git a/.changeset/silent-adults-rest.md b/.changeset/silent-adults-rest.md
new file mode 100644
index 0000000000..295d65a590
--- /dev/null
+++ b/.changeset/silent-adults-rest.md
@@ -0,0 +1,5 @@
+---
+"ensadmin": minor
+---
+
+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
new file mode 100644
index 0000000000..e2c82b3b03
--- /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 `stackInfo.ensApi` field in the OK response.
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/.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.
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-api.mock.ts b/apps/ensadmin/src/app/mock/config-api.mock.ts
deleted file mode 100644
index 734059e0f9..0000000000
--- a/apps/ensadmin/src/app/mock/config-api.mock.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { deserializeENSIndexerPublicConfig } from "@ensnode/ensnode-sdk";
-
-export const ensIndexerPublicConfig = deserializeENSIndexerPublicConfig({
- 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",
- },
-});
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/config-info/page.tsx b/apps/ensadmin/src/app/mock/config-info/page.tsx
index be3d5e14c8..bc6edc3527 100644
--- a/apps/ensadmin/src/app/mock/config-info/page.tsx
+++ b/apps/ensadmin/src/app/mock/config-info/page.tsx
@@ -2,7 +2,12 @@
import { useMemo, useState } from "react";
-import { deserializeENSApiPublicConfig, SerializedENSApiPublicConfig } from "@ensnode/ensnode-sdk";
+import {
+ buildEnsNodeStackInfo,
+ deserializeENSApiPublicConfig,
+ type EnsDbPublicConfig,
+ SerializedENSApiPublicConfig,
+} from "@ensnode/ensnode-sdk";
import {
ENSNodeConfigInfoView,
@@ -36,8 +41,15 @@ export default function MockConfigPage() {
default:
try {
- const config = deserializeENSApiPublicConfig(mockConfigData[selectedConfig]);
- return { ensApiPublicConfig: config };
+ const ensApiPublicConfig = deserializeENSApiPublicConfig(mockConfigData[selectedConfig]);
+ const ensDbPublicConfig = {
+ versionInfo: {
+ postgresql: "18.1",
+ },
+ } satisfies EnsDbPublicConfig;
+ return {
+ ensNodeStackInfo: buildEnsNodeStackInfo(ensApiPublicConfig, ensDbPublicConfig),
+ } 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 1ceffccf85..6f68cb01cf 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) {
@@ -49,18 +50,16 @@ async function fetchMockedIndexingStatus(
case OmnichainIndexingStatusIds.Backfill:
case OmnichainIndexingStatusIds.Following:
case OmnichainIndexingStatusIds.Completed: {
- const response = indexingStatusResponseOkOmnichain[
- selectedVariant
- ] as IndexingStatusResponseOk;
+ const response = indexingStatusResponseOkOmnichain[selectedVariant];
- 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 +76,14 @@ export default function MockIndexingStatusPage() {
const mockedIndexingStatus = useQuery({
queryKey: ["mock", "useIndexingStatus", selectedVariant],
queryFn: () => fetchMockedIndexingStatus(selectedVariant),
- select: (cachedSnapshot) => {
+ select: ({ responseCode, realtimeProjection, stackInfo }) => {
return {
- responseCode: IndexingStatusResponseCodes.Ok,
- realtimeProjection: createRealtimeIndexingStatusProjection(cachedSnapshot, now),
+ responseCode,
+ realtimeProjection: createRealtimeIndexingStatusProjection(
+ realtimeProjection.snapshot,
+ now,
+ ),
+ 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 914e5c329b..b23533fbc5 100644
--- a/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts
+++ b/apps/ensadmin/src/app/mock/indexing-status-api.mock.ts
@@ -1,8 +1,8 @@
import {
ChainIndexingStatusIds,
CrossChainIndexingStrategyIds,
- deserializeIndexingStatusResponse,
- type IndexingStatusResponse,
+ deserializeEnsApiIndexingStatusResponse,
+ EnsApiIndexingStatusResponseOk,
IndexingStatusResponseCodes,
type IndexingStatusResponseError,
type OmnichainIndexingStatusId,
@@ -12,21 +12,88 @@ import {
type SerializedChainIndexingStatusSnapshotCompleted,
type SerializedChainIndexingStatusSnapshotFollowing,
type SerializedChainIndexingStatusSnapshotQueued,
+ type SerializedEnsApiPublicConfig,
+ type SerializedEnsDbPublicConfig,
+ type SerializedEnsIndexerPublicConfig,
+ type SerializedEnsNodeStackInfo,
+ SerializedEnsRainbowPublicConfig,
type SerializedOmnichainIndexingStatusSnapshotBackfill,
type SerializedOmnichainIndexingStatusSnapshotCompleted,
type SerializedOmnichainIndexingStatusSnapshotFollowing,
type SerializedOmnichainIndexingStatusSnapshotUnstarted,
} 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 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;
+
+const serializedEnsDbPublicConfig = {
+ versionInfo: {
+ postgresql: "18.1",
+ },
+} satisfies SerializedEnsDbPublicConfig;
+
+const serializedEnsRainbowPublicConfig =
+ serializedEnsIndexerPublicConfig.ensRainbowPublicConfig satisfies SerializedEnsRainbowPublicConfig;
+
+const serializedStackInfo = {
+ ensApi: serializedEnsApiPublicConfig,
+ ensDb: serializedEnsDbPublicConfig,
+ ensIndexer: serializedEnsIndexerPublicConfig,
+ ensRainbow: serializedEnsRainbowPublicConfig,
+} satisfies SerializedEnsNodeStackInfo;
+
export const indexingStatusResponseError: IndexingStatusResponseError = {
responseCode: IndexingStatusResponseCodes.Error,
};
export const indexingStatusResponseOkOmnichain: Record<
OmnichainIndexingStatusId,
- IndexingStatusResponse
+ EnsApiIndexingStatusResponseOk
> = {
- [OmnichainIndexingStatusIds.Unstarted]: deserializeIndexingStatusResponse({
+ [OmnichainIndexingStatusIds.Unstarted]: deserializeEnsApiIndexingStatusResponse({
responseCode: IndexingStatusResponseCodes.Ok,
realtimeProjection: {
projectedAt: 1759409669,
@@ -85,9 +152,10 @@ export const indexingStatusResponseOkOmnichain: Record<
} satisfies SerializedOmnichainIndexingStatusSnapshotUnstarted,
},
},
+ stackInfo: serializedStackInfo,
}),
- [OmnichainIndexingStatusIds.Backfill]: deserializeIndexingStatusResponse({
+ [OmnichainIndexingStatusIds.Backfill]: deserializeEnsApiIndexingStatusResponse({
responseCode: IndexingStatusResponseCodes.Ok,
realtimeProjection: {
projectedAt: 1759409670,
@@ -163,9 +231,10 @@ export const indexingStatusResponseOkOmnichain: Record<
} satisfies SerializedOmnichainIndexingStatusSnapshotBackfill,
},
},
+ stackInfo: serializedStackInfo,
}),
- [OmnichainIndexingStatusIds.Following]: deserializeIndexingStatusResponse({
+ [OmnichainIndexingStatusIds.Following]: deserializeEnsApiIndexingStatusResponse({
responseCode: IndexingStatusResponseCodes.Ok,
realtimeProjection: {
projectedAt: 1755667460,
@@ -256,9 +325,10 @@ export const indexingStatusResponseOkOmnichain: Record<
} satisfies SerializedOmnichainIndexingStatusSnapshotFollowing,
},
},
+ stackInfo: serializedStackInfo,
}),
- [OmnichainIndexingStatusIds.Completed]: deserializeIndexingStatusResponse({
+ [OmnichainIndexingStatusIds.Completed]: deserializeEnsApiIndexingStatusResponse({
responseCode: IndexingStatusResponseCodes.Ok,
realtimeProjection: {
projectedAt: 1689337668,
@@ -293,5 +363,6 @@ export const indexingStatusResponseOkOmnichain: Record<
} satisfies SerializedOmnichainIndexingStatusSnapshotCompleted,
},
},
+ stackInfo: serializedStackInfo,
}),
};
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;
}
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/connection/cards/ensnode-info.tsx b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx
index 22f6e23c39..47baa1022f 100644
--- a/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx
+++ b/apps/ensadmin/src/components/connection/cards/ensnode-info.tsx
@@ -9,8 +9,7 @@ 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 { EnsNodeStackInfo, getENSRootChainId } from "@ensnode/ensnode-sdk";
import { ErrorInfo, type ErrorInfoProps } from "@/components/error-info";
import { ENSApiIcon } from "@/components/icons/ensnode-apps/ensapi-icon";
@@ -25,6 +24,7 @@ import { ExternalLinkWithIcon } from "@/components/link";
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 {
@@ -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,39 +140,42 @@ export function ENSNodeConfigInfoView({
);
}
- return ;
+ return ;
}
/**
* ENSNodeConfigInfo component - fetches and displays ENSNode configuration data
*/
export function ENSNodeConfigInfo() {
- const ensNodeConfigQuery = useENSNodeConfig();
+ const ensNodeStackInfo = useEnsNodeStackInfo();
- return (
-
- );
+ if (ensNodeStackInfo.isError) {
+ return (
+
+ );
+ }
+
+ if (ensNodeStackInfo.isPending) {
+ 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;
@@ -399,7 +402,14 @@ function ENSNodeConfigCardContent({
docsLink={new URL("https://ensnode.io/ensdb")}
>
- Postgres
} />
+
+ Postgres {ensDbPublicConfig.versionInfo.postgresql}
+
+ }
+ />
;
+ if (ensNodeStackInfo.status === "pending") return ;
- if (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 0ae81e9423..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
@@ -8,39 +8,58 @@ import { useCallback, useMemo } from "react";
import {
createIndexingStatusQueryOptions,
QueryParameter,
- useENSNodeSDKConfig,
+ useEnsNodeProviderOptions,
type useIndexingStatus,
useSwrQuery,
- WithSDKConfigParameter,
+ WithEnsNodeProviderOptions,
} from "@ensnode/ensnode-react";
import {
- CrossChainIndexingStatusSnapshotOmnichain,
+ type CrossChainIndexingStatusSnapshotOmnichain,
createRealtimeIndexingStatusProjection,
- type IndexingStatusRequest,
- IndexingStatusResponseCodes,
- IndexingStatusResponseOk,
+ type EnsApiIndexingStatusRequest,
+ EnsApiIndexingStatusResponseCodes,
+ type EnsApiIndexingStatusResponseOk,
+ type EnsNodeStackInfo,
} from "@ensnode/ensnode-sdk";
const DEFAULT_REFETCH_INTERVAL = secondsToMilliseconds(10);
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;
+
+ /**
+ * Stack info of the connected ENSNode.
+ */
+ stackInfo: EnsNodeStackInfo;
+}
+
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: WithEnsNodeProviderOptions & UseIndexingStatusParameters = {},
) {
- const { config, query = {} } = parameters;
- const _config = useENSNodeSDKConfig(config);
+ const { options, query = {} } = parameters;
+ const providerOptions = useEnsNodeProviderOptions(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 +67,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 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.
// - Return this non-null value.
- return response.realtimeProjection.snapshot;
+ return {
+ crossChainIndexingStatusSnapshot: response.realtimeProjection.snapshot,
+ stackInfo: response.stackInfo,
+ } satisfies CacheableIndexingStatus;
}),
[queryOptions.queryFn],
);
@@ -66,16 +89,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: CacheableIndexingStatus): 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;
+ stackInfo: cachedResult.stackInfo,
+ } satisfies EnsApiIndexingStatusResponseOk;
},
[now],
);
diff --git a/apps/ensadmin/src/components/layout-wrapper.tsx b/apps/ensadmin/src/components/layout-wrapper.tsx
index d9c910753a..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 { SelectedENSNodeProvider } 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 dff46ac856..b5277ca460 100644
--- a/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx
+++ b/apps/ensadmin/src/components/providers/selected-ensnode-provider.tsx
@@ -1,34 +1,36 @@
"use client";
-import type { PropsWithChildren } from "react";
+import { type PropsWithChildren, useMemo } from "react";
-import { ENSNodeProvider } from "@ensnode/ensnode-react";
+import { createEnsNodeProviderOptions, EnsNodeProvider } from "@ensnode/ensnode-react";
import { useSelectedConnection } from "@/hooks/active/use-selected-connection";
/**
- * Provider component that configures ENSNodeProvider with the currently
+ * Provider component that configures EnsNodeProvider with the currently
* selected ENSNode connection.
*
- * This component wraps the ENSNodeProvider 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.
+ * 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
*/
-export function SelectedENSNodeProvider({ children }: PropsWithChildren) {
+export function SelectedEnsNodeProvider({ children }: PropsWithChildren) {
const selectedConnection = useSelectedConnection();
- if (selectedConnection.validatedSelectedConnection.isValid) {
- return (
-
- {children}
-
- );
- } else {
+ const options = useMemo(() => {
+ if (!selectedConnection.validatedSelectedConnection.isValid) {
+ return undefined;
+ }
+
+ return createEnsNodeProviderOptions({
+ url: selectedConnection.validatedSelectedConnection.url,
+ });
+ }, [selectedConnection.validatedSelectedConnection]);
+
+ 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
@@ -41,4 +43,11 @@ export function SelectedENSNodeProvider({ 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};
}
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-config.tsx b/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx
deleted file mode 100644
index 6ac758338c..0000000000
--- a/apps/ensadmin/src/hooks/active/use-active-ensnode-config.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-"use client";
-
-import { useENSNodeConfig } from "@ensnode/ensnode-react";
-
-/**
- * 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
- * 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 } = useENSNodeConfig();
-
- 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..3121c74ced
--- /dev/null
+++ b/apps/ensadmin/src/hooks/active/use-active-ensnode-stack-info.tsx
@@ -0,0 +1,30 @@
+"use client";
+
+import { EnsNodeStackInfo } from "@ensnode/ensnode-sdk";
+
+import { useEnsNodeStackInfo } from "@/hooks/use-ensnode-stack-info";
+
+/**
+ * Hook to synchronously get the {@link EnsNodeStackInfo} for the active ENSNode connection.
+ *
+ * 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 {@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 {@link EnsNodeStackInfo} for the active ENSNode connection
+ * @throws Error if no active ENSNode connection is available
+ */
+export function useActiveEnsNodeStackInfo(): EnsNodeStackInfo {
+ const { data } = useEnsNodeStackInfo();
+
+ if (data === undefined) {
+ throw new Error(
+ `Invariant(useActiveEnsNodeStackInfo): Expected 'EnsNodeStackInfo' 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
deleted file mode 100644
index 5dab6e5fe7..0000000000
--- a/apps/ensadmin/src/hooks/active/use-active-namespace.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useActiveENSNodeConfig } from "./use-active-ensnode-config";
-
-/**
- * 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 = () => useActiveENSNodeConfig().ensIndexerPublicConfig.namespace;
diff --git a/apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx b/apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx
index 587781a6be..7d6ec90c9c 100644
--- a/apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx
+++ b/apps/ensadmin/src/hooks/active/use-ensadmin-features.tsx
@@ -1,6 +1,5 @@
import { useMemo } from "react";
-import { useENSNodeConfig } from "@ensnode/ensnode-react";
import {
hasOmnigraphApiConfigSupport,
hasRegistrarActionsConfigSupport,
@@ -45,11 +44,6 @@ const prerequisiteResultToFeatureStatus = (result: PrerequisiteResult): FeatureS
const CONNECTING_STATUS: FeatureStatus = { type: "connecting" };
-const CONFIG_ERROR_STATUS: FeatureStatus = {
- type: "error",
- reason: "ENSNode config could not be fetched successfully.",
-};
-
const INDEXING_STATUS_ERROR_STATUS: FeatureStatus = {
type: "error",
reason: "Indexing Status could not be fetched successfully.",
@@ -59,50 +53,43 @@ const INDEXING_STATUS_ERROR_STATUS: FeatureStatus = {
* Hook that derives whether certain ENSAdmin features are supported by the connected ENSNode.
*/
export function useENSAdminFeatures(): ENSAdminFeatures {
- const configQuery = useENSNodeConfig();
const indexingStatusQuery = useIndexingStatusWithSwr();
const registrarActions = useMemo(() => {
- if (configQuery.status === "error") return CONFIG_ERROR_STATUS;
- if (configQuery.status === "pending") return CONNECTING_STATUS;
-
- const { ensIndexerPublicConfig } = configQuery.data;
- const result = hasRegistrarActionsConfigSupport(ensIndexerPublicConfig);
- if (!result.supported) return prerequisiteResultToFeatureStatus(result);
-
- switch (indexingStatusQuery.status) {
- case "error": {
- return INDEXING_STATUS_ERROR_STATUS;
- }
- case "pending": {
- return CONNECTING_STATUS;
- }
- case "success": {
- const { realtimeProjection } = indexingStatusQuery.data;
- const { omnichainSnapshot } = realtimeProjection.snapshot;
-
- const result = hasRegistrarActionsIndexingStatusSupport(omnichainSnapshot.omnichainStatus);
- if (!result.supported) return { type: "not-ready", reason: result.reason };
- return { type: "supported" };
- }
- }
- }, [configQuery, indexingStatusQuery]);
+ if (indexingStatusQuery.status === "error") return INDEXING_STATUS_ERROR_STATUS;
+ if (indexingStatusQuery.status === "pending") return CONNECTING_STATUS;
+
+ const { ensIndexer: ensIndexerPublicConfig } = indexingStatusQuery.data.stackInfo;
+ const configSupportResult = hasRegistrarActionsConfigSupport(ensIndexerPublicConfig);
+ if (!configSupportResult.supported)
+ return prerequisiteResultToFeatureStatus(configSupportResult);
+
+ const { realtimeProjection } = indexingStatusQuery.data;
+ const { omnichainSnapshot } = realtimeProjection.snapshot;
+
+ const indexingStatusSupportResult = hasRegistrarActionsIndexingStatusSupport(
+ omnichainSnapshot.omnichainStatus,
+ );
+ if (!indexingStatusSupportResult.supported)
+ return { type: "not-ready", reason: indexingStatusSupportResult.reason };
+ return { type: "supported" };
+ }, [indexingStatusQuery]);
const subgraph: FeatureStatus = useMemo(() => {
- if (configQuery.status === "error") return CONFIG_ERROR_STATUS;
- if (configQuery.status === "pending") return CONNECTING_STATUS;
+ if (indexingStatusQuery.status === "error") return INDEXING_STATUS_ERROR_STATUS;
+ if (indexingStatusQuery.status === "pending") return CONNECTING_STATUS;
- const { ensIndexerPublicConfig } = configQuery.data;
+ const { ensIndexer: ensIndexerPublicConfig } = indexingStatusQuery.data.stackInfo;
return prerequisiteResultToFeatureStatus(hasSubgraphApiConfigSupport(ensIndexerPublicConfig));
- }, [configQuery]);
+ }, [indexingStatusQuery]);
const omnigraph: FeatureStatus = useMemo(() => {
- if (configQuery.status === "error") return CONFIG_ERROR_STATUS;
- if (configQuery.status === "pending") return CONNECTING_STATUS;
+ if (indexingStatusQuery.status === "error") return INDEXING_STATUS_ERROR_STATUS;
+ if (indexingStatusQuery.status === "pending") return CONNECTING_STATUS;
- const { ensIndexerPublicConfig } = configQuery.data;
+ const { ensIndexer: ensIndexerPublicConfig } = indexingStatusQuery.data.stackInfo;
return prerequisiteResultToFeatureStatus(hasOmnigraphApiConfigSupport(ensIndexerPublicConfig));
- }, [configQuery]);
+ }, [indexingStatusQuery]);
return { registrarActions, subgraph, omnigraph };
}
diff --git a/apps/ensadmin/src/hooks/async/use-namespace.ts b/apps/ensadmin/src/hooks/async/use-namespace.ts
index be3a0925ed..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 { useENSNodeConfig } from "@ensnode/ensnode-react";
+import { useEnsNodeStackInfo } from "@/hooks/use-ensnode-stack-info";
/**
* Hook to get the namespace ID from the active ENSNode connection.
@@ -22,10 +22,10 @@ import { useENSNodeConfig } from "@ensnode/ensnode-react";
* ```
*/
export function useNamespace() {
- const query = useENSNodeConfig();
+ const query = useEnsNodeStackInfo();
return {
...query,
- data: query.data?.ensIndexerPublicConfig.namespace ?? null,
+ data: query.data?.ensIndexer.namespace ?? null,
};
}
diff --git a/apps/ensadmin/src/hooks/use-ensnode-stack-info.ts b/apps/ensadmin/src/hooks/use-ensnode-stack-info.ts
new file mode 100644
index 0000000000..360d8c6e58
--- /dev/null
+++ b/apps/ensadmin/src/hooks/use-ensnode-stack-info.ts
@@ -0,0 +1,13 @@
+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 };
+}
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..7bc864bf6f
--- /dev/null
+++ b/apps/ensapi/src/cache/stack-info.cache.ts
@@ -0,0 +1,70 @@
+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 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 for
+ * the lifecycle of an ENSApi process instance, 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 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.
+ *
+ * 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 ENSNode 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.routes.ts b/apps/ensapi/src/handlers/api/meta/status-api.routes.ts
index a8487d3856..0b7b9970dc 100644
--- a/apps/ensapi/src/handlers/api/meta/status-api.routes.ts
+++ b/apps/ensapi/src/handlers/api/meta/status-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];
diff --git a/apps/ensapi/src/handlers/api/meta/status-api.ts b/apps/ensapi/src/handlers/api/meta/status-api.ts
index 86a7c06692..f2db1b4515 100644
--- a/apps/ensapi/src/handlers/api/meta/status-api.ts
+++ b/apps/ensapi/src/handlers/api/meta/status-api.ts
@@ -1,28 +1,20 @@
-import config from "@/config";
-
import {
EnsApiIndexingStatusResponseCodes,
type EnsApiIndexingStatusResponseError,
type EnsApiIndexingStatusResponseOk,
- serializeENSApiPublicConfig,
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 { getConfigRoute, getIndexingStatusRoute } from "./status-api.routes";
-
-const app = createApp({ middlewares: [indexingStatusMiddleware] });
+import { getIndexingStatusRoute } from "./status-api.routes";
-app.openapi(getConfigRoute, async (c) => {
- const ensApiPublicConfig = buildEnsApiPublicConfig(config);
- return c.json(serializeENSApiPublicConfig(ensApiPublicConfig));
-});
+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,
@@ -36,6 +28,7 @@ app.openapi(getIndexingStatusRoute, async (c) => {
serializeEnsApiIndexingStatusResponse({
responseCode: EnsApiIndexingStatusResponseCodes.Ok,
realtimeProjection: c.var.indexingStatus,
+ 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..7011ff6386
--- /dev/null
+++ b/apps/ensapi/src/middleware/stack-info.middleware.ts
@@ -0,0 +1,54 @@
+import type { EnsNodeStackInfo } from "@ensnode/ensnode-sdk";
+
+import { stackInfoCache } from "@/cache/stack-info.cache";
+import { factory, producing } from "@/lib/hono-factory";
+import { makeLogger } from "@/lib/logger";
+
+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;
+}
+
+/**
+ * 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.
+ *
+ * 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"],
+ factory.createMiddleware(async (c, next) => {
+ const stackInfo = await stackInfoCache.read();
+
+ if (stackInfo instanceof Error) {
+ logger.error(
+ { error: stackInfo },
+ "Failed to retrieve ENSNode Stack Info in stackInfoMiddleware",
+ );
+ }
+
+ c.set("stackInfo", stackInfo);
+
+ await next();
+ }),
+);
diff --git a/apps/ensindexer/ponder/src/api/handlers/ensnode-api.ts b/apps/ensindexer/ponder/src/api/handlers/ensnode-api.ts
index af826cb617..b4d3c9cb3e 100644
--- a/apps/ensindexer/ponder/src/api/handlers/ensnode-api.ts
+++ b/apps/ensindexer/ponder/src/api/handlers/ensnode-api.ts
@@ -3,11 +3,11 @@ import { Hono } from "hono";
import {
createRealtimeIndexingStatusProjection,
- IndexingStatusResponseCodes,
- type IndexingStatusResponseError,
- type IndexingStatusResponseOk,
- serializeENSIndexerPublicConfig,
- serializeIndexingStatusResponse,
+ EnsIndexerIndexingStatusResponseCodes,
+ type EnsIndexerIndexingStatusResponseError,
+ type EnsIndexerIndexingStatusResponseOk,
+ serializeEnsIndexerIndexingStatusResponse,
+ serializeEnsIndexerPublicConfig,
} from "@ensnode/ensnode-sdk";
import { ensDbClient } from "@/lib/ensdb/singleton";
@@ -26,7 +26,7 @@ app.get("/config", async (c) => {
}
// respond with the serialized public config object
- return c.json(serializeENSIndexerPublicConfig(publicConfig));
+ return c.json(serializeEnsIndexerPublicConfig(publicConfig));
});
app.get("/indexing-status", async (c) => {
@@ -50,10 +50,10 @@ app.get("/indexing-status", async (c) => {
);
return c.json(
- serializeIndexingStatusResponse({
- responseCode: IndexingStatusResponseCodes.Ok,
+ serializeEnsIndexerIndexingStatusResponse({
+ responseCode: EnsIndexerIndexingStatusResponseCodes.Ok,
realtimeProjection,
- } satisfies IndexingStatusResponseOk),
+ } satisfies EnsIndexerIndexingStatusResponseOk),
);
} catch (error) {
logger.error({
@@ -64,9 +64,9 @@ app.get("/indexing-status", async (c) => {
});
return c.json(
- serializeIndexingStatusResponse({
- responseCode: IndexingStatusResponseCodes.Error,
- } satisfies IndexingStatusResponseError),
+ serializeEnsIndexerIndexingStatusResponse({
+ responseCode: EnsIndexerIndexingStatusResponseCodes.Error,
+ } satisfies EnsIndexerIndexingStatusResponseError),
500,
);
}
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/ensnode.io/ensapi-openapi.json b/docs/ensnode.io/ensapi-openapi.json
index e4c4dbd20e..0ee68499fb 100644
--- a/docs/ensnode.io/ensapi-openapi.json
+++ b/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,251 @@
"worstCaseDistance": { "type": "number" }
},
"required": ["snapshot", "projectedAt", "worstCaseDistance"]
+ },
+ "stackInfo": {
+ "type": "object",
+ "properties": {
+ "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": "integer",
+ "minimum": 0
+ }
+ },
+ "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": {
+ "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": ["versionInfo"],
+ "description": "Serialized Indexing Status Response OK.ensDb"
+ },
+ "ensIndexer": {
+ "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": "integer", "minimum": 0 }
+ },
+ "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"
+ ]
+ },
+ "ensRainbow": {
+ "type": "object",
+ "properties": {
+ "version": { "type": "string", "minLength": 1 },
+ "labelSet": {
+ "type": "object",
+ "properties": {
+ "labelSetId": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 50,
+ "pattern": "^[a-z-]+$"
+ },
+ "highestLabelSetVersion": { "type": "integer", "minimum": 0 }
+ },
+ "required": ["labelSetId", "highestLabelSetVersion"]
+ },
+ "recordsCount": { "type": "integer", "minimum": 0 }
+ },
+ "required": ["version", "labelSet", "recordsCount"]
+ }
+ },
+ "required": ["ensApi", "ensDb", "ensIndexer"]
}
},
- "required": ["responseCode", "realtimeProjection"],
- "additionalProperties": false
+ "required": ["responseCode", "realtimeProjection", "stackInfo"]
}
}
}
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/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/ensdb-sdk/src/client/ensdb-reader.ts b/packages/ensdb-sdk/src/client/ensdb-reader.ts
index ac6b0b72e3..49d61d3ee1 100644
--- a/packages/ensdb-sdk/src/client/ensdb-reader.ts
+++ b/packages/ensdb-sdk/src/client/ensdb-reader.ts
@@ -4,6 +4,8 @@ import {
type CrossChainIndexingStatusSnapshot,
deserializeCrossChainIndexingStatusSnapshot,
deserializeEnsIndexerPublicConfig,
+ type EnsDbPublicConfig,
+ type EnsDbVersionInfo,
type EnsIndexerPublicConfig,
} from "@ensnode/ensnode-sdk";
@@ -14,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,
@@ -156,6 +159,17 @@ export class EnsDbReader<
return deserializeEnsIndexerPublicConfig(record);
}
+ /**
+ * Build ENSDb Public Config
+ */
+ async buildEnsDbPublicConfig(): Promise {
+ const versionInfo = await this.buildEnsDbVersionInfo();
+
+ return {
+ versionInfo,
+ };
+ }
+
/**
* Get Indexing Status Snapshot
*
@@ -208,4 +222,37 @@ 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 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) ..." }]
+ const versionString = result.rows[0]?.version;
+
+ 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}`);
+ }
+ }
+
+ /**
+ * 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/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;
+}
diff --git a/packages/ensnode-react/README.md b/packages/ensnode-react/README.md
index e259684963..be1bd48a99 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 `EnsNodeProvider`:
```tsx
-import { ENSNodeProvider, createConfig } from "@ensnode/ensnode-react";
+import { EnsNodeProvider, createEnsNodeProviderOptions } from "@ensnode/ensnode-react";
-const config = createConfig({ url: "https://api.alpha.ensnode.io" });
+const options = createEnsNodeProviderOptions({ url: "https://api.alpha.ensnode.io" });
function App() {
return (
-
+
-
+
);
}
```
@@ -124,13 +124,13 @@ function DisplayPrimaryNames() {
## API Reference
-### ENSNodeProvider
+### EnsNodeProvider
-The provider component that supplies ENSNode configuration to all child components.
+The provider component that supplies ENSNode Provider Options to all child components.
```tsx
-interface ENSNodeProviderProps {
- config: ENSNodeConfig;
+interface EnsNodeProviderProps {
+ options: EnsNodeProviderOptions;
queryClient?: QueryClient;
queryClientOptions?: QueryClientOptions;
}
@@ -138,16 +138,16 @@ interface ENSNodeProviderProps {
#### Props
-- `config`: ENSNode configuration 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)
-### createConfig
+### createEnsNodeProviderOptions
-Helper function to create ENSNode configuration with defaults.
+Helper function to create ENSNode Provider Options with defaults.
```tsx
-const config = createConfig({
+const options = createEnsNodeProviderOptions({
url: "https://api.alpha.ensnode.io",
});
```
@@ -230,12 +230,12 @@ 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
-
-
+
```
### 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..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 { ENSNodeSDKConfig } from "./types";
+import type { EnsNodeProviderOptions } from "./types";
/**
- * React context for ENSNode configuration
+ * React context for ENSNodeProvider options
*/
-export const ENSNodeContext = createContext(undefined);
+export const EnsNodeContext = createContext(undefined);
/**
* Display name for debugging
*/
-ENSNodeContext.displayName = "ENSNodeContext";
+EnsNodeContext.displayName = "EnsNodeContext";
diff --git a/packages/ensnode-react/src/hooks/index.ts b/packages/ensnode-react/src/hooks/index.ts
index 1a3e1c3cd3..f55864b962 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 "./useEnsNodeProviderOptions";
export * from "./useIndexingStatus";
export * from "./useNameTokens";
export * from "./usePrimaryName";
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);
-}
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/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 c56f3d6928..4457b01eeb 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, WithEnsNodeProviderOptions } from "../types";
import { createIndexingStatusQueryOptions } from "../utils/query";
-import { useENSNodeSDKConfig } from "./useENSNodeSDKConfig";
+import { useEnsNodeProviderOptions } from "./useEnsNodeProviderOptions";
interface UseIndexingStatusParameters
- extends IndexingStatusRequest,
- QueryParameter {}
+ extends EnsApiIndexingStatusRequest,
+ QueryParameter {}
export function useIndexingStatus(
- parameters: WithSDKConfigParameter & UseIndexingStatusParameters = {},
+ parameters: WithEnsNodeProviderOptions & UseIndexingStatusParameters = {},
) {
- const { config, query = {} } = parameters;
- const _config = useENSNodeSDKConfig(config);
+ const { options, query = {} } = parameters;
+ const providerOptions = useEnsNodeProviderOptions(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..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, WithSDKConfigParameter } from "../types";
+import type { QueryParameter, WithEnsNodeProviderOptions } from "../types";
import { createNameTokensQueryOptions } from "../utils/query";
-import { useENSNodeSDKConfig } from "./useENSNodeSDKConfig";
+import { useEnsNodeProviderOptions } from "./useEnsNodeProviderOptions";
type UseNameTokensParameters = NameTokensRequest & QueryParameter;
@@ -13,11 +13,11 @@ type UseNameTokensParameters = NameTokensRequest & QueryParameter(
- parameters: UseRecordsParameters & WithSDKConfigParameter,
+ parameters: UseRecordsParameters & WithEnsNodeProviderOptions,
) {
- const { config, query = {}, name, ...args } = parameters;
- const _config = useENSNodeSDKConfig(config);
+ const { options, query = {}, name, ...args } = parameters;
+ const _config = useEnsNodeProviderOptions(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..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, WithSDKConfigParameter } from "../types";
+import type { QueryParameter, WithEnsNodeProviderOptions } from "../types";
import { createRegistrarActionsQueryOptions } from "../utils/query";
-import { useENSNodeSDKConfig } from "./useENSNodeSDKConfig";
+import { useEnsNodeProviderOptions } from "./useEnsNodeProviderOptions";
interface UseRegistrarActionsParameters
extends RegistrarActionsRequest,
@@ -16,19 +16,17 @@ interface UseRegistrarActionsParameters
* Query ENSNode Registrar Actions API.
*/
export function useRegistrarActions(
- parameters: WithSDKConfigParameter & UseRegistrarActionsParameters = {},
+ parameters: WithEnsNodeProviderOptions & UseRegistrarActionsParameters = {},
) {
- const { config, query = {} } = parameters;
- const _config = useENSNodeSDKConfig(config);
+ const { options, query = {}, ...request } = parameters;
+ const providerOptions = useEnsNodeProviderOptions(options);
- const queryOptions = createRegistrarActionsQueryOptions(_config, parameters);
+ const queryOptions = createRegistrarActionsQueryOptions(providerOptions, request);
- const options = {
+ return useQuery({
...queryOptions,
refetchInterval: 10 * 1000, // 10 seconds - latest registrar actions change frequently
...query,
enabled: query.enabled ?? queryOptions.enabled,
- };
-
- return useQuery(options);
+ });
}
diff --git a/packages/ensnode-react/src/hooks/useResolvedIdentity.ts b/packages/ensnode-react/src/hooks/useResolvedIdentity.ts
index a913b54a47..b5c84c8410 100644
--- a/packages/ensnode-react/src/hooks/useResolvedIdentity.ts
+++ b/packages/ensnode-react/src/hooks/useResolvedIdentity.ts
@@ -15,7 +15,6 @@ import {
import type { UseResolvedIdentityParameters } from "../types";
import { ASSUME_IMMUTABLE_QUERY } from "../utils/query";
-import { useENSNodeConfig } from "./useENSNodeConfig";
import { usePrimaryName } from "./usePrimaryName";
/**
@@ -25,7 +24,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)
@@ -40,10 +39,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/provider.tsx b/packages/ensnode-react/src/provider.tsx
index a99fa3a45c..4280373d3f 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 { EnsApiClient } from "@ensnode/ensnode-sdk";
+import { EnsNodeClient } from "@ensnode/ensnode-sdk";
-import { ENSNodeContext } from "./context";
-import type { ENSNodeSDKConfig } from "./types";
+import { EnsNodeContext } from "./context";
+import type { EnsNodeProviderOptions } from "./types";
-export interface ENSNodeProviderProps {
- /** ENSNode configuration */
- config: ENSNodeSDKConfig;
+export interface EnsNodeProviderProps {
+ /** ENSNode Provider Options */
+ options: EnsNodeProviderOptions;
/**
* 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, EnsNodeProvider will create and manage its own QueryClient internally.
*/
queryClient?: QueryClient;
@@ -26,21 +26,18 @@ export interface ENSNodeProviderProps {
queryClientOptions?: ConstructorParameters[0];
}
-function ENSNodeInternalProvider({
+function EnsNodeInternalProvider({
children,
- config,
+ options,
}: {
children?: React.ReactNode;
- config: ENSNodeSDKConfig;
+ options: EnsNodeProviderOptions;
}) {
- // Memoize the config to prevent unnecessary re-renders
- const memoizedConfig = useMemo(() => config, [config]);
-
- return createElement(ENSNodeContext.Provider, { value: memoizedConfig }, children);
+ return createElement(EnsNodeContext.Provider, { value: options }, children);
}
-export function ENSNodeProvider(parameters: React.PropsWithChildren) {
- const { children, config, queryClient, queryClientOptions } = parameters;
+export function EnsNodeProvider(parameters: React.PropsWithChildren) {
+ const { children, options, queryClient, queryClientOptions } = parameters;
// Check if we're already inside a QueryClientProvider
let hasExistingQueryClient = false;
@@ -59,12 +56,12 @@ export function ENSNodeProvider(parameters: React.PropsWithChildren {
/**
* Configuration parameter for hooks that need access to config
*/
-export interface WithSDKConfigParameter {
- config?: TConfig | undefined;
+export interface WithEnsNodeProviderOptions<
+ TOptions extends EnsNodeProviderOptions = EnsNodeProviderOptions,
+> {
+ options?: TOptions | undefined;
}
/**
@@ -75,4 +78,5 @@ export interface UseResolvedIdentityParameters
extends QueryParameter,
AcceleratableRequest {
identity: UnresolvedIdentity;
+ namespace: ENSNamespaceId;
}
diff --git a/packages/ensnode-react/src/utils/query.ts b/packages/ensnode-react/src/utils/query.ts
index 16729f8db1..2e43642952 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 {
- EnsApiClient,
+ EnsNodeClient,
type NameTokensRequest,
type RegistrarActionsRequest,
type ResolvePrimaryNameRequest,
@@ -12,7 +12,7 @@ import {
type ResolverRecordsSelection,
} from "@ensnode/ensnode-sdk";
-import type { ENSNodeSDKConfig } 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.
@@ -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: EnsNodeProviderOptions,
args: ResolveRecordsRequest,
) {
return {
enabled: true,
queryKey: queryKeys.records(config.client.url.href, args),
queryFn: async () => {
- const client = new EnsApiClient(config.client);
+ const client = new EnsNodeClient(config.client);
return client.resolveRecords(args.name, args.selection, args);
},
};
@@ -86,14 +84,14 @@ export function createRecordsQueryOptions {
- const client = new EnsApiClient(config.client);
+ const client = new EnsNodeClient(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: EnsNodeProviderOptions,
args: ResolvePrimaryNamesRequest,
) {
return {
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);
},
};
}
-/**
- * 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 EnsApiClient(config.client);
- return client.config();
- },
- };
-}
-
/**
* Creates query options for ENSNode Indexing Status API
*/
-export function createIndexingStatusQueryOptions(config: ENSNodeSDKConfig) {
+export function createIndexingStatusQueryOptions(config: EnsNodeProviderOptions) {
return {
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();
},
};
@@ -148,14 +132,14 @@ export function createIndexingStatusQueryOptions(config: ENSNodeSDKConfig) {
* Creates query options for ENSNode Registrar Actions API
*/
export function createRegistrarActionsQueryOptions(
- config: ENSNodeSDKConfig,
+ config: EnsNodeProviderOptions,
args: RegistrarActionsRequest,
) {
return {
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);
},
@@ -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: EnsNodeProviderOptions,
+ args: NameTokensRequest,
+) {
return {
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..01e1836e9b 100644
--- a/packages/ensnode-sdk/README.md
+++ b/packages/ensnode-sdk/README.md
@@ -10,20 +10,19 @@ 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 ENSNode REST APIs:
- Resolution API (Protocol Accelerated Forward/Reverse Resolution)
- Indexing Status API
-- Configuration API
### 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", {
@@ -152,41 +151,32 @@ 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()`
-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
```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();
+
+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
```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/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/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 {
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/ensdb/config.ts b/packages/ensnode-sdk/src/ensdb/config.ts
new file mode 100644
index 0000000000..bb2d2d0b7e
--- /dev/null
+++ b/packages/ensnode-sdk/src/ensdb/config.ts
@@ -0,0 +1,19 @@
+/**
+ * Version info about ENSDb and its dependencies.
+ */
+export interface EnsDbVersionInfo {
+ /**
+ * Version of the PostgreSQL server hosting the ENSDb instance.
+ */
+ postgresql: string;
+}
+
+/**
+ * The public configuration of an ENSDb instance.
+ */
+export interface EnsDbPublicConfig {
+ /**
+ * Version info about ENSDb.
+ */
+ versionInfo: EnsDbVersionInfo;
+}
diff --git a/packages/ensnode-sdk/src/ensdb/index.ts b/packages/ensnode-sdk/src/ensdb/index.ts
new file mode 100644
index 0000000000..938f4f05c3
--- /dev/null
+++ b/packages/ensnode-sdk/src/ensdb/index.ts
@@ -0,0 +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/ensdb/zod-schemas/config.ts b/packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts
new file mode 100644
index 0000000000..1d9db9ed3b
--- /dev/null
+++ b/packages/ensnode-sdk/src/ensdb/zod-schemas/config.ts
@@ -0,0 +1,24 @@
+import { z } from "zod/v4";
+
+const makeEnsDbVersionInfoSchema = (valueLabel?: string) => {
+ const label = valueLabel ?? "EnsDbVersionInfo";
+
+ return z
+ .object({
+ postgresql: z
+ .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/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/ensapi/api/index.ts b/packages/ensnode-sdk/src/ensnode/api/index.ts
similarity index 85%
rename from packages/ensnode-sdk/src/ensapi/api/index.ts
rename to packages/ensnode-sdk/src/ensnode/api/index.ts
index 661de1ea75..306cc2cf3e 100644
--- a/packages/ensnode-sdk/src/ensapi/api/index.ts
+++ b/packages/ensnode-sdk/src/ensnode/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/api/indexing-status/deserialize.ts b/packages/ensnode-sdk/src/ensnode/api/indexing-status/deserialize.ts
similarity index 62%
rename from packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts
rename to packages/ensnode-sdk/src/ensnode/api/indexing-status/deserialize.ts
index 08e927795c..06ab70914a 100644
--- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/deserialize.ts
+++ b/packages/ensnode-sdk/src/ensnode/api/indexing-status/deserialize.ts
@@ -2,8 +2,18 @@ import { prettifyError } from "zod/v4";
import { buildUnvalidatedRealtimeIndexingStatusProjection } from "../../../indexing-status/deserialize/realtime-indexing-status-projection";
import type { Unvalidated } from "../../../shared/types";
-import { type EnsApiIndexingStatusResponse, EnsApiIndexingStatusResponseCodes } from "./response";
-import type { SerializedEnsApiIndexingStatusResponse } from "./serialized-response";
+import { buildUnvalidatedEnsNodeStackInfo } from "../../../stack-info/deserialize/ensnode-stack-info";
+import {
+ type EnsApiIndexingStatusResponse,
+ EnsApiIndexingStatusResponseCodes,
+ type EnsApiIndexingStatusResponseError,
+ type EnsApiIndexingStatusResponseOk,
+} from "./response";
+import type {
+ SerializedEnsApiIndexingStatusResponse,
+ SerializedEnsApiIndexingStatusResponseError,
+ SerializedEnsApiIndexingStatusResponseOk,
+} from "./serialized-response";
import {
makeEnsApiIndexingStatusResponseSchema,
makeSerializedEnsApiIndexingStatusResponseSchema,
@@ -23,17 +33,27 @@ function buildUnvalidatedEnsApiIndexingStatusResponse(
return serializedResponse;
}
+ const { realtimeProjection, stackInfo, ...rest } = serializedResponse;
+
return {
- ...serializedResponse,
- realtimeProjection: buildUnvalidatedRealtimeIndexingStatusProjection(
- serializedResponse.realtimeProjection,
- ),
+ realtimeProjection: buildUnvalidatedRealtimeIndexingStatusProjection(realtimeProjection),
+ stackInfo: buildUnvalidatedEnsNodeStackInfo(stackInfo),
+ ...rest,
};
}
/**
* 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 {
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 95%
rename from packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts
rename to packages/ensnode-sdk/src/ensnode/api/indexing-status/response.ts
index 14cd8b9ba5..744a79311d 100644
--- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/response.ts
+++ b/packages/ensnode-sdk/src/ensnode/api/indexing-status/response.ts
@@ -1,4 +1,5 @@
import type { RealtimeIndexingStatusProjection } from "../../../indexing-status/realtime-indexing-status-projection";
+import type { EnsNodeStackInfo } from "../../../stack-info/ensnode-stack-info";
/**
* A status code for indexing status responses.
@@ -41,6 +42,7 @@ export type IndexingStatusResponseCode = EnsApiIndexingStatusResponseCode;
export type EnsApiIndexingStatusResponseOk = {
responseCode: typeof EnsApiIndexingStatusResponseCodes.Ok;
realtimeProjection: RealtimeIndexingStatusProjection;
+ stackInfo: EnsNodeStackInfo;
};
/**
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 91%
rename from packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts
rename to packages/ensnode-sdk/src/ensnode/api/indexing-status/serialize.ts
index 5dcf85b87f..bf2243e6fb 100644
--- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialize.ts
+++ b/packages/ensnode-sdk/src/ensnode/api/indexing-status/serialize.ts
@@ -1,4 +1,5 @@
import { serializeRealtimeIndexingStatusProjection } from "../../../indexing-status/serialize/realtime-indexing-status-projection";
+import { serializeEnsNodeStackInfo } from "../../../stack-info/serialize/ensnode-stack-info";
import {
type EnsApiIndexingStatusResponse,
EnsApiIndexingStatusResponseCodes,
@@ -31,6 +32,7 @@ export function serializeEnsApiIndexingStatusResponse(
return {
responseCode: response.responseCode,
realtimeProjection: serializeRealtimeIndexingStatusProjection(response.realtimeProjection),
+ 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/ensnode/api/indexing-status/serialized-response.ts
similarity index 91%
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
index 6db56e7587..475173aed3 100644
--- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/serialized-response.ts
+++ b/packages/ensnode-sdk/src/ensnode/api/indexing-status/serialized-response.ts
@@ -1,4 +1,5 @@
import type { SerializedRealtimeIndexingStatusProjection } from "../../../indexing-status/serialize/realtime-indexing-status-projection";
+import type { SerializedEnsNodeStackInfo } from "../../../stack-info/serialize/ensnode-stack-info";
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;
+ stackInfo: SerializedEnsNodeStackInfo;
}
/**
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 90%
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
index 0123a6aedd..7adb03a98e 100644
--- a/packages/ensnode-sdk/src/ensapi/api/indexing-status/zod-schemas.ts
+++ b/packages/ensnode-sdk/src/ensnode/api/indexing-status/zod-schemas.ts
@@ -4,6 +4,10 @@ import {
makeRealtimeIndexingStatusProjectionSchema,
makeSerializedRealtimeIndexingStatusProjectionSchema,
} from "../../../indexing-status/zod-schema/realtime-indexing-status-projection";
+import {
+ makeEnsNodeStackInfoSchema,
+ makeSerializedEnsNodeStackInfoSchema,
+} from "../../../stack-info/zod-schemas/ensnode-stack-info";
import {
type EnsApiIndexingStatusResponse,
EnsApiIndexingStatusResponseCodes,
@@ -24,6 +28,7 @@ export const makeEnsApiIndexingStatusResponseOkSchema = (
z.strictObject({
responseCode: z.literal(EnsApiIndexingStatusResponseCodes.Ok),
realtimeProjection: makeRealtimeIndexingStatusProjectionSchema(valueLabel),
+ stackInfo: makeEnsNodeStackInfoSchema(valueLabel),
});
/**
@@ -59,9 +64,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),
+ stackInfo: makeSerializedEnsNodeStackInfoSchema(valueLabel),
});
/**
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 82%
rename from packages/ensnode-sdk/src/ensapi/client.test.ts
rename to packages/ensnode-sdk/src/ensnode/client.test.ts
index 222a4c3464..d23e7a9ce0 100644
--- a/packages/ensnode-sdk/src/ensapi/client.test.ts
+++ b/packages/ensnode-sdk/src/ensnode/client.test.ts
@@ -2,13 +2,18 @@ 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 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";
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,
@@ -21,11 +26,9 @@ 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 { deserializeEnsApiPublicConfig } from "./config/deserialize";
-import type { SerializedEnsApiPublicConfig } from "./config/serialized-types";
-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";
@@ -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,6 +100,55 @@ const EXAMPLE_CONFIG_RESPONSE = {
},
} satisfies SerializedEnsApiPublicConfig;
+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,
+ },
+ 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({
realtimeProjection: {
projectedAt: 1755182604,
@@ -141,7 +193,7 @@ const EXAMPLE_INDEXING_STATUS_BACKFILL_RESPONSE = deserializeEnsApiIndexingStatu
},
},
},
-
+ stackInfo: serializedStackInfo,
responseCode: EnsApiIndexingStatusResponseCodes.Ok,
} satisfies SerializedEnsApiIndexingStatusResponseOk);
@@ -199,7 +251,7 @@ const _EXAMPLE_INDEXING_STATUS_FOLLOWING_RESPONSE: EnsApiIndexingStatusResponse
} satisfies SerializedOmnichainIndexingStatusSnapshotFollowing,
},
},
-
+ stackInfo: serializedStackInfo,
responseCode: EnsApiIndexingStatusResponseCodes.Ok,
});
@@ -207,14 +259,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) });
@@ -222,14 +274,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);
@@ -242,12 +294,12 @@ 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(
`/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(","));
@@ -260,14 +312,14 @@ 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,
});
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(","));
@@ -280,7 +332,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,
);
@@ -294,12 +346,12 @@ 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(
`/api/resolve/primary-name/${EXAMPLE_ADDRESS}/1`,
- DEFAULT_ENSNODE_API_URL_MAINNET,
+ DEFAULT_ENSNODE_URL_MAINNET,
);
expect(mockFetch).toHaveBeenCalledWith(expectedUrl);
@@ -310,12 +362,12 @@ 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(
`/api/resolve/primary-name/${EXAMPLE_ADDRESS}/1`,
- DEFAULT_ENSNODE_API_URL_MAINNET,
+ DEFAULT_ENSNODE_URL_MAINNET,
);
expectedUrl.searchParams.set("trace", "true");
@@ -329,12 +381,12 @@ 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(
`/api/resolve/primary-name/${EXAMPLE_ADDRESS}/1`,
- DEFAULT_ENSNODE_API_URL_MAINNET,
+ DEFAULT_ENSNODE_URL_MAINNET,
);
expectedUrl.searchParams.set("accelerate", "true");
@@ -344,7 +396,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);
});
});
@@ -356,12 +408,12 @@ 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(
`/api/resolve/primary-names/${EXAMPLE_ADDRESS}`,
- DEFAULT_ENSNODE_API_URL_MAINNET,
+ DEFAULT_ENSNODE_URL_MAINNET,
);
expect(mockFetch).toHaveBeenCalledWith(expectedUrl);
@@ -374,12 +426,12 @@ 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(
`/api/resolve/primary-names/${EXAMPLE_ADDRESS}`,
- DEFAULT_ENSNODE_API_URL_MAINNET,
+ DEFAULT_ENSNODE_URL_MAINNET,
);
expectedUrl.searchParams.set("chainIds", "1,10");
@@ -390,12 +442,12 @@ 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(
`/api/resolve/primary-names/${EXAMPLE_ADDRESS}`,
- DEFAULT_ENSNODE_API_URL_MAINNET,
+ DEFAULT_ENSNODE_URL_MAINNET,
);
expectedUrl.searchParams.set("trace", "true");
@@ -409,12 +461,12 @@ 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(
`/api/resolve/primary-names/${EXAMPLE_ADDRESS}`,
- DEFAULT_ENSNODE_API_URL_MAINNET,
+ DEFAULT_ENSNODE_URL_MAINNET,
);
expectedUrl.searchParams.set("accelerate", "true");
@@ -424,45 +476,18 @@ 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);
});
});
- 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
- 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 EnsApiClient();
+ const client = new EnsNodeClient();
mockFetch.mockResolvedValueOnce({
ok: true,
@@ -476,7 +501,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 90%
rename from packages/ensnode-sdk/src/ensapi/client.ts
rename to packages/ensnode-sdk/src/ensnode/client.ts
index f8822f5193..eccef4fed1 100644
--- a/packages/ensnode-sdk/src/ensapi/client.ts
+++ b/packages/ensnode-sdk/src/ensnode/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,
@@ -31,36 +28,28 @@ import { ClientError } from "./client-error";
import { getDefaultEnsNodeUrl } from "./deployments";
/**
- * Configuration options for ENSNode API client
+ * Configuration options for an ENSNode client
*/
-export interface EnsApiClientOptions {
- /** The ENSNode API URL */
+export interface EnsNodeClientOptions {
+ /** The ENSNode URL */
url: URL;
}
/**
- * Configuration options for ENSNode API client
- *
- * @deprecated Use {@link EnsApiClientOptions} instead.
- */
-export type ClientOptions = EnsApiClientOptions;
-
-/**
- * EnsApi Client
+ * ENSNode Client
*
* Provides access to the following ENSNode APIs:
* - Resolution API
- * - Configuration API
* - Indexing Status API
* - Registrar Actions API
* - Name Tokens API
*
* @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", {
@@ -71,51 +60,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({
+ * // Use default ENSNode URL for Mainnet
+ * 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({
+ * // Use default ENSNode URL for Sepolia
+ * 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),
});
@@ -310,39 +299,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
*
@@ -408,13 +364,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();
@@ -622,14 +578,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 75%
rename from packages/ensnode-sdk/src/ensapi/deployments.test.ts
rename to packages/ensnode-sdk/src/ensnode/deployments.test.ts
index 8578adb2fc..83d2671661 100644
--- a/packages/ensnode-sdk/src/ensapi/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/ensapi/deployments.ts b/packages/ensnode-sdk/src/ensnode/deployments.ts
similarity index 70%
rename from packages/ensnode-sdk/src/ensapi/deployments.ts
rename to packages/ensnode-sdk/src/ensnode/deployments.ts
index 74a0138021..35deafe4e6 100644
--- a/packages/ensnode-sdk/src/ensapi/deployments.ts
+++ b/packages/ensnode-sdk/src/ensnode/deployments.ts
@@ -1,14 +1,14 @@
import { type ENSNamespaceId, ENSNamespaceIds } from "@ensnode/datasources";
/**
- * Default ENSNode API endpoint URL for Mainnet
+ * Default ENSNode 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
+ * Default ENSNode 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
new file mode 100644
index 0000000000..227ed19cdb
--- /dev/null
+++ b/packages/ensnode-sdk/src/ensnode/index.ts
@@ -0,0 +1,7 @@
+export * from "./api";
+export {
+ EnsNodeClient,
+ type EnsNodeClientOptions,
+} from "./client";
+export * from "./client-error";
+export * from "./deployments";
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/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.
diff --git a/packages/ensnode-sdk/src/index.ts b/packages/ensnode-sdk/src/index.ts
index ea5341ec7e..06ca0096f8 100644
--- a/packages/ensnode-sdk/src/index.ts
+++ b/packages/ensnode-sdk/src/index.ts
@@ -5,7 +5,9 @@ export { ENSNamespaceIds } from "@ensnode/datasources";
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";
@@ -30,6 +32,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/internal.ts b/packages/ensnode-sdk/src/internal.ts
index 163ff75451..e48bec775d 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";
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..40ee912e5a
--- /dev/null
+++ b/packages/ensnode-sdk/src/stack-info/deserialize/ensnode-stack-info.ts
@@ -0,0 +1,53 @@
+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 {
+ // 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 {
+ ...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..94f39d1955
--- /dev/null
+++ b/packages/ensnode-sdk/src/stack-info/ensnode-stack-info.ts
@@ -0,0 +1,48 @@
+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";
+
+/**
+ * Information about the stack of services inside an ENSNode instance.
+ */
+export interface EnsNodeStackInfo {
+ /**
+ * ENSApi Public Config
+ */
+ ensApi: EnsApiPublicConfig;
+
+ /**
+ * ENSDb Public Config
+ */
+ ensDb: EnsDbPublicConfig;
+
+ /**
+ * ENSIndexer Public Config
+ */
+ ensIndexer: EnsIndexerPublicConfig;
+
+ /**
+ * ENSRainbow Public Config
+ *
+ * If undefined, represents that ENSRainbow is currently undergoing
+ * a cold start and may take up to an hour to become ready.
+ */
+ 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..1c1f2f2638
--- /dev/null
+++ b/packages/ensnode-sdk/src/stack-info/serialize/ensnode-stack-info.ts
@@ -0,0 +1,29 @@
+import { serializeEnsApiPublicConfig } from "../../ensapi/config/serialize";
+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 {
+ ensApi: SerializedEnsApiPublicConfig;
+ ensDb: SerializedEnsDbPublicConfig;
+ ensIndexer: SerializedEnsIndexerPublicConfig;
+ ensRainbow?: SerializedEnsRainbowPublicConfig;
+}
+
+/**
+ * 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(),
+ });
+}
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 (