pmxt-core 2.51.3 → 2.52.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 (46) hide show
  1. package/dist/BaseExchange.d.ts +7 -0
  2. package/dist/BaseExchange.js +10 -0
  3. package/dist/exchanges/gemini-titan/errors.d.ts +5 -0
  4. package/dist/exchanges/gemini-titan/errors.js +37 -2
  5. package/dist/exchanges/gemini-titan/fetcher.d.ts +33 -0
  6. package/dist/exchanges/gemini-titan/fetcher.js +77 -0
  7. package/dist/exchanges/gemini-titan/index.d.ts +4 -0
  8. package/dist/exchanges/gemini-titan/index.js +22 -1
  9. package/dist/exchanges/hyperliquid/index.js +14 -1
  10. package/dist/exchanges/hyperliquid/normalizer.js +35 -2
  11. package/dist/exchanges/kalshi/api.d.ts +1 -1
  12. package/dist/exchanges/kalshi/api.js +1 -1
  13. package/dist/exchanges/kalshi/fetcher.d.ts +37 -2
  14. package/dist/exchanges/kalshi/fetcher.js +10 -0
  15. package/dist/exchanges/kalshi/index.d.ts +7 -0
  16. package/dist/exchanges/kalshi/index.js +9 -0
  17. package/dist/exchanges/kalshi/normalizer.js +34 -10
  18. package/dist/exchanges/limitless/api.d.ts +1 -1
  19. package/dist/exchanges/limitless/api.js +1 -1
  20. package/dist/exchanges/limitless/index.d.ts +1 -0
  21. package/dist/exchanges/limitless/index.js +5 -0
  22. package/dist/exchanges/limitless/websocket.js +6 -2
  23. package/dist/exchanges/myriad/api.d.ts +1 -1
  24. package/dist/exchanges/myriad/api.js +1 -1
  25. package/dist/exchanges/opinion/api.d.ts +1 -1
  26. package/dist/exchanges/opinion/api.js +1 -1
  27. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  28. package/dist/exchanges/polymarket/api-clob.js +1 -1
  29. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  30. package/dist/exchanges/polymarket/api-data.js +1 -1
  31. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  32. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  33. package/dist/exchanges/polymarket_us/websocket.js +17 -1
  34. package/dist/exchanges/probable/api.d.ts +1 -1
  35. package/dist/exchanges/probable/api.js +1 -1
  36. package/dist/exchanges/rain/index.d.ts +2 -0
  37. package/dist/exchanges/rain/index.js +9 -0
  38. package/dist/exchanges/smarkets/normalizer.js +8 -6
  39. package/dist/exchanges/suibets/api.d.ts +0 -1
  40. package/dist/exchanges/suibets/api.js +0 -1
  41. package/dist/exchanges/suibets/fetcher.d.ts +6 -0
  42. package/dist/exchanges/suibets/normalizer.js +2 -1
  43. package/dist/server/method-verbs.json +10 -0
  44. package/dist/server/openapi.yaml +35 -2
  45. package/dist/types.d.ts +4 -0
  46. package/package.json +4 -4
