diff --git a/.changeset/brown-readers-talk.md b/.changeset/brown-readers-talk.md
new file mode 100644
index 0000000000..de2efcee7b
--- /dev/null
+++ b/.changeset/brown-readers-talk.md
@@ -0,0 +1,5 @@
+---
+"@ensnode/ensnode-sdk": minor
+---
+
+BREAKING: Removed DefaultRecordsSelection export: integrating apps should define their own set of records to request when using useRecords().
diff --git a/.changeset/nine-ducks-lick.md b/.changeset/nine-ducks-lick.md
new file mode 100644
index 0000000000..c86240587c
--- /dev/null
+++ b/.changeset/nine-ducks-lick.md
@@ -0,0 +1,5 @@
+---
+"ensadmin": minor
+---
+
+ENSAdmin now supports ENSApi Version info.
diff --git a/.changeset/shaky-schools-nail.md b/.changeset/shaky-schools-nail.md
new file mode 100644
index 0000000000..ffd51a53b6
--- /dev/null
+++ b/.changeset/shaky-schools-nail.md
@@ -0,0 +1,5 @@
+---
+"@ensnode/ensnode-sdk": minor
+---
+
+BREAKING: client.config() now returns Promise instead of ENSIndexerPublicConfig.
diff --git a/.changeset/sixty-onions-leave.md b/.changeset/sixty-onions-leave.md
new file mode 100644
index 0000000000..542b2cf811
--- /dev/null
+++ b/.changeset/sixty-onions-leave.md
@@ -0,0 +1,5 @@
+---
+"ensadmin": minor
+---
+
+ENSAdmin now displays whether ENSNode attempted acceleration for an acceleratable endpoint in the Protocol Inspector.
diff --git a/.changeset/young-badgers-trade.md b/.changeset/young-badgers-trade.md
new file mode 100644
index 0000000000..812a843358
--- /dev/null
+++ b/.changeset/young-badgers-trade.md
@@ -0,0 +1,5 @@
+---
+"@ensnode/ensnode-react": minor
+---
+
+BREAKING: `useENSNodeConfig` has been renamed to `useENSNodeSDKConfig`. `useENSIndexerConfig` has been renamed to `useENSNodeConfig`.
diff --git a/apps/ensadmin/src/app/inspect/_components/render-requests-output.tsx b/apps/ensadmin/src/app/inspect/_components/render-requests-output.tsx
index fcce26208e..2e3257124b 100644
--- a/apps/ensadmin/src/app/inspect/_components/render-requests-output.tsx
+++ b/apps/ensadmin/src/app/inspect/_components/render-requests-output.tsx
@@ -1,5 +1,5 @@
import type { UseQueryResult } from "@tanstack/react-query";
-import { useMemo, useState } from "react";
+import { useEffect, useMemo, useState } from "react";
import {
type AcceleratableResponse,
@@ -36,29 +36,23 @@ export function RenderRequestsOutput({
}) {
const [tab, setTab] = useState("accelerated");
- // TODO: produce a diff between accelerated/not-accelerated and display any differences
- const result = useMemo(() => {
- if (tab === "accelerated" && accelerated.status === "success") {
- return accelerated.data[dataKey];
- }
+ const focused = useMemo(() => {
+ if (tab === "accelerated") return accelerated;
+ if (tab === "unaccelerated") return unaccelerated;
- if (tab === "unaccelerated" && unaccelerated.status === "success") {
- return unaccelerated.data[dataKey];
- }
-
- return accelerated.data?.[dataKey] || unaccelerated.data?.[dataKey];
- }, [accelerated, unaccelerated, tab, dataKey]);
+ throw new Error("never");
+ }, [accelerated, unaccelerated]);
- const someError = accelerated.error || unaccelerated.error;
+ // need special derivation to capture refetching state
+ const acceleratedLoading = accelerated.isPending || accelerated.isRefetching;
+ const unacceleratedLoading = unaccelerated.isPending || unaccelerated.isRefetching;
- // show major loading if either query is pending/refreshing
- const showLoading =
- (accelerated.isPending || accelerated.isRefetching) &&
- (unaccelerated.isPending || unaccelerated.isRefetching);
+ const acceleratedSuccess = !acceleratedLoading && !accelerated.isError;
+ const unacceleratedSuccess = !unacceleratedLoading && !unaccelerated.isError;
const multipleDiff = useMemo(() => {
- if (accelerated.status !== "success") return null;
- if (unaccelerated.status !== "success") return null;
+ if (!acceleratedSuccess) return null;
+ if (!unacceleratedSuccess) return null;
if (!accelerated.data.trace) return null;
if (!unaccelerated.data.trace) return null;
@@ -66,17 +60,21 @@ export function RenderRequestsOutput({
const acceleratedDuration = getTraceDuration(accelerated.data.trace);
const unacceleratedDuration = getTraceDuration(unaccelerated.data.trace);
- if (acceleratedDuration === 0) return null;
+ if (acceleratedDuration === 0) return null; // prevent division by zero...
- const multiple = unacceleratedDuration / acceleratedDuration;
- return multiple;
+ return unacceleratedDuration / acceleratedDuration;
}, [accelerated, unaccelerated]);
- if (showLoading) {
- // if we're loading but there's no active fetch, the query is unable to be executed, so render null
- if (accelerated.fetchStatus === "idle") return null;
+ useEffect(() => {
+ if (unacceleratedLoading) setTab("accelerated");
+ }, [unacceleratedLoading, setTab]);
+
+ // if we're loading but there's no active fetch, the query is unable to be executed, so render null
+ const isNotExecutable = acceleratedLoading && accelerated.fetchStatus === "idle";
+ if (isNotExecutable) return null;
- // otherwise, we're in-flight, render loading
+ // show major loading if accelerated query is pending/refreshing
+ if (acceleratedLoading) {
return (
@@ -90,20 +88,21 @@ export function RenderRequestsOutput({
return (
<>
+ {/* Response Card */}
ENSNode Response
{(() => {
- if (someError) {
+ if (focused.error) {
return (
{JSON.stringify(
{
- message: someError.message,
- ...(someError instanceof ClientError &&
- !!someError.details && { details: someError.details }),
+ message: focused.error.message,
+ ...(focused.error instanceof ClientError &&
+ !!focused.error.details && { details: focused.error.details }),
},
null,
2,
@@ -114,13 +113,15 @@ export function RenderRequestsOutput({
return (
- {JSON.stringify(result, null, 2)}
+ {JSON.stringify(focused.data?.[dataKey], null, 2)}
);
})()}
- {!someError && (accelerated.data?.trace || unaccelerated.data?.trace) && (
+
+ {/* Execution Trace Card */}
+ {acceleratedSuccess && (
@@ -156,25 +157,24 @@ export function RenderRequestsOutput({
return null;
})()}
+
Accelerated
- {accelerated.data ? (
- `(${
- // biome-ignore lint/style/noNonNullAssertion: exists
- renderTraceDuration(accelerated.data.trace!)
- })`
+ {acceleratedSuccess && accelerated.data.trace ? (
+ `(${renderTraceDuration(accelerated.data.trace)})`
) : (
)}
-
+
Unaccelerated
- {unaccelerated.data ? (
- `(${
- // biome-ignore lint/style/noNonNullAssertion: exists
- renderTraceDuration(unaccelerated.data.trace!)
- })`
+ {unacceleratedSuccess && unaccelerated.data.trace ? (
+ `(${renderTraceDuration(unaccelerated.data.trace)})`
) : (
)}
@@ -184,44 +184,14 @@ export function RenderRequestsOutput({
- {(() => {
- switch (accelerated.status) {
- case "pending": {
- return (
-
-
-
- );
- }
- case "success": {
- if (accelerated.data.trace)
- return ;
- throw new Error(
- "Invariant: RenderRequestsOutput accelerated.data.trace is undefined.",
- );
- }
- }
- })()}
+ {acceleratedSuccess && !!accelerated.data.trace && (
+
+ )}
- {(() => {
- switch (unaccelerated.status) {
- case "pending": {
- return (
-
-
-
- );
- }
- case "success": {
- if (unaccelerated.data.trace)
- return ;
- throw new Error(
- "Invariant: RenderRequestsOutput unaccelerated.data.trace is undefined.",
- );
- }
- }
- })()}
+ {unacceleratedSuccess && !!unaccelerated.data.trace && (
+
+ )}
diff --git a/apps/ensadmin/src/app/inspect/records/page.tsx b/apps/ensadmin/src/app/inspect/records/page.tsx
index 893e93f4a8..caec8c8e33 100644
--- a/apps/ensadmin/src/app/inspect/records/page.tsx
+++ b/apps/ensadmin/src/app/inspect/records/page.tsx
@@ -5,7 +5,6 @@ import { useState } from "react";
import { useDebouncedValue } from "rooks";
import { useRecords } from "@ensnode/ensnode-react";
-import { DefaultRecordsSelection } from "@ensnode/ensnode-sdk";
import { RenderRequestsOutput } from "@/app/inspect/_components/render-requests-output";
import { Pill } from "@/components/pill";
@@ -13,6 +12,8 @@ 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 { DefaultRecordsSelection } from "@/lib/default-records-selection";
const EXAMPLE_INPUT = [
"vitalik.eth",
@@ -30,8 +31,9 @@ const EXAMPLE_INPUT = [
// TODO: showcase current ENSNode configuration and viable acceleration pathways?
// TODO: use shadcn/form, react-hook-form, and zod to make all of this nicer aross the board
-// TODO: sync form state to query params, current just defaulting is supported
+// TODO: sync form state to query params, currently just defaulting is supported
export default function ResolveRecordsInspector() {
+ const namespace = useActiveNamespace();
const searchParams = useSearchParams();
const [name, setName] = useState(searchParams.get("name") || EXAMPLE_INPUT[0]);
@@ -39,8 +41,7 @@ export default function ResolveRecordsInspector() {
const canQuery = !!debouncedName && debouncedName.length > 0;
- // TODO: switch on connected ensnode's configured namespace
- const selection = DefaultRecordsSelection.mainnet;
+ const selection = DefaultRecordsSelection[namespace];
const accelerated = useRecords({
name: debouncedName,
diff --git a/apps/ensadmin/src/app/mock/recent-registrations/page.tsx b/apps/ensadmin/src/app/mock/recent-registrations/page.tsx
index f690213b34..fcc711102e 100644
--- a/apps/ensadmin/src/app/mock/recent-registrations/page.tsx
+++ b/apps/ensadmin/src/app/mock/recent-registrations/page.tsx
@@ -16,7 +16,6 @@ import {
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
-import { ensIndexerPublicConfig } from "../config-api.mock";
import { indexingStatusResponseOkOmnichain } from "../indexing-status-api.mock";
type LoadingVariant = "Loading" | "Loading Error";
@@ -39,7 +38,7 @@ export default function MockRegistrationsPage() {
return {
error: {
title: "RecentRegistrations Error",
- description: "Failed to fetch ENSIndexerConfig or IndexingStatus.",
+ description: "Failed to fetch IndexingStatus.",
},
} satisfies RecentRegistrationsErrorProps;
@@ -61,7 +60,6 @@ export default function MockRegistrationsPage() {
}
return {
- ensIndexerConfig: ensIndexerPublicConfig,
realtimeProjection: indexingStatus.realtimeProjection,
} satisfies RecentRegistrationsOkProps;
} catch (error) {
@@ -113,10 +111,7 @@ export default function MockRegistrationsPage() {
{typeof props.error !== "undefined" ? (
) : (
-
+
)}
);
diff --git a/apps/ensadmin/src/app/name/_components/NameDetailPageContent.tsx b/apps/ensadmin/src/app/name/_components/NameDetailPageContent.tsx
index 40b49e7829..b0b1340ab4 100644
--- a/apps/ensadmin/src/app/name/_components/NameDetailPageContent.tsx
+++ b/apps/ensadmin/src/app/name/_components/NameDetailPageContent.tsx
@@ -1,10 +1,11 @@
"use client";
import { ASSUME_IMMUTABLE_QUERY, useRecords } from "@ensnode/ensnode-react";
-import { getCommonCoinTypes, type Name, type ResolverRecordsSelection } from "@ensnode/ensnode-sdk";
+import { type Name, type ResolverRecordsSelection } from "@ensnode/ensnode-sdk";
import { Card, CardContent } from "@/components/ui/card";
import { useActiveNamespace } from "@/hooks/active/use-active-namespace";
+import { getCommonCoinTypes } from "@/lib/default-records-selection";
import { AdditionalRecords } from "./AdditionalRecords";
import { Addresses } from "./Addresses";
diff --git a/apps/ensadmin/src/components/connection/config-info/config-info.tsx b/apps/ensadmin/src/components/connection/config-info/config-info.tsx
index a1108faa0f..0a11419ad5 100644
--- a/apps/ensadmin/src/components/connection/config-info/config-info.tsx
+++ b/apps/ensadmin/src/components/connection/config-info/config-info.tsx
@@ -8,8 +8,8 @@
import { Replace } from "lucide-react";
import { ReactNode } from "react";
-import { useENSIndexerConfig } from "@ensnode/ensnode-react";
-import { ENSIndexerPublicConfig } from "@ensnode/ensnode-sdk";
+import { useENSNodeConfig } from "@ensnode/ensnode-react";
+import { type ENSApiPublicConfig, getENSRootChainId } from "@ensnode/ensnode-sdk";
import { ChainIcon } from "@/components/chains/ChainIcon";
import { ConfigInfoAppCard } from "@/components/connection/config-info/app-card";
@@ -84,16 +84,16 @@ function ENSNodeCardLoadingSkeleton() {
* Props for ENSNodeConfigCardDisplay - display component that accepts props for testing/mocking
*/
export interface ENSNodeConfigCardDisplayProps {
- ensIndexerConfig: ENSIndexerPublicConfig;
+ ensApiPublicConfig: ENSApiPublicConfig;
}
/**
* Display component that receives props - used for reusable/mockable presentation
*/
-export function ENSNodeConfigCardDisplay({ ensIndexerConfig }: ENSNodeConfigCardDisplayProps) {
+export function ENSNodeConfigCardDisplay({ ensApiPublicConfig }: ENSNodeConfigCardDisplayProps) {
return (
-
+
);
}
@@ -102,7 +102,7 @@ export function ENSNodeConfigCardDisplay({ ensIndexerConfig }: ENSNodeConfigCard
* Props for ENSNodeConfigInfoView - internal component that accepts props for testing/mocking
*/
export interface ENSNodeConfigInfoViewProps {
- ensIndexerConfig?: ENSIndexerPublicConfig;
+ ensApiPublicConfig?: ENSApiPublicConfig;
error?: ErrorInfoProps;
isLoading?: boolean;
}
@@ -111,7 +111,7 @@ export interface ENSNodeConfigInfoViewProps {
* Internal view component that accepts props - used by both the main component and mock pages
*/
export function ENSNodeConfigInfoView({
- ensIndexerConfig,
+ ensApiPublicConfig,
error,
isLoading = false,
}: ENSNodeConfigInfoViewProps) {
@@ -120,7 +120,7 @@ export function ENSNodeConfigInfoView({
}
// Show ENSNode card - shell with skeleton while loading, or content when ready
- if (isLoading || !ensIndexerConfig) {
+ if (isLoading || !ensApiPublicConfig) {
return (
@@ -128,42 +128,44 @@ export function ENSNodeConfigInfoView({
);
}
- return ;
+ return ;
}
/**
* ENSNodeConfigInfo component - fetches and displays ENSNode configuration data
*/
export function ENSNodeConfigInfo() {
- const ensIndexerConfigQuery = useENSIndexerConfig();
+ const ensNodeConfigQuery = useENSNodeConfig();
return (
);
}
function ENSNodeConfigCardContent({
- ensIndexerConfig,
+ ensApiPublicConfig,
}: {
- ensIndexerConfig: ENSIndexerPublicConfig;
+ ensApiPublicConfig: ENSApiPublicConfig;
}) {
const cardItemValueStyles = "text-sm leading-6 font-normal text-black";
- const healReverseAddressesActivated = !ensIndexerConfig.isSubgraphCompatible;
- const indexAdditionalRecordsActivated = !ensIndexerConfig.isSubgraphCompatible;
- const replaceUnnormalizedLabelsActivated = !ensIndexerConfig.isSubgraphCompatible;
- const subgraphCompatibilityActivated = ensIndexerConfig.isSubgraphCompatible;
+ const { ensIndexerPublicConfig } = ensApiPublicConfig;
+
+ const healReverseAddressesActivated = !ensIndexerPublicConfig.isSubgraphCompatible;
+ const indexAdditionalRecordsActivated = !ensIndexerPublicConfig.isSubgraphCompatible;
+ const replaceUnnormalizedLabelsActivated = !ensIndexerPublicConfig.isSubgraphCompatible;
+ const subgraphCompatibilityActivated = ensIndexerPublicConfig.isSubgraphCompatible;
const healReverseAddressesDescription = healReverseAddressesActivated ? (
Subnames of addr.reverse will all be known (healed) labels.
@@ -212,6 +214,8 @@ function ENSNodeConfigCardContent({
);
+ const ensRootChainId = getENSRootChainId(ensIndexerPublicConfig.namespace);
+
return (
<>
{/*ENSDb*/}
@@ -225,7 +229,9 @@ function ENSNodeConfigCardContent({
},
{
label: "Database Schema",
- value: {ensIndexerConfig.databaseSchemaName}
,
+ value: (
+ {ensIndexerPublicConfig.databaseSchemaName}
+ ),
additionalInfo: (
ENSIndexer writes indexed data to tables within this Postgres database schema.
),
@@ -233,7 +239,7 @@ function ENSNodeConfigCardContent({
]}
version={
- v{ensIndexerConfig.versionInfo.ensDb}
+ v{ensIndexerPublicConfig.versionInfo.ensDb}
}
docsLink={new URL("https://ensnode.io/ensdb/")}
@@ -246,12 +252,14 @@ function ENSNodeConfigCardContent({
items={[
{
label: "Node.js",
- value: {ensIndexerConfig.versionInfo.nodejs}
,
+ value: (
+ {ensIndexerPublicConfig.versionInfo.nodejs}
+ ),
additionalInfo: (
Version of the{" "}
Node.js
{" "}
@@ -261,12 +269,14 @@ function ENSNodeConfigCardContent({
},
{
label: "Ponder",
- value:
{ensIndexerConfig.versionInfo.ponder}
,
+ value: (
+ {ensIndexerPublicConfig.versionInfo.ponder}
+ ),
additionalInfo: (
Version of the{" "}
ponder
{" "}
@@ -277,13 +287,15 @@ function ENSNodeConfigCardContent({
{
label: "ens-normalize.js",
value: (
-
{ensIndexerConfig.versionInfo.ensNormalize}
+
+ {ensIndexerPublicConfig.versionInfo.ensNormalize}
+
),
additionalInfo: (
Version of the{" "}
@adraffy/ens-normalize
{" "}
@@ -296,7 +308,8 @@ function ENSNodeConfigCardContent({
value: (
-
- {ensIndexerConfig.labelSet.labelSetId}:{ensIndexerConfig.labelSet.labelSetVersion}
+ {ensIndexerPublicConfig.labelSet.labelSetId}:
+ {ensIndexerPublicConfig.labelSet.labelSetVersion}
),
@@ -315,14 +328,14 @@ function ENSNodeConfigCardContent({
},
{
label: "ENS Namespace",
- value: {ensIndexerConfig.namespace}
,
+ value: {ensIndexerPublicConfig.namespace}
,
additionalInfo: The ENS namespace that ENSNode operates in the context of.
,
},
{
label: "Indexed Chains",
value: (
- {Array.from(ensIndexerConfig.indexedChainIds).map((chainId) => (
+ {Array.from(ensIndexerPublicConfig.indexedChainIds).map((chainId) => (
@@ -342,7 +355,7 @@ function ENSNodeConfigCardContent({
label: "Plugins",
value: (
- {ensIndexerConfig.plugins.map((plugin) => (
+ {ensIndexerPublicConfig.plugins.map((plugin) => (
- v{ensIndexerConfig.versionInfo.ensIndexer}
+ v{ensIndexerPublicConfig.versionInfo.ensIndexer}
}
docsLink={new URL("https://ensnode.io/ensindexer/")}
@@ -399,7 +412,8 @@ function ENSNodeConfigCardContent({
label: "Server LabelSet",
value: (
- {ensIndexerConfig.labelSet.labelSetId}:{ensIndexerConfig.labelSet.labelSetVersion}
+ {ensIndexerPublicConfig.labelSet.labelSetId}:
+ {ensIndexerPublicConfig.labelSet.labelSetVersion}
),
additionalInfo: (
@@ -416,7 +430,7 @@ function ENSNodeConfigCardContent({
]}
version={
- v{ensIndexerConfig.versionInfo.ensRainbow}
+ v{ensIndexerPublicConfig.versionInfo.ensRainbow}
}
docsLink={new URL("https://ensnode.io/ensrainbow/")}
diff --git a/apps/ensadmin/src/components/connection/index.tsx b/apps/ensadmin/src/components/connection/index.tsx
index d714f69ca9..c71a88f171 100644
--- a/apps/ensadmin/src/components/connection/index.tsx
+++ b/apps/ensadmin/src/components/connection/index.tsx
@@ -1,11 +1,12 @@
"use client";
+import packageJson from "@/../package.json" with { type: "json" };
+
import { PlugZap } from "lucide-react";
import { ENSNodeConfigInfo } from "@/components/connection/config-info";
import { ConfigInfoAppCard } from "@/components/connection/config-info/app-card";
import { CopyButton } from "@/components/copy-button";
-import { ENSAdminVersion } from "@/components/ensadmin-version";
import { ENSAdminIcon } from "@/components/icons/ensnode-apps/ensadmin-icon";
import { useSelectedConnection } from "@/hooks/active/use-selected-connection";
@@ -26,7 +27,11 @@ export default function ConnectionInfo() {
}
- version={}
+ version={
+
+ v{packageJson.version}
+
+ }
docsLink={new URL("https://ensnode.io/ensadmin/")}
/>
diff --git a/apps/ensadmin/src/components/connections/connections-library-selector.tsx b/apps/ensadmin/src/components/connections/connections-library-selector.tsx
index be9fe64e3c..7eb69b71af 100644
--- a/apps/ensadmin/src/components/connections/connections-library-selector.tsx
+++ b/apps/ensadmin/src/components/connections/connections-library-selector.tsx
@@ -73,7 +73,7 @@ export function ConnectionsLibrarySelector() {
} else if (!selectedConnection.validatedSelectedConnection.isValid) {
connectionMessage = "Invalid connection";
} else {
- connectionMessage = "Select ENSNode";
+ connectionMessage = selectedConnection.validatedSelectedConnection.url.href;
}
const serverConnections = connectionLibrary.filter((connection) => connection.type === "server");
diff --git a/apps/ensadmin/src/components/connections/require-active-connection.tsx b/apps/ensadmin/src/components/connections/require-active-connection.tsx
index 5f0190f76b..683f4308a7 100644
--- a/apps/ensadmin/src/components/connections/require-active-connection.tsx
+++ b/apps/ensadmin/src/components/connections/require-active-connection.tsx
@@ -2,7 +2,7 @@
import type { PropsWithChildren } from "react";
-import { useENSIndexerConfig } from "@ensnode/ensnode-react";
+import { useENSNodeConfig } from "@ensnode/ensnode-react";
import { ErrorInfo } from "@/components/error-info";
import { LoadingSpinner } from "@/components/loading-spinner";
@@ -11,7 +11,7 @@ import { LoadingSpinner } from "@/components/loading-spinner";
* Allows consumers to use `useActiveConnection` by blocking rendering until it is available.
*/
export function RequireActiveConnection({ children }: PropsWithChildren) {
- const { status, error } = useENSIndexerConfig();
+ const { status, error } = useENSNodeConfig();
if (status === "pending") return ;
diff --git a/apps/ensadmin/src/components/ensadmin-version.tsx b/apps/ensadmin/src/components/ensadmin-version.tsx
deleted file mode 100644
index 4416b492e8..0000000000
--- a/apps/ensadmin/src/components/ensadmin-version.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-"use client";
-
-import { useEffect, useState } from "react";
-
-import { Skeleton } from "@/components/ui/skeleton";
-import { ensAdminVersion } from "@/lib/env";
-
-export function ENSAdminVersion() {
- const [version, setVersion] = useState(null);
-
- useEffect(() => {
- ensAdminVersion().then(setVersion);
- }, []);
-
- if (version === null) {
- return ;
- }
-
- return (
- v{version}
- );
-}
diff --git a/apps/ensadmin/src/components/recent-registrations/components.tsx b/apps/ensadmin/src/components/recent-registrations/components.tsx
index e151e62db6..dd8a9c6078 100644
--- a/apps/ensadmin/src/components/recent-registrations/components.tsx
+++ b/apps/ensadmin/src/components/recent-registrations/components.tsx
@@ -5,7 +5,6 @@ import Link from "next/link";
import { Fragment } from "react";
import {
- type ENSIndexerPublicConfig,
type OmnichainIndexingStatusId,
OmnichainIndexingStatusIds,
type OmnichainIndexingStatusSnapshot,
@@ -41,7 +40,6 @@ const SUPPORTED_OMNICHAIN_INDEXING_STATUSES: OmnichainIndexingStatusId[] = [
];
export interface RecentRegistrationsOkProps {
- ensIndexerConfig: ENSIndexerPublicConfig | undefined;
realtimeProjection: RealtimeIndexingStatusProjection | undefined;
maxRecords?: number;
}
@@ -54,18 +52,15 @@ export interface RecentRegistrationsErrorProps {
* RecentRegistrations display variations:
*
* Standard -
- * ensIndexerConfig: {@link ENSIndexerPublicConfig},
* indexingStatus: {@link OmnichainIndexingStatusSnapshotCompleted} |
* {@link OmnichainIndexingStatusSnapshotFollowing},
*
* UnsupportedOmnichainIndexingStatusMessage -
- * ensIndexerConfig: {@link ENSIndexerPublicConfig},
* indexingStatus: {@link OmnichainIndexingStatusSnapshot} other than
* {@link OmnichainIndexingStatusSnapshotCompleted} |
* {@link OmnichainIndexingStatusSnapshotFollowing},
*
* Loading -
- * ensIndexerConfig: undefined,
* indexingStatus: undefined,
*
* Error -
@@ -84,9 +79,9 @@ export function RecentRegistrations(props: RecentRegistrationsProps) {
return ;
}
- const { ensIndexerConfig, realtimeProjection, maxRecords = DEFAULT_MAX_RECORDS } = props;
+ const { realtimeProjection, maxRecords = DEFAULT_MAX_RECORDS } = props;
- if (ensIndexerConfig === undefined || realtimeProjection === undefined) {
+ if (realtimeProjection === undefined) {
return ;
}
diff --git a/apps/ensadmin/src/components/recent-registrations/hooks.ts b/apps/ensadmin/src/components/recent-registrations/hooks.ts
index 2496126768..28c0e2c0b8 100644
--- a/apps/ensadmin/src/components/recent-registrations/hooks.ts
+++ b/apps/ensadmin/src/components/recent-registrations/hooks.ts
@@ -6,7 +6,6 @@ import { deserializeUnixTimestamp, type Name, type UnixTimestamp } from "@ensnod
import { useActiveNamespace } from "@/hooks/active/use-active-namespace";
import { useSelectedConnection } from "@/hooks/active/use-selected-connection";
-import { ensAdminVersion } from "@/lib/env";
import { getNameWrapperAddress } from "@/lib/namespace-utils";
import type { Registration } from "./types";
@@ -126,10 +125,7 @@ async function fetchRecentRegistrations(
const response = await fetch(new URL(`/subgraph`, ensNodeUrl), {
method: "POST",
- headers: {
- "content-type": "application/json",
- "x-ensadmin-version": await ensAdminVersion(),
- },
+ headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query }),
});
diff --git a/apps/ensadmin/src/components/recent-registrations/registrations.tsx b/apps/ensadmin/src/components/recent-registrations/registrations.tsx
index ce5ca20449..6b8e91325d 100644
--- a/apps/ensadmin/src/components/recent-registrations/registrations.tsx
+++ b/apps/ensadmin/src/components/recent-registrations/registrations.tsx
@@ -1,52 +1,27 @@
"use client";
-import { useENSIndexerConfig, useIndexingStatus } from "@ensnode/ensnode-react";
+import { useIndexingStatus } from "@ensnode/ensnode-react";
import { IndexingStatusResponseCodes } from "@ensnode/ensnode-sdk";
import { RecentRegistrations } from "@/components/recent-registrations/components";
export function Registrations() {
- const ensIndexerConfigQuery = useENSIndexerConfig();
- const indexingStatusQuery = useIndexingStatus();
+ const { status, data: indexingStatus, error } = useIndexingStatus();
- if (ensIndexerConfigQuery.isError) {
+ if (status === "pending") {
return (
- );
- }
-
- if (indexingStatusQuery.isError) {
- return (
-
);
}
- if (!ensIndexerConfigQuery.isSuccess || !indexingStatusQuery.isSuccess) {
+ if (status === "error") {
return (
-
- {" "}
- {/*display loading state*/}
-
+
);
}
- const ensIndexerConfig = ensIndexerConfigQuery.data;
- const indexingStatus = indexingStatusQuery.data;
-
// even though indexing status was fetched successfully,
// it can still refer to a server-side error
if (indexingStatus.responseCode === IndexingStatusResponseCodes.Error) {
@@ -62,12 +37,5 @@ export function Registrations() {
);
}
- return (
-
- );
+ return ;
}
diff --git a/apps/ensadmin/src/hooks/active/use-active-connection.tsx b/apps/ensadmin/src/hooks/active/use-active-connection.tsx
index c6358ac0e8..e1fa783caf 100644
--- a/apps/ensadmin/src/hooks/active/use-active-connection.tsx
+++ b/apps/ensadmin/src/hooks/active/use-active-connection.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useENSIndexerConfig } from "@ensnode/ensnode-react";
+import { useENSNodeConfig } from "@ensnode/ensnode-react";
/**
* Hook to get the currently active ENSNode connection synchronously.
@@ -16,7 +16,7 @@ import { useENSIndexerConfig } from "@ensnode/ensnode-react";
* @throws Error if no active ENSNode connection is available
*/
export function useActiveConnection() {
- const { data } = useENSIndexerConfig();
+ const { data } = useENSNodeConfig();
if (data === undefined) {
throw new Error(`Invariant(useActiveConnection): Expected an active ENSNode Config`);
diff --git a/apps/ensadmin/src/hooks/active/use-active-namespace.ts b/apps/ensadmin/src/hooks/active/use-active-namespace.ts
index 9acbe53f1a..486417e7c8 100644
--- a/apps/ensadmin/src/hooks/active/use-active-namespace.ts
+++ b/apps/ensadmin/src/hooks/active/use-active-namespace.ts
@@ -13,4 +13,4 @@ import { useActiveConnection } from "./use-active-connection";
* @returns The namespace from the active ENSNode configuration
* @throws Error if no active ENSNode Config is available
*/
-export const useActiveNamespace = () => useActiveConnection().namespace;
+export const useActiveNamespace = () => useActiveConnection().ensIndexerPublicConfig.namespace;
diff --git a/apps/ensadmin/src/hooks/async/use-namespace.ts b/apps/ensadmin/src/hooks/async/use-namespace.ts
index 952ae0a3bd..be3a0925ed 100644
--- a/apps/ensadmin/src/hooks/async/use-namespace.ts
+++ b/apps/ensadmin/src/hooks/async/use-namespace.ts
@@ -1,4 +1,4 @@
-import { useENSIndexerConfig } from "@ensnode/ensnode-react";
+import { useENSNodeConfig } from "@ensnode/ensnode-react";
/**
* Hook to get the namespace ID from the active ENSNode connection.
@@ -22,10 +22,10 @@ import { useENSIndexerConfig } from "@ensnode/ensnode-react";
* ```
*/
export function useNamespace() {
- const query = useENSIndexerConfig();
+ const query = useENSNodeConfig();
return {
...query,
- data: query.data?.namespace ?? null,
+ data: query.data?.ensIndexerPublicConfig.namespace ?? null,
};
}
diff --git a/packages/ensnode-sdk/src/resolution/default-records-selection.ts b/apps/ensadmin/src/lib/default-records-selection.ts
similarity index 73%
rename from packages/ensnode-sdk/src/resolution/default-records-selection.ts
rename to apps/ensadmin/src/lib/default-records-selection.ts
index 4c0cc04a65..80e59d99d9 100644
--- a/packages/ensnode-sdk/src/resolution/default-records-selection.ts
+++ b/apps/ensadmin/src/lib/default-records-selection.ts
@@ -4,10 +4,13 @@ import {
ENSNamespaceIds,
maybeGetDatasource,
} from "@ensnode/datasources";
-
-import { type CoinType, ETH_COIN_TYPE, evmChainIdToCoinType } from "../ens";
-import { uniq } from "../shared";
-import type { ResolverRecordsSelection } from "./resolver-records-selection";
+import {
+ CoinType,
+ ETH_COIN_TYPE,
+ evmChainIdToCoinType,
+ ResolverRecordsSelection,
+ uniq,
+} from "@ensnode/ensnode-sdk";
const getENSIP19SupportedCoinTypes = (namespace: ENSNamespaceId) =>
uniq(
@@ -37,11 +40,9 @@ const TEXTS = [
"com.github",
] as const satisfies string[];
-// TODO: Phase out this concept. All apps should define their own selection of records.
-// Additionally, we should update `useRecords` so that it can return not only all the
-// (texts / addresses) records that are explicitly requested, but also any other (texts / addresses)
-// records that ENSNode has found onchain.
-// see: https://github.com/namehash/ensnode/issues/1084
+/**
+ * Defines a set of 'default' records to query when making Protocol Inspector requests.
+ */
export const DefaultRecordsSelection = {
[ENSNamespaceIds.Mainnet]: {
addresses: getCommonCoinTypes(ENSNamespaceIds.Mainnet),
diff --git a/apps/ensadmin/src/lib/env.ts b/apps/ensadmin/src/lib/env.ts
index e9d5cae92c..8d30c082d8 100644
--- a/apps/ensadmin/src/lib/env.ts
+++ b/apps/ensadmin/src/lib/env.ts
@@ -125,9 +125,3 @@ export function getServerConnectionLibrary(): HttpHostname[] {
return uniqueConnections;
}
-
-export async function ensAdminVersion(): Promise {
- const packageJson = await import("@/../package.json");
-
- return packageJson.version;
-}
diff --git a/apps/ensapi/.env.local.example b/apps/ensapi/.env.local.example
index aef395b3a6..b967d7624e 100644
--- a/apps/ensapi/.env.local.example
+++ b/apps/ensapi/.env.local.example
@@ -53,3 +53,8 @@ DATABASE_URL=postgresql://dbuser:abcd1234@localhost:5432/my_database
# RPC_URL_11155111=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
# RPC_URL_17000=https://eth-holesky.g.alchemy.com/v2/YOUR_API_KEY
# RPC_URL_1337=http://localhost:8545
+
+# Log Level
+# Optional. If this is not set, the default value is "info".
+# Allowed values: "fatal", "error", "warn", "info", "debug", "trace", "silent".
+# LOG_LEVEL=info
diff --git a/apps/ensapi/package.json b/apps/ensapi/package.json
index 64b70560db..2289740e6e 100644
--- a/apps/ensapi/package.json
+++ b/apps/ensapi/package.json
@@ -44,6 +44,7 @@
"p-memoize": "^8.0.0",
"p-reflect": "^3.1.0",
"p-retry": "^7.1.0",
+ "pino": "^10.1.0",
"ponder": "catalog:",
"ponder-enrich-gql-docs-middleware": "^0.1.3",
"viem": "catalog:",
@@ -52,6 +53,7 @@
"devDependencies": {
"@ensnode/shared-configs": "workspace:*",
"@types/node": "catalog:",
+ "pino-pretty": "^13.1.2",
"tsx": "^4.7.1",
"typescript": "catalog:",
"vitest": "catalog:"
diff --git a/apps/ensapi/src/config/config.schema.test.ts b/apps/ensapi/src/config/config.schema.test.ts
index 721a2de00c..7ca3b42bdd 100644
--- a/apps/ensapi/src/config/config.schema.test.ts
+++ b/apps/ensapi/src/config/config.schema.test.ts
@@ -9,7 +9,7 @@ import {
} from "@ensnode/ensnode-sdk";
import type { RpcConfig } from "@ensnode/ensnode-sdk/internal";
-import { buildConfigFromEnvironment } from "@/config/config.schema";
+import { buildConfigFromEnvironment, buildEnsApiPublicConfig } from "@/config/config.schema";
import { ENSApi_DEFAULT_PORT } from "@/config/defaults";
import type { EnsApiEnvironment } from "@/config/environment";
@@ -73,3 +73,61 @@ describe("buildConfigFromEnvironment", () => {
});
});
});
+
+describe("buildEnsApiPublicConfig", () => {
+ it("returns a valid ENSApi public config with correct structure", () => {
+ const mockConfig = {
+ port: ENSApi_DEFAULT_PORT,
+ databaseUrl: BASE_ENV.DATABASE_URL,
+ ensIndexerUrl: new URL(BASE_ENV.ENSINDEXER_URL),
+ ensIndexerPublicConfig: ENSINDEXER_PUBLIC_CONFIG,
+ namespace: ENSINDEXER_PUBLIC_CONFIG.namespace,
+ databaseSchemaName: ENSINDEXER_PUBLIC_CONFIG.databaseSchemaName,
+ rpcConfigs: new Map([
+ [
+ 1,
+ {
+ httpRPCs: [new URL(VALID_RPC_URL)],
+ websocketRPC: undefined,
+ } satisfies RpcConfig,
+ ],
+ ]),
+ };
+
+ const result = buildEnsApiPublicConfig(mockConfig);
+
+ expect(result).toStrictEqual({
+ version: packageJson.version,
+ ensIndexerPublicConfig: ENSINDEXER_PUBLIC_CONFIG,
+ });
+ });
+
+ it("preserves the complete ENSIndexer public config structure", () => {
+ const mockConfig = {
+ port: ENSApi_DEFAULT_PORT,
+ databaseUrl: BASE_ENV.DATABASE_URL,
+ ensIndexerUrl: new URL(BASE_ENV.ENSINDEXER_URL),
+ ensIndexerPublicConfig: ENSINDEXER_PUBLIC_CONFIG,
+ namespace: ENSINDEXER_PUBLIC_CONFIG.namespace,
+ databaseSchemaName: ENSINDEXER_PUBLIC_CONFIG.databaseSchemaName,
+ rpcConfigs: new Map(),
+ };
+
+ const result = buildEnsApiPublicConfig(mockConfig);
+
+ // Verify that all ENSIndexer public config fields are preserved
+ expect(result.ensIndexerPublicConfig.namespace).toBe(ENSINDEXER_PUBLIC_CONFIG.namespace);
+ expect(result.ensIndexerPublicConfig.plugins).toEqual(ENSINDEXER_PUBLIC_CONFIG.plugins);
+ expect(result.ensIndexerPublicConfig.versionInfo).toEqual(ENSINDEXER_PUBLIC_CONFIG.versionInfo);
+ expect(result.ensIndexerPublicConfig.indexedChainIds).toEqual(
+ ENSINDEXER_PUBLIC_CONFIG.indexedChainIds,
+ );
+ expect(result.ensIndexerPublicConfig.isSubgraphCompatible).toBe(
+ ENSINDEXER_PUBLIC_CONFIG.isSubgraphCompatible,
+ );
+ expect(result.ensIndexerPublicConfig.labelSet).toEqual(ENSINDEXER_PUBLIC_CONFIG.labelSet);
+ expect(result.ensIndexerPublicConfig.databaseSchemaName).toBe(
+ ENSINDEXER_PUBLIC_CONFIG.databaseSchemaName,
+ );
+ });
+});
diff --git a/apps/ensapi/src/config/config.schema.ts b/apps/ensapi/src/config/config.schema.ts
index c1b3fa40cd..c5e1bfe39b 100644
--- a/apps/ensapi/src/config/config.schema.ts
+++ b/apps/ensapi/src/config/config.schema.ts
@@ -1,7 +1,9 @@
+import packageJson from "@/../package.json" with { type: "json" };
+
import pRetry from "p-retry";
import { prettifyError, ZodError, z } from "zod/v4";
-import { ENSNodeClient, serializeENSIndexerPublicConfig } from "@ensnode/ensnode-sdk";
+import { type ENSApiPublicConfig, serializeENSIndexerPublicConfig } from "@ensnode/ensnode-sdk";
import {
buildRpcConfigsFromEnv,
DatabaseSchemaNameSchema,
@@ -17,6 +19,8 @@ import {
import { ENSApi_DEFAULT_PORT } from "@/config/defaults";
import type { EnsApiEnvironment } from "@/config/environment";
import { invariant_ensIndexerPublicConfigVersionInfo } from "@/config/validations";
+import { fetchENSIndexerConfig } from "@/lib/fetch-ensindexer-config";
+import logger from "@/lib/logger";
const EnsApiConfigSchema = z
.object({
@@ -42,12 +46,11 @@ export type EnsApiConfig = z.infer;
export async function buildConfigFromEnvironment(env: EnsApiEnvironment): Promise {
try {
const ensIndexerUrl = EnsIndexerUrlSchema.parse(env.ENSINDEXER_URL);
- const client = new ENSNodeClient({ url: ensIndexerUrl });
- const ensIndexerPublicConfig = await pRetry(() => client.config(), {
+ const ensIndexerPublicConfig = await pRetry(() => fetchENSIndexerConfig(ensIndexerUrl), {
retries: 3,
onFailedAttempt: ({ error, attemptNumber, retriesLeft }) => {
- console.log(
+ logger.info(
`ENSIndexer Config fetch attempt ${attemptNumber} failed (${error.message}). ${retriesLeft} retries left.`,
);
},
@@ -67,13 +70,29 @@ export async function buildConfigFromEnvironment(env: EnsApiEnvironment): Promis
});
} catch (error) {
if (error instanceof ZodError) {
- throw new Error(`Failed to parse environment configuration: \n${prettifyError(error)}\n`);
+ logger.error(`Failed to parse environment configuration: \n${prettifyError(error)}\n`);
+ process.exit(1);
}
if (error instanceof Error) {
- error.message = `Failed to build EnsApiConfig: ${error.message}`;
+ logger.error(error, `Failed to build EnsApiConfig`);
+ process.exit(1);
}
- throw error;
+ logger.error(`Unknown Error`);
+ process.exit(1);
}
}
+
+/**
+ * Builds the ENSApi public configuration from an EnsApiConfig object.
+ *
+ * @param config - The validated EnsApiConfig object
+ * @returns A complete ENSApiPublicConfig object
+ */
+export function buildEnsApiPublicConfig(config: EnsApiConfig): ENSApiPublicConfig {
+ return {
+ version: packageJson.version,
+ ensIndexerPublicConfig: config.ensIndexerPublicConfig,
+ };
+}
diff --git a/apps/ensapi/src/config/environment.ts b/apps/ensapi/src/config/environment.ts
index d69cbc624a..a02ac785cc 100644
--- a/apps/ensapi/src/config/environment.ts
+++ b/apps/ensapi/src/config/environment.ts
@@ -1,6 +1,7 @@
import type {
DatabaseEnvironment,
EnsIndexerUrlEnvironment,
+ LogLevelEnvironment,
PortEnvironment,
RpcEnvironment,
} from "@ensnode/ensnode-sdk/internal";
@@ -15,4 +16,5 @@ import type {
export type EnsApiEnvironment = Omit &
EnsIndexerUrlEnvironment &
RpcEnvironment &
- PortEnvironment;
+ PortEnvironment &
+ LogLevelEnvironment;
diff --git a/apps/ensapi/src/handlers/ensnode-api.ts b/apps/ensapi/src/handlers/ensnode-api.ts
index c858675971..2fd9ad89c5 100644
--- a/apps/ensapi/src/handlers/ensnode-api.ts
+++ b/apps/ensapi/src/handlers/ensnode-api.ts
@@ -3,19 +3,21 @@ import config from "@/config";
import {
IndexingStatusResponseCodes,
type IndexingStatusResponseError,
- serializeENSIndexerPublicConfig,
+ serializeENSApiPublicConfig,
serializeIndexingStatusResponse,
} from "@ensnode/ensnode-sdk";
+import { buildEnsApiPublicConfig } from "@/config/config.schema";
import { factory } from "@/lib/hono-factory";
import resolutionApi from "./resolution-api";
const app = factory.createApp();
-// include ENSIndexer Public Config endpoint
+// include ENSApi Public Config endpoint
app.get("/config", async (c) => {
- return c.json(serializeENSIndexerPublicConfig(config.ensIndexerPublicConfig));
+ const ensApiPublicConfig = buildEnsApiPublicConfig(config);
+ return c.json(serializeENSApiPublicConfig(ensApiPublicConfig));
});
// include ENSIndexer Indexing Status endpoint
diff --git a/apps/ensapi/src/handlers/resolution-api.ts b/apps/ensapi/src/handlers/resolution-api.ts
index 68cad3ffe6..45cc15cde1 100644
--- a/apps/ensapi/src/handlers/resolution-api.ts
+++ b/apps/ensapi/src/handlers/resolution-api.ts
@@ -6,7 +6,6 @@ import type {
ResolveRecordsResponse,
} from "@ensnode/ensnode-sdk";
-import { errorResponse } from "@/lib/handlers/error-response";
import { params } from "@/lib/handlers/params.schema";
import { validate } from "@/lib/handlers/validate";
import { factory } from "@/lib/hono-factory";
@@ -51,24 +50,19 @@ app.get(
const { selection, trace: showTrace, accelerate } = c.req.valid("query");
const canAccelerate = c.var.canAccelerate;
- try {
- const { result, trace } = await captureTrace(() =>
- resolveForward(name, selection, { accelerate, canAccelerate }),
- );
+ const { result, trace } = await captureTrace(() =>
+ resolveForward(name, selection, { accelerate, canAccelerate }),
+ );
- const response = {
- records: result,
+ const response = {
+ records: result,
- accelerationRequested: accelerate,
- accelerationAttempted: accelerate && canAccelerate,
- ...(showTrace && { trace }),
- } satisfies ResolveRecordsResponse;
+ accelerationRequested: accelerate,
+ accelerationAttempted: accelerate && canAccelerate,
+ ...(showTrace && { trace }),
+ } satisfies ResolveRecordsResponse;
- return c.json(response);
- } catch (error) {
- console.error(error);
- return errorResponse(c, error);
- }
+ return c.json(response);
},
);
@@ -99,24 +93,19 @@ app.get(
const { trace: showTrace, accelerate } = c.req.valid("query");
const canAccelerate = c.var.canAccelerate;
- try {
- const { result, trace } = await captureTrace(() =>
- resolveReverse(address, chainId, { accelerate, canAccelerate }),
- );
+ const { result, trace } = await captureTrace(() =>
+ resolveReverse(address, chainId, { accelerate, canAccelerate }),
+ );
- const response = {
- name: result,
+ const response = {
+ name: result,
- accelerationRequested: accelerate,
- accelerationAttempted: accelerate && canAccelerate,
- ...(showTrace && { trace }),
- } satisfies ResolvePrimaryNameResponse;
+ accelerationRequested: accelerate,
+ accelerationAttempted: accelerate && canAccelerate,
+ ...(showTrace && { trace }),
+ } satisfies ResolvePrimaryNameResponse;
- return c.json(response);
- } catch (error) {
- console.error(error);
- return errorResponse(c, error);
- }
+ return c.json(response);
},
);
@@ -145,24 +134,19 @@ app.get(
const { chainIds, trace: showTrace, accelerate } = c.req.valid("query");
const canAccelerate = c.var.canAccelerate;
- try {
- const { result, trace } = await captureTrace(() =>
- resolvePrimaryNames(address, chainIds, { accelerate, canAccelerate }),
- );
+ const { result, trace } = await captureTrace(() =>
+ resolvePrimaryNames(address, chainIds, { accelerate, canAccelerate }),
+ );
- const response = {
- names: result,
+ const response = {
+ names: result,
- accelerationRequested: accelerate,
- accelerationAttempted: accelerate && canAccelerate,
- ...(showTrace && { trace }),
- } satisfies ResolvePrimaryNamesResponse;
+ accelerationRequested: accelerate,
+ accelerationAttempted: accelerate && canAccelerate,
+ ...(showTrace && { trace }),
+ } satisfies ResolvePrimaryNamesResponse;
- return c.json(response);
- } catch (error) {
- console.error(error);
- return errorResponse(c, error);
- }
+ return c.json(response);
},
);
diff --git a/apps/ensapi/src/index.ts b/apps/ensapi/src/index.ts
index c0a547d763..07f1e4e330 100644
--- a/apps/ensapi/src/index.ts
+++ b/apps/ensapi/src/index.ts
@@ -10,6 +10,7 @@ import { prettyPrintJson } from "@ensnode/ensnode-sdk/internal";
import { redactEnsApiConfig } from "@/config/redact";
import { errorResponse } from "@/lib/handlers/error-response";
import { factory } from "@/lib/hono-factory";
+import logger from "@/lib/logger";
import { sdk } from "@/lib/tracing/instrumentation";
import { canAccelerateMiddleware } from "@/middleware/can-accelerate.middleware";
import { indexingStatusMiddleware } from "@/middleware/indexing-status.middleware";
@@ -51,7 +52,7 @@ app.get("/health", async (c) => {
// log hono errors to console
app.onError((error, ctx) => {
- console.error(error);
+ logger.error(error);
return errorResponse(ctx, "Internal Server Error");
});
@@ -65,8 +66,9 @@ const server = serve(
port: config.port,
},
async (info) => {
- console.log(`ENSApi listening on port ${info.port} with config:`);
- console.log(prettyPrintJson(redactEnsApiConfig(config)));
+ logger.info(
+ `ENSApi listening on port ${info.port} with config:\n${prettyPrintJson(redactEnsApiConfig(config))}`,
+ );
// self-healthcheck to connect to ENSIndexer & warm Indexing Status / Can Accelerate cache
await app.request("/health");
@@ -90,7 +92,7 @@ const gracefulShutdown = async () => {
process.exit(0);
} catch (error) {
- console.error(error);
+ logger.error(error);
process.exit(1);
}
};
@@ -100,6 +102,6 @@ process.on("SIGINT", gracefulShutdown);
process.on("SIGTERM", gracefulShutdown);
process.on("uncaughtException", async (error) => {
- console.error(`Fatal Error:`, error);
+ logger.error(error, "uncaughtException");
await gracefulShutdown();
});
diff --git a/apps/ensapi/src/lib/fetch-ensindexer-config.ts b/apps/ensapi/src/lib/fetch-ensindexer-config.ts
new file mode 100644
index 0000000000..0cd11beb8b
--- /dev/null
+++ b/apps/ensapi/src/lib/fetch-ensindexer-config.ts
@@ -0,0 +1,17 @@
+import {
+ deserializeENSIndexerPublicConfig,
+ deserializeErrorResponse,
+ type SerializedENSIndexerPublicConfig,
+} from "@ensnode/ensnode-sdk";
+
+export async function fetchENSIndexerConfig(url: URL) {
+ const response = await fetch(new URL(`/api/config`, url));
+ const responseData = await response.json();
+
+ if (!response.ok) {
+ const errorResponse = deserializeErrorResponse(responseData);
+ throw new Error(`Fetching ENSNode Config Failed: ${errorResponse.message}`);
+ }
+
+ return deserializeENSIndexerPublicConfig(responseData as SerializedENSIndexerPublicConfig);
+}
diff --git a/apps/ensapi/src/lib/logger.ts b/apps/ensapi/src/lib/logger.ts
new file mode 100644
index 0000000000..50c2642a63
--- /dev/null
+++ b/apps/ensapi/src/lib/logger.ts
@@ -0,0 +1,19 @@
+import pino from "pino";
+
+import { getLogLevelFromEnv, type LogLevel } from "@ensnode/ensnode-sdk/internal";
+
+const DEFAULT_LOG_LEVEL: LogLevel = "info";
+
+export default pino({
+ level: getLogLevelFromEnv(process.env, DEFAULT_LOG_LEVEL),
+ transport:
+ process.env.NODE_ENV === "production"
+ ? undefined
+ : {
+ target: "pino-pretty",
+ options: {
+ colorize: true,
+ ignore: "pid,hostname",
+ },
+ },
+});
diff --git a/apps/ensapi/src/lib/resolution/forward-resolution.ts b/apps/ensapi/src/lib/resolution/forward-resolution.ts
index 18fad49796..afd9742b20 100644
--- a/apps/ensapi/src/lib/resolution/forward-resolution.ts
+++ b/apps/ensapi/src/lib/resolution/forward-resolution.ts
@@ -19,6 +19,7 @@ import {
TraceableENSProtocol,
} from "@ensnode/ensnode-sdk";
+import logger from "@/lib/logger";
import { ENS_ROOT_REGISTRY } from "@/lib/protocol-acceleration/ens-root-registry";
import { findResolver } from "@/lib/protocol-acceleration/find-resolver";
import { getENSIP19ReverseNameRecordFromIndex } from "@/lib/protocol-acceleration/get-primary-name-from-index";
@@ -231,7 +232,7 @@ async function _resolveForward(
// the selection should just be `{ name: true }`, but technically not prohibited to
// select more records than just 'name', so just warn if that happens.
if (selection.addresses !== undefined || selection.texts !== undefined) {
- console.warn(
+ logger.warn(
`Sanity Check(ENSIP-19 Reverse Resolvers Protocol Acceleration): expected a selection of exactly '{ name: true }' but received ${JSON.stringify(selection)}.`,
);
}
diff --git a/apps/ensapi/src/middleware/can-accelerate.middleware.ts b/apps/ensapi/src/middleware/can-accelerate.middleware.ts
index 8ba1719b16..993cfdd4b9 100644
--- a/apps/ensapi/src/middleware/can-accelerate.middleware.ts
+++ b/apps/ensapi/src/middleware/can-accelerate.middleware.ts
@@ -13,6 +13,7 @@ import {
} from "@ensnode/ensnode-sdk";
import { factory } from "@/lib/hono-factory";
+import logger from "@/lib/logger";
export type CanAccelerateVariables = { canAccelerate: boolean };
@@ -62,7 +63,7 @@ export const canAccelerateMiddleware = factory.createMiddleware(async (c, next)
// log one warning to the console if !hasProtocolAccelerationPlugin
if (!didWarnNoProtocolAccelerationPlugin && !hasProtocolAccelerationPlugin) {
- console.warn(
+ logger.warn(
`ENSApi is connected to an ENSIndexer that does NOT include the ${PluginName.ProtocolAcceleration} plugin: ENSApi will NOT be able to accelerate Resolution API requests, even if ?accelerate=true. Resolution requests will abide by the full Forward/Reverse Resolution specification, including RPC calls and CCIP-Read requests to external CCIP-Read Gateways.`,
);
@@ -81,7 +82,7 @@ export const canAccelerateMiddleware = factory.createMiddleware(async (c, next)
(!didInitialIndexingStatus && indexingStatusOk) || // first time
(didInitialIndexingStatus && !prevIndexingStatusOk && indexingStatusOk) // future change in status
) {
- console.log(`ENSIndexer Indexing Status: AVAILABLE`);
+ logger.info(`ENSIndexer Indexing Status: AVAILABLE`);
}
// log notice with reason when Indexing Status is unavilable
@@ -90,11 +91,11 @@ export const canAccelerateMiddleware = factory.createMiddleware(async (c, next)
(didInitialIndexingStatus && prevIndexingStatusOk && !indexingStatusOk) // future change in status
) {
if (c.var.indexingStatus.isRejected) {
- console.warn(
+ logger.warn(
`ENSIndexer Indexing Status: UNAVAILABLE. ENSApi was unable to fetch the current ENSIndexer Indexing Status: ${c.var.indexingStatus.reason}`,
);
} else if (c.var.indexingStatus.value.responseCode === IndexingStatusResponseCodes.Error) {
- console.warn(
+ logger.warn(
`ENSIndexer Indexing Status: UNAVAILABLE. ENSIndexer is reporting an Indexing Status Error.`,
);
}
@@ -126,7 +127,7 @@ export const canAccelerateMiddleware = factory.createMiddleware(async (c, next)
(!didInitialRealtime && isWithinMaxRealtime) || // first time
(didInitialRealtime && !prevIsWithinMaxRealtime && isWithinMaxRealtime) // future change in status
) {
- console.log(`ENSIndexer is realtime, Protocol Acceleration is now ENABLED.`);
+ logger.info(`ENSIndexer is realtime, Protocol Acceleration is now ENABLED.`);
}
// log notice when ENSIndexer transitions out of realtime
@@ -134,7 +135,7 @@ export const canAccelerateMiddleware = factory.createMiddleware(async (c, next)
(!didInitialRealtime && !isWithinMaxRealtime) || // first time
(didInitialRealtime && prevIsWithinMaxRealtime && !isWithinMaxRealtime) // future change in status
) {
- console.warn(
+ logger.warn(
`ENSIndexer is NOT realtime (Worst Case Lag: ${c.var.indexingStatus.value.realtimeProjection.worstCaseDistance} seconds > ${MAX_REALTIME_DISTANCE_TO_ACCELERATE} seconds), Protocol Acceleration is currently DISABLED.`,
);
}
diff --git a/apps/ensrainbow/package.json b/apps/ensrainbow/package.json
index 6e0b031662..01ad96eb95 100644
--- a/apps/ensrainbow/package.json
+++ b/apps/ensrainbow/package.json
@@ -35,7 +35,6 @@
"classic-level": "^1.4.1",
"hono": "catalog:",
"pino": "^10.1.0",
- "pino-pretty": "^13.1.2",
"progress": "^2.0.3",
"protobufjs": "^7.4.0",
"viem": "catalog:",
@@ -46,6 +45,7 @@
"@types/node": "^20.17.14",
"@types/progress": "^2.0.7",
"@types/yargs": "^17.0.32",
+ "pino-pretty": "^13.1.2",
"tsx": "^4.19.3",
"typescript": "^5.3.3",
"vitest": "catalog:"
diff --git a/apps/ensrainbow/src/commands/convert-command.ts b/apps/ensrainbow/src/commands/convert-command.ts
index dde51af7fa..e48258e6a5 100644
--- a/apps/ensrainbow/src/commands/convert-command.ts
+++ b/apps/ensrainbow/src/commands/convert-command.ts
@@ -50,6 +50,10 @@ function setupProgressBar(): ProgressBar {
incomplete: " ",
width: 40,
total: 150000000, // estimated
+ stream:
+ logger.level === "silent" || logger.level === "fatal"
+ ? createWriteStream("/dev/null")
+ : undefined,
},
);
}
diff --git a/apps/ensrainbow/src/commands/ingest-protobuf-command.ts b/apps/ensrainbow/src/commands/ingest-protobuf-command.ts
index 2f3b07277f..e11e69d3f7 100644
--- a/apps/ensrainbow/src/commands/ingest-protobuf-command.ts
+++ b/apps/ensrainbow/src/commands/ingest-protobuf-command.ts
@@ -1,4 +1,4 @@
-import { createReadStream } from "node:fs";
+import { createReadStream, createWriteStream } from "node:fs";
import ProgressBar from "progress";
import protobuf from "protobufjs";
@@ -105,6 +105,10 @@ export async function ingestProtobufCommand(options: IngestProtobufCommandOption
incomplete: " ",
width: 40,
total: 1000000000, // Placeholder total
+ stream:
+ logger.level === "silent" || logger.level === "fatal"
+ ? createWriteStream("/dev/null")
+ : undefined,
},
);
diff --git a/apps/ensrainbow/src/utils/logger.test.ts b/apps/ensrainbow/src/utils/logger.test.ts
deleted file mode 100644
index 9bceb266fa..0000000000
--- a/apps/ensrainbow/src/utils/logger.test.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { afterEach, describe, expect, it, vi } from "vitest";
-
-import {
- createLogger,
- DEFAULT_LOG_LEVEL,
- getEnvLogLevel,
- parseLogLevel,
- VALID_LOG_LEVELS,
-} from "./logger";
-
-describe("logger", () => {
- describe("parseLogLevel", () => {
- it("should accept valid log levels", () => {
- VALID_LOG_LEVELS.forEach((level) => {
- expect(parseLogLevel(level)).toBe(level);
- });
- });
-
- it("should handle case-insensitive input", () => {
- expect(parseLogLevel("INFO")).toBe("info");
- expect(parseLogLevel("Debug")).toBe("debug");
- expect(parseLogLevel("ERROR")).toBe("error");
- });
-
- it("should throw error for invalid log level", () => {
- expect(() => parseLogLevel("invalid")).toThrow(
- 'Invalid log level "invalid". Valid levels are: fatal, error, warn, info, debug, trace, silent',
- );
- });
- });
-
- describe("getEnvLogLevel", () => {
- afterEach(() => {
- vi.unstubAllEnvs();
- });
-
- it("should return DEFAULT_LOG_LEVEL when LOG_LEVEL is not set", () => {
- vi.stubEnv("LOG_LEVEL", undefined);
- expect(getEnvLogLevel()).toBe(DEFAULT_LOG_LEVEL);
- });
-
- it("should return valid log level from environment", () => {
- vi.stubEnv("LOG_LEVEL", "debug");
- expect(getEnvLogLevel()).toBe("debug");
- });
-
- it("should error when invalid log level in environment", () => {
- vi.stubEnv("LOG_LEVEL", "invalid");
- expect(() => getEnvLogLevel()).toThrow(
- 'Environment variable error: (LOG_LEVEL): Invalid log level "invalid". Valid levels are: fatal, error, warn, info, debug, trace, silent.',
- );
- });
- });
-
- describe("createLogger", () => {
- it("should create logger with default level when no level provided", () => {
- const logger = createLogger();
- expect(logger.level).toBe(DEFAULT_LOG_LEVEL);
- });
-
- it("should create logger with specified level", () => {
- const logger = createLogger("debug");
- expect(logger.level).toBe("debug");
- });
- });
-});
diff --git a/apps/ensrainbow/src/utils/logger.ts b/apps/ensrainbow/src/utils/logger.ts
index 21e7a0f17e..b7e6f3d1a4 100644
--- a/apps/ensrainbow/src/utils/logger.ts
+++ b/apps/ensrainbow/src/utils/logger.ts
@@ -1,72 +1,20 @@
-import pino, { type LevelWithSilent } from "pino";
+import pino from "pino";
-import { getErrorMessage } from "@/utils/error-utils";
+import { getLogLevelFromEnv, type LogLevel } from "@ensnode/ensnode-sdk/internal";
-export type LogLevel = LevelWithSilent;
+const DEFAULT_LOG_LEVEL: LogLevel = "info";
-export const DEFAULT_LOG_LEVEL: LogLevel = "info";
-
-// Creating our own definition of the log levels recognized by Pino
-// to provide a better user experience with clear error messages when invalid log levels are
-// parsed.
-export const VALID_LOG_LEVELS: LogLevel[] = [
- "fatal",
- "error",
- "warn",
- "info",
- "debug",
- "trace",
- "silent",
-];
-
-export function parseLogLevel(maybeLevel: string): LogLevel {
- const normalizedLevel = maybeLevel.toLowerCase();
- if (VALID_LOG_LEVELS.includes(normalizedLevel as LogLevel)) {
- return normalizedLevel as LogLevel;
- }
- throw new Error(
- `Invalid log level "${maybeLevel}". Valid levels are: ${VALID_LOG_LEVELS.join(", ")}.`,
- );
-}
-
-export function getEnvLogLevel(): LogLevel {
- const envLogLevel = process.env.LOG_LEVEL;
- if (!envLogLevel) {
- return DEFAULT_LOG_LEVEL;
- }
-
- try {
- return parseLogLevel(envLogLevel);
- } catch (error: unknown) {
- const errorMessage = `Environment variable error: (LOG_LEVEL): ${getErrorMessage(error)}`;
- // Log error to console since we can't use logger yet
- console.error(errorMessage);
- throw new Error(errorMessage);
- }
-}
-
-export function createLogger(level: LogLevel = DEFAULT_LOG_LEVEL): pino.Logger {
- const isProduction = process.env.NODE_ENV === "production";
-
- return pino({
- level,
- ...(isProduction
- ? {} // In production, use default pino output format
+// Create and export the global logger instance
+export const logger = pino({
+ level: getLogLevelFromEnv(process.env, DEFAULT_LOG_LEVEL),
+ transport:
+ process.env.NODE_ENV === "production"
+ ? undefined
: {
- transport: {
- target: "pino-pretty",
- options: {
- colorize: true,
- translateTime: "HH:MM:ss",
- ignore: "pid,hostname",
- },
+ target: "pino-pretty",
+ options: {
+ colorize: true,
+ ignore: "pid,hostname",
},
- }),
- });
-}
-
-// Create and export the global logger instance
-export const logger = createLogger(getEnvLogLevel());
-
-// Re-export pino types for convenience
-export type { Logger } from "pino";
+ },
+});
diff --git a/apps/ensrainbow/tsconfig.json b/apps/ensrainbow/tsconfig.json
index f16245724b..3a25d8b4ba 100644
--- a/apps/ensrainbow/tsconfig.json
+++ b/apps/ensrainbow/tsconfig.json
@@ -1,6 +1,8 @@
{
"extends": "@ensnode/shared-configs/tsconfig.lib.json",
"compilerOptions": {
+ "target": "esnext",
+ "typeRoots": ["./types"],
"paths": {
"@/*": ["./src/*"]
}
diff --git a/apps/ensrainbow/types/env.d.ts b/apps/ensrainbow/types/env.d.ts
new file mode 100644
index 0000000000..dd956c6006
--- /dev/null
+++ b/apps/ensrainbow/types/env.d.ts
@@ -0,0 +1,7 @@
+import type { LogLevelEnvironment } from "@ensnode/ensnode-sdk/internal";
+
+declare global {
+ namespace NodeJS {
+ interface ProcessEnv extends LogLevelEnvironment {}
+ }
+}
diff --git a/packages/ensnode-react/src/context.ts b/packages/ensnode-react/src/context.ts
index db97bad131..42474d2430 100644
--- a/packages/ensnode-react/src/context.ts
+++ b/packages/ensnode-react/src/context.ts
@@ -1,11 +1,11 @@
import { createContext } from "react";
-import type { ENSNodeConfig } from "./types";
+import type { ENSNodeSDKConfig } from "./types";
/**
* React context for ENSNode configuration
*/
-export const ENSNodeContext = createContext(undefined);
+export const ENSNodeContext = createContext(undefined);
/**
* Display name for debugging
diff --git a/packages/ensnode-react/src/hooks/index.ts b/packages/ensnode-react/src/hooks/index.ts
index 49b99706c6..0d59970704 100644
--- a/packages/ensnode-react/src/hooks/index.ts
+++ b/packages/ensnode-react/src/hooks/index.ts
@@ -1,5 +1,5 @@
-export * from "./useENSIndexerConfig";
export * from "./useENSNodeConfig";
+export * from "./useENSNodeSDKConfig";
export * from "./useIndexingStatus";
export * from "./usePrimaryName";
export * from "./usePrimaryNames";
diff --git a/packages/ensnode-react/src/hooks/useENSIndexerConfig.ts b/packages/ensnode-react/src/hooks/useENSIndexerConfig.ts
deleted file mode 100644
index b44782447e..0000000000
--- a/packages/ensnode-react/src/hooks/useENSIndexerConfig.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { useQuery } from "@tanstack/react-query";
-
-import type { ConfigResponse } from "@ensnode/ensnode-sdk";
-
-import type { ConfigParameter, QueryParameter } from "../types";
-import { ASSUME_IMMUTABLE_QUERY, createENSIndexerConfigQueryOptions } from "../utils/query";
-import { useENSNodeConfig } from "./useENSNodeConfig";
-
-type UseENSIndexerConfigParameters = QueryParameter;
-
-export function useENSIndexerConfig(
- parameters: ConfigParameter & UseENSIndexerConfigParameters = {},
-) {
- const { config, query = {} } = parameters;
- const _config = useENSNodeConfig(config);
-
- const queryOptions = createENSIndexerConfigQueryOptions(_config);
-
- const options = {
- ...queryOptions,
- ...ASSUME_IMMUTABLE_QUERY,
- ...query,
- enabled: query.enabled ?? queryOptions.enabled,
- };
-
- return useQuery(options);
-}
diff --git a/packages/ensnode-react/src/hooks/useENSNodeConfig.ts b/packages/ensnode-react/src/hooks/useENSNodeConfig.ts
index e1d3c297fa..c246d61afd 100644
--- a/packages/ensnode-react/src/hooks/useENSNodeConfig.ts
+++ b/packages/ensnode-react/src/hooks/useENSNodeConfig.ts
@@ -1,30 +1,27 @@
-"use client";
+import { useQuery } from "@tanstack/react-query";
-import { useContext } from "react";
+import type { ConfigResponse } from "@ensnode/ensnode-sdk";
-import { ENSNodeContext } from "../context";
-import type { ENSNodeConfig } from "../types";
+import type { QueryParameter, WithSDKConfigParameter } from "../types";
+import { ASSUME_IMMUTABLE_QUERY, createConfigQueryOptions } from "../utils/query";
+import { useENSNodeSDKConfig } from "./useENSNodeSDKConfig";
-/**
- * Hook to access the ENSNode configuration 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 useENSNodeConfig(
- config: TConfig | undefined,
-): TConfig {
- const contextConfig = useContext(ENSNodeContext);
+type UseENSNodeConfigParameters = QueryParameter;
- // Use provided config or fall back to context
- const resolvedConfig = config ?? contextConfig;
+export function useENSNodeConfig(
+ parameters: WithSDKConfigParameter & UseENSNodeConfigParameters = {},
+) {
+ const { config, query = {} } = parameters;
+ const _config = useENSNodeSDKConfig(config);
- if (!resolvedConfig) {
- throw new Error(
- "useENSNodeConfig must be used within an ENSNodeProvider or you must pass a config parameter",
- );
- }
+ const queryOptions = createConfigQueryOptions(_config);
- return resolvedConfig as TConfig;
+ 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
new file mode 100644
index 0000000000..2f7b1016aa
--- /dev/null
+++ b/packages/ensnode-react/src/hooks/useENSNodeSDKConfig.ts
@@ -0,0 +1,30 @@
+"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/useIndexingStatus.ts b/packages/ensnode-react/src/hooks/useIndexingStatus.ts
index d587a434ad..c56f3d6928 100644
--- a/packages/ensnode-react/src/hooks/useIndexingStatus.ts
+++ b/packages/ensnode-react/src/hooks/useIndexingStatus.ts
@@ -2,17 +2,19 @@ import { useQuery } from "@tanstack/react-query";
import type { IndexingStatusRequest, IndexingStatusResponse } from "@ensnode/ensnode-sdk";
-import type { ConfigParameter, QueryParameter } from "../types";
+import type { QueryParameter, WithSDKConfigParameter } from "../types";
import { createIndexingStatusQueryOptions } from "../utils/query";
-import { useENSNodeConfig } from "./useENSNodeConfig";
+import { useENSNodeSDKConfig } from "./useENSNodeSDKConfig";
interface UseIndexingStatusParameters
extends IndexingStatusRequest,
QueryParameter {}
-export function useIndexingStatus(parameters: ConfigParameter & UseIndexingStatusParameters = {}) {
+export function useIndexingStatus(
+ parameters: WithSDKConfigParameter & UseIndexingStatusParameters = {},
+) {
const { config, query = {} } = parameters;
- const _config = useENSNodeConfig(config);
+ const _config = useENSNodeSDKConfig(config);
const queryOptions = createIndexingStatusQueryOptions(_config);
diff --git a/packages/ensnode-react/src/hooks/usePrimaryName.ts b/packages/ensnode-react/src/hooks/usePrimaryName.ts
index b823c357fd..2bd7c9ffba 100644
--- a/packages/ensnode-react/src/hooks/usePrimaryName.ts
+++ b/packages/ensnode-react/src/hooks/usePrimaryName.ts
@@ -2,9 +2,9 @@
import { useQuery } from "@tanstack/react-query";
-import type { ConfigParameter, UsePrimaryNameParameters } from "../types";
+import type { UsePrimaryNameParameters, WithSDKConfigParameter } from "../types";
import { createPrimaryNameQueryOptions } from "../utils/query";
-import { useENSNodeConfig } from "./useENSNodeConfig";
+import { useENSNodeSDKConfig } from "./useENSNodeSDKConfig";
/**
* Resolves the primary name of a specified address (Reverse Resolution).
@@ -37,9 +37,9 @@ import { useENSNodeConfig } from "./useENSNodeConfig";
* }
* ```
*/
-export function usePrimaryName(parameters: UsePrimaryNameParameters & ConfigParameter) {
+export function usePrimaryName(parameters: UsePrimaryNameParameters & WithSDKConfigParameter) {
const { config, query = {}, address, ...args } = parameters;
- const _config = useENSNodeConfig(config);
+ const _config = useENSNodeSDKConfig(config);
const canEnable = address !== null;
diff --git a/packages/ensnode-react/src/hooks/usePrimaryNames.ts b/packages/ensnode-react/src/hooks/usePrimaryNames.ts
index 0d02d53c5f..4c3f9026e3 100644
--- a/packages/ensnode-react/src/hooks/usePrimaryNames.ts
+++ b/packages/ensnode-react/src/hooks/usePrimaryNames.ts
@@ -2,9 +2,9 @@
import { useQuery } from "@tanstack/react-query";
-import type { ConfigParameter, UsePrimaryNamesParameters } from "../types";
+import type { UsePrimaryNamesParameters, WithSDKConfigParameter } from "../types";
import { createPrimaryNamesQueryOptions } from "../utils/query";
-import { useENSNodeConfig } from "./useENSNodeConfig";
+import { useENSNodeSDKConfig } from "./useENSNodeSDKConfig";
/**
* Resolves the primary names of a specified address across multiple chains.
@@ -40,9 +40,9 @@ import { useENSNodeConfig } from "./useENSNodeConfig";
* }
* ```
*/
-export function usePrimaryNames(parameters: UsePrimaryNamesParameters & ConfigParameter) {
+export function usePrimaryNames(parameters: UsePrimaryNamesParameters & WithSDKConfigParameter) {
const { config, query = {}, address, ...args } = parameters;
- const _config = useENSNodeConfig(config);
+ const _config = useENSNodeSDKConfig(config);
const canEnable = address !== null;
diff --git a/packages/ensnode-react/src/hooks/useRecords.ts b/packages/ensnode-react/src/hooks/useRecords.ts
index e3ef4c0ea8..ee7d9ed9f9 100644
--- a/packages/ensnode-react/src/hooks/useRecords.ts
+++ b/packages/ensnode-react/src/hooks/useRecords.ts
@@ -4,9 +4,9 @@ import { useQuery } from "@tanstack/react-query";
import type { ResolverRecordsSelection } from "@ensnode/ensnode-sdk";
-import type { ConfigParameter, UseRecordsParameters } from "../types";
+import type { UseRecordsParameters, WithSDKConfigParameter } from "../types";
import { createRecordsQueryOptions } from "../utils/query";
-import { useENSNodeConfig } from "./useENSNodeConfig";
+import { useENSNodeSDKConfig } from "./useENSNodeSDKConfig";
/**
* Resolves records for an ENS name (Forward Resolution).
@@ -51,10 +51,10 @@ import { useENSNodeConfig } from "./useENSNodeConfig";
* ```
*/
export function useRecords(
- parameters: UseRecordsParameters & ConfigParameter,
+ parameters: UseRecordsParameters & WithSDKConfigParameter,
) {
const { config, query = {}, name, ...args } = parameters;
- const _config = useENSNodeConfig(config);
+ const _config = useENSNodeSDKConfig(config);
const canEnable = name !== null;
diff --git a/packages/ensnode-react/src/provider.tsx b/packages/ensnode-react/src/provider.tsx
index 54f27789cc..457db1319e 100644
--- a/packages/ensnode-react/src/provider.tsx
+++ b/packages/ensnode-react/src/provider.tsx
@@ -7,11 +7,11 @@ import { createElement, useMemo } from "react";
import { ENSNodeClient } from "@ensnode/ensnode-sdk";
import { ENSNodeContext } from "./context";
-import type { ENSNodeConfig } from "./types";
+import type { ENSNodeSDKConfig } from "./types";
export interface ENSNodeProviderProps {
/** ENSNode configuration */
- config: ENSNodeConfig;
+ config: ENSNodeSDKConfig;
/**
* Optional QueryClient instance. If provided, you must wrap your app with QueryClientProvider yourself.
@@ -31,7 +31,7 @@ function ENSNodeInternalProvider({
config,
}: {
children?: React.ReactNode;
- config: ENSNodeConfig;
+ config: ENSNodeSDKConfig;
}) {
// Memoize the config to prevent unnecessary re-renders
const memoizedConfig = useMemo(() => config, [config]);
@@ -94,7 +94,7 @@ export function ENSNodeProvider(parameters: React.PropsWithChildren {
/**
* Configuration parameter for hooks that need access to config
*/
-export interface ConfigParameter {
+export interface WithSDKConfigParameter {
config?: TConfig | undefined;
}
diff --git a/packages/ensnode-react/src/utils/query.ts b/packages/ensnode-react/src/utils/query.ts
index 0762e467eb..6e77b25df6 100644
--- a/packages/ensnode-react/src/utils/query.ts
+++ b/packages/ensnode-react/src/utils/query.ts
@@ -10,7 +10,7 @@ import {
type ResolverRecordsSelection,
} from "@ensnode/ensnode-sdk";
-import type { ENSNodeConfig } from "../types";
+import type { ENSNodeSDKConfig } 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.
@@ -61,7 +61,7 @@ export const queryKeys = {
* Creates query options for Records Resolution
*/
export function createRecordsQueryOptions(
- config: ENSNodeConfig,
+ config: ENSNodeSDKConfig,
args: ResolveRecordsRequest,
) {
return {
@@ -78,7 +78,7 @@ export function createRecordsQueryOptions {
// arrange
const requestUrl = new URL(`/api/config`, DEFAULT_ENSNODE_API_URL);
const serializedMockedResponse = EXAMPLE_CONFIG_RESPONSE;
- const mockedResponse = deserializeENSIndexerPublicConfig(serializedMockedResponse);
+ const mockedResponse = deserializeENSApiPublicConfig(serializedMockedResponse);
const client = new ENSNodeClient();
mockFetch.mockResolvedValueOnce({
diff --git a/packages/ensnode-sdk/src/client.ts b/packages/ensnode-sdk/src/client.ts
index 2286cd2e5c..ac9b6d4037 100644
--- a/packages/ensnode-sdk/src/client.ts
+++ b/packages/ensnode-sdk/src/client.ts
@@ -15,10 +15,7 @@ import type {
ResolveRecordsResponse,
} from "./api/types";
import { ClientError } from "./client-error";
-import {
- deserializeENSIndexerPublicConfig,
- type SerializedENSIndexerPublicConfig,
-} from "./ensindexer";
+import { deserializeENSApiPublicConfig, type SerializedENSApiPublicConfig } from "./ensapi";
import type { ResolverRecordsSelection } from "./resolution";
/**
@@ -289,10 +286,9 @@ export class ENSNodeClient {
const response = await fetch(url);
- let responseData: unknown;
-
// ENSNode API 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 {
@@ -304,7 +300,7 @@ export class ENSNodeClient {
throw new Error(`Fetching ENSNode Config Failed: ${errorResponse.message}`);
}
- return deserializeENSIndexerPublicConfig(responseData as SerializedENSIndexerPublicConfig);
+ return deserializeENSApiPublicConfig(responseData as SerializedENSApiPublicConfig);
}
/**
@@ -321,10 +317,9 @@ export class ENSNodeClient {
const response = await fetch(url);
- let responseData: unknown;
-
// ENSNode API 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 {
@@ -333,9 +328,8 @@ export class ENSNodeClient {
// handle response errors accordingly
if (!response.ok) {
- let errorResponse: ErrorResponse | undefined;
-
// check for a generic errorResponse
+ let errorResponse: ErrorResponse | undefined;
try {
errorResponse = deserializeErrorResponse(responseData);
} catch {
diff --git a/packages/ensnode-sdk/src/ensapi/config/conversions.test.ts b/packages/ensnode-sdk/src/ensapi/config/conversions.test.ts
new file mode 100644
index 0000000000..3fdc2970a2
--- /dev/null
+++ b/packages/ensnode-sdk/src/ensapi/config/conversions.test.ts
@@ -0,0 +1,88 @@
+import { describe, expect, it } from "vitest";
+
+import { ENSNamespaceIds } from "@ensnode/datasources";
+
+import { PluginName } from "../../ensindexer";
+import { deserializeENSApiPublicConfig, serializeENSApiPublicConfig } from ".";
+import type { ENSApiPublicConfig } from "./types";
+
+const MOCK_ENSAPI_PUBLIC_CONFIG = {
+ version: "0.36.0",
+ ensIndexerPublicConfig: {
+ namespace: ENSNamespaceIds.Mainnet,
+ databaseSchemaName: "ensapi",
+ indexedChainIds: new Set([1]),
+ isSubgraphCompatible: false,
+ labelSet: { labelSetId: "subgraph", labelSetVersion: 0 },
+ plugins: [PluginName.Subgraph],
+ versionInfo: {
+ ensDb: "0.36.0",
+ ensIndexer: "0.36.0",
+ ensRainbow: "0.36.0",
+ ensRainbowSchema: 1,
+ ensNormalize: "1.1.1",
+ nodejs: "20.0.0",
+ ponder: "0.5.0",
+ },
+ },
+} satisfies ENSApiPublicConfig;
+
+const MOCK_SERIALIZED_ENSAPI_PUBLIC_CONFIG = serializeENSApiPublicConfig(MOCK_ENSAPI_PUBLIC_CONFIG);
+
+describe("ENSApi Config Serialization/Deserialization", () => {
+ describe("serializeENSApiPublicConfig", () => {
+ it("serializes ENSAPI public config correctly", () => {
+ const result = serializeENSApiPublicConfig(MOCK_ENSAPI_PUBLIC_CONFIG);
+
+ expect(result).toEqual({
+ version: "0.36.0",
+ ensIndexerPublicConfig: {
+ namespace: ENSNamespaceIds.Mainnet,
+ databaseSchemaName: "ensapi",
+ indexedChainIds: [1],
+ isSubgraphCompatible: false,
+ labelSet: { labelSetId: "subgraph", labelSetVersion: 0 },
+ plugins: [PluginName.Subgraph],
+ versionInfo: {
+ ensDb: "0.36.0",
+ ensIndexer: "0.36.0",
+ ensRainbow: "0.36.0",
+ ensRainbowSchema: 1,
+ ensNormalize: "1.1.1",
+ nodejs: "20.0.0",
+ ponder: "0.5.0",
+ },
+ },
+ });
+ });
+ });
+
+ describe("deserializeENSApiPublicConfig", () => {
+ it("deserializes ENSAPI public config correctly", () => {
+ const serialized = serializeENSApiPublicConfig(MOCK_ENSAPI_PUBLIC_CONFIG);
+ const result = deserializeENSApiPublicConfig(serialized);
+
+ expect(result).toEqual(MOCK_ENSAPI_PUBLIC_CONFIG);
+ });
+
+ it("handles validation errors with custom value label", () => {
+ const invalidConfig = {
+ ...MOCK_SERIALIZED_ENSAPI_PUBLIC_CONFIG,
+ version: "", // Invalid: empty string
+ };
+
+ expect(() => deserializeENSApiPublicConfig(invalidConfig, "testConfig")).toThrow(
+ /testConfig.version/,
+ );
+ });
+ });
+
+ describe("round-trip conversion", () => {
+ it("maintains data integrity through serialize -> deserialize cycle", () => {
+ const serialized = serializeENSApiPublicConfig(MOCK_ENSAPI_PUBLIC_CONFIG);
+ const deserialized = deserializeENSApiPublicConfig(serialized);
+
+ expect(deserialized).toStrictEqual(MOCK_ENSAPI_PUBLIC_CONFIG);
+ });
+ });
+});
diff --git a/packages/ensnode-sdk/src/ensapi/config/deserialize.ts b/packages/ensnode-sdk/src/ensapi/config/deserialize.ts
new file mode 100644
index 0000000000..0daa541bd2
--- /dev/null
+++ b/packages/ensnode-sdk/src/ensapi/config/deserialize.ts
@@ -0,0 +1,24 @@
+import { prettifyError, ZodError } from "zod/v4";
+
+import type { SerializedENSApiPublicConfig } from "./serialized-types";
+import type { ENSApiPublicConfig } from "./types";
+import { makeENSApiPublicConfigSchema } from "./zod-schemas";
+
+/**
+ * Deserialize a {@link ENSApiPublicConfig} object.
+ */
+export function deserializeENSApiPublicConfig(
+ maybeConfig: SerializedENSApiPublicConfig,
+ valueLabel?: string,
+): ENSApiPublicConfig {
+ const schema = makeENSApiPublicConfigSchema(valueLabel);
+ try {
+ return schema.parse(maybeConfig);
+ } catch (error) {
+ if (error instanceof ZodError) {
+ throw new Error(`Cannot deserialize ENSApiPublicConfig:\n${prettifyError(error)}\n`);
+ }
+
+ throw error;
+ }
+}
diff --git a/packages/ensnode-sdk/src/ensapi/config/index.ts b/packages/ensnode-sdk/src/ensapi/config/index.ts
new file mode 100644
index 0000000000..bff2897b57
--- /dev/null
+++ b/packages/ensnode-sdk/src/ensapi/config/index.ts
@@ -0,0 +1,5 @@
+export * from "./deserialize";
+export * from "./serialize";
+export * from "./serialized-types";
+export * from "./types";
+export * from "./zod-schemas";
diff --git a/packages/ensnode-sdk/src/ensapi/config/serialize.ts b/packages/ensnode-sdk/src/ensapi/config/serialize.ts
new file mode 100644
index 0000000000..4741e376b5
--- /dev/null
+++ b/packages/ensnode-sdk/src/ensapi/config/serialize.ts
@@ -0,0 +1,17 @@
+import { serializeENSIndexerPublicConfig } from "../../ensindexer";
+import type { SerializedENSApiPublicConfig } from "./serialized-types";
+import type { ENSApiPublicConfig } from "./types";
+
+/**
+ * Serialize a {@link ENSApiPublicConfig} object.
+ */
+export function serializeENSApiPublicConfig(
+ config: ENSApiPublicConfig,
+): SerializedENSApiPublicConfig {
+ const { version, ensIndexerPublicConfig } = config;
+
+ return {
+ version,
+ ensIndexerPublicConfig: serializeENSIndexerPublicConfig(ensIndexerPublicConfig),
+ } satisfies SerializedENSApiPublicConfig;
+}
diff --git a/packages/ensnode-sdk/src/ensapi/config/serialized-types.ts b/packages/ensnode-sdk/src/ensapi/config/serialized-types.ts
new file mode 100644
index 0000000000..f437da884a
--- /dev/null
+++ b/packages/ensnode-sdk/src/ensapi/config/serialized-types.ts
@@ -0,0 +1,13 @@
+import type { SerializedENSIndexerPublicConfig } from "../../ensindexer";
+import type { ENSApiPublicConfig } from "./types";
+
+/**
+ * Serialized representation of {@link ENSApiPublicConfig}
+ */
+export interface SerializedENSApiPublicConfig
+ extends Omit {
+ /**
+ * Serialized representation of {@link ENSApiPublicConfig.ensIndexerPublicConfig}.
+ */
+ ensIndexerPublicConfig: SerializedENSIndexerPublicConfig;
+}
diff --git a/packages/ensnode-sdk/src/ensapi/config/types.ts b/packages/ensnode-sdk/src/ensapi/config/types.ts
new file mode 100644
index 0000000000..5af6bc59ab
--- /dev/null
+++ b/packages/ensnode-sdk/src/ensapi/config/types.ts
@@ -0,0 +1,24 @@
+import type { ENSIndexerPublicConfig } from "../../ensindexer";
+
+/**
+ * Complete public configuration object for ENSApi.
+ *
+ * Contains ENSApi-specific configuration at the top level and
+ * embeds the complete ENSIndexer public configuration.
+ */
+export interface ENSApiPublicConfig {
+ /**
+ * ENSApi service version
+ *
+ * @see https://ghcr.io/namehash/ensnode/ensapi
+ */
+ version: string;
+
+ /**
+ * Complete ENSIndexer public configuration
+ *
+ * Contains all ENSIndexer public configuration including
+ * namespace, plugins, version info, etc.
+ */
+ ensIndexerPublicConfig: ENSIndexerPublicConfig;
+}
diff --git a/packages/ensnode-sdk/src/ensapi/config/zod-schemas.ts b/packages/ensnode-sdk/src/ensapi/config/zod-schemas.ts
new file mode 100644
index 0000000000..f495c995d9
--- /dev/null
+++ b/packages/ensnode-sdk/src/ensapi/config/zod-schemas.ts
@@ -0,0 +1,17 @@
+import { z } from "zod/v4";
+
+import { makeENSIndexerPublicConfigSchema } from "../../ensindexer/config/zod-schemas";
+
+/**
+ * Create a Zod schema for validating a serialized ENSApiPublicConfig.
+ *
+ * @param valueLabel - Optional label for the value being validated (used in error messages)
+ */
+export function makeENSApiPublicConfigSchema(valueLabel?: string) {
+ const label = valueLabel ?? "ENSApiPublicConfig";
+
+ return z.strictObject({
+ version: z.string().min(1, `${label}.version must be a non-empty string`),
+ ensIndexerPublicConfig: makeENSIndexerPublicConfigSchema(`${label}.ensIndexerPublicConfig`),
+ });
+}
diff --git a/packages/ensnode-sdk/src/ensapi/index.ts b/packages/ensnode-sdk/src/ensapi/index.ts
new file mode 100644
index 0000000000..5c62e04f5e
--- /dev/null
+++ b/packages/ensnode-sdk/src/ensapi/index.ts
@@ -0,0 +1 @@
+export * from "./config";
diff --git a/packages/ensnode-sdk/src/index.ts b/packages/ensnode-sdk/src/index.ts
index 32dfbeec60..32332cf0c7 100644
--- a/packages/ensnode-sdk/src/index.ts
+++ b/packages/ensnode-sdk/src/index.ts
@@ -6,6 +6,7 @@ export {
} from "./client";
export * from "./client-error";
export * from "./ens";
+export * from "./ensapi";
export * from "./ensindexer";
export * from "./ensrainbow";
export * from "./resolution";
diff --git a/packages/ensnode-sdk/src/internal.ts b/packages/ensnode-sdk/src/internal.ts
index 31cff5a512..63e0c985b6 100644
--- a/packages/ensnode-sdk/src/internal.ts
+++ b/packages/ensnode-sdk/src/internal.ts
@@ -23,5 +23,6 @@ export * from "./shared/config/types";
export * from "./shared/config/validatons";
export * from "./shared/config/zod-schemas";
export * from "./shared/datasources-with-resolvers";
+export * from "./shared/log-level";
export * from "./shared/protocol-acceleration/interpret-record-values";
export * from "./shared/zod-schemas";
diff --git a/packages/ensnode-sdk/src/resolution/index.ts b/packages/ensnode-sdk/src/resolution/index.ts
index 6e7390288b..9824550cd5 100644
--- a/packages/ensnode-sdk/src/resolution/index.ts
+++ b/packages/ensnode-sdk/src/resolution/index.ts
@@ -1,4 +1,3 @@
-export * from "./default-records-selection";
export * from "./ensip19-chainid";
export * from "./identity";
export * from "./resolver-records-response";
diff --git a/packages/ensnode-sdk/src/shared/config/environments.ts b/packages/ensnode-sdk/src/shared/config/environments.ts
index c4b3796e68..e9bd00e70a 100644
--- a/packages/ensnode-sdk/src/shared/config/environments.ts
+++ b/packages/ensnode-sdk/src/shared/config/environments.ts
@@ -35,3 +35,10 @@ export interface PortEnvironment {
* May contain a comma separated list of one or more URLs.
*/
export type ChainIdSpecificRpcEnvironmentVariable = string;
+
+/**
+ * Environment variables for log level configuration.
+ */
+export type LogLevelEnvironment = {
+ LOG_LEVEL?: string;
+};
diff --git a/packages/ensnode-sdk/src/shared/log-level.test.ts b/packages/ensnode-sdk/src/shared/log-level.test.ts
new file mode 100644
index 0000000000..58fa3516a7
--- /dev/null
+++ b/packages/ensnode-sdk/src/shared/log-level.test.ts
@@ -0,0 +1,27 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+import type { LogLevelEnvironment } from "../internal";
+import { getLogLevelFromEnv } from "./log-level";
+
+describe("logger", () => {
+ describe("getLogLevelFromEnv", () => {
+ afterEach(() => {
+ vi.unstubAllEnvs();
+ });
+
+ it("should return default when LOG_LEVEL is not set", () => {
+ vi.stubEnv("LOG_LEVEL", undefined);
+ expect(getLogLevelFromEnv(process.env as LogLevelEnvironment, "debug")).toBe("debug");
+ });
+
+ it("should return valid log level from environment", () => {
+ vi.stubEnv("LOG_LEVEL", "warn");
+ expect(getLogLevelFromEnv(process.env as LogLevelEnvironment, "warn")).toBe("warn");
+ });
+
+ it("should return default when invalid log level in environment", () => {
+ vi.stubEnv("LOG_LEVEL", "invalid");
+ expect(getLogLevelFromEnv(process.env as LogLevelEnvironment, "debug")).toBe("debug");
+ });
+ });
+});
diff --git a/packages/ensnode-sdk/src/shared/log-level.ts b/packages/ensnode-sdk/src/shared/log-level.ts
new file mode 100644
index 0000000000..e38c51b954
--- /dev/null
+++ b/packages/ensnode-sdk/src/shared/log-level.ts
@@ -0,0 +1,21 @@
+import { z } from "zod/v4";
+
+import type { LogLevelEnvironment } from "../internal";
+
+/**
+ * Set of valid log levels, mirroring pino#LogLevelWithSilent.
+ */
+const LogLevelSchema = z.enum(["fatal", "error", "warn", "info", "debug", "trace", "silent"]);
+
+export type LogLevel = z.infer;
+
+export function getLogLevelFromEnv(env: LogLevelEnvironment, defaultLogLevel: LogLevel): LogLevel {
+ try {
+ return LogLevelSchema.default(defaultLogLevel).parse(env.LOG_LEVEL);
+ } catch {
+ console.warn(
+ `Invalid LOG_LEVEL '${env.LOG_LEVEL}', expected one of '${Object.values(LogLevelSchema.enum).join("' | '")}' defaulting to '${defaultLogLevel}'`,
+ );
+ return defaultLogLevel;
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2ac9d41fc0..3cbce62484 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -327,6 +327,9 @@ importers:
p-retry:
specifier: ^7.1.0
version: 7.1.0
+ pino:
+ specifier: 10.1.0
+ version: 10.1.0
ponder:
specifier: 'catalog:'
version: 0.13.14(@opentelemetry/api@1.9.0(patch_hash=4b2adeefaf7c22f9987d0a125d69cab900719bec7ed7636648bea6947107033a))(@types/node@22.15.3)(bufferutil@4.0.9)(hono@4.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.3)(typescript@5.7.3)(utf-8-validate@5.0.10)(viem@2.23.2(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.25.76))(yaml@2.7.0)(zod@3.25.76)
@@ -346,6 +349,9 @@ importers:
'@types/node':
specifier: 'catalog:'
version: 22.15.3
+ pino-pretty:
+ specifier: ^13.1.2
+ version: 13.1.2
tsx:
specifier: ^4.7.1
version: 4.19.3
@@ -437,9 +443,6 @@ importers:
pino:
specifier: 10.1.0
version: 10.1.0
- pino-pretty:
- specifier: ^13.1.2
- version: 13.1.2
progress:
specifier: ^2.0.3
version: 2.0.3
@@ -465,6 +468,9 @@ importers:
'@types/yargs':
specifier: ^17.0.32
version: 17.0.33
+ pino-pretty:
+ specifier: ^13.1.2
+ version: 13.1.2
tsx:
specifier: ^4.19.3
version: 4.19.3
diff --git a/vitest.config.ts b/vitest.config.ts
index d8873fd84b..8ff5bda545 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -3,6 +3,8 @@ import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
projects: ["apps/*/vitest.config.ts", "packages/*/vitest.config.ts"],
+ // we place LOG_LEVEL here at the root such that running vitest within a specific project continues
+ // to print logs at the default log level
env: {
LOG_LEVEL: "silent",
},