Off-chain oracle that publishes Merkle roots of SSV cluster effective balances to the SSV Network contract.
- Event-sourced - Syncs SSV contract events to SQLite for point-in-time queries
- Epoch-aligned - Commits only after beacon chain finalization
- OpenZeppelin-compatible - StandardMerkleTree format with deterministic ordering
- Single binary - Embedded SQLite database
- HTTP API - Query committed data and generate merkle proofs
Prerequisites: Go 1.25+, Ethereum execution client, Beacon node, funded wallet
# Setup
cp .env.example .env && cp config.yaml.example config.yaml
# Edit config.yaml with your endpoints and contract addresses
# Run
make fresh # Fresh start (clears DB)
make fresh-all # Fresh start with updater
make run # Oracle only
make run-all # Oracle + cluster updaterCLI:
make build
./ssv-oracle run --config config.yaml # Oracle only
./ssv-oracle run --config config.yaml --updater # With cluster updater
./ssv-oracle run --config config.yaml --fresh # Clear DB first
./ssv-oracle run --config config.yaml --fresh --updater # BothThe oracle is event-driven, reacting to beacon chain finalization:
- Subscribe to beacon node finalized checkpoint events (SSE)
- Sync SSV contract events up to the finalized block
- Fetch validator effective balances from beacon chain
- Build Merkle tree aggregated by cluster
- Submit root to SSV Network contract
Finalization: When beacon reports checkpoint.Epoch = N, epoch N-1 is fully finalized. The oracle uses this to determine which targets can be committed.
Runs alongside the oracle (--updater flag) to update individual cluster balances on-chain:
- Listen for
RootCommittedevents - Rebuild Merkle tree from stored balances
- For each cluster with changed balance, submit proof via
UpdateClusterBalance
Clusters with unchanged balances are skipped to save gas.
SQLite at ./data/oracle.db. Reset with make db-reset, make fresh, or make fresh-all.
Backup:
sqlite3 data/oracle.db ".backup data/oracle.db.backup"The oracle exposes an HTTP API for querying committed data and generating merkle proofs.
Endpoints:
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/commit |
Latest confirmed commit metadata |
| GET | /api/v1/commit?full=true |
Include clusters and tree layers |
| GET | /api/v1/proof/{clusterId} |
Merkle proof for a cluster |
| GET | / |
Tree visualization UI |
Configuration:
api_address: "127.0.0.1:8080" # Default: localhost onlyTo expose externally, use 0.0.0.0:8080 (ensure firewall/proxy protection).
Example:
# Get latest commit
curl http://127.0.0.1:8080/api/v1/commit
# Get merkle proof for a cluster
curl http://127.0.0.1:8080/api/v1/proof/0x1234...
# Open tree visualization
open http://127.0.0.1:8080Run make to see all available targets.
Set DEV=true env for colored console output.
| Issue | Solution |
|---|---|
| Database errors | make fresh or make fresh-all to reset |
| Connection failed | Verify RPC endpoints are accessible |
| Beacon not synced | Wait for curl <beacon_url>/eth/v1/node/syncing to show is_syncing: false |