The Secondlayer CLI — one binary for Stacks indexing, real-time subgraphs, webhooks, and Clarity code generation.
bun add -g @secondlayer/cli
secondlayer --versionsl is a short alias of secondlayer.
Default API is http://127.0.0.1:3800, or the hosted API when only SECONDLAYER_API_KEY is set. Override with SECONDLAYER_API_URL.
Writes against a published bind use INSTANCE_TOKEN from secondlayer init.
Loopback reads need no token. Your hosted account (credits, archive fetches)
uses secondlayer login or SECONDLAYER_API_KEY (sk-sl_*).
export SECONDLAYER_API_URL=http://127.0.0.1:3800
export INSTANCE_TOKEN=<from secondlayer init>Against api.secondlayer.tools, resolveHostedAuth() tries these in order —
the first one set wins:
| Order | Source | Notes |
|---|---|---|
| 1 | SECONDLAYER_API_KEY (sk-sl_*) |
Hosted account key |
| 2 | INSTANCE_TOKEN / --api-key |
Rarely set on the merchant host |
| 3 | Saved session, ~/.secondlayer/session.json |
Written by secondlayer login, scoped per API URL, 0600 |
| — | none of the above | Fails: Not logged in — run secondlayer login |
A shell with no SECONDLAYER_API_KEY exported can still authenticate against
the merchant host if this machine ran secondlayer login at some point — that's
#3, a real (if easy to forget) credential, not a bug.
bun add -g @secondlayer/cli
secondlayer subgraphs scaffold SP1234ABCD.my-contract -o subgraphs/my-contract.ts
git add subgraphs/my-contract.ts # deploy refuses an unstaged file; or pass --allow-uncommitted
secondlayer subgraphs deploy subgraphs/my-contract.ts --start-block <recent-block>
secondlayer subgraphs query my-contract <table> --sort _block_height --order descTo run your own instance, follow Self-host:
docker compose from docker/oss, with init, observer, bootstrap and
verify below for the steps around it.
secondlayer subgraphs scaffold writes the definition file, creates/updates
package.json, and runs bun install (pass --no-install to skip).
secondlayer archive groups the history-plane verbs. archive bootstrap,
archive verify, and archive repair are the same commands as the top-level
bootstrap / verify / repair (golden-path aliases stay). archive latest
shows the official tip; archive quote --against <manifest> prices a fetch
without charging.
secondlayer archive latest
secondlayer archive quote --against https://archive.secondlayer.tools/latest.json
secondlayer archive bootstrap --against https://archive.secondlayer.tools/latest.jsonNo account. Writes .env.local, restores history, prints the Stacks observer stanza.
| Command | What it does |
|---|---|
secondlayer init [--network mainnet|testnet|devnet] [--api-url <url>] [--force] |
Write .env.local (token, secrets key, webhook signing key). --network and --api-url are the global flags. Idempotent |
secondlayer bootstrap --against <manifest> [--from-block <n>] [--to-block <n>] [--verify all|blocks] [--public-key <pem>] [-y] [--json] |
Restore chain history from a verified archive into an empty database. A run that died mid-way resumes per dataset on re-run. After the load, digests for blocks, transactions, and events are checked over the restored range (--verify blocks skips the child datasets and the minutes they cost). Partition fetches retry three times with backoff on resets, timeouts, 429 (honoring Retry-After) and 5xx; a link that stays down exits 1 with a re-run hint, and the re-run resumes. Exit 0 restored, 1 diverged or interrupted, 2 refused |
secondlayer observer [--mode indexer|signer-shared] [--endpoint host:port] [--network …] |
Print the [[events_observer]] stanza. Signer-shared skips retries; a missed block stays a gap until repair |
secondlayer verify [all|raw] --against <manifest> [--quick|--deep|--anchor] |
Compare local data to a signed archive. Default target raw. Exit 0 clean, 1 diverged, 2 unanchored |
secondlayer verify block <height|index_block_hash> [--node <url>] [--checkpoint <file>] [--rows] [--json] |
Prove one Stacks block from the checkpoint built into this release: Bitcoin headers, burn block, signer set, signatures, state root, state diff, and with --rows the indexed rows. Blocks below the checkpoint are tied to it by hash (parent links or one MARF proof) instead of signatures. Nothing else is trusted; proofs come from the API, or a node with --node. Exit 0 proven, 1 a link is broken, 2 could not check |
secondlayer repair --against <archive> [--apply] [-y] |
Plan (default) or apply an archive repair. A fixed block is rewritten with its transactions and events from the archive; when the reference has no child partition for a height, the block is rewritten alone, the height is named with a bootstrap --from-block H --to-block H remedy, and the exit is 1. Partition fetches retry like bootstrap; a link that stays down exits 1 with a re-run hint, and heights already repaired are kept |
Bootstrap and repair against the official hosted archive (archive.secondlayer.tools)
are metered per partition; against any other manifest (a mirror, a teammate's
box, a local file) they are free. See Metered fetches.
secondlayer bootstrap and secondlayer repair pull partitions from the
signed archive instead of replaying the chain, and that pull costs money only
when it targets the official hosted archive. Point --against at a mirror or
a local manifest and nothing is charged, nothing is even contacted beyond
that manifest, because self-hosting the archive is a supported way to use
these commands, not a workaround.
Against the official host, both commands quote before they charge:
- The manifest's partition list is priced with a free, no-write call
(
POST /api/archive/quote). - The quote prints into the existing plan output, for example
metered: 528 partitions ≈ $44.00 · balance $50.00, or forrepairinside its monthly allowance,metered: free (4 of 6 monthly repair fetches remaining). - You confirm, or pass
-yto skip the prompt.-ynever skips the quote or the balance check: if the balance is short, the command exits before any partition is fetched and prints the shortfall andsecondlayer credits buy.--jsonnever stands in for-y: without-yit prints{"code":"CONFIRMATION_REQUIRED","quote":…}to stdout and exits 2 so a script can read the price, then re-run with-y. Without a TTY on stdin every confirmation exits 1 rather than letting an empty pipe answer it. - Only then does the command fetch, and only the partitions it actually reads are charged.
A partition already charged in the last 24 hours re-presigns for free, so a
resumed or retried bootstrap never pays twice for the same bytes. repair
gets 6 free range-bundles a month; bootstrap does not, since it is the
whole-chain operation the free tier exists to not subsidize.
secondlayer verify is unaffected: it reads manifests and digests, never
partition bytes, and stays free and anonymous no matter which archive it
points at.
| Command | What it does |
|---|---|
secondlayer subgraphs create <name> |
Scaffold a definition file |
secondlayer subgraphs scaffold <SP...::contract> [-o <path>] [--no-install] |
Generate a subgraph from a deployed contract |
secondlayer subgraphs deploy <file> [--start-block <n>] [-y] [--allow-uncommitted] |
Deploy; --start-block overrides the definition. The file must be staged or committed (git add), or pass --allow-uncommitted |
secondlayer subgraphs list |
List deployments (ls alias) |
secondlayer subgraphs dev <file> |
Watch + hot-redeploy |
secondlayer subgraphs query <name> <table> |
Query a table with filters, sort, pagination |
secondlayer subgraphs status <name> / gaps <name> |
Inspect a deployment |
secondlayer subgraphs spec <nameOrFile> [--format openapi|agent|markdown] |
Export API docs for a deployed subgraph or a local definition file |
secondlayer codegen subgraph <file> --target kysely|prisma|drizzle [-o <path>] |
Generate a typed ORM schema for the subgraph's tables |
secondlayer codegen client <name> -o <path> |
Generate a typed query client for a deployed subgraph |
secondlayer subgraphs reindex/backfill/stop/delete <name> |
Manage processing |
| Command | What it does |
|---|---|
secondlayer index ft-transfers / nft-transfers / events --event-type <t> / contract-calls |
Decoded Index layer. Anonymous reads OK |
secondlayer index runes / runes get <rune> / runes balances (--address <a> | --outpoint <txid:vout>) / runes activity [--kind etch,mint,transfer,burn] |
Bitcoin Runes: catalog, one rune, balances, activity. Hosted answers a "not configured" note until Runes data lands there |
secondlayer streams tip / events / consume / reorgs / canonical <h> / dumps |
Raw chain event firehose. consume emits one event per line; a reorg appears inline as {"kind":"reorg","fork_point_height":…} and the loop rewinds to re-deliver the canonical run, so a reader drops rows at or above the fork point when it sees one. --max-pages must be a positive integer. events, events by-tx, consume and reorgs take --chain bitcoin for Runes events (rune_* types; Stacks-only filters are refused). Requires INSTANCE_TOKEN past loopback |
Reads emit JSON to stdout (--json accepted across all read commands); -o/--output is a file path, not a format.
| Command | What it does |
|---|---|
secondlayer webhooks create <name> --url <url> --trigger '<json>' |
Create a webhook on chain events (repeat --trigger or pass --triggers-file) |
secondlayer webhooks list / get <id|name> |
List or show config + delivery state |
secondlayer webhooks update <id|name> --url <url> |
Patch URL, format, retry, etc. |
secondlayer webhooks pause/resume <id|name> |
Stop or restart delivery |
secondlayer webhooks rotate-secret <id|name> |
Rotate signing secret (printed once) |
secondlayer webhooks deliveries/dead <id|name> |
Recent attempts / dead-letter rows |
secondlayer webhooks requeue <id|name> <outboxId> |
Requeue one dead-letter row |
secondlayer webhooks replay <id|name> --from-block <n> --to-block <n> |
Enqueue a historical block range |
secondlayer webhooks doctor/test <id|name> |
Health check / signed fixture |
Read/action commands support --json. Destructive commands prompt unless
-y / --yes, default to no, and exit 1 when stdin is not a TTY.
Webhooks fire on raw chain events. secondlayer webhooks create takes
--trigger or --triggers-file; SDK, REST, and MCP take the same triggers
array. To react to your subgraph rows, watch the table from your own Postgres
or poll the subgraph API: see
React to your rows.
| Command | What it does |
|---|---|
secondlayer codegen contracts [files...] |
Generate TS interfaces from Clarity contracts |
secondlayer context |
Instance snapshot — Streams + Index tips, subgraphs, webhooks |
secondlayer doctor / secondlayer status |
Reachability + health checks |
secondlayer config get/set/reset/delete |
Inspect or reset local config |
| Var | Purpose |
|---|---|
INSTANCE_TOKEN |
From secondlayer init for writes. Loopback reads need no token |
SECONDLAYER_API_KEY |
Hosted account key (sk-sl_*) |
SECONDLAYER_API_URL |
API URL. Default http://127.0.0.1:3800, or hosted with only SECONDLAYER_API_KEY set |
STACKS_NETWORK |
Default network (also via --network <mainnet|testnet|devnet>) |
DATABASE_URL |
Postgres that bootstrap, verify, repair, and backup connect to. Point it at the compose Postgres (postgres://secondlayer:<POSTGRES_PASSWORD>@127.0.0.1:5432/secondlayer); unset, the shared dev URL postgres://postgres:postgres@localhost:5432/secondlayer_dev is used |
HIRO_API_KEY |
Used by secondlayer codegen contracts for remote contract fetches |
Generate type-safe interfaces, functions, and optional React hooks from Clarity
contracts — local .clar files, deployed contracts (network inferred from
address prefix), or globs.
secondlayer codegen contracts ./contracts/token.clar -o ./src/generated.ts
secondlayer codegen contracts SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.alex-vault -o ./src/generated.ts
secondlayer codegen contracts "./contracts/*.clar" -o ./src/generated.ts
secondlayer codegen contracts --watch # regenerate on .clar / config / Clarinet.toml changesWhere the types come from matters. A bare .clar path is read as source, and
source only declares argument, map, and data-var types — a function's return
type comes out of Clarity's type checker, so it generates as any. A deployed
contract id or clarinet: true (below) carries a real ABI, so returns are
exact too. Prefer those where you can.
Config-driven:
# write a secondlayer.config.ts, then:
secondlayer codegen contracts # regenerates from the config// secondlayer.config.ts
import { defineConfig } from "@secondlayer/cli"
export default defineConfig({
out: "src/generated.ts",
clarinet: true, // or { path, include, exclude, includeRequirements }
})clarinet: true loads ABIs from your Clarinet project via simnet, including
[project.requirements] dependency contracts (includeRequirements: false
to opt out). Skips silently when Clarinet.toml is missing.
Clarinet tests: getContract with @secondlayer/stacks/simnet.
Generated output includes named per-function type aliases (TokenTransferArgs,
TokenTransferResult), a TokenTypes bundle, and a tokenAbi const branded
with TypedAbi — getContract from @secondlayer/stacks picks up the brand
so hovers and type errors show the named aliases instead of expanded inline
types.
import { token } from "./generated/contracts"
// Generated call descriptors compose with any tx builder:
await makeContractCall({
...token.transfer({ amount: 100n, recipient: "SP..." }),
network: "mainnet",
})
// Maps/vars/constants accessors are built in:
const supply = await token.vars.totalSupply.get()A self-hosted stack runs from docker/oss (Self-host). For Clarinet, use
secondlayer devnet connect / status / logs / down (and devnet faucet).
Full reference: https://www.secondlayer.tools/docs
MIT