Overview
This guide demonstrates how to stream real-time orderbook data from Hyperliquid using gRPC in Go. You'll learn to:
- Stream L2 orderbook (aggregated price levels with depths)
- Stream L4 orderbook (individual orders with full details)
- Generate Go code from Protocol Buffer definitions
- Handle reconnections and errors gracefully
The orderbook streaming API uses a separate proto file (orderbook.proto) from the core streaming API, providing specialized methods optimized for market data.
For core streaming API (StreamData, StreamBlocks, Ping), see Go Setup Guide
Prerequisites
1. Install Go
Go 1.21 or later is required. Follow the official installation guide or use a package manager:
# macOS
brew install go
# Verify installation
go version
2. Install Protocol Buffers Compiler
The protoc compiler is required to generate Go code from .proto files:
# macOS
brew install protobuf
# Linux (Ubuntu/Debian)
apt-get install -y protobuf-compiler
# Verify installation
protoc --version
3. Get Quicknode Credentials
Sign up at Quicknode and create a Hyperliquid endpoint. Your credentials consist of:
From HTTP Provider URL:
https://example-endpoint.hype-mainnet.quiknode.pro/abc123def456/
Extract:
- Endpoint:
example-endpoint.hype-mainnet.quiknode.pro:10000(note port 10000) - Token:
abc123def456
Project Setup
Step 1: Create Project Structure
mkdir -p hyperliquid-orderbook-go/{proto,examples}
cd hyperliquid-orderbook-go
Step 2: Initialize Go Module
go mod init hyperliquid-orderbook-example
Step 3: Install Dependencies
go get google.golang.org/grpc@latest
go get google.golang.org/protobuf/cmd/protoc-gen-go@latest
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
This installs:
google.golang.org/grpc- gRPC runtimeprotoc-gen-go- Protocol buffer Go generatorprotoc-gen-go-grpc- gRPC Go generator
Step 4: Download Proto File
Download the orderbook proto definition:
curl -o proto/orderbook.proto \
https://raw.githubusercontent.com/quiknode-labs/hypercore-grpc-examples/main/proto/orderbook.proto
Step 5: Add Go Package Option
Edit proto/orderbook.proto and add the go_package option after the package declaration:
syntax = "proto3";
package hyperliquid;
option go_package = "hyperliquid-orderbook-example/proto";
service OrderBookStreaming {
// ... rest of the proto file
}
Step 6: Generate Go Code
Generate Go code from the proto file:
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
proto/orderbook.proto
This creates:
proto/orderbook.pb.go- Message definitionsproto/orderbook_grpc.pb.go- gRPC service client
Verify the files were generated:
ls -la proto/
# Should show: orderbook.proto, orderbook.pb.go, orderbook_grpc.pb.go
Implementation
StreamL2Book Example
Create examples/stream_l2.go:
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"math"
"strings"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
pb "hyperliquid-orderbook-example/proto"
)
const (
maxRetries = 10
baseDelay = 2 * time.Second
)
func streamL2Orderbook(grpcEndpoint, authToken, coin string, nLevels uint32) error {
fmt.Println(strings.Repeat("=", 60))
fmt.Printf("Streaming L2 Orderbook for %s\n", coin)
fmt.Printf("Levels: %d\n", nLevels)
fmt.Println(strings.Repeat("=", 60) + "\n")
retryCount := 0
for retryCount < maxRetries {
// Create TLS credentials
creds := credentials.NewClientTLSFromCert(nil, "")
conn, err := grpc.Dial(grpcEndpoint,
grpc.WithTransportCredentials(creds),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024)))
if err != nil {
return fmt.Errorf("failed to connect: %w", err)
}
// Create client and add auth token to context
client := pb.NewOrderBookStreamingClient(conn)
ctx := metadata.AppendToOutgoingContext(context.Background(), "x-token", authToken)
// Create request
request := &pb.L2BookRequest{
Coin: coin,
NLevels: nLevels,
}
if retryCount > 0 {
fmt.Printf("\n🔄 Reconnecting (attempt %d/%d)...\n", retryCount+1, maxRetries)
} else {
fmt.Printf("Connecting to %s...\n", grpcEndpoint)
}
stream, err := client.StreamL2Book(ctx, request)
if err != nil {
conn.Close()
return fmt.Errorf("failed to start stream: %w", err)
}
msgCount := 0
shouldRetry := false
for {
update, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
st, ok := status.FromError(err)
if ok && st.Code() == codes.DataLoss {
fmt.Printf("\n⚠️ Server reinitialized: %s\n", st.Message())
retryCount++
if retryCount < maxRetries {
delay := baseDelay * time.Duration(math.Pow(2, float64(retryCount-1)))
fmt.Printf("⏳ Waiting %v before reconnecting...\n", delay)
time.Sleep(delay)
shouldRetry = true
break
}
}
conn.Close()
return fmt.Errorf("stream error: %w", err)
}
msgCount++
if msgCount == 1 {
fmt.Println("✓ First L2 update received!\n")
retryCount = 0 // Reset on success
}
// Display orderbook
fmt.Println("\n" + strings.Repeat("─", 60))
fmt.Printf("Block: %d | Time: %d | Coin: %s\n", update.BlockNumber, update.Time, update.Coin)
fmt.Println(strings.Repeat("─", 60))
// Display asks (reversed for proper display)
if len(update.Asks) > 0 {
fmt.Println("\n ASKS:")
askCount := len(update.Asks)
if askCount > 10 {
askCount = 10
}
for i := askCount - 1; i >= 0; i-- {
level := update.Asks[i]
fmt.Printf(" %12s | %12s | (%d orders)\n", level.Px, level.Sz, level.N)
}
}
// Display spread
if len(update.Bids) > 0 && len(update.Asks) > 0 {
fmt.Println("\n " + strings.Repeat("─", 44))
fmt.Printf(" SPREAD: (best bid: %s, best ask: %s)\n", update.Bids[0].Px, update.Asks[0].Px)
fmt.Println(" " + strings.Repeat("─", 44))
}
// Display bids
if len(update.Bids) > 0 {
fmt.Println("\n BIDS:")
bidCount := len(update.Bids)
if bidCount > 10 {
bidCount = 10
}
for i := 0; i < bidCount; i++ {
level := update.Bids[i]
fmt.Printf(" %12s | %12s | (%d orders)\n", level.Px, level.Sz, level.N)
}
}
fmt.Printf("\n Messages received: %d\n", msgCount)
}
conn.Close()
if !shouldRetry {
break
}
}
return nil
}
func main() {
endpoint := flag.String("endpoint", "your-endpoint.hype-mainnet.quiknode.pro:10000", "gRPC endpoint")
token := flag.String("token", "your-auth-token", "Authentication token")
coin := flag.String("coin", "BTC", "Coin symbol to stream")
levels := flag.Uint("levels", 20, "Number of price levels")
flag.Parse()
fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Println("Hyperliquid StreamL2Book Example")
fmt.Printf("Endpoint: %s\n", *endpoint)
fmt.Println(strings.Repeat("=", 60))
if err := streamL2Orderbook(*endpoint, *token, *coin, uint32(*levels)); err != nil {
log.Fatal(err)
}
}
For individual order streaming with full details, see the StreamL4Book method documentation which includes complete code examples.
Build and Run
Build the Example
# Tidy dependencies
go mod tidy
# Build L2 example
cd examples
go build -o stream_l2 stream_l2.go
Run StreamL2Book
Stream aggregated orderbook with configurable depth:
./stream_l2 \
-endpoint="your-endpoint.hype-mainnet.quiknode.pro:10000" \
-token="your-auth-token" \
-coin="BTC" \
-levels=20
Options:
-endpoint- gRPC endpoint with port 10000-token- Authentication token from Quicknode-coin- Trading pair (BTC, ETH, SOL, etc.)-levels- Number of price levels (max 100)
Output:
============================================================
Streaming L2 Orderbook for BTC
Levels: 20
============================================================
Connecting to your-endpoint.hype-mainnet.quiknode.pro:10000...
✓ First L2 update received!
────────────────────────────────────────────────────────────
Block: 903697656 | Time: 1771865026011 | Coin: BTC
────────────────────────────────────────────────────────────
ASKS:
65577 | 0.00017 | (1 orders)
65576 | 0.00017 | (1 orders)
65575 | 1.14631 | (3 orders)
...
────────────────────────────────────────
SPREAD: (best bid: 65558, best ask: 65564)
────────────────────────────────────────
BIDS:
65558 | 25.0365 | (48 orders)
65557 | 18.9296 | (11 orders)
...
Messages received: 1
Understanding the Output:
- Block/Time - Blockchain block number and timestamp
- Asks - Sell orders (displayed high to low)
- Spread - Difference between best bid and ask
- Bids - Buy orders (displayed high to low)
- Order Count - Number of individual orders at each price level
We ❤️ Feedback!
If you have any feedback or questions about this documentation, let us know. We'd love to hear from you!