Cursors & live streaming
STEP_NEW- Process the incoming block. It may still be undone by a reorganization.
STEP_UNDO- Roll back the previously processed block identified by this response.
STEP_FINAL- The block is final according to the chain's finality rules; returned in final_blocks_only mode.
final_blocks_only picks between the two modes. With false (the Quickstart default) you receive each block as soon as it is produced, and a reorganization sends STEP_UNDO for every block to roll back before the new branch. With true you receive only STEP_FINAL blocks and never an undo, at the cost of waiting for the chain's finality (see Per-chain notes). Each response's metadata.lib_num tells you how far finality has reached.
1// Resume from the last saved cursor; start one block behind the head otherwise.2// Send the cursor alone when you have one: it is the resume position.3const cursor = await loadCursor();4const request = cursor5 ? { cursor, final_blocks_only: false }6 : { start_block_num: "-1", final_blocks_only: false };78for await (const res of client.Blocks(request, metadata)) {9 if (res.step === "STEP_NEW") await applyBlock(res.block); // may be undone later10 if (res.step === "STEP_UNDO") await revertBlock(res.metadata.num); // reorg: roll back11 if (res.step === "STEP_FINAL") await applyBlock(res.block); // final_blocks_only: true12 await saveCursor(res.cursor); // in the same transaction as the write above13}
Persist the response cursor after processing the block and its step, in the same transaction as your write. Reconnect with that cursor and the same filters; the stream resumes after the last block you saved, including any undo you had not yet seen. Handle stream errors and reconnect with backoff; do not parse or construct cursors yourself.
The Node.js example uses a 16 MiB HTTP/2 receive window and a separate 64 MiB message limit. The larger window resolved a client backlog in the Robinhood comparison; size these settings for your workload. Filtering reduces payload and decoding work without changing when the chain executes a transaction.
With final_blocks_only: false, live blocks are pushed as soon as the chain produces them; there is no polling interval on your side. With final_blocks_only: true, each block arrives only once it is final, so add the chain's finality delay (see Per-chain notes). End-to-end delay (chain tip to your client) also depends on the chain's block time, your network path and, for Substreams, your modules. Measure it from each block's timestamp in your own deployment. There is no latency SLA: the Free plan is best effort, and service levels for Enterprise are set in the contract.
Try It previews stop after 15 seconds, 50 responses or a data limit. Preview timing includes the App proxy and connection setup. For direct delivery measurements, use a native client. Free and Pro share delivery infrastructure and streaming bandwidth limits; usage allowances differ.