Docs
Docs
Home
Getting Started
Agent Tooling
CLI
Supported Chains
Acala
Aptos
Arbitrum
Arc
Asset Hub
Astar
Avalanche
Base
Berachain
Bifrost
Binance Smart Chain
Bittensor
Blast
Boba Network
Bridge Hub
Celo
Centrifuge
Chiliz
Cronos
Enjin
Ethereum
Etherlink
Flow EVM Gateway
Gnosis
Hydration
Hyperliquid
Immutable
Ink
IoTeX
Kusama
Linea
LISK
Manta Atlantic
Manta Pacific
Mantle
MegaETH
Monad
Movement
Neuroweb
opBNB
Optimism
Plasma
Polkadot
Polygon
Pulsechain
Robinhood
Ronin
Scroll
Sonic
Starknet
Taiko
Tempo
Sui
TRON
Unichain
Viction
World Chain
XDC Network
XX Network
Zetachain
zkSync Era
Zora
Supported ChainsHyperliquidHyperliquid HyperCoregRPC Streaming API

StreamFills - Real-time Fill Streaming

Stream continuous fill data from Hyperliquid L1 Gateway via gRPC. Monitor order executions in real-time.

Hyperliquid RPC endpoint: endpoint URLs, plans and pricing.

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

Latency

We measured StreamFills on the shared endpoint api-hyperliquid-mainnet-grpc.n.dwellir.com from AWS Tokyo (ap-northeast-1) on 2026-10-22. The test ran for 5 minutes and received 4,122 blocks of fills with no errors. Latency is the time from the HyperCore block timestamp to the moment the client receives the fills for that block.

PercentileShared endpointDedicated Tokyo node
p50163 ms164 ms
p95194 ms195 ms
p99229 ms228 ms

The gap between messages was 68 ms at p50 and 129 ms at p95. Your results depend on where your client runs. Run it in Tokyo for the lowest latency.

Method signature

gRPC API version
protobuf
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. Set progress to opt in to progress frames.

protobuf
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, coins and dexes filters. User matching is case-insensitive, coin matching is case-sensitive, dex matching is exact on the HIP-3 dex prefix, values within a field use OR semantics, and separate 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.

cURL request
grpcurl \
  -import-path content/docs/hyperliquid/grpc \
  -proto v3.proto \
  -H "x-api-key: ${DWELLIR_API_KEY}" \
  -d '{"position":{"block_number":"1058646974"},"filter":{"fields":[{"field":"coins","op":"FILTER_OP_IN","values":["BTC"]}]}}' \
  "${HYPERLIQUID_GRPC_ENDPOINT}" \
  hyperliquid_l1_gateway.v3.HyperliquidL1Gateway/StreamFills

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):

