Overview
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
Authentication for Go Requests
To securely access Hyperliquid gRPC, authentication is required. Quicknode endpoints consist of two components: endpoint name and token. 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
The getClientWithBasicAuth function demonstrates how to handle authentication using Basic Authentication. It encodes the credentials in base64 and adds them to the authorization header.
Implementation
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
}
Usage
conn, err := getClientWithBasicAuth("ENDPOINT_NAME", "TOKEN")
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
x-token Authentication
The getClientWithXToken function demonstrates how to authenticate using an x-token. This method attaches the token to the x-token header of each request.
Implementation
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
}
Usage
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;
}
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
The 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
Create a .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
We ❤️ Feedback!
If you have any feedback or questions about this documentation, let us know. We'd love to hear from you!