Stream live blockchain data over WebSocket

Decoded blocks, swaps, transfers, and any Substreams DatabaseChanges output — delivered in seconds. Subscribe to one or many streams across SVM, EVM, TVM, HyperLiquid, Polymarket, and any chain Pinax supports. Resume exactly where you left off after a disconnect.

Real-time, multi-chain

One generic decoder for every db_out-style Substreams package. Solana, Ethereum, Base, Arbitrum, TVM, HyperLiquid, Polymarket — same URL pattern.

Reorg-aware

Every payload carries block_num and module_hash. undo messages fire on chain reorganizations so subscribers can roll back deterministically.

Use the Try it button in the bottom-right corner to open a full-screen WebSocket client — connect, subscribe, and watch blocks roll in without leaving this page.

Available streams

Live snapshot of every stream this server is configured to broadcast. Click a tile to copy its network@stream selector. Data sourced from GET /streams.

Loading from /streams

URL modes

Stream selectors are <network>@<stream>. * is a wildcard on either side. Two ways to connect:

URLModeEnvelope
WS/ws/<a> Single, path raw payload
WS/ws/<a>/<b>/... Multi, path wrapped
WS/stream?streams=<a>/<b>/... Combined, query wrapped

When wrapped, every payload is delivered as:

{ "stream": "<network>@<table>", "data": <raw payload> }

