API and live feed
The data API serves what the app itself reads: coins, trades, holders, market state and fee configuration. No account, no key, no agreement.
https://api.frontier.fun
Everything is served for Robinhood Chain, id 4663. Endpoints taking a chainId parameter default to it, so it can be omitted.
Quick start
curl 'https://api.frontier.fun/coinsPage?sort=volume24h&limit=1'
{
"data": [
{
"id": "0x4eCB998F7F7Fec237f9d6d6321038933669dd0cC",
"token": "0x4eCB998F7F7Fec237f9d6d6321038933669dd0cC",
"name": "great work",
"symbol": "GREAT",
"image": "ipfs://bafkreignslcu4pda3myluyggbix5td4dsmfsyi5726avpnhsu3nkuqsgl4",
"creator": "0x9cE7aA416E2c3E7cFd5F3f7c19Fe0201cd2235C9",
"launchKind": "curve",
"isLPd": false,
"price": "0.000000002466473546",
"marketCap": "1.032698723869568022",
"volume24h": 0.712236692171066,
"bondingProgress": 0.16956346471975636
}
],
"total": 11
}
The row is abridged here; the full shape is The coin row.
Conventions
Response envelopes
| Shape | Used by |
|---|---|
{ "data": … } | most reads |
{ "success": true, "data": … } | /coinCandles, /pools-v4/…/history/txs, /tokens |
{ "data": [...], "total": n } | the paginated listing |
| raw bytes | /ipfs/{hash} |
Types
| Kind | Encoding |
|---|---|
| Addresses | 0x + 40 hex, accepted in any case — see Address casing |
| Pool ids | bytes32, 0x + 64 hex |
| Coin and ETH amounts | decimal strings, already scaled out of wei |
| Prices, market caps | strings in ETH, or numbers where the field is a computed statistic |
| Volumes | numbers in ETH, except fields named USD |
| Timestamps | Unix seconds |
| Images | ipfs://<cid> references, fetchable through /ipfs/{cid} |
| Basis points and pips | strings of integers, as the contracts store them |
Amounts are strings because they come straight from on-chain values and must not pass through a float. Statistics computed by the API, such as volume24h, are numbers by design.
Address casing
Send an address in whatever case you have it. Lowercase, checksummed or anything in between resolves to the same coin, the same wallet and the same answer, on every endpoint and in every parameter.
What comes back follows one rule:
- An address field is EIP-55 checksummed —
token,creator,user,userAddress,stakingVaultand the rest. That is the form to display, and the form an explorer or a wallet expects. - An address used as an object key is lowercase — the map in
/tokens, the map in/vaults. A key is something you look up, so it has one spelling, and your own lookups do not have to guess.
Compare addresses case-insensitively anyway: it costs nothing and it survives a value arriving from somewhere else, a URL or a wallet among others.
Errors
{ "success": false, "error": "…" }
On a validation failure error is the validation report rather than a string, listing the offending parameters:
{
"success": false,
"error": {
"name": "ZodError",
"message": "[{ \"code\": \"invalid_value\", \"path\": [\"kind\"], … }]"
}
}
| Status | Meaning |
|---|---|
400 | A parameter is missing, malformed or out of range |
404 | Unknown route |
429 | Rate limited. A Retry-After header says when to come back |
503 | An upstream read failed or the surface is not available on this chain. Treat it as "no data right now" and retry, never as an error state to show |
500 | Unexpected. Worth reporting |
Rate limits
Per IP, per minute:
| Tier | Limit | Endpoints |
|---|---|---|
| Standard | 120 | everything not listed below |
| Heavy | 30 | /coinCandles, /coins/{address}/extensions, and the four vault endpoints |
| Media | 600 | /ipfs/{hash} |
| Upload | 10 | /upload — it pins to our account |
Caching
Answers are cached at the edge and carry their own Cache-Control. Polling faster than the cache window returns the same bytes.
| Endpoint | Cached |
|---|---|
/coinsPage, /coinEvents, /recentActivity, /bondedActivity | 5s |
/coinsStats | 15s |
/coinsTop, /currentHighlight, /coinCandles | 30s |
/highlights | 60s |
| vault and referral reads | 10s |
Five seconds is the useful polling floor for the fast surfaces.
Objects
The coin row
Returned by /coinsPage and /coinsHydrate. It is card-complete: everything needed to render a coin, with no follow-up call.
| Field | Type | Notes |
|---|---|---|
id, token | address | The coin's ERC-20 address, same value in both |
name, symbol | string | |
image | string | ipfs://…, may be empty |
description | string | Only when full=1 is asked of /coinsHydrate |
creator | address | |
createdTimestamp | number | |
launchKind | curve | instant | The launch mode |
isLPd | boolean | Whether the coin is on its pool. With launchKind this gives the three trading phases |
lp | bytes32 | The pool id, once it exists |
price | string | ETH per coin. After graduation it follows the pool |
price24hAgo | number | null | The oldest price mark in the window, for the 24-hour change |
marketCap | string | ETH |
totalSupply, maxSupply | string | Circulating and maximum |
ETHReserves, targetETH | string | Curve reserves and the raise target |
curveAllocation | string | Coins the curve sells before graduation |
initialETHReserves, initialPrice | string | The curve's starting point, frozen at creation |
bondingProgress | number | null | 0 to 1, on the reserves basis. null when the coin has no curve |
volume24h | number | ETH, curve and pool volume folded together |
curveVolume24h, ammVolume24h | number | The two halves, kept readable apart |
volume24hAt | number | When the volume snapshot was computed |
eventsCount | number | Lifetime curve trades |
lastActivityAt | number | |
modules | array | The extensions bound to the pool: { id, name, kind }. Absent until the pool is indexed |
feeKind | dynamic | fixed | Whether the pool runs the volatility fee or a flat one |
Unknown fields may appear. Read by name and ignore the rest.
The trade event
Returned by /coinEvents, /recentActivity and /bondedActivity. Curve trades and pool swaps share one shape, so a single client can render both.
| Field | Type | Notes |
|---|---|---|
id | string | Unique. {txHash}-{logIndex} on the curve, {txHash}:{poolId} on a pool |
event | Buy | Sell | Direction from the trader's point of view |
token | address | |
user | address | The trader |
amount | string | What went in: ETH on a buy, coins on a sell |
amountOut | string | What came out: coins on a buy, ETH on a sell |
price | string | ETH per coin right after the trade |
timestamp | number | |
transactionHash | string | Curve events |
marketCap, totalSupply, reserveBalance | string | Curve events, state right after the trade |
bondedEvent | boolean | true on pool swaps, absent on curve trades |
name, symbol | string | Added by /recentActivity, so feed items stand alone |
Discovery
GET /chains
The chain registry: RPCs, explorers, every contract address, the pool parameters, the curve defaults and bounds, and the indexing endpoints. Nothing should hardcode an address that this document serves. See Contracts and addresses.
curl https://api.frontier.fun/chains
[
{
"id": 4663,
"name": "Robinhood",
"type": "mainnet",
"rpcs": ["https://robinhood-rpc.publicnode.com", "…"],
"explorers": ["https://robinscan.io", "…"],
"native": { "symbol": "ETH", "decimals": 18, "wrapped": "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73" },
"contracts": {
"tokenFactory": "0xe3A826C056e578c240D362BF4C2fa53E5c0c17a5",
"univ4": { "poolManager": "…", "quoter": "…", "hook": "…", "pool": { "fee": 8388608, "tickSpacing": 60 } }
},
"curveConfig": { "maxSupply": "1000000000000000000000000000", "…": "…" }
}
]
The response is an array: one entry per chain served.
GET /extensions
The extension catalog, fee calculators first. Each entry carries its own parameter schema, so an extension admitted to the catalog needs no client release to be configurable.
| Parameter | Type | Default |
|---|---|---|
chainId | number | 4663 |
{
"chainId": 4663,
"extensions": [
{
"id": "dynamic-fee",
"kind": "feeCalculator",
"name": "Dynamic Fee",
"status": "active",
"orderRank": 0,
"baseFeeProvider": true,
"incompatibleWith": ["maturity-curve"],
"configAbi": [
{ "key": "floorFee", "abiType": "uint24" },
{ "key": "midFee", "abiType": "uint24" },
{ "key": "capFee", "abiType": "uint24" }
],
"params": { "encoding": "abi", "fields": [ … ] }
}
]
}
configAbi is the encoding of the extension's on-chain configuration, in order. status is active or coming-soon; an entry that is not active is listed but not bindable.
Coins
GET /coinsPage
One page of coins, sorted, filtered and searched server-side.
| Parameter | Type | Default | Notes |
|---|---|---|---|
sort | enum | activity | activity, newest, oldest, marketcap, volume24h, trending (curve trades in the last 24h) |
filter | enum | all | all, new (created within 4 hours), bonded, trending, instant |
order | enum | desc | asc walks each axis the other way |
search | string | Substring of the name or symbol, or an address prefix. 1 to 64 characters | |
offset | number | 0 | Up to 10000 |
limit | number | 20 | Up to 100 |
Returns { data: [coin row], total }. total counts what the chosen axis can serve, so on volume24h it counts the coins that traded in the window. A search that hits its scan bound answers capped: true, meaning total is a floor.
On the mutable sorts a coin trading between two calls can cross a page boundary. Refetch the page rather than assuming offsets are stable.
GET /coinsHydrate
The same rows for a known set of coins: deep links, holdings, anything outside the paginated grid.
| Parameter | Type | Default | Notes |
|---|---|---|---|
addresses | string | required | Comma-separated, up to 100 |
full | 1 | Also resolve each coin's description |
Unknown addresses are dropped from the answer rather than returned empty.
GET /coinsStats
{
"data": {
"total": 11,
"newTodayCount": 1,
"bondedCount": 0,
"totalVolume24h": 5.271701598958937,
"curveVolume24h": 4.917442490567309,
"ammVolume24h": 0.35425910839162805,
"totalVolumeAllTime": 37.20174364623039,
"volumeComputedAt": 1786898147,
"ethPriceUsd": 1882.30325411
}
}
Counts are exact and live. Volumes are recomputed about once a minute; volumeComputedAt stamps the snapshot. ethPriceUsd is the rate the app uses to show USD figures.
GET /coinsTop
| Parameter | Type | Default | Notes |
|---|---|---|---|
kind | enum | gainers | gainers, or closest for the coins nearest to graduation |
limit | number | 10 | Up to 20 |
gainers entries carry price, price24hAgo and change as a percentage; closest entries carry progress from 0 to 1.
{
"data": [
{
"token": "0x4eCB998F7F7Fec237f9d6d6321038933669dd0cC",
"name": "great work",
"symbol": "GREAT",
"image": "ipfs://bafkreignslcu4pda3myluyggbix5td4dsmfsyi5726avpnhsu3nkuqsgl4",
"progress": 0.16956346471975636
}
]
}
GET /coinEvents
A coin's most recent curve trades, oldest first.
| Parameter | Type | Default | Notes |
|---|---|---|---|
tokenAddress | address | required | |
limit | number | 100 | Up to 200 |
Only the newest 200 trades per coin are kept on this surface. For anything older, use the candles.
{
"data": [
{
"id": "0x5d97b25e…4017-5",
"event": "Buy",
"token": "0x86A3d4890Cd62a1d44E6EC3D405406Ee925674c1",
"user": "0x652BCfE9958cae3f0758a1D0376D501A73390e2E",
"amount": "0.005597",
"amountOut": "2230215.570191796250870861",
"price": "0.00000000247880742",
"marketCap": "1.043304581352876953",
"totalSupply": "420889728.235877619414878571",
"reserveBalance": "2.179145067268661814",
"transactionHash": "0x5d97b25e…4017",
"timestamp": 1786898871
}
]
}
GET /coinCandles
OHLC buckets for a coin's curve trading, oldest first, prices and volume in ETH. Computed from the full indexed history, not from the retained events, so it goes back to the first trade.
| Parameter | Type | Default | Notes |
|---|---|---|---|
tokenAddress | address | required | |
interval | number | 300 | Seconds: 30, 60, 300, 600, 1800, 3600, 14400, 86400, 2592000 |
limit | number | 300 | Up to 500 |
chainId | number | 4663 |
{
"success": true,
"interval": 3600,
"data": [
{ "t": 1786892400, "o": 2.424213674e-9, "h": 2.424213674e-9, "l": 2.402081015e-9, "c": 2.411606836e-9, "v": 0.0209019 }
]
}
Buckets are sparse: an interval with no trade is absent rather than flat. Post-graduation price action is on the pool, not here.
GET /coins/{address}/extensions
The coin's live market configuration, read from the pool rather than from a cached row.
| Parameter | Type | Default |
|---|---|---|
address | address, in the path | required |
chainId | number | 4663 |
{
"chainId": 4663,
"coin": "0x1Fa8978238b6A43A68B9F51Ac70b6E7526A38a8E",
"poolId": "0x724cb43a2106e72650a7690f97f29c1fc69ec8cdffc5188bc385d06305afc1bd",
"hookConfig": {
"lpShareBps": "1000",
"fixedFee": "15000",
"sniperWindow": "0",
"communityFeeRatio": "0",
"stakingVault": "0x0000000000000000000000000000000000000000",
"registeredAtTimestamp": "1786806129"
},
"protocolFeeRatio": "25",
"extensions": {
"feeCalculators": [
{ "address": "0xb96fBe3c…75D8", "known": true, "id": "dynamic-fee", "kind": "feeCalculator", "rawConfig": "0x…" }
],
"observers": []
}
}
fixedFee is in pips, lpShareBps and communityFeeRatio in basis points, sniperWindow in seconds. Lists keep their on-chain order. Each bound entry arrives joined against the catalog, with the raw on-chain configuration in rawConfig, decodable with the catalog entry's configAbi. An address the catalog does not list comes back as known: false. A coin with no pool yet answers poolId: null with empty lists.
protocolFeeRatio is read live rather than copied, because it is owner-settable. See Admin controls.
Holders, and per-wallet views
The holder distribution of a coin, the coins a wallet has traded and the coins it created are served for the app's own screens, on endpoints that are not part of this reference. They are per-request reads with no cache in front of them, so they are kept for the app rather than offered as an integration surface.
If your integration needs holder or per-wallet data, ask at [email protected] rather than reverse-engineering the app's calls: the shape it uses is not stable, and a documented one can be.
Activity and markets
GET /recentActivity
The most recent curve trades across every coin, newest first. limit up to 50, default 20. Items carry name and symbol so a feed row needs no lookup.
GET /bondedActivity
The same, for swaps on graduated coins' pools. A graduated coin stops producing curve trades, so a complete activity feed merges the two.
{
"data": [
{
"id": "0x00abf1f5…31cc:0x724cb43a…fc1bd",
"token": "0x1Fa8978238b6A43A68B9F51Ac70b6E7526A38a8E",
"event": "Sell",
"user": "0xf91D05Ec8B943634268748c1733984E13fe9963b",
"amount": "32483587.620178584",
"amountOut": "0.07564535770703193",
"price": "2.2493273178952734e-9",
"timestamp": 1786893734,
"bondedEvent": true
}
]
}
GET /pools-v4/{poolAddress}/history/txs
Recent swaps, adds and removes for one pool, addressed by pool id or by the pool address the app shows.
| Parameter | Type | Default |
|---|---|---|
poolAddress | pool id or address, in the path | required |
chainId | number | 4663 |
{
"success": true,
"data": [
{
"time": 1786893734,
"value": {
"type": "swap",
"account": "0xf91D05Ec8B943634268748c1733984E13fe9963b",
"token0Amount": "0.07564535770703193",
"token1Amount": "32483587.620178584",
"isFromToken0": false,
"fromToken": "mittens",
"toToken": "WETH",
"txHash": "0x00abf1f5…31cc",
"priceInEth": "2.2493273178952734e-9",
"totalValueUSD": "0.00"
}
}
]
}
token1Amount / token0Amount includes the swap fee, and a Frontier pool sets its own fee, which can be high and can move between blocks. Use priceInEth, the price at the pool right after the swap, the same figure the candles and the activity feeds carry.
GET /tokens
24-hour volume in USD per coin, keyed by lowercase address.
{
"success": true,
"data": {
"error": null,
"tokens": {
"0x1fa8978238b6a43a68b9f51ac70b6e7526a38a8e": {
"address": "0x1Fa8978238b6A43A68B9F51Ac70b6E7526A38a8E",
"oneDayVolumeUSD": 0
}
}
}
}
Staking vaults
GET /vaults
Every staking vault, keyed by lowercase vault address, each with its coin, total staked assets, cooldown in seconds, and its events.
{
"data": {
"0x2dcc79ed57e8b90ae144e75e896b353687525c28": {
"id": "0x2DCC79ED57e8B90AE144E75e896B353687525C28",
"vault": "0x2DCC79ED57e8B90AE144E75e896B353687525C28",
"bcToken": "0x8fBDe5D76f24e73667E1be2B095fD8DF560E6fF6",
"totalAssets": 0,
"cooldownDuration": 86400,
"events": [ … ]
}
}
}
GET /vaultByToken
One vault, by tokenAddress. data is null when the coin has no vault.
GET /vaultEventsByToken
That vault's events by tokenAddress: deployment, deposits, withdrawal requests and claims, each with vault, user, assets and timestamp.
GET /vaultPositionByUserVault
One staker's position. Requires userAddress and vaultAddress; answers null when there is none.
| Field | Type |
|---|---|
user, vault | address |
currentDepositAmount | string, coins staked |
pendingWithdrawAmount | string, coins in cooldown |
Referrals and highlights
GET /referralRewards
Referral rewards earned by userAddress: referrer, referredUser, amount, token, timestamp.
GET /referralPayouts
Referral payouts received by userAddress: referrer, amount, timestamp.
GET /highlights · GET /currentHighlight
Recent highlights, and the one running now, or null. Entries carry token, buyer, amount and bookedUntil. Booking is not open yet, so both answer empty today.
Media
POST /upload
Pins an image to IPFS and returns its CID, so a launch made by calling the factory directly can carry a real logo instead of the placeholder. It needs no wallet and no key — a launch legitimately happens before a wallet is connected.
Send exactly one of:
image— a base64 data URL. The bytes are checked against the type you declare (magic bytes, or a structural check for SVG), so a mislabelled file is rejected rather than pinned.imageUrl— a public http(s) URL; we fetch it server-side. Plain public hostnames only (no literal IPs, no localhost, nothing of ours), at most 3 redirects — each hop re-checked against the same rules — and the type is read from the fetched bytes, not from the server's headers.
Either way: PNG · JPEG · GIF · WebP · SVG, 2 MB maximum — for a URL the cap is enforced while downloading, so pointing us at something huge just cuts the transfer.
curl -X POST https://api.frontier.fun/upload \
-H 'Content-Type: application/json' \
-d '{"image":"data:image/png;base64,iVBORw0KGgo...","name":"my-coin"}'
# or, if the image already lives at a URL:
curl -X POST https://api.frontier.fun/upload \
-H 'Content-Type: application/json' \
-d '{"imageUrl":"https://example.com/my-logo.png","name":"my-coin"}'
{ "ipfsHash": "bafkreignslcu4pda3myluyggbix5td4dsmfsyi5726avpnhsu3nkuqsgl4" }
name is required and only labels the pin — omitting it is a 400, and anything past 128
characters is trimmed. Pass the CID to deploy() as the image argument, either bare or
as ipfs://<cid> — both resolve.
| Status | Meaning |
|---|---|
400 | A field is missing, both forms were sent at once, or the URL points somewhere we refuse to fetch |
413 | Over 2 MB |
415 | Not one of the five accepted types, or the bytes contradict the type declared |
502 | The URL could not be fetched, or pinning failed — retry; nothing was stored |
429 | Over the rate limit below; retry after a minute |
502 | The pinning service failed — never a 200 with a missing CID |
Rate-limited to 10 per minute per IP — it pins to our account, so it is the tightest bucket on the API. If you already hold the image at a URL, you still have to fetch and encode it yourself for now.
Uploading is not what makes the image survive. The CID is adopted by our own pinning when the deployment is indexed — but if you pin the image elsewhere and it lapses there before that happens, the bytes are gone for good while the CID stays on-chain forever. Uploading here first is the safe order.
GET /ipfs/{hash}
Fetches a CID through the gateway, streamed and cached immutably. Coin rows carry ipfs://<cid>; swap the prefix for this endpoint. The path accepts a bare CID and nothing else. Content larger than 2 MB — the same bound as /upload — is refused with a 413: a coin image is at most 2 MB however it arrives on-chain.
https://api.frontier.fun/ipfs/bafkreignslcu4pda3myluyggbix5td4dsmfsyi5726avpnhsu3nkuqsgl4
Calling it from a browser
The API is meant to be called server-side. Only the app's own origins are on the cross-origin allow list, so a page hosted elsewhere cannot call it from the client. Proxy it through your own backend, which also lets you cache and rate-limit for your users.
The live feed
The app holds a WebSocket open and receives trades, deploys, graduations and vault events as they happen, roughly a tenth of a second behind the write that records them, with a short replay on reconnect so nothing is missed across a blip.
It is not open to third-party clients yet. Connections are accepted from the app's own origins, and the message protocol is not stable enough to publish. Until it is, the polling equivalent is /recentActivity and /bondedActivity merged newest-first, both refreshed every five seconds, plus /coinsPage for the state of the grid.
If a push feed is what your integration needs, say so at [email protected]. Access is a question of readiness, not of policy.
Guarantees
- The chain is the authority. Everything here is indexed from it. A trade is final when it is on-chain, not when it appears in a response.
- Nothing is authenticated, so nothing served is private. Every figure comes from public state.
- Event ids are stable and unique. Deduplicate on
idrather than on position; a feed can re-deliver an item. - Ordering is by timestamp, newest first on the feeds, oldest first on
/coinEventsand the candles. Two trades in the same block can arrive in either order. - Counters and volumes are snapshots, recomputed about once a minute, and the response says when.
- Shapes are additive, not versioned. Fields get added; read by name and ignore what you do not know.
Anything unclear, or a shape you need that is not served: [email protected].