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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brown-readers-talk.md
Original file line number Diff line number Diff line change
@@ -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().
5 changes: 5 additions & 0 deletions .changeset/nine-ducks-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensadmin": minor
---

ENSAdmin now supports ENSApi Version info.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How do you suggest we test this? Appreciate your advice 👍

CleanShot 2025-10-28 at 14 39 39

5 changes: 5 additions & 0 deletions .changeset/shaky-schools-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ensnode/ensnode-sdk": minor
---

BREAKING: client.config() now returns Promise<EnsApiPublicConfig> instead of ENSIndexerPublicConfig.
5 changes: 5 additions & 0 deletions .changeset/sixty-onions-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensadmin": minor
---

ENSAdmin now displays whether ENSNode attempted acceleration for an acceleratable endpoint in the Protocol Inspector.
5 changes: 5 additions & 0 deletions .changeset/young-badgers-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ensnode/ensnode-react": minor
---

BREAKING: `useENSNodeConfig` has been renamed to `useENSNodeSDKConfig`. `useENSIndexerConfig` has been renamed to `useENSNodeConfig`.
128 changes: 49 additions & 79 deletions apps/ensadmin/src/app/inspect/_components/render-requests-output.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -36,47 +36,45 @@ export function RenderRequestsOutput<KEY extends string>({
}) {
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;

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 (
<Card className="w-full">
<CardContent className="h-96">
Expand All @@ -90,20 +88,21 @@ export function RenderRequestsOutput<KEY extends string>({

return (
<>
{/* Response Card */}
<Card className="w-full">
<CardHeader>
<CardTitle>ENSNode Response</CardTitle>
</CardHeader>
<CardContent className="max-h-[30rem] overflow-scroll">
{(() => {
if (someError) {
if (focused.error) {
return (
<CodeBlock className="rounded-lg text-xs">
{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,
Expand All @@ -114,13 +113,15 @@ export function RenderRequestsOutput<KEY extends string>({

return (
<CodeBlock className="rounded-lg text-xs">
{JSON.stringify(result, null, 2)}
{JSON.stringify(focused.data?.[dataKey], null, 2)}
</CodeBlock>
);
})()}
</CardContent>
</Card>
{!someError && (accelerated.data?.trace || unaccelerated.data?.trace) && (

{/* Execution Trace Card */}
{acceleratedSuccess && (
<Tabs value={tab} onValueChange={setTab}>
<Card className="w-full">
<CardHeader>
Expand Down Expand Up @@ -156,25 +157,24 @@ export function RenderRequestsOutput<KEY extends string>({

return null;
})()}

<TabsList>
<TabsTrigger value="accelerated" className="flex flex-row gap-2">
<span>Accelerated</span>
{accelerated.data ? (
`(${
// biome-ignore lint/style/noNonNullAssertion: exists
renderTraceDuration(accelerated.data.trace!)
})`
{acceleratedSuccess && accelerated.data.trace ? (
`(${renderTraceDuration(accelerated.data.trace)})`
) : (
<LoadingSpinner className="h-4 w-4" />
)}
</TabsTrigger>
<TabsTrigger value="unaccelerated" className="flex flex-row gap-2">
<TabsTrigger
value="unaccelerated"
className="flex flex-row gap-2"
disabled={unacceleratedLoading}
>
<span>Unaccelerated</span>
{unaccelerated.data ? (
`(${
// biome-ignore lint/style/noNonNullAssertion: exists
renderTraceDuration(unaccelerated.data.trace!)
})`
{unacceleratedSuccess && unaccelerated.data.trace ? (
`(${renderTraceDuration(unaccelerated.data.trace)})`
) : (
<LoadingSpinner className="h-4 w-4" />
)}
Expand All @@ -184,44 +184,14 @@ export function RenderRequestsOutput<KEY extends string>({
</CardHeader>
<CardContent>
<TabsContent value="accelerated">
{(() => {
switch (accelerated.status) {
case "pending": {
return (
<div className="h-64 w-full flex flex-col justify-center items-center p-8">
<LoadingSpinner className="h-16 w-16" />
</div>
);
}
case "success": {
if (accelerated.data.trace)
return <TraceRenderer trace={accelerated.data.trace} />;
throw new Error(
"Invariant: RenderRequestsOutput accelerated.data.trace is undefined.",
);
}
}
})()}
{acceleratedSuccess && !!accelerated.data.trace && (
<TraceRenderer trace={accelerated.data.trace} />
)}
</TabsContent>
<TabsContent value="unaccelerated">
{(() => {
switch (unaccelerated.status) {
case "pending": {
return (
<div className="h-64 w-full flex flex-col justify-center items-center p-8">
<LoadingSpinner className="h-16 w-16" />
</div>
);
}
case "success": {
if (unaccelerated.data.trace)
return <TraceRenderer trace={unaccelerated.data.trace} />;
throw new Error(
"Invariant: RenderRequestsOutput unaccelerated.data.trace is undefined.",
);
}
}
})()}
{unacceleratedSuccess && !!unaccelerated.data.trace && (
<TraceRenderer trace={unaccelerated.data.trace} />
)}
</TabsContent>
</CardContent>
</Card>
Expand Down
9 changes: 5 additions & 4 deletions apps/ensadmin/src/app/inspect/records/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ 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";
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",
Expand All @@ -30,17 +31,17 @@ 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]);
const [debouncedName] = useDebouncedValue(name, 150);

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,
Expand Down
9 changes: 2 additions & 7 deletions apps/ensadmin/src/app/mock/recent-registrations/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;

Expand All @@ -61,7 +60,6 @@ export default function MockRegistrationsPage() {
}

return {
ensIndexerConfig: ensIndexerPublicConfig,
realtimeProjection: indexingStatus.realtimeProjection,
} satisfies RecentRegistrationsOkProps;
} catch (error) {
Expand Down Expand Up @@ -113,10 +111,7 @@ export default function MockRegistrationsPage() {
{typeof props.error !== "undefined" ? (
<RecentRegistrations error={props.error} />
) : (
<RecentRegistrations
ensIndexerConfig={props.ensIndexerConfig}
realtimeProjection={props.realtimeProjection}
/>
<RecentRegistrations realtimeProjection={props.realtimeProjection} />
)}
</section>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
Loading