Bare /ws with no streams returns HTTP 400. Use /ws/*@* to opt into every stream explicitly. See the HTTP group in the sidebar for non-WebSocket endpoints (/streams, /version, /healthz, /SKILL.md, /llms.txt).

Connected message

Sent once per connection. Lists every configured stream and echoes this connection's resolved subscriptions.

{
  "type": "session",
  "status": "connected",
  "client_id": 1,
  "streams": [
    {
      "network": "solana-mainnet",
      "module": "db_out",
      "manifest": "https://.../svm-dex-v0.5.1.spkg",
      "module_hash": "bd388f2e39f5dcc237cfbdb8d6c96d9e5678c797",
      "package_name": "svm_dex",
      "package_version": "v0.5.1",
      "tables": ["swaps"]
    }
  ],
  "subscriptions": ["solana-mainnet@swaps"],
  "wrap_envelope": false
}

Block payload

One payload per (network, table) group per block. A spkg that emits both swaps and transfers in one block produces two per-table broadcasts.

{
  "network":     "solana-mainnet",
  "table":       "swaps",
  "block_num":   350000000,
  "block_hash":  "Gsk6...",
  "timestamp":   "2026-05-13 17:00:00",
  "module_hash": "bd388f2e...",
  "events": [
    {
      "input_amount":  "1287000000",
      "input_mint":    "So11111111111111111111111111111111111111112",
      "output_amount": "6848381008732",
      "output_mint":   "13muFY...",
      "protocol":      "raydium_cpmm",
      "user":          "F2MUE..."
    }
  ]
}

Field rules:

Stream lifecycle

Same connection, distinguished by "type": "stream". Filtered by subscriptions just like block payloads.

{ "type": "stream", "status": "started",   ... }
{ "type": "stream", "status": "completed", ... }
{ "type": "stream", "status": "error",     ..., "message": "..." }
{ "type": "stream", "status": "fatal",     ..., "message": "..." }
{ "type": "stream", "status": "undo",      ..., "last_valid_block": 350000000 }

undo fires on chain reorganizations. Roll back any state materialized past last_valid_block.

SUBSCRIBE

Add to the per-connection subscription set. Idempotent.

// request
{ "method": "SUBSCRIBE",
  "params": ["solana-mainnet@swaps", "ethereum-mainnet@transfers"],
  "id": 1 }

// reply
{ "result": null, "id": 1 }

Wildcards accepted (["*@swaps"]). A single bad selector rejects the whole command — existing set unchanged. The wrap_envelope mode is fixed at upgrade time and is not affected by SUBSCRIBE.

UNSUBSCRIBE

Remove from the subscription set. Silent on unknown selectors.

// request
{ "method": "UNSUBSCRIBE",
  "params": ["ethereum-mainnet@transfers"],
  "id": 2 }

// reply
{ "result": null, "id": 2 }

To remove a wildcard subscription, pass the exact wildcard form (*@swaps removes the wildcard entry, not the individual streams it currently matches).

LIST_SUBSCRIPTIONS

Inspect the current subscription set.

// request
{ "method": "LIST_SUBSCRIPTIONS", "id": 3 }

// reply
{ "result": ["solana-mainnet@swaps", "ethereum-mainnet@*"], "id": 3 }

Insertion order preserved. Wildcards returned verbatim, not expanded.

SET_FILTER

Drop non-matching events from the wire for the given selector. params[1] is an SQE expression string (the same language as Firehose substreams run -t): field:value string equality (case-insensitive), bare value matches any column, combined with || (OR), && / whitespace (AND), ! (NOT), and ( ) grouping. Missing field on an event is a miss. Wildcard selectors are accepted and applied to every matching channel. SET_FILTER replaces the filter for that selector (not accumulate — combine with || in one expression) — read the {"result"|"error"} reply, and use LIST_FILTERS to confirm one is active.

// request
{ "method": "SET_FILTER",
  "params": ["solana-mainnet@swaps", "protocol:raydium_cpmm && user:F2MUE…"],
  "id": 1 }

// reply (accept)
{ "result": null, "id": 1 }
// reply (reject — previous filter left unchanged)
{ "error": "…", "id": 1 }

Replaces any existing filter for that selector. Top-level fields (block_num, network, module_hash) are not filterable — only columns inside events[*].

CLEAR_FILTER

Drop the filter for the listed selectors.

// request
{ "method": "CLEAR_FILTER", "params": ["solana-mainnet@swaps"], "id": 2 }

// reply
{ "result": null, "id": 2 }

Silently ignores selectors without a filter.

LIST_FILTERS

Inspect the current filter map for this connection.

// request
{ "method": "LIST_FILTERS", "id": 3 }

// reply
{ "result": { "solana-mainnet@swaps": "protocol:raydium_cpmm" }, "id": 3 }

Returns the selector → expression-string map. An empty {} means no filter is active.

Event filters

Per-subscription filters drop events from events[] before delivery. The filter is an SQE expression string (StreamingFast Substreams Query Expression — the same language as Firehose substreams run -t). Pass ?filter=<url-encoded-expr> (alias ?sqe=) on connect, or use the live SET_FILTER / CLEAR_FILTER / LIST_FILTERS commands.

# ?filter=protocol:raydium_cpmm
ws://host/ws/solana-mainnet@swaps?filter=protocol%3Araydium_cpmm

Semantics:

Bounds — the values cap is the total number of terms across the whole expression, not per field; over-cap (or a parse error / non-string) rejects the filter and leaves the previous one in place:

SUBSTREAMS_WEBSOCKET_MAX_FILTER_FIELDS=16   # max distinct field names
SUBSTREAMS_WEBSOCKET_MAX_FILTER_VALUES=512  # max total terms in the expression

Common filter shapes per stream — server does not enforce these, just operator-friendly examples matching commonly-present columns:

# SVM swaps — Raydium CPMM by user
protocol:raydium_cpmm && user:F2MUEfN1HG5mC5EiUoxhjjc7HpKi4QQnzvipnbGx6Av8

# SVM swaps — WSOL on either leg
input_mint:So11111111111111111111111111111111111111112 || output_mint:So11111111111111111111111111111111111111112

# SVM SPL transfers — a wallet as source OR destination
source:<wallet> || destination:<wallet>

# SVM system transfers — system program touching a wallet in any role
program_id:11111111111111111111111111111111 && (source:<wallet> || destination:<wallet> || fee_payer:<wallet>)

Full spec: docs/filters.md.

Reconnects (live-only)

This is a live-only feed: blocks are delivered as they arrive and there is no server-side buffer of past blocks. On reconnect you simply resume the live stream — there is no catch-up.

The query parameters ?from_timestamp= and ?from_block= are not supported and return HTTP 400 at the WebSocket upgrade. To backfill history, use Substreams directly — it resumes natively from any block, cursor, or timestamp and is the right tool for replay.

Cursor handling stays internal to the server: on restart each stream resumes from its persisted cursor so the live feed continues without operator action. The cursor is not exposed to clients.

Heartbeats

The server sends WebSocket ping frames every SUBSTREAMS_WEBSOCKET_HEARTBEAT_INTERVAL_SECS seconds (default 180s). Standard WebSocket clients pong automatically. The server closes connections that do not pong within SUBSTREAMS_WEBSOCKET_HEARTBEAT_TIMEOUT_SECS seconds (default 600s).

Limits