Every Clarity state change on Stacks, indexed from genesis
Maps, data vars and inner calls from every Clarity contract, as rows from block 1 and checked against each block. What that opens up for apps, wallets and protocols.
We're replaying every Stacks block from genesis on a node that records each change a Clarity contract makes to its own storage, plus every call one contract makes into another¹1. The replay runs on our fork of stacks-core, published as ghcr.io/ryanwaits/stacks-core:6e005af, which adds two observer events: storage writes and inner calls. LNow proposed contract events upstream in 2022 (PR 3054, closed). The fork is built for an indexer's needs and stacks-core has its own tradeoffs to weigh, so we offer it as a working reference if these events are ever considered upstream.. At the tip, every contract's maps, data vars and inner calls become rows you can query, subscribe to and check, from block 1.
As of November 5 the replay is at block 7,278,709 of 9,420,376.
An indexer on Stacks sees what the node emits: transactions, token transfers and whatever a contract prints. Most of a protocol lives in the maps and variables it writes inside the VM: vault shares, listings, reserves and fees.
One ALEX swap at block 1,230,200 shows the gap. A public API lists a single call to swap-helper and three events (all the node emits). Here is what ran inside it:
That's 22 inner calls and two map writes. ALEX prints the new pool balances, so one write was visible already. The vault's reserve for wKIKI wasn't.
A node can read that reserve without a getter through /v2/map_entry, even at an old block on an archival node. It can't say which transaction changed it, which calls led there, or every key the map holds.
That reserve write is one committed row: block, transaction, contract, map or variable, key and value. Replay the rows in order, like a bank statement, and you know what any stored value was after any transaction, without asking a node. Inner calls carry caller, sender, arguments and result, even when the call failed and its caller handled the error.
A subgraph turns these rows into tables. This one tracks that vault's reserves:
- History of any stored value. A vault share, a listing or a fee setting at any block, with the transaction that changed it. Today that takes an archival node and one read per block, or a print the author remembered to add.
- Contracts as they were deployed. A contract can't start printing without a new deploy, so the write log indexes it as it is, from the day it launched.
- Wallets that show positions. Wallets show token balances, but a stacking share, a vault deposit or a listed NFT lives in a protocol's map. A wallet could render those positions from the protocol's subgraph.
- Your protocol when users never call you. Routers and pools call your contract inside other transactions. Filter inner calls on your contract and they show up with their callers, as CityCoins mining pools asked for in 2022.
- Webhooks on state. A keeper gets a push when a map key changes, instead of polling the contract.
100proof wrote up a listing pattern common in Stacks NFT contracts. list-in-ustx checks tx-sender, which is still you when a contract you called makes the call. A malicious commission contract can run during your purchase and list your other NFTs at 1 uSTX. Post-conditions don't catch it, because no other NFT moved; a listing is a map write.
An explorer shows a purchase. The write log shows one list-in-ustx call per NFT with the commission contract as caller, so a collection can be paged when an unknown contract lists for someone.
Sentinel, our monitoring service for Stacks contracts, will watch the same rows: describe the calls or map writes you care about, and it pings you when one happens.
Each block commits to a state root, a hash over all contract storage. Every hour, a check picks 50 random blocks from the replay and rebuilds each root from our rows, so a missing, extra or wrong write breaks it. It has run 667 times since October 9 without a failure.
Before then, it caught two bugs in our node patch. At the swap's block, a nested call overwrote a slot, so the reserve write was logged with the old reserve as its key. Older blocks also logged writes out of execution order²2. Pre-Nakamoto microblocks execute before their anchored block's transactions but arrive after them.. We fixed both and restarted from genesis on October 8.
sl verify block already proves what a block changed, but not what the changes were:
The root commits only to hashed keys; the write log names them, so a proof can say which map, key and value changed.
- LiveBlock proofssl verify block proves any block's writes, unnamed.
- NextReplay at the tip7.28M of 9.42M blocks, checked every hour.in progress
- At cutoverRows and triggersMap and var writes and inner calls in Index, Subgraphs and Webhooks.
- AfterArchiveThe write log as a fourth signed dataset, from block 1.
- LaterSentinelWatches on calls into your contracts and writes to their maps.
Webhooks already accept these triggers; on mainnet they match nothing until the cutover:
This started with Kourier, aulneau's 2022 proposal for how Stacks indexing should work. It ended on this idea:
At a high level, if the Stacks node software were to emit events for all Clarity state changes, this would open the door for completely new kinds of indexing services built on Stacks that other chains cannot replicate. You could imagine a service that would give historical data for any Clarity value you wanted.
Once the replay passes its data check at the tip, map, var and inner-call rows open as a beta across Index, Subgraphs and Webhooks. Get an email when it does. sl verify block 1230200 works today, no key needed.