Skip to main content

Setting Up Node.js for OrderBook (L2/L4)

Updated on
Feb 24, 2026

Overviewโ€‹

This guide demonstrates how to stream real-time orderbook data from Hyperliquid using gRPC in Node.js. You'll learn to:

  • Stream L2 orderbook (aggregated price levels with depths)
  • Stream L4 orderbook (individual orders with full details)
  • Load Protocol Buffer definitions dynamically
  • Handle reconnections and errors gracefully

The orderbook streaming API uses a separate proto file (orderbook.proto) from the core streaming API, providing specialized methods optimized for market data.

Related Guides

For core streaming API (StreamData, StreamBlocks, Ping), see Node.js Setup Guide

Prerequisitesโ€‹

1. Install Node.jsโ€‹

Node.js 16 or later is required. Follow the official installation guide or verify your installation:

node --version
npm --version

Project Setupโ€‹

Step 1: Create Project Directoryโ€‹

mkdir hyperliquid-orderbook-nodejs
cd hyperliquid-orderbook-nodejs
npm init -y

Your directory structure:

hyperliquid-orderbook-nodejs/
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ proto/ # Protocol buffer definitions
โ””โ”€โ”€ examples/ # Example scripts

Step 2: Install Dependenciesโ€‹

Install gRPC and Protocol Buffers packages:

npm install @grpc/grpc-js @grpc/proto-loader

These packages provide:

  • @grpc/grpc-js - Pure JavaScript gRPC implementation
  • @grpc/proto-loader - Loads .proto files at runtime

Step 3: Download Proto Fileโ€‹

Create directories and download the orderbook protocol definition:

mkdir -p proto examples

curl -o proto/orderbook.proto \
https://raw.githubusercontent.com/quiknode-labs/hypercore-grpc-examples/main/proto/orderbook.proto

Verify the file was downloaded:

ls -la proto/
# Should show: orderbook.proto

Implementationโ€‹

StreamL2Book Exampleโ€‹

Create examples/stream_l2.js:

const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');

// Parse command-line arguments
const args = process.argv.slice(2);
let endpoint = 'your-endpoint.hype-mainnet.quiknode.pro:10000';
let token = 'your-auth-token';
let coin = 'BTC';
let levels = 20;

for (let i = 0; i < args.length; i++) {
if (args[i].startsWith('--endpoint=')) endpoint = args[i].split('=')[1];
else if (args[i].startsWith('--token=')) token = args[i].split('=')[1];
else if (args[i].startsWith('--coin=')) coin = args[i].split('=')[1];
else if (args[i].startsWith('--levels=')) levels = parseInt(args[i].split('=')[1]);
}

const PROTO_PATH = path.join(__dirname, '..', 'proto', 'orderbook.proto');

const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const proto = grpc.loadPackageDefinition(packageDefinition).hyperliquid;

function createClient() {
return new proto.OrderBookStreaming(
endpoint,
grpc.credentials.createSsl(),
{ 'grpc.max_receive_message_length': 100 * 1024 * 1024 }
);
}

