Build
Indexer API
Read launches, trades, candles, holders, and portfolios.
Base URL#
https://api.joseon.funA Cloudflare Worker indexes GIWA Sepolia into D1 and serves it as JSON. Reads are public, unauthenticated, and CORS-open, so you can call them straight from a browser. There is no rate limit documented — be reasonable.
Amounts are always decimal strings in base units (wei for ETH, 1e18 for tokens). They exceed Number.MAX_SAFE_INTEGER,
so parse them with BigInt, not parseFloat.
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/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/wallets/:addr/portfolio | holdings, created tokens, trade history |
GET | /meta/:addr.json | crawler-friendly metadata document |
GET | /media/:file | uploaded logos, immutable cache |
POST | /api/tokens/:addr/metadata | upload logo and description |
POST | /api/sync | nudge a sync batch |
POST | /api/admin/sync | manual catch-up, bearer token required |
Launches#
curl -s https://api.joseon.fun/api/launches{
"launches": [
{
"token": "0xdd34da56701987bfb24b255c9f10bd9f4dff86ab",
"creator": "0x76cea8ae31dc4b23806b757e55758d82c6169da4",
"curve": "0x03d140998c8d3c2bb793c972fb74eb7aae090925",
"name": "Giwa",
"symbol": "GIWA",
"initialBuy": "100000000000000000",
"createdBlock": "31727099",
"createdTx": "0x5b6ecdcf…",
"graduatedPair": "0xaf0114c1a2f91f6bfcad3128d45bf636069e3e74",
"graduatedBlock": "31727133",
"migrationWeth": "3600000000000000000",
"migrationToken": "200000000000000000000000000",
"tokenIs0": 1,
"imageUrl": "https://api.joseon.fun/media/0xdd34…86ab.jpg",
"website": "https://giwa.io",
"feeRecipient": "0x76cea8ae…",
"feeShares": [{ "wallet": "0x76cea8ae…", "bps": 10000 }],
"graduated": true,
"progress": "1000000000000000000",
"price": "1178028017",
"reserve": "921252157177225341",
"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 |
tokenIs0 | whether the token is token0 in the pair, which fixes reserve ordering |
feeShares | off-chain creator split; on-chain accrual always targets feeRecipient |
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 pair; if that read fails the worker falls back to the curve.
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/0xdd34…86ab/trades?limit=50"Curve trades and DEX swaps come back in one list, newest first, each tagged with venue (CURVE or DEX) and side.
feeAmount is populated for curve trades and "0" for DEX swaps, whose fees are booked inside the pair rather than
emitted per swap. limit defaults to 100 and caps at 500.
Candles#
curl -s "https://api.joseon.fun/api/tokens/0xdd34…86ab/candles?interval=300"interval is in seconds, clamped between 60 and 86,400. Buckets are built from both curve trades and DEX swaps, with
pool swaps normalised to the same eth-per-token price the curve used — so a graduation does not create a discontinuity
in the series.
Holders#
curl -s "https://api.joseon.fun/api/tokens/0xdd34…86ab/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. Balances are maintained as BigInt deltas from Transfer events, because
a 1e27 supply overflows SQLite's 64-bit integers.
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.
Uploading metadata#
curl -X POST https://api.joseon.fun/api/tokens/0xTOKEN/metadata \
-F image=@logo.png \
-F description='A test launch' \
-F website=https://example.comThe worker stores the image in R2, writes a metadata document reachable at /meta/:addr.json, and optionally pins to
IPFS when configured. That document is what the token's on-chain tokenURI() resolves to. See
token metadata.
Sync behaviour#
The indexer backfills confirmed logs in bounded 500-block ranges from a persisted checkpoint, at a 5-block confirmation depth, so a restart or an RPC failure resumes without gaps or double counting. A cron trigger runs the same catch-up loop every minute.
POST /api/sync nudges a batch — the app calls it right after a create, trade, or swap so the UI does not wait up to a
minute for the cron. Indexed events:
| Source | Event | Feeds |
|---|---|---|
| Launch factory | TokenCreated | the launch list |
| Bonding curve | TokenBought, TokenSold | trades and candles |
| Migration manager | TokenGraduated | pair address, migration amounts |
| Pair | Swap | post-graduation trades |
| Token | Transfer | holder balances |
LAUNCH_FACTORY and MIGRATION_MANAGER accept comma-separated lists, so a retired stack can be re-indexed by
configuration — though that also needs the start block rewound far enough to cover it.
Errors#
Unknown routes return 404 with a plain-text body. Missing records return 404. Media endpoints return 503 when
object storage is unavailable. There is no structured error envelope, so check the status code before parsing.