Reference / Changelog

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-enforced canonical column — replace-per-height is the documented reorg contract for burnchain data. If your sl index codegen mirror includes these tables, re-run codegen and drop the column; it was always true, 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 on stake, register-for-bond, stake-update) and pox-postcondition (will-not-perform / may-perform / will-perform, evaluated on unstake, 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.Clarity6 ships 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.
  • finalizedOnly holds delivery to rows at or below tip.finalized_height (which the Index tip now reports).
  • fromHeight: 0 backfills from genesis.
  • Runnable example: sales-index indexes 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-topic prints maps to typed event.data; --table-per-topic to normalize).
  • codegen --payloads emits 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 … CASCADE plus the rebuild DDL, so you can run it yourself and re-deploy.
  • sl subgraphs deploy prints the plan.
  • The SDK throws a typed ByoBreakingChangeError exposing reasons and plan.

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-call events.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 ./dump downloads 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.