A lightweight, transparent RPC load balancer and health monitor for Demos Network nodes. Run one OmniPoint instance and let it route your JSON-RPC traffic to the healthiest upstream endpoint — automatically.
Instead of hardcoding a single Demos RPC endpoint in your client, you point it at OmniPoint's meta RPC URL:
http://localhost:8080/rpc
OmniPoint continuously probes every configured upstream endpoint (ping + handshake) and scores them on latency, liveness, and block-height sync. When your client sends a JSON-RPC request to /rpc, OmniPoint transparently forwards it to the best available upstream — your client doesn't even know a switch happened.
If the chosen upstream fails and you have failover strategy enabled, OmniPoint automatically retries the next best endpoint before returning an error to your client.
Think of it as a smart reverse proxy that speaks Demos RPC natively.
- Health-aware routing — scores endpoints by handshake success, ping ratio, and EMA latency
- Block-height drift detection — flags stale nodes that lag behind the chain tip
- Three routing strategies —
best(highest score),round_robin(weighted),failover(retry on error) - Transparent forwarding — preserves method, headers, path, and body; streams responses unchanged
- Dynamic source lists — fetch endpoint lists from GitHub Gist, raw HTTPS JSON, or local files; refreshes without restart
- Prometheus metrics —
/metricsexposes probe latencies, success/failure counters, health gauges, forward metrics, and more - CLI tools —
serve,probe,list,watch,best - JSON snapshot persistence — optional disk snapshots of endpoint state
- Graceful shutdown — drains in-flight forwarded requests before exit
# Install dependencies
bun install
# Start with the local demo endpoint list
bun run src/index.ts serve
# In another terminal, use the meta RPC URL exactly like a regular Demos node
curl -X POST http://localhost:8080/rpc \
-H "Content-Type: application/json" \
-d '{"method":"nodeCall","params":[{"type":"nodeCall","message":"getLastBlockNumber","sender":null,"receiver":null,"timestamp":null,"data":{},"extra":""}]}'
# Check health and scores
curl http://localhost:8080/status
curl http://localhost:8080/metrics
# Browse interactive API docs
open http://localhost:8080/docs
# One-shot probe table
bun run src/index.ts probe
# Print the currently-best endpoint URL
bun run src/index.ts bestResolution order: defaults → config file → env vars → CLI flags
Config file: omnipoint.config.json (or .toml) in CWD, or pass --config path.json.
| Config Key | Env Var | Default | Description |
|---|---|---|---|
source.url |
OMNIPOINT_SOURCE_URL |
— (required) | Where to fetch the RPC list from |
source.refreshIntervalSec |
OMNIPOINT_SOURCE_REFRESH_SEC |
600 |
How often to re-fetch the list |
probe.pingIntervalSec |
OMNIPOINT_PING_SEC |
60 |
Light ping cadence |
probe.handshakeIntervalSec |
OMNIPOINT_HANDSHAKE_SEC |
300 |
Full handshake cadence |
probe.pingTimeoutMs |
OMNIPOINT_PING_TIMEOUT_MS |
5000 |
Per-endpoint ping timeout |
probe.handshakeTimeoutMs |
OMNIPOINT_HANDSHAKE_TIMEOUT_MS |
10000 |
Per-endpoint handshake timeout |
probe.windowSize |
OMNIPOINT_WINDOW |
20 |
Rolling history per endpoint |
probe.maxBlockLag |
OMNIPOINT_MAX_BLOCK_LAG |
5 |
Max block lag before marking stale |
forward.strategy |
OMNIPOINT_STRATEGY |
best |
best | round_robin | failover |
forward.maxRetries |
OMNIPOINT_MAX_RETRIES |
2 |
Failover retry attempts |
forward.allowStale |
OMNIPOINT_ALLOW_STALE |
false |
Allow stale endpoints in routing |
server.port |
PORT |
8080 |
HTTP server port |
server.host |
HOST |
0.0.0.0 |
HTTP server host |
auth.refreshToken |
OMNIPOINT_REFRESH_TOKEN |
— | Bearer token for POST /refresh |
persist.snapshotPath |
OMNIPOINT_SNAPSHOT |
— | Path to JSON snapshot file |
logLevel |
LOG_LEVEL |
info |
debug | info | warn | error |
- GitHub Gist — raw gist URL or
https://api.github.com/gists/<id> - Raw JSON — any HTTPS URL serving a JSON array
- Local file —
file://rpc-list.jsonor absolute/relative path
// Shape A: simple array of URLs
["https://node1.demos.sh", "https://node2.demos.sh"]
// Shape B: objects with metadata (preferred)
[
{ "url": "https://node1.demos.sh", "name": "primary-eu", "weight": 2, "tags": ["mainnet"] },
{ "url": "https://node2.demos.sh", "name": "backup-us", "weight": 1, "tags": ["mainnet"] }
]bun run src/index.ts serve # Start the HTTP server (default)
bun run src/index.ts probe # One-shot probe, print table, exit
bun run src/index.ts list # Print fetched RPC list, exit
bun run src/index.ts watch # Live TUI-style refresh (ANSI)
bun run src/index.ts best # Print URL of currently-best RPC, exitCLI flags: --source-url <url>, --port <n>, --host <addr>, --strategy <name>, --snapshot <path>, --config <path>
| Method | Path | Description |
|---|---|---|
GET |
/health |
Service health + uptime + endpoint counts + source status |
GET |
/status |
Full snapshot: all endpoints with scores, health, windows |
GET |
/status/:name |
Single endpoint detail including rolling history |
GET |
/metrics |
Prometheus metrics (see below) |
POST |
/refresh |
Force re-fetch of RPC list and immediate probe cycle. Requires Authorization: Bearer <token> if auth.refreshToken is configured. Returns 404 if not configured. |
ANY |
/rpc/* |
Meta RPC forwarder — strips /rpc prefix and forwards to the chosen upstream using the configured strategy |
GET |
/openapi.json |
OpenAPI 3.1.0 specification |
GET |
/docs |
Interactive API documentation (Scalar UI) |
Point your Demos SDK or any JSON-RPC client at http://<omnipoint-host>:<port>/rpc instead of the raw node URL. OmniPoint handles routing transparently.
Example with curl:
curl -X POST http://localhost:8080/rpc \
-H "Content-Type: application/json" \
-d '{"method":"nodeCall","params":[{"type":"nodeCall","message":"getPeerIdentity","sender":null,"receiver":null,"timestamp":null,"data":{},"extra":""}]}'Routes every request to the highest-scoring healthy endpoint. Score is computed as:
score = (handshake_ok ? 1 : 0) * 0.5
+ (recent_ping_success_ratio) * 0.4
- (ema_latency_ms / 1000) * 0.1
Stale endpoints are excluded unless allowStale is true.
Weighted round-robin across healthy endpoints. Honors the weight field from the source list (default 1). Stale endpoints are excluded unless allowStale is true.
Uses best for the first attempt. If the upstream returns a 5xx or times out, automatically retries the next-best endpoint up to maxRetries times. If all retries fail, returns 502 Bad Gateway with details.
All metrics are prefixed with omnipoint_.
| Metric | Type | Labels | Description |
|---|---|---|---|
omnipoint_uptime_seconds |
counter | — | Service uptime |
omnipoint_source_last_fetch_timestamp |
gauge | — | Unix timestamp of last successful source fetch |
omnipoint_source_error |
gauge | — | 1 if last source fetch failed |
omnipoint_endpoints_total |
gauge | — | Total configured endpoints |
omnipoint_endpoints_healthy |
gauge | — | Number of healthy endpoints |
omnipoint_endpoints_stale |
gauge | — | Number of stale endpoints |
omnipoint_probe_latency_ms |
histogram | endpoint, kind |
Probe latency (ping or handshake) |
omnipoint_probe_success_total |
counter | endpoint, kind |
Successful probes |
omnipoint_probe_failure_total |
counter | endpoint, kind, reason |
Failed probes by reason |
omnipoint_endpoint_healthy |
gauge | endpoint |
1 if healthy, 0 otherwise |
omnipoint_endpoint_block_height |
gauge | endpoint |
Latest known block height |
omnipoint_forward_requests_total |
counter | endpoint, result |
Forwarded requests |
omnipoint_forward_errors_total |
counter | endpoint |
Forward errors |
omnipoint_forward_latency_ms |
histogram | endpoint |
Forward latency |
+---------------+ +-------------------+ +------------------+
| SourceFetcher | ----> | EndpointStore | <---- | ProbeScheduler |
| (gist/URL/ | | (in-memory state | | (ping + handshake|
| local file) | | rolling windows) | | on intervals) |
+---------------+ +-------------------+ +------------------+
^
|
+---------------+
| Scorer |
| (health score |
| + stale flag)|
+---------------+
|
+-------------------+-------------------+
| | |
+-----------------+ +-----------------+ +------------------+
| /status API | | /metrics | | /rpc/* Forwarder|
+-----------------+ +-----------------+ +------------------+
|
v
upstream Demos RPC
- Runtime: Bun
- Language: TypeScript
- HTTP server:
Bun.serve - No external database — in-memory state with optional JSON snapshot
Private