What is Metashrew?
Metashrew is the indexing engine that powers everything alkanes-related on Fairmints. Bitcoin Core only knows about transactions and UTXOs — it has no idea what a DIESEL balance, an AMM pool reserve or an orbital owner is. Metashrew fills that gap: it reads every Bitcoin block, runs the alkanes metaprotocol rules over it, and stores the resulting state in a queryable database.
The clever part is that metashrew itself contains no alkanes-specific logic. It is a generic host that executes an indexer program compiled to WebAssembly (alkanes.wasm). The exact same WASM that defines the alkanes consensus rules is run by every indexer operator, which is what makes the resulting state deterministic and verifiable across the network.
Rule of thumb: bitcoind stores the chain, metashrew + alkanes.wasm derives alkanes state from it, and the Fairmints backend reads that state (plus its own indexes) to serve the website and API.
Where it fits in the stack
Bitcoin Core (bitcoind, txindex) full chain + JSON-RPC :8332
│
▼
Metashrew (rockshrew-mono + alkanes.wasm) derives alkanes state
├─ RocksDB at ~/.metashrew the indexed state (SMT + K/V)
└─ JSON-RPC :8090 metashrew_height / metashrew_view
│
▼
Fairmints backend / Espo / Sandshrew read state, build its own indexes
│
▼
Fairmints frontend (this site) tokens, pools, orbitals, chartsEspo and Sandshrew-style APIs sit on top of a synced metashrew to build higher-level indexes (OHLC candles, holders, balances, address outpoints). They are consumers of metashrew, not replacements for it.
Anatomy of a running node
| Piece | What it is |
|---|---|
| rockshrew-mono | The all-in-one metashrew binary: it fetches blocks from bitcoind, runs the WASM indexer, writes to RocksDB, and serves the JSON-RPC. Built from the metashrew repo. |
| alkanes.wasm | The indexer program (the alkanes consensus rules) compiled from alkanes-rs. Passed to rockshrew-mono via --indexer. Swapping this changes how state is computed. |
| ~/.metashrew | The RocksDB database directory (--db-path). Holds the append-only, height-annotated key/value store and a Sparse Merkle Tree (SMT) of state roots so any past height can be rolled back to. |
| RocksDB | The embedded LSM-tree storage engine. Data lives in many .sst files that are periodically compacted into fewer, larger files. |
On our servers metashrew runs as a systemd service. A typical unit's ExecStart looks like:
rockshrew-mono \ --daemon-rpc-url http://localhost:8332 --auth rpc:rpc \ --db-path /root/.metashrew \ --indexer /root/alkanes-rs/target/wasm32-unknown-unknown/release/alkanes.wasm \ --host 0.0.0.0 --port 8090
The service is managed with the usual commands: systemctl {start,stop,restart,status} metashrew and journalctl -u metashrew -f for live logs.
Talking to it (JSON-RPC)
Metashrew exposes a small JSON-RPC surface. The two you will use most:
metashrew_height— the highest block the indexer has fully processed.metashrew_view— call a read-only view function exported by the WASM (this is how balances, pool reserves, traces, etc. are read back out).
Check how far along the index is versus the chain tip:
# indexer height
curl -s -X POST http://localhost:8090 \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":0,"method":"metashrew_height","params":[]}'
# chain tip (bitcoind)
bitcoin-cli getblockcountWhen those two numbers match, the node is fully synced. A large, steadily shrinking gap means it is catching up; a gap that is stuck or barely moving means something is wrong (see below).
Keeping it healthy
RocksDB compaction & disk
A healthy database is a few thousand compacted .sst files. If compaction stops keeping up, obsolete data is never garbage-collected and the database balloons. The classic failure mode is a vicious cycle: the disk fills up → RocksDB has no scratch space to write compacted output → compaction stalls → .sst files pile up → the disk fills up faster.
Real example we hit: one node had grown to 2.2 TB with ~159,000 SST files while a healthy twin was 530 GB with ~13,800 files — same version, same indexer. The bloat was stalled compaction after the disk hit ~98 %, and it left the indexer crawling ~95k blocks behind. Watch free disk space and the SST count as early-warning signals.
# database size and SST file count du -sh ~/.metashrew ls ~/.metashrew | grep -c '\.sst#39; # free disk df -h /
A sync watchdog
A small cron script can compare metashrew_height to getblockcount and restart the service if it falls behind. Useful, but keep it disabled during any manual maintenance (backups, rollbacks) so it doesn't restart the process mid-operation.
Snapshots
Syncing from genesis is slow, so a common shortcut is to restore from a snapshot tarball of a known-good ~/.metashrew. Our snapshots extract their .sst files directly (no wrapping folder), so restore into the target directory:
systemctl stop metashrew mkdir -p /root/.metashrew tar -xzvf latest.tar.gz -C /root/.metashrew # or: tar -I pigz -xf ... for speed systemctl start metashrew
Never extract a snapshot on top of a partial/old database — mixing SST files from two databases corrupts it. Start from an empty (or freshly removed) directory.
Reorgs, healing & repair
Metashrew handles two kinds of recovery automatically, and one kind manually.
Automatic: reorgs
On every cycle metashrew compares its stored block hashes against bitcoind, walking back up to max-reorg-depth (default 100) blocks. If it finds a mismatch it rolls back to the common ancestor and re-indexes forward. This is normal Bitcoin reorg handling and needs no intervention.
Automatic: startup heal
On startup metashrew reconciles its internal pointers (indexed-height, runtime tip, block-hash records) so a crash mid-write can't leave the database inconsistent. Importantly, the heal only ever rolls back to a consistent point — it never re-indexes forward on its own.
Manual: rolling back after a consensus fix
When the alkanes rules themselves change — e.g. a bug fix in alkanes-rs — block hashes do not change, so neither automatic path triggers. Metashrew will not magically re-derive old blocks just because you swapped alkanes.wasm. You must deliberately rewind to a height before the bad state and re-index forward with the patched WASM. This is the “reparation path”: it avoids re-downloading a whole snapshot.
The tool is rockshrew-rollback (shipped in the metashrew repo). It removes all indexed data above a target height, in-place; on restart, metashrew resumes indexing from there.
rockshrew-rollback --db-path /root/.metashrew --height <SAFE_HEIGHT> [--yes]
The safe procedure end-to-end:
- Rebuild the patched
alkanes.wasmfrom the fixedalkanes-rs, and make sure the service's--indexerpoints at it. - Stop metashrew (the rollback tool refuses to run against a live DB).
- Back up
~/.metashrewfirst — the rollback is in-place and irreversible. - Roll back to a height safely before the first bad block.
- Restart metashrew and watch it re-index forward with the correct rules.
Choosing <SAFE_HEIGHT> is the one judgement call: it must be before the first block that introduced the bad state, not just before the symptom. When in doubt, roll back further (costs extra re-index time) rather than shorter (risks keeping corrupt state), and confirm the disclosed safe height with the protocol maintainers.
Aside: “poison blocks”
Because indexing is deterministic and must never skip a block, a bug that makes the WASM panic on a specific block is catastrophic: the indexer retries that block forever and the whole network halts at that height — a poison block. The July 2026 alkanes incident included exactly this (a checkpoint-stack underflow on a crafted extcall), alongside a balance-inflation bug. The fix ships in the indexer WASM, and affected nodes recover with the rollback-and-reindex procedure above rather than a fresh snapshot.