Public TypeScript client for public strategy vaults that trade Hyperliquid validator markets, HIP-3 perpetuals and HIP-4 outcomes through one vault-owned account.
The SDK intentionally contains only public client code, API types, contract ABIs and examples. Agent signing, custody, execution policy, reconciliation, deployment configuration and infrastructure remain private platform concerns.
Pre-1.0 software. Testnet integration is available; do not use this package to custody production funds until the platform's audits and public launch gates are complete.
| Goal | Start here |
|---|---|
| Add vault discovery, followers and leader trading to another terminal | docs/INTEGRATION_GUIDE.md |
| Connect a leader wallet and reuse an existing order ticket | docs/MANUAL_TRADING.md |
| Run a bot, AI agent, MCP tool or strategy | docs/API_AND_AGENT_TRADING.md |
| Implement deposits, claims and redemptions | docs/FOLLOWER_LIFECYCLE.md |
| Use Python | python/README.md |
| Generate another client or inspect endpoints | openapi/v1.yaml |
| Verify an integration | docs/TESTING.md |
The terminal manifest is the only bootstrap contract. It advertises the network, contracts, direct Hyperliquid read endpoints, execution gateway, builder policy and safety gates so clients do not hard-code deployments.
The GitHub repository is public. Publishing the scoped package to npm is a separate release step and has not happened yet. For development:
git clone https://github.com/InTheta/hl-vault-sdk.git
cd hl-vault-sdk
npm install
npm run checkThe clients use the standard Fetch API. The optional hl-vault-mcp executable
uses the official MCP TypeScript SDK and Zod for validated tool inputs.
import { PublicVaultClient } from "@intheta/hl-vault-sdk";
const vaults = new PublicVaultClient({
baseUrl: "https://vault-api.example.com",
});
const list = await vaults.vaults();
const selected = await vaults.vault(list.vaults[0].address);
const performance = await vaults.performance(selected.address);Accounting fields are explicit. tvl_usdc is the reported contract NAV;
core_spot_usdc and Core risk fields are live trading-account observations.
Applications should treat performance as authoritative only when
contract_nav_authoritative is true.
import { LeaderTradingClient } from "@intheta/hl-vault-sdk";
const trading = new LeaderTradingClient({
baseUrl: "https://trade.example.com",
token: process.env.HL_VAULT_TOKEN,
});
const policy = await trading.capabilities();
const account = await trading.account();
const result = await trading.placeOrder({
market: policy.allowed_markets[0],
side: "buy",
limit_px: 50,
size: 0.24,
tif: "Alo",
});Bot tokens are secrets. Never ship them in browser bundles or public configuration. Public Hyperliquid reads and WebSocket subscriptions may go directly to Hyperliquid using the vault address; authenticated writes use the gateway so risk policy and the mandatory builder code are applied before the vault agent signs.
No public order method accepts a builder override. The executor injects the configured builder after validating the vault, action, market and notional, so omitting or changing the fee in a caller cannot bypass it.
The client adds a UUID client_order_id before every order write when the
caller omits one. For retry-safe automation, persist the resulting intent and
reuse the same UUID after an ambiguous timeout; generating a fresh ID describes
a fresh order, not a retry.
Market-style execution is always an IOC limit with an explicit reference price and slippage ceiling; the SDK does not expose an unbounded market order:
const order = buildBoundedMarketOrder({
market: "SOL",
side: "buy",
referencePrice: 150,
size: 0.1,
maxSlippageBps: 30,
});
await trading.placeOrder(order);buildScaledOrders() creates a two-to-20-level price ladder.
buildBasketOrders() converts weighted legs into bounded IOC orders.
placeOrderBatch() sends either plan through one explicit REST call and one
builder-tagged Hyperliquid batch. The executor applies the aggregate vault
notional cap and each delegated agent limit before signing. See
examples/simple-market-order.ts, examples/scaled-orders.ts and
examples/basket-orders.ts.
Keep ALO-only batches separate from IOC/GTC batches so they retain Hyperliquid's validator priority treatment.
For recovery, cancelAllOrders() first signals matching managed TWAPs to stop,
then reads the vault's authoritative open-order set and cancels it in bounded
batches. It is idempotent when no orders are open. Leader/operator sessions can
remove legacy orders after a policy change; delegated agents remain restricted
to the markets in their signed grant:
await trading.cancelAllOrders({ markets: ["SOL"] });See examples/emergency-cancel-all.ts. Cancel-all cannot open exposure, move
funds or change account authority.
Leaders can use the same public Omni data plane as the terminal without gaining access to private node addresses or platform infrastructure:
import { OmniDataPlaneClient } from "@intheta/hl-vault-sdk";
const data = new OmniDataPlaneClient({
baseUrl: "https://data.omniterminal.app",
apiKey: process.env.OMNI_DATA_API_TOKEN,
});
const [news, liquidations, book] = await Promise.all([
data.news("BTC"),
data.liquidationStats("hyperliquid", "BTC", "aggregate"),
data.orderbook("BTC", 100),
]);
const strongestLevels = extractLiquidationLevels(liquidations, 10);The data token, when required by the selected plan, is read-only and separate from the vault leader trading token.
liquidationStats() and the x402 market-risk/market-snapshot methods return
exported TypeScript contracts by default. extractLiquidationLevels() converts
Omni's long/short bucket payload into finite, notional-ranked price levels and
ignores malformed or zero-sized buckets.
OmniX402Client accepts a caller-supplied payment-enabled Fetch implementation.
The SDK never accepts a payer private key or decides an agent's spending policy:
import { OmniX402Client } from "@intheta/hl-vault-sdk";
const intelligence = new OmniX402Client({
baseUrl: "https://omniterminal.app",
fetch: fetchWithPayment,
});
const risk = await intelligence.marketRisk("SOL");
const carry = await intelligence.marketCarry("SOL");Omni's x402 MCP is a paid data surface only. Payment credentials never grant vault execution authority.
examples/x402-risk-gated-agent.ts demonstrates this separation with two
clients: a caller-supplied payment-enabled fetch for intelligence and an opaque,
revocable vault-agent token for execution. Neither credential is forwarded to
the other service.
Use the vault contract address as user for direct Hyperliquid subscriptions.
Writes still terminate at the builder-enforcing gateway:
const stream = createReconnectingVaultUserStream({
url: "wss://api.hyperliquid-testnet.xyz/ws",
vault: policy.vault,
subscriptions: [
{ type: "webData3" },
{ type: "orderUpdates" },
{ type: "userFills", aggregateByTime: true },
],
onStatus: console.log,
onMessage: console.log,
});Browsers and Node versions with a global WebSocket work directly. Other
server runtimes pass a webSocketFactory. The reconnecting controller uses
bounded exponential backoff, resubscribes on every open and stops permanently
when close() is called. Production strategies should still add heartbeat,
snapshot de-duplication and stale-state guards. See examples/vault-websocket.ts.
pointsPreview() models the current anti-gaming rules using settled fill
volume and time-weighted capital. The output is explicitly provisional and
token_entitlement is always false; points do not promise a token or airdrop.
Verified testnet points carry into the first mainnet season at 20%, capped at
50,000 points and subject to anti-Sybil review. Use
pointsCarryoverPreview() to inspect the deterministic policy.
const session = await trading.authorizeLeader(address, async (message) => {
return walletClient.signMessage({ account: address, message });
});The callback signs a short-lived EIP-191 challenge. The returned session is vault-scoped and should remain in memory only. It does not grant fund-transfer or account-administration authority.
The leader can sign a narrower agent session without sharing their wallet, operator bearer, or Hyperliquid API-agent key:
const agent = await trading.delegateAgent(
address,
{
agentId: "risk-bot-1",
scopes: ["account_read", "orders_read", "orders_write", "orders_cancel"],
allowedMarkets: ["SOL"],
maxNotionalUsd: 10,
allowTaker: false,
sessionExpiresAt: Date.now() + 30 * 60_000,
},
(message) => walletClient.signMessage({ account: address, message }),
);Each signed field is enforced by the executor and intersected with its stricter vault policy. Agent sessions last no more than one hour and are revocable by session ID. They cannot use the raw HL proxy, transfer funds, change builders, or manage account authority.
For MCP-capable agents, run the local stdio adapter with only the opaque token:
HL_VAULT_EXECUTOR_URL=https://trade.example.com \
HL_VAULT_AGENT_TOKEN=<opaque-agent-token> \
npx hl-vault-mcpThe adapter offers explicit account, open-order, bounded market-order,
single-order, atomic batch, scaled-order, reduce-only close, single cancel,
emergency cancel-all, and scoped TWAP start/read/cancel tools. TWAP writes require
the separately signed twaps_write scope; every child is still routed through
the builder-enforcing executor. Set OMNI_DATA_URL and, when required,
OMNI_DATA_API_TOKEN to add read-only liquidation-level, orderbook, news and
margin-stress tools to the same local adapter. The data token remains separate
from the delegated execution token; see
examples/mcp-data-assisted-agent.json.
examples/one-click-risk-checked-agent.ts combines the live vault account and
executor policy with Omni AI news, liquidation levels and margin-stress data,
then prepares either a two-level maker ladder or a managed TWAP. It previews by
default and submits only when EXECUTE=1 is explicitly present:
HL_VAULT_EXECUTOR_URL=https://trade.example.com \
HL_VAULT_AGENT_TOKEN=<opaque-token> \
OMNI_DATA_URL=https://data.omniterminal.app \
MARKET=SOL SYMBOL=SOL STRATEGY=maker-ladder EXECUTE=1 \
npm run example:one-clickThe agent token must include account_read, orders_write, and—when using
STRATEGY=twap—twaps_write. Data and execution tokens stay separate. The
script prints the vault, immutable builder, fee, margin, liquidation context and
bounded order plan without printing either credential.
Use Omni's x402 MCP separately for paid data. Payment credentials and x402 receipts never become trading credentials, and an inbound MCP OAuth token must not be forwarded to either downstream service.
Use info() and exchange() when migrating an existing Hyperliquid client.
The gateway accepts familiar request shapes but replaces untrusted nonces,
signatures and builder fields before signing. Transfers, withdrawals, agent
management, builder changes and unknown actions fail closed.
const state = await trading.info({
type: "clearinghouseState",
user: "0x0000000000000000000000000000000000000000",
});
const response = await trading.exchange({
action: {
type: "cancelByCloid",
cancels: [{ asset: 0, cloid: "0x..." }],
},
vaultAddress: policy.vault,
});The supported entry point is the versioned integration manifest. It prevents another terminal from hard-coding chain, contract, Hyperliquid or gateway URLs:
import { VaultTerminalClient, buildDepositTransactions } from "@intheta/hl-vault-sdk";
const terminal = await VaultTerminalClient.connect({
vaultApiUrl: "https://your-public-vault-api.example/v1",
});
const list = await terminal.public.vaults();
const dashboard = await terminal.dashboard(list.vaults[0].address);
const [approve, request] = buildDepositTransactions({
asset: terminal.manifest.contracts.asset!,
vault: list.vaults[0].address,
assets: 25_000_000n,
});
await walletClient.writeContract(approve);
await walletClient.writeContract(request);dashboard() combines registry/performance data with direct public
Hyperliquid balances, positions, orders and portfolio history. stream() uses
the advertised Hyperliquid WebSocket with reconnect and resubscription.
Follower helpers return viem/ethers-compatible calls; the SDK never receives a
private key. Deposits and redemptions remain asynchronous, so terminals must
show pending, settled and claimable states. buildFollowerReadRequests()
returns the six public contract reads needed to render shares, deposit request,
redemption request, cost basis, lock expiry and current epoch in one RPC batch.
Public discovery is cross-origin readable when the deployment's edge permits
anonymous access. The current Omni development host remains protected by
Cloudflare Access, so external test clients must use an approved Access session
or service-token-aware fetch; a dedicated public read-only hostname or path
exception is required before third-party beta. Browser trading is allowed only
for explicitly registered HTTPS origins; wildcard credentialed CORS is never
used. Unregistered terminals can use server-side delegated-agent tokens or
request an origin registration. See examples/external-terminal.ts.
The front end may keep its own market UI and use Omni only as the vault execution boundary. For the current testnet vault deployment:
import { LeaderTradingClient } from "@intheta/hl-vault-sdk";
const vault = new LeaderTradingClient({
baseUrl: "https://vault-gateway.omniterminal.app",
});
await vault.authorizeLeader(leaderAddress, (message) =>
walletClient.signMessage({ account: leaderAddress, message }),
);
const [account, orders] = await Promise.all([
vault.account(),
vault.openOrders(),
]);
await vault.placeOrder({
market: "test:ABC",
side: "buy",
limit_px: 10.5,
size: 1,
tif: "Alo",
});The gateway can enable browser challenge/session, account, order, cancel and TWAP calls for an exact HTTPS-origin allowlist. The current development origin is a preview endpoint, not a production SLA. Keep the resulting short-lived bearer in memory; do not put it in a URL, local storage or logs.
This is not a direct signing wrapper around Hyperliquid. Every authenticated write passes through the vault executor, which binds the action to its vault, applies the risk allowlist and injects the mandatory builder immediately before the trade-only agent signs. The SDK has no builder parameter, and the public proxy rejects funding, withdrawal, transfer, builder-change and arbitrary paths. Another front end can replace Omni's UX, but cannot bypass the fee.
The current testnet public origin targets one configured vault executor. A multi-vault router keyed by authenticated session claims—not caller-provided upstream URLs—is required before exposing many vaults through one origin.
erc20Abi, publicVaultFactoryAbi and asyncHyperVaultAbi are exported for
use with viem, ethers or another EVM client. The SDK deliberately does not
choose a wallet framework or embed deployment addresses.
npm install
npm run checkSee examples/ for follower, wallet-session, WebSocket, liquidation-level,
simple market, scaled, basket, delegated-agent, managed-TWAP, near-one-click
data-assisted, x402-gated and external-terminal entry points.
Do not open public issues containing credentials, wallet material or sensitive
deployment information. Use GitHub's private security-advisory flow described
in SECURITY.md.