Add-ons & Sidecars
dvb-WarpPool is designed to be extended from the outside — a clean, stable contract that lets you (or a commercial vendor) build extra functionality without forking the core.
The sidecar model (and the AGPL boundary)
dvb-WarpPool is licensed under AGPL-3.0-or-later (see LICENSE). The single most important rule for an add-on:
An add-on must be a separate process (a "sidecar") that talks to the pool over its HTTP API / SSE — never an in-process Rust crate linked into the daemon.
Why this matters:
- A crate compiled into the AGPL daemon becomes a derived work → it is covered by the AGPL and its source must be offered to users.
- A separate process that only consumes the pool's public API/SSE is an independent work. You can license it however you like — including keeping a proprietary commercial add-on closed-source. (For commercial use of the core itself, see COMMERCIAL-LICENSE.md.)
This is not a theoretical pattern — two sidecars already ship with the project:
- the Stratum V1↔V2 translator (
dvb-warppool-translator), and - the Solar bridge (a small Python service the pool polls over HTTP; see
Configuration Reference →
[solar],kind = "generic_http").
Dock points (the extension contract)
A sidecar has four ways to integrate:
- Read API —
GET /api/overview,/api/workers,/api/blocks,/api/hashrate,/api/best-shares,/api/version. These are public (no auth), return JSON, and are the stable surface (see below). - Live events (pull) —
GET /api/eventsis a Server-Sent-Events stream. Event types:block_found,new_job,shares_accepted,health_snapshot,profile_switched,update_available(plusheartbeat). Subscribe instead of polling when you need to react in real time (e.g. a billing or notification add-on reacting toblock_found). - Live events (push) — outbound webhooks. Configure one or more
[[webhooks]]and the poolPOSTs a JSON payload to your URL when a matching event fires. This is the inverse of the SSE stream: instead of holding a connection open, your sidecar exposes an HTTP endpoint and the pool calls it. See the comparison below and Configuration Reference →[[webhooks]]. - Inbound data — the
generic_httpprovider pattern: your sidecar exposes a small JSON endpoint and the pool polls it. This is how the Solar bridge feeds live PV data into/api/energy. A clean way to push into the pool without touching its code. - Authenticated actions — for anything beyond public reads, use an API token (Admin → API tokens). Prefer a read-only token for least privilege (next section).
Webhook (push) vs SSE (pull) — which to use
Both carry the same event types; pick by how your sidecar wants to receive them:
Webhook ([[webhooks]]) | SSE (GET /api/events) | |
|---|---|---|
| Direction | Pool → you (pool initiates the POST) | You → pool (you hold the stream open) |
| Your side runs | an HTTP server (endpoint) | an HTTP client (long-lived GET) |
| Best for | serverless / no-code / cloud functions; rare low-frequency events | always-on local sidecars; high-frequency / every event |
| If your side is down | event is dropped after retries (no buffer) | you simply reconnect and resume |
| Auth model | optional HMAC signature (secret_env) | API token header (or none for public reads) |
| High-frequency events | opt-in; mind the POST-storm warning | fine — it's a single stream |
Rule of thumb: a cloud function or no-code automation that can't keep a
connection open → webhook. A persistent local process that wants every share
update → SSE. The webhook payload is
{ "event", "ts", "data" }, signed with X-WarpPool-Signature when a secret is
configured (verify it like a GitHub webhook). Loopback/LAN webhook targets are
allowed (there is no SSRF block — the URL is operator-trusted config), so a local
sidecar can receive POSTs at http://127.0.0.1:….
Authentication & least privilege
API tokens carry a scope:
admin(default) — full access, including mutatingPOST/PUT/DELETE.read— GET/HEAD/OPTIONS only. Any mutating request is rejected with403 Forbidden.
Give an add-on a read token, not an admin one: even if the add-on (or
its host) is compromised, it cannot reconfigure the pool. Create one with:
curl -X POST https://your-pool/api/admin/tokens \
-H "Authorization: Bearer <admin-token-or-session>" \
-H "content-type: application/json" \
-d '{"name": "my-sidecar", "scope": "read"}'
The raw token is returned once — store it in the sidecar's secret store.
API stability
- Stable surface: the public read endpoints listed above, the
/api/eventsSSE stream, and/api/version. Build add-ons against these. - Versioning: the project follows SemVer. Breaking changes to the stable surface happen only on a MAJOR version bump. New fields may be added within a minor release — parse defensively (ignore unknown fields).
- Check the running version at
GET /api/version→{ current, latest, update_available }. Pin or feature-detect againstcurrentif you depend on a recent field. - Admin / mutating endpoints (
/api/admin/*) are operator tooling and may evolve faster; if you depend on them, pin to a specific release.
A minimal read-only sidecar (sketch)
import requests, sseclient # pip install requests sseclient-py
POOL = "http://127.0.0.1:18334"
# Option A — poll a snapshot every 10s
ov = requests.get(f"{POOL}/api/overview").json()
print(ov["pool"]["best_share_difficulty"], ov["active_miners"])
# Option B — react to live events
for ev in sseclient.SSEClient(f"{POOL}/api/events"):
if ev.event == "block_found":
... # do your thing — notify, bill, log
No token is needed for the public reads above; add an
Authorization: Bearer <read-token> header only if you later consume an
authenticated endpoint.