Skip to main content

Tiny self-hosted CLI for Ethereum validator stats, backed by your own beacon node.

Project description

eth-validator-stats

CI PyPI Python versions License: MIT Latest release

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)

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.3-1_amd64.deb

# 3. Set up config + start the service.
sudo eth-validator-stats init --system
sudo systemctl start eth-validator-stats
sudo systemctl status eth-validator-stats        # confirm it's running

Use apt install ./path.deb (not dpkg -i). dpkg won't pull adduser etc.

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.3-1.fc40.x86_64.rpm

# 3. Set up config + start the service.
sudo eth-validator-stats init --system
sudo systemctl start eth-validator-stats
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               # populate config
sudo systemctl start eth-validator-stats             # or `systemctl --user start ...`

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 with uv run (e.g. uv run eth-validator-stats init).

The fastest path is the onboarding wizard:

eth-validator-stats init

It will:

  1. 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).
  2. Ask for one starter validator (pubkey or index), confirm it against the node.
  3. Optionally generate an ntfy topic and send a verification push to confirm the wire works end-to-end.
  4. 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 and sudo systemctl start eth-validator-stats.

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:

  1. Install the ntfy app (Play Store, App Store, F-Droid) on your phone.
  2. 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.
  3. Set ntfy_topic: "https://ntfy.sh/<your-topic>" under alerts: in config.yml.
  4. 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.

Alert intelligence

  • Beacon-down detection — if the beacon node is unreachable, you get one MONITOR BLIND push (not 1000 per-validator pushes), and one MONITOR RECOVERED when it comes back.
  • Per-validator cooldown — once a validator alerts on a rule, subsequent check runs suppress the same alert until cooldown_minutes has passed (default 30). Rule transitions (OFFLINE → MISSED_ATTESTATIONS, etc.) break the cooldown.
  • Storm grouping — if more than storm_threshold new alerts fire in a single run (default 10), they collapse into one VALIDATOR STORM summary with a sample of indices.
  • Recovery messages — when a validator returns to active_ongoing, you get a RECOVERED push.

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.

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 $XDG_CONFIG_HOME/eth-validator-stats/config.yml Override config path.
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. watch is a headless service-mode loop, not a re-rendered terminal UI.
  • No historical query command — status is current-slot, check is 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/rm CRUD command — edit config.yml directly.
  • 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

eth_validator_stats-0.3.3.tar.gz (79.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

eth_validator_stats-0.3.3-py3-none-any.whl (37.0 kB view details)

Uploaded Python 3

File details

Details for the file eth_validator_stats-0.3.3.tar.gz.

File metadata

  • Download URL: eth_validator_stats-0.3.3.tar.gz
  • Upload date:
  • Size: 79.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for eth_validator_stats-0.3.3.tar.gz
Algorithm Hash digest
SHA256 8001688f8295e366e633f266b35de829a6c3a74d75099e30f8a9cf25ebb1807a
MD5 b2d426ee33190796d3c315f0bfed1ba4
BLAKE2b-256 c8d035356012b871b3d129a40c8ed0cc69c7c18b21e8c9ccfb11aed7e41c2580

See more details on using hashes here.

Provenance

The following attestation bundles were made for eth_validator_stats-0.3.3.tar.gz:

Publisher: release.yml on Workharu/eth-validator-stats

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file eth_validator_stats-0.3.3-py3-none-any.whl.

File metadata

File hashes

Hashes for eth_validator_stats-0.3.3-py3-none-any.whl
Algorithm Hash digest
SHA256 06b1f4dabb99edcd7c0c3f136907f2e33f155ce783d6b7357415c7847418783d
MD5 7f0aa624df3cad5f709a925ed242380f
BLAKE2b-256 aa19b0184a1312ffae645feb160a39d622793590f1727d01a214331857910636

See more details on using hashes here.

Provenance

The following attestation bundles were made for eth_validator_stats-0.3.3-py3-none-any.whl:

Publisher: release.yml on Workharu/eth-validator-stats

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page