Docs

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.

Stream miscellaneous L1 events starting from a position, providing real-time access to every non-fill balance-affecting action on Hyperliquid: deposits, withdrawals, transfers, staking delegations, and validator rewards.

Full Code Examples

Clone our gRPC Code Examples Repository for complete, runnable implementations in Go, Python, and Node.js.

When to Use This Method

StreamMiscEvents is essential for:

  • Deposit & Withdrawal Monitoring - Track funds entering and leaving accounts in real-time
  • Treasury & Transfer Tracking - Follow spot transfers and internal send movements
  • Staking & Delegation Analytics - Observe delegations, undelegations, and staking withdrawals
  • Validator Reward Accounting - Reconcile per-epoch validator reward distributions
  • Compliance & Audit Trails - Build a complete record of balance-affecting activity

Completes the Balance Picture

StreamMiscEvents covers everything StreamFills does not. Fills are trades; misc events are every other action that moves funds. Consume both streams together for a complete view of all balance-affecting activity.

Method Signature

protobuf
rpc StreamMiscEvents(Position) returns (stream BlockMiscEvents) {}

Response Stream

protobuf
message BlockMiscEvents {
  // JSON-encoded block envelope from "misc_events_by_block".
  bytes data = 1;
}

The data field contains a JSON-encoded block envelope with:

  • Block reference (local_time, block_time, block_number)
  • An events array of miscellaneous L1 event objects (empty for most blocks)

Per-Block Delivery

StreamMiscEvents emits exactly one message per block, including blocks with an empty events array. Most blocks contain no miscellaneous events, so the empty envelope doubles as a per-block heartbeat: consumers always know the stream is live and exactly which block was last processed, with no extra bookkeeping.

Messages arrive in strictly increasing block_number order with no gaps and no duplicates. The stream is stateless: reconnect by passing the next block_height (or a timestamp_ms) in the Position and resume exactly where you left off. Both are inclusive; an empty or zero Position targets the latest data.

Full Misc Events Spec

StreamMiscEvents emits a JSON payload matching Hyperliquid's misc_events_by_block format, passed through byte-for-byte from the node with no normalization. Below is the exact structure.

Top-level keys (always present):

JSON
{
  "local_time": "2026-07-02T11:10:49.483839100",   // ISO-8601 timestamp when node processed the block (nanosecond precision)
  "block_time": "2026-07-02T11:10:47.023075863",    // ISO-8601 timestamp from block consensus
  "block_number": 1057659853,                        // Block height containing these events
  "events": []                                       // Array of miscellaneous event objects (empty for most blocks)
}

Each entry in events is a raw upstream Hyperliquid L1 event object:

FieldTypeDescription
timestringISO-8601 timestamp of the event
hashstringTransaction hash. An all-zeros hash indicates a system-generated event (e.g. validator rewards) rather than a user transaction
innerobjectSingle-key object whose key is the event type discriminator (e.g. "LedgerUpdate", "Delegation", "CWithdrawal", "ValidatorRewards")

Branch on the single key inside inner to determine the event type. The sections below cover the event types observed on mainnet; the upstream schema defines additional types, so treat these as examples rather than an exhaustive list. The authoritative schema reference is Hyperliquid's L1 data schemas documentation.

Guarantees and alignment:

  • events contains all miscellaneous events from the block, across all users.
  • Messages arrive in strictly increasing block_number, one per block (including empty blocks).
  • block_number corresponds to abci_block.round in StreamBlocks; block_time aligns with abci_block.time.

Developer tips:

  • Branch on the key inside inner and handle unknown event types defensively — the upstream set can grow.
  • Track the last processed block_number so you can resume cleanly after a reconnect.
  • Normalize amount fields (strings) to numeric types as appropriate.
  • Treat the all-zeros hash as the marker for system-generated events.

Event Types

LedgerUpdate

LedgerUpdate is the most common event type. It carries a users array (the involved addresses) and a delta object whose type field discriminates the specific ledger action.

JSON
{
  "time": "2026-07-02T11:10:47.023075863",
  "hash": "0xae740acbd26e0108afed043f0a9bcd02022d00b16d611fda523cb61e9161daf3",
  "inner": {
    "LedgerUpdate": {
      "users": [
        "0x2000000000000000000000000000000000000000",
        "0x0a82a35f3e54aede3a20931f446836e6c69cae0b"
      ],
      "delta": {
        "type": "spotTransfer",
        "token": "USDC",
        "amount": "100.058362",
        "usdcValue": "100.058362",
        "user": "0x2000000000000000000000000000000000000000",
        "destination": "0x0a82a35f3e54aede3a20931f446836e6c69cae0b",
        "fee": "0.0",
        "nativeTokenFee": "0.0",
        "nonce": 2053915,
        "feeToken": ""
      }
    }
  }
}

The delta.type discriminator determines the remaining fields. Observed types on mainnet include:

delta.typeMeaningNotable fields
sendInternal transfer between accountsuser, destination, token, amount, usdcValue, fee
spotTransferSpot balance transferuser, destination, token, amount, usdcValue, fee
depositFunds deposited into an accountusdc
withdrawFunds withdrawn from an accountusdc, nonce, fee
rewardsClaimRewards claimedtoken, amount
borrowLendBorrow/lend operationtoken, operation, amount, interestAmount

More delta.type values exist upstream; branch defensively on delta.type and treat this table as examples, not an exhaustive list. Example deltas:

