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/StreamSwapsOmit 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
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.
Bmeans a buyer crossed the book. Pair it withcrossedto classify each counterparty as maker or taker. - Fees can be negative. A maker rebate arrives as a negative
feewithis_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
liquidationobject 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/StreamSwapsTrack 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
- Filteringcoins, wallets, and market_type request shapes for every stream family.
- Replay & backfillStart from a past block height, then continue live without a gap.
- TWAP algosParent-order context for fills tagged with a twap_id.
- Historical query APIThe same fills, archived with the same schema. Backtest what you stream.