# Live token lists

> Follow tokens with tokens.subscribe, with thresholds, intervals, top lists and resuming.

This page is part of the Intear API docs. The Intear API is at `https://api.intea.rs` and `wss://api.intea.rs/ws`, with no API key. https://docs.intear.tech/api/llms-full.txt has all of its pages in one file, and https://docs.intear.tech/llms.txt lists every page of the Intear docs.

[`tokens.subscribe`](https://docs.intear.tech/api/reference/tokens.md#tokenssubscribe) 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.

## Which tokens

Pick tokens in one of these ways:

- **By ID:** `"ids": ["wrap.near", "usdt.tether-token.near"]`. Use [`tokens.update`](#following-the-tokens-on-screen) to change the list later.
- **By filter:** `where` narrows tokens down by reputation, parent account, creation time, or bounds on numeric fields:

  ```json
  "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`:

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

With neither `ids` nor `where`, it's every token.

## Reading pushes

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

```json
{
  "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](https://docs.intear.tech/api/quickstart.md#over-the-websocket):

```js
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);
```

The whole file, tested in CI: https://docs.intear.tech/snippets/js/src/intear-api.js

## Fewer updates

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:

  ```json
  "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](https://docs.intear.tech/api/reference/token-fields.md) 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`. It only shortens values: a field is still pushed whenever its unrounded value changes, so use `min_change` for fewer pushes.

## Following the tokens on screen

For a long list where only some rows are visible, subscribe with `ids` and change them as the user scrolls with [`tokens.update`](https://docs.intear.tech/api/reference/tokens.md#tokensupdate):

```json
{ "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.

## Icons

Use `icon_hash` rather than `icon` if possible. `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 includes hash that changes whenever the icon does. The browser downloads each icon once, only for the tokens it shows.

## Only what changes

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

## After a reconnect

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:

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

`left` can name tokens you never had: drop them if you do, ignore if you don't. 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.
