Realtime and candles
The Server-Sent Events stream, what it carries and what it does not, and the exact candle rules, including who fills empty buckets.
There is no WebSocket. Realtime is Server-Sent Events on the moto.fun app backend, with polling as the supported fallback, and the Motoswap API's ETags as the cheap way to poll.
The stream
GET <motofunUrl>/backend-fun/<chain-slug>/streamA firehose. There is no subscription protocol, no query parameter, and no way to filter to one coin or one event type: you receive every event for every coin on that chain and filter locally.
Each message is a named SSE event whose data is the whole payload:
event: trade
data: {"type":"trade","token":"0x…"}The payload is a notification, not data
Every event carries exactly { type, token }. No amounts, no prices, no block numbers, no trader.
The stream tells you what changed, and you re-fetch the REST queries it touched. Any pipeline that
tries to reconstruct state from the stream alone will be wrong.
type | Fires when |
|---|---|
created | TokenCreated was indexed |
trade | Any Trade was indexed |
frozen | FloorReached was indexed |
unfrozen | A sell past the reopen delay walked a Frozen coin back to Trading |
graduated | Graduated was indexed |
moto_leg_completed, moto_leg_deferred | Retired. Nothing on the current curve emits them |
token is always lowercased at publish time.
Connection rules
- A
pingevent is written on connect and every 25 seconds. Treat a much longer gap as a dead connection. - 2000 concurrent streams globally and 40 per IP by default, both configurable per deployment. Over
the global cap you get
503, over the per-IP cap429, each with aRetry-Afterheader. - There is no
id:field, noLast-Event-IDhandling, and no replay buffer. A reconnect resumes blind with a gap you cannot recover from the stream. On reconnect, re-fetch what you care about, then resume listening. StandardEventSourceauto-reconnect works, it just does not backfill. - The bus is in-process. One process, one stream.
Polling instead
Nothing requires SSE. The indexer polls every 5 seconds and /tokens is cached for 5 seconds, so polling
that service faster than about 5 seconds buys nothing. On the Motoswap API, poll /motofun/* with
If-None-Match: those ETags version on the curve's own head block, so a quiet curve returns 304
through every DEX block.
Candles
GET <motofunUrl>/backend-fun/<chain-slug>/tokens/{address}/candles?resolution=<sec>&from=<unix>| Parameter | Type | Default | Bounds |
|---|---|---|---|
resolution | integer seconds | 300 | Clamped to [60, 86400]. A continuous range, not an enum |
from | unix seconds | now - 86400 | Raised to the window floor, then aligned down to a bucket edge |
The response is a bare JSON array, not an envelope:
[
{
"bucket": 1755993600,
"low": 1300000000,
"high": 1400000000,
"trades": 6,
"volume": "410000000000000000",
"open": "1310000000",
"close": "1395000000"
}
]The rules, exactly
- Buckets are UTC epoch-aligned:
floor(timestamp / resolution) * resolution, no offset. - The window is 2000 buckets, anchored to the coin's last trade, not to wall clock. A dormant
coin therefore serves the tail of its own trading life at every resolution instead of an empty
set. A
fromearlier than that floor is raised to it; afromlater than it is honoured as given. - One seed row is prepended: the newest bucket strictly before the window, so a client's fill-forward has a bar to bridge from. A response can hold 2001 rows.
- Empty buckets produce no row at all. There is no server-side fill-forward, no synthetic OHLC, and no zero-volume bar. The array is sparse.
lowandhighare JSON numbers.openandcloseare decimal strings. The asymmetry is real, andopenandcloseare optional: treat them as possibly absent.volumeis an exact decimal wei string and sums buys and sells together. There is no side filter.tradesis a count.- A coin with no trades returns
[]with status200. Never a404, never an error, and there is nonextTimeornoDataenvelope. This is not a TradingView UDF datafeed.
Fill-forward is the client's job
The API is sparse by design and the app fills forward on the client. If you are drawing a chart you must synthesize the flat bars yourself, or your gaps will render as holes rather than as quiet markets.
What the app does, if you want to match it
The app's chart carries 1m, 5m, 10m, 30m, 1h and 6h, mounts at 1 minute, and applies these rules on top of the sparse response:
- Fill forward to the current bucket. Every empty bucket becomes a flat, zero-volume candle whose open, high, low and close all equal the previous close. Every real bar opens at the previous close. Capped at 10,000 filled buckets.
- Prices convert from the
1e18fixed-point values directly. A naive wei-to-float helper with a 1e-6 floor renders a live curve price as zero; curve prices routinely sit near 1e-9 ETH. - After graduation the series joins the curve history to the pool history at the graduation bucket, so the chart does not restart. The bucket at the seam keeps its own open rather than inheriting the previous close.
Graduated coins move venue
Once a coin has graduated, its candles come from the DEX side, not the curve. The curve series is
frozen history. Use /motofun/coins/{address} to learn the status and the pair addresses, then read
the pool chart from the Motoswap API like any other pool.
Where to go next
- API for both HTTP surfaces.
- Scanners to index the logs yourself instead.
- Full API inventory for exact shapes.