Joseon.funDocs
AppExplorerOpen app

Build

Indexer API

Read launches, trades, candles, holders, portfolios, and the live feed.

Base URL#

https://api.joseon.fun

A 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#

MethodPathReturns
GET/api/healthservice and chain id
GET/api/eth-priceETH/USD spot, 60s cache
GET/api/sync-statusnext block to index
GET/api/analyticslaunch count and daily volume
GET/api/launchesevery launch with live curve or pool state
GET/api/tokens/:addrtoken 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/portfolioholdings, created tokens, trade history
GET/meta/:addr.jsoncrawler-friendly metadata document
GET/media/:fileuploaded logos, immutable cache
GET/api/liveWebSocket event feed
POST/api/tokens/:addr/metadataupload logo and description, signature required
POST/api/tokens/:addr/chatpost a message, wallet session required
POST/api/syncnudge 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
    }
  ]
}
FieldMeaning
state0 curve active, 1 graduating, 2 DEX active
progressWAD, 1000000000000000000 is a sold-out curve
pricewei of ETH per whole token
reserveETH in the curve, or the pool's ETH-side reserve once graduated
graduatedtrue once graduatedPair is set
reservedPairthe pool claimed at creation, present before graduation too
tokenIs0whether the token is token0 in the pair, which fixes reserve ordering
feeSharesretired 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/portfolio

Returns 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:

SourceFeeds
Launch factorythe launch list and each launch's reserved pool
Curve poolstrades and candles
Migrationpool address and migration amounts
Pairspost-graduation trades
Tokensholder 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.