Guide

Market history CLI

Backfill one project's listings, offers, sales and mints from public infrastructure, then render them.

Index a project

bash
# Write market-index-v2-13944.json and its .sqlite sibling
npx @whitehash/archive market v2:13944
# Resolve an fxhash project slug first
npx @whitehash/archive market blokkendoos --resolver fxhash
# Extend an existing artifact from its saved cursors
npx @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.

json
{
"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 }]
}
}
FieldWhat to use it for
projectThe same normalized project metadata used by the other CLI indexes.
cursorsPer-chain resume heights for the next incremental CLI run.
eventsAscending, normalized market activity. Nullable fields represent actions without that value, such as mints without a token ID.
statsReady-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.

tsx
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.