> For the complete documentation index, see [llms.txt](https://docs.hydromancer.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hydromancer.xyz/readme/websocket/trades-data/trades.md).

# trades

HL-compatible real-time trade stream, with optional firehose add-on.

Every live data message includes both routing identifiers with the same value:

```json
{"type": "trades", "channel": "trades"}
```

Other payload fields are omitted above. Existing clients may continue routing on either field.

HL-compatible per-coin trade stream. Each message contains a batch of `WsTrade` objects synthesized from block fills — one per matched (buyer, seller) pair.

Per-coin `trades` is included in every tier (20/100/200 coins for starter/growth/scale; higher bespoke limits are available — contact us). Streaming **all** coins with no `coins` filter is an add-on and requires the `ws:allTrades` permission.

### Subscribe

```json
{
    "type": "subscribe",
    "subscription": {
        "type": "trades",
        "coin": "BTC",                  // optional, single-coin (HL-native shape)
        "coins": ["xyz:SP500", "BTC"]   // optional, multi-coin; omit both for the all-coins firehose
    }
}
```

| Parameter | Type      | Description                                                                                                                                                                            |
| --------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `coin`    | string    | Single-coin filter (HL's native wire shape, accepted for compatibility). Full coin string including any prefix, e.g. `"BTC"`, `"xyz:SP500"`, `"#90"`, `"@107"` / `"PURR/USDC"` (spot). |
| `coins`   | string\[] | Multi-coin filter (case-sensitive, up to the tier's coin cap: 20/100/200). Same coin format as `coin`.                                                                                 |

`coin` and `coins` may be combined — their union is the effective coin set. Omitting both makes this the all-coins firehose, which requires the `ws:allTrades` add-on permission.

{% hint style="info" %}
This is wire-compatible with Hyperliquid: HL clients sending the single `coin: "BTC"` field work as-is. The `coins` array is a Hyna extension for subscribing to several coins in one subscription.
{% endhint %}

{% hint style="info" %}
**Coins are additive — grow or shrink the set without gaps.** A connection holds a single trades subscription, and re-subscribing **adds** the new `coins` to it (union), like subscribing to additional `bbo` coins — no error, no unsubscribe/resubscribe cycle, and no gap in delivery for coins you already hold. To drop coins, send an `unsubscribe` naming just those coins: they are removed from the set and the rest keep streaming uninterrupted. Re-sending an identical subscription is a no-op. Switching between a per-coin filter and the all-coins firehose still requires unsubscribing first (the two forms cannot coexist on one connection, since all `trades` frames share one channel with no per-subscription identity). Separate connections each get their own trades subscription. The coin cap (20/100/200) is counted cumulatively across the API key.
{% endhint %}

### Unsubscribe

Name coins to remove just those from the active set (remaining coins keep streaming, gap-free):

```json
{
    "type": "unsubscribe",
    "subscription": {
        "type": "trades",
        "coins": ["BTC"]
    }
}
```

Omit `coins` to clear the connection's trades subscription entirely:

```json
{
    "type": "unsubscribe",
    "subscription": {
        "type": "trades"
    }
}
```

### Trade data format

The response matches HL's `WsTrade` shape exactly. Trades are batched per block, ordered by the underlying fill order — ascending `(time, tx_index)`, so the earliest fill in the block appears first.

```json
{
  "type": "trades",
  "channel": "trades",
  "seq": 1,
  "cursor": "500:1704067200000:0",
  "trades": [
    {
      "coin": "BTC",
      "side": "B",
      "px": "62541.0",
      "sz": "0.0006",
      "hash": "0xeca4564b44c8a8e5ee1e043e5cac07020fa10030dfcbc7b7906d019e03cc82d0",
      "time": 1782203279565,
      "tid": 414334974001319,
      "users": [
        "0xf83fc34248744a304872e1ed40b9b10f54a64f6f",
        "0x2ca4927174ba283d8a57f60ef3589844035a2930"
      ]
    }
  ]
}
```

**Field semantics:**

| Field   | Description                                                                                                                                         |
| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `coin`  | Coin identifier (matches `coins` filter format).                                                                                                    |
| `side`  | The **taker's** side. `"B"` = taker bought, `"A"` = taker sold.                                                                                     |
| `px`    | Trade price.                                                                                                                                        |
| `sz`    | Trade size.                                                                                                                                         |
| `hash`  | Fill hash.                                                                                                                                          |
| `time`  | Trade timestamp (ms).                                                                                                                               |
| `tid`   | Trade id. Unique within `(coin, time)`; for a globally unique id use `(block_time, coin, tid)`.                                                     |
| `users` | `[buyer, seller]` — matches Hyperliquid. The taker is `users[0]` when `side="B"` and `users[1]` when `side="A"` (use `side` to identify the taker). |

#### Common errors

```
Subscription allTrades requires permission: ws:allTrades
```

(Only the no-filter firehose form requires the add-on; per-coin `trades` is included in every tier.)

### Examples

{% tabs %}
{% tab title="Javascript" %}

```javascript
const WebSocket = require('ws');

const ws = new WebSocket(`wss://api.hydromancer.xyz/ws?token=${process.env.HYDROMANCER_API_KEY}`);

ws.on('message', (data) => {
    const msg = JSON.parse(data);

    if (msg.type === 'connected') {
        ws.send(JSON.stringify({
            type: 'subscribe',
            subscription: { type: 'trades', coins: ['BTC', 'ETH'] }
        }));
    } else if (msg.type === 'ping') {
        ws.send(JSON.stringify({ type: 'pong' }));
    } else if (msg.type === 'trades') {
        for (const t of msg.trades) {
            const [buyer, seller] = t.users;
            console.log(`${t.coin} ${t.side} ${t.sz}@${t.px}  buyer=${buyer} seller=${seller}`);
        }
    }
});
```

{% endtab %}

{% tab title="Python" %}

```python
import websocket, json, os

def on_message(ws, message):
    msg = json.loads(message)
    if msg['type'] == 'connected':
        ws.send(json.dumps({
            "type": "subscribe",
            "subscription": {"type": "trades", "coins": ["BTC", "ETH"]}
        }))
    elif msg['type'] == 'ping':
        ws.send(json.dumps({'type': 'pong'}))
    elif msg['type'] == 'trades':
        for t in msg['trades']:
            buyer, seller = t['users']
            print(f"{t['coin']} {t['side']} {t['sz']}@{t['px']}  buyer={buyer} seller={seller}")

ws = websocket.WebSocketApp(
    f"wss://api.hydromancer.xyz/ws?token={os.environ.get('HYDROMANCER_API_KEY')}",
    on_message=on_message
)
ws.run_forever()
```

{% endtab %}
{% endtabs %}
