Connect to the flashblocks stream

G3 sequencers publish flashblocks every 10 milliseconds over WebSocket. Each partial block update carries the transactions and receipts added since the previous flashblock, along with account balance metadata, so your app sees state changes hundreds of milliseconds before the block seals.

A WebSocket proxy serves the stream through one endpoint.

If you need the accumulated state instead of each update, query flashblock state through the public JSON-RPC endpoint.

Conduit enables the flashblocks stream per chain. Contact your Conduit account team for your chain’s WebSocket endpoint and credentials.

Connect

Start with your chain’s RPC endpoint, https://[YOUR_RPC_HOST]/[YOUR_API_KEY]. Connect to the same host over WebSocket at wss://[YOUR_RPC_HOST]/flashblocks/[YOUR_API_KEY].

1import asyncio
2import json
3
4import websockets
5
6WS_URL = "wss://[YOUR_RPC_HOST]/flashblocks/[YOUR_API_KEY]"
7
8
9async def main():
10 async with websockets.connect(
11 WS_URL,
12 ping_interval=20,
13 ping_timeout=30,
14 ) as ws:
15 while True:
16 flashblock = json.loads(await ws.recv())
17 print(
18 flashblock["metadata"]["block_number"],
19 flashblock["index"],
20 len(flashblock["diff"]["transactions"]),
21 )
22
23
24asyncio.run(main())

Configure your client to accept messages large enough for your chain’s busiest flashblocks. Each message can include every raw transaction and receipt added in that update.

Read a flashblock

Every message is one JSON flashblock:

1{
2 "payload_id": "0x038c03e80f2fd41d",
3 "index": 0,
4 "base": {
5 "parent_hash": "0xea7f...",
6 "fee_recipient": "0x4200000000000000000000000000000000000011",
7 "block_number": "0x1254e",
8 "gas_limit": "0x2540be400",
9 "timestamp": "0x69d83fac",
10 "base_fee_per_gas": "0x3b9aca00"
11 },
12 "diff": {
13 "state_root": "0x0000...0000",
14 "receipts_root": "0x0000...0000",
15 "gas_used": "0xb466",
16 "block_hash": "0x0000...0000",
17 "transactions": ["0x7ef90106a0488f...0190"],
18 "withdrawals": []
19 },
20 "metadata": {
21 "block_number": 75086,
22 "new_account_balances": { "0x0000f908...": "0x0" },
23 "receipts": { "0x6b034f...": { "status": "0x1", "logs": [] } }
24 }
25}
FieldNotes
payload_idIdentifies the block the sequencer is building. Stable across every flashblock of one block.
indexPosition within the block, starting at 0. Increases by 1 per flashblock.
baseHeader fields fixed for the block. Present only on index 0.
diffIncremental transactions plus fields for the partial block. transactions holds the raw, RLP-encoded transactions added in this flashblock and can be empty. gas_used is cumulative through this flashblock.
metadatablock_number as an integer, new_account_balances keyed by account address, and incremental receipts keyed by transaction hash. Balance entries can repeat across flashblocks.

Two properties are worth planning around:

  • index 0 opens a block. It carries base and always contains at least the L1 deposit transaction.
  • Intermediate roots and hashes remain zero. state_root, receipts_root, and block_hash remain zero while the block is under construction. Only the final flashblock of a block, the seal, carries their completed values.

Flashblock reorgs

A flashblock reorg replaces transactions or state published in an earlier flashblock before the block seals. Conduit targets fewer than three flashblock reorgs per year. G3’s durability design aims to limit these reorgs to scenarios involving major hardware faults.

Even with this durability target, flashblocks remain preconfirmations. Before treating flashblock data as settled, verify against the canonical block with eth_getBlockByNumber once the chain tip has moved past it.

Handle gaps

Track (metadata.block_number, index) to detect skipped updates. Wait for an index of 0 before processing to begin at a clean block boundary. If a connection gap leaves you without finalized data, backfill it from canonical blocks over RPC.