JSON
{ "type": "deposit", "usdc": "3494.915745" }
{ "type": "withdraw", "usdc": "89.15", "nonce": 1782990425200000, "fee": "1.0" }
{ "type": "rewardsClaim", "amount": "1.097128", "token": "USDC" }
{ "type": "borrowLend", "token": "USDC", "operation": "supply", "amount": "0.23068", "interestAmount": "0.0" }

Delegation

Staking delegation and undelegation. The is_undelegate flag distinguishes the two directions.

JSON
{
  "time": "2026-07-02T11:11:06.583481947",
  "hash": "0xfa0716eba98e39b0fb80043f0a9cec02029400d1448158839dcfc23e6882139b",
  "inner": {
    "Delegation": {
      "user": "0xd5a5ae523080973946ac67e34f5fc70671bd8667",
      "validator": "0x5ac99df645f3414876c816caa18b2d234024b487",
      "amount": "20.65611502",
      "is_undelegate": true
    }
  }
}

CWithdrawal

A staking withdrawal, which enters a pending state until finalization. The is_finalized flag reflects whether the withdrawal has completed.

JSON
{
  "time": "2026-07-02T11:11:07.283636968",
  "hash": "0xae4f651df8ef488eafc9043f0a9cf6021348000393e2676052181070b7e32279",
  "inner": {
    "CWithdrawal": {
      "user": "0xd5a5ae523080973946ac67e34f5fc70671bd8667",
      "amount": "20.65611502",
      "is_finalized": false
    }
  }
}

ValidatorRewards

Per-epoch validator reward distributions. These are system-generated, so they carry an all-zeros hash. The validator_to_reward array holds [validator_address, amount] pairs.

JSON
{
  "time": "2026-07-02T11:11:00.042812129",
  "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "inner": {
    "ValidatorRewards": {
      "validator_to_reward": [
        ["0x000000000056f99d36b6f2e0c51fd41496bbacb8", "0.2806444"],
        ["0x15458aed3c7a49b215fbfa863c6ff550c31e1a31", "0.22056816"],
        ["0x30c66ebc7f5ef4f340b424a26e4d944f60129815", "0.34709989"]
      ]
    }
  }
}

Common Use Cases

1. Deposit and Withdrawal Monitor

Python
def classify_event(event):
    """Return a (kind, detail) tuple for a misc event."""
    inner = event['inner']
    kind = next(iter(inner))          # the single discriminator key
    body = inner[kind]

    if kind == 'LedgerUpdate':
        delta = body['delta']
        return (delta['type'], delta)  # e.g. ('deposit', {...})
    return (kind, body)                # e.g. ('Delegation', {...})


def watch_flows(client, metadata):
    request = hyperliquid_pb2.Position()  # latest
    for response in client.StreamMiscEvents(request, metadata=metadata):
        block = json.loads(response.data)
        for event in block.get('events', []):
            kind, detail = classify_event(event)
            if kind in ('deposit', 'withdraw'):
                print(f"block {block['block_number']}: {kind} {detail.get('usdc')} USDC")

2. Streaming Misc Events in Python

Python
import grpc
import json
import os
import hyperliquid_pb2
import hyperliquid_pb2_grpc

def stream_misc_events():
    endpoint = os.getenv('HYPERLIQUID_ENDPOINT')
    api_key = os.getenv('API_KEY')

    credentials = grpc.ssl_channel_credentials()
    options = [('grpc.max_receive_message_length', 150 * 1024 * 1024)]  # 150MB max
    metadata = [('x-api-key', api_key)]

    with grpc.secure_channel(endpoint, credentials, options=options) as channel:
        client = hyperliquid_pb2_grpc.HyperliquidL1GatewayStub(channel)

        # Empty Position means latest; set block_height or timestamp_ms to backfill
        request = hyperliquid_pb2.Position()

        for response in client.StreamMiscEvents(request, metadata=metadata):
            block = json.loads(response.data)
            events = block.get('events', [])
            if events:
                print(f"block {block['block_number']}: {len(events)} misc events")

Error Handling and Reconnection

Reconnect by replaying from the last processed block. Pass the next block_height in the Position so no events are skipped or double-counted.

Python
def stream_with_resume(client, metadata, start_block):
    next_block = start_block
    while True:
        request = hyperliquid_pb2.Position(block_height=next_block)
        try:
            for response in client.StreamMiscEvents(request, metadata=metadata):
                block = json.loads(response.data)
                process(block)
                next_block = block['block_number'] + 1
        except grpc.RpcError as e:
            print(f"stream error, resuming from {next_block}: {e}")

Best Practices

  1. Connection Management: Implement robust reconnection logic with exponential backoff
  2. Resumable State: Track the last processed block_number so you can resume from the next block
  3. Defensive Parsing: Branch on the key inside inner (and delta.type for LedgerUpdate) and handle unknown types gracefully
  4. Heartbeat Awareness: Use the per-block empty envelope to confirm liveness without extra polling
  5. Performance: Process events asynchronously to avoid blocking the stream
  6. Resource Cleanup: Properly close streams and connections on shutdown

Current Limitations

  • Data Retention: The node maintains only 24 hours of historical data
  • Schema Coverage: The upstream event set can grow; treat the documented event and delta types as examples, not an exhaustive list
  • Backpressure: High-volume periods may require careful handling to avoid overwhelming downstream systems

Resources

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