Skip to content

Live token lists

tokens.subscribe keeps a list of tokens up to date. The reply has the tokens as they are now, and each push after it only has what changed.

Pick tokens in one of these ways:

  • By ID: "ids": ["wrap.near", "usdt.tether-token.near"]. Use tokens.update to change the list later.

  • By filter: where narrows tokens down by reputation, parent account, creation time, or bounds on numeric fields:

    "where": { "min_reputation": "NotFake", "min": { "liquidity_usd": 10000 } }
  • A top list: sort and limit follow the top tokens by a numeric field as they change places. The sort field has to be one of fields:

    "sort": { "by": "volume_usd_24h", "desc": true }, "limit": 20

With neither ids nor where, it’s every token.

A push has up to three parts, each left out when empty:

{
"sub": 1,
"height": 219969292,
"timestamp_millis": 1792003519000,
"set": {
"wrap.near": { "price_usd": "4.82" },
"new-token.near": { "symbol": "NEW", "price_usd": "0", "volume_usd_24h": 0 }
},
"created": ["new-token.near"],
"left": ["old-token.near"]
}
  • set: the fields that changed, and only those. A token the list didn’t have before (a new token, or one that came to match where or into the top limit) has every field.
  • created: the tokens in set that were just created.
  • left: tokens the list no longer has.

So keeping a list is: merge set into what you have, and drop left.

Using the client from the Quickstart:

const tokens = new Map();
const { result } = await api.subscribe(
'tokens.subscribe',
{
fields: ['symbol', 'icon_hash', 'price_usd', 'volume_usd_24h'],
where: { min_reputation: 'NotFake' },
sort: { by: 'volume_usd_24h', desc: true },
limit: 20,
min_change: { price_usd: '0.1%', volume_usd_24h: '1%' },
precision: 6,
},
(push) => {
if (push.end) throw new Error(`Subscription ended: ${push.end.message}`);
for (const [token, fields] of Object.entries(push.set ?? {})) {
tokens.set(token, { ...tokens.get(token), ...fields });
}
for (const token of push.left ?? []) tokens.delete(token);
render(tokens);
},
);
for (const [token, fields] of Object.entries(tableToObjects(result))) tokens.set(token, fields);
render(tokens);

Prices and volumes change almost every block. Two params cut the updates down to the ones worth showing:

  • min_change sends a field only once it has moved by a threshold since the value last sent, in either direction. A change from or to 0 always counts:

    "min_change": { "price_usd": "0.1%", "liquidity_usd": "1%" }

    Thresholds are 0.01%, 0.1%, 0.25%, 0.5%, 1%, 2%, 5% or 10%. Everyone using the same threshold gets the same values, and where and sort go by them too, so a token near a bound doesn’t flicker in and out of the list. See Token fields for the fields that take one.

  • min_interval_ms sends at most one push per that much block time, with everything that changed since the one before: 200, 600, 1000, 5000, 15000 or 60000.

precision rounds string prices to that many significant digits (1 to 30), but keeps at least 3 decimals, so 12345.6789 at 6 is 12345.679 rather than 12346. Rounded prices also change less often.

For a long list where only some rows are visible, subscribe with ids and change them as the user scrolls with tokens.update:

{ "id": 2, "method": "tokens.update", "params": { "sub": 1, "add": ["meta-pool.near"], "remove": ["wrap.near"] } }

The reply has the added tokens as of its height, and pushes after it carry their changes.

Use icon_hash rather than icon in lists. icon is the whole image inline, in every snapshot. icon_hash is short, and the image is at https://api.intea.rs/icon/<icon_hash>.webp: a WebP of up to 128×128, cached forever since its URL changes whenever the icon does. The browser downloads each icon once, only for the tokens it shows.

"snapshot": false leaves rows in the reply empty, for a client that already has the tokens and only wants changes from now on.

Subscribe with the same params, and resume_from set to the height of the last reply or push you got. The reply then has only what changed since, shaped like a push:

{ "height": 219970000, "timestamp_millis": 1792003900000, "resumed": true, "set": { ... }, "created": [...], "left": [...] }

left can name tokens you never had: drop them if you do. When the server can’t work out what changed (it restarted since, or the subscription has a limit), the reply has "resumed": false and every row, like a fresh subscription.

Thresholds are kept for 10 minutes after their last subscriber leaves, so a client back within that only gets what crossed its threshold.