@@ -526,6 +526,13 @@ export declare abstract class PredictionMarketExchange {
526
526
  * @throws EventNotFound if no event matches the parameters
527
527
  */
528
528
  fetchEvent(params?: EventFetchParams): Promise<UnifiedEvent>;
529
+ /**
530
+ * Fetch venue-native metadata for a specific event when the exchange
531
+ * exposes a dedicated metadata endpoint.
532
+ *
533
+ * Kalshi implements this for `GET /events/{event_ticker}/metadata`.
534
+ */
535
+ fetchEventMetadata(eventTicker: string): Promise<Record<string, unknown>>;
529
536
  /**
530
537
  * Fetch historical OHLCV (candlestick) price data for a specific market outcome.
531
538
  *
@@ -487,6 +487,16 @@ class PredictionMarketExchange {
487
487
  }
488
488
  return events[0];
489
489
  }
490
+ /**
491
+ * Fetch venue-native metadata for a specific event when the exchange
492
+ * exposes a dedicated metadata endpoint.
493
+ *
494
+ * Kalshi implements this for `GET /events/{event_ticker}/metadata`.
495
+ */
496
+ async fetchEventMetadata(eventTicker) {
497
+ void eventTicker;
498
+ throw new Error("Method fetchEventMetadata not implemented.");
499
+ }
490
500
  /**
491
501
  * Fetch historical OHLCV (candlestick) price data for a specific market outcome.
492
502
  *
@@ -10,10 +10,15 @@ import { BadRequest } from '../../errors';
10
10
  * - InvalidSignature -> AuthenticationError
11
11
  * - InsufficientFunds -> InsufficientFunds
12
12
  * - InvalidQuantity, InvalidPrice, MarketNotOpen -> InvalidOrder
13
+ * - TermsNotAccepted -> AuthenticationError (with auto-accept flow)
13
14
  */
14
15
  export declare class GeminiErrorMapper extends ErrorMapper {
15
16
  constructor();
16
17
  protected extractErrorMessage(error: unknown): string;
18
+ /**
19
+ * Check if an error is related to terms acceptance
20
+ */
21
+ private isTermsError;
17
22
  protected mapBadRequestError(message: string, data: unknown): BadRequest;
18
23
  mapError(error: unknown): ReturnType<ErrorMapper['mapError']>;
19
24
  }
@@ -7,6 +7,14 @@ exports.geminiErrorMapper = exports.GeminiErrorMapper = void 0;
7
7
  const axios_1 = __importDefault(require("axios"));
8
8
  const error_mapper_1 = require("../../utils/error-mapper");
9
9
  const errors_1 = require("../../errors");
10
+ // Terms-related error patterns
11
+ const TERMS_ERROR_PATTERNS = [
12
+ 'terms_not_accepted',
13
+ 'terms required',
14
+ 'prediction markets terms',
15
+ 'accept terms',
16
+ 'terms must be accepted',
17
+ ];
10
18
  /**
11
19
  * Maps Gemini Titan API errors to PMXT unified error classes.
12
20
  *
@@ -17,6 +25,7 @@ const errors_1 = require("../../errors");
17
25
  * - InvalidSignature -> AuthenticationError
18
26
  * - InsufficientFunds -> InsufficientFunds
19
27
  * - InvalidQuantity, InvalidPrice, MarketNotOpen -> InvalidOrder
28
+ * - TermsNotAccepted -> AuthenticationError (with auto-accept flow)
20
29
  */
21
30
  class GeminiErrorMapper extends error_mapper_1.ErrorMapper {
22
31
  constructor() {
@@ -37,12 +46,25 @@ class GeminiErrorMapper extends error_mapper_1.ErrorMapper {
37
46
  }
38
47
  return super.extractErrorMessage(error);
39
48
  }
49
+ /**
50
+ * Check if an error is related to terms acceptance
51
+ */
52
+ isTermsError(message) {
53
+ const lowerMessage = message.toLowerCase();
54
+ return TERMS_ERROR_PATTERNS.some(pattern => lowerMessage.includes(pattern));
55
+ }
40
56
  mapBadRequestError(message, data) {
41
57
  const reason = typeof data === 'object' && data !== null && 'reason' in data
42
58
  ? String(data.reason)
43
59
  : '';
44
60
  const lowerReason = reason.toLowerCase();
45
61
  const lowerMessage = message.toLowerCase();
62
+ // ✅ Check for terms-related errors first
63
+ if (this.isTermsError(lowerMessage) || this.isTermsError(lowerReason)) {
64
+ return new errors_1.AuthenticationError(`Gemini Prediction Markets terms must be accepted before placing orders. ` +
65
+ `The adapter will automatically accept terms on your behalf. ` +
66
+ `Original error: ${message}`, this.exchangeName);
67
+ }
46
68
  if (lowerReason.includes('insufficientfunds') || lowerMessage.includes('insufficient')) {
47
69
  return new errors_1.InsufficientFunds(message, this.exchangeName);
48
70
  }
@@ -56,8 +78,7 @@ class GeminiErrorMapper extends error_mapper_1.ErrorMapper {
56
78
  return new errors_1.InvalidOrder(message, this.exchangeName);
57
79
  }
58
80
  if (lowerReason.includes('invalidsignature') ||
59
- lowerReason.includes('invalidapikey') ||
60
- lowerMessage.includes('terms_not_accepted')) {
81
+ lowerReason.includes('invalidapikey')) {
61
82
  return new errors_1.AuthenticationError(message, this.exchangeName);
62
83
  }
63
84
  return super.mapBadRequestError(message, data);
@@ -68,6 +89,20 @@ class GeminiErrorMapper extends error_mapper_1.ErrorMapper {
68
89
  const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : undefined;
69
90
  return new errors_1.RateLimitExceeded(this.extractErrorMessage(error), retryAfterSeconds, this.exchangeName);
70
91
  }
92
+ // ✅ Check for terms errors in non-4xx responses
93
+ if (axios_1.default.isAxiosError(error) && error.response?.data) {
94
+ const data = error.response.data;
95
+ const message = typeof data === 'object' && data !== null && 'message' in data
96
+ ? String(data.message)
97
+ : typeof data === 'string'
98
+ ? data
99
+ : '';
100
+ if (this.isTermsError(message)) {
101
+ return new errors_1.AuthenticationError(`Gemini Prediction Markets terms must be accepted before placing orders. ` +
102
+ `The adapter will automatically accept terms on your behalf. ` +
103
+ `Original error: ${message}`, this.exchangeName);
104
+ }
105
+ }
71
106
  return super.mapError(error);
72
107
  }
73
108
  }
@@ -6,13 +6,42 @@ export declare class GeminiFetcher implements IExchangeFetcher<GeminiRawEvent, G
6
6
  private readonly ctx;
7
7
  private readonly baseUrl;
8
8
  private readonly auth;
9
+ private readonly httpClient;
9
10
  private symbolToEventTicker;
11
+ private termsAccepted;
10
12
  constructor(ctx: FetcherContext, baseUrl: string, auth?: GeminiAuth);
11
13
  fetchRawMarkets(params?: MarketFilterParams): Promise<GeminiRawEvent[]>;
12
14
  fetchRawEvents(params: EventFetchParams): Promise<GeminiRawEvent[]>;
13
15
  fetchRawSingleEvent(eventTicker: string): Promise<GeminiRawEvent>;
14
16
  fetchRawOrderBook(instrumentSymbol: string): Promise<GeminiRawOrderBook | undefined>;
15
17
  getEventTickerForSymbol(instrumentSymbol: string): string | undefined;
18
+ /**
19
+ * Get current terms version and content
20
+ */
21
+ getTerms(): Promise<{
22
+ version: string;
23
+ content: string;
24
+ }>;
25
+ /**
26
+ * Check if API key has accepted the latest terms
27
+ */
28
+ getTermsStatus(): Promise<{
29
+ hasAcceptedLatest: boolean;
30
+ acceptedVersion?: string;
31
+ latestVersion?: string;
32
+ }>;
33
+ /**
34
+ * Accept the latest terms version
35
+ */
36
+ acceptTerms(): Promise<{
37
+ accepted: boolean;
38
+ version: string;
39
+ }>;
40
+ /**
41
+ * Ensure terms are accepted before placing orders.
42
+ * This is called automatically before order submission.
43
+ */
44
+ ensureTermsAccepted(): Promise<void>;
16
45
  submitRawOrder(payload: Record<string, unknown>): Promise<GeminiRawOrder>;
17
46
  cancelRawOrder(orderId: number): Promise<GeminiRawOrder>;
18
47
  fetchRawActiveOrders(symbol?: string): Promise<GeminiRawOrder[]>;
@@ -20,5 +49,9 @@ export declare class GeminiFetcher implements IExchangeFetcher<GeminiRawEvent, G
20
49
  private fetchPaginatedOrders;
21
50
  fetchRawPositions(): Promise<GeminiRawPosition[]>;
22
51
  private get;
52
+ /**
53
+ * Authenticated GET request
54
+ */
55
+ private getAuthenticated;
23
56
  private postAuthenticated;
24
57
  }
