Docs

GetBlock - Get Block Data

Retrieve a single block from Hyperliquid L1 Gateway via gRPC. Get blockchain data at a specific position.

Retrieve a single block 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

GetBlock is essential for:

  • Point-in-Time Analysis - Get blockchain state at a specific block or timestamp
  • Backtesting - Retrieve historical block data for strategy analysis
  • Auditing - Verify transactions and state changes at specific points
  • Debugging - Investigate specific blocks during development

Method Signature

protobuf
rpc GetBlock(Position) returns (Block) {}

Request Message

protobuf
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 block targeting:

  • timestamp_ms: Get block at or after a specific time (milliseconds since Unix epoch)
  • block_height: Get block at a specific height
  • Empty/zero: Get the latest block

Response Message

protobuf
message Block {
  // JSON-encoded Hyperliquid block from "replica_cmds".
  bytes data = 1;
}

The data field contains a JSON-encoded block with the following structure:

Data Structure

JSON
{
  "abci_block": {
    "time": "2025-09-08T06:41:57.997372546",
    "round": 992814678,
    "parent_round": 992814677,
    "proposer": "0x5ac99df645f3414876c816caa18b2d234024b487",
    "hardfork": {
      "version": 57,
      "round": 990500929
    },
    "signed_action_bundles": [
      [
        "0xb4b1f5a9c233f9d90fd24b9961fd12708b36cc3d56f8fda47f32b667ee8d1227",
        {
          "signed_actions": [
            {
              "signature": {
                "r": "0x...",
                "s": "0x...",
                "v": 27
              },
              "action": {
                "type": "order",
                "orders": [...]
              },
              "nonce": 1757313597362
            }
          ],
          "broadcaster": "0x67e451964e0421f6e7d07be784f35c530667c2b3",
          "broadcaster_nonce": 1757313597367
        }
      ]
    ]
  },
  "resps": {
    "Full": [
      [
        "0xb4b1f5a9c233f9d90fd24b9961fd12708b36cc3d56f8fda47f32b667ee8d1227",
        [
          {
            "user": "0xecb63caa47c7c4e77f60f1ce858cf28dc2b82b00",
            "res": {
              "status": "ok",
              "response": {
                "type": "order",
                "data": {...}
              }
            }
          }
        ]
      ]
    ]
  }
}

Field Descriptions

FieldTypeDescription
abci_block.timestringISO-8601 timestamp with nanosecond precision
abci_block.roundnumberCurrent block number (height)
abci_block.parent_roundnumberPrevious block number
abci_block.proposerstringValidator address that proposed the block
abci_block.hardforkobjectProtocol version information
abci_block.signed_action_bundlesarrayArray of [hash, bundle_data] pairs
resps.FullarrayExecution responses matching bundle structure

For the complete block specification including all action types, see the StreamBlocks documentation.

Implementation Examples

Go
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 BlockData struct {
    AbciBlock struct {
        Time               string `json:"time"`
        Round              int64  `json:"round"`
        ParentRound        int64  `json:"parent_round"`
        Proposer           string `json:"proposer"`
        SignedActionBundles []interface{} `json:"signed_action_bundles"`
    } `json:"abci_block"`
    Resps struct {
        Full []interface{} `json:"Full"`
    } `json:"resps"`
}

