@ironflowsh/mcp 0.8.3 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/tools.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // MCP tool definitions for Ironflow market data.
2
2
  import { MAX_SUMMARY_MINUTES, summarizeLiquidations } from "./liquidations.js";
3
3
  import { MAX_LIMIT, rankSnapshot, SORT_KEYS } from "./snapshot.js";
4
- import { DEFAULT_FUNDING_ROWS, DEFAULT_MARKETS_LIMIT, shapeMarkets, shapeUserFunding, shapeWalletLabels } from "./shape.js";
4
+ import { BEHAVIOR_LABELS, BEHAVIOR_SORT_KEYS, behaviorSQL, MAX_BEHAVIOR_LIMIT, MAX_VAULT_LEADERBOARD_LIMIT, rowsToObjects, runQuery, VAULT_LEADERBOARD_DAYS, vaultLeaderboardSQL, } from "./query.js";
5
+ import { DEFAULT_FUNDING_ROWS, DEFAULT_MARKETS_LIMIT, MAX_MARKETS_LIMIT, shapeMarkets, shapeUserFunding, shapeWalletLabels } from "./shape.js";
5
6
  // ─── Arg validation helpers ──────────────────────────────────────────────────
6
7
  function requireString(args, key) {
7
8
  const val = args[key];
@@ -29,15 +30,111 @@ function optionalString(args, key, defaultVal) {
29
30
  }
30
31
  return val;
31
32
  }
33
+ function optionalBoolean(args, key) {
34
+ const val = args[key];
35
+ if (val === undefined || val === null)
36
+ return false;
37
+ if (typeof val === "boolean")
38
+ return val;
39
+ if (val === "true" || val === "false")
40
+ return val === "true";
41
+ throw new Error(`"${key}" must be true or false`);
42
+ }
43
+ // optionalTime reads a window bound and passes it on as the caller wrote it:
44
+ // the API takes Unix ms, Unix seconds and ISO 8601 (UTC when no zone is
45
+ // given), so nothing is converted here. "" means not set.
46
+ function optionalTime(args, key) {
47
+ const val = args[key];
48
+ if (val === undefined || val === null || val === 0)
49
+ return "";
50
+ const invalid = new Error(`"${key}" must be Unix ms, Unix seconds or an ISO 8601 date such as 2026-10-01T00:00:00Z`);
51
+ if (typeof val === "number") {
52
+ if (!Number.isFinite(val) || val < 0)
53
+ throw invalid;
54
+ return String(Math.floor(val));
55
+ }
56
+ if (typeof val !== "string")
57
+ throw invalid;
58
+ const text = val.trim();
59
+ if (/^\d*$/.test(text))
60
+ return Number(text) > 0 ? text : "";
61
+ // The API reads 2026-10-01, 2026-10-01T12:00 and RFC 3339 date-times.
62
+ if (!/^\d{4}-\d{2}-\d{2}(T|$)/.test(text) || Number.isNaN(Date.parse(text)))
63
+ throw invalid;
64
+ return text;
65
+ }
66
+ // Shared parameter descriptions. The API resolves Hyperliquid names itself.
67
+ const MARKET = "Market: BTC-PERP, xyz:NVDA-PERP, HYPE/USDC, or a Hyperliquid name (BTC, @107, xyz:NVDA)";
68
+ const MARKET_FILTER = "Optional market filter: BTC-PERP or a Hyperliquid name (BTC, xyz:NVDA)";
69
+ const time = (bound, extra = "") => `Window ${bound}: Unix ms or seconds, or ISO 8601 (2026-10-01T00:00:00Z)${extra ? `. ${extra}` : ""}`;
32
70
  // ─── Tools ──────────────────────────────────────────────────────────────────
33
71
  export const tools = [
72
+ {
73
+ name: "describe_data",
74
+ title: "Describe SQL tables",
75
+ description: "Start here for any Hyperliquid question the other tools do not answer directly. Returns the SQL tables you can query with run_query (every fill, per-wallet daily totals, wallet behaviour labels, funding, mark and oracle prices, open interest, liquidations, builder-code fills, transfers, vault flows), their columns, how far back each goes, query tips, worked examples and your own limits.",
76
+ inputSchema: { type: "object", properties: {}, required: [] },
77
+ handler: async (_args, api) => JSON.stringify(await api.get("/v1/query/schema")),
78
+ },
79
+ {
80
+ name: "run_query",
81
+ title: "Run SQL query",
82
+ description: "Run one read-only ClickHouse SELECT over Ironflow's Hyperliquid tables (hl.fills, hl.wallet_daily, hl.wallet_behavior, hl.funding, hl.mark_prices, hl.oracle_prices, hl.open_interest, hl.liquidations, hl.builder_fills, hl.transfers, hl.vault_operations). Call describe_data first for columns and examples. Aggregate in SQL: results are capped (1,000 rows keyless). Keyless: last 24 hours, 5 s per query, 60 query seconds per hour per IP; a free key from https://ironflow.sh/key gives 30 days of history.",
83
+ inputSchema: {
84
+ type: "object",
85
+ properties: {
86
+ sql: { type: "string", description: "One ClickHouse SELECT (or WITH ... SELECT) over hl.* tables, without a FORMAT clause" },
87
+ max_rows: { type: "number", description: "Most rows to return (default 200, capped by your tier)" },
88
+ },
89
+ required: ["sql"],
90
+ },
91
+ handler: async (args, api) => {
92
+ const sql = requireString(args, "sql");
93
+ const maxRows = optionalNumber(args, "max_rows", 200);
94
+ return JSON.stringify(await runQuery(api, sql, maxRows));
95
+ },
96
+ },
97
+ {
98
+ name: "get_wallet_behavior",
99
+ title: "Wallet behavior labels",
100
+ description: "How Hyperliquid wallets trade, from their last 30 days: labels (market_maker, high_frequency, whale, directional, consistent_winner, big_loser, revenge_sizing, often_liquidated, hip3_trader, spot_trader, prediction_trader) and the numbers behind them (maker share, buy/sell imbalance, fills per day, green-day share, profit factor, liquidations). Look up specific addresses, or list wallets with one label.",
101
+ inputSchema: {
102
+ type: "object",
103
+ properties: {
104
+ addresses: { type: "string", description: "Comma-separated wallet addresses (up to 50). Omit to list wallets instead." },
105
+ label: { type: "string", description: "Only wallets carrying this label", enum: [...BEHAVIOR_LABELS] },
106
+ sort_by: { type: "string", description: "Sort order when listing (default volume)", enum: [...BEHAVIOR_SORT_KEYS] },
107
+ min_volume_usd: { type: "number", description: "Only wallets with at least this 30-day volume" },
108
+ limit: { type: "number", description: `Wallets to return (default 20, max ${MAX_BEHAVIOR_LIMIT})` },
109
+ },
110
+ required: [],
111
+ },
112
+ handler: async (args, api) => {
113
+ const addresses = optionalString(args, "addresses", "")
114
+ .split(",")
115
+ .map((a) => a.trim())
116
+ .filter(Boolean);
117
+ const sortBy = optionalString(args, "sort_by", "volume");
118
+ const minVol = args.min_volume_usd === undefined ? 0 : Number(args.min_volume_usd);
119
+ const sql = behaviorSQL({
120
+ addresses,
121
+ label: optionalString(args, "label", ""),
122
+ sortBy,
123
+ limit: optionalNumber(args, "limit", addresses.length || 20),
124
+ minVolumeUsd: minVol,
125
+ });
126
+ const res = (await runQuery(api, sql, MAX_BEHAVIOR_LIMIT));
127
+ return JSON.stringify({ window_days: 30, wallets: rowsToObjects(res) });
128
+ },
129
+ },
34
130
  {
35
131
  name: "get_price",
132
+ title: "Market price",
36
133
  description: "Latest mark price of a Hyperliquid market: native perps, HIP-3 builder perps (e.g. xyz:NVDA-PERP) and spot.",
37
134
  inputSchema: {
38
135
  type: "object",
39
136
  properties: {
40
- market: { type: "string", description: 'Market symbol — native HL perp (e.g. "BTC-PERP", "ETH-PERP") or HIP-3 builder market namespaced as "<issuer>:<base>-PERP" (e.g. "flx:GAS-PERP", "xyz:NVDA-PERP")' },
137
+ market: { type: "string", description: MARKET },
41
138
  },
42
139
  required: ["market"],
43
140
  },
@@ -49,11 +146,12 @@ export const tools = [
49
146
  },
50
147
  {
51
148
  name: "get_recent_trades",
149
+ title: "Recent trades",
52
150
  description: "Latest tick-level trades on a Hyperliquid market from our own nodes: price, size, side and block time. Covers native perps, HIP-3 builder markets, HIP-4 outcomes and spot.",
53
151
  inputSchema: {
54
152
  type: "object",
55
153
  properties: {
56
- market: { type: "string", description: 'Market symbol — native HL perp (e.g. "BTC-PERP", "ETH-PERP") or HIP-3 builder market namespaced as "<issuer>:<base>-PERP" (e.g. "flx:GAS-PERP", "xyz:NVDA-PERP")' },
154
+ market: { type: "string", description: MARKET },
57
155
  limit: { type: "number", description: "Number of trades to return (1-100, default 20)" },
58
156
  },
59
157
  required: ["market"],
@@ -67,11 +165,12 @@ export const tools = [
67
165
  },
68
166
  {
69
167
  name: "get_candles",
168
+ title: "Candles (OHLCV)",
70
169
  description: "OHLCV candles for a Hyperliquid market, computed from tick-level trades.",
71
170
  inputSchema: {
72
171
  type: "object",
73
172
  properties: {
74
- market: { type: "string", description: 'Market symbol — native HL perp (e.g. "BTC-PERP", "ETH-PERP") or HIP-3 builder market namespaced as "<issuer>:<base>-PERP" (e.g. "flx:GAS-PERP", "xyz:NVDA-PERP")' },
173
+ market: { type: "string", description: MARKET },
75
174
  interval: { type: "string", description: "Candle interval (1m, 5m, 15m, 1h, 4h, 1d). Default: 1h", enum: ["1m", "5m", "15m", "1h", "4h", "1d"] },
76
175
  limit: { type: "number", description: "Number of candles to return (1-500, default 24)" },
77
176
  },
@@ -87,11 +186,12 @@ export const tools = [
87
186
  },
88
187
  {
89
188
  name: "get_funding_rates",
189
+ title: "Funding rate history",
90
190
  description: "Hourly funding rate history for a Hyperliquid perp, including HIP-3 builder perps. A positive rate means longs pay shorts.",
91
191
  inputSchema: {
92
192
  type: "object",
93
193
  properties: {
94
- market: { type: "string", description: 'Market symbol — native HL perp (e.g. "BTC-PERP", "ETH-PERP") or HIP-3 builder market namespaced as "<issuer>:<base>-PERP" (e.g. "flx:GAS-PERP", "xyz:NVDA-PERP")' },
194
+ market: { type: "string", description: MARKET },
95
195
  limit: { type: "number", description: "Number of funding rate entries (1-100, default 10)" },
96
196
  },
97
197
  required: ["market"],
@@ -105,11 +205,12 @@ export const tools = [
105
205
  },
106
206
  {
107
207
  name: "get_open_interest",
108
- description: "Latest open interest for a Hyperliquid perp.",
208
+ title: "Open interest",
209
+ description: "Latest open interest of a Hyperliquid perp, in base units, with its 24h volume in USD.",
109
210
  inputSchema: {
110
211
  type: "object",
111
212
  properties: {
112
- market: { type: "string", description: 'Market symbol — native HL perp (e.g. "BTC-PERP", "ETH-PERP") or HIP-3 builder market namespaced as "<issuer>:<base>-PERP" (e.g. "flx:GAS-PERP", "xyz:NVDA-PERP")' },
213
+ market: { type: "string", description: MARKET },
113
214
  },
114
215
  required: ["market"],
115
216
  },
@@ -121,11 +222,12 @@ export const tools = [
121
222
  },
122
223
  {
123
224
  name: "get_liquidations",
225
+ title: "Liquidations",
124
226
  description: "Liquidations on a Hyperliquid perp, read from our own nodes: price, size, side and the liquidated wallet. Hyperliquid's public API has no liquidation feed.",
125
227
  inputSchema: {
126
228
  type: "object",
127
229
  properties: {
128
- market: { type: "string", description: 'Market symbol — native HL perp (e.g. "BTC-PERP", "ETH-PERP") or HIP-3 builder market namespaced as "<issuer>:<base>-PERP" (e.g. "flx:GAS-PERP", "xyz:NVDA-PERP")' },
230
+ market: { type: "string", description: MARKET },
129
231
  limit: { type: "number", description: "Number of liquidation events (1-100, default 20)" },
130
232
  },
131
233
  required: ["market"],
@@ -140,12 +242,13 @@ export const tools = [
140
242
  // ─── Additional Market Data ──────────────────────────────────────────
141
243
  {
142
244
  name: "get_liquidation_summary",
245
+ title: "Liquidation summary",
143
246
  description: "Liquidations across every Hyperliquid market (or one market) over the last N minutes, totalled: count, notional, longs vs shorts liquidated, the top markets and the largest single liquidation. Use it for 'who got liquidated in the last hour?'. Keyless calls can look back 60 minutes, keyed calls up to 240.",
144
247
  inputSchema: {
145
248
  type: "object",
146
249
  properties: {
147
250
  minutes: { type: "number", description: "Lookback in minutes (default 60, max 240)" },
148
- market: { type: "string", description: 'Optional market filter (e.g. "BTC-PERP")' },
251
+ market: { type: "string", description: MARKET_FILTER },
149
252
  },
150
253
  required: [],
151
254
  },
@@ -160,12 +263,13 @@ export const tools = [
160
263
  },
161
264
  {
162
265
  name: "get_fills",
266
+ title: "Wallet fills",
163
267
  description: "Every fill for one Hyperliquid wallet, optionally for one market, from indexed history: price, size, side, fee and realized PnL. Hyperliquid's own userFills endpoints stop at the 10,000 most recent fills.",
164
268
  inputSchema: {
165
269
  type: "object",
166
270
  properties: {
167
271
  address: { type: "string", description: "Wallet address (e.g. 0x...)" },
168
- market: { type: "string", description: 'Optional market filter — native HL (e.g. "BTC-PERP") or HIP-3 builder market "<issuer>:<base>-PERP" (e.g. "flx:GAS-PERP")' },
272
+ market: { type: "string", description: MARKET_FILTER },
169
273
  limit: { type: "number", description: "Number of fills (1-100, default 20)" },
170
274
  },
171
275
  required: ["address"],
@@ -180,11 +284,12 @@ export const tools = [
180
284
  },
181
285
  {
182
286
  name: "get_mark_prices",
183
- description: "Mark price and oracle price series for a Hyperliquid perp.",
287
+ title: "Mark and oracle price",
288
+ description: "Latest mark price, oracle price and hourly funding rate of a Hyperliquid perp.",
184
289
  inputSchema: {
185
290
  type: "object",
186
291
  properties: {
187
- market: { type: "string", description: 'Market symbol — native HL perp (e.g. "BTC-PERP") or HIP-3 builder market "<issuer>:<base>-PERP" (e.g. "flx:GAS-PERP")' },
292
+ market: { type: "string", description: MARKET },
188
293
  },
189
294
  required: ["market"],
190
295
  },
@@ -196,19 +301,25 @@ export const tools = [
196
301
  },
197
302
  {
198
303
  name: "get_vault_operations",
199
- description: "Deposits into and withdrawals from Hyperliquid vaults. Pass a vault address, a depositor address, or both; one is required.",
304
+ title: "Vault deposits and withdrawals",
305
+ description: "Deposits into and withdrawals from one Hyperliquid vault or one depositor. Pass vault, address, or both; at least one is required (HLP is 0xdfc24b077bc1425ad1dea75bcb6f8158e10df303). For flows across all vaults use get_vault_leaderboard.",
200
306
  inputSchema: {
201
307
  type: "object",
202
308
  properties: {
203
- vault: { type: "string", description: "Vault address" },
204
- address: { type: "string", description: "User address" },
309
+ vault: { type: "string", description: "Vault address (vault or address is required)" },
310
+ address: { type: "string", description: "Depositor address (vault or address is required)" },
205
311
  limit: { type: "number", description: "Number of events (1-100, default 20)" },
206
312
  },
207
313
  required: [],
208
314
  },
209
315
  handler: async (args, api) => {
210
- const vault = optionalString(args, "vault", "");
211
- const address = optionalString(args, "address", "");
316
+ // The API stores addresses lowercase and answers 400 when neither
317
+ // filter is set, so check here and say what to pass.
318
+ const vault = optionalString(args, "vault", "").trim().toLowerCase();
319
+ const address = optionalString(args, "address", "").trim().toLowerCase();
320
+ if (!vault && !address) {
321
+ throw new Error('Pass "vault" (a vault address, e.g. HLP 0xdfc24b077bc1425ad1dea75bcb6f8158e10df303) or "address" (a depositor), or both. For flows across all vaults use get_vault_leaderboard.');
322
+ }
212
323
  const limit = optionalNumber(args, "limit", 20);
213
324
  const data = await api.getVaultOperations(vault, address, String(limit));
214
325
  return JSON.stringify(data, null, 2);
@@ -217,11 +328,12 @@ export const tools = [
217
328
  // ─── Analytics ───────────────────────────────────────────────────────
218
329
  {
219
330
  name: "get_liquidation_levels",
331
+ title: "Liquidation levels",
220
332
  description: "Liquidated notional on a Hyperliquid perp bucketed by price: where longs and shorts were wiped out. Needs a Builder or Enterprise key; keyless and free-key calls return 403.",
221
333
  inputSchema: {
222
334
  type: "object",
223
335
  properties: {
224
- market: { type: "string", description: 'Market symbol — native HL perp (e.g. "BTC-PERP") or HIP-3 builder market "<issuer>:<base>-PERP" (e.g. "flx:GAS-PERP")' },
336
+ market: { type: "string", description: MARKET },
225
337
  bucket_size: { type: "string", description: "Price bucket size (1, 5, 10, 50, 100, 500, 1000, 5000). Default: 100" },
226
338
  },
227
339
  required: ["market"],
@@ -235,27 +347,45 @@ export const tools = [
235
347
  },
236
348
  {
237
349
  name: "get_vault_leaderboard",
238
- description: "Hyperliquid vaults ranked by net deposits. Needs a Builder or Enterprise key; keyless and free-key calls return 403.",
350
+ title: "Vault leaderboard",
351
+ description: `Hyperliquid vaults ranked by net deposits (deposits minus withdrawals) over the last ${VAULT_LEADERBOARD_DAYS} days, with depositor counts. Keyless calls see the last 24 hours; a free key from https://ironflow.sh/key gives the full ${VAULT_LEADERBOARD_DAYS} days.`,
239
352
  inputSchema: {
240
353
  type: "object",
241
354
  properties: {
242
- limit: { type: "number", description: "Number of vaults (default 10)" },
355
+ limit: { type: "number", description: `Number of vaults (default 10, max ${MAX_VAULT_LEADERBOARD_LIMIT})` },
243
356
  },
244
357
  required: [],
245
358
  },
246
359
  handler: async (args, api) => {
247
360
  const limit = optionalNumber(args, "limit", 10);
248
- const data = await api.getVaultLeaderboard(String(limit));
249
- return JSON.stringify(data, null, 2);
361
+ // GET /v1/analytics/vault-leaderboard is Builder and Enterprise only
362
+ // (403 TIER_FORBIDDEN otherwise). Keyed callers try it first; everyone
363
+ // else, and keys below Builder, get the same ranking from SQL.
364
+ if (api.hasKey()) {
365
+ try {
366
+ return JSON.stringify(await api.getVaultLeaderboard(String(limit)), null, 2);
367
+ }
368
+ catch (err) {
369
+ if (!(err instanceof Error && err.message.includes("TIER_FORBIDDEN")))
370
+ throw err;
371
+ }
372
+ }
373
+ const res = (await runQuery(api, vaultLeaderboardSQL(limit), MAX_VAULT_LEADERBOARD_LIMIT));
374
+ const historyDays = res.limits?.history_days;
375
+ return JSON.stringify({
376
+ window_days: historyDays ? Math.min(historyDays, VAULT_LEADERBOARD_DAYS) : VAULT_LEADERBOARD_DAYS,
377
+ data: rowsToObjects(res).map((row) => ({ source: "hyperliquid", ...row })),
378
+ });
250
379
  },
251
380
  },
252
381
  {
253
382
  name: "get_funding_stats",
383
+ title: "Funding statistics",
254
384
  description: "Funding statistics per time bucket for a Hyperliquid perp: average, minimum, maximum and annualized rate.",
255
385
  inputSchema: {
256
386
  type: "object",
257
387
  properties: {
258
- market: { type: "string", description: 'Market symbol — native HL perp (e.g. "BTC-PERP") or HIP-3 builder market "<issuer>:<base>-PERP" (e.g. "flx:GAS-PERP")' },
388
+ market: { type: "string", description: MARKET },
259
389
  interval: { type: "string", description: "Time interval (1h, 4h, 8h, 1d, 7d). Default: 1d", enum: ["1h", "4h", "8h", "1d", "7d"] },
260
390
  limit: { type: "number", description: "Number of data points (default 7)" },
261
391
  },
@@ -278,6 +408,7 @@ export const tools = [
278
408
  // JSON object the agent can reason over.
279
409
  {
280
410
  name: "get_user_state",
411
+ title: "Wallet positions",
281
412
  description: "Open positions and unrealized PnL for a Hyperliquid wallet right now, across native and HIP-3 perps. Use it for questions like 'is this wallet long or short?'",
282
413
  inputSchema: {
283
414
  type: "object",
@@ -294,14 +425,15 @@ export const tools = [
294
425
  },
295
426
  {
296
427
  name: "get_user_funding",
428
+ title: "Wallet funding payments",
297
429
  description: "Funding a Hyperliquid wallet paid or received, per market per bucket. Positive means received, negative means paid. bucket=1d gives daily totals, 1h the raw hourly payments.",
298
430
  inputSchema: {
299
431
  type: "object",
300
432
  properties: {
301
433
  address: { type: "string", description: "0x-prefixed 20-byte hex address" },
302
- market: { type: "string", description: "Optional market filter (e.g. BTC-PERP)" },
303
- from: { type: "number", description: "Unix ms window start (inclusive)" },
304
- to: { type: "number", description: "Unix ms window end (exclusive)" },
434
+ market: { type: "string", description: MARKET_FILTER },
435
+ from: { type: "string", description: time("start") },
436
+ to: { type: "string", description: time("end") },
305
437
  bucket: { type: "string", enum: ["1h", "1d", "raw"], description: "Aggregation bucket; default 1d" },
306
438
  limit: { type: "number", description: `Most recent rows to return (default ${DEFAULT_FUNDING_ROWS})` },
307
439
  },
@@ -310,8 +442,8 @@ export const tools = [
310
442
  handler: async (args, api) => {
311
443
  const address = requireString(args, "address");
312
444
  const market = optionalString(args, "market", "");
313
- const from = optionalNumber(args, "from", 0);
314
- const to = optionalNumber(args, "to", 0);
445
+ const from = optionalTime(args, "from");
446
+ const to = optionalTime(args, "to");
315
447
  const bucket = optionalString(args, "bucket", "1d");
316
448
  const data = (await api.getUserFunding(address, market, from, to, bucket));
317
449
  return JSON.stringify(shapeUserFunding(data, optionalNumber(args, "limit", DEFAULT_FUNDING_ROWS)));
@@ -319,35 +451,37 @@ export const tools = [
319
451
  },
320
452
  {
321
453
  name: "get_user_maker_taker",
454
+ title: "Wallet maker/taker split",
322
455
  description: "Maker vs taker breakdown of a Hyperliquid wallet's fills per market: volume, fees and rebates. A high maker share with negative maker fees marks a market maker.",
323
456
  inputSchema: {
324
457
  type: "object",
325
458
  properties: {
326
459
  address: { type: "string", description: "0x-prefixed 20-byte hex address" },
327
- market: { type: "string", description: "Optional market filter" },
328
- from: { type: "number", description: "Unix ms window start" },
329
- to: { type: "number", description: "Unix ms window end" },
460
+ market: { type: "string", description: MARKET_FILTER },
461
+ from: { type: "string", description: time("start") },
462
+ to: { type: "string", description: time("end") },
330
463
  },
331
464
  required: ["address"],
332
465
  },
333
466
  handler: async (args, api) => {
334
467
  const address = requireString(args, "address");
335
468
  const market = optionalString(args, "market", "");
336
- const from = optionalNumber(args, "from", 0);
337
- const to = optionalNumber(args, "to", 0);
469
+ const from = optionalTime(args, "from");
470
+ const to = optionalTime(args, "to");
338
471
  const data = await api.getUserMakerTaker(address, market, from, to);
339
472
  return JSON.stringify(data, null, 2);
340
473
  },
341
474
  },
342
475
  {
343
476
  name: "get_user_ledger",
477
+ title: "Wallet ledger",
344
478
  description: "Vault deposits, withdrawals and transfers for a Hyperliquid wallet. Positive amounts go into the account. Account deposits and withdrawals are recorded only up to 2026-06-06.",
345
479
  inputSchema: {
346
480
  type: "object",
347
481
  properties: {
348
482
  address: { type: "string", description: "0x-prefixed 20-byte hex address" },
349
- from: { type: "number", description: "Unix ms window start" },
350
- to: { type: "number", description: "Unix ms window end" },
483
+ from: { type: "string", description: time("start") },
484
+ to: { type: "string", description: time("end") },
351
485
  event_types: { type: "string", description: "Comma-separated type filter (deposit,withdrawal,vault_deposit,vault_withdraw,vault_transfer_in,vault_transfer_out)" },
352
486
  limit: { type: "number", description: "Max entries, default 1000, cap 10000" },
353
487
  },
@@ -355,8 +489,8 @@ export const tools = [
355
489
  },
356
490
  handler: async (args, api) => {
357
491
  const address = requireString(args, "address");
358
- const from = optionalNumber(args, "from", 0);
359
- const to = optionalNumber(args, "to", 0);
492
+ const from = optionalTime(args, "from");
493
+ const to = optionalTime(args, "to");
360
494
  const eventTypes = optionalString(args, "event_types", "");
361
495
  const limit = optionalNumber(args, "limit", 1000);
362
496
  const data = await api.getUserLedger(address, from, to, eventTypes, limit);
@@ -366,12 +500,13 @@ export const tools = [
366
500
  // ─── Cross-wallet ranking (v0.34+) ─────────────────────────────────────
367
501
  {
368
502
  name: "get_pnl_leaderboard",
503
+ title: "PnL leaderboard",
369
504
  description: "Every Hyperliquid wallet ranked by realized PnL, biggest losses or volume over a window. Wallets with fewer than 5 fills in the window are left out.",
370
505
  inputSchema: {
371
506
  type: "object",
372
507
  properties: {
373
- from: { type: "number", description: "Unix ms window start (default 30d ago)" },
374
- to: { type: "number", description: "Unix ms window end (default now)" },
508
+ from: { type: "string", description: time("start", "Default 30 days ago") },
509
+ to: { type: "string", description: time("end", "Default now") },
375
510
  limit: { type: "number", description: "Number of rows, max 200, default 50" },
376
511
  sort_by: {
377
512
  type: "string",
@@ -382,8 +517,8 @@ export const tools = [
382
517
  required: [],
383
518
  },
384
519
  handler: async (args, api) => {
385
- const from = optionalNumber(args, "from", 0);
386
- const to = optionalNumber(args, "to", 0);
520
+ const from = optionalTime(args, "from");
521
+ const to = optionalTime(args, "to");
387
522
  const limit = optionalNumber(args, "limit", 50);
388
523
  const sortBy = optionalString(args, "sort_by", "realized_pnl");
389
524
  const data = await api.getPnLLeaderboard(from, to, limit, sortBy);
@@ -392,13 +527,14 @@ export const tools = [
392
527
  },
393
528
  {
394
529
  name: "get_market_top_wallets",
530
+ title: "Top wallets in a market",
395
531
  description: "Top wallets on one Hyperliquid market by volume or realized PnL, up to 200 rows.",
396
532
  inputSchema: {
397
533
  type: "object",
398
534
  properties: {
399
- market: { type: "string", description: "Market symbol (e.g. 'BTC-PERP' or 'flx:GAS-PERP')" },
400
- from: { type: "number", description: "Unix ms window start (default 24h ago)" },
401
- to: { type: "number", description: "Unix ms window end (default now)" },
535
+ market: { type: "string", description: MARKET },
536
+ from: { type: "string", description: time("start", "Default 24 hours ago") },
537
+ to: { type: "string", description: time("end", "Default now") },
402
538
  limit: { type: "number", description: "Number of rows, max 200, default 25" },
403
539
  sort_by: {
404
540
  type: "string",
@@ -410,8 +546,8 @@ export const tools = [
410
546
  },
411
547
  handler: async (args, api) => {
412
548
  const market = requireString(args, "market");
413
- const from = optionalNumber(args, "from", 0);
414
- const to = optionalNumber(args, "to", 0);
549
+ const from = optionalTime(args, "from");
550
+ const to = optionalTime(args, "to");
415
551
  const limit = optionalNumber(args, "limit", 25);
416
552
  const sortBy = optionalString(args, "sort_by", "volume");
417
553
  const data = await api.getMarketTopWallets(market, from, to, limit, sortBy);
@@ -420,6 +556,7 @@ export const tools = [
420
556
  },
421
557
  {
422
558
  name: "get_markets_snapshot",
559
+ title: "Markets snapshot",
423
560
  description: "Active Hyperliquid markets ranked in one call: mark price, 24h change, 24h volume, open interest and current hourly funding. Sort by abs_funding for the most extreme funding, abs_change_24h for the biggest movers, or volume (default). Use min_volume_usd to skip illiquid markets. Returns the top 25 unless limit is set.",
424
561
  inputSchema: {
425
562
  type: "object",
@@ -459,26 +596,28 @@ export const tools = [
459
596
  },
460
597
  {
461
598
  name: "get_user_summary",
599
+ title: "Wallet summary",
462
600
  description: "One Hyperliquid wallet's stats over a window: volume, realized PnL, fill count, win rate, maker share and markets traded.",
463
601
  inputSchema: {
464
602
  type: "object",
465
603
  properties: {
466
604
  address: { type: "string", description: "0x-prefixed 20-byte hex address" },
467
- from: { type: "number", description: "Unix ms window start (default 24h ago)" },
468
- to: { type: "number", description: "Unix ms window end (default now)" },
605
+ from: { type: "string", description: time("start", "Default 24 hours ago") },
606
+ to: { type: "string", description: time("end", "Default now") },
469
607
  },
470
608
  required: ["address"],
471
609
  },
472
610
  handler: async (args, api) => {
473
611
  const address = requireString(args, "address");
474
- const from = optionalNumber(args, "from", 0);
475
- const to = optionalNumber(args, "to", 0);
612
+ const from = optionalTime(args, "from");
613
+ const to = optionalTime(args, "to");
476
614
  const data = await api.getUserSummary(address, from, to);
477
615
  return JSON.stringify(data, null, 2);
478
616
  },
479
617
  },
480
618
  {
481
619
  name: "get_wallet_labels",
620
+ title: "Wallet labels",
482
621
  description: "Labels for notable Hyperliquid wallets: top whales by 24h volume, top traders by 30-day realized PnL, vaults and vault leaders. Pass addresses to check specific wallets; without them it lists the whales and smart money.",
483
622
  inputSchema: {
484
623
  type: "object",
@@ -502,21 +641,22 @@ export const tools = [
502
641
  },
503
642
  {
504
643
  name: "get_user_pnl_series",
644
+ title: "Wallet PnL curve",
505
645
  description: "Realized PnL curve of a Hyperliquid wallet over time, bucketed (for example hourly or daily).",
506
646
  inputSchema: {
507
647
  type: "object",
508
648
  properties: {
509
649
  address: { type: "string", description: "0x-prefixed 20-byte hex address" },
510
- from: { type: "number", description: "Unix ms window start" },
511
- to: { type: "number", description: "Unix ms window end (default now)" },
650
+ from: { type: "string", description: time("start") },
651
+ to: { type: "string", description: time("end", "Default now") },
512
652
  bucket_ms: { type: "number", description: "Bucket size in ms (e.g. 3600000 for 1h)" },
513
653
  },
514
654
  required: ["address"],
515
655
  },
516
656
  handler: async (args, api) => {
517
657
  const address = requireString(args, "address");
518
- const from = optionalNumber(args, "from", 0);
519
- const to = optionalNumber(args, "to", 0);
658
+ const from = optionalTime(args, "from");
659
+ const to = optionalTime(args, "to");
520
660
  const bucketMs = optionalNumber(args, "bucket_ms", 0);
521
661
  const data = await api.getUserPnLSeries(address, from, to, bucketMs);
522
662
  return JSON.stringify(data, null, 2);
@@ -525,6 +665,7 @@ export const tools = [
525
665
  // ─── Signals (public endpoints: 60 requests per minute per IP, no key) ──
526
666
  {
527
667
  name: "get_top_traders",
668
+ title: "Top traders",
528
669
  description: "Hyperliquid traders ranked across all markets by results over 7, 30 or 90 days: win rate, profit factor, realized PnL, max drawdown and best markets. sort=top ranks by realized PnL, lowrisk by smallest max drawdown, new by most recently first seen.",
529
670
  inputSchema: {
530
671
  type: "object",
@@ -546,11 +687,12 @@ export const tools = [
546
687
  },
547
688
  {
548
689
  name: "get_market_leaders",
690
+ title: "Market leaders",
549
691
  description: "The best traders on one Hyperliquid market, ranked by profit factor then win rate, among wallets with at least 5 closed trades there.",
550
692
  inputSchema: {
551
693
  type: "object",
552
694
  properties: {
553
- market: { type: "string", description: 'Market symbol (e.g. "BTC-PERP", "xyz:NVDA-PERP")' },
695
+ market: { type: "string", description: MARKET },
554
696
  window: { type: "string", description: "Lookback window. Default 30d", enum: ["7d", "30d", "90d"] },
555
697
  limit: { type: "number", description: "Number of traders (1-50, default 20)" },
556
698
  },
@@ -567,11 +709,12 @@ export const tools = [
567
709
  },
568
710
  {
569
711
  name: "get_early_movers",
712
+ title: "Early movers",
570
713
  description: "Wallets whose large orders (at least $10k) on a Hyperliquid market were followed by a price move in their direction within 30 minutes. Returns a lead score in basis points and a hit rate per wallet. Past activity does not predict future results.",
571
714
  inputSchema: {
572
715
  type: "object",
573
716
  properties: {
574
- market: { type: "string", description: 'Market symbol (e.g. "BTC-PERP", "xyz:SKHX-PERP")' },
717
+ market: { type: "string", description: MARKET },
575
718
  window: { type: "string", description: "Lookback window. Default 30d", enum: ["7d", "30d"] },
576
719
  limit: { type: "number", description: "Number of wallets (1-25, default 10)" },
577
720
  },
@@ -588,6 +731,7 @@ export const tools = [
588
731
  },
589
732
  {
590
733
  name: "get_trader_profile",
734
+ title: "Trader profile",
591
735
  description: "A Hyperliquid wallet's trading record over 7, 30 or 90 days: win rate, profit factor, realized PnL, max drawdown and its best markets.",
592
736
  inputSchema: {
593
737
  type: "object",
@@ -608,6 +752,7 @@ export const tools = [
608
752
  // ─── Cohorts ─────────────────────────────────────────────────────────
609
753
  {
610
754
  name: "list_cohorts",
755
+ title: "List cohorts",
611
756
  description: "List available address cohorts: six predefined groups (top_pnl_30d, high_volume_30d, whale_fills, net_withdrawers_7d, liquidation_prone, vault_whales) plus any custom cohorts the caller has created",
612
757
  inputSchema: { type: "object", properties: {}, required: [] },
613
758
  handler: async (_args, api) => {
@@ -617,7 +762,8 @@ export const tools = [
617
762
  },
618
763
  {
619
764
  name: "get_cohort_addresses",
620
- description: "Get the wallet addresses in a specific cohort",
765
+ title: "Cohort addresses",
766
+ description: "Wallet addresses in one cohort; list_cohorts gives the names.",
621
767
  inputSchema: {
622
768
  type: "object",
623
769
  properties: {
@@ -634,7 +780,8 @@ export const tools = [
634
780
  // ─── Account & Status ────────────────────────────────────────────────
635
781
  {
636
782
  name: "get_status",
637
- description: "Get system status, data freshness, and venue health (unauthenticated)",
783
+ title: "API status",
784
+ description: "Ironflow service status right now: overall state, data freshness per venue and component health.",
638
785
  inputSchema: { type: "object", properties: {}, required: [] },
639
786
  handler: async (_args, api) => {
640
787
  const data = await api.getStatus();
@@ -643,7 +790,8 @@ export const tools = [
643
790
  },
644
791
  {
645
792
  name: "get_status_metrics",
646
- description: "Get rolling-window API + pipeline + synthetic performance metrics (unauthenticated)",
793
+ title: "API performance metrics",
794
+ description: "Ironflow performance over rolling windows: API latency and error rate, ingestion lag and synthetic probe timings.",
647
795
  inputSchema: { type: "object", properties: {}, required: [] },
648
796
  handler: async (_args, api) => {
649
797
  const data = await api.getStatusMetrics();
@@ -652,7 +800,8 @@ export const tools = [
652
800
  },
653
801
  {
654
802
  name: "get_status_history",
655
- description: "Get an uptime / freshness timeline. period: '24h' (default) or '7d' (unauthenticated)",
803
+ title: "API uptime history",
804
+ description: "Ironflow uptime and data freshness per hour over the last 24 hours (default) or 7 days.",
656
805
  inputSchema: {
657
806
  type: "object",
658
807
  properties: {
@@ -672,34 +821,47 @@ export const tools = [
672
821
  },
673
822
  {
674
823
  name: "list_markets",
675
- description: "Active Hyperliquid markets with display symbol, class (perp, spot, prediction), HIP-3 issuer and base asset. Use search to find how a market is named (e.g. search 'nvda' finds xyz:NVDA-PERP).",
824
+ title: "List markets",
825
+ description: "Find how a Hyperliquid market is named: perps (native and HIP-3), spot and live HIP-4 outcomes, with symbol, class, issuer, base asset and Hyperliquid coin. Search with q (q=nvda finds xyz:NVDA-PERP and the other NVDA perps). Pages of up to 100 rows; pass pagination.next_cursor as cursor for the next page.",
676
826
  inputSchema: {
677
827
  type: "object",
678
828
  properties: {
679
- source: { type: "string", description: "Venue. Defaults to 'hyperliquid'." },
829
+ q: { type: "string", description: "Case-insensitive search over symbol, base asset, Hyperliquid coin and issuer" },
680
830
  market_class: {
681
831
  type: "string",
682
- description: "Market class. Omit to return perp + spot + prediction. 'prediction' filters to HIP-4 outcome contracts.",
832
+ description: "Only this class. 'prediction' is HIP-4 outcomes. Omit for all.",
683
833
  enum: ["perp", "spot", "prediction", "option"],
684
834
  },
685
835
  issuer: {
686
836
  type: "string",
687
- description: "HIP-3 issuer code (e.g. 'flx'). Pass empty string '' to return only native non-builder markets. Omit to include every issuer.",
837
+ description: "HIP-3 issuer code (e.g. 'xyz'). '' returns native markets only. Omit for every issuer.",
838
+ },
839
+ include_inactive: {
840
+ type: "boolean",
841
+ description: "Also return closed markets (delisted, settled HIP-4 outcomes), which carry active_to_ms. Default false.",
688
842
  },
689
- search: { type: "string", description: "Case-insensitive match on symbol, base asset or Hyperliquid coin" },
690
- limit: { type: "number", description: `Rows to return (default ${DEFAULT_MARKETS_LIMIT})` },
843
+ limit: { type: "number", description: `Rows per page (default ${DEFAULT_MARKETS_LIMIT}, max ${MAX_MARKETS_LIMIT})` },
844
+ cursor: { type: "string", description: "pagination.next_cursor from the previous page, with the same filters" },
845
+ source: { type: "string", description: "Venue. Default 'hyperliquid'." },
691
846
  },
692
847
  required: [],
693
848
  },
694
849
  handler: async (args, api) => {
695
- const source = optionalString(args, "source", "");
696
- const market_class = optionalString(args, "market_class", "");
697
850
  // Tri-state: present-with-empty-string means "native only".
698
- const issuer = Object.prototype.hasOwnProperty.call(args, "issuer")
699
- ? optionalString(args, "issuer", "")
700
- : undefined;
701
- const data = (await api.listMarkets(source, market_class, issuer));
702
- return JSON.stringify(shapeMarkets(data, { search: optionalString(args, "search", ""), limit: optionalNumber(args, "limit", DEFAULT_MARKETS_LIMIT) }));
851
+ const issuer = Object.prototype.hasOwnProperty.call(args, "issuer") ? optionalString(args, "issuer", "") : undefined;
852
+ // search was this tool's name for q before 0.11.0; still accepted.
853
+ const q = optionalString(args, "q", "").trim() || optionalString(args, "search", "").trim();
854
+ const limit = Math.min(optionalNumber(args, "limit", DEFAULT_MARKETS_LIMIT), MAX_MARKETS_LIMIT);
855
+ const data = (await api.listMarkets({
856
+ source: optionalString(args, "source", ""),
857
+ marketClass: optionalString(args, "market_class", ""),
858
+ issuer,
859
+ q,
860
+ includeInactive: optionalBoolean(args, "include_inactive"),
861
+ limit,
862
+ cursor: optionalString(args, "cursor", "").trim(),
863
+ }));
864
+ return JSON.stringify(shapeMarkets(data, { search: q, limit }));
703
865
  },
704
866
  },
705
867
  ];