# hlz — Zig Tooling for Hyperliquid ## Benchmarks Measured on Apple M4, median of 10,000 iterations (100,000 for sub-µs ops). Take these with a grain of salt — different machines, different compilers, different results. ### Signing hlz uses Zig's stdlib secp256k1 by default. An optional custom GLV endomorphism path (`-Dfast-crypto=true`) with precomputed tables gives \~3.4x faster signing — see [Crypto Backends](#crypto-backends). | Operation | stdlib (default) | `-Dfast-crypto` | Rust SDK (alloy) | | ------------------- | ---------------- | --------------- | ---------------- | | Sign order | \~112 µs | \~34.6 µs | \~34.0 µs | | Sign cancel | \~112 µs | \~34.1 µs | \~34.3 µs | | Sign USD send | \~116 µs | \~34.4 µs | \~37.8 µs | | EIP-712 struct hash | \~291 ns | \~291 ns | \~1.2 µs | | keccak256 (32B) | \~125 ns | \~125 ns | \~125 ns | | msgpack encode | \~333 ns | \~333 ns | \~250 ns | Rust SDK column measured with [hypersdk](https://github.com/infinitefield/hypersdk) v0.2.5, `--release` profile, same machine and inputs. ### Crypto Backends The signing hot path is dominated by a single scalar multiplication `k·G` (computing the ECDSA nonce point). hlz offers two backends: **stdlib (default)** — Uses `std.crypto.ecc.Secp256k1.basePoint.mul()`. Constant-time, audited as part of Zig's standard library. Safe for all use cases including servers signing on behalf of users. **Custom GLV** (`-Dfast-crypto=true`) — Uses GLV endomorphism with precomputed affine tables and 5×52-bit field arithmetic (same representation as libsecp256k1 and RustCrypto/k256). \~3.4x faster because it halves the number of point doublings and avoids recomputing known multiples of the generator. The custom path is designed for constant-time behavior (branchless table lookups via `cMov`, fixed iteration count, mask-based sign flips), but has **not been verified with Valgrind/ctgrind**. For local CLI tools this is fine. For servers exposed to timing attacks, use the stdlib default. ```bash # Default: stdlib secp256k1 (safe for all use cases) zig build -Doptimize=ReleaseSmall # Opt-in: custom GLV (~3.4x faster signing) zig build -Doptimize=ReleaseSmall -Dfast-crypto=true ``` Both produce identical signatures — the switch only affects which algorithm computes the nonce point. ### Binary Size | Component | Size | | --------------------------------------- | ------- | | `hlz` (ReleaseSmall, stripped) | 891 KB | | `hlz-terminal` (ReleaseSmall, stripped) | 1036 KB | | SDK only (no HTTP/TLS) | 116 KB | ### Build Time Clean build takes \~3 seconds on Apple M4. Zig's compilation model helps here. ### Reproduce ```bash git clone https://github.com/dzmbs/hlz cd hlz zig build bench # stdlib (default) zig build bench -Dfast-crypto=true # custom GLV ``` ## App `App.zig` manages the frame lifecycle — the entry point for any TUI application. ### Usage ```zig const tui = @import("tui"); var app = try tui.App.init(); defer app.deinit(); // restores terminal to cooked mode while (true) { app.beginFrame(); // ... render to app.buf ... if (app.pollKey()) |key| { switch (key) { 'q' => break, else => {}, } } app.endFrame(); // flushes buffer diff to terminal } ``` ### API #### `App.init() -> !App` Initializes the terminal in raw mode and allocates the double buffer. #### `app.deinit()` Restores terminal to cooked mode. Always call this (use `defer`). #### `app.beginFrame()` Starts a new frame. Clears dirty state from previous frame. #### `app.endFrame()` Flushes the buffer diff to the terminal. Only changed cells are written. #### `app.pollKey() -> ?Key` Non-blocking key read. Returns `null` if no key is pressed. Uses `VTIME=0 VMIN=0` for truly non-blocking reads — no blocking, no busy-wait. #### `app.size() -> struct { rows: u16, cols: u16 }` Returns current terminal dimensions. #### `app.buf` The current frame buffer. Write to this during your render phase. ## Buffer `Buffer.zig` is a double-buffered cell grid for flicker-free terminal rendering. ### How It Works 1. **Write** to the current buffer (`buf`) 2. **Diff** against the previous buffer (`prev`) 3. **Emit** only changed cells to the terminal 4. **Swap** buffers This minimizes terminal I/O and eliminates flicker. ### Color System ```zig const Color = union(enum) { default, // Terminal default basic: u8, // 16-color (0-15) rgb: [3]u8, // 24-bit RGB }; // Comptime hex constructor const green = Color.hex(0x4ade80); const red = Color.hex(0xf87171); ``` ### Cell Style ```zig const Style = struct { fg: Color = .default, bg: Color = .default, bold: bool = false, dim: bool = false, }; ``` ### Performance Stats Every flush produces `Buffer.Stats`: | Field | Description | | --------------- | ------------------------------------------------ | | `cells_changed` | Number of cells that differ from previous frame | | `cursor_moves` | Number of cursor repositioning sequences emitted | | `style_emits` | Number of SGR (color/style) sequences emitted | | `write_bytes` | Total bytes written to terminal | | `flush_ns` | Time taken for the flush operation | ### Synchronized Updates Frames are wrapped in terminal synchronized update sequences: ``` \x1b[?2026h ← begin synchronized update ... cell data ... \x1b[?2026l ← end synchronized update ``` This tells the terminal to batch all changes and display them atomically, preventing partial-frame artifacts. ## TUI Framework hlz includes a standalone TUI framework for building terminal applications. It has no SDK dependency — you can use it for anything. ### Why We needed a small TUI layer for the trading terminal and interactive list views. \~1,800 lines, just enough to get the job done. ### Modules | Module | Lines | What it does | | ----------------------------- | ----- | ------------------------------------------------------ | | [App.zig](/tui/app) | 108 | Frame lifecycle — `beginFrame`, `endFrame`, `pollKey` | | [Buffer.zig](/tui/buffer) | 482 | Double-buffered cell grid, RGB color, diff-based flush | | [Terminal.zig](/tui/terminal) | 197 | Raw mode, terminal size, non-blocking input | | [Layout.zig](/tui/layout) | 193 | Two-pass constraint layout (fixed, min, ratio, fill) | | [List.zig](/tui/widgets) | 380 | Scrollable list with search, sort, pagination | | [Chart.zig](/tui/widgets) | 408 | Candlestick chart with Unicode half-blocks | ### How It Fits Together ``` App.init() ├── Terminal.init() enters raw mode └── Buffer.init() allocates cell grids loop: app.beginFrame() ├── Layout.horizontal/vertical() divide space ├── List.render(buf, region) draw widgets ├── Chart.render(buf, region) └── buf.putStr(...) direct cell writes app.endFrame() └── buf.flush() diff against prev, emit only changes app.deinit() └── Terminal.deinit() restores cooked mode ``` ### Design Choices * **No allocations in render** — all stack buffers and fixed arrays * **Static limits** — `MAX_ROWS=32`, `MAX_COLS=8`, `MAX_ITEMS=512`, `MAX_CANDLES=512` * **Double buffered** — write to `buf`, diff against `prev`, emit only changed cells * **Synchronized updates** — `\x1b[?2026h` ... `\x1b[?2026l` wrapping for atomic frame display * **Double buffered diff flush** — only changed cells get written to the terminal * **No dependencies** — pure Zig stdlib. Doesn't import the SDK or anything else. ### Quick Example ```zig const tui = @import("tui"); var app = try tui.App.init(); defer app.deinit(); while (true) { app.beginFrame(); const size = app.size(); app.buf.putStr(0, 0, "Hello from hlz TUI!", .{ .fg = .{ .rgb = .{ 0xf7, 0xa4, 0x1d } }, }); if (app.pollKey()) |key| { if (key == 'q') break; } app.endFrame(); } ``` ## Layout `Layout.zig` is a two-pass constraint layout engine for dividing terminal space. ### Constraints ```zig const Constraint = union(enum) { fixed: u16, // Exact number of cells min: u16, // Minimum cells ratio: f32, // Proportion of remaining space fill, // Take all remaining space }; ``` ### Usage ```zig const Layout = tui.Layout; // Horizontal split: 30-cell sidebar + fill const cols = Layout.horizontal(size.width, &[_]Constraint{ .{ .fixed = 30 }, .fill, }); // cols[0] = { .x = 0, .width = 30 } // cols[1] = { .x = 30, .width = remaining } // Vertical split: header + fill + status bar const rows = Layout.vertical(size.height, &[_]Constraint{ .{ .fixed = 1 }, .fill, .{ .fixed = 1 }, }); ``` ### Two-Pass Algorithm 1. **First pass**: Allocate fixed and min constraints 2. **Second pass**: Distribute remaining space to ratio and fill constraints No cassowary solver or constraint propagation — just direct arithmetic. Fast and predictable. ## Terminal `Terminal.zig` handles raw mode, terminal size detection, and input. ### Raw Mode Entering raw mode disables: * Line buffering (characters available immediately, not after Enter) * Echo (typed characters aren't displayed) * Signal handling for Ctrl+C (you handle it yourself) ```zig var term = try tui.Terminal.init(); // enters raw mode defer term.deinit(); // restores cooked mode ``` Always pair `init` with `deinit` via `defer`. If your program crashes without restoring cooked mode, the terminal will be unusable (run `reset` to fix). ### Terminal Size ```zig const size = term.getSize(); // size.rows, size.cols ``` ### Input The terminal uses a ring buffer for input and `VTIME=0 VMIN=0` for non-blocking reads. This means: * `pollKey()` returns immediately with `null` if no input * No busy-wait, no blocking * Escape sequences (arrow keys, function keys) are parsed into single key events ### Raw Mode Details On POSIX systems, raw mode sets: * `ICANON` off — no line buffering * `ECHO` off — no character echo * `ISIG` off — no signal generation from Ctrl+C etc * `VTIME=0, VMIN=0` — non-blocking reads ### Important * Always emit `\r\n` not just `\n` in raw mode (the terminal won't do CR for you) * Use hex literals for escape sequences: `"\x1b[2J"` not literal escape chars * Don't use `SO_RCVTIMEO` with TLS on macOS — it corrupts state ## Widgets ### List `List.zig` — Scrollable list with search, sort, and pagination. #### Features * **Vim-style navigation** — `j`/`k` or arrow keys * **Search** — `/` to filter, `Esc` to clear * **Sort** — `s` to cycle sort column * **Pagination** — `n`/`p` for next/previous page * **Static limits** — `MAX_ITEMS=512`, no dynamic allocation #### Used For * Market browser (`hlz markets`) * Perps list (`hlz perps`) * Spot list (`hlz spot`) * Funding rates (`hlz funding`) * Mid prices (`hlz mids`) ### Chart `Chart.zig` — Candlestick chart renderer using Unicode block characters. #### Features * **OHLCV candles** with body and wick rendering * **Auto-scaling** Y-axis based on visible data range * **Multiple timeframes** — 1m, 5m, 15m, 1h, 4h, 1d * **Color-coded** — Green for bullish, red for bearish candles * **Zero allocations** — Fixed-size candle buffer on stack #### Rendering Uses Unicode half-block characters (`▀`, `▄`, `█`) for sub-cell resolution, giving 2× vertical resolution compared to character-level rendering. ## Terminal Architecture The trading terminal is a \~2,200 line single-file module (`src/terminal/trade.zig`) using the numbered-section pattern. ### Thread Model | Thread | Rate | Reads | Writes | | ------------- | ---------- | ----------------------- | ------------------------------------ | | **UI** (main) | 4ms loop | Shared State (snapshot) | Buffer, Terminal | | **WS** | Blocking | WebSocket | Shared State (lock → write → unlock) | | **REST** | 500ms poll | HTTP | Shared State (lock → write → unlock) | **Shared State** holds: bids, asks, trades, candles, positions, orders, fills, asset context. Protected by a mutex with a generation counter for change detection. * UI thread snapshots shared state (lock → memcpy → unlock), then renders from the snapshot * Workers parse data outside the lock, then apply under the lock * UI thread **never** does network I/O. Workers **never** touch Buffer/Terminal. ### State Ownership #### UiState (UI thread only) ```zig const UiState = struct { focus: Panel, // Which panel has focus cursor: usize, // Cursor position in active list input_buf: [64]u8, // Text input buffer input_len: usize, // ... never shared, never locked }; ``` #### Shared (mutex-protected) ```zig const Shared = struct { mu: std.Thread.Mutex, gen: u64, // Bumped on every update bids: [64]BookLevel, asks: [64]BookLevel, trades: [128]Trade, candles: [512]Candle, // ... workers write, UI snapshots }; ``` #### Snapshot (immutable copy) ```zig fn takeSnapshot(shared: *Shared) Snapshot { shared.mu.lock(); defer shared.mu.unlock(); return .{ .gen = shared.gen, .bids = shared.bids, .asks = shared.asks, // ... memcpy under lock, then render freely }; } ``` ### Parse Outside Lock, Apply Under Lock Workers follow this pattern for minimal lock contention: ```zig fn decodeAndApplyBook(data: []const u8, shared: *Shared) void { // 1. Parse JSON (no lock held) const parsed = std.json.parseFromSlice(...) catch return; defer parsed.deinit(); // 2. Build result on stack var bids: [64]BookLevel = undefined; // ... fill from parsed data // 3. Apply under lock (fast memcpy only) shared.applyBook(bids, asks, n, max_cum); } ``` ### Rendering * **Double-buffered**: Write to `buf`, diff against `prev`, emit only changed cells * **Synchronized updates**: Frames wrapped in `\x1b[?2026h` ... `\x1b[?2026l` * **Incremental SGR**: Track fg/bg/bold/dim state, emit only diffs * **No allocations**: All rendering uses stack buffers and the pre-allocated Buffer grid ### Coin Switching When the user switches coins: 1. UI writes new coin to Shared 2. UI calls `shutdown()` on the WS socket file descriptor 3. WS thread detects the closed socket, reads new coin from Shared 4. WS thread reconnects with new subscriptions This avoids `SO_RCVTIMEO` which corrupts macOS TLS state. ## Trading Terminal `hlz-terminal` (or `hlz trade`) is a full-featured trading terminal for Hyperliquid. ### Launch ```bash hlz trade BTC # Open with BTC hlz trade ETH # Open with ETH hlz trade # Opens with default market ``` ### Features * **Candlestick chart** — Multiple timeframes (1m, 5m, 15m, 1h, 4h, 1d) * **Live order book** — Depth visualization with bid/ask spread * **Trade tape** — Real-time fills with size highlighting * **Order entry** — Place, modify, cancel from the terminal * **Position display** — Current positions with PnL * **Double-buffered rendering** — only changed cells written to terminal ### Architecture The terminal runs three threads: | Thread | Loop | Data | | ------------- | ----------------------------------- | -------------------------------- | | **UI** (main) | 4ms — `pollKey → snapshot → render` | Never blocks on I/O | | **WS** | Blocking reads | l2Book, trades, candle, assetCtx | | **REST** | 500ms poll | positions, orders, fills | All three communicate through **Shared State** protected by a mutex. Workers write, UI snapshots. **Key rule**: UI thread never does network I/O. Workers never touch the terminal buffer. ### State Model * **UiState** — Owned by UI thread. Cursor position, panel focus, input buffer. Never locked. * **Shared** — Written by workers, read by UI via snapshot. Locked with a mutex. * **Snapshot** — Immutable copy taken under lock once per frame. Render reads freely. ### Colors Uses the Hyperliquid color palette: * Green (#4ade80) for buys / positive PnL * Red (#f87171) for sells / negative PnL * Cyan for headers and highlights * Gray for secondary information ## Keybindings ### Navigation | Key | Action | | --------- | ------------------ | | `Tab` | Switch panel focus | | `↑` / `k` | Move up | | `↓` / `j` | Move down | | `←` / `h` | Scroll left | | `→` / `l` | Scroll right | | `/` | Search / filter | | `Enter` | Select | | `Esc` | Cancel / close | | `q` | Quit | ### Timeframes | Key | Timeframe | | --- | ---------- | | `1` | 1 minute | | `2` | 5 minutes | | `3` | 15 minutes | | `4` | 1 hour | | `5` | 4 hours | | `6` | 1 day | ### Market Switching | Key | Action | | ----------- | ------------ | | `c` | Change coin | | Type symbol | Quick search | ### Interactive Lists When viewing markets, perps, spot, or other list views: | Key | Action | | --------- | -------------------- | | `s` | Sort by column | | `n` / `p` | Next / previous page | | `/` | Search filter | | `Esc` | Clear filter | ## HTTP Client The SDK client provides typed access to all Hyperliquid HTTP endpoints. ### Creating a Client ```zig const Client = hlz.hypercore.client.Client; // Mainnet var client = Client.mainnet(allocator); defer client.deinit(); // Testnet var client = Client.testnet(allocator); defer client.deinit(); ``` ### Info Endpoints (No Auth) All info endpoints are public. They return `Parsed(T)` — call `.deinit()` when done. ```zig // All mid prices var mids = try client.getAllMids(null); defer mids.deinit(); // Market metadata + asset contexts var meta = try client.getMetaAndAssetCtxs(null); defer meta.deinit(); // Account state var state = try client.getClearinghouseState(address, null); defer state.deinit(); // Open orders var orders = try client.getOpenOrders(address, null); defer orders.deinit(); // User fills var fills = try client.getUserFills(address, null); defer fills.deinit(); // L2 order book var book = try client.getL2Book("BTC", null); defer book.deinit(); // Candle data var candles = try client.getCandleSnapshot("BTC", "1h", null); defer candles.deinit(); ``` #### Full List of Info Methods | Method | Returns | Description | | --------------------------- | ------------------------ | ----------------------------- | | `getAllMids` | Raw JSON | All mid prices (dynamic keys) | | `getMeta` | `Meta` | Market metadata | | `getMetaAndAssetCtxs` | `MetaAndAssetCtxs` | Meta + live context | | `getClearinghouseState` | `ClearinghouseState` | Account margin state | | `getOpenOrders` | `[]OpenOrder` | User's open orders | | `getUserFills` | `[]Fill` | User's recent fills | | `getOrderStatus` | `OrderStatus` | Status by OID | | `getL2Book` | `L2Book` | Order book snapshot | | `getCandleSnapshot` | `[]Candle` | OHLCV candles | | `getFundingHistory` | `[]FundingEntry` | Funding rate history | | `getSpotMeta` | `SpotMeta` | Spot market metadata | | `getSpotClearinghouseState` | `SpotClearinghouseState` | Spot balances | | `getPerpDexs` | `[]PerpDex` | HIP-3 DEX list | | `getUserFees` | `UserFees` | Fee rates | | `getReferral` | `Referral` | Referral info | | `getSubAccounts` | `[]SubAccount` | Sub-accounts | | `getPerpsAtOpenInterest` | Raw JSON | OI data | ### Exchange Endpoints (Signed) Exchange endpoints require a `Signer` and produce EIP-712 signatures. ```zig const signer = try Signer.fromHex("your_key"); const nonce = @as(u64, @intCast(std.time.milliTimestamp())); // Place order var result = try client.place(signer, batch_order, nonce, null, null); defer result.deinit(); // Cancel by OID var result = try client.cancel(signer, cancel_request, nonce, null, null); defer result.deinit(); // Cancel by CLOID var result = try client.cancelByCloid(signer, cancel_request, nonce, null, null); defer result.deinit(); // Modify order var result = try client.modify(signer, modify_request, nonce, null, null); defer result.deinit(); // Set leverage var result = try client.updateLeverage(signer, asset, leverage, nonce, null, null); defer result.deinit(); // Send USDC var result = try client.usdSend(signer, send_request, nonce); defer result.deinit(); ``` #### Full List of Exchange Methods | Method | Description | | ---------------------------------- | ------------------------------------- | | `place` | Place order(s) | | `cancel` | Cancel by OID | | `cancelByCloid` | Cancel by client order ID | | `modify` | Modify existing order | | `scheduleCancel` | Schedule future cancellation | | `updateLeverage` | Set leverage | | `updateIsolatedMargin` | Adjust isolated margin | | `sendUsdc` | Send USDC to address | | `spotSend` | Send spot tokens | | `sendAsset` | Send between contexts/dexes | | `withdraw` | Bridge withdrawal | | `usdClassTransfer` | Move USDC between spot ↔ perp | | `approveAgent` | Approve API wallet | | `approveBuilderFee` | Approve builder fee rate | | `tokenDelegate` | Stake/unstake to validator | | `vaultTransfer` | Deposit/withdraw from vault | | `createSubAccount` | Create sub-account | | `subAccountTransfer` | Transfer USDC to/from sub-account | | `subAccountSpotTransfer` | Transfer spot to/from sub-account | | `twapOrder` | Place TWAP order | | `twapCancel` | Cancel TWAP order | | `convertToMultisig` | Convert to multi-sig account | | `userDexAbstraction` | Enable/disable DEX abstraction | | `userSetAbstraction` | Set abstraction mode | | `spotDeployRegisterToken` | Register new spot token | | `spotDeployGenesis` | Initialize token genesis | | `spotDeployUserGenesis` | Distribute tokens to users | | `spotDeployRegisterSpot` | Register spot trading pair | | `spotDeployRegisterHyperliquidity` | Register hyperliquidity | | `spotDeployFreezeUser` | Freeze/unfreeze user for token | | `spotDeployTokenAction` | Generic token action (enable/disable) | | `perpDeployRegisterAsset` | Register perp asset | | `perpDeploySetOracle` | Set oracle prices | | `cValidatorRegister` | Register validator | | `cValidatorChangeProfile` | Update validator profile | | `cValidatorUnregister` | Unregister validator | | `cSignerJailSelf` | Jail validator signer | | `cSignerUnjailSelf` | Unjail validator signer | ### Raw vs Typed For `--json` passthrough, use raw methods. For typed access, use `get*` methods: ```zig // Typed (returns parsed struct) var typed = try client.getClearinghouseState(addr, null); defer typed.deinit(); // typed.value.marginSummary.accountValue... // Raw (returns HTTP body as string) var raw = try client.clearinghouseState(addr, null); defer raw.deinit(); // raw.body is the JSON string ``` **Rule**: Never fetch both. Branch on output format early. ## Decimal Math hlz includes a 38-digit decimal type for precise financial arithmetic. No floating-point errors. ### Creating Decimals ```zig const Decimal = hlz.math.decimal.Decimal; // From string (most common) const price = try Decimal.fromString("50000.5"); const size = try Decimal.fromString("0.001"); // Special values const zero = Decimal.ZERO; ``` ### Formatting ```zig var buf: [32]u8 = undefined; const str = price.toString(&buf); // "50000.5" ``` Smart formatting auto-scales by magnitude — no trailing zeros, appropriate decimal places. ### Why Not `f64`? Floating-point math produces rounding errors that are unacceptable for financial operations: ``` f64: 0.1 + 0.2 = 0.30000000000000004 Decimal: 0.1 + 0.2 = 0.3 ``` The Hyperliquid API uses string-encoded decimals. hlz's `Decimal` type preserves exact precision through the entire pipeline: parse → compute → sign → serialize. ## SDK Overview Zig library for Hyperliquid with typed API responses. Signing adapted from [zabi](https://github.com/Raiden1411/zabi)'s EIP-712 and ECDSA implementation. ### What's Included | Component | Details | | ---------------- | ------------------------------------------------ | | **Signing** | secp256k1 + EIP-712 (based on zabi) | | **HTTP Client** | 18 info + 12 exchange endpoints, typed responses | | **WebSocket** | 13 subscription types | | **Decimal Math** | 38-digit precision | | **MessagePack** | Byte-exact with Rust `rmp-serde::to_vec_named` | ### Module Structure ```zig const hlz = @import("hlz"); // Core primitives (lib/) hlz.crypto.signer // secp256k1 ECDSA + RFC 6979 hlz.crypto.eip712 // EIP-712 typed data hashing hlz.math.decimal // 38-digit decimal type hlz.encoding.msgpack // MessagePack encoder // Hyperliquid SDK (sdk/) hlz.hypercore.client // HTTP client hlz.hypercore.signing // Signing orchestration hlz.hypercore.types // Order types, BatchOrder, TimeInForce hlz.hypercore.ws // WebSocket subscriptions hlz.hypercore.response // 62 response types hlz.hypercore.tick // Price tick rounding ``` ### Quick Example ```zig const hlz = @import("hlz"); const Signer = hlz.crypto.signer.Signer; const Decimal = hlz.math.decimal.Decimal; const types = hlz.hypercore.types; const signing = hlz.hypercore.signing; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); const signer = try Signer.fromHex("your_64_char_hex_key"); // Build an order const order = types.OrderRequest{ .asset = 0, // BTC .is_buy = true, .limit_px = try Decimal.fromString("50000"), .sz = try Decimal.fromString("0.1"), .reduce_only = false, .order_type = .{ .limit = .{ .tif = .Gtc } }, .cloid = types.ZERO_CLOID, }; const batch = types.BatchOrder{ .orders = &[_]types.OrderRequest{order}, .grouping = .na, }; // Sign (34.5µs, zero allocs) const nonce = @as(u64, @intCast(std.time.milliTimestamp())); const sig = try signing.signOrder(signer, batch, nonce, .mainnet, null, null); // Submit var client = hlz.hypercore.client.Client.mainnet(allocator); defer client.deinit(); var result = try client.place(signer, batch, nonce, null, null); defer result.deinit(); } ``` ### Design Principles * **Explicit allocators** — Every function that allocates takes an `Allocator` parameter * **Type-safe responses** — All endpoints return `Parsed(T)` with proper Zig types * **Comptime where possible** — EIP-712 type hashes computed at compile time ## Signing hlz implements two signing paths, matching the Hyperliquid protocol. The secp256k1 and EIP-712 implementation is adapted from [zabi](https://github.com/Raiden1411/zabi). ### Signing Paths #### RMP Path (Orders, Cancels) Used for: `place`, `cancel`, `cancelByCloid`, `modify`, `batchModify`, `scheduleCancel` ``` Action → MessagePack → prepend nonce + vault → keccak256 → Agent EIP-712 (chainId 1337) ``` ```zig const signing = hlz.hypercore.signing; const sig = try signing.signOrder(signer, batch, nonce, .mainnet, null, null); // sig.r, sig.s, sig.v ready for JSON: {"r":"0x...","s":"0x...","v":27} ``` #### Typed Data Path (Transfers, Approvals) Used for: `sendUsdc`, `spotSend`, `sendAsset`, `withdraw`, `usdClassTransfer`, `approveAgent`, `approveBuilderFee`, `tokenDelegate`, `userDexAbstraction`, `userSetAbstraction`, `convertToMultiSig` ``` Fields → EIP-712 struct hash → Arbitrum domain (chainId 42161 mainnet / 421614 testnet) ``` ```zig const sig = try signing.signUsdSend(signer, destination, amount, nonce, .mainnet); ``` ### The Signer ```zig const Signer = hlz.crypto.signer.Signer; // From hex string (64 chars, no 0x prefix) const signer = try Signer.fromHex("abcdef1234..."); // The signer holds the private key and can produce ECDSA signatures // It uses RFC 6979 deterministic nonces (no randomness needed) ``` ### Chain Enum ```zig const Chain = signing.Chain; Chain.mainnet // Arbitrum One (42161) for typed data, 1337 for agent Chain.testnet // Arbitrum Sepolia (421614) for typed data, 1337 for agent ``` ### MessagePack Compatibility The msgpack encoding must be **byte-exact** with Rust's `rmp-serde::to_vec_named`. This means: * Named fields (not positional) * Specific integer encoding widths * Map ordering matches Rust struct field order * The `type` field is embedded inside the map (serde `#[serde(tag = "type")]`) ### EIP-712 Details All 14 EIP-712 type hashes are computed at **compile time** — no runtime string hashing or allocation. ### Crypto Backend By default, hlz uses Zig's stdlib secp256k1 (constant-time, safe for servers). For latency-sensitive use cases, opt into the custom GLV backend: ```bash zig build -Dfast-crypto=true # ~3.4x faster signing ``` | Backend | Median sign\_order | Use case | | ---------------- | ------------------ | ------------------------- | | stdlib (default) | \~112 µs | Servers, production | | custom GLV | \~34 µs | Trading bots, low-latency | The domain separator uses: * `name = "Exchange"` * `version = "1"` * `chainId` = 42161 (mainnet) or 421614 (testnet) for typed data * `chainId` = 1337 for agent-signed actions (orders, cancels) * `verifyingContract = 0x0000000000000000000000000000000000000000` ## Types Core types for orders, actions, and responses. ### Order Types #### `OrderRequest` ```zig const OrderRequest = struct { asset: u32, // Market index (0=BTC, 1=ETH, ...) is_buy: bool, limit_px: Decimal, // Price as 38-digit decimal sz: Decimal, // Size reduce_only: bool, order_type: OrderType, // Limit, trigger, or market cloid: Cloid, // Client order ID (optional) }; ``` #### `OrderType` ```zig const OrderType = union(enum) { limit: struct { tif: TimeInForce }, trigger: struct { trigger_px: Decimal, is_market: bool, tpsl: TpSl, }, }; ``` #### `TimeInForce` | Value | Meaning | | ---------------- | ------------------------------ | | `Gtc` | Good-til-cancelled | | `Ioc` | Immediate-or-cancel | | `Alo` | Add-liquidity-only (post-only) | | `FrontendMarket` | Market order (used internally) | #### `BatchOrder` ```zig const BatchOrder = struct { orders: []const OrderRequest, grouping: OrderGrouping, // .na, .normalTpSl, .positionTpSl }; ``` ### Response Types All 62 response types live in `response.zig`. Key ones: #### `ClearinghouseState` ```zig const ClearinghouseState = struct { marginSummary: MarginSummary, crossMarginSummary: MarginSummary, assetPositions: []AssetPosition, // ... }; ``` #### `OpenOrder` ```zig const OpenOrder = struct { coin: []const u8, side: []const u8, limitPx: []const u8, sz: []const u8, oid: u64, // ... }; ``` #### Response Conventions * All fields use `camelCase` matching JSON keys exactly * All fields have defaults for forward compatibility: `= ""`, `= 0`, `= Decimal.ZERO` * Parse options: `{ .ignore_unknown_fields = true, .allocate = .alloc_always }` * Returned as `Parsed(T)` — call `.deinit()` to free ### Decimal Type `Decimal` is a 38-digit decimal type used for all prices and sizes: ```zig const Decimal = hlz.math.decimal.Decimal; const price = try Decimal.fromString("50000.5"); const size = try Decimal.fromString("0.001"); // Arithmetic const total = price.mul(size); // Format to string (stack buffer, no allocation) var buf: [32]u8 = undefined; const str = total.toString(&buf); ``` ### Asset Index Resolution The SDK resolves human-readable asset names to numeric indices at runtime: ``` "BTC" → asset index 0 "ETH" → asset index 1 "PURR/USDC" → spot market index "xyz:BTC" → HIP-3 DEX market index ``` This resolution uses the metadata from `getMetaAndAssetCtxs()`. ## WebSocket Real-time market data and user event streaming via WebSocket. ### Subscription Types | Type | Data | Auth | | ----------------- | ---------------------------------- | ---- | | `trades` | Real-time trades | No | | `l2Book` | Order book updates | No | | `bbo` | Best bid/offer | No | | `candle` | Candlestick updates | No | | `allMids` | All mid prices | No | | `activeAssetCtx` | Asset context (funding, OI) | No | | `userEvents` | User fills, liquidations | Yes | | `userFills` | User trade fills | Yes | | `userFundings` | User funding payments | Yes | | `orderUpdates` | Order status changes | Yes | | `notification` | System notifications | Yes | | `webData2` | Extended web data | Yes | | `activeAssetData` | User asset data (leverage, margin) | Yes | ### Protocol Details #### Connection ``` wss://api.hyperliquid.xyz/ws (mainnet) wss://api.hyperliquid-testnet.xyz/ws (testnet) ``` #### Ping/Pong Hyperliquid uses **app-level** ping/pong (not WebSocket protocol-level): * Server sends: `Ping` (plain text) * Client must respond: `{"method":"pong"}` * Timeout: \~30 seconds without pong → disconnect #### Subscribe ```json {"method":"subscribe","subscription":{"type":"trades","coin":"BTC"}} ``` #### Unsubscribe ```json {"method":"unsubscribe","subscription":{"type":"trades","coin":"BTC"}} ``` #### Message Format All messages arrive as JSON with a channel wrapper: ```json {"channel":"trades","data":[{"coin":"BTC","side":"B","px":"97432.5","sz":"0.1","time":1234567890}]} ``` The SDK's `ws_types.extractData()` strips the outer wrapper — decode functions receive the `data` value directly. ### SDK Usage ```zig const Ws = hlz.hypercore.ws; // The WS module provides subscription type definitions // Actual WebSocket connection is managed by the terminal/CLI layer // using websocket.zig for the transport ``` ### Thread Safety * WebSocket reads are **blocking** — run in a dedicated thread * Use `shutdown(fd)` to break out of a blocking read (for coin/interval switches) * **Do not** use `SO_RCVTIMEO` with TLS on macOS (causes segfaults) * Parse messages outside the lock, apply results under the lock ## Configuration ### Authentication hlz needs a private key for trading operations. Market data commands work without auth. #### Encrypted Keystore (Recommended) ```bash # Generate a new key hlz keys new trading # Import an existing key hlz keys import trading --private-key 0xYOUR_KEY # Set as default hlz keys default trading # List all keys hlz keys ls ``` Keys are stored encrypted at `~/.hlz/keys/`. Use `--key-name` to select a specific key per command. #### Environment Variables | Variable | Description | | ------------- | ------------------------------------------------- | | `HL_KEY` | Private key (raw hex, no 0x prefix) | | `HL_PASSWORD` | Keystore password (for `--key-name`) | | `HL_ADDRESS` | Default wallet address (for read-only queries) | | `HL_CHAIN` | Default chain: `mainnet` (default) or `testnet` | | `HL_OUTPUT` | Default output format: `json`, `pretty`, or `csv` | | `NO_COLOR` | Disable colored output | #### Config Files hlz loads config from key=value files (same format as `.env`): **Project-level** — `.env` in the current directory: ```bash HL_KEY=your_private_key_hex HL_ADDRESS=0xYourAddress HL_CHAIN=mainnet ``` **System-level** — `~/.hl/config` (used as fallback if no `.env` found): ```bash HL_KEY=your_private_key_hex HL_ADDRESS=0xYourAddress HL_CHAIN=mainnet ``` Supported keys: `HL_KEY`, `TRADING_KEY`, `HL_ADDRESS`, `ADDRESS`, `HL_CHAIN`, `CHAIN`. #### Priority Order 1. Command-line flags (`--key`, `--key-name`, `--chain`) 2. Environment variables (`HL_KEY`, `HL_CHAIN`) 3. `.env` file in current directory 4. `~/.hl/config` file 5. Defaults (mainnet, no key) ### Testnet Add `--chain testnet` to any command: ```bash hlz buy BTC 0.1 @50000 --chain testnet ``` Or set globally: ```bash export HL_CHAIN=testnet ``` ### Output Formats | Flag | When | Format | | ----------------- | --------------------- | -------------------- | | (none, TTY) | Interactive terminal | Colored tables | | (none, piped) | `hlz price BTC \| jq` | JSON (auto-detected) | | `--json` | Explicit | JSON | | `--output pretty` | Explicit | Formatted tables | | `--output csv` | Explicit | CSV | | `--quiet` / `-q` | Minimal | Just the value | ## Getting Started hlz gives you three things: 1. **`hlz`** — A 38-command CLI for Hyperliquid (827KB) 2. **`hlz-terminal`** — A full trading terminal (768KB) 3. **hlz** — A Zig library for building your own tools ### Install the CLI ```bash curl -fsSL https://raw.githubusercontent.com/dzmbs/hlz/main/install.sh | sh ``` Or build from source: ```bash git clone https://github.com/dzmbs/hlz cd hlz zig build -Doptimize=ReleaseSmall # Binary at zig-out/bin/hlz ``` ### Configure Set your private key: ```bash # Option 1: Environment variable export HL_KEY="your_private_key_hex" # Option 2: Encrypted keystore (recommended) hlz keys new default # Enter password when prompted # Option 3: Config file echo 'HL_KEY=your_private_key_hex' > .env # Or system-wide: ~/.hl/config (same key=value format) ``` ### Your First Commands No authentication needed for market data: ```bash hlz price BTC # Current price + spread hlz funding --top 5 # Top funding rates hlz book ETH --live # Live order book ``` Place a trade: ```bash hlz buy BTC 0.1 @50000 # Limit buy 0.1 BTC at $50,000 hlz sell ETH 1.0 # Market sell 1 ETH ``` Check your account: ```bash hlz portfolio # Positions + balances hlz orders # Open orders ``` Launch the trading terminal: ```bash hlz trade BTC # Full TUI with chart, book, tape ``` ### Use as a Zig Library Add to your `build.zig.zon`: ```zig .dependencies = .{ .hlz = .{ .url = "git+https://github.com/dzmbs/hlz#main", }, }, ``` Then in your code: ```zig const hlz = @import("hlz"); const client = hlz.hypercore.client.Client.mainnet(allocator); defer client.deinit(); // Fetch all mid prices (no auth needed) var result = try client.getAllMids(null); defer result.deinit(); // result.value is a parsed response ``` ### Next Steps * [CLI commands](/cli) — Full command reference * [SDK guide](/sdk) — Building with the Zig library * [Trading terminal](/terminal) — Terminal keybindings and features * [Agent integration](/cli/agent-integration) — Using `hlz` in automated workflows ## Installation ### Quick Install Auto-detects OS and architecture, then installs to: * `/usr/local/bin` when run as root * `~/.local/bin` when run as a regular user ```bash curl -fsSL https://raw.githubusercontent.com/dzmbs/hlz/main/install.sh | sh ``` #### Install Options ```bash # Explicit install directory curl -fsSL https://raw.githubusercontent.com/dzmbs/hlz/main/install.sh | sh -s -- --bin-dir "$HOME/.local/bin" # System-wide install curl -fsSL https://raw.githubusercontent.com/dzmbs/hlz/main/install.sh | sh -s -- --system ``` ### Manual Download Download from [GitHub Releases](https://github.com/dzmbs/hlz/releases/latest): :::code-group ```bash [macOS (Apple Silicon)] curl -fsSL -o hlz https://github.com/dzmbs/hlz/releases/latest/download/hlz-darwin-arm64 chmod +x hlz && mv hlz "$HOME/.local/bin/" ``` ```bash [macOS (Intel)] curl -fsSL -o hlz https://github.com/dzmbs/hlz/releases/latest/download/hlz-darwin-x64 chmod +x hlz && mv hlz "$HOME/.local/bin/" ``` ```bash [Linux (x86_64)] curl -fsSL -o hlz https://github.com/dzmbs/hlz/releases/latest/download/hlz-linux-x64 chmod +x hlz && mv hlz "$HOME/.local/bin/" ``` ```bash [Linux (aarch64)] curl -fsSL -o hlz https://github.com/dzmbs/hlz/releases/latest/download/hlz-linux-arm64 chmod +x hlz && mv hlz "$HOME/.local/bin/" ``` ::: > If `~/.local/bin` is not in your PATH, add `export PATH="$HOME/.local/bin:$PATH"` to your shell profile. > **Static binaries** — Linux builds are statically linked (musl). Zero dependencies, runs on any distro. \~827KB. ### Build from Source Requires [Zig 0.15.2](https://ziglang.org/download/). ```bash git clone https://github.com/dzmbs/hlz cd hlz # Production build (827KB stripped binary) zig build -Doptimize=ReleaseSmall # Fastest execution (1.2MB) zig build -Doptimize=ReleaseFast # Debug build (fast compile) zig build ``` Binaries are output to `zig-out/bin/`. ### As a Zig Dependency Add to your `build.zig.zon`: ```zig .dependencies = .{ .hlz = .{ .url = "git+https://github.com/dzmbs/hlz#main", }, }, ``` Then in `build.zig`: ```zig const hlz_dep = b.dependency("hlz", .{ .target = target, .optimize = optimize, }); exe.root_module.addImport("hlz", hlz_dep.module("hlz")); ``` ### Verify ```bash hlz version hlz price BTC ``` ### Updating ```bash curl -fsSL https://raw.githubusercontent.com/dzmbs/hlz/main/install.sh | sh ``` Or from source: ```bash git pull && zig build -Doptimize=ReleaseSmall ``` ## Agent Payments Transfers on Hyperliquid are free and settle in under a second. No gas, no approval transactions. That makes it practical for agent-to-agent payments, even tiny ones. ### Setup ```bash # Generate a wallet for your agent hlz keys new agent # Export for non-interactive use export HL_KEY_NAME=agent export HL_PASSWORD=your_password ``` The printed address is where your agent receives funds. ### Funding the Wallet **Bridge from another chain** — [app.hyperliquid.xyz](https://app.hyperliquid.xyz) or [cctp.to](https://cctp.to) both work. Connect your wallet, pick a source chain (Ethereum, Arbitrum, Base, etc.), enter the amount, and send to your agent's address. Takes a few minutes. **Transfer from another Hyperliquid account** — if someone already has funds on Hyperliquid, they can send directly: ```bash hlz send 100 USDC 0xYourAgentAddress ``` ### Sending Payments ```bash # Send USDC to another address hlz send 10 USDC 0xRecipientAddress --json # Send HYPE tokens hlz send 5 HYPE 0xRecipientAddress --json # Exit code tells you what happened: 0 = ok, 4 = network error echo $? ``` ### Checking Balance ```bash hlz balance --json | jq '.accountValue' # Or just the number hlz balance -q ``` ### Spot vs Perps Balance USDC exists in two places on Hyperliquid: your perps balance (for trading) and your spot balance (for token transfers). Bridged funds arrive in spot. ```bash # Move bridged funds to perps for trading hlz send 100 USDC --to perp # Move back to spot for sending tokens hlz send 50 USDC --to spot ``` ### In a Script ```bash #!/bin/bash RESULT=$(hlz send "$1" "${3:-USDC}" "$2" --json 2>&1) if [ $? -eq 0 ]; then echo "Sent $1 ${3:-USDC} to $2" else echo "Failed: $RESULT" >&2 exit 1 fi ``` ### Supported Tokens USDC, HYPE, and any listed spot token: ```bash hlz spot --json | jq '.[].symbol' ``` ## Guides Step-by-step guides for common tasks. ### Getting Started * [Building a Trading Bot](/guides/trading-bot) — Automated order placement and monitoring * [Streaming Market Data](/guides/streaming) — Real-time data pipelines * [Agent Payments](/guides/agent-payments) — Using `hlz` for AI agent payments ### Common Patterns #### Quick Price Check → Trade ```bash PRICE=$(hlz price BTC -q) hlz buy BTC 0.1 @${PRICE} ``` #### Monitor → React ```bash hlz stream trades BTC | while read -r line; do echo "$line" | jq -r '.sz' | xargs -I {} sh -c ' if [ $(echo "{} > 10" | bc) -eq 1 ]; then echo "Whale alert: {} BTC" fi ' done ``` #### Portfolio Snapshot ```bash hlz portfolio --json > portfolio_$(date +%Y%m%d).json ``` #### Batch Execution ```bash cat <> btc_trades.jsonl # Rotate daily hlz stream trades BTC >> trades_$(date +%Y%m%d).jsonl ``` ### Filtering with jq ```bash # Only large trades (> 1 BTC) hlz stream trades BTC | jq 'select(.sz > 1)' # Only sells hlz stream trades BTC | jq 'select(.side == "S")' # Extract just price and size hlz stream trades BTC | jq '{px: .px, sz: .sz}' ``` ### Feeding to Other Programs ```bash # Python consumer hlz stream trades BTC | python3 my_analyzer.py # Custom Zig program hlz stream bbo ETH | ./my_strategy ``` ### Multi-Stream Run multiple streams in parallel: ```bash # Background streams hlz stream trades BTC > btc.jsonl & hlz stream trades ETH > eth.jsonl & hlz stream trades SOL > sol.jsonl & wait ``` ### User Event Streams Monitor your own activity (requires auth): ```bash # Your fills hlz stream fills 0xYourAddress # Your order updates hlz stream orders 0xYourAddress ``` ## Building a Trading Bot This guide shows how to build a simple trading bot using the `hlz` CLI and shell scripting. For complex bots, use the Zig SDK directly. ### Prerequisites ```bash # Install hlz curl -fsSL https://raw.githubusercontent.com/dzmbs/hlz/main/install.sh | sh # Set up your key hlz keys new bot export HL_KEY_NAME=bot export HL_PASSWORD=your_password export HL_OUTPUT=json ``` ### Simple Grid Bot Places buy and sell orders at fixed intervals around the current price: ```bash #!/bin/bash set -euo pipefail COIN="BTC" SIZE="0.01" LEVELS=5 SPREAD="50" # dollars between levels # Get current mid price MID=$(hlz price $COIN -q) echo "Mid price: $MID" # Place grid orders for i in $(seq 1 $LEVELS); do OFFSET=$(echo "$i * $SPREAD" | bc) BUY_PX=$(echo "$MID - $OFFSET" | bc) SELL_PX=$(echo "$MID + $OFFSET" | bc) hlz buy $COIN $SIZE @$BUY_PX --json hlz sell $COIN $SIZE @$SELL_PX --json done echo "Grid placed: $LEVELS levels, $SPREAD spread" ``` ### Monitor and Rebalance ```bash #!/bin/bash # Check positions every 30 seconds, rebalance if needed while true; do POS=$(hlz positions --json | jq -r '.[] | select(.coin == "BTC") | .szi') if [ -z "$POS" ]; then POS="0" fi # If position exceeds threshold, reduce if (( $(echo "$POS > 0.5" | bc -l) )); then echo "Position too large ($POS), reducing..." hlz sell BTC 0.1 --reduce-only --json elif (( $(echo "$POS < -0.5" | bc -l) )); then echo "Short too large ($POS), reducing..." hlz buy BTC 0.1 --reduce-only --json fi sleep 30 done ``` ### Using the Zig SDK For lower latency and more control, use the SDK directly: ```zig const hlz = @import("hlz"); const Client = hlz.hypercore.client.Client; const Signer = hlz.crypto.signer.Signer; const Decimal = hlz.math.decimal.Decimal; const types = hlz.hypercore.types; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); const signer = try Signer.fromHex("your_key"); var client = Client.mainnet(allocator); defer client.deinit(); // Fetch current mid price var mids = try client.getAllMids(null); defer mids.deinit(); // Build and sign order (34.5µs, zero allocs) const order = types.OrderRequest{ .asset = 0, .is_buy = true, .limit_px = try Decimal.fromString("50000"), .sz = try Decimal.fromString("0.01"), .reduce_only = false, .order_type = .{ .limit = .{ .tif = .Gtc } }, .cloid = types.ZERO_CLOID, }; const batch = types.BatchOrder{ .orders = &[_]types.OrderRequest{order}, .grouping = .na, }; const nonce = @as(u64, @intCast(std.time.milliTimestamp())); var result = try client.place(signer, batch, nonce, null, null); defer result.deinit(); } ``` ### Best Practices 1. **Always use `--dry-run` first** — Test your bot logic without real orders 2. **Set position limits** — Use `--reduce-only` to prevent runaway positions 3. **Handle all exit codes** — `3` = auth error, `4` = network error 4. **Use keystores** — Don't put private keys in scripts 5. **Rate limit yourself** — Hyperliquid allows 1200 req/min per IP ## Account Account commands show positions, orders, fills, and balances. They require an address — either via `--address`, `HL_ADDRESS`, config file, or derived from your key. ### `hlz portfolio [ADDR]` Combined view of positions and spot balances. ```bash hlz portfolio hlz portfolio 0x1234... # View another address ``` ### `hlz positions [ADDR]` Open perpetual positions. ```bash hlz positions hlz positions --json | jq '.[] | {coin, size, pnl}' ``` ### `hlz orders [ADDR]` Open orders. ```bash hlz orders hlz orders --json ``` ### `hlz fills [ADDR]` Recent trade fills. ```bash hlz fills hlz fills --json | jq '.[] | select(.coin == "BTC")' ``` ### `hlz balance [ADDR]` Account balance and margin health. ```bash hlz balance ``` ### `hlz status ` Check the status of a specific order by OID. ```bash hlz status 12345 hlz status 12345 --json ``` ### `hlz referral [set ]` View referral status or set a referral code. ```bash hlz referral # View current status hlz referral set MYCODE # Set referral code ``` ### `hlz withdraw [DESTINATION]` Bridge withdrawal to Arbitrum. ```bash hlz withdraw 100 # Withdraw to own address hlz withdraw 100 0xDest... # Withdraw to specific address ``` ### `hlz transfer --to-spot|--to-perp` Move USDC between spot and perp balances. ```bash hlz transfer 100 --to-spot hlz transfer 50 --to-perp ``` ### `hlz stake [ARGS]` Staking operations. ```bash hlz stake status # View staking summary hlz stake delegate 0xValidator... 1000000000000000000 # Delegate (wei) hlz stake undelegate 0xValidator... 1000000000000000000 # Undelegate (wei) ``` ### `hlz vault [info|deposit|withdraw] [ARGS]` Vault operations. ```bash hlz vault 0xVault... # View vault details (shorthand) hlz vault info 0xVault... # View vault details (explicit) hlz vault deposit 0xVault... 100 hlz vault withdraw 0xVault... 50 ``` ### `hlz subaccount [ARGS]` Sub-account management. Also accepts `hlz sub` as shorthand. ```bash hlz subaccount ls # List sub-accounts hlz subaccount create myaccount # Create sub-account hlz subaccount transfer 0xSub... 100 # USDC transfer (deposit) hlz subaccount transfer 0xSub... 100 --withdraw # USDC transfer (withdraw) hlz subaccount transfer 0xSub... 10 --token PURR # Spot token transfer ``` ### `hlz approve-agent
[--name NAME]` Approve an API agent wallet. ```bash hlz approve-agent 0xAgent... hlz approve-agent 0xAgent... --name my-bot ``` ### `hlz account [standard|unified|portfolio]` View or change account abstraction mode. Controls how spot and perp balances interact. * **standard** — Separate spot and perp wallets. You must `hlz transfer --to-perp` before trading perps. Required for builder code addresses. No daily action limit. * **unified** — Single balance per asset, shared across all perps and spot. Default on app.hyperliquid.xyz. 50k actions/day limit. * **portfolio** — Portfolio margin (pre-alpha). Most capital efficient, unifies HYPE/BTC/USDH/USDC as collateral. 50k actions/day limit. ```bash hlz account # Show current mode hlz account standard # Switch to standard (separate wallets) hlz account unified # Switch to unified (single balance) hlz account portfolio # Switch to portfolio margin ``` See [Account Abstraction Modes](https://hyperliquid.gitbook.io/hyperliquid-docs/trading/account-abstraction-modes) for full details. ### `hlz approve-builder ` Approve a builder fee rate. The rate is a percent string (e.g. `"0.001%"` = 0.001%). ```bash hlz approve-builder 0xBuilder... "0.001%" ``` ### HIP-3 DEX Queries Account commands support HIP-3 DEX filtering: ```bash hlz positions --dex xyz # Positions on a specific DEX hlz orders --all-dexes # Orders across all DEXes ``` ## Agent Integration hlz is designed for AI agents and automated workflows. Every command works non-interactively with structured output. ### Design Guarantees | Property | Guarantee | | ----------------------- | --------------------------------------------------- | | **No prompts** | Every command completes without user input | | **Structured output** | JSON when piped, `--json` flag always available | | **Semantic exit codes** | `0`=OK, `1`=error, `2`=usage, `3`=auth, `4`=network | | **Dry-run mode** | `--dry-run` previews any trade without submitting | | **Stdin batch** | Pipe order lists via `--stdin` | | **Deterministic** | Same inputs → same outputs (except market data) | ### Detecting Output Mode hlz auto-detects whether stdout is a TTY: ```bash # TTY: colored tables hlz positions # Piped: JSON automatically hlz positions | jq . # Explicit JSON hlz positions --json # Minimal output hlz price BTC -q # Just: 97432.5 ``` ### Agent Workflow Examples #### Check-then-trade ```bash PRICE=$(hlz price BTC -q) if (( $(echo "$PRICE < 50000" | bc -l) )); then hlz buy BTC 0.1 @${PRICE} --json fi ``` #### Monitor and react ```bash hlz stream trades BTC | while read -r line; do SIZE=$(echo "$line" | jq -r '.sz') if (( $(echo "$SIZE > 10" | bc -l) )); then echo "Large trade detected: $line" # React to whale trades fi done ``` #### Batch from file ```bash # orders.txt: # buy BTC 0.1 @98000 # buy ETH 1.0 @3400 # sell SOL 100 @180 cat orders.txt | hlz batch --stdin --json ``` #### Portfolio snapshot ```bash # Capture full state as JSON hlz portfolio --json > snapshot_$(date +%s).json hlz orders --json >> snapshot_$(date +%s).json ``` ### Environment Variables Configure everything via environment for CI/agents: ```bash export HL_KEY="private_key_hex" # Trading key export HL_ADDRESS="0x..." # Default address export HL_CHAIN="mainnet" # or testnet export HL_OUTPUT="json" # Always JSON export HL_PASSWORD="keystore_pass" # Keystore password ``` ### Error Handling In `--json` mode, errors are written to **stdout** as a structured JSON envelope — same stream as success output. Parse the `status` field to distinguish: ```bash hlz buy BTC 0.1 @50000 --json # stdout: {"v":1,"status":"error","cmd":"buy","error":"MissingKey","message":"","retryable":false,"hint":"set HL_KEY env var or pass --key","timing_ms":0} # Check status in your agent: RESULT=$(hlz buy BTC 0.1 @50000 --json) STATUS=$(echo "$RESULT" | jq -r '.status') if [ "$STATUS" = "error" ]; then echo "$RESULT" | jq -r '.hint' fi ``` Exit codes work in all modes: ```bash hlz buy INVALID 0.1 echo $? # 2 (usage error) ``` ### Rate Limits Hyperliquid API: 1200 requests/minute per IP. The CLI doesn't add any additional throttling — manage this in your agent logic. ### Agent-Approve Workflow For security, use a dedicated API wallet: ```bash # Generate a new API wallet hlz keys new api-agent # Approve it from your main wallet hlz approve-agent 0xAPI_WALLET_ADDRESS # Agent uses the API wallet (limited permissions) export HL_KEY_NAME=api-agent ``` ## CLI Overview `hlz` is a 38-command CLI for Hyperliquid. 827KB static binary, zero config required for market data. ### Design Principles * **Pipe-aware** — Tables on TTY, JSON when piped. No surprises. * **Agent-native** — Structured output, semantic exit codes, no interactive prompts. * **One binary** — Everything in 827KB. No runtime dependencies. * **Smart defaults** — Works out of the box. Power users customize. ### Command Categories | Category | Commands | Auth Required | | ------------------------------- | ---------------------------------------------------------------------------- | ------------------------------- | | [Market Data](/cli/market-data) | `price`, `mids`, `funding`, `book`, `perps`, `spot`, `dexes` | No | | [Trading](/cli/trading) | `buy`, `sell`, `cancel`, `modify`, `leverage`, `twap`, `batch` | Yes | | [Account](/cli/account) | `portfolio`, `positions`, `orders`, `fills`, `balance`, `status`, `referral` | Address only | | [Transfers](/cli/transfers) | `send` | Yes | | [Streaming](/cli/streaming) | `stream` | No (public) / Yes (user events) | | [Keys](/cli/keys) | `keys ls/new/import/export/default/rm` | No | | [TUI](/terminal) | `trade`, `markets` | Yes (trading) / No (viewing) | ### Global Flags ``` --output json|pretty|csv Output format (auto-json when piped) --json Shorthand for --output json --quiet, -q Minimal output (just result value) --chain mainnet|testnet Target chain --key Private key (prefer keystore) --key-name Use named keystore key --address User address for queries --dry-run, -n Preview trade without sending ``` ### Exit Codes | Code | Meaning | Example | | ---- | ------------- | ------------------------------ | | `0` | Success | Command completed | | `1` | Error | API error, invalid response | | `2` | Usage error | Bad arguments, unknown command | | `3` | Auth error | Missing key or address | | `4` | Network error | Connection refused, timeout | ### Asset Name Syntax hlz uses a unified asset syntax across all commands: | Format | Example | Description | | ------------ | ------------ | ---------------------------- | | `SYMBOL` | `BTC`, `ETH` | Perpetual on Hyperliquid DEX | | `BASE/QUOTE` | `PURR/USDC` | Spot market | | `dex:SYMBOL` | `xyz:BTC` | HIP-3 DEX perpetual | ## Key Management hlz stores keys in encrypted keystores, compatible with Foundry's format. ### Commands #### `hlz keys new ` Generate a new secp256k1 private key and store it encrypted. ```bash hlz keys new trading # Enter password: **** # Address: 0x1234...abcd # Stored in: ~/.hlz/keys/trading ``` #### `hlz keys import ` Import an existing private key. ```bash hlz keys import trading --private-key 0xYOUR_KEY # Enter password: **** ``` #### `hlz keys ls` List all stored keys. ```bash hlz keys ls # NAME ADDRESS DEFAULT # trading 0x1234...abcd ✓ # backup 0x5678...efgh ``` #### `hlz keys default ` Set the default key used when no `--key-name` is specified. ```bash hlz keys default trading ``` #### `hlz keys export ` Export the decrypted private key (use with caution). ```bash hlz keys export trading # Enter password: **** # 0xYOUR_PRIVATE_KEY ``` #### `hlz keys rm ` Remove a stored key. ```bash hlz keys rm backup ``` ### Security * Keys are encrypted with AES-128-CTR + scrypt KDF * Password is never stored * Use `HL_PASSWORD` env var for automation (be careful with shell history) * Consider `hlz approve-agent ` for API wallets with limited permissions ## Market Data All market data commands work without authentication. ### `hlz price ` Get the current price for any asset — perps, spot pairs, or HIP-3 DEX markets. **Resolution rules:** * `BTC` → perp on default dex (most liquid, USDC-settled) * `HYPE/USDC` → explicit spot pair (oracle-adjusted USD price via `tokenDetails`) * `xyz:AAPL` → perp on xyz dex * `HYPE --quote USDH` → spot pair HYPE/USDH * `HYPE --all` → every venue: perp + all spot quote pairs When only a perp is shown, a hint suggests `--all` for additional markets. **Flags:** * `--dex ` — target a specific HIP-3 DEX (e.g. `xyz`, `flx`) * `--quote ` — filter to a specific spot quote asset (e.g. `USDC`, `USDH`, `USDT0`, `USDE`) * `--all` — show all matching markets (perps + every spot pair) ```bash # Perps (default) hlz price BTC # → $65,000 (perp, default dex) hlz price ETH -q # → 1925.0 (quiet, just the number) # HIP-3 DEX perps hlz price xyz:AAPL # → $265 (stocks on xyz) hlz price BTC --dex flx # → BTC on flx (USDH collateral) # Spot pairs hlz price HYPE/USDC # → $27 (oracle USD price) hlz price UETH/USDC # → $1,925 (not the raw book unit price) hlz price HYPE --quote USDH # → HYPE/USDH spot price # All venues hlz price HYPE --all # Shows: HYPE perp, HYPE/USDC, HYPE/USDT0, HYPE/USDH, HYPE/USDE hlz price HYPE --all --json # [{"market":"HYPE","type":"perp","venue":"hl","price":27.08}, # {"market":"HYPE/USDC","type":"spot","venue":"USDC","price":27.12}, ...] ``` **Spot price accuracy:** For spot pairs, `price` uses the `tokenDetails` API which returns oracle-adjusted USD prices (same as the Hyperliquid web frontend). The raw `allMids` endpoint returns per-sz-unit book midpoints which can differ significantly for non-canonical tokens — use `hlz mids` if you need raw book data. **Perp collateral:** Different HIP-3 DEXes settle in different tokens. The default Hyperliquid dex uses USDC, but others may use USDH (e.g. Felix) or USDE. Prices on these venues are denominated in their respective collateral. Use `--dex` to target a specific venue, and `hlz dexes` to list available DEXes. ### `hlz mids [COIN]` Raw order book mid prices from the `allMids` API. Returns per-sz-unit midpoints — for human-friendly spot prices, use `hlz price` instead. Spot pairs show as human-readable names (e.g. `PURR/USDC`), not raw `@index` keys. ```bash hlz mids # Top 20 by default hlz mids --all # All markets hlz mids --page 2 # Page 2 hlz mids BTC # Filter to BTC-related # JSON output is a {coin: price} object hlz mids BTC --json # {"BTC":"65000.5","UBTC/USDC":"65012.3"} hlz mids --json | jq '.BTC' ``` ### `hlz funding [--top N]` Funding rates with visual heat bars. `--json` returns a clean filtered array. ```bash hlz funding # Top 20 by absolute rate hlz funding --top 5 # Top 5 hlz funding --filter ETH # Search hlz funding --top 3 --json # [{"coin":"MAVIA","funding":0.00081,"annualized":718.04,"mark":"0.033"},...] ``` ### `hlz book [--live]` L2 order book. Use `--live` for real-time WebSocket updates. Returns exit 1 if the coin doesn't exist. ```bash hlz book BTC # Snapshot hlz book ETH --live # Live updating (Ctrl+C to exit) ``` ### `hlz perps [--dex xyz]` List perpetual markets. ```bash hlz perps # Hyperliquid native markets hlz perps --dex xyz # HIP-3 DEX markets hlz perps --all # All DEXes combined hlz perps --filter BTC # Search markets ``` ### `hlz spot [--all]` List spot markets. ```bash hlz spot # Top spot markets hlz spot --all # All spot markets ``` ### `hlz dexes` List available HIP-3 DEXes with their collateral tokens. ```bash hlz dexes ``` ## Streaming Real-time WebSocket streams. Data flows continuously until you Ctrl+C. ### `hlz stream ` #### Market Data Streams (No Auth) ```bash hlz stream trades BTC # Real-time trades hlz stream bbo BTC # Best bid/offer updates hlz stream book ETH # L2 order book updates hlz stream candles BTC # Candlestick updates hlz stream mids # All mid price updates ``` #### User Streams (Requires Auth) ```bash hlz stream fills 0xAddr # User's trade fills hlz stream orders 0xAddr # User's order updates ``` #### Output Each message is a JSON line, making it easy to pipe: ```bash # Log trades to file hlz stream trades BTC >> btc_trades.jsonl # Filter large trades hlz stream trades BTC --json | jq 'select(.sz > 1)' # Feed to another program hlz stream bbo ETH | my_trading_bot ``` #### WebSocket Details * Server sends `Ping`, client responds with `{"method":"pong"}` * Reconnects automatically on connection loss * Messages are JSON, one per line * Supports all 13 Hyperliquid subscription types ## Trading All trading commands require authentication. See [Configuration](/introduction/configuration). ### Placing Orders #### `hlz buy [@PRICE]` ```bash # Market buy (executes immediately at best available price) hlz buy BTC 0.1 # Limit buy at $50,000 hlz buy BTC 0.1 @50000 # With take-profit and stop-loss hlz buy BTC 0.1 @50000 --tp 55000 --sl 48000 # Reduce-only order hlz sell BTC 0.1 --reduce-only # Maker-only (post-only) hlz buy ETH 1.0 @3500 --tif alo ``` #### Spot Orders Use `BASE/QUOTE` syntax for spot markets: ```bash hlz buy PURR/USDC 100 @0.065 # Limit buy 100 PURR hlz sell HYPE/USDC 1.0 # Market sell HYPE hlz buy HPL/USDH 0.5 @500 # Buy on non-USDC pair ``` Spot pairs are resolved via the spot universe. Transfer USDC to spot balance first with `hlz send USDC --to spot`. #### `hlz sell [@PRICE]` Same syntax as `buy`, but sells. ```bash hlz sell ETH 1.0 @3500 # Limit sell hlz sell SOL 10 # Market sell ``` #### Trigger Orders ```bash # Take-profit trigger at $55,000 hlz sell BTC 0.1 --trigger-above 55000 # Stop-loss trigger at $48,000 hlz sell BTC 0.1 --trigger-below 48000 ``` #### Dry Run Preview any order without submitting: ```bash hlz buy BTC 0.1 @50000 --dry-run # Shows the signed order payload without sending ``` ### Managing Orders #### `hlz cancel [OID]` ```bash hlz cancel BTC 12345 # Cancel specific order hlz cancel BTC # Cancel all BTC orders hlz cancel --all # Cancel all open orders ``` #### `hlz modify ` ```bash hlz modify BTC 12345 0.2 51000 # Change size and price ``` ### Leverage #### `hlz leverage [N]` ```bash hlz leverage BTC # Query current leverage hlz leverage BTC 10 # Set to 10x ``` ### Advanced #### `hlz twap buy|sell --duration