PERP.WIKI
Home / Learn / Hyperliquid TypeScript SDK Guide 2026 — Build on Hyperliquid with TS/JS
Guides11 min readUpdated 2026-03-18

Hyperliquid TypeScript SDK Guide 2026 — Build on Hyperliquid with TS/JS

Complete guide to the Hyperliquid TypeScript SDK: installation, authentication, placing orders, reading market data, WebSocket subscriptions, and building trading bots on Hyperliquid.

Introduction

The @nktkas/hyperliquid package is the leading TypeScript SDK for the Hyperliquid API. It provides a fully typed, minimal-dependency interface for reading market data, executing trades, and subscribing to real-time updates — everything you need to build trading bots, analytics dashboards, or custom trading interfaces on Hyperliquid.

The SDK is community-maintained, MIT-licensed, and works across all major JavaScript runtimes: Node.js, Deno, Bun, and browsers. It integrates with popular Ethereum libraries like viem and ethers.js for wallet management and EIP-712 signature generation. For background on the underlying API, see our Hyperliquid API Guide.

Installation

The SDK is published on both npm and JSR. Install it with your preferred package manager:

# npm / pnpm / yarn
npm i @nktkas/hyperliquid

# Deno
deno add jsr:@nktkas/hyperliquid

# Bun
bun add @nktkas/hyperliquid

You will also need viem (recommended) or ethers for wallet operations if you plan to place trades. For read-only market data, no wallet library is needed.

npm i viem

Authentication

The SDK uses two distinct client types that mirror Hyperliquid's two-API architecture. The InfoClient accesses the public Info API — no authentication required. The ExchangeClient accesses the authenticated Exchange API and requires a wallet (private key) for EIP-712 signing.

Setting up an authenticated client with viem:

import { ExchangeClient, HttpTransport } from "@nktkas/hyperliquid";
import { privateKeyToAccount } from "viem/accounts";

// Load your private key from environment variables — never hardcode it
const wallet = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);

const transport = new HttpTransport(); // mainnet by default
const exchange = new ExchangeClient({ transport, wallet });
!Security best practice
Use an API-only wallet, not your main wallet. Hyperliquid lets you authorize a separate wallet address that can trade on your account but cannot withdraw funds. If your API wallet key is compromised, your funds remain safe.

Connecting to testnet: Pass the testnet URL to the transport constructor to develop and test without risking real funds. Request testnet USDC through the Hyperliquid Discord.

const transport = new HttpTransport({
  url: "https://api.hyperliquid-testnet.xyz",
});

Core Concepts

The SDK is organized around three client classes, each paired with a transport layer:

ClientTransportPurpose
InfoClientHttpTransportRead market data, account state, order books (public, no auth)
ExchangeClientHttpTransportPlace orders, cancel, update leverage, withdraw (requires wallet)
SubscriptionClientWebSocketTransportReal-time streaming: order book updates, trades, fills

