# StreamL2Book - Stream L2 Book Snapshots

`StreamL2Book` sends an aggregated full L2 book for one coin. It opens with the current book and then sends at most one full snapshot per coalesce window.

## Request and opening behavior

`coin` is required and case-sensitive. Omitted `n_levels` returns 20 levels per side, `1` through `100` selects a bounded depth, and explicit `0` requests full depth when the endpoint supports it. An unsupported full-depth request returns `FAILED_PRECONDITION`. `n_sig_figs` accepts `2` through `5`; `mantissa` is valid only with `n_sig_figs: 5` and must be `2` or `5`. `strict: true` suppresses frames when the aggregated levels are unchanged.

The opening snapshot is registered atomically with the subscription. `bids` are ordered best to worst, as are `asks`, and every frame supersedes the previous one.

## Request Parameters

- `coin` (`string, required`): One case-sensitive native coin identifier; validated against the enabled market universe.
- `n_levels` (`optional uint32, optional`): Omitted means 20 levels per side; 1-100 selects bounded depth; explicit 0 requests full depth when the endpoint supports it.
- `n_sig_figs` (`optional uint32, optional`): Optional price aggregation using 2, 3, 4, or 5 significant figures.
- `mantissa` (`optional uint64, optional`): Optional aggregation mantissa; valid only with n_sig_figs 5 and value 2 or 5.
- `strict` (`optional bool, optional`): When true, suppresses a frame whose aggregated bid and ask levels are unchanged.

## 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"
    "google.golang.org/protobuf/proto"
)

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).StreamL2Book(ctx, &pb.L2BookRequest{
        Coin:     "BTC",
        NLevels:  proto.Uint32(20),
        NSigFigs: proto.Uint32(5),
        Mantissa: proto.Uint64(5),
        Strict:   proto.Bool(true),
    })
    if err != nil {
        log.Fatal(err)
    }

    for {
        update, err := stream.Recv()
        if errors.Is(err, io.EOF) {
            return
        }
        if err != nil {
            log.Fatal(err)
        }
        log.Printf("coin=%s block=%d bids=%d asks=%d", update.GetCoin(), update.GetBlockNumber(), len(update.GetBids()), len(update.GetAsks()))
    }
}
```

## Response Fields

- `coin` (`string, required`): Native coin identifier for this book.
- `time` (`int64, required`): Frame block time in Unix milliseconds.
- `block_number` (`int64, required`): Sequential Hyperliquid block number at the end of the coalesce window.
- `bids` (`repeated L2Level, required`): Aggregated bid levels ordered best to worst.
- `bids.px` (`string, required`): Aggregated price as a decimal string.
- `bids.sz` (`string, required`): Total resting size at this price as a decimal string.
- `bids.n` (`uint32, required`): Number of resting orders aggregated into this level.
- `asks` (`repeated L2Level, required`): Aggregated ask levels ordered best to worst.
- `asks.px` (`string, required`): Aggregated price as a decimal string.
- `asks.sz` (`string, required`): Total resting size at this price as a decimal string.
- `asks.n` (`uint32, required`): Number of resting orders aggregated into this level.

## Successful Response

```protobuf
coin: "BTC"
time: 1785736814057
block_number: 1058646974
bids: { px: "114250.0" sz: "1.35" n: 4 }
asks: { px: "114251.0" sz: "0.82" n: 3 }
```

## Error Responses

### Invalid request

- Code: `INVALID_ARGUMENT`
- Description: The coin or an aggregation parameter is malformed, unsupported, or outside its allowed range.

### Endpoint capability unavailable

- Code: `FAILED_PRECONDITION`
- Description: This stream is unavailable on the endpoint, or the endpoint does not support a full-depth request.

### View unavailable

- Code: `UNAVAILABLE`
- Description: The requested order-book view is not ready.

### Stream state reset

- Code: `ABORTED`
- Description: The stream state reset; reconnect for a fresh 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.

## Delivery and recovery

`block_number` can skip because the stream coalesces completed windows. Subscriber lag drops obsolete snapshots in-band; the next frame restores current state. `ABORTED` means the stream state reset, while `DEADLINE_EXCEEDED` means the consumer was too slow. See the [shared market-stream contract](https://www.dwellir.com/docs/hyperliquid/grpc#v3-market-streaming-service) for coin namespaces, common errors, and the live-only subscription model.

Related references: [StreamL2BookDiff](https://www.dwellir.com/docs/hyperliquid/stream_l2_book_diff), [StreamBbo](https://www.dwellir.com/docs/hyperliquid/stream_bbo), and the [Order Book WebSocket L2 book](https://www.dwellir.com/docs/hyperliquid/l2-book).
