GetFills - Get Fill Data
Retrieve fills at a specific position from Hyperliquid L1 Gateway via gRPC. Get order execution data for analysis and reconciliation.
Retrieve fills at a specific position from the Hyperliquid L1 Gateway.
Full Code Examples
Clone our gRPC Code Examples Repository for complete, runnable implementations in Go, Python, and Node.js.
When to Use This Method
GetFills is essential for:
- Trade Reconciliation - Verify fills at a specific point in time
- Historical Analysis - Retrieve fill data for backtesting or reporting
- Auditing - Verify trade executions at specific blocks
- Debugging - Investigate specific fills during development
Method Signature
rpc GetFills(Position) returns (BlockFills) {}Request Message
message Position {
// Leave all fields unset or zero to target the latest data.
oneof position {
int64 timestamp_ms = 1; // ms since Unix epoch, inclusive
int64 block_height = 2; // block height, inclusive
}
}The Position message allows flexible fill targeting:
- timestamp_ms: Get fills at or after a specific time (milliseconds since Unix epoch)
- block_height: Get fills at a specific block height
- Empty/zero: Get the latest fills
Response Message
message BlockFills {
// JSON-encoded object from "node_fills" or "node_fills_by_block".
bytes data = 1;
}The data field contains a JSON-encoded fills object with the following structure:
Data Structure
{
"local_time": "2025-07-27T08:50:10.334741319",
"block_time": "2025-07-27T08:50:10.273720809",
"block_number": 676607012,
"events": [
[
"0x7839e2f2c375dd2935193f2736167514efff9916",
{
"coin": "BTC",
"px": "118136.0",
"sz": "0.00009",
"side": "B",
"time": 1753606210273,
"startPosition": "-1.41864",
"dir": "Close Short",
"closedPnl": "-0.003753",
"hash": "0xe7822040155eaa2e737e042854342401120052bbf063906ce8c8f3babe853a79",
"oid": 121670079265,
"crossed": false,
"fee": "-0.000212",
"tid": 161270588369408,
"cloid": "0x09367b9f8541c581f95b02aaf05f1508",
"feeToken": "USDC",
"builder": "0x49ae63056b3a0be0b166813ee687309ab653c07c",
"builderFee": "0.005528"
}
]
]
}Field Descriptions
| Field | Type | Description |
|---|---|---|
local_time | string | ISO-8601 timestamp when node processed the fill |
block_time | string | ISO-8601 timestamp from block consensus |
block_number | number | Block height containing these fills |
events | array | Array of [address, fill_data] pairs |
Fill Event Fields
| Field | Type | Description |
|---|---|---|
coin | string | Trading pair symbol (e.g., "BTC", "ETH") |
px | string | Fill price |
sz | string | Fill size |
side | string | "B" for buy, "A" for sell |
time | number | Fill timestamp in milliseconds |
startPosition | string | Position size before this fill |
dir | string | Direction: "Open Long", "Open Short", "Close Long", "Close Short" |
closedPnl | string | Realized PnL if closing position |
hash | string | Transaction hash |
oid | number | Order ID |
crossed | boolean | Whether order crossed the spread |
fee | string | Trading fee (negative = rebate) |
tid | number | Unique trade identifier |
cloid | string | Client order ID (optional) |
feeToken | string | Token used for fee |
builder | string | Builder address (optional) |
builderFee | string | Builder fee amount (optional) |
For the complete fill specification, see the StreamFills documentation.
Implementation Examples
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"time"
pb "hyperliquid-grpc-client/api/v2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
)
type FillEvent struct {
Coin string `json:"coin"`
Px string `json:"px"`
Sz string `json:"sz"`
Side string `json:"side"`
Time int64 `json:"time"`
StartPosition string `json:"startPosition"`
Dir string `json:"dir"`
ClosedPnl string `json:"closedPnl"`
Hash string `json:"hash"`
Oid int64 `json:"oid"`
Crossed bool `json:"crossed"`
Fee string `json:"fee"`
Tid int64 `json:"tid"`
Cloid string `json:"cloid,omitempty"`
FeeToken string `json:"feeToken"`
}
type FillsData struct {
LocalTime string `json:"local_time"`
BlockTime string `json:"block_time"`
BlockNumber int64 `json:"block_number"`
Events [][]interface{} `json:"events"`
}
func getFills(ctx context.Context, client pb.HyperliquidL1GatewayClient, position *pb.Position) (*FillsData, error) {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
response, err := client.GetFills(ctx, position)
if err != nil {
return nil, fmt.Errorf("RPC call failed: %v", err)
}
fmt.Printf("Response size: %d bytes\n\n", len(response.Data))
var fills FillsData
if err := json.Unmarshal(response.Data, &fills); err != nil {
return nil, fmt.Errorf("failed to parse JSON: %v", err)
}
return &fills, nil
}
func main() {
endpoint := os.Getenv("HYPERLIQUID_ENDPOINT")
apiKey := os.Getenv("API_KEY")
if endpoint == "" {
log.Fatal("Error: HYPERLIQUID_ENDPOINT environment variable is required.")
}
if apiKey == "" {
log.Fatal("Error: API_KEY environment variable is required.")
}
fmt.Println("Hyperliquid Go gRPC Client - Get Fills")
fmt.Println("======================================")
fmt.Printf("Endpoint: %s\n\n", endpoint)
creds := credentials.NewTLS(nil)
opts := []grpc.DialOption{
grpc.WithTransportCredentials(creds),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(150 * 1024 * 1024),
),
}
fmt.Println("Connecting to gRPC server...")
conn, err := grpc.NewClient(endpoint, opts...)
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewHyperliquidL1GatewayClient(conn)
fmt.Println("Connected successfully!\n")
ctx := context.Background()
ctx = metadata.AppendToOutgoingContext(ctx, "x-api-key", apiKey)
// Request latest fills (empty position)
request := &pb.Position{}
// Or request by block height:
// request := &pb.Position{Position: &pb.Position_BlockHeight{BlockHeight: 676607012}}
fmt.Println("Fetching fills...\n")
fills, err := getFills(ctx, client, request)
if err != nil {
log.Fatalf("Failed to get fills: %v", err)
}
// Display fills information
fmt.Printf("Block Number: %d\n", fills.BlockNumber)
fmt.Printf("Block Time: %s\n", fills.BlockTime)
fmt.Printf("Local Time: %s\n", fills.LocalTime)
fmt.Printf("Fill Events: %d\n\n", len(fills.Events))
// Process each fill event
for i, event := range fills.Events {
if len(event) >= 2 {
address := event[0].(string)
fillData, _ := json.Marshal(event[1])
var fill FillEvent
json.Unmarshal(fillData, &fill)
fmt.Printf("Fill #%d:\n", i+1)
fmt.Printf(" Address: %s\n", address)
fmt.Printf(" Coin: %s\n", fill.Coin)
fmt.Printf(" Side: %s\n", fill.Side)
fmt.Printf(" Price: %s\n", fill.Px)
fmt.Printf(" Size: %s\n", fill.Sz)
fmt.Printf(" Direction: %s\n", fill.Dir)
fmt.Printf(" Fee: %s %s\n", fill.Fee, fill.FeeToken)
fmt.Println()
}
}
}import grpc
import json
import sys
import os
from datetime import datetime
from dotenv import load_dotenv
import hyperliquid_pb2
import hyperliquid_pb2_grpc
load_dotenv()
def get_fills(block_height=None, timestamp_ms=None):
endpoint = os.getenv('HYPERLIQUID_ENDPOINT')
api_key = os.getenv('API_KEY')
if not endpoint:
print("Error: HYPERLIQUID_ENDPOINT environment variable is required.")
sys.exit(1)
if not api_key:
print("Error: API_KEY environment variable is required.")
sys.exit(1)
print('Hyperliquid Python gRPC Client - Get Fills')
print('==========================================')
print(f'Endpoint: {endpoint}\n')
credentials = grpc.ssl_channel_credentials()
options = [
('grpc.max_receive_message_length', 150 * 1024 * 1024),
]
metadata = [('x-api-key', api_key)]
print('Connecting to gRPC server...')
with grpc.secure_channel(endpoint, credentials, options=options) as channel:
client = hyperliquid_pb2_grpc.HyperliquidL1GatewayStub(channel)
print('Connected successfully!\n')
# Create request based on parameters
if block_height is not None:
request = hyperliquid_pb2.Position(block_height=block_height)
print(f'Requesting fills at block height: {block_height}')
elif timestamp_ms is not None:
request = hyperliquid_pb2.Position(timestamp_ms=timestamp_ms)
print(f'Requesting fills at timestamp: {timestamp_ms}')
else:
request = hyperliquid_pb2.Position()
print('Requesting latest fills')
print('\nFetching fills...\n')
try:
response = client.GetFills(request, metadata=metadata)
print(f'Response size: {len(response.data)} bytes\n')
process_fills(response.data)
except grpc.RpcError as e:
print(f'RPC error: {e}')
except Exception as e:
print(f'Error: {e}')
def process_fills(data):
try:
fills = json.loads(data.decode('utf-8'))
print('FILLS DATA')
print('==========')
print(f'Block Number: {fills.get("block_number")}')
print(f'Block Time: {fills.get("block_time")}')
print(f'Local Time: {fills.get("local_time")}')
events = fills.get('events', [])
print(f'Fill Events: {len(events)}\n')
# Aggregate statistics
total_volume = 0
total_pnl = 0
coins_traded = set()
buy_count = 0
sell_count = 0
for i, event in enumerate(events):
if len(event) >= 2:
address = event[0]
fill = event[1]
coins_traded.add(fill.get('coin', ''))
size = float(fill.get('sz', 0))
price = float(fill.get('px', 0))
total_volume += size * price
pnl = float(fill.get('closedPnl', 0))
total_pnl += pnl
if fill.get('side') == 'B':
buy_count += 1
else:
sell_count += 1
# Display first 5 fills in detail
if i < 5:
print(f'Fill #{i + 1}:')
print(f' Address: {address}')
print(f' Coin: {fill.get("coin")}')
print(f' Side: {"Buy" if fill.get("side") == "B" else "Sell"}')
print(f' Price: {fill.get("px")}')
print(f' Size: {fill.get("sz")}')
print(f' Direction: {fill.get("dir")}')
print(f' Closed PnL: {fill.get("closedPnl")}')
print(f' Fee: {fill.get("fee")} {fill.get("feeToken")}')
print()
if len(events) > 5:
print(f'... and {len(events) - 5} more fills\n')
# Summary
print('SUMMARY')
print('=======')
print(f'Total Fills: {len(events)}')
print(f'Buys: {buy_count}, Sells: {sell_count}')
print(f'Coins Traded: {", ".join(coins_traded)}')
print(f'Total Volume: ${total_volume:,.2f}')
print(f'Total Realized PnL: ${total_pnl:,.4f}')
except json.JSONDecodeError as e:
print(f'Failed to parse JSON: {e}')
except Exception as e:
print(f'Error processing fills: {e}')
if __name__ == '__main__':
# Get latest fills
get_fills()
# Or get by block height:
# get_fills(block_height=676607012)
# Or get by timestamp:
# get_fills(timestamp_ms=1753606210273)const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');
require('dotenv').config();
const PROTO_PATH = path.join(__dirname, 'v2.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const proto = grpc.loadPackageDefinition(packageDefinition);
async function getFills(options = {}) {
const endpoint = process.env.HYPERLIQUID_ENDPOINT;
const apiKey = process.env.API_KEY;
if (!endpoint) {
console.error('Error: HYPERLIQUID_ENDPOINT environment variable is required.');
process.exit(1);
}
if (!apiKey) {
console.error('Error: API_KEY environment variable is required.');
process.exit(1);
}
console.log('Hyperliquid Node.js gRPC Client - Get Fills');
console.log('===========================================');
console.log(`Endpoint: ${endpoint}\n`);
const metadata = new grpc.Metadata();
metadata.add('x-api-key', apiKey);
const client = new proto.hyperliquid_l1_gateway.v2.HyperliquidL1Gateway(
endpoint,
grpc.credentials.createSsl(),
{
'grpc.max_receive_message_length': 150 * 1024 * 1024
}
);
// Build request
const request = {};
if (options.blockHeight) {
request.block_height = options.blockHeight;
console.log(`Requesting fills at block height: ${options.blockHeight}`);
} else if (options.timestampMs) {
request.timestamp_ms = options.timestampMs;
console.log(`Requesting fills at timestamp: ${options.timestampMs}`);
} else {
console.log('Requesting latest fills');
}
console.log('\nFetching fills...\n');
client.GetFills(request, metadata, (error, response) => {
if (error) {
console.error('gRPC error:', error.message);
return;
}
try {
const fills = JSON.parse(response.data);
console.log(`Response size: ${response.data.length} bytes\n`);
processFills(fills);
} catch (error) {
console.error('Failed to parse response:', error.message);
}
});
}
function processFills(fills) {
console.log('FILLS DATA');
console.log('==========');
console.log(`Block Number: ${fills.block_number}`);
console.log(`Block Time: ${fills.block_time}`);
console.log(`Local Time: ${fills.local_time}`);
const events = fills.events || [];
console.log(`Fill Events: ${events.length}\n`);
// Aggregate statistics
let totalVolume = 0;
let totalPnl = 0;
const coinsTraded = new Set();
let buyCount = 0;
let sellCount = 0;
for (let i = 0; i < events.length; i++) {
const event = events[i];
if (event.length >= 2) {
const address = event[0];
const fill = event[1];
coinsTraded.add(fill.coin || '');
const size = parseFloat(fill.sz || 0);
const price = parseFloat(fill.px || 0);
totalVolume += size * price;
const pnl = parseFloat(fill.closedPnl || 0);
totalPnl += pnl;
if (fill.side === 'B') {
buyCount++;
} else {
sellCount++;
}
// Display first 5 fills in detail
if (i < 5) {
console.log(`Fill #${i + 1}:`);
console.log(` Address: ${address}`);
console.log(` Coin: ${fill.coin}`);
console.log(` Side: ${fill.side === 'B' ? 'Buy' : 'Sell'}`);
console.log(` Price: ${fill.px}`);
console.log(` Size: ${fill.sz}`);
console.log(` Direction: ${fill.dir}`);
console.log(` Closed PnL: ${fill.closedPnl}`);
console.log(` Fee: ${fill.fee} ${fill.feeToken}`);
console.log();
}
}
}
if (events.length > 5) {
console.log(`... and ${events.length - 5} more fills\n`);
}
// Summary
console.log('SUMMARY');
console.log('=======');
console.log(`Total Fills: ${events.length}`);
console.log(`Buys: ${buyCount}, Sells: ${sellCount}`);
console.log(`Coins Traded: ${[...coinsTraded].join(', ')}`);
console.log(`Total Volume: $${totalVolume.toLocaleString(undefined, { minimumFractionDigits: 2 })}`);
console.log(`Total Realized PnL: $${totalPnl.toFixed(4)}`);
}
// Get latest fills
getFills();
// Or get by block height:
// getFills({ blockHeight: 676607012 });
// Or get by timestamp:
// getFills({ timestampMs: 1753606210273 });Common Use Cases
1. PnL Reconciliation
def reconcile_pnl(client, metadata, start_block, end_block):
"""Calculate total PnL between two blocks"""
pnl_by_address = {}
for block_height in range(start_block, end_block + 1):
request = hyperliquid_pb2.Position(block_height=block_height)
response = client.GetFills(request, metadata=metadata)
fills = json.loads(response.data)
for event in fills.get('events', []):
if len(event) >= 2:
address = event[0]
fill = event[1]
pnl = float(fill.get('closedPnl', 0))
if address not in pnl_by_address:
pnl_by_address[address] = 0
pnl_by_address[address] += pnl
return pnl_by_address2. Fill Verification
func verifyFill(client pb.HyperliquidL1GatewayClient, ctx context.Context, blockHeight int64, traderId string) []map[string]interface{} {
request := &pb.Position{Position: &pb.Position_BlockHeight{BlockHeight: blockHeight}}
response, _ := client.GetFills(ctx, request)
var fills map[string]interface{}
json.Unmarshal(response.Data, &fills)
var traderFills []map[string]interface{}
events := fills["events"].([]interface{})
for _, event := range events {
eventArr := event.([]interface{})
if len(eventArr) >= 2 {
address := eventArr[0].(string)
if address == traderId {
fillData := eventArr[1].(map[string]interface{})
traderFills = append(traderFills, fillData)
}
}
}
return traderFills
}3. Volume Analysis
function analyzeVolume(fills) {
const volumeByCoin = {};
const events = fills.events || [];
for (const event of events) {
if (event.length >= 2) {
const fill = event[1];
const coin = fill.coin;
const volume = parseFloat(fill.sz) * parseFloat(fill.px);
if (!volumeByCoin[coin]) {
volumeByCoin[coin] = {
totalVolume: 0,
buyVolume: 0,
sellVolume: 0,
fillCount: 0
};
}
volumeByCoin[coin].totalVolume += volume;
volumeByCoin[coin].fillCount++;
if (fill.side === 'B') {
volumeByCoin[coin].buyVolume += volume;
} else {
volumeByCoin[coin].sellVolume += volume;
}
}
}
return volumeByCoin;
}Error Handling
async function robustGetFills(client, metadata, position, retries = 3) {
for (let attempt = 0; attempt < retries; attempt++) {
try {
return await new Promise((resolve, reject) => {
client.GetFills(position, metadata, (error, response) => {
if (error) reject(error);
else resolve(response);
});
});
} catch (error) {
console.warn(`Attempt ${attempt + 1} failed:`, error.message);
if (attempt === retries - 1) {
throw error;
}
// Exponential backoff
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
}
}
}Best Practices
- Position Selection: Use block height for precise queries; timestamps may return different fills across requests
- Data Validation: Always validate JSON structure and handle optional fields
- Deduplication: Use
tid(trade ID) as the unique identifier for deduplication - Error Recovery: Implement retry logic with exponential backoff
- Resource Management: Close gRPC connections properly to avoid resource leaks
Current Limitations
- Data Retention: Historical fill data is limited to a 24-hour rolling window
- Rate Limits: Be mindful of request frequency to avoid overwhelming the service
Resources
- GitHub: gRPC Code Examples - Complete working examples
- StreamFills Documentation - Full fill specification and streaming examples
- Copy Trading Bot - Production-ready example using fill data
Need help? Contact our support team or check the Hyperliquid gRPC documentation.
GetBlock - Get Block Data
Retrieve a single block from Hyperliquid L1 Gateway via gRPC. Get blockchain data at a specific position.
GetOrderBookSnapshot - Get Order Book Data
Retrieve a full order book snapshot with individual order visibility from Hyperliquid L1 Gateway via gRPC. Premium endpoint available on dedicated nodes.