Skip to main content

Making Hyperliquid gRPC Requests with Go

Updated on
Feb 24, 2026

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
OrderBook Streaming

For streaming L2/L4 orderbook data (StreamL2Book, StreamL4Book), see Go OrderBook Setup Guide

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:

  1. Basic Authentication
  2. 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!

Share this doc