Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
253dcf3
feat(ensnode-sdk): extend the client
tk-o Aug 15, 2025
c56930d
Merge remote-tracking branch 'origin/main' into feat/ensnode-sdk-clie…
tk-o Aug 15, 2025
2846b32
feat(ensindexer): indexing status response codes
tk-o Aug 18, 2025
bcdfe3b
feat(ensadmin): indexing status view
tk-o Aug 18, 2025
3a8c4bb
Merge remote-tracking branch 'origin/main' into feat/ensnode-sdk-clie…
tk-o Aug 19, 2025
c61ac30
feat(ensindexer): include `latestSyncedBlock` for backfill chain status
tk-o Aug 19, 2025
48e9202
refactor(ensadmin): full integration of Indexing Status API
tk-o Aug 19, 2025
f5aa81b
Merge remote-tracking branch 'origin/main' into feat/ensnode-sdk-clie…
tk-o Aug 19, 2025
8767920
rename files
tk-o Aug 19, 2025
98dae0c
fix: suspense boundary
tk-o Aug 19, 2025
d09f7d7
fix: deps info fallback for ensrainbow db schema version
tk-o Aug 19, 2025
b4c99b6
docs(changeset): Extends `ENSNodeClient` with new methods: `.config()…
tk-o Aug 19, 2025
096274d
docs(changeset): Includes `latestSyncedBlock` field in `ChainIndexing…
tk-o Aug 19, 2025
02d3164
docs(changeset): Integrates `latestSyncedBlock` field in `ChainIndexi…
tk-o Aug 19, 2025
f6ba2a7
docs(changeset): Uses custom response codes for building Indexing Sta…
tk-o Aug 19, 2025
9e888a3
docs(changeset): Integrates new ENSNode APIs: Config API and Indexing…
tk-o Aug 19, 2025
5aa7ace
refactor(ensadmin): drop unused code
tk-o Aug 19, 2025
865d062
Merge remote-tracking branch 'origin/main' into feat/ensnode-sdk-clie…
tk-o Aug 19, 2025
52e34e9
fix: typos
tk-o Aug 19, 2025
abba8a5
Merge remote-tracking branch 'origin/main' into feat/ensnode-sdk-clie…
tk-o Aug 19, 2025
9342e7f
Merge remote-tracking branch 'origin/main' into feat/ensnode-sdk-clie…
tk-o Aug 20, 2025
95d7cb1
Refine comments
lightwalker-eth Aug 20, 2025
1443294
move react hooks from ensadmin to ensnode-react
lightwalker-eth Aug 20, 2025
95356d9
Merge branch 'main' into feat/ensnode-sdk-client-ext
lightwalker-eth Aug 21, 2025
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/flat-cities-dig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ensnode/ensnode-sdk": minor
---

Includes `latestSyncedBlock` field in `ChainIndexingBackfillStatus` data model.
5 changes: 5 additions & 0 deletions .changeset/honest-rooms-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensindexer": minor
---

Integrates `latestSyncedBlock` field in `ChainIndexingBackfillStatus` data model.
5 changes: 5 additions & 0 deletions .changeset/pretty-pens-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ensnode/ensnode-sdk": minor
---

Extends `ENSNodeClient` with new methods: `.config()` and `.indexingStatus()`.
5 changes: 5 additions & 0 deletions .changeset/tired-knives-pick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensindexer": minor
---

Uses custom response codes for building Indexing Status API response.
5 changes: 5 additions & 0 deletions .changeset/young-ears-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensadmin": minor
---

Integrates new ENSNode APIs: Config API and Indexing Status API. Removes dependency on the legacy ENSNode `/metadata` endpoint.
18 changes: 5 additions & 13 deletions apps/ensadmin/src/app/status/page.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import { Suspense } from "react";

import { IndexingStatus } from "@/components/indexing-status/components";
import { IndexingStatus } from "@/components/indexing-status";
import { ENSNodeProvider } from "@/components/providers/ensnode-provider";
import { RecentRegistrations } from "@/components/recent-registrations";

export default function Status() {
return (
<>
<Suspense fallback={<Loading />}>
<Suspense fallback={<Loading />}>
<ENSNodeProvider>
<IndexingStatus />
</Suspense>
<Suspense>
<ENSNodeProvider>
<div className="px-6 pb-6">
<RecentRegistrations />
</div>
</ENSNodeProvider>
</Suspense>
</>
</ENSNodeProvider>
</Suspense>
);
}

Expand Down
76 changes: 0 additions & 76 deletions apps/ensadmin/src/components/ensnode/hooks.ts

This file was deleted.

2 changes: 0 additions & 2 deletions apps/ensadmin/src/components/ensnode/index.ts

This file was deleted.

34 changes: 0 additions & 34 deletions apps/ensadmin/src/components/ensnode/types.ts

This file was deleted.

189 changes: 189 additions & 0 deletions apps/ensadmin/src/components/indexing-status/backfill-status.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/**
* This file describes UI components required for displaying a timeline for
* the {@link ENSIndexerOverallIndexingBackfillStatus} indexing status object.
*/

import {
ChainIndexingStatusIds,
ENSIndexerOverallIndexingBackfillStatus,
getTimestampForHighestOmnichainKnownBlock,
getTimestampForLowestOmnichainStartBlock,
sortAscChainStatusesByStartBlock,
} from "@ensnode/ensnode-sdk";
import { fromUnixTime } from "date-fns";
import { Clock } from "lucide-react";

import { FormattedDate } from "@/components/datetime-utils";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { getChainName } from "@/lib/namespace-utils";

import {
generateYearMarkers,
getTimelinePosition,
} from "@/components/indexing-status/indexing-timeline-utils";
import { blockViewModel } from "./block-refs";
import { ChainIndexingTimeline } from "./indexing-timeline";

interface ChainIndexingPhaseViewModel {
status: typeof ChainIndexingStatusIds.Unstarted | typeof ChainIndexingStatusIds.Backfill;
startDate: Date;
endDate: Date;
}

interface BackfillStatusProps {
indexingStatus: ENSIndexerOverallIndexingBackfillStatus;
}

/**
* Presents indexing status when overall status is "backfill".
*/
export function BackfillStatus({ indexingStatus }: BackfillStatusProps) {
const chainEntries = sortAscChainStatusesByStartBlock([...indexingStatus.chains.entries()]);
const chains = chainEntries.map(([, chain]) => chain);

const timelineStartUnixTimestamp = getTimestampForLowestOmnichainStartBlock(chains);
const timelineEndUnixTimestamp = getTimestampForHighestOmnichainKnownBlock(chains);

const timelineStart = fromUnixTime(timelineStartUnixTimestamp);
const timelineEnd = fromUnixTime(timelineEndUnixTimestamp);
const omnichainIndexingCursorDate = fromUnixTime(indexingStatus.omnichainIndexingCursor);

const yearMarkers = generateYearMarkers(timelineStart, timelineEnd);
const timelinePositionValue = getTimelinePosition(
omnichainIndexingCursorDate,
timelineStart,
timelineEnd,
);

const timelinePosition =
timelinePositionValue > 0 && timelinePositionValue < 100
? timelinePositionValue.toFixed(4)
: timelinePositionValue;

return (
<main className="grid gap-4">
<Card className="w-full">
<CardHeader className="pb-2">
<CardTitle className="flex justify-between items-center">
<span>Backfill Status</span>

<div className="flex items-center gap-1.5">
<Clock size={16} className="text-blue-600" />
<span className="text-sm font-medium">
Last indexed block on{" "}
<FormattedDate
date={omnichainIndexingCursorDate}
options={{
year: "numeric",
month: "short",
day: "numeric",
}}
/>
</span>
</div>
</CardTitle>
</CardHeader>

<CardContent>
{/* Timeline header with years */}
<div className="relative h-6 mb-1 mt-4 ml-24">
{yearMarkers.map((marker) => (
<div
key={`year-${marker.label}`}
className="absolute -translate-x-1/2"
style={{ left: `${marker.position}%` }}
>
<div className="h-3 w-0.5 bg-gray-400"></div>
<div className="text-xs text-gray-400">{marker.label}</div>
</div>
))}
</div>

{/* Main timeline */}
<div className="relative mb-4 ml-24">
{/* Timeline track */}
<div className="absolute top-0 left-0 w-full h-0.5 bg-gray-200"></div>

<div className="opacity-100 transition-opacity hover:opacity-0">
{/* Current date indicator */}
<div
className="absolute h-full w-0.5 bg-green-800 z-20"
style={{
left: `${timelinePosition}%`,
top: "0",
bottom: "0",
height: `${chainEntries.length * 60}px`,
}}
>
<div className="absolute -bottom-8 -translate-x-1/2 whitespace-nowrap">
<Badge className="text-xs bg-green-800 text-white flex flex-col">
<span>Processing data</span> <span>{timelinePosition}%</span>
</Badge>
</div>
</div>
</div>
</div>

{/* Chain indexing status: progress bars */}
<div className="space-y-6">
{chainEntries.map(([chainId, chain]) => {
const phases: ChainIndexingPhaseViewModel[] = [];

if (timelineStartUnixTimestamp < chain.config.startBlock.timestamp) {
phases.push({
startDate: timelineStart,
endDate: fromUnixTime(chain.config.startBlock.timestamp - 1),
status: ChainIndexingStatusIds.Unstarted,
});
}

phases.push({
startDate: fromUnixTime(chain.config.startBlock.timestamp),
endDate: timelineEnd,
status: ChainIndexingStatusIds.Backfill,
});

const lastIndexedBlock =
chain.status === ChainIndexingStatusIds.Backfill
? blockViewModel(chain.latestIndexedBlock)
: null;

return (
<ChainIndexingTimeline
key={chainId}
currentIndexingDate={omnichainIndexingCursorDate}
chainStatus={{
chainId,
chainName: getChainName(chainId),
firstBlockToIndex: blockViewModel(chain.config.startBlock),
lastIndexedBlock,
phases,
}}
timelineStart={timelineStart}
timelineEnd={timelineEnd}
/>
);
})}
</div>

{/* Legend */}
<div className="flex items-center justify-end mt-8 text-xs gap-4">
<div className="flex items-center gap-1.5">
<div className="w-3 h-3 rounded-sm bg-gray-400" />
<span>Unstarted</span>
</div>
<div className="flex items-center gap-1.5">
<div className="w-3 h-3 rounded-sm bg-blue-500" />
<span>Backfill</span>
</div>
<div className="flex items-center gap-1.5">
<div className="w-0.5 h-3 bg-green-800"></div>
<span>Omnichain Indexing Cursor</span>
</div>
</div>
</CardContent>
</Card>
</main>
);
}
Loading