Indexer API
Reading token, market and holder data without running your own indexer.
The indexer follows the launchpad from one Yellowstone gRPC stream (the programs' transactions, our mints' and pools' transactions, one account filter per mint, block metadata), keeps every number by ingest in Postgres, and serves the two pages from an in-memory snapshot and one WebSocket. Its design is indexer/DESIGN.md in the main repository; this page is the site's side of it.
Routes, per page
| Page | Route | Purpose |
|---|---|---|
| Explore | GET /api/explore | the table: sort (market_cap, ath, age, txns, volume, traders, change_1h, change_6h, change_24h), dir, page, limit (1 to 50), q, graduated=true, mcap_min, mcap_max, vol_min, vol_max (USD); or mints= for the watchlist |
| Explore | GET /api/ws | live rows for the mints on the page: {"type":"subscribe","mints":[…]} first |
| Pro | GET /api/pro/columns | the three columns, 30 rows each, from the snapshot; Authorization: Bearer <Privy access token> |
| Pro | GET /api/pro/column | one column (name) with q or bounds applied, a fresh 30; same bearer |
| Pro | GET /api/ws | {"type":"auth","token"} then {"type":"subscribe","columns":true}; with a filter on, {"type":"subscribe","mints":[…]} |
| Both | GET /api/tokens/:mint | one summary, for anything that has a mint and wants a name |
| Both | GET /health | readiness, lag, snapshot version and age, open sockets, metadata queue |
The old /api/tokens list, the per-token detail, candles, trades, holders and POST /api/prices are gone with the token page.
The token summary
Every route and the socket's snapshot carry the same row:
{
"mint": "…", "symbol": "HODL", "name": "hodl launchpad",
"image_url": "https://ipfs.io/ipfs/…", "description": "…",
"links": { "website": "https://…", "x": "https://x.com/…", "telegram": "" },
"created_at": "2026-09-21T05:48:35Z", "creator": "…",
"lifecycle": "bonding", "graduated": false, "graduated_at": null,
"pool": "…", "config": "…", "uri": "https://…",
"price": 6.3e-7, "price_usd": 0.000063, "market_cap_usd": 63792.1, "ath_market_cap_usd": 63792.1,
"volume_24h_usd": 12761.6, "txns_24h": 3, "traders_24h": 2,
"change_15m": 0.0, "change_1h": null, "change_6h": null, "change_24h": null,
"holder_count": 4, "creator_supply_pct": 2.47, "trading_fee_bps": 125,
"tier": 0, "graduation_market_cap_usd": 75398.7,
"updated_at": "2026-09-21T05:49:02Z", "version": 36
}
lifecycle is bonding, completed (the curve is full, the migration not yet landed) or migrated; graduated is true for the last two. pool is the curve until the token migrates, then its DAMM v2 pool. A null metric is unknown, not zero: a change is null while the token is younger than its window.
The socket
Every subscribe replaces the set and is answered with {"type":"snapshot","version","rows"}. After that, {"type":"row",…} carries a changed mint's hot fields (one per mint per 500 ms), {"type":"columns",…} in columns mode carries the server's new memberships with the full rows of anything the page has not seen, and {"type":"ping"} every 15 s expects {"type":"pong"}. The page closes the socket the moment its tab is hidden and subscribes again when it is visible. Close codes: 4000 no first frame or bad token, 4001 over a limit (five sockets per user on Pro, four per address on Explore; the sixth is told {"type":"limit","open":5,"max":5} first, and the page shows "You have 5 tabs open on Pro. Close one to see live updates here."), 4002 two pings unanswered, 4003 token expired.
Where each field comes from
| Field | Source |
|---|---|
| symbol, name, uri, trading fee, tier | the launchpad's own create_launch arguments; tier is 0 for A through 4 for E |
| graduation market cap | DBC's own migration price for the launch's config (decoded from the launch transaction's create_config_with_transfer_hook), times the total supply, in USD at the SOL/USD rate at read. Null for launches indexed before it existed until their launch slot is replayed |
| mint, pool, config, creator | the launch's DBC pool creation, through the shared program parser |
| age | the slot's block time from block metadata |
| total supply | the launch transaction's post token balances, where DBC mints the whole supply |
| price | the last trade, quote per whole token, guarded by the trade's position in its block |
| market cap | price × total supply, in USD at the SOL/USD rate at read |
| ATH | the highest market cap in USD at the time of each trade |
| volume, txns, traders | bumped on trade, made exact every 30 s over the last 24 h of trades |
| changes | last price against the close of the latest minute at or before each window's start |
| holders, dev supply | token accounts by the stream's account updates, aggregated per wallet |
| lifecycle | the curve-complete event, then the DAMM v2 pool's creation |
| image, description, links | the uri document, fetched through the configured IPFS and Arweave gateways |
USD and images
The quote is WSOL. One SOL/USD source (Jupiter live, a fixed rate on a fork) is refreshed every 30 s; USD fields are the stored quote figures times that rate at read, so a rate move changes them without a write. Images are served as gateway URLs, not proxied.
Freshness and recovery
The stream runs at processed commitment, which is final here. The indexer keeps two cursors: the newest live slot and the contiguous replayed slot. Slots the stream did not deliver (boot, a gap, a reconnect, the moment after a migration changes the filters) are recorded as ranges and replayed from the RPC through the same apply path; the replayed cursor never passes a range that is still open.
Backfill only reaches as far back as the RPC node retains. A node that has pruned a range cannot complete it; the range stays open and says so until an RPC that keeps those slots is configured.
Reading responses safely
- Treat a missing or null metric as unavailable, not zero. The frontend shows a dash for what the indexer has not supplied.
- Use mint addresses as identifiers. Names and tickers can repeat; preserve address capitalization.
- Pages are the server's. The table asks for one page at a time and shows
Page X of Yfrom the answer; a row can move between pages between two reads. - A request error is not an empty market. Keep the last successful data visibly distinct from a newly refreshed response.
Preview versus live data
The hosted design preview uses fictional tokens and simulated actions stored in the browser. It does not connect to the live indexer or submit blockchain transactions.