@ironflowsh/mcp 0.9.0 → 0.11.1

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,8 +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 { BEHAVIOR_LABELS, BEHAVIOR_SORT_KEYS, behaviorSQL, MAX_BEHAVIOR_LIMIT, rowsToObjects, runQuery } from "./query.js";
5
- 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";
6
6
  // ─── Arg validation helpers ──────────────────────────────────────────────────
7
7
  function requireString(args, key) {
8
8
  const val = args[key];
@@ -30,17 +30,56 @@ function optionalString(args, key, defaultVal) {
30
30
  }
31
31
  return val;
32
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}` : ""}`;
33
70
  // ─── Tools ──────────────────────────────────────────────────────────────────
34
71
  export const tools = [
35
72
  {
36
73
  name: "describe_data",
37
- 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.",
74
+ title: "Describe SQL tables",
75
+ description: "Returns the SQL tables of Ironflow's Hyperliquid data (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.",
38
76
  inputSchema: { type: "object", properties: {}, required: [] },
39
77
  handler: async (_args, api) => JSON.stringify(await api.get("/v1/query/schema")),
40
78
  },
41
79
  {
42
80
  name: "run_query",
43
- 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.",
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). 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.",
44
83
  inputSchema: {
45
84
  type: "object",
46
85
  properties: {
@@ -57,6 +96,7 @@ export const tools = [
57
96
  },
58
97
  {
59
98
  name: "get_wallet_behavior",
99
+ title: "Wallet behavior labels",
60
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.",
61
101
  inputSchema: {
62
102
  type: "object",
@@ -89,11 +129,12 @@ export const tools = [
89
129
  },
90
130
  {
91
131
  name: "get_price",
132
+ title: "Market price",
92
133
  description: "Latest mark price of a Hyperliquid market: native perps, HIP-3 builder perps (e.g. xyz:NVDA-PERP) and spot.",
93
134
  inputSchema: {
94
135
  type: "object",
95
136
  properties: {
96
- 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 },
97
138
  },
98
139
  required: ["market"],
99
140
  },
@@ -105,11 +146,12 @@ export const tools = [
105
146
  },
106
147
  {
107
148
  name: "get_recent_trades",
149
+ title: "Recent trades",
108
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.",
109
151
  inputSchema: {
110
152
  type: "object",
111
153
  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")' },
154
+ market: { type: "string", description: MARKET },
113
155
  limit: { type: "number", description: "Number of trades to return (1-100, default 20)" },
114
156
  },
115
157
  required: ["market"],
@@ -123,11 +165,12 @@ export const tools = [
123
165
  },
124
166
  {
125
167
  name: "get_candles",
168
+ title: "Candles (OHLCV)",
126
169
  description: "OHLCV candles for a Hyperliquid market, computed from tick-level trades.",
127
170
  inputSchema: {
128
171
  type: "object",
129
172
  properties: {
130
- 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 },
131
174
  interval: { type: "string", description: "Candle interval (1m, 5m, 15m, 1h, 4h, 1d). Default: 1h", enum: ["1m", "5m", "15m", "1h", "4h", "1d"] },
132
175
  limit: { type: "number", description: "Number of candles to return (1-500, default 24)" },
133
176
  },
@@ -143,11 +186,12 @@ export const tools = [
143
186
  },
144
187
  {
145
188
  name: "get_funding_rates",
189
+ title: "Funding rate history",
146
190
  description: "Hourly funding rate history for a Hyperliquid perp, including HIP-3 builder perps. A positive rate means longs pay shorts.",
147
191
  inputSchema: {
148
192
  type: "object",
149
193
  properties: {
150
- 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 },
151
195
  limit: { type: "number", description: "Number of funding rate entries (1-100, default 10)" },
152
196
  },
153
197
  required: ["market"],
@@ -161,11 +205,12 @@ export const tools = [
161
205
  },
162
206
  {
163
207
  name: "get_open_interest",
164
- 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.",
165
210
  inputSchema: {
166
211
  type: "object",
167
212
  properties: {
168
- 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 },
169
214
  },
170
215
  required: ["market"],
171
216
  },
@@ -177,11 +222,12 @@ export const tools = [
177
222
  },
178
223
  {
179
224
  name: "get_liquidations",
225
+ title: "Liquidations",
180
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.",
181
227
  inputSchema: {
182
228
  type: "object",
183
229
  properties: {
184
- 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 },
185
231
  limit: { type: "number", description: "Number of liquidation events (1-100, default 20)" },
186
232
  },
187
233
  required: ["market"],
@@ -196,12 +242,13 @@ export const tools = [
196
242
  // ─── Additional Market Data ──────────────────────────────────────────
197
243
  {
198
244
  name: "get_liquidation_summary",
245
+ title: "Liquidation summary",
199
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.",
200
247
  inputSchema: {
201
248
  type: "object",
202
249
  properties: {
203
250
  minutes: { type: "number", description: "Lookback in minutes (default 60, max 240)" },
204
- market: { type: "string", description: 'Optional market filter (e.g. "BTC-PERP")' },
251
+ market: { type: "string", description: MARKET_FILTER },
205
252
  },
206
253
  required: [],
207
254
  },
@@ -216,12 +263,13 @@ export const tools = [
216
263
  },
217
264
  {
218
265
  name: "get_fills",
266
+ title: "Wallet fills",
219
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.",
220
268
  inputSchema: {
221
269
  type: "object",
222
270
  properties: {
223
271
  address: { type: "string", description: "Wallet address (e.g. 0x...)" },
224
- 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 },
225
273
  limit: { type: "number", description: "Number of fills (1-100, default 20)" },
226
274
  },
227
275
  required: ["address"],
@@ -236,11 +284,12 @@ export const tools = [
236
284
  },
237
285
  {
238
286
  name: "get_mark_prices",
239
- 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.",
240
289
  inputSchema: {
241
290
  type: "object",
242
291
  properties: {
243
- 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 },
244
293
  },
245
294
  required: ["market"],
246
295
  },
@@ -252,19 +301,25 @@ export const tools = [
252
301
  },
253
302
  {
254
303
  name: "get_vault_operations",
255
- 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).",
256
306
  inputSchema: {
257
307
  type: "object",
258
308
  properties: {
259
- vault: { type: "string", description: "Vault address" },
260
- 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)" },
261
311
  limit: { type: "number", description: "Number of events (1-100, default 20)" },
262
312
  },
263
313
  required: [],
264
314
  },
265
315
  handler: async (args, api) => {
266
- const vault = optionalString(args, "vault", "");
267
- 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
+ }
268
323
  const limit = optionalNumber(args, "limit", 20);
269
324
  const data = await api.getVaultOperations(vault, address, String(limit));
270
325
  return JSON.stringify(data, null, 2);
@@ -273,11 +328,12 @@ export const tools = [
273
328
  // ─── Analytics ───────────────────────────────────────────────────────
274
329
  {
275
330
  name: "get_liquidation_levels",
331
+ title: "Liquidation levels",
276
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.",
277
333
  inputSchema: {
278
334
  type: "object",
279
335
  properties: {
280
- 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 },
281
337
  bucket_size: { type: "string", description: "Price bucket size (1, 5, 10, 50, 100, 500, 1000, 5000). Default: 100" },
282
338
  },
283
339
  required: ["market"],
@@ -291,27 +347,45 @@ export const tools = [
291
347
  },
292
348
  {
293
349
  name: "get_vault_leaderboard",
294
- 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.`,
295
352
  inputSchema: {
296
353
  type: "object",
297
354
  properties: {
298
- limit: { type: "number", description: "Number of vaults (default 10)" },
355
+ limit: { type: "number", description: `Number of vaults (default 10, max ${MAX_VAULT_LEADERBOARD_LIMIT})` },
299
356
  },
300
357
  required: [],
301
358
  },
302
359
  handler: async (args, api) => {
303
360
  const limit = optionalNumber(args, "limit", 10);
304
- const data = await api.getVaultLeaderboard(String(limit));
305
- 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
+ });
306
379
  },
307
380
  },
308
381
  {
309
382
  name: "get_funding_stats",
383
+ title: "Funding statistics",
310
384
  description: "Funding statistics per time bucket for a Hyperliquid perp: average, minimum, maximum and annualized rate.",
311
385
  inputSchema: {
312
386
  type: "object",
313
387
  properties: {
314
- 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 },
315
389
  interval: { type: "string", description: "Time interval (1h, 4h, 8h, 1d, 7d). Default: 1d", enum: ["1h", "4h", "8h", "1d", "7d"] },
316
390
  limit: { type: "number", description: "Number of data points (default 7)" },
317
391
  },
@@ -334,6 +408,7 @@ export const tools = [
334
408
  // JSON object the agent can reason over.
335
409
  {
336
410
  name: "get_user_state",
411
+ title: "Wallet positions",
337
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?'",
338
413
  inputSchema: {
339
414
  type: "object",
@@ -350,14 +425,15 @@ export const tools = [
350
425
  },
351
426
  {
352
427
  name: "get_user_funding",
428
+ title: "Wallet funding payments",
353
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.",
354
430
  inputSchema: {
355
431
  type: "object",
356
432
  properties: {
357
433
  address: { type: "string", description: "0x-prefixed 20-byte hex address" },
358
- market: { type: "string", description: "Optional market filter (e.g. BTC-PERP)" },
359
- from: { type: "number", description: "Unix ms window start (inclusive)" },
360
- 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") },
361
437
  bucket: { type: "string", enum: ["1h", "1d", "raw"], description: "Aggregation bucket; default 1d" },
362
438
  limit: { type: "number", description: `Most recent rows to return (default ${DEFAULT_FUNDING_ROWS})` },
363
439
  },
@@ -366,8 +442,8 @@ export const tools = [
366
442
  handler: async (args, api) => {
367
443
  const address = requireString(args, "address");
368
444
  const market = optionalString(args, "market", "");
369
- const from = optionalNumber(args, "from", 0);
370
- const to = optionalNumber(args, "to", 0);
445
+ const from = optionalTime(args, "from");
446
+ const to = optionalTime(args, "to");
371
447
  const bucket = optionalString(args, "bucket", "1d");
372
448
  const data = (await api.getUserFunding(address, market, from, to, bucket));
373
449
  return JSON.stringify(shapeUserFunding(data, optionalNumber(args, "limit", DEFAULT_FUNDING_ROWS)));
@@ -375,35 +451,37 @@ export const tools = [
375
451
  },
376
452
  {
377
453
  name: "get_user_maker_taker",
454
+ title: "Wallet maker/taker split",
378
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.",
379
456
  inputSchema: {
380
457
  type: "object",
381
458
  properties: {
382
459
  address: { type: "string", description: "0x-prefixed 20-byte hex address" },
383
- market: { type: "string", description: "Optional market filter" },
384
- from: { type: "number", description: "Unix ms window start" },
385
- 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") },
386
463
  },
387
464
  required: ["address"],
388
465
  },
389
466
  handler: async (args, api) => {
390
467
  const address = requireString(args, "address");
391
468
  const market = optionalString(args, "market", "");
392
- const from = optionalNumber(args, "from", 0);
393
- const to = optionalNumber(args, "to", 0);
469
+ const from = optionalTime(args, "from");
470
+ const to = optionalTime(args, "to");
394
471
  const data = await api.getUserMakerTaker(address, market, from, to);
395
472
  return JSON.stringify(data, null, 2);
396
473
  },
397
474
  },
398
475
  {
399
476
  name: "get_user_ledger",
477
+ title: "Wallet ledger",
400
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.",
401
479
  inputSchema: {
402
480
  type: "object",
403
481
  properties: {
404
482
  address: { type: "string", description: "0x-prefixed 20-byte hex address" },
405
- from: { type: "number", description: "Unix ms window start" },
406
- to: { type: "number", description: "Unix ms window end" },
483
+ from: { type: "string", description: time("start") },
484
+ to: { type: "string", description: time("end") },
407
485
  event_types: { type: "string", description: "Comma-separated type filter (deposit,withdrawal,vault_deposit,vault_withdraw,vault_transfer_in,vault_transfer_out)" },
408
486
  limit: { type: "number", description: "Max entries, default 1000, cap 10000" },
409
487
  },
@@ -411,8 +489,8 @@ export const tools = [
411
489
  },
412
490
  handler: async (args, api) => {
413
491
  const address = requireString(args, "address");
414
- const from = optionalNumber(args, "from", 0);
415
- const to = optionalNumber(args, "to", 0);
492
+ const from = optionalTime(args, "from");
493
+ const to = optionalTime(args, "to");
416
494
  const eventTypes = optionalString(args, "event_types", "");
417
495
  const limit = optionalNumber(args, "limit", 1000);
418
496
  const data = await api.getUserLedger(address, from, to, eventTypes, limit);
@@ -422,12 +500,13 @@ export const tools = [
422
500
  // ─── Cross-wallet ranking (v0.34+) ─────────────────────────────────────
423
501
  {
424
502
  name: "get_pnl_leaderboard",
503
+ title: "PnL leaderboard",
425
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.",
426
505
  inputSchema: {
427
506
  type: "object",
428
507
  properties: {
429
- from: { type: "number", description: "Unix ms window start (default 30d ago)" },
430
- 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") },
431
510
  limit: { type: "number", description: "Number of rows, max 200, default 50" },
432
511
  sort_by: {
433
512
  type: "string",
@@ -438,8 +517,8 @@ export const tools = [
438
517
  required: [],
439
518
  },
440
519
  handler: async (args, api) => {
441
- const from = optionalNumber(args, "from", 0);
442
- const to = optionalNumber(args, "to", 0);
520
+ const from = optionalTime(args, "from");
521
+ const to = optionalTime(args, "to");
443
522
  const limit = optionalNumber(args, "limit", 50);
444
523
  const sortBy = optionalString(args, "sort_by", "realized_pnl");
445
524
  const data = await api.getPnLLeaderboard(from, to, limit, sortBy);
@@ -448,13 +527,14 @@ export const tools = [
448
527
  },
449
528
  {
450
529
  name: "get_market_top_wallets",
530
+ title: "Top wallets in a market",
451
531
  description: "Top wallets on one Hyperliquid market by volume or realized PnL, up to 200 rows.",
452
532
  inputSchema: {
453
533
  type: "object",
454
534
  properties: {
455
- market: { type: "string", description: "Market symbol (e.g. 'BTC-PERP' or 'flx:GAS-PERP')" },
456
- from: { type: "number", description: "Unix ms window start (default 24h ago)" },
457
- 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") },
458
538
  limit: { type: "number", description: "Number of rows, max 200, default 25" },
459
539
  sort_by: {
460
540
  type: "string",
@@ -466,8 +546,8 @@ export const tools = [
466
546
  },
467
547
  handler: async (args, api) => {
468
548
  const market = requireString(args, "market");
469
- const from = optionalNumber(args, "from", 0);
470
- const to = optionalNumber(args, "to", 0);
549
+ const from = optionalTime(args, "from");
550
+ const to = optionalTime(args, "to");
471
551
  const limit = optionalNumber(args, "limit", 25);
472
552
  const sortBy = optionalString(args, "sort_by", "volume");
473
553
  const data = await api.getMarketTopWallets(market, from, to, limit, sortBy);
@@ -476,6 +556,7 @@ export const tools = [
476
556
  },
477
557
  {
478
558
  name: "get_markets_snapshot",
559
+ title: "Markets snapshot",
479
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.",
480
561
  inputSchema: {
481
562
  type: "object",
@@ -515,26 +596,28 @@ export const tools = [
515
596
  },
516
597
  {
517
598
  name: "get_user_summary",
599
+ title: "Wallet summary",
518
600
  description: "One Hyperliquid wallet's stats over a window: volume, realized PnL, fill count, win rate, maker share and markets traded.",
519
601
  inputSchema: {
520
602
  type: "object",
521
603
  properties: {
522
604
  address: { type: "string", description: "0x-prefixed 20-byte hex address" },
523
- from: { type: "number", description: "Unix ms window start (default 24h ago)" },
524
- 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") },
525
607
  },
526
608
  required: ["address"],
527
609
  },
528
610
  handler: async (args, api) => {
529
611
  const address = requireString(args, "address");
530
- const from = optionalNumber(args, "from", 0);
531
- const to = optionalNumber(args, "to", 0);
612
+ const from = optionalTime(args, "from");
613
+ const to = optionalTime(args, "to");
532
614
  const data = await api.getUserSummary(address, from, to);
533
615
  return JSON.stringify(data, null, 2);
534
616
  },
535
617
  },
536
618
  {
537
619
  name: "get_wallet_labels",
620
+ title: "Wallet labels",
538
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.",
539
622
  inputSchema: {
540
623
  type: "object",
@@ -558,21 +641,22 @@ export const tools = [
558
641
  },
559
642
  {
560
643
  name: "get_user_pnl_series",
644
+ title: "Wallet PnL curve",
561
645
  description: "Realized PnL curve of a Hyperliquid wallet over time, bucketed (for example hourly or daily).",
562
646
  inputSchema: {
563
647
  type: "object",
564
648
  properties: {
565
649
  address: { type: "string", description: "0x-prefixed 20-byte hex address" },
566
- from: { type: "number", description: "Unix ms window start" },
567
- 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") },
568
652
  bucket_ms: { type: "number", description: "Bucket size in ms (e.g. 3600000 for 1h)" },
569
653
  },
570
654
  required: ["address"],
571
655
  },
572
656
  handler: async (args, api) => {
573
657
  const address = requireString(args, "address");
574
- const from = optionalNumber(args, "from", 0);
575
- const to = optionalNumber(args, "to", 0);
658
+ const from = optionalTime(args, "from");
659
+ const to = optionalTime(args, "to");
576
660
  const bucketMs = optionalNumber(args, "bucket_ms", 0);
577
661
  const data = await api.getUserPnLSeries(address, from, to, bucketMs);
578
662
  return JSON.stringify(data, null, 2);
@@ -581,6 +665,7 @@ export const tools = [
581
665
  // ─── Signals (public endpoints: 60 requests per minute per IP, no key) ──
582
666
  {
583
667
  name: "get_top_traders",
668
+ title: "Top traders",
584
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.",
585
670
  inputSchema: {
586
671
  type: "object",
@@ -602,11 +687,12 @@ export const tools = [
602
687
  },
603
688
  {
604
689
  name: "get_market_leaders",
690
+ title: "Market leaders",
605
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.",
606
692
  inputSchema: {
607
693
  type: "object",
608
694
  properties: {
609
- market: { type: "string", description: 'Market symbol (e.g. "BTC-PERP", "xyz:NVDA-PERP")' },
695
+ market: { type: "string", description: MARKET },
610
696
  window: { type: "string", description: "Lookback window. Default 30d", enum: ["7d", "30d", "90d"] },
611
697
  limit: { type: "number", description: "Number of traders (1-50, default 20)" },
612
698
  },
@@ -623,11 +709,12 @@ export const tools = [
623
709
  },
624
710
  {
625
711
  name: "get_early_movers",
712
+ title: "Early movers",
626
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.",
627
714
  inputSchema: {
628
715
  type: "object",
629
716
  properties: {
630
- market: { type: "string", description: 'Market symbol (e.g. "BTC-PERP", "xyz:SKHX-PERP")' },
717
+ market: { type: "string", description: MARKET },
631
718
  window: { type: "string", description: "Lookback window. Default 30d", enum: ["7d", "30d"] },
632
719
  limit: { type: "number", description: "Number of wallets (1-25, default 10)" },
633
720
  },
@@ -644,6 +731,7 @@ export const tools = [
644
731
  },
645
732
  {
646
733
  name: "get_trader_profile",
734
+ title: "Trader profile",
647
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.",
648
736
  inputSchema: {
649
737
  type: "object",
@@ -664,6 +752,7 @@ export const tools = [
664
752
  // ─── Cohorts ─────────────────────────────────────────────────────────
665
753
  {
666
754
  name: "list_cohorts",
755
+ title: "List cohorts",
667
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",
668
757
  inputSchema: { type: "object", properties: {}, required: [] },
669
758
  handler: async (_args, api) => {
@@ -673,7 +762,8 @@ export const tools = [
673
762
  },
674
763
  {
675
764
  name: "get_cohort_addresses",
676
- description: "Get the wallet addresses in a specific cohort",
765
+ title: "Cohort addresses",
766
+ description: "Wallet addresses in one named wallet cohort.",
677
767
  inputSchema: {
678
768
  type: "object",
679
769
  properties: {
@@ -690,7 +780,8 @@ export const tools = [
690
780
  // ─── Account & Status ────────────────────────────────────────────────
691
781
  {
692
782
  name: "get_status",
693
- 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.",
694
785
  inputSchema: { type: "object", properties: {}, required: [] },
695
786
  handler: async (_args, api) => {
696
787
  const data = await api.getStatus();
@@ -699,7 +790,8 @@ export const tools = [
699
790
  },
700
791
  {
701
792
  name: "get_status_metrics",
702
- 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.",
703
795
  inputSchema: { type: "object", properties: {}, required: [] },
704
796
  handler: async (_args, api) => {
705
797
  const data = await api.getStatusMetrics();
@@ -708,7 +800,8 @@ export const tools = [
708
800
  },
709
801
  {
710
802
  name: "get_status_history",
711
- 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.",
712
805
  inputSchema: {
713
806
  type: "object",
714
807
  properties: {
@@ -728,34 +821,47 @@ export const tools = [
728
821
  },
729
822
  {
730
823
  name: "list_markets",
731
- 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.",
732
826
  inputSchema: {
733
827
  type: "object",
734
828
  properties: {
735
- source: { type: "string", description: "Venue. Defaults to 'hyperliquid'." },
829
+ q: { type: "string", description: "Case-insensitive search over symbol, base asset, Hyperliquid coin and issuer" },
736
830
  market_class: {
737
831
  type: "string",
738
- 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.",
739
833
  enum: ["perp", "spot", "prediction", "option"],
740
834
  },
741
835
  issuer: {
742
836
  type: "string",
743
- 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.",
744
842
  },
745
- search: { type: "string", description: "Case-insensitive match on symbol, base asset or Hyperliquid coin" },
746
- 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'." },
747
846
  },
748
847
  required: [],
749
848
  },
750
849
  handler: async (args, api) => {
751
- const source = optionalString(args, "source", "");
752
- const market_class = optionalString(args, "market_class", "");
753
850
  // Tri-state: present-with-empty-string means "native only".
754
- const issuer = Object.prototype.hasOwnProperty.call(args, "issuer")
755
- ? optionalString(args, "issuer", "")
756
- : undefined;
757
- const data = (await api.listMarkets(source, market_class, issuer));
758
- 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 }));
759
865
  },
760
866
  },
761
867
  ];