概覽
Go is a statically-typed, compiled language known for its simplicity, efficiency, and strong concurrency support. Follow the official installation guide to install Go. Verify the installation:
go version
For streaming orderbook data (StreamL2Book, StreamL4Book, StreamBboBook, StreamL2BookDiff, StreamL4BookUpdates, StreamTpslUpdates), see Go OrderBook Setup Guide
Access to /hypercore (JSON-RPC and WebSocket) and HyperCore gRPC streaming requires a Quicknode Build plan or higher. View pricing to upgrade.
Authentication for Go Requests
To securely access Hyperliquid gRPC, authentication is required. Quicknode endpoints consist of two components: endpoint name 以及 代幣. You must use these components to configure a gRPC client with authentication credentials.
There are two authentication methods:
- Basic Authentication
- x-token Authentication
This document provides implementation details for both methods, though x-token authentication is the recommended approach.
Basic Authentication
該 getClientWithBasicAuth function demonstrates how to handle authentication using Basic Authentication. It encodes the credentials in base64 and adds them to the authorization 頁首。
實施
import (
"context"
"crypto/tls"
"encoding/base64"
"fmt"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func getClientWithBasicAuth(endpoint, token string) (*grpc.ClientConn, error) {
target := endpoint + ":10000"
conn, err := grpc.Dial(target,
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),
grpc.WithPerRPCCredentials(basicAuth{
username: endpoint,
password: token,
}),
)
if err != nil {
return nil, fmt.Errorf("unable to dial endpoint: %w", err)
}
return conn, nil
}
type basicAuth struct {
username string
password string
}
func (b basicAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) {
auth := b.username + ":" + b.password
encoded := base64.StdEncoding.EncodeToString([]byte(auth))
return map[string]string{"authorization": "Basic " + encoded}, nil
}
func (basicAuth) RequireTransportSecurity() bool {
return true
}
用法
conn, err := getClientWithBasicAuth("ENDPOINT_NAME", "TOKEN")
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
x-token Authentication
該 getClientWithXToken function demonstrates how to authenticate using an x-token. This method attaches the token to the x-token header of each request.
實施
import (
"context"
"crypto/tls"
"fmt"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func getClientWithXToken(endpoint, token string) (*grpc.ClientConn, error) {
target := endpoint + ":10000"
conn, err := grpc.Dial(target,
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),
grpc.WithPerRPCCredentials(xTokenAuth{
token: token,
}),
)
if err != nil {
return nil, fmt.Errorf("unable to dial endpoint: %w", err)
}
return conn, nil
}
type xTokenAuth struct {
token string
}
func (x xTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{"x-token": x.token}, nil
}
func (xTokenAuth) RequireTransportSecurity() bool {
return true
}
用法
conn, err := getClientWithXToken("ENDPOINT_NAME", "TOKEN")
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
Initiating the Go Project for Hyperliquid gRPC
Step 1: Initialize Go Project
Create a dedicated directory for your Hyperliquid gRPC project and navigate into it:
mkdir hyperliquid-grpc-go
cd hyperliquid-grpc-go
go mod init hyperliquid-grpc-go
Step 2: Install Dependencies
Install the necessary Go dependencies for gRPC and Protocol Buffers:
# Install gRPC and Protocol Buffers packages
go get google.golang.org/grpc
go get google.golang.org/protobuf/cmd/protoc-gen-go
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc
# Make sure the protoc plugins are in your PATH
export PATH="$PATH:$(go env GOPATH)/bin"
Step 3: Create Proto Files
Create the proto directory and add the Hyperliquid streaming protocol definition:
# Create proto directory
mkdir -p proto
Create a new file named streaming.proto in the proto/ directory and add the following content:
syntax = "proto3";
package hyperliquid;
service Streaming {
// Bi-directional streaming
rpc StreamData (stream SubscribeRequest) returns (stream SubscribeUpdate);
rpc Ping (PingRequest) returns (PingResponse);
}
service BlockStreaming {
// Stream replica_cmds (raw blocks)
rpc StreamBlocks (Timestamp) returns (stream Block);
}
// --- Requests ---
message SubscribeRequest {
oneof request {
StreamSubscribe subscribe = 1;
Ping ping = 3;
}
reserved 2;
}
message StreamSubscribe {
StreamType stream_type = 1;
// Generic filters - field name to allowed values
// Recursively searches each event for matching field/value pairs
// Example: {"coin": ["ETH", "BTC"], "user": ["0x123..."], "type": ["deposit"]}
map<string, FilterValues> filters = 3;
// Optional name for this filter
// Allows multiple independent filters per stream (OR logic)
string filter_name = 4;
}
// Container for filter values
message FilterValues {
repeated string values = 1;
}
message Ping { int64 timestamp = 1; }
// --- Responses ---
message SubscribeUpdate {
oneof update {
StreamResponse data = 1;
Pong pong = 2;
}
}
message StreamResponse {
uint64 block_number = 1;
uint64 timestamp = 2; // Server ingress timestamp
// Raw JSON data from the file (Exact replica of source)
string data = 3;
}
// --- Data Types ---
enum StreamType {
UNKNOWN = 0;
TRADES = 1;
ORDERS = 2;
BOOK_UPDATES = 3;
TWAP = 4;
EVENTS = 5;
BLOCKS = 6;
WRITER_ACTIONS = 7;
MEMPOOL_TXS = 8;
ORDER_PRIORITY = 9;
GOSSIP_PRIORITY = 10;
}
message Block {
string data_json = 1;
}
message Pong { int64 timestamp = 1; }
message Timestamp { int64 timestamp = 1; }
message PingRequest { int32 count = 1; }
message PingResponse { int32 count = 1; }
Step 4: Generate Go Code from Proto Files
Generate Go client code from the protocol buffer definition:
# Create pb directory for generated files
mkdir -p pb
# Generate Go code from proto files
protoc --go_out=pb --go_opt=paths=source_relative \
--go-grpc_out=pb --go-grpc_opt=paths=source_relative \
proto/*.proto
After running these commands, your project structure should look like this:
hyperliquid-grpc-go/
├── go.mod
├── go.sum
├── pb/
│ ├── streaming.pb.go
│ └── streaming_grpc.pb.go
├── proto/
│ └── streaming.proto
└── main.go
該 pb/ directory contains the auto-generated Go code from your proto files. You'll import these generated files in your application code.
Step 5: Create Main Application
Create your main application file to test the connection:
package main
import (
"context"
"crypto/tls"
"fmt"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
pb "hyperliquid-grpc-go/pb/proto"
)
const (
grpcEndpoint = "your-endpoint.hype-mainnet.quiknode.pro:10000"
authToken = "your-auth-token"
)
// Create gRPC connection with TLS
func createConnection() (*grpc.ClientConn, error) {
tlsConfig := &tls.Config{
InsecureSkipVerify: false,
}
creds := credentials.NewTLS(tlsConfig)
conn, err := grpc.Dial(
grpcEndpoint,
grpc.WithTransportCredentials(creds),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(100*1024*1024), // 100MB
),
)
return conn, err
}
// Create context with auth metadata
func createContext() context.Context {
md := metadata.Pairs("x-token", authToken)
return metadata.NewOutgoingContext(context.Background(), md)
}
// Test connectivity
func pingTest(client pb.StreamingClient) error {
ctx := createContext()
resp, err := client.Ping(ctx, &pb.PingRequest{Count: 1})
if err != nil {
return fmt.Errorf("ping failed: %v", err)
}
log.Printf("✅ Ping successful: %+v", resp)
return nil
}
func main() {
// Create connection
conn, err := createConnection()
if err != nil {
log.Fatalf("❌ Failed to connect: %v", err)
}
defer conn.Close()
// Create client
client := pb.NewStreamingClient(conn)
// Test connectivity
fmt.Printf("Testing connection to: %s\n", grpcEndpoint)
if err := pingTest(client); err != nil {
log.Fatalf("❌ Ping test failed: %v", err)
}
fmt.Println("✅ Connection test completed successfully!")
}
Step 6: Run the Application
Build and execute your application to test the gRPC connection:
# Download and update dependencies
go mod tidy
# Build and run
go build -o hyperliquid-grpc main.go
./hyperliquid-grpc
# Or run directly
go run main.go
Step 7: Verify Setup
Confirm that your setup is working correctly by checking the output, it should be something like below:
Testing connection to: docs-demo.hype-mainnet.quiknode.pro:10000
2025/12/30 18:10:52 ✅ Ping successful: count:1
✅ Connection test completed successfully!
If everything is set up correctly, you should see:
- Connection established to the gRPC endpoint
- Successful ping response
- No compilation or runtime errors
Complete Example: Streaming Trades with Filtering
Below is a complete working example that demonstrates streaming trade data with coin filtering. This example shows how to:
- Authenticate using x-token
- Stream real-time trades for specific coins (BTC, ETH, SOL, HYPE)
- Parse and display trade events with formatted output
- Handle connection keep-alives with periodic pings
Click to view complete trades.go example
package main
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/joho/godotenv"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
pb "hyperliquid-grpc-go/pb"
)
// xTokenAuth implements credentials.PerRPCCredentials for x-token authentication
type xTokenAuth struct {
token string
}
func (x xTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{"x-token": x.token}, nil
}
func (xTokenAuth) RequireTransportSecurity() bool {
return true
}
// Trade event structure
type TradeEvent struct {
ClosedPnl string `json:"closedPnl"`
Coin string `json:"coin"`
Crossed bool `json:"crossed"`
Dir string `json:"dir"`
Fee string `json:"fee"`
FeeToken string `json:"feeToken"`
Hash string `json:"hash"`
Oid int64 `json:"oid"`
Px string `json:"px"`
Side string `json:"side"`
StartPosition string `json:"startPosition"`
Sz string `json:"sz"`
Tid int64 `json:"tid"`
Time int64 `json:"time"`
TwapId interface{} `json:"twapId"`
Cloid string `json:"cloid,omitempty"`
}
// Trade data structure
type TradeData struct {
BlockNumber int64 `json:"block_number"`
BlockTime string `json:"block_time"`
Events [][]interface{} `json:"events"`
LocalTime string `json:"local_time"`
}
// Create gRPC connection with x-token authentication
func createConnection(endpoint, token string) (*grpc.ClientConn, error) {
target := endpoint + ":10000"
conn, err := grpc.Dial(
target,
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),
grpc.WithPerRPCCredentials(xTokenAuth{token: token}),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(100*1024*1024), // 100MB
),
)
return conn, err
}
// Test connectivity
func pingTest(client pb.StreamingClient) error {
resp, err := client.Ping(context.Background(), &pb.PingRequest{Count: 1})
if err != nil {
return fmt.Errorf("ping failed: %v", err)
}
log.Printf("✅ Ping successful: %+v", resp)
return nil
}
// Parse and display trade events in a readable format
func displayTrade(data *TradeData) {
blockTime, err := time.Parse(time.RFC3339Nano, data.BlockTime)
if err != nil {
blockTime = time.Now()
}
fmt.Printf("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
fmt.Printf("📦 Block %d | %s\n", data.BlockNumber, blockTime.Format("15:04:05.000"))
fmt.Printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
for _, event := range data.Events {
if len(event) < 2 {
continue
}
wallet, ok := event[0].(string)
if !ok {
continue
}
tradeDataBytes, err := json.Marshal(event[1])
if err != nil {
continue
}
var trade TradeEvent
if err := json.Unmarshal(tradeDataBytes, &trade); err != nil {
continue
}
tradeTime := time.Unix(trade.Time/1000, (trade.Time%1000)*1000000)
sideEmoji := "🟢"
sideName := "BUY "
if trade.Side == "A" {
sideEmoji = "🔴"
sideName = "SELL"
}
dirEmoji := "📈"
if trade.Dir == "Open Short" || trade.Dir == "Close Long" {
dirEmoji = "📉"
}
fmt.Printf("\n%s %s %s %-12s | %s @ $%s | Size: %s\n",
sideEmoji,
sideName,
dirEmoji,
trade.Dir,
trade.Coin,
trade.Px,
trade.Sz,
)
fmt.Printf(" 💳 Wallet: %s\n", wallet)
fmt.Printf(" 📊 Position: %s | Fee: %s %s | Crossed: %v",
trade.StartPosition,
trade.Fee,
trade.FeeToken,
trade.Crossed,
)
if trade.TwapId != nil {
fmt.Printf(" | TWAP")
}
fmt.Printf(" | Time: %s\n", tradeTime.Format("15:04:05"))
}
}
// Stream trades with improved output
func streamTrades(client pb.StreamingClient, coins []string) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
stream, err := client.StreamData(ctx)
if err != nil {
return fmt.Errorf("failed to create stream: %v", err)
}
coinFilter := &pb.FilterValues{
Values: coins,
}
filters := map[string]*pb.FilterValues{
"coin": coinFilter,
}
subscribeReq := &pb.SubscribeRequest{
Request: &pb.SubscribeRequest_Subscribe{
Subscribe: &pb.StreamSubscribe{
StreamType: pb.StreamType_TRADES,
StartBlock: 0,
Filters: filters,
FilterName: "coin-filter",
},
},
}
if err := stream.Send(subscribeReq); err != nil {
return fmt.Errorf("failed to send subscription: %v", err)
}
log.Printf("🔥 Subscribed to TRADES stream")
log.Printf("📊 Monitoring coins: %v", coins)
log.Println("\n⏳ Waiting for trade data...")
go func() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
pingReq := &pb.SubscribeRequest{
Request: &pb.SubscribeRequest_Ping{
Ping: &pb.Ping{
Timestamp: time.Now().Unix(),
},
},
}
if err := stream.Send(pingReq); err != nil {
return
}
}
}
}()
messageCount := 0
for {
resp, err := stream.Recv()
if err == io.EOF {
log.Println("Stream closed by server")
break
}
if err != nil {
return fmt.Errorf("failed to receive: %v", err)
}
switch update := resp.Update.(type) {
case *pb.SubscribeUpdate_Data:
messageCount++
var tradeData TradeData
if err := json.Unmarshal([]byte(update.Data.Data), &tradeData); err == nil {
displayTrade(&tradeData)
} else {
log.Printf("Error parsing trade data: %v", err)
}
case *pb.SubscribeUpdate_Pong:
log.Printf("\n🏓 Connection alive (pong: %d)\n", update.Pong.Timestamp)
}
}
return nil
}
func main() {
if err := godotenv.Load("../.env"); err != nil {
log.Fatalf("❌ Error loading ../.env file: %v", err)
}
grpcEndpoint := os.Getenv("qn_hyper_mainnet_grpc")
authToken := os.Getenv("grpc_token")
if grpcEndpoint == "" || authToken == "" {
log.Fatal("❌ Missing required environment variables")
}
fmt.Printf("🔗 Connecting to: %s:10000\n", grpcEndpoint)
conn, err := createConnection(grpcEndpoint, authToken)
if err != nil {
log.Fatalf("❌ Failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewStreamingClient(conn)
if err := pingTest(client); err != nil {
log.Fatalf("❌ Ping test failed: %v", err)
}
fmt.Println("✅ Connection test completed successfully!")
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
coins := []string{"BTC", "ETH", "SOL", "HYPE"}
go func() {
if err := streamTrades(client, coins); err != nil {
log.Printf("❌ Stream error: %v", err)
}
}()
<-sigChan
fmt.Println("\n\n🛑 Shutting down gracefully...")
}
This example requires the godotenv package for loading environment variables:
go get github.com/joho/godotenv
建立一個 .env file with your credentials:
qn_hyper_mainnet_grpc=your-grpc-endpoint
grpc_token=your-token
When you run this example, you'll see formatted output like:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📦 Block 123456 | 14:30:45.123
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🟢 BUY 📈 Open Long | BTC @ $45000.50 | Size: 0.1
💳 Wallet: 0x123...
📊 Position: 0 | Fee: 0.02 USDC | Crossed: true | Time: 14:30:45
我們 ❤️ 您的回饋!
如果您對這份文件有任何意見或疑問,請告訴我們。我們非常樂意聽取您的意見!