@secondlayer/cli
v15.0.1
Published
Run a Stacks index on your own hardware — set it up, restore verified history, and keep it current.
Downloads
2,656
Maintainers
Readme
@secondlayer/cli
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.
Auth
Default API is http://127.0.0.1:3800. Override with SL_API_URL.
Writes against a published bind use INSTANCE_TOKEN from secondlayer init.
Loopback reads need no token. Hosted API and archive credits use
SECONDLAYER_API_KEY (sk-sl_*).
export SL_API_URL=http://127.0.0.1:3800
export INSTANCE_TOKEN=<from secondlayer init>Quickstart
bun add -g @secondlayer/cli
secondlayer setup
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 descsecondlayer setup is a guided wizard: it picks network + node mode (with the
RAM/disk floor shown live), generates secrets, writes docker-compose.yml and
.env (including the DATABASE_URL every later bootstrap/verify/repair
in that directory connects with) into a target directory, brings the stack up,
prints the observer stanza for an external node, and restores + verifies
history from the archive: the one-command replacement for init → hand-copy
secrets → docker compose up → observer → bootstrap → verify. setup
is also the only way to bring the stack up: the old secondlayer start, which
printed a compose line for a monorepo checkout, is gone. Without a TTY (or
with --yes), it skips the interactive prompts and runs from flags instead;
--network and --node-mode are then required, and --against is required
unless you pass --skip-bootstrap:
secondlayer setup --yes \
--network mainnet --node-mode external \
--against https://archive.secondlayer.tools/latest.jsonsecondlayer subgraphs scaffold writes the definition file, creates/updates
package.json, and runs bun install (pass --no-install to skip).
Archive
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.jsonCommands
Local runtime
No account. Writes .env.local, restores history, prints the Stacks observer stanza.
| Command | What it does |
|---|---|
| secondlayer setup [--network …] [--node-mode external\|stacks\|full] [--api-port <spec>] [--dir <path>] [--against <manifest>] [--skip-bootstrap] [--skip-verify] [--yes] [--force] | Guided self-host onboarding — secrets, compose + .env, docker up, observer stanza, bootstrap, verify. TUI when interactive; flags-only (no prompts) with --yes or no TTY |
| 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] [--recovery journal\|archive] [--network …] | Print the [[events_observer]] stanza. Signer-shared requires --recovery |
| secondlayer verify [all\|raw\|decode:<name>\|subgraph:<name>] --against <manifest> [--quick\|--deep\|--anchor] | Compare local data to a signed archive. Default target raw. Exit 0 clean, 1 diverged, 2 unanchored |
| 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.
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.
Subgraphs
| 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 |
Data products (reads)
| Command | What it does |
|---|---|
| secondlayer index ft-transfers / nft-transfers / events --event-type <t> / contract-calls | Decoded Index layer. Anonymous reads OK |
| 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. 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.
Webhooks
| Command | What it does |
|---|---|
| secondlayer webhooks create <name> --subgraph <name> --table <name> [--runtime <inngest\|trigger\|cloudflare\|node>] [--url <url>] | Subgraph webhook (optional local receiver scaffold). Runtime defaults to node once any of -s/-t/-u or --no-scaffold is given; the menu only appears in a terminal with no flags |
| secondlayer webhooks create <name> --url <url> --trigger '<json>' | Chain webhook (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> [--filter key.gte=value] | Patch URL, filter, 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. Filters are schema-aware: unknown tables/columns, bad operators,
and non-scalar columns are rejected before the API call.
Webhooks are subgraph (a table's rows) or chain (raw events, no
subgraph). secondlayer webhooks create with --subgraph/--table makes the
first. Pass --trigger or --triggers-file for the second. SDK, REST, and
MCP take the same triggers array. Every other secondlayer webhooks command
operates on both kinds.
Other
| 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 |
Environment variables
| Var | Purpose |
|---|---|
| INSTANCE_TOKEN | From secondlayer init for writes. Loopback reads need no token |
| SECONDLAYER_API_KEY | Hosted account key (sk-sl_*). SL_API_KEY is a one-release hosted fallback |
| SL_API_URL | Instance API. Default http://127.0.0.1:3800 |
| SL_PLATFORM_API_URL | Alias of SL_API_URL |
| STACKS_NETWORK | Default network (also via --network <mainnet\|testnet\|devnet>) |
| DATABASE_URL | Postgres that bootstrap, verify, repair, and backup connect to. secondlayer setup writes it into .env pointing at the compose Postgres; 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 |
Code generation (secondlayer codegen contracts)
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()Local development
secondlayer setup brings up a self-hosted stack. For Clarinet, use
secondlayer devnet connect / status / logs / down (and devnet faucet).
Docs
Full reference: https://secondlayer.tools/docs
License
MIT
