# StreamTpslUpdates - Stream TP/SL Order Updates

`StreamTpslUpdates` exposes the lifecycle of open take-profit and stop-loss trigger orders, including position-level TP/SL effective sizes.

## Request and opening behavior

An empty `coins` list means all perp coins, including enabled HIP-3 perps. Explicit coins are deduplicated and validated against the perp universe; blank and wildcard entries are invalid. Optional `users` filters owners case-insensitively.

The first frame has `snapshot: true`, `sequence: 1`, and matching `ADD` diffs. Later `ADD`, `REMOVE`, and `UPDATE` diffs preserve the same sequence even when filtering leaves `diffs` empty. `resolved_sz` is unset only until a position-level TP/SL size can first be resolved.

## Request Parameters

- `coins` (`repeated string, optional`): Optional exact perp-coin scope; empty means all perps, while blank and wildcard entries are invalid.
- `users` (`repeated string, optional`): Optional 0x-prefixed owner addresses matched 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).StreamTpslUpdates(ctx, &pb.TpslUpdatesRequest{
        Coins: []string{"BTC", "ETH"},
        Users: []string{"0x1111111111111111111111111111111111111111"},
    })
    if err != nil {
        log.Fatal(err)
    }
    var last uint64
    for {
        update, err := stream.Recv()
        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 TpslOrderDiff, required`): Trigger-order lifecycle changes, or matching ADD entries on the snapshot.
- `diffs.diff_type` (`TpslDiffType, required`): ADD, REMOVE, or UPDATE operation.
- `diffs.oid` (`uint64, required`): Trigger-order identifier.
- `diffs.coin` (`string, required`): Perp coin identifier.
- `diffs.user` (`string, required`): Order owner address.
- `diffs.side` (`string, required`): Native side B or A.
- `diffs.trigger_px` (`string, required`): Trigger price as a decimal string.
- `diffs.limit_px` (`string, required`): Limit price as a decimal string.
- `diffs.sz` (`string, required`): Configured order size as a decimal string.
- `diffs.trigger_condition` (`string, required`): Native trigger condition.
- `diffs.order_type` (`string, required`): Native trigger-order type.
- `diffs.is_position_tpsl` (`bool, required`): Whether the trigger is position-level TP/SL.
- `diffs.reduce_only` (`bool, required`): Whether the trigger order is reduce-only.
- `diffs.timestamp` (`int64, required`): Order placement time in Unix milliseconds.
- `diffs.reason` (`optional string, optional`): Native removal reason, set on REMOVE.
- `diffs.resolved_sz` (`optional string, optional`): Live effective size; unset until a position-level trigger can first be resolved.
- `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: TPSL_DIFF_TYPE_ADD
  oid: 123456789
  coin: "BTC"
  user: "0x1111111111111111111111111111111111111111"
  side: "A"
  trigger_px: "110000.0"
  limit_px: "109900.0"
  sz: "0.0"
  trigger_condition: "Price below 110000"
  order_type: "Stop Market"
  is_position_tpsl: true
  reduce_only: true
  timestamp: 1785736813000
  resolved_sz: "0.25"
}
snapshot: true
sequence: 1
```

## Error Responses

### Invalid filter

- Code: `INVALID_ARGUMENT`
- Description: A coin or user is malformed, or an explicitly listed perp is unavailable.

### Feature unavailable

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

### View unavailable

- Code: `UNAVAILABLE`
- Description: The requested TP/SL 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 contiguous `sequence` values. Frame loss or a stream state reset returns `ABORTED`; reconnect and rebuild from the new snapshot. Quiet blocks produce no frame, and an idle open stream remains healthy. See the [shared market-stream contract](https://www.dwellir.com/docs/hyperliquid/grpc#v3-market-streaming-service) for common errors.

Related references: [StreamL4OrderUpdates](https://www.dwellir.com/docs/hyperliquid/stream_l4_order_updates) and [StreamTwapStatuses](https://www.dwellir.com/docs/hyperliquid/stream_twap_statuses).
