.md

CLI

Deploy subgraphs and webhooks hosted or on your own box, and run the instance, from one terminal.

$bun add -g @secondlayer/cli

The binary is secondlayer. sl is a short alias.

Command map

Every top-level secondlayer command, with the page that owns it.

CommandWhat it doesDocs
initWrite the instance token and keys to .env.localLocal runtime
bootstrapRestore chain history from a verified archiveLocal runtime
observerPrint the node's events_observer stanzaSelf-host
login / logoutSign in through your browser; logout signs the machine out (--email for an emailed code, --with-token to read a key from stdin)Keys and billing
whoamiShow the instance and account targetsKeys and billing
subgraphsCreate, deploy, query, reindex, and backfill subgraphsSubgraphs
webhooksCreate, test, pause, replay, and inspect webhook deliveriesWebhooks
streamsRead raw chain events, tips, reorgs, and bulk dumps (--chain bitcoin for Runes events)Streams
indexQuery decoded events, transfers, contract calls, blocks, and Runes (index runes)Index
codegenGenerate typed contract clients, prints, and ORM schemasbelow
devnetRun Secondlayer against a local Clarinet devnetLocal chain
statusLocal instance healthLocal runtime
doctorDiagnose the full stackSelf-host
configRead and write CLI configurationsecondlayer config --help
contextPrint an orientation snapshot of tips, subgraphs, webhooks, and operationsMCP and skills
verifyCompare local data to a signed archive; prove one block; replay a subgraphVerify
repairReplace local data that diverges from a signed archiveVerified archive
archivePrice, fetch, verify, and repair against the hosted archiveVerified archive
creditsBuy, check, and auto-refill your creditsKeys and billing
backupWrite a restorable bundle of the index and keysSelf-host
restoreRestore an instance from a backup bundleSelf-host
uninstallStop the stack; the index and keys surviveSelf-host
  • --api-key shape-routes hex to INSTANCE_TOKEN and sk-sl_* to SECONDLAYER_API_KEY.
  • Instance commands refuse api.secondlayer.tools, except secondlayer subgraphs and secondlayer webhooks, which take SECONDLAYER_API_KEY against the hosted URL (hosted subgraphs).
  • Credentials: Keys and billing and Instance token.
  • login prints a code and opens your browser; approve it and the CLI is signed in until logout. It never makes an API key; keys are for reading data and live under Keys. login --email asks for an emailed code instead.

Webhook triggers

  • webhooks create takes --trigger / --triggers-file.
  • SDK and REST accept the same triggers array (Triggers).

Local runtime

Write keys, restore history, print the observer stanza.

$secondlayer init --network mainnet
$secondlayer bootstrap --against https://archive.secondlayer.tools/.../snapshots/<digest>.json
$secondlayer observer --mode indexer
CommandFlagDefaultNotes
secondlayer init--networkSTACKS_NETWORK or mainnetmainnet, testnet, or devnet
--api-urlthe local API, port 3800Local API
--forceoffOverwrite generated values in .env.local
secondlayer status--jsonoffLocal instance health from /public/status
secondlayer bootstrap--againstrequiredManifest URL or file
--to-blockarchive tipStop early
--from-blockgenesisForward-only: start here, history below is out of scope
--verifyallPost-load digests: all or blocks
--public-keyresolvedPin the signing key
-y, --yesoffSkip confirm
--jsonoffMachine output
secondlayer observer--modeindexerindexer retries; signer-shared does not
--endpointindexer:3700 (loopback :3700 on devnet)host:port only, no URL
--networkSTACKS_NETWORK or mainnetLoopback refused except devnet
  • secondlayer bootstrap refuses a non-empty database. Fix an existing instance with secondlayer repair.
  • Exit 0 restored, 1 diverged, 2 refused.

secondlayer verify has three forms:

  • secondlayer verify [target] compares local data to a signed archive. Target is all or raw (default); the archive has no digests for decode:<name> or subgraph:<name> yet. --quick is identity, --deep is semantic, --anchor requires a verified signature. Flags live on Verified archive.
  • secondlayer verify block <height|index_block_hash> proves one Stacks block from the checkpoint built into the CLI, trusting nothing else. Flags --node, --checkpoint, --rows, --json; exit 0 proven, 1 a link is broken, 2 could not check. Links and output: Verify.
  • secondlayer verify subgraph <name> --replay recomputes a state subgraph from proven blocks and compares the served rows. --from/--to narrow the range. See Check rows.

Signer-shared nodes skip retries

A block the node fails to deliver stays a gap. secondlayer repair fetches it from the archive with credits. The full stanza: Connect your node.

