1The indexer
The indexer is a small service (indexer/ in the repository) that scans the chain a few blocks behind the tip and keeps what the site needs in one snapshot: launches, trades, holder balances, The Pound's ledger and the signed metadata people post. It answers over plain JSON with CORS open and no keys. Everything it says is derived from contract events and reads, so anything that matters can be checked against the chain directly.
| Network | Chain id | Base URL | Explorer |
|---|---|---|---|
| Arc Testnet | 5042002 | https://radian-indexer-production.up.railway.app | https://testnet.arcscan.app |
| Arc Mainnet | 5042 | not deployed here | https://arcscan.app |
| Robinhood Testnet | 46630 | https://radian-indexer-robinhood-production.up.railway.app | https://explorer.testnet.chain.robinhood.com |
| Robinhood Chain | 4663 | https://radian-indexer-robinhood-mainnet-production.up.railway.app | https://robinhoodchain.blockscout.com |
The base URL comes from the network config (NEXT_PUBLIC_*_INDEXER_URL overrides it per network). Each network has its own indexer with its own snapshot; a token address is only meaningful on the network it was launched on.
curl https://radian-indexer-production.up.railway.app/health
{ "ok": true, "checkpoint": "70311204", "through": "70311204", "backfillDone": true,
"launches": 12, "trades": 3410, "ledger": 88,
"identity": { "checked": true, "ok": true, "mismatches": [] } }Public reads carry Cache-Control: public, max-age=5, s-maxage=15 (/stats: 10 s and 30 s). Per-wallet and signed data are no-store. Launch rows are rebuilt from the trade log at most once every 4 s, so two polls inside that window return the same bytes.
2Launches and trades
/launches lists every live launch on the network with its curve, pair token, template, holder count and the 24h facts computed from the trade log. Retired launches leave the list but still resolve at /token/:addr with a sunset field naming the successor; hidden ones are left out.
curl https://radian-indexer-production.up.railway.app/launches
{ "launches": [ {
"token": "0x…", "curve": "0x…", "deployer": "0x…", "name": "…", "symbol": "…",
"logo": "https://…", "description": "…",
"pairToken": "0x0000000000000000000000000000000000000000", // zero = the gas coin (USDC here)
"quoteSymbol": "USDC", "quoteDecimals": 18,
"quoteReserve": "…", "trackedQuote": "…", "graduationThreshold": "…", // raw integer strings
"progress": 0.31, "graduated": false, "buybackLocked": "0",
"template": null | { "kind": "wall", "treasury", "staking", "ladder"? } | { "kind": "pof", "vault", "pofRouter" },
"spark": [[ts, price], …], "lastPrice": 0.0021 | null, "change24h": 4.2 | null,
"volume24h": 120.5, "trades24h": 17, "holders": 42 | null, "createdAt": 1758700000000,
"creatorName": "", "sunset": null
} ],
"through": "70311204", "backfillDone": true }/token/:addr returns the same row plus its newest 100 trades. /activity?limit= is the newest trades across every launch (1–200, default 50). /address/:addr/activity is one wallet's own buys, sells, launches and referral claims, up to 200, uncached, with through so a client can say how far the index reaches.
{ "txHash": "0x…", "ts": 1758700123456, "block": "70311100",
"token": "0x…", "side": "buy" | "sell", "trader": "0x…",
"quote": "2500000", // raw, in quoteDecimals of the pair asset
"tokens": "1234000000000000000000", // raw, 18 decimals
"quoteSymbol": "USDG", "quoteDecimals": 6 }Amounts are raw integer strings. Launch tokens always have 18 decimals; the quote leg is in the pair asset's own decimals (18 for a gas coin, 6 for EURC, USDGx and USDG), and every row says which. lastPrice and spark are already quote-per-token floats.
/stats?window=24h|all aggregates the trade log: counts, TVL on curves, buyback locked, volumes by side, 24 hourly buckets and a per-token ranking. /pound is the whole waterfall for the network (vault and burner parameters, per-asset totals, the Pack, the referrer leaderboard, the ledger); /pound/referral/:addr reads one referrer's claimable and accrued live from the vault. /radian serves the older $RADIAN flywheel where it still runs.
3Signed messages: wall, logo, profile
The holder wall, the creator's logo and wallet profiles cost no gas: the wallet signs a plain-text message (personal_sign; EOA and ERC-1271 signatures both verify) and the indexer checks it against the chain before storing anything. A wall line must come from a wallet that holds the token at the moment of posting; a logo from the wallet that launched it; a profile from the wallet itself; a hide from a moderator (the factory owner, read live, plus any configured moderators).
// token and addresses lower-cased; time = Date.now() in milliseconds "Radian wall\ntoken: <token>\ntime: <time>\n\n<text>" "Radian logo\ntoken: <token>\nsha256: <hex of the image bytes>\ntime: <time>" "Radian moderation: hide wall message\ntoken: <token>\naddress: <target>\ntime: <time>" "Radian profile\naddress: <address>\nname: <name>\nbio: <bio>\nx: <handle>\ntime: <time>"
POST https://radian-indexer-production.up.railway.app/token/<token>/wall { "address", "text", "time", "signature" }
POST https://radian-indexer-production.up.railway.app/token/<token>/wall/hide { "address": <target>, "by": <moderator>, "time", "signature" }
POST https://radian-indexer-production.up.railway.app/token/<token>/logo { "address", "image": "data:image/png;base64,…", "time", "signature" }
POST https://radian-indexer-production.up.railway.app/profile/<address> { "name", "bio", "x", "time", "signature" }
// success: { "ok": true } (+ "url" for a logo, "profile" for a profile); failure: { "ok": false, "error": "<code>" }| Rule | Value |
|---|---|
| Clock skew | time within ±10 minutes of the indexer's clock, else stale |
| Rate | 30 signed posts per IP per hour, else rate (429) |
| Wall line | ≤ 280 code points; tab and line breaks allowed, other control characters refused; one line per wallet, a new one replaces it; 200 kept per token, the newest 50 served |
| Hide | hides one line, not the wallet: the next line from that wallet shows again |
| Logo | png, webp or jpeg; ≤ 200 kB decoded; the data URL ≤ 300 000 characters; the sha256 in the message is of the decoded bytes |
| Profile | name ≤ 32, bio ≤ 160, x ≤ 15 of [A-Za-z0-9_] (a leading @ is stripped); no angle brackets or control characters |
| Body | JSON ≤ 512 kB; over the limit is a plain too-large (413) |
Error codes are short strings: bad-input, empty, stale, signature, rate, holding (the poster holds none of the token), creator (not the deployer), not-moderator, not-found, too-large, storage, network (the balance read failed), unknown token.
4The agent API
Under /v1 the indexer plans transactions for agents and bots without ever holding a key: every plan comes back unsigned as { to, data, value, approve? } for the caller to sign and broadcast, and every price says how it was produced. simulation is an exact eth_call of the real curve; reserves is computed from the curve's reserves and fee policy and should be treated as an estimate.
curl https://radian-indexer-production.up.railway.app/v1/manifest
{ "name": "Radian", "version": "1",
"network": { "chainId": 5042002, "name": "Arc Testnet", "rpc": "…" },
"contracts": { "factory", "launchRouter", "pofRouter", "executor", "keeper" },
"identity": { "checked": true, "ok": true, "mismatches": [] },
"payments": { "protocol": "x402", "enabled": false, "note": "…" },
"services": [ { "id", "method", "path", "price": null, "note" }, … ],
"rules": [ "Plans are unsigned; the caller signs and broadcasts.", … ] }curl https://radian-indexer-production.up.railway.app/v1/curve/<token>
{ "block": { "number": "70311204", "hash": "0x…", "timestamp": 1758700000 },
"token", "curve", "pairToken", "quoteSymbol", "quoteDecimals",
"quoteReserve": "…", "tokenReserve": "…", "spotPer1e18": "…" | null,
"trackedQuote": "…", "graduationThreshold": "…", "progress": 0.31 | null,
"graduated": false, "feeBps": 100, "creatorTaxBps": 0, "launchedAt": 1758690000,
"template": null | { … } }
// every read at one block; the block hash is re-read afterwards and the snapshot retried once if it changedcurl "https://radian-indexer-production.up.railway.app/v1/quote?token=<token>&side=buy&amount=10000000&recipient=<you>&slippageBps=100"
{ "side": "buy", "amountIn": "10000000", "expectedOut": "…", "minOut": "…",
"method": "simulation" | "reserves", "exact": true | false,
"fees": { "feeBps": 100, "creatorTaxBps": 0, "snipeTaxBps": 0 },
"plan": { "to": "<curve or PoF router>", "data": "0x…", "value": "10000000" | "0",
"approve": null | { "token", "spender", "amount" } } }
// side=sell: amount is tokens (18 decimals); approve names the token and the curveA buy on a Proof-of-Fee launch is routed through the PoF router so it counts as Work; the plan already picks it. A native buy with a recipient is simulated exactly; ERC-20 buys and every sell are computed from reserves. Once a launch has graduated the quote answers 409: trade on the Uniswap V4 pool instead.
POST https://radian-indexer-production.up.railway.app/v1/launch-plan
{ "template": "standard" | "wall" | "pof", "creator": "<addr>", "pairToken": "<quote or 0x0>",
"buyAmount": "<raw>", "minTokensOut": "0", "launchConfigId": "0",
"params": { "name", "symbol", "logo", "description", "socials": {…}, "salt": "<bytes32>",
"creatorFeeRecipient"?, "creatorTaxBps"?, "buybackEnabled"? },
"cfg"?: { …wall or pof settings } }
// → { to, data, value, launchFee, approve?, predicted: { treasury, staking } | { vault } | null }
POST https://radian-indexer-production.up.railway.app/v1/auth // a signed EIP-712 BuyAuth for RadianExecutor (domain "RadianExecutor", version 1 | 2)
{ "auth": { user, token, perBuyMax, maxGasPrice, totalCount, minInterval, deadline, nonce,
asset?, minPerBuy?, minTokensPerQuote? }, // the last three on executor v2
"signature": "0x…" }
// → { ok, authId }; GET https://radian-indexer-production.up.railway.app/v1/auth/<user> lists them with count, lastAt, statusPayments: the API is shaped for x402 (the manifest has a price per service) but every service is free until a facilitator settles on this chain; no endpoint charges before then. JSON bodies on /v1 are capped at 32 kB. slippageBps is clamped to 0–5000 and defaults to 100.
5Every endpointHow long a browser may reuse the response (max-age); a CDN in front may keep it three times as long (s-maxage). no-store: never reused. none set: the server sends no header, so the browser decides.
All paths are relative to the base URL above. Every response is JSON except /img. A chain read that fails answers 503 with a reason, never an empty result, because empty is a real answer.
| Method | Path | Returns | Cache | Limits |
|---|---|---|---|---|
| GET | /health | Liveness: the live cursor (checkpoint), through, backfillDone, counts of launches, trades and ledger rows, and the indexer's own code-hash check (identity). | no-store | — |
| GET | /launches | Every live launch (retired and hidden ones left out) with curve, pair token, template, holders, spark, last price, 24h change / volume / trades, creator name and logo; plus through and backfillDone. | public 5 s (CDN 15 s) | Launch rows are rebuilt at most once every 4 s. |
| GET | /token/:addr | The launch row (a retired one too, with its sunset) and its newest trades, each carrying quoteDecimals and quoteSymbol. | public 5 s (CDN 15 s) | 100 trades; 400 on a bad address; 404 when unknown. |
| GET | /activity?limit= | The newest trades across every live launch, with name, symbol, quoteSymbol and quoteDecimals. | public 5 s (CDN 15 s) | limit 1–200, default 50. |
| GET | /address/:addr/activity | One wallet's buys, sells, launches and referral claims, newest first, with indexed, through and backfillDone. | no-store | 200 events. |
| GET | /tx/:hash | One transaction's status (pending / success / reverted / unknown), block and confirmations, the decoded events (launch, buy, sell, claim, flush, settle, burn) and, for a launch, the new token's address. | public 30 s (CDN 90 s) | no-store while pending or unknown; 400 on a malformed hash; 502 when the node cannot be reached. |
| GET | /reconcile | The index re-derived from the chain at its checkpoint block: registry, supply, reserve, Pound attributed / claimed / settled, flywheel flushed — each check with expected, actual and the first mismatch; allOk and the block. | public 60 s (CDN 180 s) | Computed at most once a minute; concurrent requests share one computation; a failed RPC read is a failed check, never a pass. |
| GET | /stats?window=24h|all | Launch and graduation counts, curve TVL, buyback locked, trade counts and volumes by side, average trade, 24 hourly volume buckets and a per-token ranked table. | public 10 s (CDN 30 s) | Bounded by the retained trade log (10 000 trades). |
| GET | /radian | The $RADIAN flywheel where it runs: staked, APR, buyback burned, revenue distributed, the treasury ledger. 404 on chains without it. | none set | 50 ledger rows. |
| GET | /pound | The Pound: vault and burner parameters, per-asset totals, the Pack, the referrer leaderboard and the ledger. 404 on chains without it. | none set | 50 referrers; 100 ledger rows. |
| GET | /pound/referral/:addr | One referrer: claimable and accrued per quote asset (read live from the vault), indexed trade counts and recent events. | none set | 50 recent events. |
| GET | /pound/feed.rss | The burn feed: the newest Pack burns as RSS 2.0, one item per Burned event with the transaction link as the item link; /pound/feed.json is the same list as a JSON Feed (raw amounts under _radian). Point any RSS-to-X, Zapier or IFTTT automation at it and every burn posts itself. 404 on chains without The Pound. | public 60 s (CDN 180 s) | 50 burns; a coin is named from our launches or one cached symbol() read, else by its address. |
| GET | /token/:addr/wall | The visible wall lines, newest first, each with the poster's profile name and balance at posting, and the moderator list. | no-store | 50 lines served (200 kept per token). |
| POST | /token/:addr/wall | { ok } after verifying the signature and that the poster holds the token right now. One line per wallet; a new line replaces it. | no-store | 280 code points; time within ±10 min; 30 signed posts per IP per hour; JSON body ≤ 512 kB. |
| POST | /token/:addr/wall/hide | { ok }: a moderator hides one wallet's line (the line, not the wallet). | no-store | Signer must be the factory owner (read live) or a configured moderator; same time and rate rules. |
| POST | /token/:addr/logo | { ok, url }: the creator's image replaces the on-chain logo in what this indexer serves. | no-store | png / webp / jpeg, ≤ 200 kB decoded (data URL ≤ 300 000 chars); signer must be the launch's deployer; same time and rate rules. |
| GET | /profile/:addr | { profile }: address, name, bio, x, updatedAt — or null. | no-store | — |
| POST | /profile/:addr | { ok, profile } after verifying the wallet's own signature. | no-store | name ≤ 32, bio ≤ 160, x ≤ 15 of [A-Za-z0-9_]; no angle brackets or control characters; same time and rate rules. |
| POST | /upload | { url } for a raw image body (Content-Type image/png, jpeg, webp or gif); the URL goes into the token's on-chain logo field at launch. | none set | 2 MB; 20 uploads per IP per hour; the type is sniffed from the bytes; 500 MB shared quota. |
| GET | /img/:file | An uploaded image. | 1 year, immutable | Only names this server wrote: 24 hex characters plus png, jpg, webp or gif. |
| GET | /v1/manifest | What the API knows about itself: network, contracts, the keeper, identity, payments (x402, disabled), the service list and the rules. | none set | — |
| GET | /v1/curve/:token | A block-consistent snapshot of one launch: reserves, spot per 1e18, tracked quote, threshold, progress, fees, launch time and template, with the block it was read at. | none set | Every read at one block, the block hash re-read after; one retry, then 503. |
| GET | /v1/quote?token=&side=&amount=&recipient=&slippageBps= | Expected and minimum output, the fee parts (feeBps, creatorTaxBps, snipeTaxBps), the price method (simulation | reserves) and an unsigned plan { to, data, value, approve? } aimed at the curve or the PoF router. | none set | amount: a positive integer in raw units; slippageBps 0–5000, default 100; 409 once graduated. |
| POST | /v1/launch-plan | Unsigned router calldata for a standard, Wall or Proof-of-Fee launch: to, data, value (launch fee plus a native first buy), launchFee, approve? and the predicted template addresses. | none set | JSON body ≤ 32 kB; params.name, params.symbol, a bytes32 salt and a creator address are required. |
| POST | /v1/auth | { ok, authId } after recovering the EIP-712 signer and checking the on-chain nonce; the platform keeper then runs the schedule. | none set | JSON body ≤ 32 kB; a 65-byte signature; deadline in the future; 409 on a nonce mismatch; executor v2 also needs asset, minPerBuy and minTokensPerQuote. |
| GET | /v1/auth/:user | The user's authorizations, newest first, each with count, lastAt and status (active | done | expired | cancelled). | none set | — |
6Addresses
For Arc Testnet (chain 5042002); switch networks in the top bar for another. The third column is the address's three-word fingerprint: every byte feeds each word, so a look-alike address that differs anywhere reads differently. The same phrase appears on the Verify page and in the token's contracts panel.
Quote assets a launch can be paired against on this network. The zero address means the gas coin; the launch's pairToken field uses the same convention.
| Asset | Address | Decimals | Note |
|---|---|---|---|
| USDC | 0x0000000000000000000000000000000000000000 (native) | 18 | Native dollar — the Arc gas coin |
| EURC | 0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a | 6 | Circle's euro stablecoin |
| NVDAx | 0xDebcC47bf6e1DEFE1eC76290441836E4981dA882 | 18 | stock stand-in (testnet) |
| TSLAx | 0xb538054166A5f9aa98b945d32A7d1F122c1c3c87 | 18 | stock stand-in (testnet) |
| AAPLx | 0x88Ad67D823791C2DD5dDd96CF88e573A1F5Bb785 | 18 | stock stand-in (testnet) |
| GOOGLx | 0x195a4d07E2Bd492F70063e6c7017E336e28a8F68 | 18 | stock stand-in (testnet) |
| MSFTx | 0xD7DeD9057a4EC0a329d2c70784C8723f7df56e7F | 18 | stock stand-in (testnet) |
| AMZNx | 0x865e62E6327C572C7fb8d3427f67b03b9A4558fb | 18 | stock stand-in (testnet) |
| METAx | 0xfFBA38678178dC0B7a0EaCeceAF36795BB750977 | 18 | stock stand-in (testnet) |
| SPYx | 0x072F8FA84c12e56fE20d3F0BFbB903a96B099973 | 18 | stock stand-in (testnet) |
The runtime code hash of each platform contract is pinned in the network config and re-hashed on every visit; the site refuses to launch or trade on a proven mismatch, and the indexer reports its own check as identity. The Verify page shows both.
7Gotchas
Confirmation lag. The scanner stays two blocks behind the tip so a reorg never leaves phantom rows, then polls; a receipt in your wallet precedes the index by a few blocks plus the poll. The site keeps its own record of what this browser sent and shows it as indexing… until the index has it. Do the same rather than reporting a landed trade as missing.
Indexed through block N. /launches and /address/:addr/activity carry through (the last block scanned) and backfillDone. A cold index says still indexing, not nothing launched; show the block, not a zero.
A failed read is not zero. holders: null means no Transfer has been seen yet (unknown, not 0); change24h: null means fewer than two trades; lastPrice: null means no trade; spotPer1e18: null means an empty token reserve; a 503 from /v1 means the chain read failed. The site lists a balance it could not read with that does not mean zero and a retry.
Quote assets are never summed. Each launch is priced in its pair asset with that asset's decimals; a value in ETH and a value in USDG are not comparable, so the site groups holdings by pairToken and never adds across groups. The totals in /stats add every trade's quote leg after decimal scaling whatever the asset: read them as activity, not as a dollar figure.
Native versus ERC-20 quote. pairToken zero means the gas coin (USDC on this network) and a buy carries value; any other pairToken is an ERC-20 and needs an approve to the curve (or the PoF router) first. An ERC-20 you have no config for: read its symbol and decimals from the chain and never treat it as native, or your msg.value goes to an ERC-20 curve.
The trade log is capped at 10 000 rows (the oldest dropped by time). Per-token trade lists, /stats?window=all and the 24h facts are bounded by what is retained; the chain is the full record.
Logos. A creator-signed logo replaces the on-chain one in what the indexer serves, and the chain is untouched: logo in /launches can differ from the token's logo() getter. /upload is for the launch flow; the URL it returns is what goes on chain.
Delegated buys. The BuyAuth shape follows the executor version on the network: v2 adds asset, minPerBuy and minTokensPerQuote and rejects a v1 body. Read the executor address from the manifest, and note that a network can have the contracts deployed but the feature switched off in the site (networks.ts features).
Verify, do not trust. Anyone can copy a front end and anyone can run an indexer. The addresses and code hashes in the network config, the identity field in /health and /v1/manifest, and the Verify page are the checks; the router's buy and sell take a referrer as the last argument and a zero address forfeits the share.