pmxt-core 2.50.16 → 2.51.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.
Files changed (50) hide show
  1. package/dist/exchanges/hunch/api.d.ts +736 -0
  2. package/dist/exchanges/hunch/api.js +820 -0
  3. package/dist/exchanges/hunch/auth.d.ts +36 -0
  4. package/dist/exchanges/hunch/auth.js +63 -0
  5. package/dist/exchanges/hunch/errors.d.ts +26 -0
  6. package/dist/exchanges/hunch/errors.js +94 -0
  7. package/dist/exchanges/hunch/fetcher.d.ts +204 -0
  8. package/dist/exchanges/hunch/fetcher.js +130 -0
  9. package/dist/exchanges/hunch/index.d.ts +51 -0
  10. package/dist/exchanges/hunch/index.js +330 -0
  11. package/dist/exchanges/hunch/normalizer.d.ts +56 -0
  12. package/dist/exchanges/hunch/normalizer.js +180 -0
  13. package/dist/exchanges/hunch/price.d.ts +5 -0
  14. package/dist/exchanges/hunch/price.js +12 -0
  15. package/dist/exchanges/hunch/utils.d.ts +79 -0
  16. package/dist/exchanges/hunch/utils.js +182 -0
  17. package/dist/exchanges/hunch/websocket.d.ts +25 -0
  18. package/dist/exchanges/hunch/websocket.js +137 -0
  19. package/dist/exchanges/hyperliquid/auth.js +15 -2
  20. package/dist/exchanges/hyperliquid/fetcher.d.ts +26 -1
  21. package/dist/exchanges/hyperliquid/fetcher.js +66 -4
  22. package/dist/exchanges/hyperliquid/index.d.ts +4 -1
  23. package/dist/exchanges/hyperliquid/index.js +84 -20
  24. package/dist/exchanges/hyperliquid/normalizer.d.ts +4 -3
  25. package/dist/exchanges/hyperliquid/normalizer.js +69 -11
  26. package/dist/exchanges/hyperliquid/utils.d.ts +7 -1
  27. package/dist/exchanges/hyperliquid/utils.js +16 -4
  28. package/dist/exchanges/kalshi/api.d.ts +1 -1
  29. package/dist/exchanges/kalshi/api.js +1 -1
  30. package/dist/exchanges/limitless/api.d.ts +1 -1
  31. package/dist/exchanges/limitless/api.js +1 -1
  32. package/dist/exchanges/myriad/api.d.ts +1 -1
  33. package/dist/exchanges/myriad/api.js +1 -1
  34. package/dist/exchanges/opinion/api.d.ts +1 -1
  35. package/dist/exchanges/opinion/api.js +1 -1
  36. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  37. package/dist/exchanges/polymarket/api-clob.js +1 -1
  38. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  39. package/dist/exchanges/polymarket/api-data.js +1 -1
  40. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  41. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  42. package/dist/exchanges/probable/api.d.ts +1 -1
  43. package/dist/exchanges/probable/api.js +1 -1
  44. package/dist/index.d.ts +4 -0
  45. package/dist/index.js +5 -1
  46. package/dist/server/app.js +1 -0
  47. package/dist/server/exchange-factory.js +8 -0
  48. package/dist/server/openapi.yaml +17 -0
  49. package/dist/types.d.ts +4 -0
  50. package/package.json +3 -3
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HUNCH_PROMOTED_MARKET_KEYS = exports.BASE_USDC_ADDRESS = exports.BASE_CHAIN_ID = exports.DEFAULT_BASE_URL = void 0;
4
+ exports.mapHunchStatus = mapHunchStatus;
5
+ exports.mapStatusToHunch = mapStatusToHunch;
6
+ exports.buildOutcomeId = buildOutcomeId;
7
+ exports.parseHunchSide = parseHunchSide;
8
+ exports.mapHunchMarketToUnified = mapHunchMarketToUnified;
9
+ const market_utils_1 = require("../../utils/market-utils");
10
+ const metadata_1 = require("../../utils/metadata");
11
+ /**
12
+ * Canonical Hunch agent-platform base URL. Reads are keyless (CORS `*`);
13
+ * the trade route settles real Base USDC via x402 / EIP-3009.
14
+ */
15
+ exports.DEFAULT_BASE_URL = 'https://www.playhunch.xyz';
16
+ /** Base mainnet — the ONLY settlement chain on Hunch's agent rail. */
17
+ exports.BASE_CHAIN_ID = 8453;
18
+ /** USDC on Base (the EIP-3009 `transferWithAuthorization` asset). */
19
+ exports.BASE_USDC_ADDRESS = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913';
20
+ /**
21
+ * Raw Hunch fields already promoted to first-class Unified columns — excluded
22
+ * from `sourceMetadata` so we capture only what the unified shape would drop
23
+ * (e.g. headline, deadlineLabel, feeRecipientLabel, defaultTicketUsd, links).
24
+ */
25
+ exports.HUNCH_PROMOTED_MARKET_KEYS = [
26
+ 'id',
27
+ 'slug',
28
+ 'question',
29
+ 'summary',
30
+ 'category',
31
+ 'tokenSymbol',
32
+ 'chainId',
33
+ 'deadlineAt',
34
+ 'status',
35
+ 'feeBps',
36
+ 'virtualLiquidityUsd',
37
+ 'volumeUsd',
38
+ 'targetMarketCapUsd',
39
+ 'outcomes',
40
+ ];
41
+ /**
42
+ * Map a Hunch status (open / closed / resolved) to the pmxt unified lifecycle
43
+ * vocabulary (active / inactive / closed). Unknown → 'active' (Hunch only lists
44
+ * `open` markets when `status=open`, so this is defensive).
45
+ */
46
+ function mapHunchStatus(status) {
47
+ switch (status) {
48
+ case 'open':
49
+ return 'active';
50
+ case 'closed':
51
+ return 'inactive';
52
+ case 'resolved':
53
+ case 'voided':
54
+ return 'closed';
55
+ default:
56
+ return 'active';
57
+ }
58
+ }
59
+ /**
60
+ * Translate the pmxt unified `status` filter into the Hunch `status` query
61
+ * value. Hunch only accepts `open` | `all`; 'inactive'/'closed' have no live
62
+ * listing, so they fold to `all` (the caller filters client-side afterwards).
63
+ */
64
+ function mapStatusToHunch(status) {
65
+ if (!status)
66
+ return undefined;
67
+ switch (status) {
68
+ case 'active':
69
+ return 'open';
70
+ case 'inactive':
71
+ case 'closed':
72
+ case 'all':
73
+ return 'all';
74
+ default:
75
+ return undefined;
76
+ }
77
+ }
78
+ /**
79
+ * Compose an outcome id that round-trips back to a Hunch trade `side`.
80
+ *
81
+ * Encoding: `${marketId}:${side}` where `side` is `yes` | `no` for binary
82
+ * markets, or the parimutuel bucket `key` (e.g. `le-330m`, `up`, `down`,
83
+ * a date-window key) for N-way markets. `parseHunchSide()` reverses it.
84
+ */
85
+ function buildOutcomeId(marketId, side) {
86
+ return `${marketId}:${side}`;
87
+ }
88
+ /**
89
+ * Reverse {@link buildOutcomeId}: pull the Hunch `side` token back out of a
90
+ * unified `outcomeId`. The market id itself may contain a `:` is NOT a concern —
91
+ * Hunch ids are slug-shaped (`[a-z0-9-]`), so the FIRST colon never appears
92
+ * inside an id; we split on the LAST colon to be safe regardless.
93
+ */
94
+ function parseHunchSide(outcomeId) {
95
+ const idx = outcomeId.lastIndexOf(':');
96
+ if (idx <= 0 || idx === outcomeId.length - 1) {
97
+ // No separator — treat the whole thing as the side with no market id.
98
+ return { marketId: '', side: outcomeId };
99
+ }
100
+ return {
101
+ marketId: outcomeId.slice(0, idx),
102
+ side: outcomeId.slice(idx + 1),
103
+ };
104
+ }
105
+ /**
106
+ * Shared market normalizer used by both the live fetch path and the (rare)
107
+ * direct-mapping helper. Pulls a Hunch market ref into a {@link UnifiedMarket}.
108
+ *
109
+ * - Binary markets (`outcomes == null`) get YES/NO outcomes priced from `odds`
110
+ * when supplied (research/quote-derived), else flat 0 (the list endpoint
111
+ * does not carry live odds — they ride on the quote/research single-market
112
+ * reads, matching Myriad's "static on list, live on detail" model).
113
+ * - N-way markets expand `outcomes[]` into one {@link MarketOutcome} per rung,
114
+ * carrying `impliedPct/100` as the price when a live ladder is supplied.
115
+ *
116
+ * `outcomeId` round-trips to a Hunch `side` (see {@link buildOutcomeId}).
117
+ */
118
+ function mapHunchMarketToUnified(raw, odds, ladder) {
119
+ if (!raw || !raw.id)
120
+ return null;
121
+ const marketId = raw.id;
122
+ let outcomes;
123
+ if (Array.isArray(raw.outcomes) && raw.outcomes.length > 0) {
124
+ // N-way parimutuel ladder / date-window market.
125
+ const ladderByKey = new Map();
126
+ for (const lo of ladder?.outcomes ?? [])
127
+ ladderByKey.set(lo.key, lo);
128
+ outcomes = raw.outcomes.map((o) => {
129
+ const live = ladderByKey.get(o.key);
130
+ const price = live && typeof live.impliedPct === 'number' ? live.impliedPct / 100 : 0;
131
+ return {
132
+ outcomeId: buildOutcomeId(marketId, o.key),
133
+ marketId,
134
+ label: o.label,
135
+ price,
136
+ metadata: {
137
+ key: o.key,
138
+ shortLabel: o.shortLabel,
139
+ lowerUsd: o.lowerUsd,
140
+ upperUsd: o.upperUsd,
141
+ startAt: o.startAt ?? null,
142
+ endAt: o.endAt ?? null,
143
+ backedUsd: live?.backedUsd ?? null,
144
+ isCurrent: live?.isCurrent ?? null,
145
+ },
146
+ };
147
+ });
148
+ }
149
+ else {
150
+ // Binary YES/NO market.
151
+ const yesPrice = odds && typeof odds.yesPriceCents === 'number' ? odds.yesPriceCents / 100 : 0;
152
+ const noPrice = odds && typeof odds.noPriceCents === 'number'
153
+ ? odds.noPriceCents / 100
154
+ : yesPrice > 0
155
+ ? 1 - yesPrice
156
+ : 0;
157
+ outcomes = [
158
+ { outcomeId: buildOutcomeId(marketId, 'yes'), marketId, label: 'Yes', price: yesPrice },
159
+ { outcomeId: buildOutcomeId(marketId, 'no'), marketId, label: 'No', price: noPrice },
160
+ ];
161
+ }
162
+ const um = {
163
+ marketId,
164
+ title: raw.question || raw.shortTitle || '',
165
+ description: raw.summary || '',
166
+ slug: raw.slug,
167
+ outcomes,
168
+ resolutionDate: raw.deadlineAt ? new Date(raw.deadlineAt) : undefined,
169
+ // Hunch is parimutuel and reports no 24h volume split — surface 0.
170
+ volume24h: 0,
171
+ volume: typeof raw.volumeUsd === 'number' ? raw.volumeUsd : undefined,
172
+ liquidity: Number(raw.virtualLiquidityUsd || 0),
173
+ url: raw.links?.app || `${exports.DEFAULT_BASE_URL}/markets/${raw.slug || marketId}`,
174
+ category: raw.category,
175
+ tags: raw.tokenSymbol ? [raw.tokenSymbol] : [],
176
+ status: mapHunchStatus(raw.status),
177
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(raw, exports.HUNCH_PROMOTED_MARKET_KEYS),
178
+ };
179
+ // Standardize yes/no/up/down convenience accessors for binary markets.
180
+ (0, market_utils_1.addBinaryOutcomes)(um);
181
+ return um;
182
+ }
@@ -0,0 +1,25 @@
1
+ import { OrderBook, Trade } from '../../types';
2
+ export type FetchOrderBookFn = (id: string) => Promise<OrderBook>;
3
+ export type FetchTradesFn = (id: string, limit: number) => Promise<Trade[]>;
4
+ export declare class HunchWebSocket {
5
+ private readonly fetchOrderBook;
6
+ private readonly fetchTrades;
7
+ private readonly pollInterval;
8
+ private orderBookTimers;
9
+ private tradeTimers;
10
+ private orderBookResolvers;
11
+ private orderBookRejecters;
12
+ private tradeResolvers;
13
+ private tradeRejecters;
14
+ private seenTradeIds;
15
+ private orderBookFailureCount;
16
+ private tradeFailureCount;
17
+ private closed;
18
+ constructor(fetchOrderBook: FetchOrderBookFn, fetchTrades: FetchTradesFn, pollInterval?: number);
19
+ watchOrderBook(outcomeId: string): Promise<OrderBook>;
20
+ watchTrades(outcomeId: string): Promise<Trade[]>;
21
+ close(): Promise<void>;
22
+ private startOrderBookPolling;
23
+ private startTradePolling;
24
+ private handleFailure;
25
+ }
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HunchWebSocket = void 0;
4
+ const logger_1 = require("../../utils/logger");
5
+ /**
6
+ * Hunch exposes SSE at /api/agent/v1/events for resolution awareness, but no
7
+ * market-data WebSocket. watchOrderBook / watchTrades are emulated with a
8
+ * poll-based fallback (matching the CCXT Pro async pattern + Myriad's adapter).
9
+ */
10
+ const DEFAULT_POLL_INTERVAL = 5000; // 5s
11
+ const MAX_CONSECUTIVE_FAILURES = 5;
12
+ class HunchWebSocket {
13
+ fetchOrderBook;
14
+ fetchTrades;
15
+ pollInterval;
16
+ orderBookTimers = new Map();
17
+ tradeTimers = new Map();
18
+ orderBookResolvers = new Map();
19
+ orderBookRejecters = new Map();
20
+ tradeResolvers = new Map();
21
+ tradeRejecters = new Map();
22
+ seenTradeIds = new Map();
23
+ orderBookFailureCount = new Map();
24
+ tradeFailureCount = new Map();
25
+ closed = false;
26
+ constructor(fetchOrderBook, fetchTrades, pollInterval) {
27
+ this.fetchOrderBook = fetchOrderBook;
28
+ this.fetchTrades = fetchTrades;
29
+ this.pollInterval = pollInterval || DEFAULT_POLL_INTERVAL;
30
+ }
31
+ async watchOrderBook(outcomeId) {
32
+ if (this.closed)
33
+ throw new Error('Hunch watch connection is closed');
34
+ return new Promise((resolve, reject) => {
35
+ if (!this.orderBookResolvers.has(outcomeId)) {
36
+ this.orderBookResolvers.set(outcomeId, []);
37
+ this.orderBookRejecters.set(outcomeId, []);
38
+ }
39
+ this.orderBookResolvers.get(outcomeId).push(resolve);
40
+ this.orderBookRejecters.get(outcomeId).push(reject);
41
+ if (!this.orderBookTimers.has(outcomeId))
42
+ this.startOrderBookPolling(outcomeId);
43
+ });
44
+ }
45
+ async watchTrades(outcomeId) {
46
+ if (this.closed)
47
+ throw new Error('Hunch watch connection is closed');
48
+ return new Promise((resolve, reject) => {
49
+ if (!this.tradeResolvers.has(outcomeId)) {
50
+ this.tradeResolvers.set(outcomeId, []);
51
+ this.tradeRejecters.set(outcomeId, []);
52
+ }
53
+ this.tradeResolvers.get(outcomeId).push(resolve);
54
+ this.tradeRejecters.get(outcomeId).push(reject);
55
+ if (!this.tradeTimers.has(outcomeId))
56
+ this.startTradePolling(outcomeId);
57
+ });
58
+ }
59
+ async close() {
60
+ this.closed = true;
61
+ for (const timer of this.orderBookTimers.values())
62
+ clearInterval(timer);
63
+ for (const timer of this.tradeTimers.values())
64
+ clearInterval(timer);
65
+ this.orderBookTimers.clear();
66
+ this.tradeTimers.clear();
67
+ this.orderBookResolvers.clear();
68
+ this.orderBookRejecters.clear();
69
+ this.tradeResolvers.clear();
70
+ this.tradeRejecters.clear();
71
+ this.seenTradeIds.clear();
72
+ }
73
+ startOrderBookPolling(id) {
74
+ const poll = async () => {
75
+ try {
76
+ const book = await this.fetchOrderBook(id);
77
+ this.orderBookFailureCount.set(id, 0);
78
+ const resolvers = this.orderBookResolvers.get(id) || [];
79
+ this.orderBookResolvers.set(id, []);
80
+ this.orderBookRejecters.set(id, []);
81
+ for (const resolve of resolvers)
82
+ resolve(book);
83
+ }
84
+ catch (error) {
85
+ this.handleFailure(id, error, 'watchOrderBook', this.orderBookFailureCount, this.orderBookTimers, this.orderBookResolvers, this.orderBookRejecters);
86
+ }
87
+ };
88
+ poll();
89
+ this.orderBookTimers.set(id, setInterval(poll, this.pollInterval));
90
+ }
91
+ startTradePolling(id) {
92
+ const poll = async () => {
93
+ try {
94
+ const trades = await this.fetchTrades(id, 50);
95
+ let seen = this.seenTradeIds.get(id);
96
+ if (!seen) {
97
+ seen = new Set();
98
+ this.seenTradeIds.set(id, seen);
99
+ }
100
+ const fresh = trades.filter((t) => !seen.has(t.id));
101
+ for (const t of fresh)
102
+ seen.add(t.id);
103
+ this.tradeFailureCount.set(id, 0);
104
+ const resolvers = this.tradeResolvers.get(id) || [];
105
+ this.tradeResolvers.set(id, []);
106
+ this.tradeRejecters.set(id, []);
107
+ for (const resolve of resolvers)
108
+ resolve(fresh);
109
+ }
110
+ catch (error) {
111
+ this.handleFailure(id, error, 'watchTrades', this.tradeFailureCount, this.tradeTimers, this.tradeResolvers, this.tradeRejecters);
112
+ }
113
+ };
114
+ poll();
115
+ this.tradeTimers.set(id, setInterval(poll, this.pollInterval));
116
+ }
117
+ handleFailure(id, error, label, failures, timers, resolvers, rejecters) {
118
+ const count = (failures.get(id) || 0) + 1;
119
+ failures.set(id, count);
120
+ logger_1.logger.warn(`Hunch ${label} poll failed for outcomeId=${id} (consecutive failures: ${count})`, {
121
+ error: String(error),
122
+ });
123
+ if (count >= MAX_CONSECUTIVE_FAILURES) {
124
+ const timer = timers.get(id);
125
+ if (timer)
126
+ clearInterval(timer);
127
+ timers.delete(id);
128
+ failures.delete(id);
129
+ const rej = rejecters.get(id) || [];
130
+ resolvers.set(id, []);
131
+ rejecters.set(id, []);
132
+ for (const reject of rej)
133
+ reject(error);
134
+ }
135
+ }
136
+ }
137
+ exports.HunchWebSocket = HunchWebSocket;
@@ -46,12 +46,25 @@ function convertLargeInts(obj) {
46
46
  }
