Streams referenceNative schema

Trades / Swaps

Every executed fill, with the trader's wallet, realized PnL, fees, and maker/taker role.

The richest feed: full wallet attribution on every fill. The flat swap schema (Solana-swap-compatible) is the recommended surface; the same fills are also available QuickNode-compatibly via StreamData with stream_type TRADES. Around 6.5M fills a day across 462 markets.

Method
hyperliquid_swaps.v1.SwapStreaming/StreamSwaps
Also via
hyperliquid.Streaming/StreamData (stream_type: TRADES)
Endpoint
stream.hyperliquidrpc.com:443 · gRPC over TLS (HTTP/2)
Auth
API key in x-api-key metadata
Volume
~6.5M fills/day across all markets (~75/s average; bursts are much higher). Filter server-side to pay only for what you subscribe to.

When to use it

Reach for this stream whenever you care about who traded, not just what printed. Every fill carries the trader's wallet, position context (dir, start_position), realized PnL, and the fee actually paid, so copy-trading engines, flow dashboards, and PnL leaderboards work off this one feed with no enrichment step.

It is also the right tape for price-driven bots that need more than a ticker: side plus crossed tells you the aggressor, and notional saves you a multiplication you would otherwise do in floats. If you only need the BTC price, use the cheaper btc-price ticker instead.

Method

// stream.hyperliquidrpc.com:443 — gRPC over TLS (HTTP/2)// auth: API key in x-api-key metadata · server reflection enabledpackage hyperliquid_swaps.v1; service SwapStreaming {  // start_block > 0 replays from that height first, then continues live.  rpc StreamSwaps (SwapRequest) returns (stream Swap);}

The same fills are also available QuickNode-compatibly via hyperliquid.Streaming/StreamData with stream_type: TRADES, so existing QuickNode clients work unchanged. The flat swap schema above is the recommended surface for new integrations.

Subscribe

# Streams JSON-encoded messages until you disconnect.grpcurl -H 'x-api-key: YOUR_KEY' \  -d '{"coins":["BTC"],"market_type":"ALL"}' \  stream.hyperliquidrpc.com:443 \  hyperliquid_swaps.v1.SwapStreaming/StreamSwaps

Omit coins for every market, add wallets to follow specific traders, and set start_block to backfill before going live. Streams are long-lived; reconnect with backoff.

Message fields

FieldTypeDescription
coinstringMarket symbol (perp, spot @N, or HIP-3 dex:SYMBOL).
pxstringFill price.Byte-exact decimal string, never a float.
szstringFill size in base units.Byte-exact decimal string.
notionalstringFill notional in USDC.Byte-exact decimal string.
sidestringAggressor side: B (buy) or A (sell).
userstringTrader's wallet address. Full attribution on every fill.
dirstringPosition context: Open Long, Close Short, etc.
start_positionstringTrader's position before the fill.Byte-exact decimal string.
closed_pnlstringRealized PnL closed by this fill.Byte-exact decimal string.
feestringFee paid (negative when rebated).Pair with is_rebate.
is_rebateboolTrue when the fee is a maker rebate.
crossedboolTrue if this side crossed the book (taker).
oid / cloid / tiduint64 / string / uint64Order id, client order id, and trade id.
twap_iduint64?Set when the fill belongs to a native TWAP execution.
liquidationobject?Liquidation details (liquidated user, mark price) when applicable.

Fields typed string that carry numbers are byte-exact decimal strings, verbatim from the node. Compare and store them as strings or arbitrary-precision decimals. Never parseFloat a price you intend to reconcile.

Server-side filters

  • coins[]: one or more markets
  • wallets[]: one or more trader addresses
  • market_type: ALL / SPOT / PERP
  • replay from a block height

Filters are applied on the server, so filtered-out messages never leave our edge. You only pay for what you subscribe to. Request shapes for every stream family are on Filtering; backfill semantics on Replay & backfill.

Semantics & gotchas

Pure event stream: one message per executed fill, in block order. No snapshot phase; replay from a past block height to backfill.

  • side is the aggressor side. B means a buyer crossed the book. Pair it with crossed to classify each counterparty as maker or taker.
  • Fees can be negative. A maker rebate arrives as a negative fee with is_rebate: true. Sum fees signed, or your cost model overstates.
  • TWAP fills are tagged, not separate. Fills that belong to a native TWAP carry a twap_id; join it against the TWAP stream for parent-order context.
  • Liquidations appear on the tape. Forced fills carry a liquidation object with the liquidated user and mark price, so tape-level liquidation detection needs no separate subscription.
  • There is no snapshot phase. The first message is simply the next fill. To start with history, replay from a block height. See Replay & backfill.

Sample message

{  "block_height": 612408133,  "block_time": "2026-06-09T08:14:02.418Z",  "coin": "BTC",  "px": "67012.0",  "sz": "0.14523",  "notional": "9732.15",  "side": "B",  "crossed": true,  "user": "0x7c4ee0c0c5d3ab91f7f0f3c2a3d6b18452e9a3f9",  "dir": "Close Short",  "start_position": "-0.42100",  "closed_pnl": "182.337041",  "fee": "2.433037",  "is_rebate": false,  "oid": 91834221107,  "cloid": "0x00000000000000000000000000a1b2c3",  "tid": 442191083327011,  "twap_id": null,  "liquidation": null}

~6.5M fills/day across all markets (~75/s average; bursts are much higher). Filter server-side to pay only for what you subscribe to.

Recipes

Follow a set of wallets

Pass wallets in the request and the server delivers only those traders' fills, across all 462+ markets. This is the core of a copy-trading engine: no client-side filtering, no wasted bandwidth.

grpcurl -H 'x-api-key: YOUR_KEY' \  -d '{"wallets":["0x7c4ee0c0c5d3ab91f7f0f3c2a3d6b18452e9a3f9"],"market_type":"PERP"}' \  stream.hyperliquidrpc.com:443 \  hyperliquid_swaps.v1.SwapStreaming/StreamSwaps

Track realized PnL per wallet

closed_pnl is the realized PnL closed by each fill and fee is what it cost. Accumulate both with a decimal library. The strings are byte-exact, so your ledger reconciles to the chain.

from collections import defaultdictfrom decimal import Decimal pnl = defaultdict(Decimal) for msg in stub.StreamSwaps(request, metadata=meta):    if msg.closed_pnl:        pnl[msg.user] += Decimal(msg.closed_pnl) - Decimal(msg.fee)

Alert on liquidation fills

Watch the liquidation field on the live tape and alert when a forced fill crosses your size threshold. Use notional directly rather than multiplying price by size yourself.

from decimal import Decimal for msg in stub.StreamSwaps(request, metadata=meta):    if msg.liquidation and Decimal(msg.notional) > Decimal("250000"):        alert(msg.coin, msg.notional, msg.user)

Related