CLI
The sl command-line tool — authenticate once, manage subgraphs and subscriptions from your terminal.
Install
bun add -g @secondlayer/cliCommon commands
sl login # authenticate (opens the browser)
sl subgraphs deploy # deploy a subgraph from a config
sl subgraphs ls # list your deployments
sl subscriptions create # wire up a webhook subscriptionEvery tool shares one session — sign in once and the SDK, CLI, and MCP server all inherit it.
ORM codegen
Generate a typed ORM schema for a subgraph's tables — pair it with --database-url to query your own Postgres with Prisma or Drizzle (joins, relations, transactions):
sl subgraphs codegen ./subgraph.config.ts --target prisma -o prisma/schema.prisma
sl subgraphs codegen ./subgraph.config.ts --target drizzle -o db/schema.tsFor Kysely, run kysely-codegen against your database. See Subgraphs.
Local devnet
Run the full pipeline against a local Clarinet devnet — write Clarity, clarinet devnet start, and index it locally with subgraphs, datasets, and subscriptions. Something Chainhooks can't do.
cd my-clarinet-project
sl devnet connect # patches Devnet.toml + starts the Secondlayer stack
clarinet devnet start # your normal command — events now stream to the indexer
SL_API_URL=http://localhost:3800 SL_API_KEY=dummy \
sl subgraphs deploy ./subgraph.tsconnect pulls the published OSS images (Docker required) — no repo checkout needed. Stop with sl devnet down (add --purge to wipe the local index when restarting your devnet from scratch).
To populate rows you need a real contract-call transaction (clarinet console is simnet-only and won't broadcast on-chain). Fire one with @stacks/transactions:
import {
broadcastTransaction,
getAddressFromPrivateKey,
makeContractCall,
} from "@stacks/transactions";
const key =
"753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601"; // devnet deployer
const sender = getAddressFromPrivateKey(key, "testnet");
const { nonce } = await fetch(
`http://localhost:3999/v2/accounts/${sender}?proof=0`,
).then((r) => r.json());
const tx = await makeContractCall({
contractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM",
contractName: "counter",
functionName: "increment",
functionArgs: [],
senderKey: key,
network: "devnet",
fee: 3000n,
nonce: BigInt(nonce),
});
console.log(await broadcastTransaction({ transaction: tx, network: "devnet" }));The row appears at /api/subgraphs/<name>/<table> within ~5s.