Field definitions, proto messages, example updates, and comparisons for this stream live on the L4 Book Updates dataset page. This page covers the method signature and client snippets.
Please note that this method is metered based on data consumption at 0.0165 MB = 10 API credits.
Parameters
- coinsrepeated string
List of symbols to subscribe to (e.g., 'BTC', 'ETH'). Empty means all coins
Returns
- streamstream<L4BookUpdatesUpdate>
Stream of L4BookUpdatesUpdate messages containing typed per-order diffs per block
- timeuint64
Block timestamp in milliseconds
- heightuint64
Block height
- snapshotbool
True when the diffs carry a full reset snapshot of all resting orders
- diffsarray<L4OrderDiff>
Per-order diffs, each containing diff_type (L4_ORDER_DIFF_TYPE_NEW, L4_ORDER_DIFF_TYPE_UPDATE, or L4_ORDER_DIFF_TYPE_REMOVE), coin, oid (order ID), user (address), side ('A' for ask, 'B' for bid), px (price), and sz (new/current size for new and update diffs; omitted on remove)
1// StreamL4BookUpdates Example - Stream typed per-order book updates via gRPC2package main34import (5"context"6"flag"7"fmt"8"io"9"log"10"math"11"strings"12"time"1314"google.golang.org/grpc"15"google.golang.org/grpc/codes"16"google.golang.org/grpc/credentials"17"google.golang.org/grpc/metadata"18"google.golang.org/grpc/status"1920pb "hyperliquid-orderbook-example/proto"21)2223const (24grpcEndpoint = "your-endpoint.hype-mainnet.quiknode.pro:10000"25authToken = "your-auth-token"26maxRetries = 1027baseDelay = 2 * time.Second28)2930type localOrder struct {31coin string32user string33side string34px string35sz string36}3738func streamL4BookUpdates(coins []string) error {39fmt.Println(strings.Repeat("=", 60))40fmt.Printf("Streaming L4 Book Updates for %s\n", strings.Join(coins, ", "))41fmt.Println("Auto-reconnect: true")42fmt.Println(strings.Repeat("=", 60) + "\n")4344retryCount := 04546// Simple local order map keyed by oid47orders := make(map[uint64]localOrder)4849for retryCount < maxRetries {50creds := credentials.NewClientTLSFromCert(nil, "")51conn, err := grpc.Dial(grpcEndpoint,52grpc.WithTransportCredentials(creds),53grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024)))54if err != nil {55return fmt.Errorf("failed to connect: %w", err)56}5758client := pb.NewOrderBookStreamingClient(conn)59ctx := metadata.AppendToOutgoingContext(context.Background(), "x-token", authToken)6061request := &pb.L4BookUpdatesRequest{62Coins: coins,63}6465if retryCount > 0 {66fmt.Printf("\n🔄 Reconnecting (attempt %d/%d)...\n", retryCount+1, maxRetries)67} else {68fmt.Printf("Connecting to %s...\n", grpcEndpoint)69}7071stream, err := client.StreamL4BookUpdates(ctx, request)72if err != nil {73conn.Close()74return fmt.Errorf("failed to start stream: %w", err)75}7677msgCount := 078shouldRetry := false7980for {81update, err := stream.Recv()82if err == io.EOF {83break84}85if err != nil {86st, ok := status.FromError(err)87if ok && st.Code() == codes.DataLoss {88fmt.Printf("\n⚠️ Server reinitialized: %s\n", st.Message())89retryCount++90if retryCount < maxRetries {91delay := baseDelay * time.Duration(math.Pow(2, float64(retryCount-1)))92fmt.Printf("⏳ Waiting %v before reconnecting...\n", delay)93time.Sleep(delay)94shouldRetry = true95break96} else {97fmt.Printf("\n❌ Max retries (%d) reached. Giving up.\n", maxRetries)98conn.Close()99return nil100}101}102conn.Close()103return fmt.Errorf("stream error: %w", err)104}105106msgCount++107if msgCount == 1 {108fmt.Println("✓ First L4 update received!\n")109retryCount = 0 // Reset on success110}111112if update.Snapshot {113// Full reset snapshot: rebuild local state114orders = make(map[uint64]localOrder)115}116117// Display update118fmt.Println("\n" + strings.Repeat("─", 60))119snapshotLabel := ""120if update.Snapshot {121snapshotLabel = " | SNAPSHOT"122}123fmt.Printf("Block: %d | Time: %d%s | Diffs: %d\n", update.Height, update.Time, snapshotLabel, len(update.Diffs))124fmt.Println(strings.Repeat("─", 60))125126for _, diff := range update.Diffs {127side := "ASK"128if diff.Side == "B" {129side = "BID"130}131132switch diff.DiffType {133case pb.L4OrderDiffType_L4_ORDER_DIFF_TYPE_NEW:134orders[diff.Oid] = localOrder{coin: diff.Coin, user: diff.User, side: diff.Side, px: diff.Px, sz: diff.Sz}135if !update.Snapshot {136fmt.Printf(" NEW %s oid: %d | %s %s x %s | %s\n", diff.Coin, diff.Oid, side, diff.Px, diff.Sz, diff.User)137}138case pb.L4OrderDiffType_L4_ORDER_DIFF_TYPE_UPDATE:139if existing, ok := orders[diff.Oid]; ok {140existing.sz = diff.Sz141orders[diff.Oid] = existing142}143fmt.Printf(" UPDATE %s oid: %d | %s %s x %s | %s\n", diff.Coin, diff.Oid, side, diff.Px, diff.Sz, diff.User)144case pb.L4OrderDiffType_L4_ORDER_DIFF_TYPE_REMOVE:145delete(orders, diff.Oid)146fmt.Printf(" REMOVE %s oid: %d | %s %s | %s\n", diff.Coin, diff.Oid, side, diff.Px, diff.User)147}148}149150fmt.Printf("\n Resting orders tracked: %d | Messages received: %d\n", len(orders), msgCount)151}152153conn.Close()154155if !shouldRetry {156break157}158}159160return nil161}162163func main() {164coinsFlag := flag.String("coins", "BTC", "Comma-separated coin symbols to stream (e.g., BTC,ETH)")165166flag.Parse()167168coins := strings.Split(*coinsFlag, ",")169170fmt.Println("\n" + strings.Repeat("=", 60))171fmt.Println("Hyperliquid StreamL4BookUpdates Example")172fmt.Printf("Endpoint: %s\n", grpcEndpoint)173fmt.Println(strings.Repeat("=", 60))174175if err := streamL4BookUpdates(coins); err != nil {176log.Fatal(err)177}178}179
1// StreamL4BookUpdates Example - Stream typed per-order book updates via gRPC2package main34import (5"context"6"flag"7"fmt"8"io"9"log"10"math"11"strings"12"time"1314"google.golang.org/grpc"15"google.golang.org/grpc/codes"16"google.golang.org/grpc/credentials"17"google.golang.org/grpc/metadata"18"google.golang.org/grpc/status"1920pb "hyperliquid-orderbook-example/proto"21)2223const (24grpcEndpoint = "your-endpoint.hype-mainnet.quiknode.pro:10000"25authToken = "your-auth-token"26maxRetries = 1027baseDelay = 2 * time.Second28)2930type localOrder struct {31coin string32user string33side string34px string35sz string36}3738func streamL4BookUpdates(coins []string) error {39fmt.Println(strings.Repeat("=", 60))40fmt.Printf("Streaming L4 Book Updates for %s\n", strings.Join(coins, ", "))41fmt.Println("Auto-reconnect: true")42fmt.Println(strings.Repeat("=", 60) + "\n")4344retryCount := 04546// Simple local order map keyed by oid47orders := make(map[uint64]localOrder)4849for retryCount < maxRetries {50creds := credentials.NewClientTLSFromCert(nil, "")51conn, err := grpc.Dial(grpcEndpoint,52grpc.WithTransportCredentials(creds),53grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024)))54if err != nil {55return fmt.Errorf("failed to connect: %w", err)56}5758client := pb.NewOrderBookStreamingClient(conn)59ctx := metadata.AppendToOutgoingContext(context.Background(), "x-token", authToken)6061request := &pb.L4BookUpdatesRequest{62Coins: coins,63}6465if retryCount > 0 {66fmt.Printf("\n🔄 Reconnecting (attempt %d/%d)...\n", retryCount+1, maxRetries)67} else {68fmt.Printf("Connecting to %s...\n", grpcEndpoint)69}7071stream, err := client.StreamL4BookUpdates(ctx, request)72if err != nil {73conn.Close()74return fmt.Errorf("failed to start stream: %w", err)75}7677msgCount := 078shouldRetry := false7980for {81update, err := stream.Recv()82if err == io.EOF {83break84}85if err != nil {86st, ok := status.FromError(err)87if ok && st.Code() == codes.DataLoss {88fmt.Printf("\n⚠️ Server reinitialized: %s\n", st.Message())89retryCount++90if retryCount < maxRetries {91delay := baseDelay * time.Duration(math.Pow(2, float64(retryCount-1)))92fmt.Printf("⏳ Waiting %v before reconnecting...\n", delay)93time.Sleep(delay)94shouldRetry = true95break96} else {97fmt.Printf("\n❌ Max retries (%d) reached. Giving up.\n", maxRetries)98conn.Close()99return nil100}101}102conn.Close()103return fmt.Errorf("stream error: %w", err)104}105106msgCount++107if msgCount == 1 {108fmt.Println("✓ First L4 update received!\n")109retryCount = 0 // Reset on success110}111112if update.Snapshot {113// Full reset snapshot: rebuild local state114orders = make(map[uint64]localOrder)115}116117// Display update118fmt.Println("\n" + strings.Repeat("─", 60))119snapshotLabel := ""120if update.Snapshot {121snapshotLabel = " | SNAPSHOT"122}123fmt.Printf("Block: %d | Time: %d%s | Diffs: %d\n", update.Height, update.Time, snapshotLabel, len(update.Diffs))124fmt.Println(strings.Repeat("─", 60))125126for _, diff := range update.Diffs {127side := "ASK"128if diff.Side == "B" {129side = "BID"130}131132switch diff.DiffType {133case pb.L4OrderDiffType_L4_ORDER_DIFF_TYPE_NEW:134orders[diff.Oid] = localOrder{coin: diff.Coin, user: diff.User, side: diff.Side, px: diff.Px, sz: diff.Sz}135if !update.Snapshot {136fmt.Printf(" NEW %s oid: %d | %s %s x %s | %s\n", diff.Coin, diff.Oid, side, diff.Px, diff.Sz, diff.User)137}138case pb.L4OrderDiffType_L4_ORDER_DIFF_TYPE_UPDATE:139if existing, ok := orders[diff.Oid]; ok {140existing.sz = diff.Sz141orders[diff.Oid] = existing142}143fmt.Printf(" UPDATE %s oid: %d | %s %s x %s | %s\n", diff.Coin, diff.Oid, side, diff.Px, diff.Sz, diff.User)144case pb.L4OrderDiffType_L4_ORDER_DIFF_TYPE_REMOVE:145delete(orders, diff.Oid)146fmt.Printf(" REMOVE %s oid: %d | %s %s | %s\n", diff.Coin, diff.Oid, side, diff.Px, diff.User)147}148}149150fmt.Printf("\n Resting orders tracked: %d | Messages received: %d\n", len(orders), msgCount)151}152153conn.Close()154155if !shouldRetry {156break157}158}159160return nil161}162163func main() {164coinsFlag := flag.String("coins", "BTC", "Comma-separated coin symbols to stream (e.g., BTC,ETH)")165166flag.Parse()167168coins := strings.Split(*coinsFlag, ",")169170fmt.Println("\n" + strings.Repeat("=", 60))171fmt.Println("Hyperliquid StreamL4BookUpdates Example")172fmt.Printf("Endpoint: %s\n", grpcEndpoint)173fmt.Println(strings.Repeat("=", 60))174175if err := streamL4BookUpdates(coins); err != nil {176log.Fatal(err)177}178}179
1// StreamL4BookUpdates Example - Stream typed per-order book updates via gRPC2const grpc = require('@grpc/grpc-js');3const protoLoader = require('@grpc/proto-loader');4const path = require('path');56const GRPC_ENDPOINT = 'your-endpoint.hype-mainnet.quiknode.pro:10000';7const AUTH_TOKEN = 'your-auth-token';8const PROTO_PATH = path.join(__dirname, 'proto', 'orderbook.proto');910const packageDefinition = protoLoader.loadSync(PROTO_PATH, {11keepCase: true,12longs: String,13enums: String,14defaults: true,15oneofs: true16});17const proto = grpc.loadPackageDefinition(packageDefinition).hyperliquid;1819function createClient() {20return new proto.OrderBookStreaming(21GRPC_ENDPOINT,22grpc.credentials.createSsl(),23{ 'grpc.max_receive_message_length': 100 * 1024 * 1024 }24);25}2627// Stream typed per-order L4 book updates28async function streamL4BookUpdates(coins, autoReconnect = true, retryCount = 0) {29console.log('='.repeat(60));30console.log(`Streaming L4 Book Updates for ${coins.length > 0 ? coins.join(', ') : 'all coins'}`);31console.log(`Auto-reconnect: ${autoReconnect}`);32console.log('='.repeat(60) + '\n');3334const maxRetries = 10;35const baseDelay = 2000;3637// Simple local order map keyed by oid38const orders = new Map();3940while (retryCount < maxRetries) {41const client = createClient();42const metadata = new grpc.Metadata();43metadata.add('x-token', AUTH_TOKEN);4445const request = {46coins: coins47};4849try {50if (retryCount > 0) {51console.log(`\n🔄 Reconnecting (attempt ${retryCount + 1}/${maxRetries})...`);52} else {53console.log(`Connecting to ${GRPC_ENDPOINT}...`);54}5556let msgCount = 0;57const call = client.StreamL4BookUpdates(request, metadata);5859call.on('data', (update) => {60msgCount++;6162if (msgCount === 1) {63console.log('✓ First L4 update received!\n');64retryCount = 0; // Reset on success65}6667if (update.snapshot) {68// Full reset snapshot: rebuild local state69orders.clear();70}7172console.log('\n' + '─'.repeat(60));73console.log(`Block: ${update.height} | Time: ${update.time}${update.snapshot ? ' | SNAPSHOT' : ''} | Diffs: ${(update.diffs || []).length}`);74console.log('─'.repeat(60));7576(update.diffs || []).forEach(diff => {77switch (diff.diff_type) {78case 'L4_ORDER_DIFF_TYPE_NEW':79orders.set(diff.oid, { coin: diff.coin, user: diff.user, side: diff.side, px: diff.px, sz: diff.sz });80if (!update.snapshot) {81console.log(` NEW ${diff.coin} oid: ${diff.oid} | ${diff.side === 'B' ? 'BID' : 'ASK'} ${diff.px} x ${diff.sz} | ${diff.user}`);82}83break;84case 'L4_ORDER_DIFF_TYPE_UPDATE':85const existing = orders.get(diff.oid);86if (existing) {87existing.sz = diff.sz;88}89console.log(` UPDATE ${diff.coin} oid: ${diff.oid} | ${diff.side === 'B' ? 'BID' : 'ASK'} ${diff.px} x ${diff.sz} | ${diff.user}`);90break;91case 'L4_ORDER_DIFF_TYPE_REMOVE':92orders.delete(diff.oid);93console.log(` REMOVE ${diff.coin} oid: ${diff.oid} | ${diff.side === 'B' ? 'BID' : 'ASK'} ${diff.px} | ${diff.user}`);94break;95}96});9798console.log(`\n Resting orders tracked: ${orders.size} | Messages received: ${msgCount}`);99});100101call.on('error', (err) => {102if (err.code === grpc.status.DATA_LOSS && autoReconnect) {103console.log(`\n⚠️ Server reinitialized: ${err.message}`);104retryCount++;105if (retryCount < maxRetries) {106const delay = baseDelay * Math.pow(2, retryCount - 1);107console.log(`⏳ Waiting ${delay / 1000}s before reconnecting...`);108setTimeout(() => streamL4BookUpdates(coins, autoReconnect, retryCount), delay);109} else {110console.log(`\n❌ Max retries (${maxRetries}) reached. Giving up.`);111}112} else {113console.error('\ngRPC error:', err.code, '-', err.message);114}115});116117call.on('end', () => {118console.log('\nStream ended');119});120121// Wait for stream to complete122await new Promise((resolve) => {123call.on('end', resolve);124call.on('error', resolve);125});126127break; // Exit retry loop on success128129} catch (err) {130console.error('Error:', err.message);131break;132}133}134}135136// Parse command line args137const args = process.argv.slice(2);138const coins = (args.find(a => a.startsWith('--coins='))?.split('=')[1] || 'BTC').split(',');139140console.log('\n' + '='.repeat(60));141console.log('Hyperliquid StreamL4BookUpdates Example');142console.log(`Endpoint: ${GRPC_ENDPOINT}`);143console.log('='.repeat(60));144145streamL4BookUpdates(coins);146
1// StreamL4BookUpdates Example - Stream typed per-order book updates via gRPC2const grpc = require('@grpc/grpc-js');3const protoLoader = require('@grpc/proto-loader');4const path = require('path');56const GRPC_ENDPOINT = 'your-endpoint.hype-mainnet.quiknode.pro:10000';7const AUTH_TOKEN = 'your-auth-token';8const PROTO_PATH = path.join(__dirname, 'proto', 'orderbook.proto');910const packageDefinition = protoLoader.loadSync(PROTO_PATH, {11keepCase: true,12longs: String,13enums: String,14defaults: true,15oneofs: true16});17const proto = grpc.loadPackageDefinition(packageDefinition).hyperliquid;1819function createClient() {20return new proto.OrderBookStreaming(21GRPC_ENDPOINT,22grpc.credentials.createSsl(),23{ 'grpc.max_receive_message_length': 100 * 1024 * 1024 }24);25}2627// Stream typed per-order L4 book updates28async function streamL4BookUpdates(coins, autoReconnect = true, retryCount = 0) {29console.log('='.repeat(60));30console.log(`Streaming L4 Book Updates for ${coins.length > 0 ? coins.join(', ') : 'all coins'}`);31console.log(`Auto-reconnect: ${autoReconnect}`);32console.log('='.repeat(60) + '\n');3334const maxRetries = 10;35const baseDelay = 2000;3637// Simple local order map keyed by oid38const orders = new Map();3940while (retryCount < maxRetries) {41const client = createClient();42const metadata = new grpc.Metadata();43metadata.add('x-token', AUTH_TOKEN);4445const request = {46coins: coins47};4849try {50if (retryCount > 0) {51console.log(`\n🔄 Reconnecting (attempt ${retryCount + 1}/${maxRetries})...`);52} else {53console.log(`Connecting to ${GRPC_ENDPOINT}...`);54}5556let msgCount = 0;57const call = client.StreamL4BookUpdates(request, metadata);5859call.on('data', (update) => {60msgCount++;6162if (msgCount === 1) {63console.log('✓ First L4 update received!\n');64retryCount = 0; // Reset on success65}6667if (update.snapshot) {68// Full reset snapshot: rebuild local state69orders.clear();70}7172console.log('\n' + '─'.repeat(60));73console.log(`Block: ${update.height} | Time: ${update.time}${update.snapshot ? ' | SNAPSHOT' : ''} | Diffs: ${(update.diffs || []).length}`);74console.log('─'.repeat(60));7576(update.diffs || []).forEach(diff => {77switch (diff.diff_type) {78case 'L4_ORDER_DIFF_TYPE_NEW':79orders.set(diff.oid, { coin: diff.coin, user: diff.user, side: diff.side, px: diff.px, sz: diff.sz });80if (!update.snapshot) {81console.log(` NEW ${diff.coin} oid: ${diff.oid} | ${diff.side === 'B' ? 'BID' : 'ASK'} ${diff.px} x ${diff.sz} | ${diff.user}`);82}83break;84case 'L4_ORDER_DIFF_TYPE_UPDATE':85const existing = orders.get(diff.oid);86if (existing) {87existing.sz = diff.sz;88}89console.log(` UPDATE ${diff.coin} oid: ${diff.oid} | ${diff.side === 'B' ? 'BID' : 'ASK'} ${diff.px} x ${diff.sz} | ${diff.user}`);90break;91case 'L4_ORDER_DIFF_TYPE_REMOVE':92orders.delete(diff.oid);93console.log(` REMOVE ${diff.coin} oid: ${diff.oid} | ${diff.side === 'B' ? 'BID' : 'ASK'} ${diff.px} | ${diff.user}`);94break;95}96});9798console.log(`\n Resting orders tracked: ${orders.size} | Messages received: ${msgCount}`);99});100101call.on('error', (err) => {102if (err.code === grpc.status.DATA_LOSS && autoReconnect) {103console.log(`\n⚠️ Server reinitialized: ${err.message}`);104retryCount++;105if (retryCount < maxRetries) {106const delay = baseDelay * Math.pow(2, retryCount - 1);107console.log(`⏳ Waiting ${delay / 1000}s before reconnecting...`);108setTimeout(() => streamL4BookUpdates(coins, autoReconnect, retryCount), delay);109} else {110console.log(`\n❌ Max retries (${maxRetries}) reached. Giving up.`);111}112} else {113console.error('\ngRPC error:', err.code, '-', err.message);114}115});116117call.on('end', () => {118console.log('\nStream ended');119});120121// Wait for stream to complete122await new Promise((resolve) => {123call.on('end', resolve);124call.on('error', resolve);125});126127break; // Exit retry loop on success128129} catch (err) {130console.error('Error:', err.message);131break;132}133}134}135136// Parse command line args137const args = process.argv.slice(2);138const coins = (args.find(a => a.startsWith('--coins='))?.split('=')[1] || 'BTC').split(',');139140console.log('\n' + '='.repeat(60));141console.log('Hyperliquid StreamL4BookUpdates Example');142console.log(`Endpoint: ${GRPC_ENDPOINT}`);143console.log('='.repeat(60));144145streamL4BookUpdates(coins);146
1#!/usr/bin/env python32"""3StreamL4BookUpdates Example - Stream typed per-order book updates via gRPC45Setup:6pip install grpcio grpcio-tools protobuf zstandard7python -m grpc_tools.protoc -I../../proto --python_out=. --grpc_python_out=. ../../proto/orderbook.proto89Usage:10python stream_l4_updates_example.py --coins BTC,ETH11"""1213import grpc14import sys15import time16import argparse1718try:19import orderbook_pb2 as pb20import orderbook_pb2_grpc as pb_grpc21except ImportError:22print("Error: Proto files not generated. Run:")23print(" python -m grpc_tools.protoc -I../../proto --python_out=. --grpc_python_out=. ../../proto/orderbook.proto")24sys.exit(1)2526# Configuration27GRPC_ENDPOINT = "your-endpoint.hype-mainnet.quiknode.pro:10000"28AUTH_TOKEN = "your-auth-token"293031def stream_l4_book_updates(coins: list, auto_reconnect: bool = True):32"""33Stream typed per-order L4 book updates for one or more coins.3435Args:36coins: Symbols to stream (e.g., ["BTC", "ETH"]). Empty list means all coins37auto_reconnect: Auto-reconnect on DATA_LOSS errors (default True)38"""39print(f"\n{'='*60}")40print(f"Streaming L4 Book Updates for {', '.join(coins) if coins else 'all coins'}")41print(f"Auto-reconnect: {auto_reconnect}")42print(f"{'='*60}\n")4344retry_count = 045max_retries = 1046base_delay = 24748# Simple local order map keyed by oid49orders = {}5051while retry_count < max_retries:52channel = grpc.secure_channel(53GRPC_ENDPOINT,54grpc.ssl_channel_credentials(),55options=[56('grpc.max_receive_message_length', 100 * 1024 * 1024),57('grpc.keepalive_time_ms', 30000),58]59)60stub = pb_grpc.OrderBookStreamingStub(channel)6162# Build request63request = pb.L4BookUpdatesRequest(coins=coins)6465msg_count = 06667try:68if retry_count > 0:69print(f"\n🔄 Reconnecting (attempt {retry_count + 1}/{max_retries})...")70else:71print(f"Connecting to {GRPC_ENDPOINT}...")7273for update in stub.StreamL4BookUpdates(request, metadata=[('x-token', AUTH_TOKEN)]):74msg_count += 17576if msg_count == 1:77print(f"✓ First L4 update received!\n")78retry_count = 0 # Reset retry count on successful connection7980if update.snapshot:81# Full reset snapshot: rebuild local state82orders.clear()8384# Display the update85print(f"\n{'─'*60}")86snapshot_label = " | SNAPSHOT" if update.snapshot else ""87print(f"Block: {update.height} | Time: {update.time}{snapshot_label} | Diffs: {len(update.diffs)}")88print(f"{'─'*60}")8990for diff in update.diffs:91side = "BID" if diff.side == "B" else "ASK"9293if diff.diff_type == pb.L4_ORDER_DIFF_TYPE_NEW:94orders[diff.oid] = {"coin": diff.coin, "user": diff.user, "side": diff.side, "px": diff.px, "sz": diff.sz}95if not update.snapshot:96print(f" NEW {diff.coin} oid: {diff.oid} | {side} {diff.px} x {diff.sz} | {diff.user}")97elif diff.diff_type == pb.L4_ORDER_DIFF_TYPE_UPDATE:98if diff.oid in orders:99orders[diff.oid]["sz"] = diff.sz100print(f" UPDATE {diff.coin} oid: {diff.oid} | {side} {diff.px} x {diff.sz} | {diff.user}")101elif diff.diff_type == pb.L4_ORDER_DIFF_TYPE_REMOVE:102orders.pop(diff.oid, None)103print(f" REMOVE {diff.coin} oid: {diff.oid} | {side} {diff.px} | {diff.user}")104105print(f"\n Resting orders tracked: {len(orders)} | Messages received: {msg_count}")106107except grpc.RpcError as e:108if e.code() == grpc.StatusCode.DATA_LOSS and auto_reconnect:109print(f"\n⚠️ Server reinitialized: {e.details()}")110retry_count += 1111if retry_count < max_retries:112delay = base_delay * (2 ** (retry_count - 1)) # Exponential backoff113print(f"⏳ Waiting {delay}s before reconnecting...")114time.sleep(delay)115channel.close()116continue117else:118print(f"\n❌ Max retries ({max_retries}) reached. Giving up.")119break120else:121print(f"\ngRPC error: {e.code()} - {e.details()}")122break123except KeyboardInterrupt:124print("\nStopping L4 updates stream...")125break126finally:127channel.close()128129# If we get here without error, break the retry loop130break131132133def main():134parser = argparse.ArgumentParser(description='Stream Hyperliquid typed per-order L4 book updates via gRPC')135parser.add_argument('--coins', default='BTC', help='Comma-separated coin symbols to stream (e.g., BTC,ETH)')136137args = parser.parse_args()138coins = [c.strip() for c in args.coins.split(',') if c.strip()]139140print(f"\n{'='*60}")141print("Hyperliquid StreamL4BookUpdates Example")142print(f"Endpoint: {GRPC_ENDPOINT}")143print(f"{'='*60}")144145try:146stream_l4_book_updates(coins)147except Exception as e:148print(f"\nError: {e}")149import traceback150traceback.print_exc()151sys.exit(1)152153154if __name__ == "__main__":155main()156
1#!/usr/bin/env python32"""3StreamL4BookUpdates Example - Stream typed per-order book updates via gRPC45Setup:6pip install grpcio grpcio-tools protobuf zstandard7python -m grpc_tools.protoc -I../../proto --python_out=. --grpc_python_out=. ../../proto/orderbook.proto89Usage:10python stream_l4_updates_example.py --coins BTC,ETH11"""1213import grpc14import sys15import time16import argparse1718try:19import orderbook_pb2 as pb20import orderbook_pb2_grpc as pb_grpc21except ImportError:22print("Error: Proto files not generated. Run:")23print(" python -m grpc_tools.protoc -I../../proto --python_out=. --grpc_python_out=. ../../proto/orderbook.proto")24sys.exit(1)2526# Configuration27GRPC_ENDPOINT = "your-endpoint.hype-mainnet.quiknode.pro:10000"28AUTH_TOKEN = "your-auth-token"293031def stream_l4_book_updates(coins: list, auto_reconnect: bool = True):32"""33Stream typed per-order L4 book updates for one or more coins.3435Args:36coins: Symbols to stream (e.g., ["BTC", "ETH"]). Empty list means all coins37auto_reconnect: Auto-reconnect on DATA_LOSS errors (default True)38"""39print(f"\n{'='*60}")40print(f"Streaming L4 Book Updates for {', '.join(coins) if coins else 'all coins'}")41print(f"Auto-reconnect: {auto_reconnect}")42print(f"{'='*60}\n")4344retry_count = 045max_retries = 1046base_delay = 24748# Simple local order map keyed by oid49orders = {}5051while retry_count < max_retries:52channel = grpc.secure_channel(53GRPC_ENDPOINT,54grpc.ssl_channel_credentials(),55options=[56('grpc.max_receive_message_length', 100 * 1024 * 1024),57('grpc.keepalive_time_ms', 30000),58]59)60stub = pb_grpc.OrderBookStreamingStub(channel)6162# Build request63request = pb.L4BookUpdatesRequest(coins=coins)6465msg_count = 06667try:68if retry_count > 0:69print(f"\n🔄 Reconnecting (attempt {retry_count + 1}/{max_retries})...")70else:71print(f"Connecting to {GRPC_ENDPOINT}...")7273for update in stub.StreamL4BookUpdates(request, metadata=[('x-token', AUTH_TOKEN)]):74msg_count += 17576if msg_count == 1:77print(f"✓ First L4 update received!\n")78retry_count = 0 # Reset retry count on successful connection7980if update.snapshot:81# Full reset snapshot: rebuild local state82orders.clear()8384# Display the update85print(f"\n{'─'*60}")86snapshot_label = " | SNAPSHOT" if update.snapshot else ""87print(f"Block: {update.height} | Time: {update.time}{snapshot_label} | Diffs: {len(update.diffs)}")88print(f"{'─'*60}")8990for diff in update.diffs:91side = "BID" if diff.side == "B" else "ASK"9293if diff.diff_type == pb.L4_ORDER_DIFF_TYPE_NEW:94orders[diff.oid] = {"coin": diff.coin, "user": diff.user, "side": diff.side, "px": diff.px, "sz": diff.sz}95if not update.snapshot:96print(f" NEW {diff.coin} oid: {diff.oid} | {side} {diff.px} x {diff.sz} | {diff.user}")97elif diff.diff_type == pb.L4_ORDER_DIFF_TYPE_UPDATE:98if diff.oid in orders:99orders[diff.oid]["sz"] = diff.sz100print(f" UPDATE {diff.coin} oid: {diff.oid} | {side} {diff.px} x {diff.sz} | {diff.user}")101elif diff.diff_type == pb.L4_ORDER_DIFF_TYPE_REMOVE:102orders.pop(diff.oid, None)103print(f" REMOVE {diff.coin} oid: {diff.oid} | {side} {diff.px} | {diff.user}")104105print(f"\n Resting orders tracked: {len(orders)} | Messages received: {msg_count}")106107except grpc.RpcError as e:108if e.code() == grpc.StatusCode.DATA_LOSS and auto_reconnect:109print(f"\n⚠️ Server reinitialized: {e.details()}")110retry_count += 1111if retry_count < max_retries:112delay = base_delay * (2 ** (retry_count - 1)) # Exponential backoff113print(f"⏳ Waiting {delay}s before reconnecting...")114time.sleep(delay)115channel.close()116continue117else:118print(f"\n❌ Max retries ({max_retries}) reached. Giving up.")119break120else:121print(f"\ngRPC error: {e.code()} - {e.details()}")122break123except KeyboardInterrupt:124print("\nStopping L4 updates stream...")125break126finally:127channel.close()128129# If we get here without error, break the retry loop130break131132133def main():134parser = argparse.ArgumentParser(description='Stream Hyperliquid typed per-order L4 book updates via gRPC')135parser.add_argument('--coins', default='BTC', help='Comma-separated coin symbols to stream (e.g., BTC,ETH)')136137args = parser.parse_args()138coins = [c.strip() for c in args.coins.split(',') if c.strip()]139140print(f"\n{'='*60}")141print("Hyperliquid StreamL4BookUpdates Example")142print(f"Endpoint: {GRPC_ENDPOINT}")143print(f"{'='*60}")144145try:146stream_l4_book_updates(coins)147except Exception as e:148print(f"\nError: {e}")149import traceback150traceback.print_exc()151sys.exit(1)152153154if __name__ == "__main__":155main()156