Transport layer. The transport handles the actual HTTP or WebSocket connection. HttpTransport connects to the REST API (default: https://api.hyperliquid.xyz). WebSocketTransport connects to the WebSocket endpoint (wss://api.hyperliquid.xyz/ws). Both accept an optional url parameter for testnet or custom endpoints.

Asset identifiers. Hyperliquid identifies perpetual markets by numeric index (e.g., BTC = 0, ETH = 1) in the Exchange API, and by string symbol (e.g., “BTC”, “ETH”) in the Info API. The meta endpoint returns the full mapping of asset indices to symbols.

Reading Market Data

The InfoClient provides methods for all public market data queries. No authentication is needed.

import { HttpTransport, InfoClient } from "@nktkas/hyperliquid";

const transport = new HttpTransport();
const info = new InfoClient({ transport });

// Get mid prices for all markets
const mids = await info.allMids();
console.log("BTC mid:", mids["BTC"]);

// Get L2 order book for BTC
const book = await info.l2Book({ coin: "BTC" });
console.log("Best bid:", book.levels[0][0]);
console.log("Best ask:", book.levels[1][0]);

// Get a user's open orders
const orders = await info.openOrders({ user: "0x..." });

// Get user positions and account state
const state = await info.clearinghouseState({ user: "0x..." });
console.log("Account value:", state.marginSummary.accountValue);

// Get market metadata (symbols, leverage limits, tick sizes)
const meta = await info.meta();
meta.universe.forEach((asset) => {
  console.log(asset.name, "max leverage:", asset.maxLeverage);
});

// Get funding rate history
const funding = await info.fundingHistory({
  coin: "BTC",
  startTime: Date.now() - 86400000, // last 24 hours
});

The Info API rate limit is 1,200 requests per minute per IP. For real-time data, use WebSocket subscriptions instead of polling to conserve your rate limit budget.

Placing Orders

The ExchangeClient handles all authenticated trading actions. Each order is signed with your wallet's private key using EIP-712 — the SDK handles this automatically.

Limit order:

const result = await exchange.order({
  orders: [{
    a: 0,        // asset index (0 = BTC)
    b: true,     // buy = true, sell = false
    p: "95000",  // price as string
    s: "0.01",   // size as string
    r: false,    // reduce-only
    t: { limit: { tif: "Gtc" } }, // Good-til-canceled
  }],
  grouping: "na",
});

console.log("Order status:", result.response.data.statuses);

Order parameters explained: The a field is the numeric asset index from the meta endpoint. The b field is a boolean for direction (true = buy/long, false = sell/short). Prices and sizes are passed as strings to avoid floating-point precision issues. The t field specifies the order type — limit with a time-in-force of Gtc (good-til-canceled), Alo (add-liquidity-only, i.e. post-only), or Ioc (immediate-or-cancel, for market orders).

Market order (using IOC with aggressive price):

// Market buy: use IOC with a price above the current ask
const result = await exchange.order({
  orders: [{
    a: 0,
    b: true,
    p: "100000",  // set well above market to ensure fill
    s: "0.01",
    r: false,
    t: { limit: { tif: "Ioc" } },
  }],
  grouping: "na",
});

Take-profit and stop-loss:

// Take-profit at $100,000
await exchange.order({
  orders: [{
    a: 0,
    b: false,     // sell to close long
    p: "100000",
    s: "0.01",
    r: true,      // reduce-only
    t: { trigger: { triggerPx: "100000", isMarket: true, tpsl: "tp" } },
  }],
  grouping: "na",
});

// Stop-loss at $90,000
await exchange.order({
  orders: [{
    a: 0,
    b: false,
    p: "90000",
    s: "0.01",
    r: true,
    t: { trigger: { triggerPx: "90000", isMarket: true, tpsl: "sl" } },
  }],
  grouping: "na",
});

Batch ordering: You can place up to 20 orders in a single API call by adding multiple entries to the orders array. This is more efficient and counts as a single request against rate limits.

Canceling orders:

// Cancel by order ID
await exchange.cancel({
  cancels: [{ a: 0, o: 123456 }], // asset index + order ID
});

Managing Positions

Use the InfoClient to read position data and the ExchangeClient to modify positions.

// Get current positions
const state = await info.clearinghouseState({ user: wallet.address });
const positions = state.assetPositions
  .filter((p) => parseFloat(p.position.szi) !== 0);

positions.forEach((p) => {
  console.log(
    p.position.coin,
    "size:", p.position.szi,
    "entry:", p.position.entryPx,
    "uPnL:", p.position.unrealizedPnl
  );
});

// Update leverage for BTC
await exchange.updateLeverage({
  asset: 0,
  isCross: true,  // true = cross margin, false = isolated
  leverage: 5,
});

// Close a position (reduce-only market order)
await exchange.order({
  orders: [{
    a: 0,
    b: false,       // opposite direction to close
    p: "80000",     // aggressive price for IOC fill
    s: "0.01",      // exact position size
    r: true,        // reduce-only
    t: { limit: { tif: "Ioc" } },
  }],
  grouping: "na",
});

// Withdraw USDC to Arbitrum
await exchange.withdraw3({
  destination: "0x...", // your Arbitrum address
  amount: "100",
});

WebSocket Subscriptions

The SubscriptionClient uses WebSocketTransport to stream real-time data. This is essential for trading bots — polling REST endpoints introduces latency and wastes rate limit budget.

import { SubscriptionClient, WebSocketTransport } from "@nktkas/hyperliquid";

const transport = new WebSocketTransport();
const subs = new SubscriptionClient({ transport });

// Stream mid prices for all markets
await subs.allMids((data) => {
  console.log("BTC mid:", data.mids["BTC"]);
});

// Stream L2 order book for ETH
await subs.l2Book({ coin: "ETH" }, (data) => {
  const bestBid = data.levels[0][0];
  const bestAsk = data.levels[1][0];
  console.log(`ETH spread: ${bestBid.px} / ${bestAsk.px}`);
});

// Stream your order fills
await subs.userFills({ user: "0x..." }, (data) => {
  data.fills.forEach((fill) => {
    console.log(`Filled: ${fill.coin} ${fill.side} ${fill.sz} @ ${fill.px}`);
  });
});

// Stream your open orders
await subs.openOrders({ user: "0x..." }, (data) => {
  console.log("Open orders:", data.length);
});
Reconnection
The WebSocketTransport handles reconnection automatically. However, for production bots you should implement additional logic to reconcile state after a reconnection — query the REST API for current positions and open orders to ensure your local state matches on-chain reality.

Error Handling & Rate Limits

The SDK throws standard JavaScript errors when API calls fail. Wrap your calls in try/catch blocks and handle common failure modes:

try {
  const result = await exchange.order({
    orders: [{ a: 0, b: true, p: "95000", s: "0.01", r: false,
               t: { limit: { tif: "Gtc" } } }],
    grouping: "na",
  });

  // Check individual order statuses
  for (const status of result.response.data.statuses) {
    if ("error" in status) {
      console.error("Order rejected:", status.error);
    }
  }
} catch (err) {
  console.error("API call failed:", err);
}
APIRate LimitWindowNotes
Info API (REST)1,200 requests1 minutePer IP address
Exchange API100 requests10 secondsPer wallet address; batch up to 20 orders/request
WebSocketNo message limitContinuousMax 100 subscriptions per connection

Track your request count locally and throttle proactively rather than waiting for 429 errors. Use batch ordering to place multiple orders in a single request. Prefer WebSocket subscriptions over REST polling for real-time data.

Building a Simple Trading Bot

Here is a minimal example that combines the SDK's three clients into a simple spread-monitoring bot that places a limit buy when the bid-ask spread exceeds a threshold:

import {
  InfoClient,
  ExchangeClient,
  SubscriptionClient,
  HttpTransport,
  WebSocketTransport,
} from "@nktkas/hyperliquid";
import { privateKeyToAccount } from "viem/accounts";

const wallet = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);

