概覽
Establishes a bidirectional streaming connection to receive real-time data updates from Hyperliquid. StreamData is the primary method for subscribing to trades, orders, events, and other blockchain data streams.
Key capabilities:
- Subscribe to multiple stream types (trades, orders, events, blocks, and more)
- Filter data by coin, user, event type, and other fields to reduce bandwidth
- Use named filters for complex OR logic across multiple filter sets
- Start from a specific block number for historical data replay
- Maintain connection with periodic ping/pong messages
This method is metered based on data consumption at 0.1 MB = 10 API credits. Concurrent stream caps and filter limits vary by plan - see the Streaming Limits table.
請求
The client sends SubscribeRequest messages with one of two variants:
- StreamSubscribe - Subscribe to a stream with optional filters
- Ping - Keep-alive message with
時間戳記(int64) to maintain connection
Subscribe Parameters
| Parameter | 類型 | Required | 說明 |
|---|---|---|---|
| stream_type | enum | 是的 | Stream type to subscribe to (see below) |
| filters | map | 不 | Field filters - key = field name, value = list of allowed values |
| start_block | uint64 | 不 | Block number to start from (omit for latest) |
| filter_name | 字串 | 不 | Name for this filter set (enables OR logic across subscriptions) |
Stream Types
TRADESExecuted trades with price, size, side, and maker/taker info
ORDERSOrder lifecycle events (open, filled, canceled, triggered)
BOOK_UPDATESOrder book changes with bid/ask price level updates
TWAPTWAP execution status and progress updates
EVENTSLedger updates, deposits, withdrawals, funding, liquidations
BLOCKSRaw HyperCore blockchain data (replica_cmds)
WRITER_ACTIONSHyperCore to HyperEVM asset transfers
MEMPOOL_TXSPending mempool transactions before block confirmation
ORDER_PRIORITYNormalized priority-fee order events from mempool and blocks
GOSSIP_PRIORITYNormalized gossip/read-priority bid events from mempool and blocks
回應
每個 SubscribeUpdate response contains one of:
- 資料 - Stream data with
block_number(uint64),時間戳記(uint64, server ingress in ms), and資料(JSON string payload) - pong - Pong response with
時間戳記(int64) echoing your ping
該 data.data payload structure varies by stream type - see Data Streams for detailed schemas.
Filtering
StreamData supports powerful filtering to reduce bandwidth and focus on specific data. Filters use AND logic across fields and OR logic within field values.
Filter by coin:
filters: {
"coin": FilterValues { values: ["BTC", "ETH"] }
}
Filter by user address:
filters: {
"user": FilterValues { values: ["0x1234...", "0x5678..."] }
}
Combine multiple filters (AND logic):
filters: {
"coin": FilterValues { values: ["BTC"] },
"side": FilterValues { values: ["B"] }
}
For complete filtering documentation including field references for each stream type, see the Stream Filtering Guide.
程式碼範例
- JavaScript
Python
- 去
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');
// Configuration
const GRPC_ENDPOINT = 'your-endpoint.hype-mainnet.quiknode.pro:10000';
const AUTH_TOKEN = 'your-auth-token';
// Load proto file
const packageDefinition = protoLoader.loadSync(
path.join(__dirname, 'proto/streaming.proto'),
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
}
);
const hyperliquidProto = grpc.loadPackageDefinition(packageDefinition).hyperliquid;
// Create gRPC client with TLS
function createClient() {
const credentials = grpc.credentials.createSsl();
const client = new hyperliquidProto.Streaming(
GRPC_ENDPOINT,
credentials,
{
'grpc.max_receive_message_length': 100 * 1024 * 1024, // 100MB
}
);
return client;
}
// Create metadata with auth token
function createMetadata() {
const metadata = new grpc.Metadata();
metadata.add('x-token', AUTH_TOKEN);
return metadata;
}
// Test connectivity with ping
function testPing(client) {
return new Promise((resolve, reject) => {
const metadata = createMetadata();
client.Ping({ count: 1 }, metadata, (error, response) => {
if (error) {
reject(new Error(`Ping failed: ${error.message}`));
return;
}
console.log('✅ Ping successful:', response);
resolve(response);
});
});
}
// Stream trades data
function streamTrades(client) {
return new Promise((resolve, reject) => {
const metadata = createMetadata();
const stream = client.StreamData(metadata);
// Handle incoming messages
stream.on('data', (response) => {
if (response.data) {
try {
const data = JSON.parse(response.data.data);
const trades = data.trades || [];
console.log(`📈 Block ${response.data.block_number}: ${trades.length} trades`);
// Pretty print first trade if available
if (trades.length > 0) {
console.log(' Example trade:', JSON.stringify(trades[0], null, 2));
}
} catch (error) {
console.log('⚠️ Failed to parse data:', error.message);
}
} else if (response.pong) {
console.log('🏓 Pong received:', response.pong.timestamp);
}
});
stream.on('error', (error) => {
console.error('❌ Stream error:', error.message);
reject(error);
});
stream.on('end', () => {
console.log('Stream ended');
resolve();
});
// Send subscription request
// Empty filters means receive all events for this stream type
stream.write({
subscribe: {
stream_type: 'TRADES',
filters: {}
}
});
// Start ping interval to keep connection alive
const pingInterval = setInterval(() => {
stream.write({
ping: {
timestamp: Date.now()
}
});
}, 30000);
// Clean up on exit
const cleanup = () => {
clearInterval(pingInterval);
stream.end();
};
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
console.log('🔄 Starting trades stream...');
});
}
// Stream orders data
function streamOrders(client) {
return new Promise((resolve, reject) => {
const metadata = createMetadata();
const stream = client.StreamData(metadata);
stream.on('data', (response) => {
if (response.data) {
try {
const data = JSON.parse(response.data.data);
console.log(`📋 Orders Block ${response.data.block_number}:`);
console.log(JSON.stringify(data, null, 2));
} catch (error) {
console.log('⚠️ Failed to parse order data:', error.message);
}
}
});
stream.on('error', (error) => {
console.error('❌ Stream error:', error.message);
reject(error);
});
stream.on('end', () => {
console.log('Orders stream ended');
resolve();
});
// Send subscription request for orders
stream.write({
subscribe: {
stream_type: 'ORDERS',
filters: {}
}
});
console.log('🔄 Starting orders stream...');
});
}
// Interactive menu
function showMenu() {
console.log('\n🎉 Connected successfully!');
console.log('\nAvailable streams:');
console.log('1. All trades');
console.log('2. Filtered trades (BTC, ETH)');
console.log('3. Orders');
console.log('4. Test ping');
console.log('5. Exit');
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question('\nSelect stream (1-5): ', (choice) => {
rl.close();
resolve(choice);
});
});
}
// Stream filtered trades
function streamFilteredTrades(client, coins) {
return new Promise((resolve, reject) => {
const metadata = createMetadata();
const stream = client.StreamData(metadata);
stream.on('data', (response) => {
if (response.data) {
try {
const data = JSON.parse(response.data.data);
const trades = data.trades || [];
if (trades.length > 0) {
console.log(`📈 Filtered Block ${response.data.block_number}: ${trades.length} trades`);
console.log(JSON.stringify(data, null, 2));
}
} catch (error) {
console.log('⚠️ Failed to parse data:', error.message);
}
}
});
stream.on('error', (error) => {
console.error('❌ Stream error:', error.message);
reject(error);
});
stream.on('end', () => {
console.log('Filtered stream ended');
resolve();
});
// Send filtered subscription
stream.write({
subscribe: {
stream_type: 'TRADES',
filters: {
coin: { values: coins }
}
}
});
console.log(`🔄 Starting filtered trades stream for coins: ${coins.join(', ')}`);
});
}
// Main function
async function main() {
try {
console.log(`Testing connection to: ${GRPC_ENDPOINT}`);
// Create client
const client = createClient();
// Test initial connectivity
await testPing(client);
// Show interactive menu
while (true) {
const choice = await showMenu();
switch (choice) {
case '1':
await streamTrades(client);
break;
case '2':
await streamFilteredTrades(client, ['BTC', 'ETH']);
break;
case '3':
await streamOrders(client);
break;
case '4':
await testPing(client);
break;
case '5':
console.log('👋 Goodbye!');
client.close();
return;
default:
console.log('❌ Invalid choice. Please select 1-5.');
}
}
} catch (error) {
console.error('❌ Error:', error.message);
process.exit(1);
}
}
// Run the application
main();
import grpc
import json
import time
import signal
import sys
import threading
from pb import streaming_pb2, streaming_pb2_grpc
# Configuration
GRPC_ENDPOINT = 'your-endpoint.hype-mainnet.quiknode.pro:10000'
AUTH_TOKEN = 'your-auth-token'
def create_channel():
"""Create gRPC channel with TLS"""
credentials = grpc.ssl_channel_credentials()
channel = grpc.secure_channel(
GRPC_ENDPOINT,
credentials,
options=[
('grpc.max_receive_message_length', 100 * 1024 * 1024), # 100MB
('grpc.max_send_message_length', 100 * 1024 * 1024), # 100MB
]
)
return channel
def create_metadata():
"""Create metadata with auth token"""
return [('x-token', AUTH_TOKEN)]
def test_ping(stub):
"""Test connectivity with ping"""
try:
metadata = create_metadata()
request = streaming_pb2.PingRequest(count=1)
response = stub.Ping(request, metadata=metadata)
print(f"✅ Ping successful: {response}")
return True
except grpc.RpcError as e:
print(f"❌ Ping failed: {e.details()}")
return False
def stream_trades(stub):
"""Stream trades data"""
try:
metadata = create_metadata()
print("🔄 Starting trades stream...")
# Create request generator for bidirectional stream
def request_generator():
# Send initial subscription request
subscribe_request = streaming_pb2.SubscribeRequest()
subscribe_request.subscribe.stream_type = streaming_pb2.StreamType.TRADES
# An empty filters map means receive all events for this stream type
yield subscribe_request
# Keep sending pings to maintain connection
while True:
time.sleep(30)
ping_request = streaming_pb2.SubscribeRequest()
ping_request.ping.timestamp = int(time.time() * 1000)
yield ping_request
# Create bidirectional stream
stream = stub.StreamData(request_generator(), metadata=metadata)
# Handle stream responses
for response in stream:
try:
if response.HasField('data'):
data = json.loads(response.data.data)
trades = data.get('trades', [])
print(f"📈 Block {response.data.block_number}: {len(trades)} trades")
# Pretty print first trade if available
if trades:
print(f" Example trade: {json.dumps(trades[0], indent=2)}")
elif response.HasField('pong'):
print(f"🏓 Pong received: {response.pong.timestamp}")
except json.JSONDecodeError as e:
print(f"⚠️ Failed to parse data: {e}")
except KeyboardInterrupt:
print("\n⏹️ Stream interrupted by user")
break
except grpc.RpcError as e:
print(f"❌ Stream error: {e.details()}")
except KeyboardInterrupt:
print("\n⏹️ Stream interrupted by user")
def stream_orders(stub):
"""Stream orders data"""
try:
metadata = create_metadata()
print("🔄 Starting orders stream...")
# Create request generator for bidirectional stream
def request_generator():
# Send initial subscription request for orders
subscribe_request = streaming_pb2.SubscribeRequest()
subscribe_request.subscribe.stream_type = streaming_pb2.StreamType.ORDERS
yield subscribe_request
# Keep sending pings to maintain connection
while True:
time.sleep(30)
ping_request = streaming_pb2.SubscribeRequest()
ping_request.ping.timestamp = int(time.time() * 1000)
yield ping_request
# Create bidirectional stream
stream = stub.StreamData(request_generator(), metadata=metadata)
# Handle stream responses
for response in stream:
try:
if response.HasField('data'):
data = json.loads(response.data.data)
print(f"📋 Orders Block {response.data.block_number}:")
print(json.dumps(data, indent=2))
except json.JSONDecodeError as e:
print(f"⚠️ Failed to parse order data: {e}")
except KeyboardInterrupt:
print("\n⏹️ Stream interrupted by user")
break
except grpc.RpcError as e:
print(f"❌ Stream error: {e.details()}")
except KeyboardInterrupt:
print("\n⏹️ Stream interrupted by user")
def stream_filtered_trades(stub, coins):
"""Stream filtered trades for specific coins"""
try:
metadata = create_metadata()
print(f"🔄 Starting filtered trades stream for coins: {', '.join(coins)}")
# Create request generator for bidirectional stream
def request_generator():
# Send filtered subscription request
subscribe_request = streaming_pb2.SubscribeRequest()
subscribe_request.subscribe.stream_type = streaming_pb2.StreamType.TRADES
subscribe_request.subscribe.filters["coin"].values.extend(coins)
yield subscribe_request
# Keep sending pings to maintain connection
while True:
time.sleep(30)
ping_request = streaming_pb2.SubscribeRequest()
ping_request.ping.timestamp = int(time.time() * 1000)
yield ping_request
# Create bidirectional stream
stream = stub.StreamData(request_generator(), metadata=metadata)
# Handle stream responses
for response in stream:
try:
if response.HasField('data'):
data = json.loads(response.data.data)
trades = data.get('trades', [])
if trades:
print(f"📈 Filtered Block {response.data.block_number}: {len(trades)} trades")
print(json.dumps(data, indent=2))
except json.JSONDecodeError as e:
print(f"⚠️ Failed to parse data: {e}")
except KeyboardInterrupt:
print("\n⏹️ Stream interrupted by user")
break
except grpc.RpcError as e:
print(f"❌ Stream error: {e.details()}")
except KeyboardInterrupt:
print("\n⏹️ Stream interrupted by user")
def show_menu():
"""Display interactive menu"""
print("\n🎉 Connected successfully!")
print("\nAvailable streams:")
print("1. All trades")
print("2. Filtered trades (BTC, ETH)")
print("3. Orders")
print("4. Test ping")
print("5. Exit")
while True:
try:
choice = input("\nSelect stream (1-5): ").strip()
if choice in ['1', '2', '3', '4', '5']:
return choice
else:
print("❌ Invalid choice. Please select 1-5.")
except KeyboardInterrupt:
return '5'
def main():
"""Main function"""
try:
print(f"Testing connection to: {GRPC_ENDPOINT}")
# Create channel and stub
channel = create_channel()
streaming_stub = streaming_pb2_grpc.StreamingStub(channel)
# Test initial connectivity
if not test_ping(streaming_stub):
print("❌ Connection test failed!")
return
# Interactive menu loop
while True:
choice = show_menu()
if choice == '1':
stream_trades(streaming_stub)
elif choice == '2':
stream_filtered_trades(streaming_stub, ['BTC', 'ETH'])
elif choice == '3':
stream_orders(streaming_stub)
elif choice == '4':
test_ping(streaming_stub)
elif choice == '5':
print("👋 Goodbye!")
break
# Close channel
channel.close()
except KeyboardInterrupt:
print("\n👋 Goodbye!")
except Exception as e:
print(f"❌ Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
package main
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
pb "hyperliquid-grpc-go/pb"
)
const (
// Using Quicknode endpoint
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, // Set to true only for testing
}
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)
}
// Stream trades
func streamTrades(client pb.StreamingClient) error {
ctx := createContext()
stream, err := client.StreamData(ctx)
if err != nil {
return fmt.Errorf("failed to create stream: %v", err)
}
// Send subscription request
// Empty Filters means receive all events for this stream type
err = stream.Send(&pb.SubscribeRequest{
Request: &pb.SubscribeRequest_Subscribe{
Subscribe: &pb.StreamSubscribe{
StreamType: pb.StreamType_TRADES,
Filters: map[string]*pb.FilterValues{},
},
},
})
if err != nil {
return fmt.Errorf("failed to subscribe: %v", err)
}
// Start ping goroutine
go func() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
err := stream.Send(&pb.SubscribeRequest{
Request: &pb.SubscribeRequest_Ping{
Ping: &pb.Ping{
Timestamp: time.Now().UnixMilli(),
},
},
})
if err != nil {
log.Printf("Failed to send ping: %v", err)
return
}
}
}()
// Receive messages
log.Println("🔄 Starting trades stream...")
for {
msg, err := stream.Recv()
if err == io.EOF {
log.Println("Stream ended")
break
}
if err != nil {
return fmt.Errorf("receive error: %v", err)
}
switch update := msg.Update.(type) {
case *pb.SubscribeUpdate_Data:
var data map[string]interface{}
if err := json.Unmarshal([]byte(update.Data.Data), &data); err != nil {
log.Printf("⚠️ Failed to parse data: %v", err)
continue
}
trades, ok := data["trades"].([]interface{})
tradesCount := 0
if ok {
tradesCount = len(trades)
}
log.Printf("📈 Block %d: %d trades", update.Data.BlockNumber, tradesCount)
// Pretty print first trade if available
if tradesCount > 0 {
firstTrade := trades[0]
tradeJSON, _ := json.MarshalIndent(firstTrade, " ", " ")
fmt.Printf(" Example trade:\n%s\n", tradeJSON)
}
case *pb.SubscribeUpdate_Pong:
log.Printf("🏓 Pong received: %d", update.Pong.Timestamp)
}
}
return nil
}
// Stream filtered trades
func streamFilteredTrades(client pb.StreamingClient, coins []string) error {
ctx := createContext()
stream, err := client.StreamData(ctx)
if err != nil {
return fmt.Errorf("failed to create stream: %v", err)
}
// Send filtered subscription
err = stream.Send(&pb.SubscribeRequest{
Request: &pb.SubscribeRequest_Subscribe{
Subscribe: &pb.StreamSubscribe{
StreamType: pb.StreamType_TRADES,
Filters: map[string]*pb.FilterValues{
"coin": {Values: coins},
},
},
},
})
if err != nil {
return fmt.Errorf("failed to subscribe: %v", err)
}
log.Printf("🔄 Starting filtered trades stream for coins: %v", coins)
// Receive messages
for {
msg, err := stream.Recv()
if err == io.EOF {
log.Println("Filtered stream ended")
break
}
if err != nil {
return fmt.Errorf("receive error: %v", err)
}
switch update := msg.Update.(type) {
case *pb.SubscribeUpdate_Data:
var data map[string]interface{}
if err := json.Unmarshal([]byte(update.Data.Data), &data); err != nil {
log.Printf("⚠️ Failed to parse data: %v", err)
continue
}
if trades, ok := data["trades"].([]interface{}); ok && len(trades) > 0 {
log.Printf("📈 Filtered Block %d: %d trades", update.Data.BlockNumber, len(trades))
prettyJSON, _ := json.MarshalIndent(data, "", " ")
fmt.Println(string(prettyJSON))
}
}
}
return nil
}
// Stream orders
func streamOrders(client pb.StreamingClient) error {
ctx := createContext()
stream, err := client.StreamData(ctx)
if err != nil {
return fmt.Errorf("failed to create stream: %v", err)
}
// Send subscription request for orders
err = stream.Send(&pb.SubscribeRequest{
Request: &pb.SubscribeRequest_Subscribe{
Subscribe: &pb.StreamSubscribe{
StreamType: pb.StreamType_ORDERS,
Filters: map[string]*pb.FilterValues{},
},
},
})
if err != nil {
return fmt.Errorf("failed to subscribe: %v", err)
}
log.Println("🔄 Starting orders stream...")
// Receive messages
for {
msg, err := stream.Recv()
if err == io.EOF {
log.Println("Orders stream ended")
break
}
if err != nil {
return fmt.Errorf("receive error: %v", err)
}
switch update := msg.Update.(type) {
case *pb.SubscribeUpdate_Data:
var data map[string]interface{}
if err := json.Unmarshal([]byte(update.Data.Data), &data); err != nil {
log.Printf("⚠️ Failed to parse order data: %v", err)
continue
}
log.Printf("📋 Orders Block %d:", update.Data.BlockNumber)
prettyJSON, _ := json.MarshalIndent(data, "", " ")
fmt.Println(string(prettyJSON))
}
}
return nil
}
// Interactive menu
func runInteractiveMenu(conn *grpc.ClientConn) {
client := pb.NewStreamingClient(conn)
fmt.Println("\n🎉 Connected successfully!")
fmt.Println("\nAvailable streams:")
fmt.Println("1. All trades")
fmt.Println("2. Filtered trades (BTC, ETH)")
fmt.Println("3. Orders")
fmt.Println("4. Exit")
for {
fmt.Print("\nSelect stream (1-4): ")
var choice string
fmt.Scanln(&choice)
switch choice {
case "1":
if err := streamTrades(client); err != nil {
log.Printf("❌ Stream error: %v", err)
}
case "2":
if err := streamFilteredTrades(client, []string{"BTC", "ETH"}); err != nil {
log.Printf("❌ Filtered stream error: %v", err)
}
case "3":
if err := streamOrders(client); err != nil {
log.Printf("❌ Orders stream error: %v", err)
}
case "4":
fmt.Println("👋 Goodbye!")
return
default:
fmt.Println("❌ Invalid choice. Please select 1-4.")
}
}
}
func main() {
// Create connection
conn, err := createConnection()
if err != nil {
log.Fatalf("❌ Failed to connect: %v", err)
}
defer conn.Close()
fmt.Printf("Connected to: %s\n", grpcEndpoint)
// Run interactive menu
runInteractiveMenu(conn)
}
Related Documentation
- Data Streams Overview - All available data streams and their structures
- Stream Filtering Guide - Complete filtering syntax and field references
- gRPC API Overview - Setup guides and proto files
- Streaming Limits - Plan-based limits for concurrent streams and filters