func getBlock(ctx context.Context, client pb.HyperliquidL1GatewayClient, position *pb.Position) (*BlockData, error) {
    ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
    defer cancel()

    response, err := client.GetBlock(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 block BlockData
    if err := json.Unmarshal(response.Data, &block); err != nil {
        return nil, fmt.Errorf("failed to parse JSON: %v", err)
    }

    return &block, 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 Block")
    fmt.Println("======================================")
    fmt.Printf("Endpoint: %s\n\n", endpoint)

    // Set up TLS connection
    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")

    // Get block with API key
    ctx := context.Background()
    ctx = metadata.AppendToOutgoingContext(ctx, "x-api-key", apiKey)

    // Request latest block (empty position)
    request := &pb.Position{}

    // Or request by block height:
    // request := &pb.Position{Position: &pb.Position_BlockHeight{BlockHeight: 992814678}}

    // Or request by timestamp:
    // request := &pb.Position{Position: &pb.Position_TimestampMs{TimestampMs: 1725778917997}}

    fmt.Println("Fetching block...\n")

    block, err := getBlock(ctx, client, request)
    if err != nil {
        log.Fatalf("Failed to get block: %v", err)
    }

    // Display block information
    fmt.Printf("Block Height: %d\n", block.AbciBlock.Round)
    fmt.Printf("Parent Block: %d\n", block.AbciBlock.ParentRound)
    fmt.Printf("Time: %s\n", block.AbciBlock.Time)
    fmt.Printf("Proposer: %s\n", block.AbciBlock.Proposer)
    fmt.Printf("Action Bundles: %d\n", len(block.AbciBlock.SignedActionBundles))
}
Python
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_block(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 Block')
    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 block at height: {block_height}')
        elif timestamp_ms is not None:
            request = hyperliquid_pb2.Position(timestamp_ms=timestamp_ms)
            print(f'Requesting block at timestamp: {timestamp_ms}')
        else:
            request = hyperliquid_pb2.Position()
            print('Requesting latest block')

        print('\nFetching block...\n')

        try:
            response = client.GetBlock(request, metadata=metadata)
            print(f'Response size: {len(response.data)} bytes\n')

            process_block(response.data)

        except grpc.RpcError as e:
            print(f'RPC error: {e}')
        except Exception as e:
            print(f'Error: {e}')

def process_block(data):
    try:
        block = json.loads(data.decode('utf-8'))

        print('BLOCK DATA')
        print('==========')

        abci_block = block.get('abci_block', {})

        # Display block info
        print(f'Block Height: {abci_block.get("round")}')
        print(f'Parent Block: {abci_block.get("parent_round")}')
        print(f'Time: {abci_block.get("time")}')
        print(f'Proposer: {abci_block.get("proposer")}')

        # Display hardfork info
        hardfork = abci_block.get('hardfork', {})
        print(f'\nHardfork Version: {hardfork.get("version")}')
        print(f'Hardfork Round: {hardfork.get("round")}')

        # Count action bundles
        bundles = abci_block.get('signed_action_bundles', [])
        print(f'\nAction Bundles: {len(bundles)}')

        # Count total actions
        total_actions = 0
        action_types = {}
        for bundle in bundles:
            if len(bundle) >= 2:
                bundle_data = bundle[1]
                signed_actions = bundle_data.get('signed_actions', [])
                total_actions += len(signed_actions)

                for action in signed_actions:
                    action_type = action.get('action', {}).get('type', 'unknown')
                    action_types[action_type] = action_types.get(action_type, 0) + 1

        print(f'Total Actions: {total_actions}')

        if action_types:
            print('\nAction Types:')
            for action_type, count in sorted(action_types.items(), key=lambda x: -x[1]):
                print(f'  {action_type}: {count}')

    except json.JSONDecodeError as e:
        print(f'Failed to parse JSON: {e}')
    except Exception as e:
        print(f'Error processing block: {e}')

if __name__ == '__main__':
    # Get latest block
    get_block()

    # Or get by block height:
    # get_block(block_height=992814678)

    # Or get by timestamp:
    # get_block(timestamp_ms=1725778917997)
JavaScript
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 getBlock(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 Block');
    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 block at height: ${options.blockHeight}`);
    } else if (options.timestampMs) {
        request.timestamp_ms = options.timestampMs;
        console.log(`Requesting block at timestamp: ${options.timestampMs}`);
    } else {
        console.log('Requesting latest block');
    }

    console.log('\nFetching block...\n');

    client.GetBlock(request, metadata, (error, response) => {
        if (error) {
            console.error('gRPC error:', error.message);
            return;
        }

        try {
            const block = JSON.parse(response.data);
            console.log(`Response size: ${response.data.length} bytes\n`);

            processBlock(block);

        } catch (error) {
            console.error('Failed to parse response:', error.message);
        }
    });
}

function processBlock(block) {
    console.log('BLOCK DATA');
    console.log('==========');

    const abciBlock = block.abci_block || {};

    console.log(`Block Height: ${abciBlock.round}`);
    console.log(`Parent Block: ${abciBlock.parent_round}`);
    console.log(`Time: ${abciBlock.time}`);
    console.log(`Proposer: ${abciBlock.proposer}`);

    // Hardfork info
    const hardfork = abciBlock.hardfork || {};
    console.log(`\nHardfork Version: ${hardfork.version}`);
    console.log(`Hardfork Round: ${hardfork.round}`);

    // Count bundles and actions
    const bundles = abciBlock.signed_action_bundles || [];
    console.log(`\nAction Bundles: ${bundles.length}`);

    let totalActions = 0;
    const actionTypes = {};

    for (const bundle of bundles) {
        if (bundle.length >= 2) {
            const bundleData = bundle[1];
            const signedActions = bundleData.signed_actions || [];
            totalActions += signedActions.length;

            for (const action of signedActions) {
                const actionType = action.action?.type || 'unknown';
                actionTypes[actionType] = (actionTypes[actionType] || 0) + 1;
            }
        }
    }

    console.log(`Total Actions: ${totalActions}`);

    if (Object.keys(actionTypes).length > 0) {
        console.log('\nAction Types:');
        const sorted = Object.entries(actionTypes).sort((a, b) => b[1] - a[1]);
        for (const [type, count] of sorted) {
            console.log(`  ${type}: ${count}`);
        }
    }
}

// Get latest block
getBlock();

// Or get by block height:
// getBlock({ blockHeight: 992814678 });

// Or get by timestamp:
// getBlock({ timestampMs: 1725778917997 });

Common Use Cases

1. Historical Block Analysis

Python
def analyze_block_activity(client, block_height):
    """Analyze trading activity in a specific block"""
    request = hyperliquid_pb2.Position(block_height=block_height)
    response = client.GetBlock(request, metadata=metadata)
    block = json.loads(response.data)

    abci_block = block.get('abci_block', {})
    bundles = abci_block.get('signed_action_bundles', [])

    analysis = {
        'block_height': abci_block.get('round'),
        'timestamp': abci_block.get('time'),
        'total_bundles': len(bundles),
        'total_actions': 0,
        'order_count': 0,
        'cancel_count': 0,
        'transfer_count': 0
    }

    for bundle in bundles:
        if len(bundle) >= 2:
            for action in bundle[1].get('signed_actions', []):
                analysis['total_actions'] += 1
                action_type = action.get('action', {}).get('type', '')

                if action_type == 'order':
                    analysis['order_count'] += 1
                elif action_type in ['cancel', 'cancelByCloid']:
                    analysis['cancel_count'] += 1
                elif action_type in ['spotSend', 'usdSend', 'withdraw3']:
                    analysis['transfer_count'] += 1

    return analysis

2. Block Comparison

Go
func compareBlocks(client pb.HyperliquidL1GatewayClient, ctx context.Context, height1, height2 int64) {
    block1, _ := client.GetBlock(ctx, &pb.Position{Position: &pb.Position_BlockHeight{BlockHeight: height1}})
    block2, _ := client.GetBlock(ctx, &pb.Position{Position: &pb.Position_BlockHeight{BlockHeight: height2}})

    var data1, data2 map[string]interface{}
    json.Unmarshal(block1.Data, &data1)
    json.Unmarshal(block2.Data, &data2)

    abci1 := data1["abci_block"].(map[string]interface{})
    abci2 := data2["abci_block"].(map[string]interface{})

    bundles1 := abci1["signed_action_bundles"].([]interface{})
    bundles2 := abci2["signed_action_bundles"].([]interface{})

    fmt.Printf("Block %d: %d bundles\n", height1, len(bundles1))
    fmt.Printf("Block %d: %d bundles\n", height2, len(bundles2))
}

3. Transaction Verification

JavaScript
async function verifyTransaction(client, metadata, txHash, blockHeight) {
    return new Promise((resolve, reject) => {
        const request = { block_height: blockHeight };

        client.GetBlock(request, metadata, (error, response) => {
            if (error) {
                reject(error);
                return;
            }

            const block = JSON.parse(response.data);
            const bundles = block.abci_block?.signed_action_bundles || [];

            for (const bundle of bundles) {
                const bundleHash = bundle[0];
                if (bundleHash === txHash) {
                    resolve({
                        found: true,
                        block_height: block.abci_block.round,
                        bundle_data: bundle[1]
                    });
                    return;
                }
            }

            resolve({ found: false });
        });
    });
}

Error Handling

JavaScript
async function robustGetBlock(client, metadata, position, retries = 3) {
    for (let attempt = 0; attempt < retries; attempt++) {
        try {
            return await new Promise((resolve, reject) => {
                client.GetBlock(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 blocks across requests if near block boundaries
  2. Data Validation: Always validate JSON structure before processing
  3. Error Recovery: Implement retry logic with exponential backoff
  4. Resource Management: Close gRPC connections properly to avoid resource leaks
  5. Caching: Cache block data when analyzing the same blocks multiple times

Current Limitations

  • Data Retention: Historical block 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.