Skip to content

About

Monad Validator Delegation Program tracker: on-chain scanner + REST API + web UI. Funding, approvals, registrations, stake and onboarding stats.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Monad VDP Tracker

A dashboard and scanner for the Monad Validator Delegation Program (VDP) — tracking which addresses have been approved by the Monad Foundation, which have registered validators, and which are actively participating in the testnet.

Built by Prooflines.


What is Monad VDP?

The Monad Validator Delegation Program is the process by which the Monad Foundation approves addresses to run testnet validators. The flow is:

  1. Approval — The Foundation sends exactly 100,100 MON (a pure native transfer, no calldata) from its wallet 0xFA735CcA8424e4EF30980653BF9015331d9929dB to a recipient address.
  2. Registration — The recipient sends 100,000 MON to the validator contract 0x0000000000000000000000000000000000001000 using the function selector 0xf145204c. The contract assigns a sequential validator ID.
  3. Active operation — Active validators send heartbeat transactions (selector 0x791bdcf3) to the contract at roughly 1 per block (~2 seconds), spending 25 MON each time.

This tracker scans all three phases and presents the data in a single dashboard.


Architecture

monad-vdp/
├── config.py          # Environment-driven configuration
├── database.py        # SQLite layer (WAL mode, thread-safe)
├── rpc.py             # Async aiohttp JSON-RPC client (rate-limited, retries)
├── scanner.py         # Binary-search scanner + activity checker
├── web.py             # FastAPI dashboard (port 8081)
├── bot_commands.py    # Telegram bot extensions
├── templates/
│   └── index.html     # Dark-theme dashboard (Chart.js)
├── monad-vdp-web.service
└── monad-vdp-scanner.service

Two independent systemd services run in parallel:

  • monad-vdp-scanner — scans every 5 minutes, writes to SQLite
  • monad-vdp-web — serves the FastAPI dashboard, reads from SQLite

How the Scanner Works

RPC constraints

The Monad testnet RPC (https://testnet-rpc.monad.xyz) has a quirk: eth_getLogs with an address filter times out (413 response) even for short ranges, because the validator heartbeat events flood the log index. The scanner works entirely without eth_getLogs.

Phase 1 — Foundation tx discovery

eth_getTransactionCount(foundation, block) is fast and works for the last ~5 million blocks. The scanner binary-searches the Foundation wallet's nonce to find the exact block for each of its 323 outgoing transactions. This takes ~8,000 RPC calls but runs incrementally — already-found blocks are cached in scan_state.

Phase 2 — Approval extraction

For each discovered Foundation tx block, eth_getBlockByNumber(..., true) fetches all transactions. The scanner finds the transfer matching from=Foundation, to=recipient, value≈100100 MON, input=0x.

Phase 3 — Registration discovery

For each approved address, the scanner binary-searches that address's nonce to find its first transaction, then scans forward to find the call to 0x1000 with selector 0xf145204c.

Phase 4 — Activity check

For each registered validator, the scanner compares eth_getTransactionCount(addr, latest) vs eth_getTransactionCount(addr, block_1h_ago). A delta > 60 transactions per hour indicates an active validator.


Self-Hosting

Prerequisites

  • Python 3.11+
  • Ubuntu 24.04 (or similar)
  • sudo access for installing systemd services

Install

cd /home/admin/monad-vdp
python3 -m venv venv
venv/bin/pip install -r requirements.txt

Configure

Copy and edit .env:

cp .env.example .env  # (or create from scratch)

Available variables (all optional — defaults are sane):

RPC_URL=https://testnet-rpc.monad.xyz
FOUNDATION=0xFA735CcA8424e4EF30980653BF9015331d9929dB
CONTRACT=0x0000000000000000000000000000000000001000
DB_PATH=/home/admin/monad-vdp/vdp.db
WEB_PORT=8081
SCAN_INTERVAL=300
BOT_TOKEN=                # Optional: Telegram bot token for alerts
FOUNDATION_MAX_NONCE=323  # Update as Foundation sends more txs

Deploy systemd services

sudo cp monad-vdp-web.service     /etc/systemd/system/
sudo cp monad-vdp-scanner.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now monad-vdp-web
sudo systemctl enable --now monad-vdp-scanner

Nginx reverse proxy (optional)

location /monad/vdp/ {
    proxy_pass http://127.0.0.1:8081/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

Logs

journalctl -u monad-vdp-scanner -f
journalctl -u monad-vdp-web -f

First Sync

The initial scan takes time. Binary-searching 323 nonces with 3 concurrent RPC calls and 0.3 s minimum delay between calls takes roughly:

  • ~13 iterations per nonce × 323 nonces ÷ 3 concurrent ≈ 1,400 batches × 0.3 s ≈ ~7 minutes

Progress is saved after every 20 nonces, so a restart picks up where it left off.

The web dashboard shows a "Syncing X/323 txs" progress bar during this phase. All already-found data is displayed immediately.


Bot Commands

These commands extend @MonadBeaconBot. To integrate, see the snippet at the bottom of bot_commands.py.

Command Description
/watch <address> Subscribe to VDP approval alerts for an address
/unwatch <address> Unsubscribe
/vdp_status <address> Check approval + registration status
/vdp_list Show last 10 VDP approvals

Alert format

When a watched address receives a VDP approval, the bot sends:

🎉 VDP Approved!

Address: 0x92936...b13B5
Amount: 100,100 MON received from Monad Foundation
Time: 2026-04-16 11:37 UTC

Next step: register your validator on testnet
Track progress: https://prooflines.org/monad/vdp/

API Endpoints

Endpoint Description
GET / Dashboard HTML
GET /api/validators All validators with merged approval+registration+status
GET /api/stats Aggregate stats + sync progress
GET /api/validator/{address} Single address detail

Database Schema

Five SQLite tables (WAL journal mode):

  • approvals — Foundation approval txs indexed by recipient address
  • registrations — Validator registrations indexed by address
  • validator_status — Latest activity check results
  • watch_subscriptions — Telegram user watch subscriptions
  • scan_state — Scanner progress state (JSON blobs by key)

Updating Foundation Max Nonce

When the Foundation sends more approval txs, update FOUNDATION_MAX_NONCE in .env and restart the scanner. It will automatically scan only the new nonces.

# Check current Foundation nonce:
curl -s https://testnet-rpc.monad.xyz \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0xFA735CcA8424e4EF30980653BF9015331d9929dB","latest"],"id":1}' \
  | python3 -c "import sys,json; print(int(json.load(sys.stdin)['result'],16))"

License

MIT

About

Monad Validator Delegation Program tracker: on-chain scanner + REST API + web UI. Funding, approvals, registrations, stake and onboarding stats.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages