SDK
Talk to your instance from TypeScript without hand-writing cursor walks, reorg rewind, or signature checks.
Every export, with signatures: SDK reference.
bun add @secondlayer/sdkimport { SecondLayer } from "@secondlayer/sdk";
const sl = new SecondLayer({
apiKey: process.env.INSTANCE_TOKEN, // from secondlayer init; omit on loopback
// default: http://127.0.0.1:3800 (or SECONDLAYER_API_URL)
});Hosted Index, Streams, and archive use accountKey / SECONDLAYER_API_KEY. See Authentication.
sl.streams: raw, ordered chain events (cursor-paginated, replayable).sl.index: decoded rows: FT/NFT transfers, all event types, contract calls, andprintSchema(contractId).sl.contracts: find deployed contracts by trait (SIP-009/010/013).sl.subgraphs: your app-specific tables, plus open/v1reads (rows).sl.webhooks: create and manage webhook webhooks (subgraph rows or raw chain events).
const tip = await sl.streams.tip();
const page = await sl.streams.events.list({
types: ["ft_transfer"],
contractId: "SP000000000000000000002Q6VF78.sbtc-token",
limit: 10,
});Each surface documents its own calls:
| Task | Call | Documented in |
|---|---|---|
| Webhook on raw chain events | sl.webhooks.create({ name, url, triggers }) | Webhooks |
| Re-deliver history | sl.webhooks.replay(id, { fromBlock, toBlock }) | Replay |
| Check a delivery signature | verifyWebhookSignature, verifySecondlayerSignature | Receiving deliveries |
| Prove a tx is in a block | verifyTransactionProof, fetchRewardSet | Verification |
| Tail the live firehose | sl.streams.events.subscribe(opts) | Streams |
| Read subgraph tables | sl.subgraphs.rows, sl.subgraphs.typed | Subgraphs |
| Stream or roll up rows | subgraph.<table>.subscribe, .aggregate | Reading rows |
| Let a sink own the transaction | kyselySink, drizzleSink, bunSqliteSink | Sinks |
trigger.* is not on.*
trigger.* (from @secondlayer/sdk) configures chain webhooks. on.* (from @secondlayer/stacks) configures subgraph sources in a handler config. They configure different things. @secondlayer/stacks is the typed chain client, documented at stacks.secondlayer.tools.
consume polls and commits a cursor so you never miss or double-process. Return the committed cursor from onBatch. Available on sl.streams.events, sl.index.events (takes eventType), and sl.index.contractCalls.
await sl.index.contractCalls.consume({
contractId: "SP...marketplace-v4",
functionName: "purchase-asset",
fromCursor: await loadCheckpoint(), // null on first run
fromHeight: 0, // first run: backfill as far as the instance holds
onBatch: async (calls, envelope, ctx) => {
await commitRowsAndCheckpoint(calls, ctx.cursor);
return ctx.cursor;
},
onReorg: async (reorg) => {
await rollbackFromHeight(reorg.fork_point_height); // inclusive of the fork block
},
});Reorgs rewind the cursor to the fork point and fire onReorg. Fold running balances in a sink's onRollback, not there: Sinks. finalizedOnly holds delivery to rows at or below tip.finalized_height, and fromHeight: 0 backfills as far as your instance holds (archive restore is metered past that). Full options and the batch envelope: Index.
createStreamsClient({ apiKey }) verifies both REST reads (X-Signature) and SSE frames. The key is fetched once from /public/streams/signing-key; a rotated X-Signature-KeyId triggers a single refresh.
verify | Behavior |
|---|---|
| default (lenient) | Verify signed responses; pass unsigned through; throw on invalid |
true (strict) | A missing signature throws too |
{ publicKey } | Pin a known PEM |
false | Disable verification |
verify lives on createStreamsClient
new SecondLayer() does not accept verify; use createStreamsClient when you need it.