Build a token page
Goal: the page Chord itself shows for a token on Arc — price and change in the header, a candle chart, the trade tape, venue split, pools, holders, and a route card — built from six calls on load and two WebSocket channels after.
On load, in parallel
Section titled “On load, in parallel”| Component | Call | Read |
|---|---|---|
| Header (symbol, name, logo, price, 24h change, market cap, flags) | GET /arc/token/{address}?hours=24 |
token — the full token row; series for a 24-hour volume bar |
| Windowed stats (5m / 1h / 6h / 24h, ATH, 24h range, makers) | GET /tokens/stats/{address} |
windows, ath, range24h, markets |
| Chart | GET /tokens/chart/{address}?tf=1h |
candles (200, oldest first); page back with before |
| Trade tape | GET /tokens/trades/{address}?limit=50 |
trades, stats24h |
| Pools | GET /lp/token/{address}/pools?chain=5042 |
pools ranked by health, with launch facts |
| Holders | GET /arc/token/{address}/holders?limit=20 |
holders, concentration, top (each tagged wallet / pool / burn) |
const A = "https://api.getchord.xyz/v1";const [tok, stats, chart, tape, pools, holders] = await Promise.all([ `${A}/arc/token/${addr}?hours=24`, `${A}/tokens/stats/${addr}`, `${A}/tokens/chart/${addr}?tf=1h`, `${A}/tokens/trades/${addr}?limit=50`, `${A}/lp/token/${addr}/pools?chain=5042`, `${A}/arc/token/${addr}/holders?limit=20`,].map((u) => fetch(u).then((r) => (r.ok ? r.json() : null))));Treat a null (a 404 or 503) per component: a 503 HOLDERS_PENDING means the ledger just started tracking this token — show “building” and retry in a few minutes; a 404 on the chart means the token has no indexed pool yet.
Then stream
Section titled “Then stream”const ws = new WebSocket("wss://api.getchord.xyz/v1/ws/live");ws.onopen = () => { ws.send(JSON.stringify({ op: "sub", channel: "candles", token: addr, tf: "1h" })); ws.send(JSON.stringify({ op: "sub", channel: "trades", token: addr }));};ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.op === "candle") chart.upsert(m.candle); // by t; closed:true seals the bucket if (m.op === "trades") { tape.prepend(m.items); header.price = m.items[0].priceUsd; }};The header price is the latest print; a green dot while frames arrive is honest “live”. Re-fetch /arc/token and /tokens/stats every 30–60 s for the rolled-up figures.
Header details
Section titled “Header details”token.listedfalse: show the on-chain symbol with a “not on the list” hint;logoURIis a placeholder until art is found.token.flagsincludeswashorimpostor: badge it. The boards already sort such rows down; the page should say why.token.routablefalse: hide the trade card, or show “no route yet”.marketCapUsdis fully diluted; label it FDV.
The trade card
Section titled “The trade card”Quote on the user’s amount with GET /quote?chain=5042&inputMint=0x3600…0000&outputMint={address}&amount=…, then follow the Swap recipe. Link the row to https://getchord.xyz/arc/token/{address}?side=buy if you would rather hand off to Chord.
Venues and pools
Section titled “Venues and pools”tok.byDex (24-hour share per venue id) makes the venue table; map ids to names with the venue ids table. pools from the liquidity family carries score and reasons — show the reasons on hover, they are the difference between a pool with fifty providers and one where the launcher holds everything.
Holders
Section titled “Holders”heldByWalletsPct is supply outside pools and burn sinks. concentration.top10 above ~60 % on a token with thousands of holders is worth a badge. Rows tagged pool are the pools themselves; burn is the dead address.