sl.streams
Every Stacks event in order, as a loop you checkpoint or a page you read. The same data the Streams API serves over HTTP.
| consume(params) | AsyncIterableIterator<StreamsBatch<… | Read Streams page by page as an async iterator. |
| events.list(params) | StreamsEventsEnvelope<VmStreamsEven… | Read one page of chain events and the next cursor. |
| events.byTxId(txId) | StreamsEventsListEnvelope<StreamsEv… | Read every event a transaction emitted. |
| events.consume(params) | StreamsEventsConsumeResult | Run a checkpointed indexer that rewinds on reorgs. |
| events.replay(params) | StreamsEventsConsumeResult | Backfill from bulk dumps, then continue live in one call. |
| events.stream(params) | AsyncIterable<StreamsEventOfTypes<T… | Read events one at a time as an async iterator. |
| events.subscribe(params) | StreamsSubscription | Get each new event pushed to onEvent as it lands. |
| blocks.events(heightOrHash) | StreamsEventsListEnvelope<StreamsEv… | Read every event in one block, by height or hash. |
| reorgs.list(params) | StreamsReorgsListEnvelope | List chain reorgs since a cursor. |
| dumps.list() | StreamsDumpsManifest | Fetch and parse the latest dumps manifest. |
| dumps.fileUrl(file) | string | Absolute URL for a manifest file. |
| dumps.download(file) | Uint8Array | Download a parquet file and verify its sha256 against the manifest. |
| canonical(height) | StreamsCanonicalBlock | Get the canonical block at a height. |
| tip() | StreamsTip | Get the current chain tip. |
sl.streams.consume(params)
Read Streams page by page as an async iterator.
Yields one StreamsBatch per events page, so each yield is exactly one API call. Empty pages are skipped; at the tip the iterator polls again every intervalMs (default 2000) until signal aborts.
Reorgs appear on batch.reorgs, but the cursor is not rewound for you. Use events.consume with onReorg when you need rollback.
A literal types array narrows every batch's event union to those types. chain: "bitcoin" batches carry Runes rows only, on the bitcoin tip shape.
Parameters
hover a row to find it in the codeRequired
typesrequiredTStacks · Bitcoin only |
Options
cursorstring | nullStacks · Bitcoin only | Resume strictly after this cursor; omit to start from the oldest seekable page. |
batchSizenumberStacks · Bitcoin only | Events per page (the limit query param). Default 100. |
intervalMsnumberStacks · Bitcoin only | Poll interval while caught up at the tip, in ms. Default 2000. |
signalAbortSignalStacks · Bitcoin only | |
chain"stacks"Stacks · Bitcoin only | |
notTypesreadonly ("stx_transfer" | "stx_mint" | "stx_burn" | "s…Stacks · Bitcoin only | |
contractIdStreamsFilterValueStacks only | |
senderStreamsFilterValueStacks only |
6 more options
recipientStreamsFilterValueStacks only | |
assetIdentifierstringStacks only | |
filtersStreamsFilterMapStacks only | |
paramsStreamsConsumeBitcoinParams | StreamsConsumeStacksParamsAny chain only | |
runestringBitcoin only | A RuneRef: an id (840000:3) or a name (DOG•GO•TO•THE•MOON). |
addressstringBitcoin only | A mainnet address. |
sl.streams.events.list(params)
Read one page of chain events and the next cursor.
Narrow with types, notTypes, contractId, sender, recipient, assetIdentifier, fromHeight/toHeight, or labelled filters. Filters match exactly, and an event type without a given field never matches it. Pass next_cursor back as cursor to continue. The envelope also carries the tip and any reorgs. To write the events somewhere with a checkpoint, use events.consume instead.
A literal types array narrows every row to those event types. clock: "vm" pages carry vm rows only (a parallel vocabulary, event_index = ordinal), never StreamsEvent; it is Stacks only. chain: "bitcoin" pages carry Runes rows only, on the bitcoin tip shape, and filter by rune or address.
Parameters
hover a row to find it in the codeRequired
clockrequired"vm"Stacks only | |
paramsrequiredStreamsEventsListParamsAny chain only |
Options
cursorstring | nullStacks · Bitcoin only | |
fromHeightnumberStacks · Bitcoin only | |
toHeightnumberStacks · Bitcoin only | |
limitnumberStacks · Bitcoin only | |
chain"stacks"Stacks · Bitcoin only | |
typesreadonly ("stx_transfer" | "stx_mint" | "stx_burn" | "s…Stacks · Bitcoin only | |
notTypesreadonly ("stx_transfer" | "stx_mint" | "stx_burn" | "s…Stacks · Bitcoin only | |
contractIdStreamsFilterValueStacks only |
6 more options
senderStreamsFilterValueStacks only | |
recipientStreamsFilterValueStacks only | |
assetIdentifierstringStacks only | |
filtersStreamsFilterMapStacks only | |
runestringBitcoin only | A RuneRef: an id (840000:3) or a name (DOG•GO•TO•THE•MOON). |
addressstringBitcoin only | A mainnet address. |
Returns Promise<StreamsEventsEnvelope<VmStreamsEvent, StreamsTi…
events- TEvent[]
next_cursor- string | null
tip- TTip
reorgs- StreamsReorg[]
sl.streams.events.byTxId(txId)
Read every event a transaction emitted.
Returns the events and the tip, with no next_cursor: one transaction is one response.
chain: "bitcoin" returns the Runes events for one Bitcoin transaction.
Parameters
hover a row to find it in the codeRequired
txIdrequiredstring | |
opts.chainrequired"bitcoin"Bitcoin only |
Returns Promise<StreamsEventsListEnvelope<StreamsEvent, Streams…
events- TEvent[]
tip- TTip
reorgs- StreamsReorg[]
sl.streams.events.consume(params)
Run a checkpointed indexer that rewinds on reorgs.
Write your rows in onBatch and commit ctx.cursor in the same transaction; resume with fromCursor. Reorgs at or below your checkpoint call onReorg, then the loop re-reads from the fork point. Delivery is at-least-once, so keep writes idempotent. With a sink, the checkpoint and rollback are handled for you. mode: "tail" (default) keeps polling at the tip; mode: "bounded" returns on the first empty page. It also stops on maxPages, maxEmptyPolls, or signal. Pass finalizedOnly to skip reorg handling entirely.
chain: "bitcoin" sweeps Runes events; see StreamsEventsConsumeBitcoinParams.
Parameters
hover a row to find it in the codeOptions
chain"stacks" | chain: "stacks" (default, omit chain entirely) — the bitcoin branch is StreamsEventsConsumeBitcoinParams, a separate overload with no filters/on/decoded (Stacks-only concepts). |
fromCursorstring | null | |
decodedDStacks only | Deliver events pre-decoded as the flat, event_type-discriminated rows Index serves (IndexEvent), so Streams consumption reads identically to Index consumption — no guard+decode pairs in your handler. |
sinkConsumerSink<TTx> | |
onProgress(ctx: ConsumerBatchContext<unknown, unknown>) => void | Fires once per page, before onBatch and before any early return. |
mode"tail" | "bounded" | |
finalizedOnlyboolean | Emit only finalized (immutable) events and never surface reorgs. The SDK checkpoints at the last finalized event and re-reads the unfinalized tail until it settles. Trades finality lag for zero reorg handling; onReorg is ignored. |
typesreadonly ("stx_transfer" | "stx_mint" | "stx_burn" | "s… |
21 more options
notTypesreadonly ("stx_transfer" | "stx_mint" | "stx_burn" | "s… | |
contractIdStreamsFilterValueStacks only | |
senderStreamsFilterValueStacks only | |
recipientStreamsFilterValueStacks only | |
assetIdentifierstringStacks only | |
filtersFStacks only | Labelled filter groups. The groups OR together in ONE server-side scan, so two unrelated concerns share one page, one cursor, and one checkpoint instead of two consume loops. Pair with on to handle each label. |
on{ [K in keyof F]: ( events: StreamsEventForFilter<F[K],…Stacks only | One handler per label in filters. Every label must be handled, so adding a label is a compile error until its handler exists. A label's declared types narrows its handler's events, so payload is typed without an event_type guard. Labels are dispatched in declaration order: a label's own events stay in cursor order, but events of different labels do not interleave within a page — add onBatch when you need strict global order across labels. |
batchSizenumber | |
onBatch(events: D extends true ? IndexEvent[] : StreamsEvent[]… | Apply a page of canonical events. Persist ctx.cursor in the same transaction as your writes. Returning a cursor overrides ctx.cursor as the resume point (advanced manual control); returning nothing uses it. Optional only when on handles the page instead; a consume call with neither throws. |
onReorg(reorg: StreamsReorg, ctx: StreamsReorgContext) => Prom… | Roll your projection back to reorg.fork_point_height, persisting ctx.cursor in the same transaction. Called once per *new* reorg at or below the checkpoint (deduped in-memory, fork-ascending) before the SDK rewinds and re-reads the now-canonical events. A reorg whose fork sits above the checkpoint is not a rollback (nothing past it was written) and is skipped. Omit it to ignore reorgs (events stay canonical, but stale rows from an orphaned fork are left in place). With onReorg or a sink attached, every empty page also polls reorgs.list (one extra request per idle poll) so a fork that lands while the consumer idles at the tip is rolled back too. |
maxRollbackDepthnumber | Deepest rewind one reorg may make below the checkpoint before the loop refuses with ValidationError. Default 1000 blocks. The fork point is server supplied and drives a delete on every declared table, so raise this only for a source you trust. |
retryCountnumber | Page-fetch retries after the first failure (429/5xx/network only — 4xx and handler throws always propagate). Default 3; 0 disables. |
retryDelaynumber | Base retry delay in ms; the n-th retry waits retryDelay * n. A server Retry-After overrides it. Default 1000. |
onError(err: unknown, ctx: { attempt: number; retriesLeft: num… | Void observer, called before each retry sleep (metrics/logging). |
emptyBackoffMsnumber | |
maxPagesnumber | |
maxEmptyPollsnumber | |
signalAbortSignal | |
wake() => Promise<void> | Cuts the empty-poll backoff short: on an empty page, the loop races emptyBackoffMs against this promise instead of sleeping the full amount. Pass a function that resolves once when new data MIGHT be available (e.g. a Postgres NOTIFY listener) — it is called fresh on every empty page, never awaited more than once concurrently per page. The backoff timer still runs underneath, so a wake source that never resolves (or a wake this call omits) degrades to the same polling behavior as before. |
runestringBitcoin only | A RuneRef: an id (840000:3) or a name (DOG•GO•TO•THE•MOON). |
addressstringBitcoin only | A mainnet address. |
Callback context ctx: ConsumerBatchContext<unknown, unknown>
cursor- Checkpoint to commit alongside your rows.
height- Highest canonical block a row was DELIVERED from.
nullbefore the first row of a fresh consume. With a sparse filter this parks at the last matching event — it measures event recency, not progress. scannedHeight- Highest canonical block the sweep has VERIFIED through: an empty page is the server confirming nothing matches between the cursor and the tip, so a caught-up tail scans to the tip even when
heightis far below it. Rolls back with reorg rewinds. tipHeight- Chain tip as of this page's read.
blocksBehindtipHeight - scannedHeight, floored at 0 — the consumer's actual backlog.nulluntil a position is known. A caught-up consumer on a quiet contract reads ~0 here whiletipHeight - heightgrows — that difference is event age, not lag.tip- The full tip for this page —
StreamsTiporIndexTipdepending on the surface. Exposed here so a handler never needs theenvelopeparameter, which is the only way to reach it today and is also how the worstfinalizedOnlymistake becomes reachable (return envelope.next_cursorjumps the cursor past events that were filtered out and never delivered). reorgs- Reorgs reported alongside this page; empty when none. Rollback still belongs in
onReorg— this is for observability inside a batch.
Callback context ctx: StreamsReorgContext
cursor- string | null
Returns Promise<StreamsEventsConsumeResult>
cursor- string | null
pages- number
emptyPolls- number
Backfill from bulk dumps, then continue live in one call.
Hands each finalized dump file to onDumpFile in block order, then tails live from the manifest's latest_finalized_cursor, with no gap or duplicate at the seam. A file that straddles from is delivered whole, so skip the rows at or below it. Requires dumpsBaseUrl.
Parameters
hover a row to find it in the codeRequired
onDumpFilerequired(file: StreamsDumpFile, ctx: { from: string | null }) =… | Called once per finalized dump file, in block order, before live tailing. Process the parquet with your own tooling (e.g. DuckDB) — the SDK does not decode parquet. Use client.dumps.download(file) to fetch + verify bytes. Delivery is file-granular and at-least-once: a file whose range straddles from is handed over whole. ctx.from is that cursor (null from genesis); skip rows at or below it, or key rows by cursor so a re-run is an idempotent no-op. Files ending at or below from are not handed over at all. |
onBatchrequired(events: StreamsEvent[], envelope: StreamsEventsEnvelop… | Called per live page after the dump phase, like consume. |
Options
fromstring | Start point: "genesis" (default) or a <block>:<index> cursor. |
onReorg(reorg: StreamsReorg, ctx: StreamsReorgContext) => Prom… | Called when the live-tail phase (after the dump backfill) crosses a reorg, before the cursor rewinds and re-reads the now-canonical events — same contract as consume. The dump-backfill phase is finalized and never reorgs; omit this to leave stale rows from an orphaned fork in place. |
mode"tail" | "bounded" | |
batchSizenumber | |
emptyBackoffMsnumber | |
maxPagesnumber | |
maxEmptyPollsnumber | |
signalAbortSignal |
3 more options
typesreadonly ("stx_transfer" | "stx_mint" | "stx_burn" | "s… | Narrow the LIVE TAIL after the dump phase. The dump files themselves are always all-type (block-partitioned parquet) — filter those in your own tooling while processing each file. |
notTypesreadonly ("stx_transfer" | "stx_mint" | "stx_burn" | "s… | |
filtersStreamsFilterMap | Labelled OR-groups for the live tail, like consume. |
Callback context ctx: StreamsReorgContext
cursor- string | null
Returns Promise<StreamsEventsConsumeResult>
cursor- string | null
pages- number
emptyPolls- number
sl.streams.events.stream(params)
Read events one at a time as an async iterator.
For live processors and watch-style apps that keep their own state. It tails indefinitely and stops when signal, maxPages, or maxEmptyPolls ends it. There is no checkpoint or reorg handling; use events.consume for that.
A literal types array narrows every event to those types. chain: "bitcoin" yields Runes events only.
Parameters
hover a row to find it in the codeRequired
typesrequiredTStacks · Bitcoin only |
Options
fromCursorstring | nullStacks · Bitcoin only | |
batchSizenumberStacks · Bitcoin only | |
emptyBackoffMsnumberStacks · Bitcoin only | |
maxPagesnumberStacks · Bitcoin only | |
maxEmptyPollsnumberStacks · Bitcoin only | |
signalAbortSignalStacks · Bitcoin only | |
chain"stacks"Stacks · Bitcoin only | |
notTypesreadonly ("stx_transfer" | "stx_mint" | "stx_burn" | "s…Stacks · Bitcoin only |
8 more options
contractIdStreamsFilterValueStacks only | |
senderStreamsFilterValueStacks only | |
recipientStreamsFilterValueStacks only | |
assetIdentifierstringStacks only | |
filtersStreamsFilterMapStacks only | |
paramsStreamsEventsStreamBitcoinParams | StreamsEventsStreamS…Any chain only | |
runestringBitcoin only | A RuneRef: an id (840000:3) or a name (DOG•GO•TO•THE•MOON). |
addressstringBitcoin only | A mainnet address. |
sl.streams.events.subscribe(params)
Get each new event pushed to onEvent as it lands.
Reconnects from the last handled cursor after a transport error, and verifies each frame's ed25519 signature when the client has verify on. Returns an unsubscribe function whose done promise settles when the loop ends. Not reorg-aware: an event delivered and then orphaned is never retracted. Durable writers use events.consume.
chain: "bitcoin" delivers Runes rows to onEvent.
Parameters
hover a row to find it in the codeRequired
onEventrequired(event: StreamsEvent) => Promise<void> | void | Called for each pushed event, in order. The resume cursor advances only after this resolves, so a handler that throws sees the same event again on reconnect (at-least-once). Key durable writes by cursor. |
Options
fromCursorstring | null | Resume strictly after this cursor; omit to live-tail from the tip. |
signalAbortSignal | |
onError(err: unknown) => void | Called on every failure. Transport errors (dropped socket, 5xx, 429, a stale connection, a throwing handler) reconnect from the last handled cursor with exponential backoff. Errors that a retry cannot fix (401, 4xx, a bad signature) end the subscription and reject its done. |
reconnectDelayMsnumber | First reconnect pause in ms (default 1000). Doubles per consecutive failure up to 30 s with jitter, resets once a frame arrives, and never undercuts a Retry-After. |
staleAfterMsnumber | Reconnect when no frame (pings included) arrives for this long (default 60000, three server heartbeats). Catches a half-open socket that never errors; set it above your instance's heartbeat interval. |
chain"stacks" | |
typesreadonly ("stx_transfer" | "stx_mint" | "stx_burn" | "s… | |
notTypesreadonly ("stx_transfer" | "stx_mint" | "stx_burn" | "s… |
7 more options
contractIdStreamsFilterValueStacks only | |
senderStreamsFilterValueStacks only | |
recipientStreamsFilterValueStacks only | |
assetIdentifierstringStacks only | |
filtersStreamsFilterMapStacks only | Labelled filter groups; see StreamsEventsListParams.filters. |
runestringBitcoin only | A RuneRef: an id (840000:3) or a name (DOG•GO•TO•THE•MOON). |
addressstringBitcoin only | A mainnet address. |
Returns StreamsSubscription
done- Promise<void>
sl.streams.blocks.events(heightOrHash)
Read every event in one block, by height or hash.
Returns the events and the tip, with no next_cursor.
chain: "bitcoin" returns the Runes events in one Bitcoin block.
Parameters
hover a row to find it in the codeRequired
heightOrHashrequiredstring | number | |
opts.chainrequired"bitcoin"Bitcoin only |
Returns Promise<StreamsEventsListEnvelope<StreamsEvent, Streams…
events- TEvent[]
tip- TTip
reorgs- StreamsReorg[]
sl.streams.reorgs.list(params)
List chain reorgs since a cursor.
since is required. Each reorg gives its fork_point_height and the orphaned range. Page with next_since.
chain: "bitcoin" lists Bitcoin reorgs, on their own cursor space.
Parameters
hover a row to find it in the codeRequired
sincerequiredstring | |
chainrequired"bitcoin"Bitcoin only |
Options
limitnumber |
Returns Promise<StreamsReorgsListEnvelope>
reorgs- StreamsReorg[]
next_since- string | null
Fetch and parse the latest dumps manifest.
Returns Promise<StreamsDumpsManifest>
dataset- string
network- string
version- string
schema_version- number
generated_at- string
producer_version- string
finality_lag_blocks- number
latest_finalized_cursor- Cursor at the end of the finalized bulk coverage — hand to live tailing.
coverage- { from_block: number; to_block: number }
files- StreamsDumpFile[]
signature- ed25519 signature over the manifest's canonical bytes. Absent on legacy unsigned manifests. Verified by
list()whenverifyDumpsManifestis on. key_id- Short id of the signing public key.
Absolute URL for a manifest file.
Parameters
hover a row to find it in the codeRequired
pathrequiredstring | |
from_blockrequirednumber | |
to_blockrequirednumber | |
min_cursorrequiredstring | null | |
max_cursorrequiredstring | null | |
row_countrequirednumber | |
byte_sizerequirednumber | |
sha256requiredstring | |
schema_versionrequirednumber | |
created_atrequiredstring |
Download a parquet file and verify its sha256 against the manifest.
Parameters
hover a row to find it in the codeRequired
pathrequiredstring | |
from_blockrequirednumber | |
to_blockrequirednumber | |
min_cursorrequiredstring | null | |
max_cursorrequiredstring | null | |
row_countrequirednumber | |
byte_sizerequirednumber | |
sha256requiredstring | |
schema_versionrequirednumber | |
created_atrequiredstring |
sl.streams.canonical(height)
Get the canonical block at a height.
Returns the block hash and, on Stacks, the burn block it anchors to.
chain: "bitcoin" returns the Bitcoin block, without burn-chain fields.
Parameters
hover a row to find it in the codeRequired
heightrequirednumber | |
opts.chainrequired"bitcoin"Bitcoin only |
Returns Promise<StreamsCanonicalBlock>
block_height- number
block_hash- string
burn_block_height- number
burn_block_hash- string | null
is_canonical- true
sl.streams.tip()
Get the current chain tip.
Returns the tip height and hash, finalized_height, and lag_seconds. On Stacks it also reports the oldest height your tier can seek; older reads come from the bulk dumps.
chain: "bitcoin" returns the Bitcoin tip, without burn-chain or retention fields.
Parameters
hover a row to find it in the codeRequired
chainrequired"bitcoin"Bitcoin only |
Returns Promise<StreamsTip>
block_height- number
block_hash- string
burn_block_height- number
finalized_height- Highest Stacks block past the burn-confirmation finality boundary. Optional for back-compat; the API always sets it.
lag_seconds- number
oldest_seekable_height- Oldest height still seekable on the live API for the caller's tier (
tip - retention).null= unlimited retention. Older reads must use the cold dumps lane. Optional for back-compat. oldest_cursor- Oldest seekable cursor (
<oldest_seekable_height>:0);null= unlimited.
ConsumerBatchContextonBatch.ConsumerSinkFtBurnPayloadasset_identifier, sender, amountFtMintPayloadasset_identifier, recipient, amountFtTransferPayloadasset_identifier, sender, recipient, amountMapDeletePayloadcontract_identifier, map_name, raw_keyMapWritePayloadcontract_identifier, map_name, raw_key, raw_valueNestedContractCallPayloadcontract_identifier, sender, caller, function_name, function_args, raw_resultNftBurnPayloadasset_identifier, sender, value, raw_valueNftMintPayloadasset_identifier, recipient, value, raw_valueNftTransferPayloadasset_identifier, sender, recipient, value, raw_valuePrintPayloadcontract_id, topic, value, raw_valueRuneEtchEntryrune_etch event created — everything a consumer needs to render a fresh rune without a second lookup, minus what the envelope already carries (rune_id).RuneEventPayloadamount, vout, address, entryRuneEventType"rune_etch" | "rune_mint" | "rune_transfer" | "rune_burn"RuneStreamsEventStreamsBatchconsume, with cursor to persist and resume.StreamsBatchContextcursor, height, scannedHeight, tipHeight, blocksBehind, tip, … Deprecated. Use ConsumerBatchContext.StreamsBitcoinCanonicalBlockStreamsBitcoinTipStreamsCanonicalBlockStreamsClarityValue{ hex }), or a decoded Clarity-JSON object.StreamsConsumeBitcoinParamsStreamsConsumeCommonStreamsConsumeStacksParamschain: "stacks" (default, omit chain entirely).StreamsDumpFileStreamsDumpsManifestStreamsEventStreamsEventOfcursor, block_height, block_hash, burn_block_height, tx_id, tx_index, …StreamsEventOfTypestypes selection.StreamsEventsBitcoinListParamsStreamsEventsConsumeBitcoinParamsStreamsEventsConsumeParamsStreamsEventsConsumeResultStreamsEventsEnvelopeStreamsEventsListEnvelopeStreamsEventsListParamsStreamsEventsListParamsCommonStreamsEventsReplayParamsStreamsEventsStacksListParamschain: "stacks" (default, omit chain entirely) — unchanged since before Runes events were added.StreamsEventsStreamBitcoinParamsStreamsEventsStreamCommonStreamsEventsStreamStacksParamsStreamsEventsSubscribeBitcoinParamsStreamsEventsSubscribeStacksParamschain: "stacks" (default, omit chain entirely).StreamsFilterMapStreamsFilterValueStreamsLabelledFilterStreamsReorgdetected_at, fork_point_height, orphaned_range, new_canonical_tipStreamsReorgContextcursor is the rewind position the loop resumes from after onReorg: the foot of the fork point, or null for a fork at genesis.StreamsReorgsListEnvelopeStreamsReorgsListParamsStreamsSubscriptionStreamsTipStreamsWireEventGET /v1/streams/events can return.StxBurnPayloadsender, amountStxLockPayloadlocked_address, locked_amount, unlock_heightStxMintPayloadrecipient, amountStxTransferPayloadsender, recipient, amount, memoVarSetPayloadcontract_identifier, var_name, raw_valueVmStreamsEventclock=vm row.VmStreamsEventOfcursor, block_height, block_hash, burn_block_height, tx_id, tx_index, …