@@ -9,12 +9,16 @@ class GeminiFetcher {
9
9
  ctx;
10
10
  baseUrl;
11
11
  auth;
12
+ httpClient; // Add httpClient
12
13
  // Index mapping instrumentSymbol -> eventTicker, built during fetchRawEvents
13
14
  symbolToEventTicker = new Map();
15
+ // Track terms acceptance status to avoid repeated checks
16
+ termsAccepted = false;
14
17
  constructor(ctx, baseUrl, auth) {
15
18
  this.ctx = ctx;
16
19
  this.baseUrl = baseUrl;
17
20
  this.auth = auth;
21
+ this.httpClient = ctx.http; // Initialize httpClient from ctx
18
22
  }
19
23
  // -- Public data -----------------------------------------------------------
20
24
  async fetchRawMarkets(params) {
@@ -95,8 +99,60 @@ class GeminiFetcher {
95
99
  getEventTickerForSymbol(instrumentSymbol) {
96
100
  return this.symbolToEventTicker.get(instrumentSymbol);
97
101
  }
102
+ // -- Terms Acceptance Flow -----------------------------------------------
103
+ /**
104
+ * Get current terms version and content
105
+ */
106
+ async getTerms() {
107
+ return this.getAuthenticated('/v1/prediction-markets/terms');
108
+ }
109
+ /**
110
+ * Check if API key has accepted the latest terms
111
+ */
112
+ async getTermsStatus() {
113
+ return this.getAuthenticated('/v1/prediction-markets/terms/status');
114
+ }
115
+ /**
116
+ * Accept the latest terms version
117
+ */
118
+ async acceptTerms() {
119
+ const result = await this.postAuthenticated('/v1/prediction-markets/terms/accept', {});
120
+ this.termsAccepted = true;
121
+ return result;
122
+ }
123
+ /**
124
+ * Ensure terms are accepted before placing orders.
125
+ * This is called automatically before order submission.
126
+ */
127
+ async ensureTermsAccepted() {
128
+ // Skip if already accepted in this session
129
+ if (this.termsAccepted) {
130
+ return;
131
+ }
132
+ try {
133
+ const status = await this.getTermsStatus();
134
+ if (!status.hasAcceptedLatest) {
135
+ // Terms not accepted - accept them
136
+ await this.acceptTerms();
137
+ // Log acceptance (using logger instead of console if available)
138
+ }
139
+ else {
140
+ this.termsAccepted = true;
141
+ }
142
+ }
143
+ catch (error) {
144
+ // If terms check fails with a specific error, re-throw
145
+ if (error.message?.includes('TERMS') || error.message?.includes('terms')) {
146
+ throw errors_1.geminiErrorMapper.mapError(error);
147
+ }
148
+ // Otherwise log warning but don't block order submission
149
+ // The order will fail with a clear error if terms are required
150
+ }
151
+ }
98
152
  // -- Authenticated endpoints -----------------------------------------------
99
153
  async submitRawOrder(payload) {
154
+ // ✅ Ensure terms are accepted before placing order
155
+ await this.ensureTermsAccepted();
100
156
  return this.postAuthenticated('/v1/prediction-markets/order', payload);
101
157
  }
102
158
  async cancelRawOrder(orderId) {
@@ -153,6 +209,27 @@ class GeminiFetcher {
153
209
  throw errors_1.geminiErrorMapper.mapError(error);
154
210
  }
155
211
  }
212
+ /**
213
+ * Authenticated GET request
214
+ */
215
+ async getAuthenticated(path) {
216
+ if (!this.auth) {
217
+ throw new Error('Authentication required. Provide apiKey and apiSecret.');
218
+ }
219
+ const url = `${this.baseUrl}${path}`;
220
+ const payload = {
221
+ request: path,
222
+ nonce: this.auth.nonce(),
223
+ };
224
+ const headers = this.auth.buildHeaders(payload);
225
+ try {
226
+ const response = await this.httpClient.get(url, { headers });
227
+ return response.data;
228
+ }
229
+ catch (error) {
230
+ throw errors_1.geminiErrorMapper.mapError(error);
231
+ }
232
+ }
156
233
  async postAuthenticated(path, extraFields) {
157
234
  if (!this.auth) {
158
235
  throw new Error('Authentication required. Provide apiKey and apiSecret.');
@@ -33,3 +33,7 @@ export declare class GeminiTitanExchange extends PredictionMarketExchange {
33
33
  watchTrades(outcomeId: string): Promise<Trade[]>;
34
34
  close(): Promise<void>;
35
35
  }
36
+ export { GeminiFetcher } from './fetcher';
37
+ export { GeminiAuth } from './auth';
38
+ export { geminiErrorMapper } from './errors';
39
+ export * from './types';
@@ -1,6 +1,20 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GeminiTitanExchange = void 0;
17
+ exports.geminiErrorMapper = exports.GeminiAuth = exports.GeminiFetcher = exports.GeminiTitanExchange = void 0;
4
18
  const BaseExchange_1 = require("../../BaseExchange");
5
19
  const errors_1 = require("../../errors");
6
20
  const config_1 = require("./config");
@@ -258,3 +272,10 @@ class GeminiTitanExchange extends BaseExchange_1.PredictionMarketExchange {
258
272
  }
259
273
  }
260
274
  exports.GeminiTitanExchange = GeminiTitanExchange;
275
+ var fetcher_2 = require("./fetcher");
276
+ Object.defineProperty(exports, "GeminiFetcher", { enumerable: true, get: function () { return fetcher_2.GeminiFetcher; } });
277
+ var auth_2 = require("./auth");
278
+ Object.defineProperty(exports, "GeminiAuth", { enumerable: true, get: function () { return auth_2.GeminiAuth; } });
279
+ var errors_3 = require("./errors");
280
+ Object.defineProperty(exports, "geminiErrorMapper", { enumerable: true, get: function () { return errors_3.geminiErrorMapper; } });
281
+ __exportStar(require("./types"), exports);
@@ -185,12 +185,25 @@ class HyperliquidExchange extends BaseExchange_1.PredictionMarketExchange {
185
185
  ? { limit: { tif: 'Ioc' } }
186
186
  : { limit: { tif: 'Gtc' } },
187
187
  };
188
- // Key order matters for msgpack hash: type, orders, grouping
188
+ // Key order matters for msgpack hash: type, orders, grouping, builder
189
189
  const action = {
190
190
  type: 'order',
191
191
  orders: [orderWire],
192
192
  grouping: 'na',
193
193
  };
194
+ if (params.builder !== undefined || params.builderFee !== undefined) {
195
+ if (!params.builder) {
196
+ throw new Error('Hyperliquid builderFee requires builder address');
197
+ }
198
+ if (!/^0x[0-9a-fA-F]{40}$/.test(params.builder)) {
199
+ throw new Error(`Invalid Hyperliquid builder address: ${params.builder}`);
200
+ }
201
+ const builderFee = params.builderFee ?? 0;
202
+ if (!Number.isInteger(builderFee) || builderFee < 0) {
203
+ throw new Error('Hyperliquid builderFee must be a non-negative integer in tenths of a basis point');
204
+ }
205
+ action.builder = { b: params.builder.toLowerCase(), f: builderFee };
206
+ }
194
207
  return {
195
208
  exchange: this.name,
196
209
  params,
@@ -73,6 +73,38 @@ function parseExpiryDate(expiry) {
73
73
  const [, year, month, day, hour, minute] = match;
74
74
  return new Date(`${year}-${month}-${day}T${hour}:${minute}:00Z`);
75
75
  }
76
+ const MONTH_INDEX = {
77
+ January: 0, February: 1, March: 2, April: 3, May: 4, June: 5,
78
+ July: 6, August: 7, September: 8, October: 9, November: 10, December: 11,
79
+ };
80
+ /**
81
+ * Fall back to scanning the question's prose description for a resolution
82
+ * deadline. Hyperliquid puts no structured endTime on the Outcome/Question
83
+ * objects, but every non-recurring question's description contains at least
84
+ * one "<Month> <DD>[,] <YYYY>" date (e.g. "by October 14, 2026 at 23:59 UTC").
85
+ * Returns the LATEST such date — questions often mention an event date and a
86
+ * later resolution deadline, and we want the deadline. Defaults to 23:59 UTC
87
+ * when no time is given.
88
+ */
89
+ function parseDeadlineFromText(text) {
90
+ if (!text)
91
+ return undefined;
92
+ const re = /(January|February|March|April|May|June|July|August|September|October|November|December)\s+(\d{1,2}),?\s+(\d{4})(?:\s+at\s+(\d{1,2}):(\d{2})\s*UTC)?/g;
93
+ let latest;
94
+ let m;
95
+ while ((m = re.exec(text)) !== null) {
96
+ const [, monthName, day, year, hour, minute] = m;
97
+ const month = MONTH_INDEX[monthName];
98
+ if (month === undefined)
99
+ continue;
100
+ const d = new Date(Date.UTC(parseInt(year, 10), month, parseInt(day, 10), hour !== undefined ? parseInt(hour, 10) : 23, minute !== undefined ? parseInt(minute, 10) : 59, 0));
101
+ if (Number.isNaN(d.getTime()))
102
+ continue;
103
+ if (!latest || d > latest)
104
+ latest = d;
105
+ }
106
+ return latest;
107
+ }
76
108
  /**
77
109
  * Build a human-readable title from the parsed description.
78
110
  */
@@ -154,10 +186,11 @@ class HyperliquidNormalizer {
154
186
  price: noPrice,
155
187
  });
156
188
  }
157
- // Resolution date: prefer outcome-level expiry, fall back to question-level
189
+ // Resolution date: prefer outcome-level expiry, then question-level expiry tag,
190
+ // then a deadline phrase scraped from the question's prose description.
158
191
  const expiryDate = parsed.expiryDate
159
192
  ?? questionParsed?.expiryDate
160
- ?? undefined;
193
+ ?? parseDeadlineFromText(raw.question?.description ?? "");
161
194
  // Build a descriptive title from the parsed description
162
195
  const title = buildTitle(outcome.name, parsed, questionParsed);
163
196
  // Derive underlying from outcome or question
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
3
- * Generated at: 2026-06-24T09:17:49.567Z
3
+ * Generated at: 2026-07-18T00:54:13.384Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const kalshiApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.kalshiApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
6
- * Generated at: 2026-06-24T09:17:49.567Z
6
+ * Generated at: 2026-07-18T00:54:13.384Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.kalshiApiSpec = {
@@ -70,25 +70,48 @@ export interface KalshiRawEventPage {
70
70
  }
71
71
  export interface KalshiRawCandlestick {
72
72
  end_period_ts: number;
73
+ /** @deprecated Old API field — new API uses `volume_fp` (string) */
73
74
  volume?: number;
75
+ /** New API field: cumulative volume as a fixed-point string */
76
+ volume_fp?: string;
77
+ /**
78
+ * Kalshi candlestick prices come nested under `price`. The new API returns
79
+ * dollar-denominated string fields (e.g. `close_dollars: "0.9700"`); older
80
+ * responses used integer cent fields (e.g. `close: 97`). Both shapes are
81
+ * supported by the normalizer.
82
+ */
74
83
  price?: {
75
84
  open?: number;
76
85
  high?: number;
77
86
  low?: number;
78
87
  close?: number;
79
88
  previous?: number;
89
+ open_dollars?: string;
90
+ high_dollars?: string;
91
+ low_dollars?: string;
92
+ close_dollars?: string;
93
+ mean_dollars?: string;
94
+ previous_dollars?: string;
80
95
  };
81
96
  yes_ask?: {
82
97
  open?: number;
83
98
  high?: number;
84
99
  low?: number;
85
100
  close?: number;
101
+ open_dollars?: string;
102
+ high_dollars?: string;
103
+ low_dollars?: string;
104
+ close_dollars?: string;
86
105
  };
87
106
  yes_bid?: {
88
107
  open?: number;
89
108
  high?: number;
90
109
  low?: number;
91
110
  close?: number;
111
+ open_dollars?: string;
112
+ high_dollars?: string;
113
+ low_dollars?: string;
114
+ close_dollars?: string;
92
115
  };
93
116
  [key: string]: unknown;
94
117
  }
@@ -114,7 +137,12 @@ export interface KalshiRawTrade {
114
137
  count?: number;
115
138
  /** New API field: count as a string e.g. "424.00" */
116
139
  count_fp?: string;
117
- taker_side: string;
140
+ /** @deprecated removal deadline May 14, 2026 */
141
+ taker_side?: string;
142
+ /** New v2 directional fields */
143
+ taker_outcome_side?: string;
144
+ taker_book_side?: string;
145
+ is_block_trade?: boolean;
118
146
  [key: string]: unknown;
119
147
  }
120
148
  export interface KalshiRawFill {
@@ -126,8 +154,14 @@ export interface KalshiRawFill {
126
154
  /** @deprecated Old API field */
127
155
  count?: number;
128
156
  count_fp?: string;
129
- side: string;
130
157
  order_id: string;
158
+ /** @deprecated removal deadline May 14, 2026 */
159
+ side?: string;
160
+ /** @deprecated removal deadline May 14, 2026 */
161
+ action?: string;
162
+ /** New v2 directional fields */
163
+ outcome_side?: string;
164
+ book_side?: string;
131
165
  [key: string]: unknown;
132
166
  }
133
167
  export interface KalshiRawOrder {
@@ -174,6 +208,7 @@ export declare class KalshiFetcher implements IExchangeFetcher<KalshiRawEvent, K
174
208
  fetchRawOrders(queryParams: Record<string, any>): Promise<KalshiRawOrder[]>;
175
209
  fetchRawHistoricalOrders(queryParams: Record<string, any>): Promise<KalshiRawOrder[]>;
176
210
  fetchRawSeriesList(): Promise<KalshiRawSeries[]>;
211
+ fetchRawEventMetadata(eventTicker: string): Promise<Record<string, unknown>>;
177
212
  fetchRawSeriesMap(): Promise<Map<string, KalshiSeriesInfo>>;
178
213
  fetchRawEventByTicker(eventTicker: string): Promise<KalshiRawEvent[]>;
179
214
  private fetchRawEventsDefault;
@@ -238,6 +238,16 @@ class KalshiFetcher {
238
238
  throw errors_1.kalshiErrorMapper.mapError(e);
239
239
  }
240
240
  }
241
+ async fetchRawEventMetadata(eventTicker) {
242
+ try {
243
+ return this.ctx.callApi('GetEventMetadata', {
244
+ event_ticker: eventTicker.toUpperCase(),
245
+ });
246
+ }
247
+ catch (e) {
248
+ throw errors_1.kalshiErrorMapper.mapError(e);
249
+ }
250
+ }
241
251
  async fetchRawSeriesMap() {
242
252
  try {
243
253
  const data = await this.ctx.callApi('GetSeriesList');
@@ -24,6 +24,13 @@ export declare class KalshiExchange extends PredictionMarketExchange {
24
24
  protected fetchMarketsImpl(params?: MarketFilterParams): Promise<UnifiedMarket[]>;
25
25
  protected fetchEventsImpl(params: EventFetchParams): Promise<UnifiedEvent[]>;
26
26
  protected fetchSeriesImpl(params: SeriesFetchParams): Promise<UnifiedSeries[]>;
27
+ /**
28
+ * Fetch Kalshi's venue-native per-event metadata payload.
29
+ *
30
+ * This surfaces Kalshi's `GET /events/{event_ticker}/metadata` endpoint,
31
+ * which is distinct from the normalized `fetchEvent` response.
32
+ */
33
+ fetchEventMetadata(eventTicker: string): Promise<Record<string, unknown>>;
27
34
  fetchEventsPage(params?: EventFetchParams): Promise<{
28
35
  events: UnifiedEvent[];
29
36
  cursor: string | null;
@@ -151,6 +151,15 @@ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
151
151
  }
152
152
  return filtered;
153
153
  }
154
+ /**
155
+ * Fetch Kalshi's venue-native per-event metadata payload.
156
+ *
157
+ * This surfaces Kalshi's `GET /events/{event_ticker}/metadata` endpoint,
158
+ * which is distinct from the normalized `fetchEvent` response.
159
+ */
160
+ async fetchEventMetadata(eventTicker) {
161
+ return this.fetcher.fetchRawEventMetadata(eventTicker);
162
+ }
154
163
  async fetchEventsPage(params = {}) {
155
164
  const page = await this.fetcher.fetchRawEventPage(params);
156
165
  const query = (params?.query || '').toLowerCase();
@@ -180,24 +180,47 @@ class KalshiNormalizer {
180
180
  const p = c.price || {};
181
181
  const ask = c.yes_ask || {};
182
182
  const bid = c.yes_bid || {};
183
+ /**
184
+ * Kalshi's new candlestick API returns prices as dollar-denominated
185
+ * strings under `*_dollars` fields (e.g. `close_dollars: "0.9700"`).
186
+ * The legacy API returned integer cents under bare fields
187
+ * (e.g. `close: 97`). Prefer dollars when present; fall back to
188
+ * cents (converted via {@link fromKalshiCents}).
189
+ */
183
190
  const getVal = (field) => {
191
+ const dollarsKey = `${field}_dollars`;
192
+ const pd = p[dollarsKey];
193
+ const ad = ask[dollarsKey];
194
+ const bd = bid[dollarsKey];
195
+ if (pd != null)
196
+ return Number(pd);
197
+ if (ad != null && bd != null) {
198
+ return (Number(ad) + Number(bd)) / 2;
199
+ }
184
200
  const pf = p[field];
185
201
  const af = ask[field];
186
202
  const bf = bid[field];
187
203
  if (pf != null)
188
- return pf;
204
+ return (0, price_1.fromKalshiCents)(pf);
189
205
  if (af != null && bf != null) {
190
- return (af + bf) / 2;
206
+ return ((0, price_1.fromKalshiCents)(af) + (0, price_1.fromKalshiCents)(bf)) / 2;
191
207
  }
192
- return p.previous || 0;
208
+ if (p.previous_dollars != null)
209
+ return Number(p.previous_dollars);
210
+ if (p.previous != null)
211
+ return (0, price_1.fromKalshiCents)(p.previous);
212
+ return 0;
193
213
  };
214
+ const volume = c.volume_fp != null
215
+ ? parseFloat(c.volume_fp) || 0
216
+ : (c.volume || 0);
194
217
  return {
195
218
  timestamp: c.end_period_ts * 1000,
196
- open: (0, price_1.fromKalshiCents)(getVal('open')),
197
- high: (0, price_1.fromKalshiCents)(getVal('high')),
198
- low: (0, price_1.fromKalshiCents)(getVal('low')),
199
- close: (0, price_1.fromKalshiCents)(getVal('close')),
200
- volume: c.volume || 0,
219
+ open: getVal('open'),
220
+ high: getVal('high'),
221
+ low: getVal('low'),
222
+ close: getVal('close'),
223
+ volume,
201
224
  };
202
225
  });
203
226
  if (params.limit && candles.length > params.limit) {
@@ -251,7 +274,8 @@ class KalshiNormalizer {
251
274
  timestamp: new Date(raw.created_time).getTime(),
252
275
  price,
253
276
  amount,
254
- side: raw.taker_side === 'yes' ? 'buy' : 'sell',
277
+ // Support legacy taker_side, fallback to v2 taker_outcome_side
278
+ side: (raw.taker_side || raw.taker_outcome_side) === 'yes' ? 'buy' : 'sell',
255
279
  };
256
280
  }
257
281
  normalizeUserTrade(raw, _index) {
@@ -268,7 +292,7 @@ class KalshiNormalizer {
268
292
  timestamp: new Date(raw.created_time).getTime(),
269
293
  price,
270
294
  amount,
271
- side: raw.side === 'yes' ? 'buy' : 'sell',
295
+ side: (raw.side || raw.outcome_side) === 'yes' ? 'buy' : 'sell',
272
296
  orderId: raw.order_id,
273
297
  };
274
298
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
3
- * Generated at: 2026-06-24T09:17:49.606Z
3
+ * Generated at: 2026-07-18T00:54:13.429Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const limitlessApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.limitlessApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
6
- * Generated at: 2026-06-24T09:17:49.606Z
6
+ * Generated at: 2026-07-18T00:54:13.429Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.limitlessApiSpec = {
@@ -42,6 +42,7 @@ export declare class LimitlessExchange extends PredictionMarketExchange {
42
42
  fetchPositions(address?: string): Promise<Position[]>;
43
43
  fetchBalance(address?: string): Promise<Balance[]>;
44
44
  watchOrderBook(outcomeId: string, limit?: number, _params?: Record<string, any>): Promise<OrderBook>;
45
+ unwatchOrderBook(outcomeId: string): Promise<void>;
45
46
  watchTrades(outcomeId: string, address?: string, since?: number, limit?: number): Promise<Trade[]>;
46
47
  /**
47
48
  * Watch AMM price updates for a market address (Limitless only).
@@ -423,6 +423,11 @@ class LimitlessExchange extends BaseExchange_1.PredictionMarketExchange {
423
423
  const ws = this.ensureWs();
424
424
  return ws.watchOrderBook(slug);
425
425
  }
426
+ async unwatchOrderBook(outcomeId) {
427
+ const slug = await this.resolveSlug(outcomeId);
428
+ const ws = this.ensureWs();
429
+ await ws.unsubscribe(slug);
430
+ }
426
431
  async watchTrades(outcomeId, address, since, limit) {
427
432
  const slug = await this.resolveSlug(outcomeId);
428
433
  const ws = this.ensureWs();
@@ -155,8 +155,12 @@ class LimitlessWebSocket {
155
155
  return await Promise.race([wsUpdatePromise, timeoutPromise]);
156
156
  }
157
157
  catch (err) {
158
- // Fallback to empty orderbook if all else fails
159
- return this.getEmptyOrderbook();
158
+ logger_1.logger.error('LimitlessWS: watchOrderBook failed', {
159
+ market: marketSlug,
160
+ error: err instanceof Error ? err.message : String(err)
161
+ });
162
+ // Do not return fake liquidity. Force the upstream loop to handle the break.
163
+ throw err;
160
164
  }
161
165
  }
162
166
  /**
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
3
- * Generated at: 2026-06-24T09:17:49.618Z
3
+ * Generated at: 2026-07-18T00:54:13.441Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const myriadApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.myriadApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
6
- * Generated at: 2026-06-24T09:17:49.618Z
6
+ * Generated at: 2026-07-18T00:54:13.441Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.myriadApiSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
3
- * Generated at: 2026-06-24T09:17:49.621Z
3
+ * Generated at: 2026-07-18T00:54:13.446Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const opinionApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.opinionApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
6
- * Generated at: 2026-06-24T09:17:49.621Z
6
+ * Generated at: 2026-07-18T00:54:13.446Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.opinionApiSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
3
- * Generated at: 2026-06-24T09:17:49.575Z
3
+ * Generated at: 2026-07-18T00:54:13.392Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const polymarketClobSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.polymarketClobSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
6
- * Generated at: 2026-06-24T09:17:49.575Z
6
+ * Generated at: 2026-07-18T00:54:13.392Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketClobSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
3
- * Generated at: 2026-06-24T09:17:49.588Z
3
+ * Generated at: 2026-07-18T00:54:13.410Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const polymarketDataSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.polymarketDataSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
6
- * Generated at: 2026-06-24T09:17:49.588Z
6
+ * Generated at: 2026-07-18T00:54:13.410Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketDataSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
3
- * Generated at: 2026-06-24T09:17:49.586Z
3
+ * Generated at: 2026-07-18T00:54:13.407Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const polymarketGammaSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.polymarketGammaSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
6
- * Generated at: 2026-06-24T09:17:49.586Z
6
+ * Generated at: 2026-07-18T00:54:13.407Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketGammaSpec = {
@@ -116,7 +116,23 @@ class PolymarketUSWebSocket {
116
116
  socket.on('trade', (msg) => this.handleTrade(msg));
117
117
  socket.on('error', (err) => this.handleError(err));
118
118
  socket.on('close', () => this.handleClose());
119
- await socket.connect();
119
+ // Wrap connection in a timeout with proper garbage collection
120
+ const CONNECTION_TIMEOUT_MS = 15000; // 15s is standard for trading API handshakes
121
+ let timeoutHandle;
122
+ const timeoutPromise = new Promise((_, reject) => {
123
+ timeoutHandle = setTimeout(() => {
124
+ reject(new Error(`PolymarketUS WebSocket connection timed out after ${CONNECTION_TIMEOUT_MS}ms`));
125
+ }, CONNECTION_TIMEOUT_MS);
126
+ });
127
+ try {
128
+ await Promise.race([
129
+ socket.connect(),
130
+ timeoutPromise
131
+ ]);
132
+ }
133
+ finally {
134
+ clearTimeout(timeoutHandle); // Always clear the timer so the event loop can breathe
135
+ }
120
136
  this.socket = socket;
121
137
  })();
122
138
  try {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
3
- * Generated at: 2026-06-24T09:17:49.613Z
3
+ * Generated at: 2026-07-18T00:54:13.436Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const probableApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.probableApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
6
- * Generated at: 2026-06-24T09:17:49.613Z
6
+ * Generated at: 2026-07-18T00:54:13.436Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.probableApiSpec = {
@@ -5,6 +5,7 @@ export declare class RainExchange extends PredictionMarketExchange {
5
5
  protected readonly capabilityOverrides: {
6
6
  fetchSeries: false;
7
7
  fetchOrderBook: "emulated";
8
+ fetchOrderBooks: "emulated";
8
9
  watchOrderBook: "emulated";
9
10
  watchTrades: "emulated";
10
11
  fetchOpenOrders: "emulated";
@@ -21,6 +22,7 @@ export declare class RainExchange extends PredictionMarketExchange {
21
22
  protected fetchEventsImpl(params: EventFetchParams): Promise<UnifiedEvent[]>;
22
23
  fetchOHLCV(outcomeId: string, params: OHLCVParams): Promise<PriceCandle[]>;
23
24
  fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook>;
25
+ fetchOrderBooks(outcomeIds: string[]): Promise<Record<string, OrderBook>>;
24
26
  fetchTrades(outcomeId: string, params: TradesParams | HistoryFilterParams): Promise<Trade[]>;
25
27
  fetchMyTrades(params?: MyTradesParams): Promise<UserTrade[]>;
26
28
  fetchPositions(address?: string): Promise<Position[]>;
@@ -37,6 +37,8 @@ class RainExchange extends BaseExchange_1.PredictionMarketExchange {
37
37
  capabilityOverrides = {
38
38
  fetchSeries: false,
39
39
  fetchOrderBook: 'emulated',
40
+ // Emulated batch: loops the single-outcome fetchOrderBook (no native batch endpoint).
41
+ fetchOrderBooks: 'emulated',
40
42
  watchOrderBook: 'emulated',
41
43
  watchTrades: 'emulated',
42
44
  // Trading is on-chain via Rain SDK + viem. open/closed orders + fetchOrder
@@ -115,6 +117,13 @@ class RainExchange extends BaseExchange_1.PredictionMarketExchange {
115
117
  return { bids: [], asks: [], timestamp: Date.now() };
116
118
  return this.normalizer.normalizeOrderBook(raw, outcomeId);
117
119
  }
120
+ async fetchOrderBooks(outcomeIds) {
121
+ const response = {};
122
+ for (const outcomeId of outcomeIds) {
123
+ response[outcomeId] = await this.fetchOrderBook(outcomeId);
124
+ }
125
+ return response;
126
+ }
118
127
  async fetchTrades(outcomeId, params) {
119
128
  const parts = outcomeId.split(':');
120
129
  if (parts.length < 2) {
@@ -48,6 +48,13 @@ function buildContractOutcome(contract, marketId) {
48
48
  price: 0,
49
49
  };
50
50
  }
51
+ function parseSmarketsResolutionDate(event) {
52
+ // Smarkets list and detail endpoints currently return end_date as null for
53
+ // many events. start_datetime is an event start, not a market resolution,
54
+ // so leave the unified resolution date unset instead of using a misleading
55
+ // fallback that can corrupt cross-venue ordering.
56
+ return event.end_date ? new Date(event.end_date) : undefined;
57
+ }
51
58
  // ----------------------------------------------------------------------------
52
59
  // Normalizer
53
60
  // ----------------------------------------------------------------------------
@@ -87,12 +94,7 @@ class SmarketsNormalizer {
87
94
  tags.push(cat);
88
95
  }
89
96
  }
90
- // Derive resolution date from event end_date or start_datetime
91
- const resolutionDate = event.end_date
92
- ? new Date(event.end_date)
93
- : event.start_datetime
94
- ? new Date(event.start_datetime)
95
- : new Date();
97
+ const resolutionDate = parseSmarketsResolutionDate(event);
96
98
  const um = {
97
99
  marketId: market.id,
98
100
  eventId: event.id,
@@ -11,5 +11,4 @@
11
11
  * GET /api/p2p/offers - List open P2P offers (status, matchId, sport, limit, offset)
12
12
  * GET /api/p2p/offers/:id - Get a single P2P offer by ID
13
13
  * GET /api/p2p/my?wallet=... - Get user activity (created offers, matched bets, parlays)
14
- * GET /api/events/upcoming - List upcoming sports events (sport, limit)
15
14
  */
@@ -12,6 +12,5 @@
12
12
  * GET /api/p2p/offers - List open P2P offers (status, matchId, sport, limit, offset)
13
13
  * GET /api/p2p/offers/:id - Get a single P2P offer by ID
14
14
  * GET /api/p2p/my?wallet=... - Get user activity (created offers, matched bets, parlays)
15
- * GET /api/events/upcoming - List upcoming sports events (sport, limit)
16
15
  */
17
16
  // No runtime exports — this file serves as API documentation only.
@@ -21,6 +21,12 @@ export interface SuibetsRawOffer {
21
21
  isOnchain?: boolean;
22
22
  onchainOfferId?: string;
23
23
  leagueName?: string;
24
+ onchainState?: {
25
+ makerRemaining?: string;
26
+ totalLiquidity?: string;
27
+ filledAmount?: string;
28
+ status?: string;
29
+ };
24
30
  }
25
31
  export interface SuibetsRawEvent {
26
32
  id: string;
@@ -3,7 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SuibetsNormalizer = void 0;
4
4
  const utils_1 = require("./utils");
5
5
  function liquidity(offer) {
6
- const remaining = offer.remainingStake ?? offer.creatorStake;
6
+ // Prioritize live onchainState.makerRemaining data before falling back to legacy fields
7
+ const remaining = offer.onchainState?.makerRemaining ?? offer.remainingStake ?? offer.creatorStake;
7
8
  return (0, utils_1.mistToSui)(remaining);
8
9
  }
9
10
  class SuibetsNormalizer {
@@ -79,6 +79,16 @@
79
79
  }
80
80
  ]
81
81
  },
82
+ "fetchEventMetadata": {
83
+ "verb": "get",
84
+ "args": [
85
+ {
86
+ "name": "eventTicker",
87
+ "kind": "string",
88
+ "optional": false
89
+ }
90
+ ]
91
+ },
82
92
  "fetchOHLCV": {
83
93
  "verb": "get",
84
94
  "args": [
@@ -2,8 +2,8 @@ openapi: 3.0.0
2
2
  info:
3
3
  title: PMXT Sidecar API
4
4
  description: >-
5
- A unified local sidecar API for prediction markets (Polymarket, Kalshi, Limitless). This API acts as a
6
- JSON-RPC-style gateway. Each endpoint corresponds to a specific method on the generic exchange implementation.
5
+ A unified local sidecar API for supported prediction markets. This API acts as a JSON-RPC-style gateway over PMXT
6
+ exchange implementations, with venue and write support varying by method and capability.
7
7
  version: 0.4.4
8
8
  servers:
9
9
  - url: 'http://localhost:3847'
@@ -680,6 +680,33 @@ paths:
680
680
  description: >-
681
681
  Fetch a single event by lookup parameters. Convenience wrapper around fetchEvents() that returns a single result
682
682
  or throws EventNotFound.
683
+ '/api/{exchange}/fetchEventMetadata':
684
+ get:
685
+ summary: Fetch Event Metadata
686
+ operationId: fetchEventMetadata
687
+ parameters:
688
+ - $ref: '#/components/parameters/ExchangeParam'
689
+ - in: query
690
+ name: eventTicker
691
+ required: true
692
+ schema:
693
+ type: string
694
+ responses:
695
+ '200':
696
+ description: Fetch Event Metadata response
697
+ content:
698
+ application/json:
699
+ schema:
700
+ allOf:
701
+ - $ref: '#/components/schemas/BaseResponse'
702
+ - type: object
703
+ properties:
704
+ data:
705
+ type: object
706
+ additionalProperties: {}
707
+ description: >-
708
+ Fetch venue-native metadata for a specific event when the exchange exposes a dedicated metadata endpoint. Kalshi
709
+ implements this for `GET /events/{event_ticker}/metadata`.
683
710
  '/api/{exchange}/fetchOHLCV':
684
711
  get:
685
712
  summary: Fetch OHLCV
@@ -3654,6 +3681,12 @@ components:
3654
3681
  fee:
3655
3682
  type: number
3656
3683
  description: 'Optional fee rate (e.g., 1000 for 0.1%)'
3684
+ builder:
3685
+ type: string
3686
+ description: Hyperliquid builder address to attach to the order action.
3687
+ builderFee:
3688
+ type: number
3689
+ description: Hyperliquid builder fee in tenths of a basis point (e.g. 10 = 1 bp).
3657
3690
  tickSize:
3658
3691
  type: number
3659
3692
  description: Optional override for Limitless/Polymarket
package/dist/types.d.ts CHANGED
@@ -290,6 +290,10 @@ export interface CreateOrderParams {
290
290
  denom?: 'usdc' | 'shares';
291
291
  slippage_pct?: number;
292
292
  fee?: number;
293
+ /** Hyperliquid builder address to attach to the order action. */
294
+ builder?: string;
295
+ /** Hyperliquid builder fee in tenths of a basis point (e.g. 10 = 1 bp). */
296
+ builderFee?: number;
293
297
  tickSize?: number;
294
298
  negRisk?: boolean;
295
299
  onBehalfOf?: number;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pmxt-core",
3
- "version": "2.51.3",
4
- "description": "pmxt is a unified prediction market data API. The ccxt for prediction markets.",
3
+ "version": "2.52.0",
4
+ "description": "Local sidecar API for supported prediction markets.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "repository": {
@@ -29,8 +29,8 @@
29
29
  "test": "jest -c jest.config.js",
30
30
  "server": "tsx watch src/server/index.ts",
31
31
  "server:prod": "node dist/server/index.js",
32
- "generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=2.51.3,library=urllib3",
33
- "generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=2.51.3,supportsES6=true,typescriptThreePlus=true && node ../sdks/typescript/scripts/fix-generated.js",
32
+ "generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=2.52.0,library=urllib3",
33
+ "generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=2.52.0,supportsES6=true,typescriptThreePlus=true && node ../sdks/typescript/scripts/fix-generated.js",
34
34
  "fetch:openapi": "node scripts/fetch-openapi-specs.js",
35
35
  "extract:jsdoc": "node ../scripts/extract-jsdoc.js",
36
36
  "generate:docs": "npm run extract:jsdoc && node ../scripts/generate-api-docs.js",