Build
Indexer API
Read launches, trades, candles, holders, portfolios, and the live feed.
Base URL#
https://api.joseon.funA Cloudflare Worker indexes GIWA Sepolia and serves it as JSON. Reads are public, unauthenticated, and CORS-open, so you can call them straight from a browser. Please be reasonable with volume; there is no published quota.
One thing to internalise before you parse anything: amounts are decimal strings in base units — wei for ETH, 1e18 per
token. They comfortably exceed what a JavaScript number can hold, so read them with BigInt rather than parseFloat.
If you want a trust-minimised integration, read the chain instead. This API is a convenience layer over public events, and contract calls covers doing it yourself.
curl -s https://api.joseon.fun/api/health{ "service": "joseon-indexer", "network": "GIWA Sepolia", "chainId": 91342 }Endpoints#
| Method | Path | Returns |
|---|---|---|
GET | /api/health | service and chain id |
GET | /api/eth-price | ETH/USD spot, 60s cache |
GET | /api/sync-status | next block to index |
GET | /api/analytics | launch count and daily volume |
GET | /api/launches | every launch with live curve or pool state |
GET | /api/tokens/:addr | token metadata (alias: /metadata) |
GET | /api/tokens/:addr/trades?limit= | curve and DEX trades, newest first |
GET | /api/tokens/:addr/candles?interval= | continuous OHLCV |
GET | /api/tokens/:addr/holders?limit= | ranked holders and count |
GET | /api/tokens/:addr/pool-stats?days= | windowed DEX volume for a pool |
GET | /api/tokens/:addr/chat?limit= | token chat, oldest first |
GET | /api/wallets/:addr/portfolio | holdings, created tokens, trade history |
GET | /meta/:addr.json | crawler-friendly metadata document |
GET | /media/:file | uploaded logos, immutable cache |
GET | /api/live | WebSocket event feed |
POST | /api/tokens/:addr/metadata | upload logo and description, signature required |
POST | /api/tokens/:addr/chat | post a message, wallet session required |
POST | /api/sync | nudge a sync batch |
Writes require proof that you control the relevant wallet — see writes are signed.
Launches#
curl -s https://api.joseon.fun/api/launches{
"launches": [
{
"token": "0x171e21465bfd49270bef4754f99479d88470b926",
"creator": "0x76cea8ae31dc4b23806b757e55758d82c6169da4",
"curve": "0xb90c41792e6a6f5099560e2f2ea62d3012724520",
"name": "Test",
"symbol": "TEST",
"initialBuy": "50000000000000000",
"createdBlock": "31794019",
"createdTx": "0xc1b5267d…",
"createdAt": 1785139135,
"graduatedPair": "0x3def4a712764146111dfba3bc5c3f33dc1e97832",
"graduatedBlock": "31794129",
"migrationWeth": "3600000000000000000",
"migrationToken": "200000000000000000000000000",
"tokenIs0": 1,
"reservedPair": "0x3def4a712764146111dfba3bc5c3f33dc1e97832",
"stackVersion": "v4",
"imageUrl": "https://api.joseon.fun/media/0x171e…b926.jpg",
"website": null,
"feeRecipient": "0x76cea8ae…",
"feeShares": [{ "wallet": "0x76cea8ae…", "bps": 10000 }],
"graduated": true,
"progress": "1000000000000000000",
"price": "5950067951",
"reserve": "2070296014956783323",
"state": 2
}
]
}| Field | Meaning |
|---|---|
state | 0 curve active, 1 graduating, 2 DEX active |
progress | WAD, 1000000000000000000 is a sold-out curve |
price | wei of ETH per whole token |
reserve | ETH in the curve, or the pool's ETH-side reserve once graduated |
graduated | true once graduatedPair is set |
reservedPair | the pool claimed at creation, present before graduation too |
tokenIs0 | whether the token is token0 in the pair, which fixes reserve ordering |
feeShares | retired off-chain creator split, null on new launches; accrual has always targeted feeRecipient alone |
price, progress, and reserve are read live from the chain per request rather than served from the index. For a
graduated token they come from the pool, with the curve as a fallback if that read fails.
Only launches confirmed by a factory event are served here. A launch that exists only as a metadata write never appears, which matters if you are routing value based on this list.
progress and price are WAD-scaled. Divide by 1e18 for a percentage, and remember price is per whole token, so
a market cap is price × 1e9 wei.
Trades#
curl -s "https://api.joseon.fun/api/tokens/0x171e…b926/trades?limit=50"Curve trades and pool swaps come back in one list, newest first, each tagged with venue (CURVE or DEX) and side.
feeAmount is filled in for curve trades and "0" for swaps, whose fees are booked inside the pool rather than emitted
per swap. limit defaults to 100 and caps at 500.
Candles#
curl -s "https://api.joseon.fun/api/tokens/0x171e…b926/candles?interval=300"interval is in seconds, clamped between 60 and 86,400. Buckets are built from both curve trades and pool swaps, with
swaps normalised to the same eth-per-token price the curve used, so a graduation does not put a step in the series.
Holders#
curl -s "https://api.joseon.fun/api/tokens/0x171e…b926/holders?limit=20"Returns { token, holderCount, holders } sorted by balance descending. The curve, the pair, and the zero address are
excluded, so the count reflects real holders rather than protocol addresses.
Pool stats#
curl -s "https://api.joseon.fun/api/tokens/0x171e…b926/pool-stats?days=7"Windowed swap volume for a graduated pool, which is what the app's fee APR figures are built on. days clamps between 1
and 90. Curve trades are deliberately excluded: they pay the curve's creator fee, not LP fees, so folding them in would
overstate what an LP can expect.
Portfolio#
curl -s https://api.joseon.fun/api/wallets/0x76ce…9da4/portfolioReturns holdings (non-zero balances with token metadata), created (tokens this wallet launched), and the wallet's
last 100 trades across both venues.
Live feed#
wss://api.joseon.fun/api/live?since=<cursor>One WebSocket, one indexing loop. Instead of every browser polling the chain, the indexer reads it once and pushes each
event out. Messages are JSON with a monotonic cursor, and each event carries a kind of launch, trade,
graduation, or chat.
Reconnect with since=<last cursor you saw> and you get what you missed. If the gap is bigger than the replay buffer,
the reply says so and you should refetch from the REST endpoints rather than assume you are current.
Some events arrive marked pending: true. Those are preconfirmations, useful for showing "in progress" and nothing else —
they can change or disappear. Wait for the confirmed event before treating anything as settled.
Writes are signed#
Both write paths prove wallet control before they touch anything, because both feed surfaces where money is at stake: a token's profile is what the trading UI reads, and a chat message appears next to a wallet address on a trading page.
Metadata: ask for a challenge, sign it with the launch's on-chain creator wallet, then send the fields with that signature. The signature commits to the chain, the token, the signer, an expiry, and the exact content being written, so it cannot be replayed against something else. Challenges are single-use.
Chat: ask for a challenge, sign it, exchange it for a short-lived bearer session, then post with that session.
Identity fields — creator, curve, transaction, block — are always read from the factory's own event, never from the request body. Uploaded images are identified by their actual bytes rather than a declared content type.
Freshness#
The indexer follows confirmed logs from a persisted checkpoint, a few blocks behind the head, in bounded ranges. A restart or an RPC hiccup resumes without gaps or double counting, and if the chain reorganises below the confirmation depth the affected range is rewound and reindexed rather than left wrong.
What gets indexed:
| Source | Feeds |
|---|---|
| Launch factory | the launch list and each launch's reserved pool |
| Curve pools | trades and candles |
| Migration | pool address and migration amounts |
| Pairs | post-graduation trades |
| Tokens | holder balances |
In practice a new launch or trade shows up in a second or two: the live feed indexes continuously, and POST /api/sync
nudges a pass right after a wallet action.
Errors#
Unknown routes and missing records return 404 with a plain-text body. Media returns 503 if storage is briefly
unavailable. Rejected writes return 401, 403, or 429 with a short JSON error. There is no structured error
envelope, so check the status code before parsing.