Joseon docs
AppExplorerLaunch app

Build

Indexer API

Read launches, trades, candles, holders, and portfolios.

Base URL#

https://api.joseon.fun

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

MethodPathReturns
GET/api/healthservice and chain id
GET/api/eth-priceETH/USD spot, 60s cache
GET/api/sync-statusnext block to index
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/wallets/:addr/portfolioholdings, created tokens, trade history
GET/meta/:addr.jsoncrawler-friendly metadata document
GET/media/:fileuploaded logos, immutable cache
POST/api/tokens/:addr/metadataupload logo and description
POST/api/syncnudge a sync batch
POST/api/admin/syncmanual 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
    }
  ]
}
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
tokenIs0whether the token is token0 in the pair, which fixes reserve ordering
feeSharesoff-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/portfolio

Returns 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.com

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

SourceEventFeeds
Launch factoryTokenCreatedthe launch list
Bonding curveTokenBought, TokenSoldtrades and candles
Migration managerTokenGraduatedpair address, migration amounts
PairSwappost-graduation trades
TokenTransferholder 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.