Live new token launches over WebSocket
The pulse channel: a Pulse list, the whole list first, then only the rows that changed.
Subscribe to pulse with view and any of Pulse's filters, sort, order, limit and offset, as parameters of the message. They are checked as REST checks them: a bad one is 400 with the same error text.
Subscribe:
{"op":"subscribe","id":"g","channel":"pulse","chain":"solana","view":"graduating","progress":"0.5..","limit":20}
| type | When | data |
|---|---|---|
subscribed | Once, first | Echoes view, sort, order, limit, offset |
pulse | Right after, with the whole list; then after each rebuild that changed the list or a listed token | view, total, updatedAt, order, tokens |
orderis the whole list, as addresses in order. It is authoritative: render it.tokenscarries only the rows that are new or changed since the previous message. The first message, and the first afterresubscribed, carries every row.- Keep a map from address to row, apply
tokensto it, and renderorder. A token missing fromorderhas left the list: filters are checked again at every rebuild.
Captured on a test instance:
{"type":"subscribed","id":"u1","channel":"pulse","chain":"evm:56","view":"new","sort":"createdAt","order":"desc","limit":1,"offset":0}
{
"id": "u1",
"type": "pulse",
"data": {
"view": "new",
"total": 2,
"updatedAt": 1790618622692,
"order": [
"0x5eed00000000000000000000000000000000c444"
],
"tokens": [
{
"address": "0x5eed00000000000000000000000000000000c444",
"symbol": "NEWT3",
"name": "Newt Three",
"logo": "http://localhost:7170/logos/evm:56/0x5eed00000000000000000000000000000000c444?v=cccccccccccc",
"launchpad": "fourmeme",
"pool": "0xed80c90b543397140863599d535a4427ed378a30e094a01a025bea52acb9fcc7",
"createdAt": 1790548061920,
"creator": "0x0f325bd174c70440fa62c31dce9f69d54baf717e",
"socials": {
"website": null,
"twitter": null,
"telegram": null,
"discord": null,
"others": {}
},
"priceUsd": 0.000015000000000000002,
"marketCapUsd": 15000.000000000002,
"liquidityUsd": null,
"progress": 0.18,
"graduatedAt": null,
"latestTradeAt": 1790548064920,
"creatorNetShare": 0,
"sniperCount": 2,
"sniperNetShare": 0.022,
"windows": {
"5m": {
"priceChange": 0,
"volumeUsd": 0,
"buyVolumeUsd": 0,
"sellVolumeUsd": 0,
"trades": 0,
"buys": 0,
"sells": 0,
"traders": 0,
"buyers": 0,
"sellers": 0,
"feesPaidUsd": null
},
"1h": {
"priceChange": 0,
"volumeUsd": 0,
"buyVolumeUsd": 0,
"sellVolumeUsd": 0,
"trades": 0,
"buys": 0,
"sells": 0,
"traders": 0,
"buyers": 0,
"sellers": 0,
"feesPaidUsd": null
},
"6h": {
"priceChange": 0,
"volumeUsd": 0,
"buyVolumeUsd": 0,
"sellVolumeUsd": 0,
"trades": 0,
"buys": 0,
"sells": 0,
"traders": 0,
"buyers": 0,
"sellers": 0,
"feesPaidUsd": null
},
"24h": {
"priceChange": null,
"volumeUsd": 330,
"buyVolumeUsd": 330,
"sellVolumeUsd": 0,
"trades": 2,
"buys": 2,
"sells": 0,
"traders": 2,
"buyers": 2,
"sellers": 0,
"feesPaidUsd": null
}
}
}
]
}
}
Client:
// Follow a Pulse list: keep rows by address, apply each message's tokens, render its order.
// Bun, Node 22+ or a browser. Reconnects with backoff; a refused key is not retried.
const subscribe = {
op: "subscribe", id: "new", channel: "pulse", chain: "solana", view: "new",
launchpad: "pumpfun", marketCapUsd: "5000..", limit: 20,
};
const rows = new Map(); // address → token
function render(order) {
const lines = order.map((address) => {
const t = rows.get(address);
const cap = t.marketCapUsd === null ? "-" : `$${Math.round(t.marketCapUsd)}`;
const progress = t.progress === null ? "" : ` ${Math.round(t.progress * 100)}%`;
return `${t.symbol ?? address.slice(0, 6)} ${cap}${progress}`;
});
console.log(lines.join("\n"), "\n");
}
let delay = 1000;
function connect() {
const ws = new WebSocket("wss://app.aurelia.build/api/live");
ws.onopen = () => ws.send(JSON.stringify(subscribe));
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === "subscribed") delay = 1000;
if (msg.type === "error") return console.error(msg.status, msg.error); // fix the filters
if (msg.type !== "pulse") return;
for (const t of msg.data.tokens) rows.set(t.address, t);
const listed = new Set(msg.data.order);
for (const address of rows.keys()) if (!listed.has(address)) rows.delete(address); // left the list
render(msg.data.order);
};
ws.onclose = (e) => {
if (e.code === 4401) return console.error("refused:", e.reason);
setTimeout(connect, delay);
delay = Math.min(delay * 2, 30_000);
};
}
connect();