l4Book streams the full L4 order book of one coin — every resting order with the wallet address behind it, plus the trigger orders (stops and take-profits) resting but not yet triggered — as one snapshot followed by a per-block stream of the diffs that apply to it. The server holds no book: the snapshot and every event are the node’s own bytes, split per coin and forwarded. The book lives in your process.
Who gets it
l4Book is included on Pro and above. Below that, a subscribe answers feed not in tier on the error channel. The connected frame lists l4Book in limits.feeds when your account may subscribe — read that rather than assuming from the plan table, since enterprise limits are resolved per account.
Subscribing
One argument,coin, and it is case-sensitive — one of only two argument values on this socket that are, the other being a candle’s interval. It must be the market exactly as the node spells it, builder-dex prefix and case included, because that is the only name the node’s book export answers to:
btc is not normalised to BTC: it names no market, and the subscription is dropped with unknown coin. Take the spelling from the node’s own market list. Every l4Book message echoes the coin as you sent it, and the subscription id hashes it as sent.
l4Book subscriptions count against the tier’s per-connection subscription cap like any other; there is no separate book-only limit. Going over answers too many subscriptions. Unsubscribing frees the slot immediately, and a book subscription that ends in an error frees it too.
The snapshot
Exactly one per subscription, first, sent only to the subscriber. It is fetched when you subscribe, after the subscribe ack.
Inside
data:
book_orders[0]is every bid,book_orders[1]every ask.untriggered_ordersis the resting trigger orders — stops and take-profits waiting on their trigger price.- Every entry in all three is a positional
[wallet, {order}]pair. Wallet addresses are always included. - The order object is passed through as the node writes it — nothing is added, renamed or reordered. Read its fields from the node’s export rather than from a mapping of ours; there is none.
H+1 with nothing lost and nothing repeated.
This is why the first message takes a while. One interval is about 10 seconds of chain, and attempt N waits for N intervals — so a cold subscription commonly waits 10–60 seconds before the snapshot lands, and up to about 100 seconds if all ten attempts are needed. That is expected, not a stall.
You pay it only on a cold coin. The buffer belongs to the feed group, not to you: if anyone else is already subscribed to that coin — or was within the last 60 seconds — the group is already buffered and your subscription passes straight through to the snapshot.
Two different errors end the wait, and they mean different things:
snapshot buffer did not fill— one attempt waited 3 minutes and the source never produced enough blocks. The feed has stalled.snapshot is older than retained history— all ten attempts ran and the snapshot still predated the buffer. The node’s export is further behind than the server is willing to bridge.
Updates
Then one message per block, on the same channel, whether or not the block touched this coin.
Both arrays are the node’s event bytes exactly, in the node’s order. A block with nothing for this coin carries two empty arrays — the height still advances, so silence is detectable.
A subscription therefore reads, in order (
data elided) — seq and height agree on every line, snapshot included:
seq versus prev
They answer different questions.seqis the source block height every feed carries, and your reconnect cursor. On this feed it equalsheight, so it says the same thingheightdoes.previs the chain of blocks: it names the exact predecessor, so aprevyou have not applied means a block is missing anywhere between the node and you, whatever the cause — a dropped frame, a server-side reconnect, a frame the node could not split.
prev. seq tells you where you are; prev tells you whether you got there without skipping anything, which is the question a book has to answer. Keep the last seq you applied anyway — it is what you pass back to resume after a disconnect.
The client rule
From the wire contract, verbatim:Apply an update only if itsThe first update after the snapshot hasprevequals the last applied height; on any mismatch, orpartial:truewhen attribution matters, reconnect with the last successfully appliedseq. If that cursor is no longer retained, subscribe without it to rebuild from a fresh snapshot.
prev == H, whether it was replayed or live; you cannot tell the two apart by prev, and need not. A server-side reconnect that could not resume where it left off shows up the same way, as a prev you have not applied. The generated order-book clients implement the rule.
When a book subscription is dropped
A book subscription that cannot be started, or cannot be kept, is dropped — and you are told on the ordinaryerror channel:
The subscription is gone, it releases its slot under the per-connection cap, and the subscribe ack you already received no longer stands. Any snapshot already sent for it should be discarded. An
unsubscribe while the snapshot is in flight is honoured silently.
The refusals that never start a subscription at all arrive on the same channel: feed not in tier below Pro, missing argument coin, and too many subscriptions when the connection is at its cap.
What it is not
- No history to seek into. There is no arbitrary start height to ask for. The snapshot is the node’s most recent export, and the only cursor you can resume from is one you were actually sent.
- Resume is bounded, not unlimited. Passing the last applied
seqback on resubscribe replays what you missed with no snapshot — but only within the retained window (10,000 messages per feed, and 60 seconds of warmth once a group empties). Past that you getsequence is not retainedand a fresh snapshot is the only way back; see reconnecting. - No book is held anywhere but in your process. The server splits and forwards the node’s bytes; applying diffs, and deciding when to throw the book away and start over, is your client’s job.

