Substreams WebSockets service — stream decoded blocks, swaps & transfers in real time.
    Read-only preview
    You're browsing the public WebSockets reference. Sign in to open a live stream with your team's API key.

    QuickstartBeta

    Open a WebSocket, subscribe to a network@table stream, and start receiving decoded blocks in real time. Pick a stream below — then drop the URL into wscat, Node, or the browser.

    Endpoint
    WS
    1# npm i -g wscat
    2wscat -c "wss://ws.pinax.network/ws/solana@swaps?token=<YOUR_JWT>"
    No API key loaded — samples show a <YOUR_JWT> placeholder. Open Try it to paste a token and stream live.
    Available streams (loading…)live from GET /streams

    Filters

    Filters drop non-matching events[*] rows server-side, before they ever reach the wire — so they cut both bandwidth and billable messages (a block whose every event is filtered out produces no message for you). Filtering is the main lever for turning a firehose like mainnet@erc20_transfers into just the transfers you care about.

    SQE expressions

    A filter is an SQE expression string — StreamingFast Substreams Query Expression, the same language as Firehose substreams run -t:

    • field:value — case-insensitive string equality on an events[*] column.
    • bare value (no field:) — matches when anycolumn equals it. Great for "this wallet in any role".
    • operators || (OR), && or whitespace (AND), ! (NOT), ( ) (grouping). && binds tighter than ||; quote values containing spaces or operators.
    • only events[*] columns are filtered — top-level block_num / network / module_hash are not.

    Two ways to set a filter

    • At connect time ?filter=<url-encoded-expr> (alias ?sqe=) on the upgrade URL. An invalid expression is rejected with HTTP 400 at the upgrade.
    • Live — the SET_FILTER / CLEAR_FILTER / LIST_FILTERS commands, scoped per network@table selector. SET_FILTER replaces the filter for a selector — combine conditions with || in one expression.
    wss://ws.pinax.network/ws/solana@swaps?filter=protocol%3Araydium_cpmm
    # the filter param is a URL-encoded SQE expression — here: protocol:raydium_cpmm

    Semantics

    • Case-insensitive equality. A checksummed or lowercased EVM address both match; supply the exact value for case-significant keys like Solana base58.
    • Missing field = miss.An event without the named column doesn't match that term.
    • Event-level.You receive only the matching events; a block whose every event is filtered out isn't sent to you at all.
    • Wildcard selectors (*@swaps, *@*) are filterable too — a filter applies to every (network, table) the selector matches.
    Find the column names. Filter keys must exactly match the columns inside a events[*] row. Connect, read one block payload, and filter on whatever keys you see — the server is schema-agnostic and never validates column names.

    Example — Ethereum ERC-20 transfers (mainnet@erc20_transfers)

    Useful columns to filter a transfer row on:

    log_addressstring

    The token contract that emitted the Transfer — filter this to pin a single token (e.g. USDC).

    from, tostring

    Sender / recipient addresses — watch a wallet's inflows or outflows.

    Match a token by its log_addresscontract, not a symbol — the stream carries addresses, and amounts aren't a useful equality target.

    Only USDC transfers landing on one wallet:

    SET_FILTER
    {
      "method": "SET_FILTER",
      "params": [
        "mainnet@erc20_transfers",
        "log_address:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 && to:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
      ],
      "id": 1
    }

    Any of several stablecoins (by contract) sent from one wallet — OR the contracts, AND the sender:

    SET_FILTER
    {
      "method": "SET_FILTER",
      "params": [
        "mainnet@erc20_transfers",
        "(log_address:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 || log_address:0xdAC17F958D2ee523a2206206994597C13D831ec7 || log_address:0x6B175474E89094C44Da98b954EedeAC495271d0F) && from:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
      ],
      "id": 2
    }

    Example — Solana swaps (solana@swaps)

    Common columns on a swap row:

    protocolstring

    DEX protocol — e.g. raydium_cpmm, raydium_clmm, pumpfun_amm.

    input_mint, output_mintstring

    Mint addresses of the tokens swapped. Wrapped SOL is So1111…1112.

    user, fee_payerstring

    Swapper wallet / transaction fee payer.

    amm, program_idstring

    Pool / on-chain program addresses.

    Raydium CPMM swaps that spend wrapped SOL, from one wallet:

    SET_FILTER
    {
      "method": "SET_FILTER",
      "params": [
        "solana@swaps",
        "protocol:raydium_cpmm && input_mint:So11111111111111111111111111111111111111112 && user:F2MUEfN1HG5mC5EiUoxhjjc7HpKi4QQnzvipnbGx6Av8"
      ],
      "id": 3
    }

    Swaps on either of two protocols (OR), across all wallets:

    SET_FILTER
    {
      "method": "SET_FILTER",
      "params": [
        "solana@swaps",
        "protocol:raydium_cpmm || protocol:pumpfun_amm"
      ],
      "id": 4
    }
    Limits. Max terms and distinct fields per filter are server-configured (SUBSTREAMS_WEBSOCKET_MAX_FILTER_VALUES / …_MAX_FILTER_FIELDS, defaults 512 / 16). An expression over a cap, with a parse error, or that isn't a string returns an error reply and leaves the previous filter unchanged — the socket stays open. See SET_FILTER for the live-command envelope.