REST API — /api/v1
Base URL: https://openpons.xyz/api/v1 (Cloudflare Workers). Self-hosting / local dev:
http://localhost:4663/api/v1.
- JSON only. CORS
*for GET/POST. - Auth: none during the beta. A hosted key system is PREVIEW (designed, not active); keys
will only ever scope
read/prepare. - Rate limit: best-effort 120 requests / minute / IP per server instance →
429 RATE_LIMITED. - All on-chain integers are decimal strings in base units. Addresses are checksummed.
Envelope
{ "ok": true, "data": {}, "meta": { "chainId": 4663, "version": "v1", "timestamp": "…" } }
{ "ok": false, "error": { "code": "NOT_A_PONS_TOKEN", "message": "…", "details": null }, "meta": { … } }
Errors
| Code | HTTP | Meaning |
|---|---|---|
BAD_REQUEST |
400 | missing/invalid parameter or body |
INVALID_ADDRESS |
400 | not a 20-byte hex address |
INVALID_AMOUNT |
400 | not a positive decimal ≤18 dp, or exceeds balance |
NOT_A_PONS_TOKEN |
404 | not registered in the Pons V2 factory |
UNSUPPORTED |
501 | graduated curve, ERC-20 pair, or pinned contracts changed |
RATE_LIMITED |
429 | too many requests |
UPSTREAM_ERROR |
502 | RPC failure after retries |
Read endpoints
GET /health
{ status, chainId, blockNumber, rpc, latencyMs }
GET /contracts
Verified contract list, event topics, V2 start block, index status.
GET /tokens
| Param | Values |
|---|---|
sort |
new (default) · active · graduated |
q |
name / symbol substring or full address (applies to new, over the indexed window) |
limit |
1–50 (default 24) |
cursor |
from nextCursor (new only) |
new:TokenLaunchedevents from the factory over a rolling ~150k-block window, newest first. The response includeswindow.active: curves ranked by the number ofCurveBuy/CurveSelllogs in the last 6,000 blocks (~10 min). Each token carriesactivity { trades, volumeEth, windowBlocks };volumeEthis null for ERC-20-quoted curves.graduated: tokens with a factoryLaunchSweptevent. History is scanned backwards in the background; the response includesscan { complete, progress, found }.
TokenSummary
{
"address": "0x…", "name": "…", "symbol": "…", "decimals": 18,
"totalSupply": "1000000000000000000000000000",
"creator": "0x…", "createdBlock": "…", "createdAt": "ISO (curve.launchedAt)", "launchTx": "0x…",
"logo": "ipfs://…",
"curve": { "state": "active|graduated|unknown", "progress": 0.0083, "ethReserve": "…", "tokenReserve": "…" },
"priceEth": "0.0000000017", "marketCapEth": "1.75"
}
progress = realQuoteReserve / graduationThreshold. priceEth = Q / X from getReserves(),
and is null when graduated or ERC-20-quoted. An ERC-20-quoted token carries pairToken.
GET /tokens/:address
TokenSummary plus description, socials {twitter, telegram, discord, website, farcaster},
creatorTaxBps, creatorFeeRecipient, curveAddress, phase (0 bonding, 1 swept, 2 pool
created, 3 rescued), buybackEnabled, pairToken, holders: null.
GET /tokens/:address/trades?limit=1–100
{ trades: [{ txHash, blockNumber, timestamp, side, trader, ethAmount, tokenAmount }], scannedBlocks }.
timestamp is approximated from block height using the measured block rate. trader is the
event's recipient.
GET /quote
token, side (buy|sell), amount (ETH for buy, whole tokens for sell), slippageBps
(10–2000, default 100), recipient (optional; used for snipe-tax lookup).
{ "token": "0x…", "side": "buy", "amountIn": "10000000000000000", "expectedOut": "…", "minOut": "…",
"slippageBps": 100, "priceImpactBps": 56, "blockNumber": "…", "quotedAt": "…",
"route": "PonsV2BondingCurve.buy @ 0x…",
"fees": { "curveFee": "…", "creatorTax": "…", "snipeTax": "0" }, "warnings": [], "spotPriceEth": "…" }
GET /launch/config
Live launchFee, launchEnabled, maxCreatorTaxBps, expectedEconomics, config 0 economics, metadata byte limits.
GET /wallet/:address
{ address, ethBalance, nonce, isContract }
GET /wallet/:address/balance?token=
{ eth, tokenBalance? }. The token must be a Pons token.
GET /wallet/:address/portfolio?depth=1–48
Scans Transfer(*, wallet) logs over the last depth million blocks (default 8) and adds the
wallet's own launches. It keeps only factory-registered tokens with non-zero balance.
valueEth = balance × curve spot price, or null when unpriced. Also returns source,
scannedBlocks, candidates and skippedRanges.
GET /wallet/:address/launches
Full-history TokenLaunched logs with deployer == address.
{ launches: [{ token, curve, creatorFeeRecipient, creatorTaxBps, pendingCreatorTax, pendingBaseFees, buybackEnabled, holding }], fees }
GET /wallet/:address/fees
{ address, claimableEth, escrow }: PonsV2FeeEscrow.balanceOf(address).
GET /transactions/:hash
{ hash, status: pending|success|reverted|not_found, blockNumber, from, to, value, gasUsed, explorer }
Prepare endpoints (unsigned)
Every endpoint takes a JSON body and returns a PreparedTransaction. None of them broadcast.
{
"chainId": 4663, "from": "0x…", "to": "0x…", "data": "0x…", "value": "…", "gas": "…|null",
"function": "…", "description": "…",
"simulation": { "ok": true, "error": null, "gasEstimate": "…" },
"quote": { … }, "expiresAt": "ISO (+30s)", "warnings": [],
"requires": [ PreparedTransaction ], "meta": { … }
}
| Endpoint | Body | Result |
|---|---|---|
POST /prepare/buy |
{ token, amount, from, slippageBps? } |
curve.buy(quoteIn, minTokensOut, from), value = quoteIn |
POST /prepare/sell |
{ token, amount, from, slippageBps? } |
curve.sell(tokensIn, minQuoteOut, from); requires[0] = approve(curve, tokensIn) if the allowance is short |
POST /prepare/launch |
{ from, name, symbol, logo, description?, socials?, creatorTaxBps?, devBuyEth?, slippageBps? } |
factory.launchToken (no dev buy) or router.launchAndBuy; meta.predictedToken |
POST /prepare/claim |
{ from } |
escrow.claim(); 400 if the balance is 0 |
POST /prepare/sweep |
{ token, from } |
curve.sweepFees(0); the simulation fails unless from may sweep |
Before any buy, sell or launch is prepared, the factory and router codehashes are checked against the pinned values.