Skip to main content

StreamL2BookDiff gRPC Method

Loading...

Updated on
Jun 10, 2026

StreamL2BookDiff gRPC Method

Please note that this method is metered based on data consumption at 0.0165 MB = 10 API credits.

Parameters

coins
repeated string
Loading...
n_levels
uint32
Loading...
n_sig_figs
uint32
Loading...
mantissa
uint64
Loading...
skip_initial_snapshot
bool
Loading...

Returns

stream
stream<L2BookDiffUpdate>
Loading...
time
uint64
Loading...
height
uint64
Loading...
snapshot
bool
Loading...
diffs
array<L2CoinDiff>
Loading...
Request
1
// StreamL2BookDiff Example - Stream incremental L2 price-level changes via gRPC
2
package main
3
4
import (
5
"context"
6
"flag"
7
"fmt"
8
"io"
9
"log"
10
"math"
11
"strings"
12
"time"
13
14
"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"
19
20
pb "hyperliquid-orderbook-example/proto"
21
)
22
23
const (
24
grpcEndpoint = "your-endpoint.hype-mainnet.quiknode.pro:10000"
25
authToken = "your-auth-token"
26
maxRetries = 10
27
baseDelay = 2 * time.Second
28
)
29
30
func streamL2BookDiff(coins []string, nLevels uint32) error {
31
fmt.Println(strings.Repeat("=", 60))
32
fmt.Printf("Streaming L2 Book Diffs for %s\n", strings.Join(coins, ", "))
33
fmt.Printf("Levels: %d\n", nLevels)
34
fmt.Println("Auto-reconnect: true")
35
fmt.Println(strings.Repeat("=", 60) + "\n")
36
37
retryCount := 0
38
39
// Track last seq per coin for gap detection
40
lastSeq := make(map[string]uint64)
41
42
for retryCount < maxRetries {
43
creds := credentials.NewClientTLSFromCert(nil, "")
44
conn, err := grpc.Dial(grpcEndpoint,
45
grpc.WithTransportCredentials(creds),
46
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024)))
47
if err != nil {
48
return fmt.Errorf("failed to connect: %w", err)
49
}
50
51
client := pb.NewOrderBookStreamingClient(conn)
52
ctx := metadata.AppendToOutgoingContext(context.Background(), "x-token", authToken)
53
54
request := &pb.L2BookDiffRequest{
55
Coins: coins,
56
NLevels: nLevels,
57
}
58
59
if retryCount > 0 {
60
fmt.Printf("\nšŸ”„ Reconnecting (attempt %d/%d)...\n", retryCount+1, maxRetries)
61
} else {
62
fmt.Printf("Connecting to %s...\n", grpcEndpoint)
63
}
64
65
stream, err := client.StreamL2BookDiff(ctx, request)
66
if err != nil {
67
conn.Close()
68
return fmt.Errorf("failed to start stream: %w", err)
69
}
70
71
msgCount := 0
72
shouldRetry := false
73
74
for {
75
update, err := stream.Recv()
76
if err == io.EOF {
77
break
78
}
79
if err != nil {
80
st, ok := status.FromError(err)
81
if ok && st.Code() == codes.DataLoss {
82
fmt.Printf("\nāš ļø Server reinitialized: %s\n", st.Message())
83
retryCount++
84
if retryCount < maxRetries {
85
delay := baseDelay * time.Duration(math.Pow(2, float64(retryCount-1)))
86
fmt.Printf("ā³ Waiting %v before reconnecting...\n", delay)
87
time.Sleep(delay)
88
shouldRetry = true
89
break
90
} else {
91
fmt.Printf("\nāŒ Max retries (%d) reached. Giving up.\n", maxRetries)
92
conn.Close()
93
return nil
94
}
95
}
96
conn.Close()
97
return fmt.Errorf("stream error: %w", err)
98
}
99
100
msgCount++
101
if msgCount == 1 {
102
fmt.Println("āœ“ First L2 diff update received!\n")
103
retryCount = 0 // Reset on success
104
}
105
106
// Display diff update
107
fmt.Println("\n" + strings.Repeat("─", 60))
108
snapshotLabel := ""
109
if update.Snapshot {
110
snapshotLabel = " | SNAPSHOT"
111
}
112
fmt.Printf("Block: %d | Time: %d%s\n", update.Height, update.Time, snapshotLabel)
113
fmt.Println(strings.Repeat("─", 60))
114
115
for _, diff := range update.Diffs {
116
// Check seq continuity per coin
117
if prev, ok := lastSeq[diff.Coin]; ok && diff.PrevSeq != 0 && diff.PrevSeq != prev {
118
fmt.Printf(" āš ļø Sequence gap for %s: expected prev_seq %d, got %d\n", diff.Coin, prev, diff.PrevSeq)
119
}
120
lastSeq[diff.Coin] = diff.Seq
121
122
label := "DIFF"
123
if diff.Snapshot {
124
label = "SNAPSHOT"
125
}
126
fmt.Printf("\n %s [%s] seq: %d (prev: %d)\n", diff.Coin, label, diff.Seq, diff.PrevSeq)
127
128
// Changed ask levels (sz "0" means level removed)
129
for _, level := range diff.Asks {
130
action := "SET"
131
if level.Sz == "0" {
132
action = "REMOVE"
133
}
134
fmt.Printf(" ASK %-6s %12s | %12s\n", action, level.Px, level.Sz)
135
}
136
137
// Changed bid levels
138
for _, level := range diff.Bids {
139
action := "SET"
140
if level.Sz == "0" {
141
action = "REMOVE"
142
}
143
fmt.Printf(" BID %-6s %12s | %12s\n", action, level.Px, level.Sz)
144
}
145
}
146
147
fmt.Printf("\n Messages received: %d\n", msgCount)
148
}
149
150
conn.Close()
151
152
if !shouldRetry {
153
break
154
}
155
}
156
157
return nil
158
}
159
160
func main() {
161
coinsFlag := flag.String("coins", "BTC", "Comma-separated coin symbols to stream (e.g., BTC,ETH)")
162
levels := flag.Uint("levels", 20, "Maximum price levels per side")
163
164
flag.Parse()
165
166
coins := strings.Split(*coinsFlag, ",")
167
168
fmt.Println("\n" + strings.Repeat("=", 60))
169
fmt.Println("Hyperliquid StreamL2BookDiff Example")
170
fmt.Printf("Endpoint: %s\n", grpcEndpoint)
171
fmt.Println(strings.Repeat("=", 60))
172
173
if err := streamL2BookDiff(coins, uint32(*levels)); err != nil {
174
log.Fatal(err)
175
}
176
}
177
1
// StreamL2BookDiff Example - Stream incremental L2 price-level changes via gRPC
2
package main
3
4
import (
5
"context"
6
"flag"
7
"fmt"
8
"io"
9
"log"
10
"math"
11
"strings"
12
"time"
13
14
"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"
19
20
pb "hyperliquid-orderbook-example/proto"
21
)
22
23
const (
24
grpcEndpoint = "your-endpoint.hype-mainnet.quiknode.pro:10000"
25
authToken = "your-auth-token"
26
maxRetries = 10
27
baseDelay = 2 * time.Second
28
)
29
30
func streamL2BookDiff(coins []string, nLevels uint32) error {
31
fmt.Println(strings.Repeat("=", 60))
32
fmt.Printf("Streaming L2 Book Diffs for %s\n", strings.Join(coins, ", "))
33
fmt.Printf("Levels: %d\n", nLevels)
34
fmt.Println("Auto-reconnect: true")
35
fmt.Println(strings.Repeat("=", 60) + "\n")
36
37
retryCount := 0
38
39
// Track last seq per coin for gap detection
40
lastSeq := make(map[string]uint64)
41
42
for retryCount < maxRetries {
43
creds := credentials.NewClientTLSFromCert(nil, "")
44
conn, err := grpc.Dial(grpcEndpoint,
45
grpc.WithTransportCredentials(creds),
46
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024)))
47
if err != nil {
48
return fmt.Errorf("failed to connect: %w", err)
49
}
50
51
client := pb.NewOrderBookStreamingClient(conn)
52
ctx := metadata.AppendToOutgoingContext(context.Background(), "x-token", authToken)
53
54
request := &pb.L2BookDiffRequest{
55
Coins: coins,
56
NLevels: nLevels,
57
}
58
59
if retryCount > 0 {
60
fmt.Printf("\nšŸ”„ Reconnecting (attempt %d/%d)...\n", retryCount+1, maxRetries)
61
} else {
62
fmt.Printf("Connecting to %s...\n", grpcEndpoint)
63
}
64
65
stream, err := client.StreamL2BookDiff(ctx, request)
66
if err != nil {
67
conn.Close()
68
return fmt.Errorf("failed to start stream: %w", err)
69
}
70
71
msgCount := 0
72
shouldRetry := false
73
74
for {
75
update, err := stream.Recv()
76
if err == io.EOF {
77
break
78
}
79
if err != nil {
80
st, ok := status.FromError(err)
81
if ok && st.Code() == codes.DataLoss {
82
fmt.Printf("\nāš ļø Server reinitialized: %s\n", st.Message())
83
retryCount++
84
if retryCount < maxRetries {
85
delay := baseDelay * time.Duration(math.Pow(2, float64(retryCount-1)))
86
fmt.Printf("ā³ Waiting %v before reconnecting...\n", delay)
87
time.Sleep(delay)
88
shouldRetry = true
89
break
90
} else {
91
fmt.Printf("\nāŒ Max retries (%d) reached. Giving up.\n", maxRetries)
92
conn.Close()
93
return nil
94
}
95
}
96
conn.Close()
97
return fmt.Errorf("stream error: %w", err)
98
}
99
100
msgCount++
101
if msgCount == 1 {
102
fmt.Println("āœ“ First L2 diff update received!\n")
103
retryCount = 0 // Reset on success
104
}
105
106
// Display diff update
107
fmt.Println("\n" + strings.Repeat("─", 60))
108
snapshotLabel := ""
109
if update.Snapshot {
110
snapshotLabel = " | SNAPSHOT"
111
}
112
fmt.Printf("Block: %d | Time: %d%s\n", update.Height, update.Time, snapshotLabel)
113
fmt.Println(strings.Repeat("─", 60))
114
115
for _, diff := range update.Diffs {
116
// Check seq continuity per coin
117
if prev, ok := lastSeq[diff.Coin]; ok && diff.PrevSeq != 0 && diff.PrevSeq != prev {
118
fmt.Printf(" āš ļø Sequence gap for %s: expected prev_seq %d, got %d\n", diff.Coin, prev, diff.PrevSeq)
119
}
120
lastSeq[diff.Coin] = diff.Seq
121
122
label := "DIFF"
123
if diff.Snapshot {
124
label = "SNAPSHOT"
125
}
126
fmt.Printf("\n %s [%s] seq: %d (prev: %d)\n", diff.Coin, label, diff.Seq, diff.PrevSeq)
127
128
// Changed ask levels (sz "0" means level removed)
129
for _, level := range diff.Asks {
130
action := "SET"
131
if level.Sz == "0" {
132
action = "REMOVE"
133
}
134
fmt.Printf(" ASK %-6s %12s | %12s\n", action, level.Px, level.Sz)
135
}
136
137
// Changed bid levels
138
for _, level := range diff.Bids {
139
action := "SET"
140
if level.Sz == "0" {
141
action = "REMOVE"
142
}
143
fmt.Printf(" BID %-6s %12s | %12s\n", action, level.Px, level.Sz)
144
}
145
}
146
147
fmt.Printf("\n Messages received: %d\n", msgCount)
148
}
149
150
conn.Close()
151
152
if !shouldRetry {
153
break
154
}
155
}
156
157
return nil
158
}
159
160
func main() {
161
coinsFlag := flag.String("coins", "BTC", "Comma-separated coin symbols to stream (e.g., BTC,ETH)")
162
levels := flag.Uint("levels", 20, "Maximum price levels per side")
163
164
flag.Parse()
165
166
coins := strings.Split(*coinsFlag, ",")
167
168
fmt.Println("\n" + strings.Repeat("=", 60))
169
fmt.Println("Hyperliquid StreamL2BookDiff Example")
170
fmt.Printf("Endpoint: %s\n", grpcEndpoint)
171
fmt.Println(strings.Repeat("=", 60))
172
173
if err := streamL2BookDiff(coins, uint32(*levels)); err != nil {
174
log.Fatal(err)
175
}
176
}
177
Don't have an account yet?
Create your Quicknode endpoint in seconds and start building
Get started for free