Message Format
Envelope
Every message from the server is a JSON object with two top-level keys:
{"seq": <u64>, "<MessageType>": <payload>}
seq: monotonically increasing server-assigned counter for cursor resume. This is different fromseqnoinside event payloads (see below). Gaps between consecutiveseqvalues are expected; filtered/blocked events occupy ring slots but are not delivered to the client.<MessageType>: exactly one of the types described below
seqvsseqno: The outerseqis the server-level counter that clients must track for?resume_from=. The innerseqno(inside each event payload) is the raw event ring position from the validator; it is informational and should not be used for resume logic.
Control frames: Hello, Warning, and Error messages always use
seq: 0. Clients must not update their resume cursor fromseq: 0frames; only fromseq > 0.
Serialization Rules
- Field order:
seqis always first - Numbers: JSON numbers (not strings)
- Hex types (B256, Address): 0x-prefixed lowercase
- Null fields: omitted (
serde(skip_serializing_if = "Option::is_none")) - Byte arrays (Bytes): 0x-prefixed hex string
Server → Client
Hello
Sent before the first Subscribe response. For field descriptions, see Connection.
Events
Batch of execution events:
{
"seq": 42,
"Events": [
{
"event_name": "BlockStart",
"block_number": 56147820,
"txn_idx": null,
"txn_hash": null,
"commit_stage": "Proposed",
"payload": {"type": "BlockStart", "block_number": 56147820, "block_id": "0x..."},
"seqno": 9876543210,
"timestamp_ns": 1708345678000000000
}
]
}
Multiple events are grouped into a single Events batch. The server uses drain-based batching: on each processing cycle, all pending events from the broadcast channel are collected and sent as one batch. This means batch size varies, from 1 event during low traffic to hundreds during busy blocks. The order within a batch matches the order events were received from the event ring (execution order).
TPS
{"seq": 43, "TPS": 2450}
ContentionData
{
"seq": 44,
"ContentionData": {
"block_number": 56147820,
"block_wall_time_ns": 45000000,
"total_tx_time_ns": 350000000,
"parallel_efficiency_pct": 87.14,
"total_unique_slots": 1523,
"contended_slot_count": 42,
"contention_ratio": 0.0275,
"total_txn_count": 150,
"top_contended_slots": [],
"top_contended_contracts": [],
"contract_edges": []
}
}
TopAccesses
{
"seq": 45,
"TopAccesses": {
"account": [{"key": "0x...", "count": 15234}],
"storage": [{"key": ["0x...", "0x..."], "count": 8921}]
}
}
Lifecycle
{
"seq": 46,
"Lifecycle": {
"block_hash": "0x...",
"block_number": 56147820,
"from_stage": "Proposed",
"to_stage": "Voted",
"time_in_previous_stage_ms": 412.5,
"block_age_ms": 412.5,
"txn_count": 150,
"gas_used": null
}
}
Warning
Best-effort backpressure notification. Sent every 1,000 drops:
{"seq": 0, "Warning": {"type": "backpressure", "dropped": 1000, "drop_limit": 10000}}
seq: 0: control message, does not contain stream data.
Error
Subscription error. The current subscription remains unchanged:
{"seq": 0, "Error": {"type": "subscribe_limit", "message": "Subscription exceeds limit of 3 items", "limit": 3}}
Error types: subscribe_limit, empty_subscribe, parse_error.
Client → Server
Subscribe (simple)
{"subscribe": ["BlockStart", "BlockFinalized", "TPS"]}
Subscribe (extended)
{
"subscribe": {
"events": ["TxnLog"],
"filters": [{"event_name": "TxnLog", "field_filters": [{"field": "address", "filter": {"values": ["0x..."]}}]}],
"min_stage": "Finalized",
"correlate": true
}
}
Hello (optional)
The client may send Hello for identification. The server logs it but takes no action:
{"hello": {"protocol_version": 1, "client_name": "my-bot", "client_version": "1.0.0"}}
Ping/Pong
Standard WebSocket Ping/Pong frames (binary, protocol-level).
JSON-RPC
The WebSocket also accepts JSON-RPC 2.0 requests. This is a minimal implementation; currently only one method is supported:
| Method | Description |
|---|---|
eth_getBlockByNumber | Returns the current block number |
{"jsonrpc": "2.0", "method": "eth_getBlockByNumber", "params": [], "id": 1}
Response:
{"jsonrpc": "2.0", "result": {"number": "0x359a8fc"}, "id": 1}
Unsupported methods return -32601 Method not found. This is not a full Ethereum JSON-RPC implementation. For full RPC support, use a standard Monad node.
Batching
- Events: grouped into a single
Eventsarray per processing cycle (drain-based). Batch size varies from 1 to hundreds depending on throughput. - Metrics (TPS, ContentionData, TopAccesses, lifecycle): sent as separate frames, never mixed into the
Eventsarray. A metric and an event may share the sameseqif they originate from the same ring position. - Order within a batch: matches the order of receipt from the event ring, which is the execution order within a block.
- No backfill: The server only streams live events and replays from the ring buffer. There is no way to request historical events by block range.