08 · docs/API.mdOpenPons manual

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: TokenLaunched events from the factory over a rolling ~150k-block window, newest first. The response includes window.
  • active: curves ranked by the number of CurveBuy/CurveSell logs in the last 6,000 blocks (~10 min). Each token carries activity { trades, volumeEth, windowBlocks }; volumeEth is null for ERC-20-quoted curves.
  • graduated: tokens with a factory LaunchSwept event. History is scanned backwards in the background; the response includes scan { 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.

Independent · not affiliated with Pons or Robinhood@openpons