Public, anonymous backend for
network-dashboard.
One Fly app per env; no DB; no auth.
Streams a live view of the Moonlight network over WebSocket. Cold-start
self-syncs from council-platform's public endpoints and Soroban RPC; no
persistence.
Seven event kinds drive the dashboard (see design sketch):
| Kind | Chain source |
|---|---|
council_formed |
Channel Auth contract_initialized event |
provider_added |
Channel Auth provider_added event |
provider_removed |
Channel Auth provider_removed event |
asset_registered |
New assetContractId appearing in council-platform/public/channels |
channel_deposit |
SAC transfer event TO a known channel address |
channel_settlement |
SAC transfer event FROM a known channel address |
channel_bundle |
SAC fee event whose payer is a registered PP |
| Path | Description |
|---|---|
GET /api/v1/health |
Liveness — {status,service,version} |
GET /api/v1/network/ws |
Public WebSocket. Subprotocol moonlight.network.v2. No auth. |
Server → client, JSON-encoded:
No client → server frames. Clients reconnect rather than ping; on reconnect they receive a fresh snapshot.
council-platform /public/* ─┐
├─► in-memory state ─► WS clients
Soroban /getEvents (poll) ──┘ ▲
│
minute topology re-sync + minute window sweep
- Cold start: fetch
council-platform/api/v1/public/councils(one call carries councils + channels + providers + jurisdictions), walk trailing 24h on the subscription set viarpc.getEvents, seed the rolling counter window and the activity-feed ring buffer. - Subscriptions: Channel Auth (council) contracts are watched by contractId.
SAC contracts are watched by TOPIC only —
transferpatterns pinned to known channel addresses (deposit/settlement) andfeepatterns pinned to registered PP keys (bundles). The mainnet XLM SAC emits hundreds of events per ledger; an unfiltered contractId subscription drowns in them. - Forward poll: 5s poll over the subscription set, paged via the RPC event cursor so consumption is gap-free even mid-ledger; the shared poll position only advances across fully-consumed pages (re-read overlap is deduped by event id).
- New-council discovery: network-wide
contract_initializedpoll feeds the adoption pipeline (topology refresh + historical back-fill per adopted council). - Minute topology re-sync: refresh topology from council-platform — the backstop for DB-only registrations (channels, jurisdictions, labels), which emit no chain event.
- Minute sweep: drop window entries older than 24h.
cp .env.example .env
# Edit .env — set COUNCIL_PLATFORM_URL to your running council-platform.
deno task servelocal-dev/ notes: the canonical local-dev parallel-stack guide applies here.
Set PORT=<your-port> to avoid collisions with other services on the same host.
| Env | Required | Description |
|---|---|---|
PORT |
no (default 8080) | HTTP port |
MODE |
no (default development) |
development relaxes CORS to localhost |
LOG_LEVEL |
no (default INFO) |
FATAL/ERROR/WARN/INFO/DEBUG/TRACE |
NETWORK |
yes | testnet | mainnet | local |
STELLAR_RPC_URL |
yes | Soroban RPC URL |
COUNCIL_PLATFORM_URL |
yes | URL of council-platform (no trailing /api/v1) |
ALLOWED_ORIGINS |
recommended | Comma-separated CORS allowlist |
| Env | Fly app | Config |
|---|---|---|
| testnet | moonlight-beta-network-dashboard-platform |
fly.testnet.toml |
| mainnet | moonlight-mainnet-network-dashboard |
fly.mainnet.toml |
Tagged main pushes trigger .github/workflows/deploy-{testnet,mainnet}.yml.
deno.json version is the source of truth — bump it on every PR.