Overview
The StreamL4BookUpdates stream delivers typed order-level book changes as NEW, UPDATE, and REMOVE diffs for one or more coins. It gives clients per-order L4 movement without consuming full L4 book snapshots every block, and without parsing JSON-encoded payloads: every diff is a strongly typed protobuf message.
gRPC Service: OrderBookStreaming
gRPC Method: StreamL4BookUpdates
Update Model: Full reset snapshot on subscribe, then typed per-order diffs per block
How It Works
- On subscribe: The first message has
snapshot: trueand contains every resting order as aL4_ORDER_DIFF_TYPE_NEWdiff, a full reset snapshot of the book - Per block thereafter: Each
L4BookUpdatesUpdatecontains typed diffs for orders that changed:NEW(order entered the book),UPDATE(order size changed),REMOVE(order left the book,szis omitted) - Apply diffs to a local order map keyed by
oidto maintain the current book
Unlike StreamL4Book, which sends diffs as a JSON-encoded data string, every field here is a typed protobuf field, so no secondary JSON parsing is required. A single stream can cover multiple coins (or all coins with an empty coins list).
Data Structure
Each L4BookUpdatesUpdate message contains the typed diffs for one block:
{
"time": 1781109115476,
"height": 586405189,
"diffs": [
{ "diff_type": "L4_ORDER_DIFF_TYPE_NEW", "coin": "ETH", "oid": 54764586686,
"user": "0x768484F7e2EBB675C57838366C02Ae99ba2A9B08", "side": "B", "px": "1649.1", "sz": "1.022" }
]
}
Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| coins | repeated string | No | List of symbols to subscribe to (e.g., "BTC", "ETH"). Empty means all coins |
Coin Naming
The coins parameter follows Hyperliquid's naming convention, which unambiguously distinguishes perpetuals from spot:
- Perpetuals: Human-readable names, such as
"BTC","ETH","HYPE","SOL" - Spot tokens:
@{index}format, such as"@1","@107","@142","@166" - Outcome markets (HIP-4):
#Nformat. Each outcome has two#Ncoins, one per side (Yes and No). Multi-price question markets consist of multiple grouped outcomes (price buckets plus a fallback), each with its own Yes/No coin pair. Use theoutcomeMetaendpoint to map coin indices to market names and sides. - Exception:
"PURR/USDC"is the only spot coin with a readable name
There is no overlap between formats. "BTC" always refers to the BTC perpetual; spot BTC is "@142". To discover @index mappings for spot tokens, query the meta or spotMeta info endpoints.
Response Fields
L4BookUpdatesUpdate
| Field | Type | Description |
|---|---|---|
| time | uint64 | Block timestamp in milliseconds |
| height | uint64 | Block height |
| snapshot | bool | True when the diffs carry a full reset snapshot of all resting orders (sent on subscribe) |
| diffs | L4OrderDiff[] | Typed per-order diffs for this block |
L4OrderDiff
| Field | Type | Description |
|---|---|---|
| diff_type | L4OrderDiffType | L4_ORDER_DIFF_TYPE_NEW (order entered the book), L4_ORDER_DIFF_TYPE_UPDATE (order size changed), or L4_ORDER_DIFF_TYPE_REMOVE (order left the book) |
| coin | string | Symbol (e.g., "BTC", "ETH") |
| oid | uint64 | Unique order ID |
| user | string | Ethereum address of the order owner |
| side | string | "A" (Ask/Sell) or "B" (Bid/Buy) |
| px | string | Order price as a decimal string |
| sz | string | New/current order size for NEW and UPDATE diffs. Omitted on REMOVE |
Proto Definition
StreamL4BookUpdates is defined in orderbook.proto:
service OrderBookStreaming {
rpc StreamL4BookUpdates (L4BookUpdatesRequest) returns (stream L4BookUpdatesUpdate);
}
message L4BookUpdatesRequest {
repeated string coins = 1;
}
enum L4OrderDiffType {
L4_ORDER_DIFF_TYPE_UNSPECIFIED = 0;
L4_ORDER_DIFF_TYPE_NEW = 1;
L4_ORDER_DIFF_TYPE_UPDATE = 2;
L4_ORDER_DIFF_TYPE_REMOVE = 3;
}
message L4BookUpdatesUpdate {
uint64 time = 1;
uint64 height = 2;
repeated L4OrderDiff diffs = 3;
bool snapshot = 4;
}
message L4OrderDiff {
L4OrderDiffType diff_type = 1;
string coin = 2;
uint64 oid = 3;
string user = 4;
string side = 5;
string px = 6;
string sz = 7;
}
Example Updates
Initial Snapshot (first message, truncated)
The first message has snapshot: true and contains every resting order as a NEW diff:
{
"time": 1781109063794,
"height": 586404872,
"snapshot": true,
"diffs": [
{ "diff_type": "L4_ORDER_DIFF_TYPE_NEW", "coin": "BTC", "oid": 54764492622,
"user": "0x039442e06b482Bf01A1a65742cf061bD73A80f31", "side": "B", "px": "62963", "sz": "0.00009" },
{ "diff_type": "L4_ORDER_DIFF_TYPE_NEW", "coin": "BTC", "oid": 54764537698,
"user": "0x3643290CD5DE72b37d2f10A351Ce57c30d103969", "side": "B", "px": "62963", "sz": "0.00024" },
{ "diff_type": "L4_ORDER_DIFF_TYPE_NEW", "coin": "BTC", "oid": 54764482173,
"user": "0x4547B9E33F07965711f67d0bE2423939A4E33aF5", "side": "B", "px": "62955", "sz": "0.00015" }
]
}
Per-Block Update with Removals
sz is omitted on REMOVE diffs:
{
"time": 1781109083796,
"height": 586404997,
"diffs": [
{ "diff_type": "L4_ORDER_DIFF_TYPE_REMOVE", "coin": "BTC", "oid": 54764554671,
"user": "0xc64cc00B46101bd40aA1C3121195E85c0B0918d8", "side": "B", "px": "62389.0" },
{ "diff_type": "L4_ORDER_DIFF_TYPE_REMOVE", "coin": "BTC", "oid": 54764557274,
"user": "0xc64cc00B46101bd40aA1C3121195E85c0B0918d8", "side": "B", "px": "62421.0" }
]
}
New Order and Size Update
A new order entering the book:
{
"time": 1781109115476,
"height": 586405189,
"diffs": [
{ "diff_type": "L4_ORDER_DIFF_TYPE_NEW", "coin": "ETH", "oid": 54764586686,
"user": "0x768484F7e2EBB675C57838366C02Ae99ba2A9B08", "side": "B", "px": "1649.1", "sz": "1.022" }
]
}
A size change on a resting order (sz is the new current size):
{ "diff_type": "L4_ORDER_DIFF_TYPE_UPDATE", "coin": "ETH", "oid": 54764551695,
"user": "0x4547B9E33F07965711f67d0bE2423939A4E33aF5", "side": "B", "px": "1702.9", "sz": "0.0032" }
API Usage
gRPC Streaming
// Single coin
const request = {
coins: ['BTC']
};
// Multiple coins in one stream
const multiRequest = {
coins: ['BTC', 'ETH']
};
// All coins
const allCoinsRequest = {
coins: []
};
StreamL4BookUpdates is only available via the gRPC Streaming API (OrderBookStreaming service). It is not available through JSON-RPC or WebSocket.
Comparison: L4 Book Updates vs L4 Book vs Book Updates Dataset
| Feature | StreamL4BookUpdates | StreamL4Book | BOOK_UPDATES |
|---|---|---|---|
| Granularity | Individual orders, typed diffs | Individual orders | Individual order-level diffs |
| Diff encoding | Typed protobuf (NEW/UPDATE/REMOVE) | JSON-encoded data string | JSON objects |
| Includes current state | Yes (reset snapshot as NEW diffs) | Yes (initial snapshot) | No (forward-only) |
| Needs REST bootstrap | No | No | Yes |
| Coins per stream | Multiple (or all coins) | One coin per stream | Filterable |
| Per-order details | User, oid, side, px, sz | User, oid, triggers, timestamps, tif | User, oid, size |
| Best for | Per-order book tracking without JSON parsing | Full order detail (triggers, tif, cloid) | Customers who only need book updates |
Important Notes
- Reset snapshot on subscribe and reconnect: The first message has
snapshot: truewith all resting orders asNEWdiffs. Clear any local state and rebuild from it REMOVEomitssz: Remove diffs identify the order byoid(withcoin,user,side,px) but carry no size. Delete the order from your local map- Typed alternative to StreamL4Book: If you need trigger info, timestamps,
tif, orcloidper order, useStreamL4Book; if you need lean typed per-order movement, use this stream - zstd compression recommended: Enable zstd compression on your gRPC channel to reduce bandwidth, especially when streaming multiple coins
Related Streams
- StreamL4Book - Full order book at order granularity with snapshot + JSON-encoded diffs
- StreamL2BookDiff - Incremental L2 price-level changes with sequence numbers
- Orders - Order lifecycle events (open, filled, canceled, etc.)