$secondlayer subgraphs create sbtc-registry \
--from-contract SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-registry

Infers each print topic's payload from indexed history; see print schemas. Anonymous. --table-per-topic emits one table per topic.

  • secondlayer codegen prints <file> emits a .d.ts next to the definition.
  • Incompatible with --target / --models-only / --env.
  • Deploy warnings on unobserved event.data fields are advisory.

Commit your definition before deploying

secondlayer subgraphs deploy checks the source file is committed. An uncommitted deploy leaves the only copy in the database.

  • A terminal prompts. Scripts and CI fail.
  • --allow-uncommitted overrides and logs the skip.
  • Outside a git repo, and --dry-run, skip the check.

Turn Clarity contracts into typed TypeScript: local .clar files, deployed contracts, or a Clarinet project.

$secondlayer codegen contracts ./contracts/token.clar -o ./src/generated.ts
$secondlayer codegen contracts SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.alex-vault -o ./src/generated.ts
$secondlayer codegen contracts --watch # regenerate on .clar / config / Clarinet.toml changes

Deployed contract ids fetch the ABI (address prefix picks the network). Config-driven projects regenerate with secondlayer codegen contracts:

secondlayer.config.tsts
import { defineConfig } from "@secondlayer/cli";
export default defineConfig({
out: "./src/generated/contracts.ts",
clarinet: true, // or { path, include, exclude, includeRequirements }
});
  • clarinet: true loads real ABIs from your Clarinet project via Simnet, including typed clients for [project.requirements] dependency contracts (includeRequirements: false to opt out).
  • It skips silently when Clarinet.toml is missing.

The output:

Run the full Secondlayer stack (Postgres, indexer, API, subgraph processor) against your local Clarinet devnet. No cloud, no accounts, no key to read your data.

Coming from Chainhook?

This is the local path; map your predicates over with the Chainhook migration prompt.

Connect

From inside your Clarinet project:

~/clarinet-project
$secondlayer devnet connect
Secondlayer stack up
api → http://localhost:3800
indexer → http://localhost:3700

Start the devnet

Start the devnet; it auto-deploys your contracts and streams every block to the indexer.

$clarinet devnet start

Prerequisites

  • Clarinet and a Clarinet project (a directory with Clarinet.toml).
  • Docker (Docker Desktop or OrbStack on macOS).
  • The CLI: bun add -g @secondlayer/cli.

What connect does

  • Patches settings/Devnet.toml so the devnet node forwards events to the local indexer (stacks_node_events_observers = ["host.docker.internal:3700"]).
  • Writes .secondlayer/docker-compose.yml for the published OSS images.
  • Brings the stack up with docker compose up -d.

Options

FlagDefaultPurpose
--project <dir>nearest Clarinet.tomlClarinet project directory
--image-tag <tag>latestPublished image tag to run
--owner <owner>Secondlayer namespaceghcr image owner to pull from
--no-up—Patch config + write compose without starting Docker

Patch config without starting Docker

Pass --no-up to write settings/Devnet.toml and the compose file without starting containers.

Bring it up yourself with docker compose -f .secondlayer/docker-compose.yml up -d.

Deploy a subgraph against it

Point the CLI at the local API. There is no login: a deploy is a write, and the generated stack ships a fixed local token, dev-instance-token, for exactly that:

$export SECONDLAYER_API_URL=http://localhost:3800
$export INSTANCE_TOKEN=dev-instance-token
$secondlayer subgraphs deploy ./subgraph.ts

It is a local-dev value, not a secret; a real instance gets its own from secondlayer init.

Fire a contract call in your devnet and the matching rows land in your subgraph's tables. Reading them back takes no credential: the compose publishes the API on loopback only, and /v1 reads are keyless whenever the API is reachable from this box alone.

$curl "$SECONDLAYER_API_URL/v1/subgraphs/my-app/balances"

See Instance token.

Watch it work

~/clarinet-project
$secondlayer devnet status
STACK
● indexer healthy :3700
● api healthy :3800
INGEST
chain tip 142 indexed 142 lag caught up last block 2s ago
SUBGRAPHS
my-balances active block 142 balances · 37 rows

Add -w to refresh every 2s, or --limit <n> to change how many recent rows show.

Tail logs for the whole stack or one service:

$secondlayer devnet logs indexer -f
  • Services: indexer, api, subgraph-processor, postgres.

Tear down

$secondlayer devnet down # stop the stack
$secondlayer devnet down --purge # stop and wipe the local index volumes

These work but get no further investment.

  • secondlayer codegen subgraph --target prisma|drizzle|kysely: typed ORM schema. See Subgraphs.
Edit on GitHub