47
47
  return obj;
48
48
  }
49
+ // ponytail: msgpackr encodes positive BigInts as int64 (0xd3); HL's server uses
50
+ // Python's msgpack which encodes the same value as uint64 (0xcf). Post-process the
51
+ // packed bytes to flip d3 → cf for non-negative payloads. HL actions never carry
52
+ // negative integers (oids, nonces, asset ids are all >= 0), so this is safe.
53
+ function fixInt64ToUint64(bytes) {
54
+ const out = Buffer.from(bytes);
55
+ for (let i = 0; i < out.length - 8; i++) {
56
+ if (out[i] === 0xd3 && (out[i + 1] & 0x80) === 0) {
57
+ out[i] = 0xcf;
58
+ }
59
+ }
60
+ return out;
61
+ }
49
62
  // ----------------------------------------------------------------------------
50
63
  // Action hash -- constructs the connectionId for the phantom agent
51
64
  // ----------------------------------------------------------------------------
52
65
  function computeActionHash(action, vaultAddress, nonce) {
53
- // 1. msgpack-encode the action (large ints as int64)
54
- const actionBytes = packr.pack(convertLargeInts(action));
66
+ // 1. msgpack-encode the action; coerce positive int64 to uint64 to match HL's server (Python msgpack)
67
+ const actionBytes = fixInt64ToUint64(packr.pack(convertLargeInts(action)));
55
68
  // 2. nonce as 8 bytes big-endian
56
69
  const nonceBytes = Buffer.alloc(8);
57
70
  nonceBytes.writeBigUInt64BE(BigInt(nonce));
@@ -126,10 +126,28 @@ export interface HyperliquidRawUserState {
126
126
  time?: number;
127
127
  withdrawable: string;
128
128
  }
129
+ export interface HyperliquidRawSpotBalance {
130
+ coin: string;
131
+ token: number;
132
+ total: string;
133
+ hold: string;
134
+ entryNtl: string;
135
+ }
136
+ export interface HyperliquidRawSpotState {
137
+ balances: HyperliquidRawSpotBalance[];
138
+ }
139
+ export interface HyperliquidRawSpotAssetCtx {
140
+ coin: string;
141
+ dayNtlVlm: string;
142
+ prevDayPx?: string;
143
+ markPx?: string;
144
+ midPx?: string;
145
+ }
129
146
  export interface HyperliquidRawOutcomeWithQuestion {
130
147
  outcome: HyperliquidRawOutcome;
131
148
  question: HyperliquidRawQuestion | undefined;
132
149
  midPrice: string | undefined;
150
+ volume24h?: number;
133
151
  }
134
152
  export declare class HyperliquidFetcher implements IExchangeFetcher<HyperliquidRawOutcomeWithQuestion, HyperliquidRawQuestion> {
135
153
  private readonly ctx;
@@ -140,11 +158,18 @@ export declare class HyperliquidFetcher implements IExchangeFetcher<HyperliquidR
140
158
  fetchRawEvents(params: EventFetchParams): Promise<HyperliquidRawQuestion[]>;
141
159
  fetchRawOrderBook(marketId: string): Promise<HyperliquidRawL2Book>;
142
160
  fetchRawOHLCV(marketId: string, params: OHLCVParams): Promise<HyperliquidRawCandle[]>;
143
- fetchRawTrades(marketId: string, _params: TradesParams): Promise<HyperliquidRawTrade[]>;
161
+ fetchRawTrades(marketId: string, params: TradesParams): Promise<HyperliquidRawTrade[]>;
144
162
  fetchRawUserFills(walletAddress: string): Promise<HyperliquidRawFill[]>;
145
163
  fetchRawOpenOrders(walletAddress: string): Promise<HyperliquidRawOpenOrder[]>;
164
+ fetchRawSpotState(walletAddress: string): Promise<HyperliquidRawSpotState>;
146
165
  fetchRawUserState(walletAddress: string): Promise<HyperliquidRawUserState>;
147
166
  fetchOutcomeMeta(): Promise<HyperliquidRawOutcomeMeta>;
148
167
  fetchAllMids(): Promise<HyperliquidRawMid>;
168
+ /**
169
+ * Build a map of outcomeId -> 24h notional volume (Yes leg + No leg)
170
+ * by reading spotMetaAndAssetCtxs, where outcome legs appear as
171
+ * coin "#<encoding>" with `dayNtlVlm` in USDC.
172
+ */
173
+ fetchOutcomeVolumeMap(): Promise<Map<number, number>>;
149
174
  private getMidForOutcome;
150
175
  }
@@ -25,9 +25,10 @@ class HyperliquidFetcher {
25
25
  }
26
26
  // -- Markets (outcomes) ----------------------------------------------------
27
27
  async fetchRawMarkets(params) {
28
- const [meta, mids] = await Promise.all([
28
+ const [meta, mids, volumeMap] = await Promise.all([
29
29
  this.fetchOutcomeMeta(),
30
30
  this.fetchAllMids(),
31
+ this.fetchOutcomeVolumeMap(),
31
32
  ]);
32
33
  const questionMap = new Map();
33
34
  for (const q of meta.questions) {
@@ -39,6 +40,7 @@ class HyperliquidFetcher {
39
40
  outcome,
40
41
  question: questionMap.get(outcome.outcome),
41
42
  midPrice: this.getMidForOutcome(mids, outcome.outcome),
43
+ volume24h: volumeMap.get(outcome.outcome),
42
44
  }));
43
45
  // Filter settled outcomes out by default (active only)
44
46
  if (!params?.status || params.status === 'active') {
@@ -56,6 +58,21 @@ class HyperliquidFetcher {
56
58
  results = results.filter(r => r.outcome.name.toLowerCase().includes(lowerQuery) ||
57
59
  r.outcome.description.toLowerCase().includes(lowerQuery));
58
60
  }
61
+ // Direct lookup by marketId (canonical or outcome-token form)
62
+ if (params?.marketId) {
63
+ try {
64
+ const targetOutcomeId = (0, utils_1.fromMarketId)(String(params.marketId));
65
+ results = results.filter(r => r.outcome.outcome === targetOutcomeId);
66
+ }
67
+ catch {
68
+ results = [];
69
+ }
70
+ }
71
+ // Filter by parent eventId (HL question number)
72
+ if (params?.eventId !== undefined && params.eventId !== null) {
73
+ const targetEventId = String(params.eventId);
74
+ results = results.filter(r => r.question && String(r.question.question) === targetEventId);
75
+ }
59
76
  // Limit
60
77
  const limit = params?.limit || 250000;
61
78
  const offset = params?.offset || 0;
@@ -65,6 +82,11 @@ class HyperliquidFetcher {
65
82
  async fetchRawEvents(params) {
66
83
  const meta = await this.fetchOutcomeMeta();
67
84
  let results = [...meta.questions];
85
+ // Direct lookup by eventId (HL question number)
86
+ if (params?.eventId !== undefined && params.eventId !== null) {
87
+ const targetEventId = String(params.eventId);
88
+ results = results.filter(q => String(q.question) === targetEventId);
89
+ }
68
90
  // Filter by query
69
91
  if (params?.query) {
70
92
  const lowerQuery = params.query.toLowerCase();
@@ -92,16 +114,20 @@ class HyperliquidFetcher {
92
114
  const now = Date.now();
93
115
  const startTime = params.start ? params.start.getTime() : now - 24 * 60 * 60 * 1000;
94
116
  const endTime = params.end ? params.end.getTime() : now;
95
- return this.postInfo({
117
+ const raw = await this.postInfo({
96
118
  type: 'candleSnapshot',
97
119
  req: { coin, interval: params.resolution || '1h', startTime, endTime },
98
120
  });
121
+ // ponytail: HL returns the full window; honor caller's limit by trimming to the most recent N
122
+ return params.limit && raw.length > params.limit ? raw.slice(-params.limit) : raw;
99
123
  }
100
124
  // -- Trades ----------------------------------------------------------------
101
- async fetchRawTrades(marketId, _params) {
125
+ async fetchRawTrades(marketId, params) {
102
126
  const outcomeId = (0, utils_1.fromMarketId)(marketId);
103
127
  const coin = (0, utils_1.toCoinNotation)(outcomeId, 'yes');
104
- return this.postInfo({ type: 'recentTrades', coin });
128
+ const raw = await this.postInfo({ type: 'recentTrades', coin });
129
+ // ponytail: HL recentTrades returns a fixed page; honor caller's limit (most recent N)
130
+ return params?.limit && raw.length > params.limit ? raw.slice(0, params.limit) : raw;
105
131
  }
106
132
  // -- User data -------------------------------------------------------------
107
133
  async fetchRawUserFills(walletAddress) {
@@ -116,6 +142,12 @@ class HyperliquidFetcher {
116
142
  user: walletAddress,
117
143
  });
118
144
  }
145
+ async fetchRawSpotState(walletAddress) {
146
+ return this.postInfo({
147
+ type: 'spotClearinghouseState',
148
+ user: walletAddress,
149
+ });
150
+ }
119
151
  async fetchRawUserState(walletAddress) {
120
152
  return this.postInfo({
121
153
  type: 'clearinghouseState',
@@ -129,6 +161,36 @@ class HyperliquidFetcher {
129
161
  async fetchAllMids() {
130
162
  return this.postInfo({ type: 'allMids' });
131
163
  }
164
+ /**
165
+ * Build a map of outcomeId -> 24h notional volume (Yes leg + No leg)
166
+ * by reading spotMetaAndAssetCtxs, where outcome legs appear as
167
+ * coin "#<encoding>" with `dayNtlVlm` in USDC.
168
+ */
169
+ async fetchOutcomeVolumeMap() {
170
+ const map = new Map();
171
+ try {
172
+ const resp = await this.postInfo({ type: 'spotMetaAndAssetCtxs' });
173
+ const ctxs = Array.isArray(resp) ? resp[1] : undefined;
174
+ if (!Array.isArray(ctxs))
175
+ return map;
176
+ for (const ctx of ctxs) {
177
+ if (!ctx?.coin || !ctx.coin.startsWith('#'))
178
+ continue;
179
+ const vol = parseFloat(ctx.dayNtlVlm);
180
+ if (!Number.isFinite(vol))
181
+ continue;
182
+ const encoding = parseInt(ctx.coin.slice(1), 10);
183
+ if (!Number.isFinite(encoding))
184
+ continue;
185
+ const { outcomeId } = (0, utils_1.fromCoinEncoding)(encoding);
186
+ map.set(outcomeId, (map.get(outcomeId) ?? 0) + vol);
187
+ }
188
+ }
189
+ catch {
190
+ // ponytail: best-effort volume enrichment; if spotMetaAndAssetCtxs is unreachable, return empty map and callers fall back to 0
191
+ }
192
+ return map;
193
+ }
132
194
  getMidForOutcome(mids, outcomeId) {
133
195
  const midKey = (0, utils_1.toMidKey)(outcomeId);
134
196
  return mids[midKey];
@@ -20,12 +20,15 @@ export declare class HyperliquidExchange extends PredictionMarketExchange {
20
20
  protected fetchMarketsImpl(params?: MarketFilterParams): Promise<UnifiedMarket[]>;
21
21
  protected fetchEventsImpl(params: EventFetchParams): Promise<UnifiedEvent[]>;
22
22
  fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook>;
23
- fetchOHLCV(outcomeId: string, params: OHLCVParams): Promise<PriceCandle[]>;
23
+ fetchOrderBooks(outcomeIds: string[]): Promise<Record<string, OrderBook>>;
24
+ fetchOHLCV(outcomeId: string, params?: OHLCVParams): Promise<PriceCandle[]>;
24
25
  fetchTrades(outcomeId: string, params?: TradesParams): Promise<Trade[]>;
25
26
  fetchBalance(): Promise<Balance[]>;
26
27
  fetchPositions(): Promise<Position[]>;
27
28
  fetchOpenOrders(): Promise<Order[]>;
28
29
  fetchMyTrades(params?: MyTradesParams): Promise<UserTrade[]>;
30
+ fetchClosedOrders(): Promise<Order[]>;
31
+ fetchAllOrders(): Promise<Order[]>;
29
32
  buildOrder(params: CreateOrderParams): Promise<BuiltOrder>;
30
33
  submitOrder(built: BuiltOrder): Promise<Order>;
31
34
  createOrder(params: CreateOrderParams): Promise<Order>;