JSON
{
  "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]):

jsonc
[
  "0x...",  // user_address - the trader's address
  {
    // Fill details object
  }
]

Fill details fields:

FieldTypeDescription
coinstringTrading pair symbol (e.g., "BTC", "ETH", "SOL")
pxstringFill price
szstringFill size
sidestring"B" for buy, "A" for sell
timenumberFill timestamp in milliseconds since Unix epoch
startPositionstringPosition size before this fill
dirstringPosition direction: "Open Long", "Open Short", "Close Long", "Close Short"
closedPnlstringRealized PnL if closing a position (can be negative)
hashstringTransaction hash (64 hex characters)
oidnumberOrder ID assigned by the system
crossedbooleanMaker/taker indicator. true = taker (crossed the spread), false = maker (resting order). See Maker vs Taker
feestringTrading fee (negative values indicate rebates)
tidnumberTrade ID. Within one block, group matches by (coin, tid). Across blocks, include block identity.
cloidstringClient order ID if provided (optional)
feeTokenstringToken used for fee payment
twapIdnumber/nullTWAP order ID if the fill is part of a TWAP execution (optional)
liquidationobject/nullLiquidation details, present only when the fill is part of a forced position closure. See Liquidation Data
builderstringBuilder address if order was routed through a builder (optional)
builderFeestringFee paid to builder (optional)

Guarantees and alignment:

  • events array contains all fills from the block for all users.
  • Each event pairs a user address with their fill details.
  • block_number is sequential and does not correspond to the potentially skipping abci_block.round consensus value in StreamBlocks.
  • block_time aligns with abci_block.time in StreamBlocks.
  • Multiple fills per block are delivered in a single message.

The (coin, tid) grouping applies within one block. Across blocks, include block_number or block_time in the match key. Hyperliquid documents (block_time, coin, tid) as the globally unique trade identity.

Developer tips:

  • Within one block, group a match by (coin, tid). Across blocks, include block_number or block_time. Retain account and order identity when deduplicating individual fills.
  • Track startPosition and dir to reconstruct position changes.
  • Sum closedPnl across fills to calculate realized PnL.
  • Normalize px, sz, fee, and closedPnl (strings) to numeric types as appropriate.
  • Handle optional fields (cloid, twapId, liquidation, builder, builderFee) defensively.
  • Check liquidation to detect forced position closures. See Liquidation Data.
  • Use crossed to 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:

  1. One liquidated-side fill for the forced position close
  2. One or more maker/counterparty fills for the resting liquidity that absorbed it

Multiple makers can participate in one match. Within one block, group its fills by (coin, tid). Across blocks, include block_number or block_time in the match key.

A transaction hash can contain more than one match. To identify the actual liquidated user rather than a counterparty, check that:

  • liquidation is present on the fill
  • dir contains "Close" (e.g., "Close Long", "Close Short")
  • The user address matches liquidation.liquidatedUser

Liquidation Object Fields

FieldTypeDescription
liquidatedUserstringAddress of the user being liquidated
markPxstringMark price at the time of liquidation
methodstringLiquidation method: "market" or "backstop"

Liquidation Methods

MethodDescription
marketStandard liquidation filled against resting orders on the book
backstopBackstop 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. Within that block, a multi-maker liquidation contains additional maker fills with the same (coin, tid) match key.

JSON
{
  "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 — dir is "Close Long" and their address matches liquidatedUser
  • The second fill (0x1234abcd5678ef901234abcd5678ef901234abcd) is one maker/counterparty — same liquidatedUser but a different address
  • The liquidated user has negative closedPnl (loss) and positive fee (paid fee as taker)
  • The counterparty has positive closedPnl (profit) and negative fee (received rebate as maker)

Maker vs Taker

The crossed field on every fill directly indicates maker or taker status:

crossedRoleMeaning
trueTakerThe order crossed the spread — it matched immediately against a resting order
falseMakerThe 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 signRoleExample
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.

Within one block, group all sides by (coin, tid). Across blocks, include block_number or block_time in the match key. A transaction 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.

PartycrossedfeeRole
Liquidated usertruePositive (pays fee)Taker — forced market order
Maker/counterpartiesfalseNegative (receives rebate)One or more resting orders filled

Common Use Cases

1. Trade Execution Tracker

JavaScript
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

Python
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.

Progress Frames

Progress frames are an application-level heartbeat. Set StreamRequest.progress to receive one after each interval of silence. A users or coins filter can leave the stream silent for minutes. They keep it open through idle-limited proxies, which HTTP/2 keepalive pings do not, and advance your resume cursor through blocks the filter removed.

A progress frame sets Record.progress.scanned_through.block_number and leaves the data fields unset, so check progress before decoding data. Persist scanned_through as an inclusive resume cursor. See progress frames for the full contract.

Error Handling and Reconnection

JavaScript
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

  1. Connection Management: Implement robust reconnection logic with exponential backoff
  2. Memory Management: Use bounded collections for storing recent fills to prevent memory leaks
  3. Performance: Process fills asynchronously to avoid blocking the stream
  4. Monitoring: Track stream health and fill rates
  5. Error Recovery: Handle various error types (network, parsing, processing) gracefully
  6. 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 StreamFills to 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.

StreamMempoolTxs - Stream Pre-Consensus Transactions

Stream pre-consensus Hyperliquid client blocks over V3 gRPC. Each record carries signed transaction requests seen before block inclusion, positioned by receive time.

On this page

When to Use This MethodLatencyResponse StreamFull Fill SpecLiquidation DataLiquidation Object FieldsLiquidation MethodsExample: Single-maker liquidation fillMaker vs TakerFee as a Secondary SignalMatching Both Sides of a TradeLiquidation Fills and Maker/TakerCommon Use Cases1. Trade Execution Tracker2. Fill Rate Monitor3. Position ReconciliationProgress FramesError Handling and ReconnectionBest PracticesCurrent LimitationsResources