// Set up clients
const httpTransport = new HttpTransport();
const wsTransport = new WebSocketTransport();

const info = new InfoClient({ transport: httpTransport });
const exchange = new ExchangeClient({ transport: httpTransport, wallet });
const subs = new SubscriptionClient({ transport: wsTransport });

// Configuration
const ASSET = 0;          // BTC
const COIN = "BTC";
const SIZE = "0.001";
const SPREAD_THRESHOLD = 5; // $5 spread

// Monitor order book and place orders when spread is wide
await subs.l2Book({ coin: COIN }, async (data) => {
  const bestBid = parseFloat(data.levels[0][0].px);
  const bestAsk = parseFloat(data.levels[1][0].px);
  const spread = bestAsk - bestBid;

  if (spread > SPREAD_THRESHOLD) {
    const buyPrice = (bestBid + 1).toFixed(0); // $1 above best bid
    try {
      await exchange.order({
        orders: [{
          a: ASSET,
          b: true,
          p: buyPrice,
          s: SIZE,
          r: false,
          t: { limit: { tif: "Gtc" } },
        }],
        grouping: "na",
      });
      console.log(`Placed buy at ${buyPrice}, spread was ${spread.toFixed(2)}`);
    } catch (err) {
      console.error("Order failed:", err);
    }
  }
});

console.log("Bot running — monitoring", COIN, "order book...");
!Not financial advice
This example is for educational purposes only. A production trading bot requires proper risk management, circuit breakers, position sizing, error recovery, and thorough testnet validation before trading with real funds.

For more on automated trading strategies and tools, see our Best Hyperliquid Trading Bots guide and Hyperliquid Trading Strategies.

Ready to explore Guides?

Browse projects, compare protocols, and dive deeper into the Hyperliquid ecosystem.

Bookmark perp.wiki for the latest Hyperliquid ecosystem coverage.