async function streamL2Orderbook() {
console.log('='.repeat(60));
console.log(`Streaming L2 Orderbook for ${coin}`);
console.log(`Levels: ${levels}`);
console.log('='.repeat(60) + '\n');

let retryCount = 0;
const maxRetries = 10;
const baseDelay = 2000;

while (retryCount < maxRetries) {
const client = createClient();
const metadata = new grpc.Metadata();
metadata.add('x-token', token);

const request = {
coin: coin,
n_levels: levels
};

try {
if (retryCount > 0) {
console.log(`\n๐Ÿ”„ Reconnecting (attempt ${retryCount + 1}/${maxRetries})...`);
} else {
console.log(`Connecting to ${endpoint}...`);
}

let msgCount = 0;
const call = client.StreamL2Book(request, metadata);

call.on('data', (update) => {
msgCount++;

if (msgCount === 1) {
console.log('โœ“ First L2 update received!\n');
retryCount = 0;
}

console.log('\n' + 'โ”€'.repeat(60));
console.log(`Block: ${update.block_number} | Time: ${update.time} | Coin: ${update.coin}`);
console.log('โ”€'.repeat(60));

// Display asks (reversed for display)
if (update.asks && update.asks.length > 0) {
console.log('\n ASKS:');
update.asks.slice(0, 10).reverse().forEach(level => {
console.log(` ${level.px.padStart(12)} | ${level.sz.padStart(12)} | (${level.n} orders)`);
});
}

// Display spread
if (update.bids && update.bids.length > 0 && update.asks && update.asks.length > 0) {
const bestBid = parseFloat(update.bids[0].px);
const bestAsk = parseFloat(update.asks[0].px);
const spread = bestAsk - bestBid;
const spreadBps = (spread / bestBid) * 10000;
console.log('\n ' + 'โ”€'.repeat(44));
console.log(` SPREAD: ${spread.toFixed(2)} (${spreadBps.toFixed(2)} bps)`);
console.log(' ' + 'โ”€'.repeat(44));
}

// Display bids
if (update.bids && update.bids.length > 0) {
console.log('\n BIDS:');
update.bids.slice(0, 10).forEach(level => {
console.log(` ${level.px.padStart(12)} | ${level.sz.padStart(12)} | (${level.n} orders)`);
});
}

console.log(`\n Messages received: ${msgCount}`);
});

call.on('error', (err) => {
if (err.code === grpc.status.DATA_LOSS) {
console.log(`\nโš ๏ธ Server reinitialized: ${err.message}`);
retryCount++;
if (retryCount < maxRetries) {
const delay = baseDelay * Math.pow(2, retryCount - 1);
console.log(`โณ Waiting ${delay / 1000}s before reconnecting...`);
setTimeout(() => streamL2Orderbook(), delay);
}
} else {
console.error('\ngRPC error:', err.code, '-', err.message);
}
});

call.on('end', () => {
console.log('\nStream ended');
});

await new Promise((resolve) => {
call.on('end', resolve);
call.on('error', resolve);
});

break;

} catch (err) {
console.error('Error:', err.message);
break;
}
}
}

streamL2Orderbook().catch(console.error);
StreamL4Book Example

For individual order streaming with full details, see the StreamL4Book method documentation which includes complete code examples.

Run the Exampleโ€‹

Execute the script with your credentials:

cd examples

node stream_l2.js \
--endpoint="your-endpoint.hype-mainnet.quiknode.pro:10000" \
--token="your-auth-token" \
--coin="BTC" \
--levels=20

Options:

  • --endpoint - gRPC endpoint with port 10000
  • --token - Authentication token from Quicknode
  • --coin - Trading pair (BTC, ETH, SOL, etc.)
  • --levels - Number of price levels (default: 20, max: 100)

Output:

============================================================
Streaming L2 Orderbook for BTC
Levels: 20
============================================================

Connecting to your-endpoint.hype-mainnet.quiknode.pro:10000...
โœ“ First L2 update received!

โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
Block: 904333900 | Time: 1771916717433 | Coin: BTC
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

ASKS:
62916 | 1.03755 | (5 orders)
62915 | 2.8545 | (1 orders)
62914 | 0.05136 | (3 orders)
...

โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
SPREAD: 1.00 (0.16 bps)
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

BIDS:
62906 | 4.42562 | (23 orders)
62905 | 0.20558 | (4 orders)
62904 | 0.11454 | (2 orders)
...

Messages received: 1

Understanding the Output:


  • Block/Time - Blockchain block number and timestamp
  • Asks - Sell orders (displayed high to low)
  • Spread - Difference between best bid and ask in basis points
  • Bids - Buy orders (displayed high to low)
  • Order Count - Number of individual orders at each price level

We โค๏ธ Feedback!โ€‹

If you have any feedback or questions about this documentation, let us know. We'd love to hear from you!

Share this doc