.md

Archive

Signed chain history. Check yours against it, repair what diverges, or boot a fresh instance from it.

You getSigned history from genesis, hashed per partition
You writeNothing. You run commands.

Bootstrap reads the before a single row, restores , and does it in .

~/secondlayer
$secondlayer bootstrap --against <archive>
coverage genesis → 8,745,422
signature verified
sync from genesis ~19.4 days (estimate)
restore from archive ~2.3 hours

Bootstrap refuses a database that already holds blocks; use secondlayer repair for an existing instance.

FlagEffect
--to-block <n>Stop early
-ySkip the prompt

--from-block <n> restores forward-only: whole partitions at or after that block. Everything below is declared out of scope rather than missing, so verify doesn't read it as divergence. The tradeoff: the disk floor at boot is still the full-history one.

$secondlayer repair --against <archive> # plan
$secondlayer repair --against <archive> --apply # write it

Dry-run by default. The plan names exact heights, not ranges:

~/secondlayer
$secondlayer repair --against <archive>
HEIGHT ACTION LOCAL ARCHIVE
------- ------- ------------ -------
8501111 replace 0xd4deb46c6d 0xedb6ba8322

Each partition is hashed against the signed digest before any row is read. A replaced block's transactions, events and state writes are rewritten with it. One transaction per partition, so an interrupted repair leaves no half-corrected range.

Heights the archive doesn't have

Local heights absent from the archive are reported, never deleted, because transactions and events reference them.

Decoded rows are not rebuilt. Re-run your decoders for repaired ranges.

Read-only; digests stay on your machine. The archive is a best-effort public utility, not a hosted service. Verification is free and always will be. A range that diverges looks like this:

~/secondlayerexit 1
$secondlayer verify raw --against https://archive.secondlayer.tools/latest.json
⚠ 1 of 175 ranges diverge from the archive.
RANGE DATASET STATUS EXPECTED LOCAL
----------------- ------- ------------ -------- -----
8500000-8549999 blocks data differs 50000 50000
These ranges hold different data than the signed archive.

Verify then names each diverging block and what differs in it, read free from the archive's per-block digests, and prints a repair command for every diverging range. --json lists every height under divergent_heights; older snapshots report ranges only.

JobCommand
Check raw identity columnssecondlayer verify raw --against <latest.json>
Check every published datasetsecondlayer verify all --against <latest.json>
Also check payload columnssecondlayer verify --against <latest.json> --deep
Plan a repairsecondlayer repair --against <archive>
Write the repairsecondlayer repair --against <archive> --apply
Restore a fresh instancesecondlayer bootstrap --against <archive>
Price a hosted fetchsecondlayer archive quote --against <archive>
Check from your instance's APIPOST /v1/archive/verify
Read the archive tipsecondlayer archive latest
Read the node attestationGET attestations/<digest>/node.json
Read operational statusGET status.json

<latest.json> is the signed pointer. Start there.

Each also lives under archive; examples use the short form.

ShortSame as
secondlayer verifysecondlayer archive verify
secondlayer repairsecondlayer archive repair
secondlayer bootstrapsecondlayer archive bootstrap
TargetChecks
rawblocks, transactions, events, state_writes
allEvery dataset the manifest publishes

The archive publishes digests for the raw datasets only.

Clean output exits 0. Divergence names the ranges, as in Check it.

Equal row counts with differing data means the same heights hold different blocks: a fork point left on the losing branch.

CodeMeaning
0Local data matches the archive.
1Divergence found; every divergent range is listed.
2Unanchored: the archive was unreachable or its signature failed.

2 is never success. Not being able to check differs from being fine.

FlagEffect
--against <manifest>Archive latest.json, a snapshot manifest URL, or a local path. Required.
--quickIdentity columns only. Default.
--deepAlso recompute semantic digests (raw_tx, event data, function_args).
--anchorRequire a verified archive signature.
--from-block <n> / --to-block <n>Limit the checked range.
--countsAlso compare transaction and event row counts. Slower.
--semanticAlias of --deep.
--public-key <pem>Pin a signing key. Default is the archive key built into the CLI release, or ARCHIVE_SIGNING_PUBLIC_KEY when set.
--insecureCompare without verifying the signature. Result is unverified.
--jsonMachine-readable report on stdout.

POST /v1/archive/verify runs the same raw-identity check on the instance itself, read-only, with the archive URL in the body. Body fields and responses: API reference.

AnswerMeans
cleanLocal data matches the archive
divergedDivergence found
unanchoredThe archive was unreachable or its signature failed

A failed signature is a 200 unanchored, never clean.

const result = await sl.archive.verify({
against: "https://archive.secondlayer.tools/latest.json",
fromBlock: 0,
toBlock: 99999,
});

The tradeoff against the CLI: identity digests only. --deep is CLI-only.

What the archive costs, what it is, and how much each range is worth.

Credits

Bootstrap, backfill, and reindex fetch from the archive metered per partition bundle, quoted against your balance before anything is charged; verification is free.

secondlayer archive quote --against <archive> prices a fetch without charging. It exits 2 when your balance falls short.

FlagEffect
--from-block / --to-blockNarrow the fetch
--flow repairPrice a repair

Buy with secondlayer credits buy. Packs, balance, and how the quote/confirm flow works: credits.

What the archive is

Immutable, digest-addressed Parquet partitions on R2, published from a repeatable-read snapshot of a production Stacks index, one partition per 50,000 blocks.

DatasetOne row per
blocksCanonical block
transactionsTransaction, in block order
eventsEvent, in block order
state_writesClarity state change a block made, named per key, so each can be checked against the block's state root
  • state_writes appears once the archive is exported from a stack that holds it from genesis, after the December 2026 cutover.
  • Until then a manifest simply leaves it out. That means not published, never empty, and bootstrap, verify and repair skip it.
ObjectContents
latest.jsonSigned pointer at the current snapshot. Start here.
snapshots/<digest>.jsonSigned manifest: coverage, counts, per-object digests, finality rule, audit.
blocks/, transactions/, events/, state_writes/Range-partitioned Parquet, digest in the filename. Served only through the fetch gate (presigned URLs, see Credits); every other object here is public.
digests/<range>-<sha16>.parquetPer-height sidecar: one row per height with the block digest and rolled-up transaction and event digests.
attestations/<digest>/node.jsonSigned independent node audit. coverage is the attested window; stats is exact; mismatches[] is a capped sample.
status.jsonShort-cache operational state: fresh, lagging, stale, gap, failed-audit, source-unavailable. source.decoder_head is the running Index decoder; archive.coverage_to_block is the last signed publish; next_publish_at is when the next one starts (UTC).
reports/incidents/Signed incident reports, newest first via index.json.

Publishing is append-only. Each partition carries a SHA-256 over its bytes and a semantic-v1 digest over its canonical encoding. The manifest is ed25519-signed.

Assurance levels

The manifest states one assurance level per range:

  • db-reconstructive
  • node-attested
  • observer-attested

What each means: Verify.

CLI for the full command set; Verify for everything else you can check, from a transaction to a block.

Edit on GitHub