Reading
Block stream
Streaming is an async iterator, not a callback soup. Break out of the loop or abort a signal and the stream stops immediately.
Open the unified streamOne canonical engine
There is exactly one block-reading implementation in the SDK. It owns RPC communication, head block tracking, block fetching, sequential ordering, historical backfill, the historical → live transition, poll intervals, retries, AbortSignal handling and normalization. Every watching API is a filtered view of it — none of them polls the chain itself.
Hive RPC
|
v
Canonical block engine (fetch, head tracking, retries, normalization, abort)
|
+-- hive.blocks.watch() raw normalized blocks
+-- hive.customJson.watch() id + actions filter
+-- hive.payments.watch() transfer + trigger detection
+-- hive.reader.stream() one loop, many registered filtersA single nextBlock cursor drives history and live blocks, so there is no hand-off between backfill and polling: blocks are never skipped, duplicated or read out of order. A failed read retries the same height — the cursor only advances after a block has been yielded.
Streaming events
const controller = new AbortController();
for await (const event of hive.customJson.watch({
id: "my-application",
actions: ["claim"],
signal: controller.signal,
onInvalidPayload: ({ reason, blockNumber }) => console.warn(blockNumber, reason),
})) {
console.log(event.blockNumber, event.action, event.metadata);
}Without fromBlock the stream starts at the current head block. Pass a block number to backfill history and then continue live.
Blocks
for await (const block of hive.blocks.watch({ fromBlock: 90000000 })) {
console.log(block.blockNumber, block.transactions.length);
}Normalized block
RPC nodes expose blocks inconsistently (operation tuples versus typed objects, missing transaction_ids). The engine normalizes once, so parsers and filters never deal with that. Every event keeps its chain position for deduplication: transactionId, blockNumber, blockTimestamp, transactionIndex and operationIndex.
interface NormalizedBlock {
blockNumber: number;
blockId: string | null;
timestamp: string;
transactions: NormalizedTransaction[];
raw: HiveBlock; // exactly what the node returned
}
interface NormalizedTransaction {
transactionId: string | null;
transactionIndex: number;
operations: NormalizedOperation[];
}
interface NormalizedOperation {
operationIndex: number;
operationType: string; // "custom_json", "transfer", ...
operation: HiveOperation;
}Options
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Custom JSON stream only: application id to filter on. |
| actions | string[] | No | Optional action allow-list. |
| fromBlock | number | No | First block to read. Defaults to the head block. |
| signal | AbortSignal | No | Stops the iterator cleanly. |
| pollIntervalMs | number | No | Poll interval while waiting for new blocks. Default 3000. |
| maxRetriesPerBlock | number | No | Consecutive failures before throwing. Default 5. |
| onError | (error, blockNumber) => void | No | Called on recoverable RPC errors instead of throwing. |
