diff --git a/packages/nextjs/app/builders/page.tsx b/packages/nextjs/app/builders/page.tsx
new file mode 100644
index 0000000..4275729
--- /dev/null
+++ b/packages/nextjs/app/builders/page.tsx
@@ -0,0 +1,16 @@
+import { promises as fs } from "fs";
+import type { NextPage } from "next";
+import BuildersList from "~~/components/BuildersList";
+
+const Builders: NextPage = async () => {
+ const file = await fs.readdir(process.cwd() + "/app/builders");
+ const builderProfiles = file.filter((file: string) => file.startsWith("0x") && file.length == 42);
+
+ return (
+ <>
+
+ >
+ );
+};
+
+export default Builders;
diff --git a/packages/nextjs/components/BuildersList.tsx b/packages/nextjs/components/BuildersList.tsx
new file mode 100644
index 0000000..324c6a3
--- /dev/null
+++ b/packages/nextjs/components/BuildersList.tsx
@@ -0,0 +1,112 @@
+"use client";
+
+import { useEffect, useRef, useState } from "react";
+import Link from "next/link";
+import { Address } from "~~/components/scaffold-eth";
+import { useScaffoldEventHistory } from "~~/hooks/scaffold-eth";
+
+type Builder = {
+ address: string;
+ hasPersonalPage: boolean;
+};
+
+export default function Page({ builderProfiles }: { builderProfiles: string[] }) {
+ const [builders, setBuilders] = useState([] as Builder[]);
+ const [isLoading, setIsLoading] = useState(true);
+ const elementRef = useRef(null);
+
+ const { data: events } = useScaffoldEventHistory({
+ contractName: "BatchRegistry",
+ eventName: "CheckedIn",
+ fromBlock: 126461494n,
+ });
+
+ useEffect(() => {
+ if (elementRef.current) {
+ setIsLoading(false);
+ }
+ }, []);
+
+ useEffect(() => {
+ const fetchBuilders = async () => {
+ if (!events) return;
+
+ // Filter out duplicate addresses
+ const filteredAddresses: string[] = [];
+ events.forEach(event => {
+ const address = event.args.builder;
+ if (!address) return;
+ if (!filteredAddresses.find(addr => address === addr)) filteredAddresses.push(address);
+ });
+
+ const builders = await Promise.all(
+ filteredAddresses.map(async address => {
+ if (address)
+ return {
+ address,
+ // Check if the builder has a personal page
+ hasPersonalPage: !!builderProfiles.find(bp => bp === address),
+ };
+ }),
+ );
+ setBuilders(
+ builders
+ .filter(builder => !!builder)
+ // Sort by personal apge availability
+ .sort((a, b) => +(b.hasPersonalPage === true) - +(a.hasPersonalPage === true)),
+ );
+ };
+
+ fetchBuilders().catch(console.error);
+ }, [events]);
+
+ return (
+
+
+
+
+ Builders
+
+
+
+
+
+ {isLoading ? (
+
Loading builder pages
+ ) : (
+
+
+
+ |
+ ENS or Address |
+ Builder Page |
+
+
+
+ {builders.map((builder, id) => (
+
+ {id + 1} |
+
+
+ |
+
+ {builder.hasPersonalPage ? (
+
+ Builder Page
+
+ ) : (
+ <>Not available>
+ )}
+ |
+
+ ))}
+
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/packages/nextjs/components/Header.tsx b/packages/nextjs/components/Header.tsx
index aead9dc..078b498 100644
--- a/packages/nextjs/components/Header.tsx
+++ b/packages/nextjs/components/Header.tsx
@@ -4,6 +4,7 @@ import React, { useCallback, useRef, useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
+import { FaUsers } from "react-icons/fa";
import { Bars3Icon, BugAntIcon } from "@heroicons/react/24/outline";
import { FaucetButton, RainbowKitCustomConnectButton } from "~~/components/scaffold-eth";
import { useOutsideClick } from "~~/hooks/scaffold-eth";
@@ -24,6 +25,11 @@ export const menuLinks: HeaderMenuLink[] = [
href: "/debug",
icon: ,
},
+ {
+ label: "Builders",
+ href: "/builders",
+ icon: ,
+ },
];
export const HeaderMenuLinks = () => {