Skip to content

Latest commit

 

History

History
271 lines (208 loc) · 14.7 KB

README.md

File metadata and controls

271 lines (208 loc) · 14.7 KB

@secondlayer/cli

The Secondlayer CLI — one binary for Stacks indexing, real-time subgraphs, webhooks, and Clarity code generation.

bun add -g @secondlayer/cli
secondlayer --version

sl is a short alias of secondlayer.

Auth

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>

Hosted commands (webhooks, subgraphs) — credential resolution order

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.

Quickstart

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 desc

To 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).

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.json

Commands

Local runtime

No 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.

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:

  1. The manifest's partition list is priced with a free, no-write call (POST /api/archive/quote).
  2. The quote prints into the existing plan output, for example metered: 528 partitions ≈ $44.00 · balance $50.00, or for repair inside its monthly allowance, metered: free (4 of 6 monthly repair fetches remaining).
  3. You confirm, or pass -y to skip the prompt. -y never skips the quote or the balance check: if the balance is short, the command exits before any partition is fetched and prints the shortfall and secondlayer credits buy. --json never stands in for -y: without -y it 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.
  4. 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 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.

Webhooks

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.

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_*)
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

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 changes

Where 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

A self-hosted stack runs from docker/oss (Self-host). For Clarinet, use secondlayer devnet connect / status / logs / down (and devnet faucet).

Docs

Full reference: https://www.secondlayer.tools/docs

License

MIT