# StreamL4OrderUpdates - Stream Typed Order Diffs

`StreamL4OrderUpdates` sends typed `NEW`, `UPDATE`, and `REMOVE` changes for the resting L4 book. Apply the diffs directly; this projection has already resolved matching effects.

## Request and opening behavior

An empty `coins` list scopes the subscription to all coins. Explicit coins are deduplicated and validated; blank or `"*"` entries are invalid. Optional `users` filters order owners case-insensitively and combines with `coins` using AND semantics.

The first frame has `snapshot: true`, `sequence: 1`, and `NEW` diffs for every matching resting order. Later frames are block-ordered. A fully filtered frame is still sent with empty `diffs` so the sequence stays contiguous. This stream does not synthesize HIP-4 mirror orders.

## Request Parameters

- `coins` (`repeated string, optional`): Optional exact coin scope; empty means all coins, while blank and wildcard entries are invalid.
- `users` (`repeated string, optional`): Optional 0x-prefixed order-owner addresses matched case-insensitively.

## Request Example

```go
package main

import (
    "context"
    "crypto/tls"
    "errors"
    "io"
    "log"
    "os"
    "os/signal"
    "syscall"

    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 := os.Getenv("HYPERLIQUID_GRPC_ENDPOINT")
    apiKey := 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, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()
    ctx = metadata.AppendToOutgoingContext(ctx, "x-api-key", apiKey)
    stream, err := pb.NewMarketStreamingClient(conn).StreamL4OrderUpdates(ctx, &pb.L4OrderUpdatesRequest{
        Coins: []string{"BTC", "ETH"},
        Users: []string{"0x1111111111111111111111111111111111111111"},
    })
    if err != nil {
        log.Fatal(err)
    }

    var last uint64
    for {
        update, err := stream.Recv()
        if errors.Is(err, io.EOF) {
            return
        }
        if err != nil {
            log.Fatal(err)
        }
        if update.GetSequence() != last+1 {
            log.Fatalf("sequence gap: got=%d expected=%d", update.GetSequence(), last+1)
        }
        last = update.GetSequence()
        log.Printf("block=%d snapshot=%t sequence=%d diffs=%d", update.GetBlockNumber(), update.GetSnapshot(), update.GetSequence(), len(update.GetDiffs()))
    }
}
```

## Response Fields

- `time` (`int64, required`): Frame block time in Unix milliseconds.
- `block_number` (`int64, required`): Sequential Hyperliquid block number.
- `diffs` (`repeated L4OrderDiff, required`): Resting-order changes, or all matching resting orders as NEW entries on the snapshot.
- `diffs.diff_type` (`L4OrderDiffType, required`): NEW, UPDATE, or REMOVE operation.
- `diffs.coin` (`string, required`): Native coin identifier.
- `diffs.oid` (`uint64, required`): Resting order identifier.
- `diffs.user` (`string, required`): Order owner address.
- `diffs.side` (`string, required`): Native side B or A.
- `diffs.px` (`string, required`): Resting price as a decimal string.
- `diffs.sz` (`string, required`): Resting size after the change, or 0.0 on REMOVE.
- `snapshot` (`bool, required`): True on the opening rebuild frame.
- `sequence` (`uint64, required`): Per-subscription monotonic counter beginning at 1 on the snapshot.

## Successful Response

```protobuf
time: 1785736814057
block_number: 1058646974
diffs: {
  diff_type: L4_ORDER_DIFF_TYPE_NEW
  coin: "BTC"
  oid: 123456789
  user: "0x1111111111111111111111111111111111111111"
  side: "B"
  px: "114250.0"
  sz: "0.25"
}
snapshot: true
sequence: 1
```

## Error Responses

### Invalid request

- Code: `INVALID_ARGUMENT`
- Description: A coin or user filter 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: The requested order view is not ready.

### Lost incremental frames

- Code: `ABORTED`
- Description: Frame loss or a stream state reset requires reconnecting and rebuilding from a new snapshot.

### 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.

## Sequencing and recovery

Require each `sequence` to follow the last accepted value. A gap, `ABORTED`, or a stream state reset requires a reconnect and a full rebuild from the new snapshot. Treat an `UPDATE` or `REMOVE` for an unknown `oid` as a no-op. See the [shared market-stream contract](https://www.dwellir.com/docs/hyperliquid/grpc#v3-market-streaming-service) for common errors.

Related references: [StreamL4Book](https://www.dwellir.com/docs/hyperliquid/stream_l4_book) and [StreamTpslUpdates](https://www.dwellir.com/docs/hyperliquid/stream_tpsl_updates).
