StreamFills - Real-time Fill Streaming
Stream continuous fill data from Hyperliquid L1 Gateway via gRPC. Monitor order executions in real-time.
Stream continuous fill data starting from a position, providing real-time access to order executions on Hyperliquid.
Full Code Examples
Clone our gRPC Code Examples Repository for complete, runnable implementations. See the copy-trading-bot for a production-ready example using StreamFills.
When to Use This Method
StreamFills is essential for:
- Trade Monitoring - Track order executions in real-time
- Position Management - Monitor fills for active trading strategies
- Settlement Tracking - Verify order completions and partial fills
- Analytics & Reporting - Collect comprehensive trade execution data
Method signature
rpc StreamFills(StreamRequest) returns (stream Record) {}V3 accepts an inclusive position.block_number or Unix-millisecond position.timestamp. Explicit zero is invalid. A positioned call replays from the selected record and then tails live; an empty request tails newly written fills.
position: {
block_number: 1058646974
}
filter: {
fields: {
field: "users"
op: FILTER_OP_IN
values: "0x1111111111111111111111111111111111111111"
}
fields: {
field: "coins"
op: FILTER_OP_IN
values: "BTC"
}
}Fills accept users and coins filters. User matching is case-insensitive, coin matching is case-sensitive, values within a field use OR semantics, and the two fields use AND semantics. Stream records with no matching events are skipped. Each returned Record contains optional block_number and timestamp cursors plus the filtered block envelope in data. A pruned position returns NOT_FOUND; malformed filters return INVALID_ARGUMENT.
Response Stream
V3 carries the JSON-encoded fills envelope in Record.data.
Decode the data field to access:
- Fill execution details (price, size, side)
- Order identifiers (order ID, client order ID)
- Counterparty information
- Timestamp and block reference
Full Fill Spec
StreamFills emits a JSON payload matching Hyperliquid's node_fills format. Below is the exact structure.
Top-level keys (always present):
{
"local_time": "2025-07-27T08:50:10.334741319", // ISO-8601 timestamp when node processed fill (nanosecond precision)
"block_time": "2025-07-27T08:50:10.273720809", // ISO-8601 timestamp from block consensus
"block_number": 676607012, // Block height containing this fill
"events": [ // Array of [address, fill_data] pairs
[
"0x7839e2f2c375dd2935193f2736167514efff9916", // User address (40 hex chars, lowercase)
{
"coin": "BTC", // Trading pair symbol
"px": "118136.0", // Fill price (string)
"sz": "0.00009", // Fill size (string)
"side": "B", // "B" (buy) or "A" (sell/ask)
"time": 1753606210273, // Fill timestamp (ms since Unix epoch)
"startPosition": "-1.41864", // Position size before fill (string)
"dir": "Close Short", // Direction: "Open Long" | "Open Short" | "Close Long" | "Close Short"
"closedPnl": "-0.003753", // Realized PnL from closing position (string, can be negative)
"hash": "0xe7822040155eaa2e737e042854342401120052bbf063906ce8c8f3babe853a79", // Transaction hash (64 hex)
"oid": 121670079265, // Order ID (numeric)
"crossed": false, // Whether order crossed the spread
"fee": "-0.000212", // Trading fee (string, negative = rebate)
"tid": 161270588369408, // Trade ID; not globally unique
"cloid": "0x09367b9f8541c581f95b02aaf05f1508", // Client order ID (optional, 32 hex)
"feeToken": "USDC", // Token used for fee payment
"twapId": null, // TWAP order ID (optional, null if not a TWAP fill)
"liquidation": null, // Liquidation details (optional, null for normal fills)
"builder": "0x49ae63056b3a0be0b166813ee687309ab653c07c", // Builder address (optional)
"builderFee": "0.005528" // Builder fee amount (optional, string)
}
]
// ... more [address, fill_data] pairs
]
}Fill event entry (events[i]):
[
"0x...", // user_address - the trader's address
{
// Fill details object
}
]Fill details fields:
| Field | Type | Description |
|---|---|---|
coin | string | Trading pair symbol (e.g., "BTC", "ETH", "SOL") |
px | string | Fill price |
sz | string | Fill size |
side | string | "B" for buy, "A" for sell |
time | number | Fill timestamp in milliseconds since Unix epoch |
startPosition | string | Position size before this fill |
dir | string | Position direction: "Open Long", "Open Short", "Close Long", "Close Short" |
closedPnl | string | Realized PnL if closing a position (can be negative) |
hash | string | Transaction hash (64 hex characters) |
oid | number | Order ID assigned by the system |
crossed | boolean | Maker/taker indicator. true = taker (crossed the spread), false = maker (resting order). See Maker vs Taker |
fee | string | Trading fee (negative values indicate rebates) |
tid | number | Trade ID; not globally unique, so group matches by at least (coin, tid) |
cloid | string | Client order ID if provided (optional) |
feeToken | string | Token used for fee payment |
twapId | number/null | TWAP order ID if the fill is part of a TWAP execution (optional) |
liquidation | object/null | Liquidation details, present only when the fill is part of a forced position closure. See Liquidation Data |
builder | string | Builder address if order was routed through a builder (optional) |
builderFee | string | Fee paid to builder (optional) |
Guarantees and alignment:
eventsarray contains all fills from the block for all users.- Each event pairs a user address with their fill details.
block_numberis sequential and does not correspond to the potentially skippingabci_block.roundconsensus value in StreamBlocks.block_timealigns withabci_block.timein StreamBlocks.- Multiple fills per block are delivered in a single message.
Developer tips:
- Group fills belonging to one match by at least
(coin, tid). If you deduplicate individual fill records, extend that key with the user or order identity so multi-maker fills are preserved. - Track
startPositionanddirto reconstruct position changes. - Sum
closedPnlacross fills to calculate realized PnL. - Normalize
px,sz,fee, andclosedPnl(strings) to numeric types as appropriate. - Handle optional fields (
cloid,twapId,liquidation,builder,builderFee) defensively. - Check
liquidationto detect forced position closures. See Liquidation Data. - Use
crossedto determine maker/taker status. See Maker vs Taker.
Liquidation Data
Liquidation events are embedded within the fills stream. When a fill is part of a forced position closure, the liquidation field is present on the fill object.
A liquidation match includes:
- One liquidated-side fill for the forced position close
- One or more maker/counterparty fills for the resting liquidity that absorbed it
Multiple makers can participate in one match. Group its fills by at least (coin, tid); tid is not globally unique, and a transaction hash can contain more than one match. To identify the actual liquidated user rather than a counterparty, check that:
liquidationis present on the filldircontains"Close"(e.g.,"Close Long","Close Short")- The user address matches
liquidation.liquidatedUser
Liquidation Object Fields
| Field | Type | Description |
|---|---|---|
liquidatedUser | string | Address of the user being liquidated |
markPx | string | Mark price at the time of liquidation |
method | string | Liquidation method: "market" or "backstop" |
Liquidation Methods
| Method | Description |
|---|---|
market | Standard liquidation filled against resting orders on the book |
backstop | Backstop liquidation used when the order book cannot absorb the position |
Example: Single-maker liquidation fill
This example contains one liquidated-side fill and one maker fill. A multi-maker liquidation contains additional maker fills with the same (coin, tid) match key.
{
"block_number": 859864054,
"block_time": "2026-04-16T14:22:31.456000Z",
"events": [
[
"0x7a3b1c9d2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b",
{
"coin": "BTC",
"px": "103850.0",
"sz": "0.0234",
"side": "A",
"time": 1744813351456,
"startPosition": "0.0234",
"dir": "Close Long",
"closedPnl": "-31.599",
"hash": "0xab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12",
"oid": 199445678901,
"crossed": true,
"fee": "0.073107",
"tid": 884916789012345,
"cloid": null,
"feeToken": "USDC",
"twapId": null,
"liquidation": {
"liquidatedUser": "0x7a3b1c9d2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b",
"markPx": "103842.5",
"method": "market"
}
}
],
[
"0x1234abcd5678ef901234abcd5678ef901234abcd",
{
"coin": "BTC",
"px": "103850.0",
"sz": "0.0234",
"side": "B",
"time": 1744813351456,
"startPosition": "-0.125",
"dir": "Close Short",
"closedPnl": "2.457",
"hash": "0xab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12",
"oid": 199445678902,
"crossed": false,
"fee": "-0.007311",
"tid": 884916789012345,
"cloid": "0x00000000000003849201784629103847",
"feeToken": "USDC",
"twapId": null,
"liquidation": {
"liquidatedUser": "0x7a3b1c9d2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b",
"markPx": "103842.5",
"method": "market"
}
}
]
]
}In this example:
- The first fill (
0x7a3b1c9d2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b) is the liquidated user —diris"Close Long"and their address matchesliquidatedUser - The second fill (
0x1234abcd5678ef901234abcd5678ef901234abcd) is one maker/counterparty — sameliquidatedUserbut a different address - The liquidated user has negative
closedPnl(loss) and positivefee(paid fee as taker) - The counterparty has positive
closedPnl(profit) and negativefee(received rebate as maker)
Maker vs Taker
The crossed field on every fill directly indicates maker or taker status:
crossed | Role | Meaning |
|---|---|---|
true | Taker | The order crossed the spread — it matched immediately against a resting order |
false | Maker | The order was resting on the book — it provided liquidity and was filled by an incoming order |
Fee as a Secondary Signal
The fee field corroborates the maker/taker role:
| Fee sign | Role | Example |
|---|---|---|
Positive (e.g., "0.073107") | Taker — paid a fee | "crossed": true, "fee": "0.073107" |
Negative (e.g., "-0.007311") | Maker — received a rebate | "crossed": false, "fee": "-0.007311" |
Matching Both Sides of a Trade
A match contains one taker-side fill and one or more maker-side fills. Multiple makers can satisfy one taker, so do not assume an exactly two-fill buyer/seller pair. Group all sides by at least (coin, tid); tid is not globally unique, and hash alone can cover multiple matches.
Liquidation Fills and Maker/Taker
For book-absorbed liquidation fills, the liquidated-side fill is the taker (crossed: true) and one or more counterparties whose resting orders are filled are makers (crossed: false). Preserve every maker fill in a multi-maker match.
| Party | crossed | fee | Role |
|---|---|---|---|
| Liquidated user | true | Positive (pays fee) | Taker — forced market order |
| Maker/counterparties | false | Negative (receives rebate) | One or more resting orders filled |
Common Use Cases
1. Trade Execution Tracker
class TradeExecutionTracker {
constructor(streamManager) {
this.streamManager = streamManager;
this.executedTrades = new Map();
streamManager.on('fill', (fillData) => {
this.processFill(fillData);
});
}
processFill(fillData) {
// Track each fill by order ID
const orderId = fillData.oid;
if (!this.executedTrades.has(orderId)) {
this.executedTrades.set(orderId, {
orderId,
fills: [],
totalFilled: 0,
avgPrice: 0
});
}
const trade = this.executedTrades.get(orderId);
trade.fills.push(fillData);
trade.totalFilled += parseFloat(fillData.sz);
// Recalculate average price
const totalValue = trade.fills.reduce(
(sum, f) => sum + parseFloat(f.px) * parseFloat(f.sz), 0
);
trade.avgPrice = totalValue / trade.totalFilled;
console.log(`Order ${orderId}: Filled ${trade.totalFilled} @ avg ${trade.avgPrice}`);
}
}2. Fill Rate Monitor
class FillRateMonitor:
def __init__(self):
self.fills_per_minute = []
self.current_minute_fills = 0
self.last_minute = None
def record_fill(self, fill_data):
"""Record a fill and track rates"""
from datetime import datetime
current_minute = datetime.now().replace(second=0, microsecond=0)
if self.last_minute != current_minute:
if self.last_minute is not None:
self.fills_per_minute.append({
'minute': self.last_minute,
'count': self.current_minute_fills
})
self.last_minute = current_minute
self.current_minute_fills = 0
self.current_minute_fills += 1
def get_average_rate(self, minutes=5):
"""Get average fills per minute over last N minutes"""
recent = self.fills_per_minute[-minutes:]
if not recent:
return 0
return sum(r['count'] for r in recent) / len(recent)3. Position Reconciliation
Open the V3 stream with an empty StreamRequest to tail newly written fills, or set StreamRequest.position.block_number to replay inclusively. Decode each Record.data envelope and apply every fill in its events array before persisting the returned cursor.
Error Handling and Reconnection
class RobustFillStreamer {
constructor(endpoint) {
this.endpoint = endpoint;
this.maxRetries = 5;
this.retryDelay = 1000;
this.currentRetries = 0;
}
async startStreamWithRetry() {
while (this.currentRetries < this.maxRetries) {
try {
await this.startStream();
this.currentRetries = 0;
this.retryDelay = 1000;
} catch (error) {
this.currentRetries++;
console.error(`Stream attempt ${this.currentRetries} failed:`, error.message);
if (this.currentRetries >= this.maxRetries) {
throw new Error('Max retry attempts exceeded');
}
// Exponential backoff
await this.sleep(this.retryDelay);
this.retryDelay *= 2;
}
}
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}Best Practices
- Connection Management: Implement robust reconnection logic with exponential backoff
- Memory Management: Use bounded collections for storing recent fills to prevent memory leaks
- Performance: Process fills asynchronously to avoid blocking the stream
- Monitoring: Track stream health and fill rates
- Error Recovery: Handle various error types (network, parsing, processing) gracefully
- Resource Cleanup: Properly close streams and connections on shutdown
Current Limitations
- Replay History: Available replay history varies by feed and endpoint
- Unavailable Cursors: A cursor outside retained history returns
NOT_FOUND - Backpressure: High-volume periods may require careful handling to avoid overwhelming downstream systems
Resources
- GitHub: gRPC Code Examples - Complete working examples
- Copy Trading Bot - Uses
StreamFillsto mirror trades in real-time
Need help? Contact our support team or check the Hyperliquid gRPC documentation.
StreamBlocks - Real-time Block Streaming
Stream continuous block data from Hyperliquid L1 Gateway via gRPC. Complete reference for all 47 action types.
StreamMiscEvents - Real-time Miscellaneous Event Streaming
Stream non-fill balance-affecting events from Hyperliquid L1 Gateway via gRPC. Monitor deposits, withdrawals, transfers, staking, and validator rewards in real-time.