A launchpad frontend has a strong incentive to look alive. Empty lists and flat charts do not convert. The industry answer is seed data: plausible tokens, a random-walk price series, a “volume” figure with a dollar sign in front of it. We did this too, early on. Then we deleted all of it — the commit is b9a4c4a, “index pons markets into a backend data layer, delete all mock data” — and wrote down the rules that would stop it coming back.
The two rules
- Every number written to the database comes from a chain read, and every row keeps the block and transaction it came from. There is no growth curve, no jitter, no backfill of plausible-looking history. If the API serves a figure, you can check it against the explorer.
- A price point is either a real
Swaplog or a realslot0read. They are labelledswapandsample, the former carries atxHash, and they are never merged or interpolated. When nothing trades, the chart goes flat, because nothing traded.
What the indexer does with them
scripts/pons-indexer.ts is a long-running worker. It sweeps eth_getLogs for launch events on every factory it knows, and for each new token reads name, symbol, decimals, logo, description and socials from the token contract — pons tokens are self-describing, and so are ours — plus the canonical pool, the launch record, and the graduation status from the factory. The row that lands in markets has block, blockHash and hash from the launch transaction.
Every pass after that refreshes each market’s snapshot: slot0 → price, pool balances → liquidity, graduationStatus → progress, and a count of Swap logs in the last 24 hours → volume. Reserves are re-read rather than adjusted by event deltas, so a missed log costs one stale poll rather than permanent drift.
Things we deliberately named honestly
live.volumeis the count of trades in 24 hours. We did not compute notional volume, so we do not display it. A number called “volume” that is actually a count is less misleading than a dollar amount that is actually a guess.live.staleflips true when a snapshot is older than five minutes. The UI shows the old number and says it is old, rather than pretending it is current.- Liquidity USD values both sides of the pool at the current price. The usual shortcut — double the WETH side — is only fair for a balanced constant-product pool, and a V3 position is not balanced.
/api/configreturnssimulated: falseanddataSource: "indexed-onchain". If anyone ever adds a simulated mode, that flag is where a banner has to be gated. It is a promise encoded in the API shape.
The polling endpoint
The browser does not talk to an RPC to render anything. It polls GET /api/data?kind=updates, which returns three monotonic counters and answers 304 Not Modified against an ETag. Only when a counter moves does the client fetch the heavier markets payload. Cheap polling, expensive fetches only on real change.
The cost of honesty
On a public RPC the first full sweep takes tens of minutes, because each new token costs about eight paced contract reads and Robinhood’s endpoint rate-limits hard. A fresh install shows an empty list until the indexer has run. We think an empty list that is true beats a full one that is not.