Stable

secondlayerryanwaits/secondlayer

The hosted indexer for Stacks

GitHubDemo ↗

Problem
Build your own indexer, or trust a vendor's servers. Rarely a middle ground.
Index
Curl decoded chain data, keyless. /v1/index/events
Subgraphs
Your schema, hosted. One defineSubgraph() file → Postgres + REST.
Streams
The raw signed event firehose, for building your own indexer.
Self-host
Same code as hosted. docker compose up, no asterisk.
The Problem

No Cohesive Way to Get Stacks Data

Want chain data from Stacks? Build your own indexer from scratch, or hand it to a vendor and hope they stay up, keep the API stable, and never lock you in. There's rarely a middle ground — and running the vendor's own stack yourself is almost never on the table.

Start Here

Index

Curl decoded chain data keyless — no key, no schema, nothing to run. Typed accessors cover events, transfers, blocks, and transactions, so most of what people actually ask for doesn't need custom indexing. Free during the open beta; a key only gates writes.

Terminal
curl "https://api.secondlayer.tools/v1/index/events?event_type=ft_transfer&limit=5"
{ "data": [ ... ], "next_cursor": "eyJ..." }
# No key. No schema. No infra.
Your Schema, Hosted

Subgraphs

Need something the built-in accessors don't cover? Three steps, in order, from a deployed contract to a public table.

  1. 1

    Define it

    One TypeScript file: sources, schema, and handlers that turn matched events into rows.

    subgraphs/stx-transfers.ts
    import { defineSubgraph } from "@secondlayer/subgraphs";
    export default defineSubgraph({
    name: "stx-transfers",
    version: "1.0.0",
    startBlock: 0,
    sources: {
    transfer: { type: "stx_transfer" },
    },
    schema: {
    transfers: {
    columns: {
    sender: { type: "principal", indexed: true },
    recipient: { type: "principal", indexed: true },
    amount: { type: "uint" },
    },
    },
    },
    handlers: {
    transfer(event, ctx) {
    ctx.insert("transfers", {
    sender: event.sender,
    recipient: event.recipient,
    amount: event.amount,
    });
    },
    },
    });
  2. 2

    Deploy it

    Backfills history from the given block, then stays live as new blocks arrive.

    Terminal
    sl subgraphs deploy subgraphs/stx-transfers.ts --start-block <recent-block>
  3. 3

    Query it

    Live on the public REST API immediately — no separate publish step.

    Terminal
    sl subgraphs query stx-transfers transfers --sort _block_height --order desc
Raw, If You Want It

Streams

Subgraphs still too much abstraction? Streams is the raw signed event firehose underneath everything else here — the same data secondlayer's own decoder and subgraphs run on. Cold history ships as sha256-checked, ed25519-signed parquet dumps; the live tail is checkpointed, so a restart resumes exactly where it left off, reorg and all.

Terminal
sl streams pull --to ./dump
duckdb -c "SELECT event_type, count(*) FROM read_parquet('./dump/**/*.parquet') GROUP BY 1"
# Cold history: signed parquet dumps
# Live tail: checkpointed, auto reorg rewind
Same Code, Your Infra

Self-Host

The hosted version and the self-hosted version are the same code. Clone the repo, fill in a few secrets, and docker compose up gets you the indexer, Postgres, and REST API on your own hardware — no vendor lock-in, no asterisk on the open-source claim.

Terminal
git clone https://github.com/ryanwaits/secondlayer
cp docker/.env.example docker/.env
docker compose -f docker/docker-compose.yml up -d
# indexer, api, and postgres running

A Separate Package — @secondlayer/stacks

Typed Contract Calls

Not part of the indexer, a different layer entirely: a viem-style client for writing Stacks code. Get a typed contract once, then read and call it like normal TypeScript.

await contractCall({
contractAddress: "SP2...",
contractName: "my-contract",
functionName: "transfer",
functionArgs: [
uintCV(100), // amount? recipient?
principalCV(to), // is this right?
],
});
// No autocomplete. No type safety.
A Separate Package — @secondlayer/stacks

Bitcoin, NativelyIn Progress

The same stacks package does native Bitcoin SPV verification: Stacks contracts can now check that a Bitcoin transaction was actually mined, but the built-ins need the merkle proof shaped exactly right — byte order, witness stripped, args encoded. This does that off-chain prep and ships a reference verifier contract. Real, working code — the on-chain round-trip already passes in Clarinet simnet, just not merged to main yet.

verify.ts
import { verifyBitcoinPayment, fallbackProofSource } from "@secondlayer/stacks/bitcoin";
const result = await verifyBitcoinPayment(client, {
txid: "f4184fc5...",
source: fallbackProofSource([rpcSource, esploraSource]),
expect: { address: "1A1zP1...", amount: 5_000_000_000n },
});
// → { verified, mined, output, proof }
7/7 passing in Clarinet simnet — bun test packages/stacks/src/bitcoin/__tests__/onchain.simnet.test.ts

Get Started

Pick Your Surface

No infrastructure, hosted infrastructure, or your own infrastructure for the indexer — plus the typed client, separately, if you're writing contract calls.

Zero-infra
curl "…/v1/index/events
?event_type=ft_transfer"
Hosted
bun add -g @secondlayer/cli
sl login
sl subgraphs deploy ./s.ts
Self-hosted
git clone .../secondlayer
docker compose up