Streams· Stream raw events

Hold your own cursor

A long-running pull from any point in chain history, one event per line. You decide where to resume, and reorgs are a list you can read.

299,680,847events you can replay from genesis
block_heightfinalized · 6 confirmations8,948,5068,948,5078,948,508tip506:19read506:24read507:03cursor507:08next…tipyour checkpointcursor = "8948507:03"kill the process, pass --cursor, resume exactly heretwo numbers order every event on the chain exactly once

Resume, don't restart

The cursor is height:index. Write it down, and a killed consumer picks up where it stopped.

jsonl, so pipe it anywhere

One event per line on stdout. jq, a file, a queue, Postgres. Your sink, no config to learn.

Reorgs are a list you can read

A fork rewinds to the fork point and re-delivers. streams reorgs pages the history, so you can reconcile.

The consumer

One command, until you stop it

streams consume pulls from a cursor and prints jsonl until you stop it. Filters run before the wire, so a consumer for one asset pays for one asset.

pullingsecondlayer streams consume --types ft_transferexample output
{"block_height":8948506,"event_index":12,"type":"ft_transfer"}
{"block_height":8948506,"event_index":19,"type":"ft_transfer"}
{"block_height":8948506,"event_index":24,"type":"ft_transfer"}
{"block_height":8948507,"event_index":3,"type":"ft_transfer"}
{"block_height":8948507,"event_index":8,"type":"ft_transfer"}
The cursor

Two numbers, total ordering

Block height plus event index orders every event on the chain exactly once. That pair is the whole checkpoint. No consumer group, no offset topic, no broker to run.

  1. 506:19read
  2. 506:24read
  3. 507:03cursor
  4. 507:08next
  5. …tip
~/consumer
$secondlayer streams tip
block_height 8,948,506
finalized_height 8,948,262
$secondlayer streams consume \
--types ft_transfer,nft_transfer \
--cursor 8948507:03 --batch-size 500 \
| jq -c '.type'
$secondlayer streams reorgs
✓ none in the last 1,000 blocks
$secondlayer streams by-tx 0x9f2c…
The firehose

Every frame is signed

The SSE stream carries each event with an ed25519 signature. The SDK checks every frame against our published key by default, so a tampered event fails before your code sees it.

GET /v1/streams/events/stream
$curl -N "$SL/v1/streams/events/stream?types=ft_transfer" \
-H "authorization: Bearer sk-sl_…"
data: {
"event": { "cursor": "8948507:03", "type": "ft_transfer", … },
"sig": "3f9a…c41e",
"key_id": "streams-2026"
}
✓ signature verified
History

Backfill from signed dumps, then tail live

events.replay reads the finalized parquet dumps, then switches to the live stream at the manifest's last finalized cursor. No gap and no duplicate at the seam.

backfill.tsts
1// signed dumps first, then live from the seam
2await streams.events.replay({
3 from: "genesis",
4 async onDumpFile(file, { from }) {
5 const bytes = await streams.dumps.download(file);
6 await ingestParquet(bytes, { after: from });
7 },
8 async onBatch(events, envelope) {
9 await db.insertEvents(events);
10 return envelope.next_cursor;
11 },
12});

3every window the dumps hold

4every dump is sha256-verified

8the seam: live from here

Capacity meter$0

Nothing runs on our side between pulls. A consumer you are not running costs nothing.

Volume meter$5 / 1M rows

Rows delivered, dropping to $2 per 1M past $50 of monthly spend. The first 1M each month are free. Filters cut the bill along with the output.

Bitcoin

Runes on the same loop

chain=bitcoin serves Runes events on their own cursor space, from block 840,000, checked against ord with 0 mismatches.

rolling out
rune_etchrune_mintrune_transferrune_burn
GET /v1/streams/events
$curl "$SL/v1/streams/events?chain=bitcoin&rune=840000:3"
{
"events": [{ "type": "rune_transfer", "block_height": 968557,
"rune": "840000:3", "ts": … }],
"next_cursor": "968557:41"
}

Run it yourself

Hosted is api.secondlayer.tools. Point SECONDLAYER_API_URL at your own instance and the same CLI, SDK and /v1/streams routes answer. Reads over loopback need no key, so a local consumer pays nothing per row.

Streams questions

What exactly is the cursor?
block_height:event_index, a total order over chain events. Store it wherever you store anything, and pass --cursor to resume.
What happens on a reorg?
The consumer rewinds to the fork point and re-delivers from there, fork block included. Delivery is at least once, so keep writes idempotent. Anything above finalized_height is provisional until six Bitcoin confirmations.
Can I read everything one transaction emitted?
Yes. streams by-tx <txId> returns every event it emitted. block-events does the same for a height or hash, and canonical <height> returns block metadata.
Is this push or pull?
A pull you keep open, over REST pages or SSE. To have chain events pushed to your server instead, use Webhooks: same events, our retry queue.
Do filters reduce the bill?
Yes. The meter counts rows delivered, so --types and --contract-id cut the bill and the output together.

Have a consumer tailing today