Tiny self-hosted CLI for Ethereum validator stats, backed by your own beacon node.
Project description
eth-validator-stats
A tiny self-hosted CLI for Ethereum validator stats. Talks to your own beacon node (Prysm, Lighthouse, etc.) over the standard Ethereum Beacon API. Built as a minimal replacement for the now-paid features of the beaconcha.in mobile app.
Commands:
eth-validator-stats init # interactive wizard: detect node, set up ntfy
eth-validator-stats status # rich table snapshot
eth-validator-stats check [--missed N] # cron mode: prints offenders, exits 2 if any
eth-validator-stats watch [--interval N] # service mode: loop a check every N seconds
eth-validator-stats info # probe beacon node: client/version + endpoint support
eth-validator-stats simulate <event> # fire a test ntfy push (no real outage needed)
Short alias: every command also responds to
evs. Soevs status,evs check --missed 3,evs simulate missed-attestation, etc. Identical behavior — just three characters to type. The long form remains canonical in scripts, systemd units, and these docs.
Works with any client that implements the standard Ethereum Beacon API — Prysm, Lighthouse, Teku, Nimbus, Lodestar. See COMPATIBILITY.md.
What status looks like:
Validators
┏━━━━━━━━┳━━━━━━━━┳════════════════┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ idx ┃ label ┃ status ┃ balance (ETH) ┃ last 5 atts ┃
┡━━━━━━━━╇━━━━━━━━╇════════════════╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ 123456 │ home-1 │ active_ongoing │ 32.0182 │ ● ● ● ● ● │
│ 234567 │ home-2 │ active_ongoing │ 32.0177 │ ● · ● ● ● │
└────────┴────────┴════════════────┴───────────────┴─────────────┘
The last column is a sparse glyph row: ● hit, · miss, ? not yet observed.
Install
Pick the path that matches your setup:
| Your situation | Install method | Why |
|---|---|---|
| Beacon node on Debian / Ubuntu | .deb package |
One command, bundles Python, registers a systemd service |
| Beacon node on Fedora / RHEL / Rocky / Alma | .rpm package |
Same as above for the dnf world |
| Anywhere with Python 3.11+ (laptop, Mac, NAS, container) | pipx |
No sudo, per-user install, easy to upgrade |
| Hacking on the code | from source | Run uncommitted changes directly |
All four routes give you the same eth-validator-stats binary. The .deb/.rpm packages additionally install a systemd unit named eth-validator-stats.service.
Pre-built .deb and .rpm packages (both amd64/x86_64 and arm64/aarch64) and the source distribution are attached to each tagged release — see the latest GitHub Release. PyPI publishes the wheel and sdist under the same version.
Debian / Ubuntu
Works on Debian 12+ and Ubuntu 22.04+ (also 24.04, no deadsnakes PPA needed — the .deb bundles its own Python).
# 1. Download the .deb for your architecture from the latest release.
# https://github.com/Workharu/eth-validator-stats/releases/latest
# Files: eth-validator-stats_<version>-1_amd64.deb (or _arm64.deb)
# 2. Install with apt so dependencies (e.g. adduser) auto-resolve.
sudo apt install -y ./eth-validator-stats_0.3.9-1_amd64.deb
# 3. Set up config + start the service.
sudo eth-validator-stats init --system # writes config AND starts the service
sudo systemctl status eth-validator-stats # confirm it's running
Use
apt install ./path.deb(notdpkg -i). dpkg won't pulladduseretc.
What gets installed:
| Path | Purpose |
|---|---|
/opt/eth-validator-stats/ |
Bundled Python interpreter + venv |
/usr/bin/eth-validator-stats |
CLI on $PATH (symlink into the venv) |
/lib/systemd/system/eth-validator-stats.service |
systemd unit (started by you, after init) |
/etc/eth-validator-stats/config.yml |
Created by init --system, owned by service user |
/var/lib/eth-validator-stats/state.json |
Per-validator history, owned by service user |
Upgrade: download the new .deb and sudo apt install -y ./<new>.deb — your config and state are preserved.
Uninstall:
sudo apt remove eth-validator-stats # leaves /etc and /var/lib alone
sudo apt purge eth-validator-stats # also wipes config + state + user
Fedora / RHEL / Rocky / Alma 9+
# 1. Download the .rpm for your architecture from the latest release.
# Files: eth-validator-stats-<version>-1.fcXX.x86_64.rpm (or .aarch64.rpm)
# 2. Install.
sudo dnf install ./eth-validator-stats-0.3.9-1.fc40.x86_64.rpm
# 3. Set up config + start the service.
sudo eth-validator-stats init --system # writes config AND starts the service
sudo systemctl status eth-validator-stats # confirm it's running
Paths and uninstall semantics are the same as the .deb above — config and state survive dnf remove and only get wiped on explicit purge:
sudo dnf remove eth-validator-stats # leaves /etc and /var/lib alone
# To wipe /etc/eth-validator-stats and /var/lib/eth-validator-stats:
sudo rm -rf /etc/eth-validator-stats /var/lib/eth-validator-stats
Any OS with Python 3.11+ (PyPI)
For Linux laptops/desktops without sudo, macOS, FreeBSD, or anywhere you'd rather not install a distro package:
# Install pipx if you don't have it.
# Debian/Ubuntu: sudo apt install pipx
# Fedora: sudo dnf install pipx
# macOS: brew install pipx
# Generic: python3 -m pip install --user pipx && pipx ensurepath
pipx install eth-validator-stats
eth-validator-stats init # per-user config at ~/.config/eth-validator-stats/
eth-validator-stats status # one-shot — see the table
Run as a daemon (Linux only): if you want the same systemd service the .deb/.rpm provides but you installed via pipx, use the built-in installer (does the same wiring as the distro packages' postinst, but against the pipx-installed binary):
sudo eth-validator-stats install-service # system scope (recommended)
# or, no sudo:
eth-validator-stats install-service --user # systemctl --user unit
sudo eth-validator-stats init --system # writes config AND starts the service
# For --user installs: `init --system` requires root; instead use
# `eth-validator-stats init` (per-user config) then `systemctl --user start eth-validator-stats`.
Upgrade: pipx install --force eth-validator-stats.
Uninstall:
sudo eth-validator-stats uninstall-service # remove the systemd unit (leaves config alone)
sudo eth-validator-stats uninstall-service --purge # also delete /etc + /var/lib
pipx uninstall eth-validator-stats # remove the binary
From source
For development, testing patches, or running uncommitted changes:
git clone https://github.com/Workharu/eth-validator-stats
cd eth-validator-stats
uv sync # creates .venv and installs deps
uv run eth-validator-stats status # all commands work via `uv run`
The uv run prefix is needed for source mode since the binary lives at .venv/bin/eth-validator-stats and is not on $PATH. To run without the prefix, source .venv/bin/activate first.
See packaging/linux/README.md for a manual systemd-unit install path that runs the source-mode binary as a service — useful if you want to run from a checkout without packaging.
Setup
All
eth-validator-stats <cmd>examples below assume you installed via.deb/.rpm/pipx. If you're running from source, prefix every command withuv run(e.g.uv run eth-validator-stats init).
The fastest path is the onboarding wizard:
eth-validator-stats init
It will:
- Ask where your beacon node lives, then auto-detect the client by probing the well-known ports (Prysm 3500, Lighthouse 5052, Teku 5051, Nimbus 5052, Lodestar 9596).
- Ask for one starter validator (pubkey or index), confirm it against the node.
- Optionally generate an ntfy topic and send a verification push to confirm the wire works end-to-end.
- Write
~/.config/eth-validator-stats/config.yml(per-user) or/etc/eth-validator-stats/config.yml(with--system).
Add more validators afterward by editing that YAML file directly.
Flag-driven (scriptable):
eth-validator-stats init --beacon-url http://localhost:3500 \
--validator 12345 --label home-1 \
--ntfy-topic eth-vstats-mysecret --yes
If you'd rather skip the wizard entirely, copy config.yml.example to ~/.config/eth-validator-stats/config.yml and edit it.
Verify the ntfy wire (no real outage needed):
eth-validator-stats simulate missed-attestation
Your phone should buzz with MISSED_ATTESTATIONS last=2. Use this any time you change ntfy_topic or want to confirm the alert path before trusting it. Other simulatable events: offline, withdrawal, proposing-soon, proposed, missed-proposal, blind, recovered.
Usage
eth-validator-stats status # one-shot, prints a table
eth-validator-stats check --missed 3 # cron mode, exits 2 if any alerts fired
eth-validator-stats watch --interval 60 # long-running, loops every 60s
eth-validator-stats info # probe the beacon node and check endpoint support
status prints a table and exits 0. check prints one line per offender (<index> <label>\t<rule>) and exits 2 if any alerts fire — designed for cron. watch runs the same check on a loop and is what the systemd service uses.
What gets notified
Per-event, all routed through the same notifier (ntfy by default):
| Event | When | Dedup |
|---|---|---|
OFFLINE |
validator status leaves active_ongoing/pending_* |
once per cooldown_minutes, resets on recovery |
MISSED_ATTESTATIONS |
last N consecutive liveness records are misses (N default 2, override via alerts.missed_attestations_threshold in YAML or --missed N flag) |
once per cooldown_minutes |
withdrawal |
balance drops by ≥ alerts.withdrawal_threshold_gwei (default 0.001 ETH) while still active AND the two compared balance snapshots are ≤ alerts.withdrawal_max_gap_slots apart (default 64 slots ≈ 12.8 min — avoids false positives from cumulative attestation losses during a check outage) |
once per drop (next poll resets the comparison baseline) |
proposing soon |
a configured validator is scheduled to propose within alerts.proposal_lookahead_epochs epochs (default 1 ≈ ~6 min) |
exactly once per (validator, slot) |
✓ proposed / ✗ missed proposal |
scheduled slot has passed; verified against the canonical block header | once per (validator, slot) |
MONITOR BLIND / MONITOR RECOVERED |
beacon node unreachable / reachable again | once per cooldown_minutes |
RECOVERED messages fire when a validator returns to active_ongoing after any non-pending status.
Cron
Don't call uv run from cron — it pays the resolver cost every minute. Call the binary directly. The right path depends on how you installed:
| Install method | Binary path |
|---|---|
.deb / .rpm |
/usr/bin/eth-validator-stats (or just eth-validator-stats — it's on $PATH) |
pipx |
~/.local/bin/eth-validator-stats |
| From source | /path/to/checkout/.venv/bin/eth-validator-stats |
Example crontab (every 5 min, fall back to a desktop notification if the alert path failed):
*/5 * * * * eth-validator-stats check --missed 3 || notify-send "validator alert"
Run as a service (Linux / Windows)
If you installed via .deb or .rpm, the systemd unit is already installed — see the Install section above. Just sudo eth-validator-stats init --system (which writes the config and starts the service in one step).
For pipx installs, register the same unit with sudo eth-validator-stats install-service (covered in the pipx section).
For source-mode users or anyone who prefers the legacy manual scripts:
Linux (no sudo, systemd --user):
cd packaging/linux
./install-service.sh
systemctl --user status eth-validator-stats.service
Linux (system scope, sudo):
cd packaging/linux
sudo ./install-service.sh --system
Windows (elevated PowerShell):
cd packaging\windows
.\install-service.ps1
Get-Service eth-validator-stats
See packaging/linux/README.md and packaging/windows/README.md for full
details, uninstall instructions, and the Phase 2 distro-package migration
path. Cron mode (above) continues to work alongside the service unit; the
two do not conflict, but running both at once will double the load on your
beacon node.
Push notifications (ntfy)
If ntfy_topic is set under alerts: in config.yml, check POSTs to that ntfy topic on every new alert transition (no spam — see dedup/storm below).
The fastest way to wire this up is eth-validator-stats init, which generates an unguessable topic and sends a verification push so you can confirm delivery before trusting the alert path. If you skipped that step or want to change topics later:
- Install the ntfy app (Play Store, App Store, F-Droid) on your phone.
- In the app: Subscribe → enter an unguessable topic name (e.g.
eth-vstats-9f8e7d6c5b4a). Anyone who knows the name can read messages, so treat it as a secret. - Set
ntfy_topic: "https://ntfy.sh/<your-topic>"underalerts:inconfig.yml. - Run
eth-validator-stats check. The first time an alert fires you'll get a push.
The public ntfy.sh server is free and Apache-2.0 open source. If you'd rather self-host, run ntfy serve on your beacon-node box and set ntfy_topic to http://your-host:80/your-topic.
Push icon: every notification carries the eth-validator-stats icon (a small PNG served from this repo) so you can tell at a glance the push is from this tool. To override, set alerts.icon_url to any publicly-fetchable URL. To suppress entirely, set it to an empty string:
alerts:
icon_url: ""
ntfy clients fetch the image at notification time and cache it; if the URL ever 404s the push still arrives, just without an icon.
Alert intelligence
- Beacon-down detection — if the beacon node is unreachable, you get one
MONITOR BLINDpush (not 1000 per-validator pushes), and oneMONITOR RECOVEREDwhen it comes back. - Per-validator cooldown — once a validator alerts on a rule, subsequent
checkruns suppress the same alert untilcooldown_minuteshas passed (default 30). Rule transitions (OFFLINE → MISSED_ATTESTATIONS, etc.) break the cooldown. - Storm grouping — if more than
storm_thresholdnew alerts fire in a single run (default 10), they collapse into oneVALIDATOR STORMsummary with a sample of indices. - Recovery messages — when a validator returns to
active_ongoing, you get aRECOVEREDpush.
Notification failures (network blip, ntfy server down) are logged to stderr but never crash check.
The state file in ~/.local/share/eth-validator-stats/state.json builds up a per-validator rolling buffer of the last ~10 epochs of liveness across runs. Last-5-attestations is sparse-by-design: it shows the last 5 epochs the CLI has observed, not the last 5 epochs of chain history. Run frequently (cron at 1–5 minutes is fine) and the buffer stays current.
Where is my config?
When reading, the CLI searches in this order and uses the first one it finds:
$ETH_VALIDATOR_STATS_CONFIG— explicit override (used by the systemd unit and tests)./etc/eth-validator-stats/config.yml— system-wide, written byinit --systemand the.deb/.rpmpost-install scripts. If you installed via a distro package, this is where your config lives, and it's found regardless of which user is invoking the CLI.~/.config/eth-validator-stats/config.yml— per-user XDG default, written byinit(without--system).- (legacy)
~/.config/eth-validator-stats/config.toml— pre-YAML format; prints a deprecation hint.
When writing (i.e. init), the system path is used only with --system; otherwise writes go to the per-user XDG path. The lookup order means a per-user config is never accidentally overshadowed by a system config you didn't intend to write — you have to deliberately run init --system to put one there.
Environment variables
| Var | Default | Meaning |
|---|---|---|
BEACON_NODE_URL |
http://localhost:3500 |
Beacon node HTTP endpoint. Wins over the beacon_node_url field in config. |
BEACON_NODE_AUTH_TOKEN |
(none) | Optional Bearer token for hosted providers / proxied nodes. Wins over beacon_auth_token in config. |
ETH_VALIDATOR_STATS_CONFIG |
(auto-discovered, see above) | Override config path. When set, it wins over both /etc and ~/.config. |
ETH_VALIDATOR_STATS_STATE |
$XDG_DATA_HOME/eth-validator-stats/state.json |
Override state file path. |
Diagnosing a new node
eth-validator-stats info
Prints client name/version (e.g. Prysm/v6.2.1) and probes each endpoint we depend on, marking them OK / UNSUPPORTED / ERROR. Run this first against any new beacon node. If POST /eth/v1/validator/liveness/{epoch} shows UNSUPPORTED, your client is too old for the "last N attestations" feature — everything else still works (you'll get a one-time warning on first status run).
See COMPATIBILITY.md for the per-client port table and tested-versions matrix.
How it works
Each invocation makes at most three calls to the beacon node:
GET /eth/v1/beacon/headers/head— current slot.POST /eth/v1/beacon/states/head/validators— status + balance for all configured validators in one call (POST form has no 64-id cap).POST /eth/v1/validator/liveness/{current_epoch - 1}— did each validator participate in the just-finished epoch.
The first run also fetches /eth/v1/beacon/genesis and /eth/v1/config/spec once and caches them in the state file.
Liveness answers "the validator was seen in the epoch", which is what most users mean when they ask "did I miss an attestation". On-chain head/target correctness is a v2 feature (via POST /eth/v1/beacon/rewards/attestations/{epoch} on finalized epochs).
What it does NOT do (yet)
- No live TUI / dashboard.
watchis a headless service-mode loop, not a re-rendered terminal UI. - No historical query command —
statusis current-slot,checkis recent-buffer (~10 epochs). For long-range history you still want a block explorer or beaconcha.in. - No on-chain head/target attestation-correctness scoring (only "seen / not seen" per epoch via the liveness endpoint). On-chain correctness is a planned v2 feature.
- No
validators add/list/rmCRUD command — editconfig.ymldirectly. - No Telegram / Discord / email transport — ntfy only. Telegram is the planned follow-up.
See docs/superpowers/plans/ and docs/superpowers/specs/ for what's actively in flight.
Tests
uv sync # installs dev deps automatically (pytest is in [dependency-groups].dev)
uv run pytest
Tests use httpx.MockTransport; no live beacon node required.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file eth_validator_stats-0.3.9.tar.gz.
File metadata
- Download URL: eth_validator_stats-0.3.9.tar.gz
- Upload date:
- Size: 117.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e2ab2a9fda8f8d659673d12f6960095d270deb164ffbe1e0ea326791fd41bde
|
|
| MD5 |
15846055ce3fe342cc52e8b3c3b6ae5b
|
|
| BLAKE2b-256 |
f7591cfe94fbb9fb37bb571a205708c66b35fed84bf374babc1641b9375b7c53
|
Provenance
The following attestation bundles were made for eth_validator_stats-0.3.9.tar.gz:
Publisher:
release.yml on Workharu/eth-validator-stats
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
eth_validator_stats-0.3.9.tar.gz -
Subject digest:
9e2ab2a9fda8f8d659673d12f6960095d270deb164ffbe1e0ea326791fd41bde - Sigstore transparency entry: 1628995743
- Sigstore integration time:
-
Permalink:
Workharu/eth-validator-stats@b9e9fade80998e23b047577607f7a0161900e076 -
Branch / Tag:
refs/tags/v0.3.9 - Owner: https://github.com/Workharu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b9e9fade80998e23b047577607f7a0161900e076 -
Trigger Event:
push
-
Statement type:
File details
Details for the file eth_validator_stats-0.3.9-py3-none-any.whl.
File metadata
- Download URL: eth_validator_stats-0.3.9-py3-none-any.whl
- Upload date:
- Size: 40.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f40f6ee3378c1c1c2d363124b728ae0247b6d5c3b44d7c4502795c7a29d5ec30
|
|
| MD5 |
6491e134d2bbe4226388d0ecdd765a96
|
|
| BLAKE2b-256 |
83e7434d5f0a568e4e0ce744685801b1a204b52e40d6b455f218412b0e7039c8
|
Provenance
The following attestation bundles were made for eth_validator_stats-0.3.9-py3-none-any.whl:
Publisher:
release.yml on Workharu/eth-validator-stats
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
eth_validator_stats-0.3.9-py3-none-any.whl -
Subject digest:
f40f6ee3378c1c1c2d363124b728ae0247b6d5c3b44d7c4502795c7a29d5ec30 - Sigstore transparency entry: 1628995751
- Sigstore integration time:
-
Permalink:
Workharu/eth-validator-stats@b9e9fade80998e23b047577607f7a0161900e076 -
Branch / Tag:
refs/tags/v0.3.9 - Owner: https://github.com/Workharu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b9e9fade80998e23b047577607f7a0161900e076 -
Trigger Event:
push
-
Statement type: