Documentation Index
Fetch the complete documentation index at: https://hypernode-docs.polynode.dev/llms.txt
Use this file to discover all available pages before exploring further.
Copy-paste patterns for the most common use cases. All examples use TypeScript with the ws package.
Whale alert bot
Detect large orders and send alerts.
import WebSocket from "ws";
const ws = new WebSocket("wss://hyper.polynode.dev/ws?key=pn_live_YOUR_KEY");
const THRESHOLD_USD = 100_000;
ws.on("open", () => {
ws.send(JSON.stringify({
action: "subscribe",
filters: { action_types: ["order"] }
}));
});
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.type !== "order" || event.consensus !== "confirmed") return;
const d = event.data;
const notional = parseFloat(d.sz) * parseFloat(d.px);
if (notional >= THRESHOLD_USD) {
const side = d.side === "B" ? "BUY" : "SELL";
console.log(`WHALE ${side} ${event.asset}: ${d.sz} @ ${d.px} ($${Math.round(notional).toLocaleString()})`);
console.log(` Wallet: ${event.user}`);
console.log(` Order ID: ${d.oid}`);
// Send to Telegram, Discord, etc.
}
});
Fill tracker with PnL
Track every fill on a wallet and compute running PnL.
import WebSocket from "ws";
const ws = new WebSocket("wss://hyper.polynode.dev/ws?key=pn_live_YOUR_KEY");
const WALLET = "0xd071d6d6ea52f5aa34b79e47f908ee48c8215837";
let totalPnl = 0;
let totalFees = 0;
ws.on("open", () => {
ws.send(JSON.stringify({
action: "subscribe",
filters: { addresses: [WALLET], action_types: ["fill"] }
}));
});
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.type !== "fill") return;
const d = event.data;
const pnl = parseFloat(d.closedPnl || "0");
const fee = parseFloat(d.fee || "0");
totalPnl += pnl;
totalFees += fee;
console.log(`${event.asset} ${d.dir} ${d.sz}@${d.px}`);
console.log(` PnL: ${pnl >= 0 ? "+" : ""}${pnl.toFixed(2)} | Fee: ${fee.toFixed(4)} ${d.feeToken}`);
console.log(` Running: PnL=${totalPnl.toFixed(2)} Fees=${totalFees.toFixed(4)}`);
});
Cancel storm detector
Detect when market makers are aggressively pulling orders. A burst of cancels often precedes a price move.
import WebSocket from "ws";
const ws = new WebSocket("wss://hyper.polynode.dev/ws?key=pn_live_YOUR_KEY");
const WINDOW_MS = 2000;
const THRESHOLD = 20;
const cancelWindows: Map<string, number[]> = new Map();
ws.on("open", () => {
ws.send(JSON.stringify({
action: "subscribe",
filters: { action_types: ["cancel", "cancelByCloid"], assets: ["BTC", "ETH", "SOL"] }
}));
});
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.consensus !== "pre") return;
const asset = event.asset;
const now = Date.now();
if (!cancelWindows.has(asset)) cancelWindows.set(asset, []);
const timestamps = cancelWindows.get(asset)!;
timestamps.push(now);
// Trim old timestamps
while (timestamps.length > 0 && timestamps[0] < now - WINDOW_MS) {
timestamps.shift();
}
if (timestamps.length >= THRESHOLD) {
console.log(`CANCEL STORM: ${asset} — ${timestamps.length} cancels in ${WINDOW_MS}ms`);
timestamps.length = 0; // Reset to avoid repeated alerts
}
});
Copy trader
Replicate trades from a target wallet. Logs the signal; add your execution logic.
import WebSocket from "ws";
const ws = new WebSocket("wss://hyper.polynode.dev/ws?key=pn_live_YOUR_KEY");
const TARGETS = [
"0xd071d6d6ea52f5aa34b79e47f908ee48c8215837",
"0x1234567890abcdef1234567890abcdef12345678",
];
ws.on("open", () => {
ws.send(JSON.stringify({
action: "subscribe",
filters: { addresses: TARGETS, action_types: ["fill"] }
}));
});
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.type !== "fill") return;
const d = event.data;
console.log(`COPY SIGNAL from ${event.user.slice(0, 8)}...`);
console.log(` ${event.asset} ${d.dir} ${d.sz} @ ${d.px}`);
// Execute your copy trade:
// POST to https://hyper.polynode.dev/exchange/order with your signed payload
});
Multi-asset dashboard
Stream orders and fills for multiple assets simultaneously.
import WebSocket from "ws";
const ws = new WebSocket("wss://hyper.polynode.dev/ws?key=pn_live_YOUR_KEY");
const ASSETS = ["BTC", "ETH", "SOL", "HYPE", "DOGE"];
const stats: Record<string, { orders: number; fills: number; volume: number }> = {};
for (const a of ASSETS) stats[a] = { orders: 0, fills: 0, volume: 0 };
ws.on("open", () => {
for (const asset of ASSETS) {
ws.send(JSON.stringify({
action: "subscribe",
filters: { assets: [asset], action_types: ["order", "fill"] }
}));
}
});
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (!stats[event.asset]) return;
if (event.type === "order") stats[event.asset].orders++;
if (event.type === "fill") {
stats[event.asset].fills++;
stats[event.asset].volume += parseFloat(event.data.sz) * parseFloat(event.data.px);
}
});
// Print stats every 10 seconds
setInterval(() => {
console.clear();
console.log("Asset Orders Fills Volume");
console.log("------- ------ ----- ------");
for (const [asset, s] of Object.entries(stats)) {
console.log(`${asset.padEnd(10)}${String(s.orders).padEnd(9)}${String(s.fills).padEnd(9)}$${Math.round(s.volume).toLocaleString()}`);
}
}, 10000);
L4 order book poller
Poll the L4 order book (individual orders with wallet addresses) via REST.
const API_KEY = "pn_live_YOUR_KEY";
const BASE = "https://hyper.polynode.dev";
async function getL4Book(coin: string) {
const res = await fetch(`${BASE}/v2/l4book?coin=${coin}`, {
headers: { "x-api-key": API_KEY }
});
const book = await res.json();
const bids = book.levels.filter((l: any) => l.side === "B").sort((a: any, b: any) => parseFloat(b.px) - parseFloat(a.px));
const asks = book.levels.filter((l: any) => l.side === "A").sort((a: any, b: any) => parseFloat(a.px) - parseFloat(b.px));
console.log(`${coin} L4 Book — ${bids.length} bids | ${asks.length} asks`);
console.log("\nTop 10 Bids:");
for (const b of bids.slice(0, 10)) {
console.log(` ${b.px} ${b.sz} ${b.user.slice(0, 8)}...`);
}
console.log("\nTop 10 Asks:");
for (const a of asks.slice(0, 10)) {
console.log(` ${a.px} ${a.sz} ${a.user.slice(0, 8)}...`);
}
}
// Poll every 5 seconds
setInterval(() => getL4Book("BTC"), 5000);
getL4Book("BTC");
Rejection monitor
Track order rejections across all assets. Useful for identifying accounts under margin stress.
import WebSocket from "ws";
const ws = new WebSocket("wss://hyper.polynode.dev/ws?key=pn_live_YOUR_KEY");
const rejectionCounts: Map<string, { count: number; reasons: string[] }> = new Map();
ws.on("open", () => {
ws.send(JSON.stringify({
action: "subscribe",
filters: { action_types: ["order"] }
}));
});
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.type !== "order_rejected") return;
const wallet = event.user;
const reason = event.data.status;
if (!rejectionCounts.has(wallet)) {
rejectionCounts.set(wallet, { count: 0, reasons: [] });
}
const entry = rejectionCounts.get(wallet)!;
entry.count++;
entry.reasons.push(reason);
if (entry.count >= 5) {
console.log(`HIGH REJECTION RATE: ${wallet}`);
console.log(` ${entry.count} rejections: ${[...new Set(entry.reasons)].join(", ")}`);
rejectionCounts.delete(wallet); // Reset
}
});
REST + stream combination
Use REST for initial state, stream for updates.
import WebSocket from "ws";
const API_KEY = "pn_live_YOUR_KEY";
const WALLET = "0xd071d6d6ea52f5aa34b79e47f908ee48c8215837";
// 1. Get current positions via REST
async function getPositions() {
const res = await fetch(`https://hyper.polynode.dev/v2/user/state?address=${WALLET}`, {
headers: { "x-api-key": API_KEY }
});
return await res.json();
}
// 2. Stream fills for real-time updates
function streamFills() {
const ws = new WebSocket(`wss://hyper.polynode.dev/ws?key=${API_KEY}`);
ws.on("open", () => {
ws.send(JSON.stringify({
action: "subscribe",
filters: { addresses: [WALLET], action_types: ["fill"] }
}));
});
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.type === "fill") {
console.log(`Position update: ${event.asset} ${event.data.dir} ${event.data.sz}@${event.data.px}`);
// Update local state
}
});
}
// Start both
getPositions().then((positions) => {
console.log("Initial positions:", positions);
streamFills();
});
Pattern: Watch for liquidations
Stream every liquidation happening across HyperLiquid in real-time. Useful for liquidation bots, market stress monitors, and building a public liquidation feed.
import WebSocket from "ws";
const API_KEY = process.env.HYPERNODE_API_KEY;
const ws = new WebSocket(`wss://hyper.polynode.dev/ws?key=${API_KEY}`);
ws.on("open", () => {
ws.send(JSON.stringify({
action: "subscribe",
filters: {
action_types: ["fill"],
liquidations_only: true
}
}));
});
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.type !== "fill") return;
const { coin, sz, px, closedPnl } = event.data;
const { liquidatedUser, markPx, method } = event.data.liquidation;
console.log(
`LIQUIDATION: ${coin} sz=${sz} markPx=${markPx} ` +
`user=${liquidatedUser} pnl=${closedPnl} method=${method}`
);
});
Every liquidation generates at least 2 fill events — one for the liquidated user and one for the liquidator(s). Both carry the same data.liquidation object and share the same tid and hash so you can correlate them.
Pattern: Builder DEX attribution feed
If you operate a HIP-3 builder DEX, subscribe to fills attributed to your builder address to track your flow in real-time without polling userFills per wallet.
import WebSocket from "ws";
const API_KEY = process.env.HYPERNODE_API_KEY;
const BUILDER = "0x1924b8561eef20e70ede628a296175d358be80e5";
const ws = new WebSocket(`wss://hyper.polynode.dev/ws?key=${API_KEY}`);
ws.on("open", () => {
ws.send(JSON.stringify({
action: "subscribe",
filters: {
action_types: ["fill"],
builders: [BUILDER]
}
}));
});
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.type !== "fill") return;
const { coin, sz, px, builderFee } = event.data;
console.log(`Fill via our builder: ${coin} ${sz}@${px} fee=${builderFee}`);
});
You can pass multiple builder addresses if you operate several DEXes.
Pattern: All fills firehose for market analytics
Subscribe to every fill on every market. Useful for VWAP calculations, open interest tracking, and market-wide analytics.
const ws = new WebSocket(`wss://hyper.polynode.dev/ws?key=${API_KEY}`);
ws.on("open", () => {
ws.send(JSON.stringify({
action: "subscribe",
filters: { action_types: ["fill"] }
}));
});
const volumeByAsset: Record<string, number> = {};
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.type !== "fill") return;
const coin = event.data.coin;
const notional = parseFloat(event.data.sz) * parseFloat(event.data.px);
volumeByAsset[coin] = (volumeByAsset[coin] || 0) + notional;
});
// Log top 5 by volume every 10 seconds
setInterval(() => {
const top = Object.entries(volumeByAsset)
.sort(([, a], [, b]) => b - a)
.slice(0, 5);
console.log("Top 5 by volume:", top);
}, 10000);
Fetch portfolio performance for thousands of wallets in a single request. Designed for copy trading leaderboards, portfolio trackers, and multi-user dashboards.
async function batchPortfolios(wallets: string[]) {
const res = await fetch("https://hyper.polynode.dev/v2/batch/portfolios", {
method: "POST",
headers: {
"x-api-key": process.env.HYPERNODE_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({ users: wallets }),
});
return await res.json();
}
// Up to 10,000 wallets per call
const wallets = [
"0xacd89cFCB82Ae1f843467D56b58796bb928C9E1A",
"0x711d5ee8df7f0075f5393f7c9197248e961542b1",
// ... up to 10000 more
];
const portfolios = await batchPortfolios(wallets);
for (const [address, data] of Object.entries(portfolios)) {
if (data) {
console.log(address, "portfolio:", data);
}
}
Paired with /v2/batch/clearinghouse-states you can build a complete leaderboard snapshot of thousands of traders in well under a second.