Market history CLI
Index a project
# Write market-index-v2-13944.json and its .sqlite siblingnpx @whitehash/archive market v2:13944# Resolve an fxhash project slug firstnpx @whitehash/archive market blokkendoos --resolver fxhash# Extend an existing artifact from its saved cursorsnpx @whitehash/archive market v2:13944 --update market-index-v2-13944.json
What it reads
On Tezos the command reads the fxhash marketplace contracts through TzKT and recovers the full order book: listings, offers, collection offers, their cancels and accepts, plus every mint. Mints are searched across all issuer generations, because older projects kept minting on the contract they launched with.
On Ethereum and Base it walks the collection’s own transfers and decodes the Seaport fills and mint purchases in those transactions. Sales and mints both come back; active listings do not, because fxhash listings there are signed off-chain and never touch the chain. Stats mark that with listingsAvailable: false, and floor, median, and listed count read as unavailable rather than zero.
Artifacts and incremental runs
Each run writes a versioned whitehash-market-index@1 JSON file and a queryable SQLite sibling. Both carry a resume height per chain, so --update fetches only what happened since. Add --json-only to skip SQLite, and --source rpc to force a trustless log scan instead of Blockscout, which needs an archive-capable endpoint.
The JSON artifact
The JSON file is the portable handoff for an app. The shortened shape below keeps the normalized project, ordered event history, derived statistics, and resume positions in one document. Amounts stay decimal strings in native base units, mutez or wei, so no value is lost to JavaScript number rounding.
Your app will usually read stats for a summary and charts, then useevents when it needs a transaction timeline. Keep cursorswhen you plan to refresh the artifact with --update.
{"format": "whitehash-market-index@1","generatedAt": "2026-08-03T19:21:24.292Z","project": {"chain": "tezos:mainnet","id": "v2:86","name": "Reading a book","editions": 1000,"minted": 1000},"cursors": { "tezos:mainnet": { "height": 14345860 } },"events": [{"kind": "listing_accept","chain": "tezos:mainnet","marketplace": "fxhash-tezos-v2","contract": "KT1…","tokenId": "12345","orderId": "fxhash-tezos-v2:…","price": "450000000","seller": "tz1…","buyer": "tz1…","saleKind": "secondary","timestamp": "2026-08-03T18:00:00Z","level": 14345860,"opHash": "op…","sourceId": "123456789"}],"stats": {"asOf": "2026-08-03T19:21:24.292Z","listingsAvailable": true,"floor": "450000000","median": "580000000","listed": 12,"volume": {"primary": { "all": { "sales": 1000, "volume": "10000000000" }, "…": "other spans" },"secondary": { "all": { "sales": 42, "volume": "12000000000" }, "…": "other spans" },"total": { "24h": { "sales": 2, "volume": "900000000" }, "…": "other spans" }},"daily": [{ "date": "2026-08-03", "floor": "450000000", "volume": "900000000", "sales": 2 }]}}
| Field | What to use it for |
|---|---|
project | The same normalized project metadata used by the other CLI indexes. |
cursors | Per-chain resume heights for the next incremental CLI run. |
events | Ascending, normalized market activity. Nullable fields represent actions without that value, such as mints without a token ID. |
stats | Ready-to-display floor, listing, sales, volume, and daily chart data at generatedAt. |
Display it
useMarketIndex loads and validates an artifact your app hosts, and the MarketStats parts render it. Prices arrive as base units, mutez or wei, and every part formats them with the chain the index came from.
import { useMarketIndex } from "@whitehash/react"import { MarketStats } from "@whitehash/ui"function Market() {const { index, loading, error } = useMarketIndex("/market-index.json")if (loading) return <p>Loading…</p>if (error || !index) return <p>{error ?? "Not found"}</p>return (<MarketStats.Root index={index}><MarketStats.Tiles /><MarketStats.FloorChart /><MarketStats.VolumeChart /><MarketStats.Events limit={25} /></MarketStats.Root>)}
How the numbers are defined
The statistics follow fxhash’s own definitions, so a floor here means the same thing it does on fxhash: the lowest price among listings active at the moment the index was built. Volume buckets are cumulative per span, and a period’s change compares it with the span immediately before it.
Two deliberate differences: the highest and lowest sale compare native base units rather than converted USD, because this toolkit keeps no historical exchange rates, and Tezos mint prices record the tez actually paid.