= {
+ keyCustody: "Key Custody",
+ clientDiversity: "Client Diversity",
+ infraDiversity: "Provider Diversity",
+ osDiversity: "OS Diversity",
+ cpuDiversity: "CPU Architecture",
+ geoDiversity: "Geographic Diversity",
+};
+
+const ORDER: SliceId[] = [
+ "keyCustody",
+ "clientDiversity",
+ "infraDiversity",
+ "geoDiversity",
+ "osDiversity",
+ "cpuDiversity",
+];
+
+const STAGES = [
+ {
+ stage: "0" as const,
+ tone: "red" as const,
+ num: "0",
+ name: "Stage 0 — Exposed",
+ kind: "Starting point",
+ desc: "At least one single point of failure remains. Most operators start here — the baseline.",
+ },
+ {
+ stage: "1" as const,
+ tone: "yellow" as const,
+ num: "1",
+ name: "Stage 1 — Slashing-averse",
+ kind: "Safety",
+ desc: "No single compromise of one machine, one team member, or one signer can produce a slashable message.",
+ },
+ {
+ stage: "2" as const,
+ tone: "green" as const,
+ num: "2",
+ name: "Stage 2 — Downtime-averse",
+ kind: "Liveness",
+ desc: "No single point of failure in the operator's infrastructure can take the validator offline. This is the end game.",
+ },
+];
+
+function LNav() {
+ return (
+
+
+
+ Validator Beat
+
+
+
+
+ How it works
+
+ Methodology
+
+ valOS
+
+
+
+
+ Assess your validator
+
+
+
+ );
+}
+
+function LHero() {
+ return (
+
+
+
+
+
+ A simple view into validator operations
+
+
+ The validator ecosystem is missing its beat.
+
+
+ When you stake ETH, you always (sometimes unknowingly) pick an operator. You{" "}
+ cannot see how they hold their keys, which clients they run, or how
+ correlated their setup is with everyone else securing the network. Validator Beat makes
+ that visible.
+
+
+
+ Assess your validator
+
+
+ See how it works
+
+
+
+
+
+
+
+ An example operator profile
+
+
+
+
+
+
+ );
+}
+
+function LGap() {
+ return (
+
+
+
+ The gap
+
+ Transparency has been built into every layer, except the validators.
+
+
+
+ {["L2Beat", "Walletbeat"].map((name) => (
+
+
+
+ {name}
+
+
+
+
+
+ ))}
+
+
+ Validator Beat
+
+
+
+ Rollups have L2Beat. Wallets have Walletbeat. The validator operators securing tens of
+ millions of staked ETH have had no way to differentiate themselves outside of performance
+ and reputation. Stakers, from ETH holders to institutional allocators, choose an operator or
+ protocol and forget about it. Compliance attestations like SOC 2 cover process and custody,
+ but they say nothing about the choices that actually determine whether a validator stays safe
+ and online: how signing keys are split, whether the operator runs a single client
+ implementation, how concentrated their infrastructure is, and how much of that overlaps with
+ the rest of the network.
+
+
+ That invisible risk is unpriced.
+ {" "}Validator Beat is the public entry point that compares these risks and gives operators a
+ reason to compete on it.
+
+
+
+ );
+}
+
+function LFail() {
+ return (
+
+
+
+ Why it matters
+ How validators actually fail
+
+ There are two ways a validator fails, and decentralization is the insurance against both.
+
+
+
+
+
+
+
+
+ Safety failure · slashing
+
+ It signs something it never should have.
+
+ A double attestation, or a supermajority vote on the wrong chain. The protocol slashes
+ the stake. This is the expensive failure.
+
+
+
+
+
+
+
+ Liveness failure · downtime
+
+ It goes offline and misses its duties.
+
+ No slashing, but no rewards either, and a weaker network. The validator simply isn't
+ there when it's needed.
+
+
+
+
+ Most safety and liveness issues trace back to a single point of failure:
+ one machine, one team member, one client, one provider, one region. Validator Beat measures
+ how many of those single points of failure an operator has removed.
+
+
+
+ );
+}
+
+function LRead() {
+ return (
+
+
+
+ The assessment
+ How Validator Beat assesses an operator
+
+ Most operators start exposed with two stages to climb and a six-slice risk profile.
+
+
+
+ {STAGES.map(({ tone, num, name, kind, desc }) => (
+
+
+
+ {num}
+
+
+ {name}
+ {kind}
+
+
+ {desc}
+
+ ))}
+
+
+
+
+
+ Each slice scored{" "}
+ green,{" "}
+ yellow, or{" "}
+ red
+
+
+
+ {ORDER.map((id) => (
+
+
+
+ {READ_LABEL[id]}
+
+
+ {SLICE_DESC[id]}
+
+
+ ))}
+
+
+
+ This can be read at a glance. For the exact thresholds and rubric, see the{" "}
+ methodology. To see where your
+ own validator lands,{" "}
+
+ take the assessment
+
+ .
+
+
+ Validator Beat is self-reported and consent-based.
+
+
+
+ );
+}
+
+function LSides() {
+ return (
+
+
+
+ Who it's for
+ Built for both sides of the stake
+
+
+
+ If you delegate your ETH
+
+ Read any operator's profile in five seconds.
+
+
+ Before you stake, not after an incident. Two stages and six colors give you insight into
+ validator operations that the marketing page never will.
+
+
+
+ If you're an operatooor
+ Proof of work.
+
+ Run the assessment, earn your stages, and show off your pizza wherever you list your
+ product. Operators who have removed their single points of failure now have a way to show
+ it.
+
+
+
+ Assess your validator
+
+
+
+
+
+
+ );
+}
+
+function LValos() {
+ return (
+
+
+
+
+ The standard behind the score
+ Validator Beat and valOS
+
+ Validator Beat is the public-facing who is running validators. valOS,
+ the Validator Operating Standard, is the technical how: a deep catalog
+ of the controls and mitigations behind professional validator operations.
+
+
+ A staker can quickly read an operator's stages and profile here. An operator doing
+ the hard work and implementing the mitigations should dig into valOS. Follow valOS, and
+ you'll end up at stage 2.
+
+
+
+ Learn more about valOS
+
+
+
+
+
+ Validator Beat
+
+ The who — the public dashboard
+
+
+
+
+
+
+ valOS
+
+ The how — the operating standard
+
+
+
+
+
+
+ );
+}
+
+function LNeutral() {
+ return (
+
+
+
+ Credibility
+ A neutral dashboard, built in the open
+
+ Validator Beat is co-authored by Obol and Lido. It is deliberately neutral and no single
+ team owns the rubric. The methodology is public and is meant to be adopted, challenged,
+ and improved by the whole ecosystem.
+
+
+ The goal is a race to the top, where transparency about validator risk
+ becomes the default expectation.
+
+
+ {[
+ { name: "Obol", color: "var(--theme-brand)" },
+ { name: "Lido", color: "#00a3ff" },
+ ].map(({ name, color }) => (
+
+
+ {name}
+
+ ))}
+
+
+
+
+ );
+}
+
+function LClosing() {
+ return (
+
+
+
+ Find out how secure your validator really is.
+
+
+
+ Assess your validator
+
+
+
+ Read the methodology
+ ·
+
+ Explore valOS
+
+
+
+
+ );
+}
+
+function LFooter() {
+ return (
+
+
+
+ Validator Beat
+
+
+ A simple view into validator operations
+
+
+ Methodology
+
+ valOS
+
+ Assess
+
+
+ );
+}
+
+export function Landing() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/landing/icons.tsx b/components/landing/icons.tsx
new file mode 100644
index 0000000..0cfd1c5
--- /dev/null
+++ b/components/landing/icons.tsx
@@ -0,0 +1,38 @@
+import {
+ ActivityLogIcon,
+ ArrowDownIcon,
+ ArrowRightIcon,
+ ExclamationTriangleIcon,
+ ExternalLinkIcon,
+} from "@radix-ui/react-icons";
+import type { ComponentProps } from "react";
+
+type IconSize = { size?: number };
+
+const iconStyle = (size: number): ComponentProps<"svg">["style"] => ({
+ width: size,
+ height: size,
+ flexShrink: 0,
+});
+
+export function IconArrowRight({ size = 16 }: IconSize) {
+ return ;
+}
+
+export function IconArrowDown({ size = 16 }: IconSize) {
+ return ;
+}
+
+export function IconExternalLink({ size = 16 }: IconSize) {
+ return ;
+}
+
+/** Safety / slashing failure */
+export function IconSafety({ size = 24 }: IconSize) {
+ return ;
+}
+
+/** Liveness / downtime */
+export function IconLiveness({ size = 24 }: IconSize) {
+ return ;
+}
diff --git a/components/pizza/Pizza.tsx b/components/pizza/Pizza.tsx
index 14c963e..8ef712c 100644
--- a/components/pizza/Pizza.tsx
+++ b/components/pizza/Pizza.tsx
@@ -46,6 +46,8 @@ export type PizzaProps = {
labelScale?: number;
showCenter?: boolean;
stage?: Stage | null;
+ /** Wedge glow strength. Softer (e.g. 0.28) reads better on light surfaces. */
+ glowOpacity?: number;
};
export function Pizza({
@@ -58,6 +60,7 @@ export function Pizza({
labelScale = 1,
showCenter = true,
stage = null,
+ glowOpacity = 0.5,
}: PizzaProps) {
const big = size >= 120;
const pop = big ? size * 0.022 : 0;
@@ -115,7 +118,7 @@ export function Pizza({
dy="0"
stdDeviation={size * 0.013}
floodColor={PIZZA_FILL[c]}
- floodOpacity="0.5"
+ floodOpacity={glowOpacity}
/>
))}
diff --git a/lib/rubric/index.ts b/lib/rubric/index.ts
index 806a924..83fa9b4 100644
--- a/lib/rubric/index.ts
+++ b/lib/rubric/index.ts
@@ -21,8 +21,8 @@ export const SLICES: SliceMeta[] = [
},
{
id: "infraDiversity",
- label: "Infrastructure",
- short: "Infra",
+ label: "Provider",
+ short: "Provider",
why: "One hosting provider's outage or compromise can take every validator hosted there with it.",
},
{
diff --git a/pages/assess.tsx b/pages/assess.tsx
new file mode 100644
index 0000000..7315452
--- /dev/null
+++ b/pages/assess.tsx
@@ -0,0 +1,14 @@
+import { AssessmentApp } from "@components/assessment/AssessmentApp";
+import type { GetStaticProps } from "next";
+
+export default function AssessPage() {
+ return ;
+}
+
+export const getStaticProps: GetStaticProps = () => ({
+ props: {
+ title: "Self-assessment",
+ description:
+ "Six questions about your Ethereum validator setup. Score your resilience in about a minute — nothing is submitted or stored.",
+ },
+});
diff --git a/pages/index.tsx b/pages/index.tsx
index a151268..a6a4860 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -1,14 +1,14 @@
-import { AssessmentApp } from "@components/assessment/AssessmentApp";
+import { Landing } from "@components/landing/Landing";
import type { GetStaticProps } from "next";
export default function HomePage() {
- return ;
+ return ;
}
export const getStaticProps: GetStaticProps = () => ({
props: {
- title: "Self-assessment",
+ title: "The standard for validator resilience",
description:
- "Six questions about your Ethereum validator setup. Score your resilience in about a minute — nothing is submitted or stored.",
+ "When you stake, you pick an operator — but you can't see how they run. Validator Beat makes validator resilience visible: two stages, six slices, one pizza.",
},
});
diff --git a/pages/methodology.tsx b/pages/methodology.tsx
index 39de130..34950ec 100644
--- a/pages/methodology.tsx
+++ b/pages/methodology.tsx
@@ -170,7 +170,7 @@ export default function MethodologyPage() {
-
+
Take the assessment →