Skip to main content

GetFills

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#

FieldTypeDescription
local_timestringISO-8601 timestamp when node processed the fill
block_timestringISO-8601 timestamp from block consensus
block_numbernumberBlock height containing these fills
eventsarrayArray of [address, fill_data] pairs

Fill Event Fields#

FieldTypeDescription
coinstringTrading pair symbol (e.g., "BTC", "ETH")
pxstringFill price
szstringFill size
sidestring"B" for buy, "A" for sell
timenumberFill timestamp in milliseconds
startPositionstringPosition size before this fill
dirstringDirection: "Open Long", "Open Short", "Close Long", "Close Short"
closedPnlstringRealized PnL if closing position
hashstringTransaction hash
oidnumberOrder ID
crossedbooleanWhether order crossed the spread
feestringTrading fee (negative = rebate)
tidnumberUnique trade identifier
cloidstringClient order ID (optional)
feeTokenstringToken used for fee
builderstringBuilder address (optional)
builderFeestringBuilder 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()
}
}
}

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_address

2. 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#

  1. Position Selection: Use block height for precise queries; timestamps may return different fills across requests
  2. Data Validation: Always validate JSON structure and handle optional fields
  3. Deduplication: Use tid (trade ID) as the unique identifier for deduplication
  4. Error Recovery: Implement retry logic with exponential backoff
  5. 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#

Need help? Contact our support team or check the Hyperliquid gRPC documentation.