secondlayerryanwaits/secondlayer
The hosted indexer for Stacks
- 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.
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.
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.
curl "https://api.secondlayer.tools/v1/index/events?event_type=ft_transfer&limit=5"{ "data": [ ... ], "next_cursor": "eyJ..." }# No key. No schema. No infra.
Subgraphs
Need something the built-in accessors don't cover? Three steps, in order, from a deployed contract to a public table.
- 1
Define it
One TypeScript file: sources, schema, and handlers that turn matched events into rows.
subgraphs/stx-transfers.tsimport { 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
Deploy it
Backfills history from the given block, then stays live as new blocks arrive.
Terminalsl subgraphs deploy subgraphs/stx-transfers.ts --start-block <recent-block> - 3
Query it
Live on the public REST API immediately — no separate publish step.
Terminalsl subgraphs query stx-transfers transfers --sort _block_height --order desc
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.
sl streams pull --to ./dumpduckdb -c "SELECT event_type, count(*) FROM read_parquet('./dump/**/*.parquet') GROUP BY 1"# Cold history: signed parquet dumps# Live tail: checkpointed, auto reorg rewind
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.
git clone https://github.com/ryanwaits/secondlayercp docker/.env.example docker/.envdocker compose -f docker/docker-compose.yml up -d# indexer, api, and postgres running
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.
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.
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 }
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.
curl "…/v1/index/events?event_type=ft_transfer"
bun add -g @secondlayer/clisl loginsl subgraphs deploy ./s.ts
git clone .../secondlayerdocker compose up