# StreamTrades - Stream Normalized Trades

`StreamTrades` sends normalized executions in one batch per `(block, coin)`. Within one block, identify a match by `(coin, tid)`. Across blocks, include the batch's `block_number` in the match key.

A trade can contain multiple makers. Preserve every address in `bid_users` and `ask_users`.

## Request and live-only behavior

`coins` is required and accepts `1` through `20` distinct, validated identifiers. Optional `users` keeps a trade when any counterparty matches case-insensitively. The coin and user filters combine using AND semantics.

The stream starts with executions observed after subscription. It has no opening snapshot or historical backfill. Per-coin order is preserved; ordering between coins is unspecified. A batch emptied by the user filter is dropped.

## Request Parameters

- `coins` (`repeated string, required`): One to 20 distinct, validated native coin identifiers; duplicates are ignored before applying the cap.
- `users` (`repeated string, optional`): Optional counterparties; a trade matches when any bid-side or ask-side address matches case-insensitively.

## Request Example

```go
package main

import (
    "context"
    "crypto/tls"
    "log"
    "os"

    pb "example.com/hyperliquid-v3-client/gen/v3"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/metadata"
)

func main() {
    endpoint, apiKey := os.Getenv("HYPERLIQUID_GRPC_ENDPOINT"), os.Getenv("DWELLIR_API_KEY")
    if endpoint == "" || apiKey == "" {
        log.Fatal("set HYPERLIQUID_GRPC_ENDPOINT and DWELLIR_API_KEY")
    }
    conn, err := grpc.NewClient(endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS12})))
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()
    ctx := metadata.AppendToOutgoingContext(context.Background(), "x-api-key", apiKey)
    stream, err := pb.NewMarketStreamingClient(conn).StreamTrades(ctx, &pb.TradesRequest{
        Coins: []string{"BTC", "ETH"},
        Users: []string{"0x1111111111111111111111111111111111111111"},
    })
    if err != nil {
        log.Fatal(err)
    }
    for {
        update, err := stream.Recv()
        if err != nil {
            log.Fatal(err)
        }
        for _, trade := range update.GetTrades() {
            log.Printf("block=%d coin=%s tid=%d side=%s px=%s sz=%s bid_users=%v ask_users=%v", update.GetBlockNumber(), trade.GetCoin(), trade.GetTid(), trade.GetSide(), trade.GetPx(), trade.GetSz(), trade.GetBidUsers(), trade.GetAskUsers())
        }
    }
}
```

## Response Fields

- `time` (`int64, required`): Batch block time in Unix milliseconds.
- `block_number` (`int64, required`): Sequential Hyperliquid block number.
- `trades` (`repeated Trade, required`): Normalized trades for one coin in this block.
- `trades.coin` (`string, required`): Native coin identifier.
- `trades.side` (`string, required`): Taker side B or A.
- `trades.px` (`string, required`): Execution price as a decimal string.
- `trades.sz` (`string, required`): Executed size as a decimal string.
- `trades.hash` (`string, required`): Transaction hash.
- `trades.time` (`int64, required`): Execution time in Unix milliseconds.
- `trades.tid` (`uint64, required`): Within one block, use (coin, tid). Across blocks, include the batch's block_number.
- `trades.bid_users` (`repeated string, required`): Every bid-side counterparty; a multi-maker match can contain several addresses.
- `trades.ask_users` (`repeated string, required`): Every ask-side counterparty; a multi-maker match can contain several addresses.
- `trades.synthetic` (`bool, required`): True only for a synthesized HIP-4 sibling trade.

## Successful Response

```protobuf
time: 1785736814057
block_number: 1058646974
trades: {
  coin: "BTC"
  side: "B"
  px: "114250.0"
  sz: "0.75"
  hash: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
  time: 1785736814057
  tid: 99887766
  bid_users: "0x1111111111111111111111111111111111111111"
  ask_users: "0x2222222222222222222222222222222222222222"
  ask_users: "0x3333333333333333333333333333333333333333"
  synthetic: false
}
```

## Error Responses

### Invalid filter

- Code: `INVALID_ARGUMENT`
- Description: The coin list or a user address is malformed, or an explicitly listed coin is unavailable.

### Feature unavailable

- Code: `FAILED_PRECONDITION`
- Description: This stream is unavailable on the selected endpoint.

### View unavailable

- Code: `UNAVAILABLE`
- Description: Live trades are not currently available.

### Lost live trades

- Code: `ABORTED`
- Description: Frame loss or a stream state reset ended this live-only stream.

### Slow consumer

- Code: `DEADLINE_EXCEEDED`
- Description: The consumer was too slow; reconnect.

### Unauthenticated

- Code: `UNAUTHENTICATED`
- Description: Usable x-api-key metadata was not supplied.

### Permission denied

- Code: `PERMISSION_DENIED`
- Description: The supplied credential cannot call this service.

## Delivery and recovery

Trades do not carry a sequence. Frame loss or a stream state reset closes the stream with `ABORTED`; reconnecting resumes live delivery but does not recover missed executions. Use the [raw fills stream](https://www.dwellir.com/docs/hyperliquid/stream_fills), [historical data](https://www.dwellir.com/docs/hyperliquid/historical-data), or an indexed product when durable recovery is required. Shared errors are documented on the [gRPC overview](https://www.dwellir.com/docs/hyperliquid/grpc#v3-market-streaming-service).

Related reference: [StreamAllFills](https://www.dwellir.com/docs/hyperliquid/stream_all_fills).
