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 { // On Ultra, start_block can request retained replay from a height within the last 6 hours before continuing 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. On Ultra, set start_block to a retained height within the last 6 hours before going live. Streams are long-lived; reconnect with backoff.
Message fields
Raw financial fields typed string preserve their decimal representation from the node. Store them as strings or arbitrary-precision decimals. Calculated fields, including aggregated book sizes, are identified by their field notes.
Server-side filters
- coins[]: one or more markets
- wallets[]: one or more trader addresses
- market_type: ALL / SPOT / PERP
- recent replay up to 6 hours
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; Ultra can replay from a recent block within the last 6 hours.
- 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. On Ultra, request recent retained history with 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 & backfillOn Ultra, start from a recent block within 6 hours, then continue live.
- TWAP algosParent-order context for fills tagged with a twap_id.
- Request a historical exportRequest captured fills for a confirmed dataset and date range.