Index· Query decoded rows

The whole chain, already decoded

Write an indexer against every Stacks event since genesis, in one TypeScript file. Run it on Railway, Render, Fly or your own box. We meter the rows it reads.

300,504,551events decoded
contract_callevent 10nft_mintevent 11ft_transferevent 12block_height8,948,506contractSM3V…sbtc-tokensenderSP2J…R4KrecipientSP3F…Q9Aamount25,000,000next_cursor 8948506:12

No schema decisions up front

Filter events by type, contract, sender or recipient before you commit to a table shape.

Finality is in the envelope

Every page reports the tip, the finalized height and any reorg that crossed your cursor.

You can audit the whole set

Coverage, row counts and a digest are published and signed. Checking your copy costs nothing.

The indexer

Your indexer is one file

consume walks every transfer since sBTC deployed at block 328,228, then keeps following the tip. Kill it mid-backfill and it resumes from the last committed row. No queue, no cron, no cursor table to design.

indexer.tsts
1import { SecondLayer } from "@secondlayer/sdk";
2import { kyselySink } from "@secondlayer/sdk/sinks/kysely";
3import { database } from "./database";
4
5const secondlayer = new SecondLayer();
6
7await secondlayer.index.events.consume({
8 eventType: "ft_transfer",
9 contractId: "SM3V…sbtc-token",
10 fromHeight: 328_228,
11 sink: kyselySink(database, {
12 id: "sbtc-transfers",
13 tables: ["transfers"],
14 height: "height",
15 }),
16 onBatch: async (transfers, _envelope, ctx) => {
17 await ctx.tx.insertInto("transfers")
18 .values(transfers.map(toRow)).execute();
19 },
20});

7backfill first, then the tip

10block sBTC was deployed

13rolled back on a reorg

17rows + checkpoint, one commit

The schema

Tables that can't drift

The CLI writes typed tables from the same read contract the API serves. Change the event type and the compiler tells you which column broke.

~/sbtc-transfers
$secondlayer codegen index --target kysely \
--tables decoded_events -o schema.ts
✓ Wrote kysely Index schema to schema.ts
$secondlayer codegen index --target drizzle -o schema.ts
$secondlayer codegen index --target prisma -o schema.prisma
Deploy

Ship it where you ship everything else

One Dockerfile, any host that keeps a process running. A health route that tells a long backfill from a stuck loop, and a shutdown that always finishes the batch in flight.

indexer.tsschema.tsdatabase.tsDockerfile
1{
2 "deploy": { "healthcheckPath": "/health" }
3}
Deploy guide →
indexer.tsts
1import { consumerHealth } from "@secondlayer/sdk";
2
3const health = consumerHealth();
4const shutdown = new AbortController();
5
6Bun.serve({
7 port: Number(process.env.PORT),
8 fetch: health.handler,
9});
10process.on("SIGTERM", () => shutdown.abort());
11
12await secondlayer.index.events.consume({
13 // eventType, sink and onBatch as above
14 onProgress: health.record,
15 signal: shutdown.signal,
16});
Who runs what

We meter the rows. You own the rest.

secondlayerIndexEvery event since genesis, decoded and signed
you · Renderindexer.tsYour filters, your logic, your deploy
you · any Postgresyour tablesRead them as often as you want, free

We run the decoded index, finality and the signed manifest.

You run the code, the host, the database and the uptime. Filter tighter and the bill drops with the output.

Volume meter$5 / 1M rows

Rows delivered, dropping to $2 per 1M past $50 of monthly spend. Filters cut the bill along with the output.

Free every month1M rows

Plus block headers and every verify, on any account. Reading the index stores nothing, so there is no capacity charge.

Explore

Look before you write the loop

The CLI reads the same index with the same filters. Find the shape you want, then move those flags into consume unchanged.

~/sbtc-transfers
$secondlayer index ft-transfers \
--contract-id SM3V…sbtc-token --limit 1
{
"events": [{
"block_height": 8948506,
"sender": "SP2J…R4K",
"recipient": "SP3F…Q9A",
"amount": "25000000", …
}],
"next_cursor": "8948506:12",
"reorgs": []
}
The signed set

Checking us is free, forever

The canonical set is published as one signed manifest: coverage, counts, a digest and the finality rule. Check your own database against it read-only, as often as you like, and repair only what is broken.

~/instance
$secondlayer verify all \
--against https://archive.secondlayer.tools/latest.json
manifest secondlayer-canonical v1 · mainnet
coverage 0 → 9,266,609 (9,266,610 blocks)
✓ 0 broken ranges · 0 gaps · nothing uploaded
$secondlayer repair --apply \
--against https://archive.secondlayer.tools/latest.json
planned 3 partitions · events
allowance 15 of 18 free partitions left this month
✓ charged $0.00
signedsecondlayer-canonical · v1 · mainnet
DatasetRowsShare of the setCoverage
events299,680,847
88.37%
0 → 9,266,609
transactions30,154,578
8.89%
0 → 9,266,609
blocks9,266,610
2.73%
0 → 9,266,609
Decoded datasets

Data nobody else decodes

Same rows, same price as any other event. Each one says exactly what it covers.

live

PoX-5

Stacking prints by signer and cycle. The print log, not a position scoreboard.

example stack-aggregation-commit cycle 118 · signer 02a1…7f
live

sBTC

Deposits in and withdrawals out, each tied to its Bitcoin transaction.

example deposit · 0.25 BTC Bitcoin transaction 7c1e…a0 · confirmed
Coming soon

Contract state writes

Every var and map a contract committed, keyed, including contracts that never print.

example map_set · balances key SP3F…Q9A → u2500000
rolling out

Runes

Etchings, mints and balances from block 840,000, checked against ord with 0 mismatches.

example mint · UNCOMMON•GOODS height 968,557 · amount 1
Coverage

Thirteen event types, every contract

Scope any of them to one contract, a set of up to twenty, a trait, or the addresses a factory deployed.

stx_transferstx_mintstx_burnstx_lockft_transferft_mintft_burnnft_transfernft_mintnft_burncontract_callcontract_deployprint_event

Run it yourself

Hosted is api.secondlayer.tools. Point SECONDLAYER_API_URL at your own instance and the same CLI, SDK and /v1 routes answer. Reads over loopback need no key. bootstrap stands a fresh box up from the same signed partitions instead of replaying the chain.

Index questions

How do I know the history is complete?
The manifest publishes coverage (0 → 9,266,609) and a block count (9,266,610). Count equals span, so there is no room for a gap. secondlayer verify checks your own copy against the signed document for free.
What does "assurance: db-reconstructive" mean?
It states what the archive can rebuild: the database as the indexer wrote it, rather than a re-derivation from a node. The strength of the claim is published next to the claim.
When is a block final?
At six Bitcoin confirmations. Every response carries the current tip and finalized height, so you can treat anything above the finalized height as provisional.
How is it billed?
By rows delivered. $5 per 1M, then $2 per 1M past $50 of monthly spend. The first 1M rows each month are free, and so are block headers and verification. No seats, no plans.
Should I use the index or a subgraph?
The index while you are exploring, a subgraph once you know the shape. Reading your own subgraph tables is free; the index bills the rows it delivers.

Ship your first indexer today