Changelog
Notable changes to the Secondlayer platform and SDKs.
Reorg-safe contract lookups
GET /v1/contracts/:contractId no longer serves contracts that were reorged
out of the canonical chain. A reorg flips the registry row non-canonical, and
the moment the deploy reappears on the new fork, discovery restores it
automatically — your fetched ABIs survive the round trip.
- Trait discovery (
/v1/contracts?trait=) and as-of-block trait resolution already excluded reorged-out contracts; the by-id read now agrees with them. - The burnchain reward tables (
burn_block_rewards,burn_block_reward_slots) drop their never-enforcedcanonicalcolumn — replace-per-height is the documented reorg contract for burnchain data. If yoursl index codegenmirror includes these tables, re-run codegen and drop the column; it was alwaystrue, so nothing is lost.
SIP-045 staking post-conditions, ready before the fork
@secondlayer/stacks@2.10.0 speaks the Epoch 4.0 wire format ahead of the
Bitcoin Staking hard fork (SIP-045, targeted ~July 29). The transaction codec
decodes and encodes the two new post-condition types — 0x03 Staking and
0x04 PoX — byte-identical to the reference implementation, so your decoders
don't break the day pox-5 transactions hit the chain.
- Protect staking calls with
staking-postcondition(principal + amount bound, evaluated onstake,register-for-bond,stake-update) andpox-postcondition(will-not-perform/may-perform/will-perform, evaluated onunstake,announce-l1-early-exit, and other non-locking PoX calls). - The deserializer now fails loud on unknown post-condition types instead of silently misreading everything after them — malformed or future-format transactions surface as errors, not corrupt data.
ClarityVersion.Clarity6ships for Epoch 4.0 contract deploys (SIP-044).
Type names mirror stacks.js, so post-conditions written for either SDK are portable. See the Stacks SDK docs.
Build your app index with consume()
Index now ships the loop. index.events.consume() and index.contractCalls.consume() are checkpointed consumers over decoded rows, with no node, decoders, or sync loop to hand-roll.
- Write your rows inside
onBatch, return the cursor you committed, and crash-restart resumes exactly there. - Reorgs rewind the cursor to the fork point automatically.
finalizedOnlyholds delivery to rows at or belowtip.finalized_height(which the Index tip now reports).fromHeight: 0backfills from genesis.- Runnable example:
sales-indexindexes every Gamma marketplace sale into your own Postgres in ~50 lines, then graduates to the same table as a one-file Subgraph.
Empirical print-event schemas
Clarity ABIs don't describe print payloads, and the shape varies per topic within one contract. New GET /v1/index/contracts/:id/print-schema infers each topic's real payload schema from indexed history: sampled print events deserialized from raw Clarity, with exact Clarity types, TS types, and subgraph column types per field.
sl subgraphs create <name> --from-contract <id>scaffolds a fully typed subgraph (per-topicprintsmaps to typedevent.data;--table-per-topicto normalize).codegen --payloadsemits the payload types as a.d.ts.- Deploys now warn when a handler reads a field never observed on-chain.
- SDK:
sl.index.printSchema(). MCP:index_print_schema.
PoX-4 stacking decoded
Index now decodes PoX-4 stacking. stack-stx, delegate-stx, and reward actions are normalized into typed rows at /v1/index/stacking; filter by stacker, caller, or PoX function with no node and no decoders of your own.
Keyless reads, plus pay-as-you-go credits
Index reads need no API key. Anonymous requests work with wildcard CORS, and a free-tier key reads Index too (never slower than anonymous).
- Free and anonymous reads cover the recent 24-hour window; a read into older history returns
402 UPGRADE_REQUIRED. - Top up prepaid credits with a card (
POST /api/billing/topup, packs $10–$100) to read older history without a plan. - A positive balance reads beyond the window, unthrottled, debited $5 per 1,000,000 rows across both Index and Streams from one shared balance, with the prepaid balance as a hard cap.
- Public chain data stays public.
Trait-filtered event queries
Restrict any decoded event type or contract call to a SIP standard with
?trait=. sip-010, sip-009, and sip-013 scope results to conforming
contracts without enumerating every contract id.
Aggregates
Roll up a subgraph table server-side instead of paging every row. The new
/aggregate endpoint runs count, sum, min, max, and distinct counts
over the same filters as the list endpoint.
const stats = await sl.subgraphs.sbtcFlows.transfers.aggregate({
where: { sender: "SP3PE7Q9..." },
sum: ["amount"],
countDistinct: ["recipient"],
});sum/min/max are numeric-only (enforced at compile time) and return
lossless strings; the result shape is inferred from the spec. Also available
over REST and MCP. See SDK aggregates.
Safer schema changes on your own database
For bring-your-own-database subgraphs, a breaking schema change (removed table/column, changed type, or a forced reindex) is now refused, keeping your data intact.
- The deploy returns the exact migration plan: the
DROP SCHEMA … CASCADEplus the rebuild DDL, so you can run it yourself and re-deploy. sl subgraphs deployprints the plan.- The SDK throws a typed
ByoBreakingChangeErrorexposingreasonsandplan.
Subscriptions went polymorphic. A subscription is now one of two kinds: subgraph (fires on subgraph table rows, as before) or chain, a webhook on raw chain events with no subgraph at all. The lambda for Stacks.
Direct chain subscriptions
Pass a triggers array instead of a subgraph table. A chain subscription is forward-looking: it starts at the chain tip and fires the moment a matching event lands, with no deploy and no backfill.
import { trigger } from "@secondlayer/sdk";
await sl.subscriptions.create({
name: "amm-swaps",
url: "https://my-app.com/webhook",
triggers: [
trigger.contractCall({ contractId: "SP....amm", functionName: "swap-*" }),
trigger.ftTransfer({ trait: "sip-010", minAmount: "1000000" }),
],
});trigger.* builders cover every event type (contractCall, contractDeploy, printEvent, and the stx*/ft*/nft* transfer/mint/burn variants), with * wildcards and a trait filter that scopes to a whole SIP. Over REST, POST /api/subscriptions accepts the same triggers array (1–50).
Apply / rollback delivery
Chain deliveries carry a typed envelope: a match sends chain.{type}.apply
(with block_hash, block_height, tx_id, canonical, the matched trigger,
and the event), and a reorg sends chain.reorg.rollback (with
fork_point_height and the orphaned events) so you can cleanly undo. Delivery
stays at-least-once and HMAC-signed; key your state on (tx_id, block_hash).
The raw-event availability layer matured across the board.
@secondlayer/sdk@5.9.0, @secondlayer/cli@8.2.0, @secondlayer/shared@6.11.0.
Query
New payload filters on /v1/streams/events: sender, recipient, and
asset_identifier (exact-match), alongside the existing contract_id and
types. Event types that lack a field simply don't match, so the firehose
narrows naturally.
curl -H "Authorization: Bearer $SL_API_KEY" \
"https://api.secondlayer.tools/v1/streams/events?types=ft_transfer&sender=SP...&limit=50"Finality
/v1/streams/tip now returns finalized_height, and every event carries a
finalized flag. Finality is anchored to Bitcoin (burn-block) confirmations
rather than a fixed count of Stacks blocks, so a finalized event will not reorg.
Caching
Fully-finalized pages (a closed range with to_height ≤ finalized_height) are
served Cache-Control: public, max-age=31536000, immutable with a weak ETag
and 304 on If-None-Match. Tip-spanning requests stay private, max-age=2,
so historical reads are cheap while the live edge stays fresh.
Proofs
Streams responses are signed with ed25519. Each read carries an X-Signature
(over the exact response body) and X-Signature-KeyId. Fetch the public key at
GET /public/streams/signing-key and verify, or set verify on the SDK
client; you can trust the data without trusting the server.
const streams = createStreamsClient({ apiKey, verify: true });Bulk & backfill
Download all of it. Finalized history is published as bulk dumps with an ed25519-signed manifest; the SDK verifies that signature by default (verifyDumpsManifest) before trusting any per-file sha256 it lists.
- SDK:
client.dumps.list()/download(file)and a one-callevents.replay({ from: "genesis", onDumpFile, onBatch })that backfills cold history from dumps, then tails live from the manifest's finalized cursor with no gap or duplicate at the seam. - CLI:
sl streams pull --to ./dumpdownloads finalized dumps locally (dumps are public, no API key).
Reliability
Reorgs now archive orphaned rows instead of deleting them, keeping the raw log intact and auditable. The indexer gained leader election, making it safe to run as multiple instances.