Skip to content

Commit 20f5115

Browse files
committed
feat(dx): typed SDK params, passthrough converters, outcome ID consistency
Typed SDK params replace `any` with real types (MarketFilterParams, etc.) giving consumers IDE autocomplete. Passthrough converters use spread (TS) and _auto_convert (Python) so new fields flow through automatically. All outcome-accepting methods renamed id->outcomeId with MarketOutcome object acceptance. Python backwards compat via _compat_id deprecation shim. Fixes Limitless fetchTrades missing resolveSlug bug.
1 parent 5f74dca commit 20f5115

24 files changed

Lines changed: 1167 additions & 603 deletions

File tree

changelog.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,67 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [2.39.0] - 2026-05-08
6+
7+
### DX: SDK type safety, passthrough converters, and outcome ID consistency
8+
9+
A developer experience overhaul across core, TypeScript SDK, and Python SDK.
10+
11+
**Typed SDK params** -- Generated SDK methods now have real parameter types
12+
instead of `any`. `fetchMarkets(params?: MarketFilterParams)` replaces
13+
`fetchMarkets(params?: any)`, giving consumers full IDE autocomplete and
14+
compile-time validation. Affected params: `MarketFilterParams`,
15+
`EventFetchParams`, `MyTradesParams`, `OrderHistoryParams`, `OHLCVParams`,
16+
`TradesParams`.
17+
18+
**Passthrough converters** -- SDK converter functions (`convertMarket`,
19+
`convertEvent`, etc.) no longer enumerate fields explicitly. TypeScript
20+
uses spread (`{ ...raw }`) and Python uses a new `_auto_convert()` helper
21+
that iterates dataclass fields. New fields added to `types.ts` now flow
22+
through to both SDKs automatically instead of being silently dropped.
23+
24+
**Outcome ID consistency** -- All methods that accept an outcome identifier
25+
now use `outcomeId` (TS) / `outcome_id` (Python) instead of the ambiguous
26+
`id`. Renamed in: `fetchOHLCV`, `fetchOrderBook`, `fetchTrades`,
27+
`watchOrderBook`, `watchOrderBooks`, `unwatchOrderBook`, `watchTrades`.
28+
29+
**MarketOutcome acceptance** -- All outcome-accepting methods now accept
30+
`string | MarketOutcome` (TS) / `Union[str, MarketOutcome]` (Python), so
31+
you can pass `market.yes` directly instead of `market.yes.outcomeId`. The
32+
SDK generators auto-apply this to future methods.
33+
34+
**Limitless bug fix** -- `fetchTrades` was missing `resolveSlug()`, so
35+
numeric outcome IDs that worked in `fetchOrderBook` failed silently.
36+
Added `resolveSlug` to `fetchTrades`, `watchOrderBook`, and `watchTrades`.
37+
38+
### Migration
39+
40+
No breaking changes. Python callers using the old `id=` keyword argument
41+
will see a `DeprecationWarning` but the call still works:
42+
43+
```python
44+
# Old (deprecated, still works)
45+
ex.fetch_order_book(id="token123")
46+
47+
# New (preferred)
48+
ex.fetch_order_book(outcome_id="token123")
49+
ex.fetch_order_book(market.yes) # MarketOutcome object
50+
```
51+
52+
TypeScript is fully backwards compatible -- parameter names are positional
53+
in JS/TS so the rename has no runtime impact.
54+
55+
### Files
56+
57+
- `core/src/BaseExchange.ts` -- param renames
58+
- `core/src/exchanges/*/index.ts` -- param renames (15 exchange files)
59+
- `core/src/exchanges/limitless/index.ts` -- resolveSlug bug fix
60+
- `sdks/typescript/pmxt/client.ts` -- passthrough converters, new imports
61+
- `sdks/typescript/pmxt/models.ts` -- added param types, fixed missing fields
62+
- `sdks/typescript/scripts/generate-client-methods.js` -- typed params, MarketOutcome widening
63+
- `sdks/python/pmxt/client.py` -- _auto_convert helper, compat shim, MarketOutcome widening
64+
- `sdks/python/scripts/generate-client-methods.js` -- typed params, compat shim generation
65+
566
## [2.38.0] - 2026-05-08
667

768
### Feat: Hyperliquid prediction market (HIP-4 Outcome Markets)

core/src/BaseExchange.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -813,43 +813,43 @@ export abstract class PredictionMarketExchange {
813813
/**
814814
* Fetch historical OHLCV (candlestick) price data for a specific market outcome.
815815
*
816-
* @param id - The Outcome ID (outcomeId). Use outcome.outcomeId, NOT market.marketId
816+
* @param outcomeId - The Outcome ID (outcomeId). Use outcome.outcomeId, NOT market.marketId
817817
* @param params - OHLCV parameters including resolution (required)
818818
* @returns Array of price candles
819819
*
820820
* @notes **CRITICAL**: Use `outcome.outcomeId` (TS) / `outcome.outcome_id` (Python), not the market ID.
821821
* @notes Polymarket: outcomeId is the CLOB Token ID. Kalshi: outcomeId is the Market Ticker.
822822
* @notes Common resolutions: '1m' | '5m' | '15m' | '1h' | '6h' | '1d'. Arbitrary intervals (e.g. '30s', '120s', '3h') accepted by venues that support them.
823823
*/
824-
async fetchOHLCV(id: string, params: OHLCVParams): Promise<PriceCandle[]> {
824+
async fetchOHLCV(outcomeId: string, params: OHLCVParams): Promise<PriceCandle[]> {
825825
throw new Error("Method fetchOHLCV not implemented.");
826826
}
827827

828828
/**
829829
* Fetch the current order book (bids/asks) for a specific outcome.
830830
* Essential for calculating spread, depth, and execution prices.
831831
*
832-
* @param id - The Outcome ID (outcomeId) or market slug
832+
* @param outcomeId - The Outcome ID (outcomeId) or market slug
833833
* @param side - Optional 'yes' or 'no' to explicitly indicate the
834834
* outcome side. Required for exchanges where the API returns a
835835
* single orderbook per market (e.g. Limitless) and the caller
836836
* passes a slug instead of a token ID.
837837
* @returns Current order book with bids and asks
838838
*/
839-
async fetchOrderBook(id: string, side?: 'yes' | 'no'): Promise<OrderBook> {
839+
async fetchOrderBook(outcomeId: string, side?: 'yes' | 'no'): Promise<OrderBook> {
840840
throw new Error("Method fetchOrderBook not implemented.");
841841
}
842842

843843
/**
844844
* Fetch raw trade history for a specific outcome.
845845
*
846-
* @param id - The Outcome ID (outcomeId)
846+
* @param outcomeId - The Outcome ID (outcomeId)
847847
* @param params - Trade filter parameters
848848
* @returns Array of recent trades
849849
*
850850
* @notes Polymarket requires an API key for trade history. Use fetchOHLCV for public historical data.
851851
*/
852-
async fetchTrades(id: string, params: TradesParams | HistoryFilterParams): Promise<Trade[]> {
852+
async fetchTrades(outcomeId: string, params: TradesParams | HistoryFilterParams): Promise<Trade[]> {
853853
// Deprecation warning for resolution parameter
854854
if ('resolution' in params && params.resolution !== undefined) {
855855
console.warn(
@@ -1246,11 +1246,11 @@ export abstract class PredictionMarketExchange {
12461246
* Watch order book updates in real-time via WebSocket.
12471247
* Returns a promise that resolves with the next order book update. Call repeatedly in a loop to stream updates (CCXT Pro pattern).
12481248
*
1249-
* @param id - The Outcome ID to watch
1249+
* @param outcomeId - The Outcome ID to watch
12501250
* @param limit - Optional limit for orderbook depth
12511251
* @returns Promise that resolves with the current orderbook state
12521252
*/
1253-
async watchOrderBook(id: string, limit?: number): Promise<OrderBook> {
1253+
async watchOrderBook(outcomeId: string, limit?: number): Promise<OrderBook> {
12541254
throw new Error(`watchOrderBook() is not supported by ${this.name}`);
12551255
}
12561256

@@ -1260,33 +1260,33 @@ export abstract class PredictionMarketExchange {
12601260
* Exchanges with native batch support (e.g. Kalshi) send a single subscribe message
12611261
* for all tickers; others fall back to individual watchOrderBook calls.
12621262
*
1263-
* @param ids - Array of Outcome IDs to watch
1263+
* @param outcomeIds - Array of Outcome IDs to watch
12641264
* @param limit - Optional limit for orderbook depth
12651265
* @returns Promise that resolves with order books keyed by ID
12661266
*/
1267-
async watchOrderBooks(ids: string[], limit?: number): Promise<Record<string, OrderBook>> {
1267+
async watchOrderBooks(outcomeIds: string[], limit?: number): Promise<Record<string, OrderBook>> {
12681268
// Default implementation: subscribe to each ID individually.
12691269
// Exchanges with native batch support (e.g. Kalshi) override this
12701270
// to send a single subscribe message for all tickers.
12711271
const entries = await Promise.all(
1272-
ids.map(async (id): Promise<[string, OrderBook]> => {
1273-
const book = await this.watchOrderBook(id, limit);
1274-
return [id, book];
1272+
outcomeIds.map(async (oid): Promise<[string, OrderBook]> => {
1273+
const book = await this.watchOrderBook(oid, limit);
1274+
return [oid, book];
12751275
}),
12761276
);
12771277
const result: Record<string, OrderBook> = {};
1278-
for (const [id, book] of entries) {
1279-
result[id] = book;
1278+
for (const [oid, book] of entries) {
1279+
result[oid] = book;
12801280
}
12811281
return result;
12821282
}
12831283

12841284
/**
12851285
* Unsubscribe from a previously watched order book stream.
12861286
*
1287-
* @param id - The Outcome ID to stop watching
1287+
* @param outcomeId - The Outcome ID to stop watching
12881288
*/
1289-
async unwatchOrderBook(id: string): Promise<void> {
1289+
async unwatchOrderBook(outcomeId: string): Promise<void> {
12901290
throw new Error(`unwatchOrderBook() is not supported by ${this.name}`);
12911291
}
12921292

@@ -1298,13 +1298,13 @@ export abstract class PredictionMarketExchange {
12981298
* Watch trade executions in real-time via WebSocket.
12991299
* Returns a promise that resolves with the next trade(s). Call repeatedly in a loop to stream updates (CCXT Pro pattern).
13001300
*
1301-
* @param id - The Outcome ID to watch
1301+
* @param outcomeId - The Outcome ID to watch
13021302
* @param address - Public wallet address
13031303
* @param since - Optional timestamp to filter trades from
13041304
* @param limit - Optional limit for number of trades
13051305
* @returns Promise that resolves with recent trades
13061306
*/
1307-
async watchTrades(id: string, address?: string, since?: number, limit?: number): Promise<Trade[]> {
1307+
async watchTrades(outcomeId: string, address?: string, since?: number, limit?: number): Promise<Trade[]> {
13081308
throw new Error(`watchTrades() is not supported by ${this.name}`);
13091309
}
13101310

core/src/exchanges/baozi/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ export class BaoziExchange extends PredictionMarketExchange {
123123
return [];
124124
}
125125

126-
async fetchOrderBook(id: string): Promise<OrderBook> {
127-
const rawMarket = await this.fetcher.fetchRawOrderBook(id);
128-
return this.normalizer.normalizeOrderBook(rawMarket, id);
126+
async fetchOrderBook(outcomeId: string): Promise<OrderBook> {
127+
const rawMarket = await this.fetcher.fetchRawOrderBook(outcomeId);
128+
return this.normalizer.normalizeOrderBook(rawMarket, outcomeId);
129129
}
130130

131131
async fetchTrades(): Promise<Trade[]> {
@@ -403,11 +403,11 @@ export class BaoziExchange extends PredictionMarketExchange {
403403
// WebSocket
404404
// -----------------------------------------------------------------------
405405

406-
async watchOrderBook(id: string): Promise<OrderBook> {
406+
async watchOrderBook(outcomeId: string): Promise<OrderBook> {
407407
if (!this.ws) {
408408
this.ws = new BaoziWebSocket();
409409
}
410-
return this.ws.watchOrderBook(this.connection, id);
410+
return this.ws.watchOrderBook(this.connection, outcomeId);
411411
}
412412

413413
async watchTrades(): Promise<Trade[]> {

core/src/exchanges/kalshi/index.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -186,21 +186,21 @@ export class KalshiExchange extends PredictionMarketExchange {
186186
}
187187

188188
async fetchOHLCV(
189-
id: string,
189+
outcomeId: string,
190190
params: OHLCVParams,
191191
): Promise<PriceCandle[]> {
192-
const rawCandles = await this.fetcher.fetchRawOHLCV(id, params);
192+
const rawCandles = await this.fetcher.fetchRawOHLCV(outcomeId, params);
193193
return this.normalizer.normalizeOHLCV(rawCandles, params);
194194
}
195195

196-
async fetchOrderBook(id: string): Promise<OrderBook> {
197-
validateIdFormat(id, "OrderBook");
198-
const raw = await this.fetcher.fetchRawOrderBook(id);
199-
return this.normalizer.normalizeOrderBook(raw, id);
196+
async fetchOrderBook(outcomeId: string): Promise<OrderBook> {
197+
validateIdFormat(outcomeId, "OrderBook");
198+
const raw = await this.fetcher.fetchRawOrderBook(outcomeId);
199+
return this.normalizer.normalizeOrderBook(raw, outcomeId);
200200
}
201201

202202
async fetchTrades(
203-
id: string,
203+
outcomeId: string,
204204
params: TradesParams | HistoryFilterParams,
205205
): Promise<Trade[]> {
206206
if ("resolution" in params && params.resolution !== undefined) {
@@ -209,7 +209,7 @@ export class KalshiExchange extends PredictionMarketExchange {
209209
"It will be removed in v3.0.0. Please remove it from your code.",
210210
);
211211
}
212-
const rawTrades = await this.fetcher.fetchRawTrades(id, params);
212+
const rawTrades = await this.fetcher.fetchRawTrades(outcomeId, params);
213213
return rawTrades.map((raw, i) => this.normalizer.normalizeTrade(raw, i));
214214
}
215215

@@ -341,7 +341,7 @@ export class KalshiExchange extends PredictionMarketExchange {
341341

342342
private ws?: KalshiWebSocket;
343343

344-
async watchOrderBook(id: string, limit?: number): Promise<OrderBook> {
344+
async watchOrderBook(outcomeId: string, limit?: number): Promise<OrderBook> {
345345
const auth = this.ensureAuth();
346346
if (!this.ws) {
347347
const wsConfigWithUrl: KalshiWebSocketConfig = {
@@ -350,11 +350,11 @@ export class KalshiExchange extends PredictionMarketExchange {
350350
};
351351
this.ws = new KalshiWebSocket(auth, wsConfigWithUrl);
352352
}
353-
const marketTicker = id.replace(/-NO$/, "");
353+
const marketTicker = outcomeId.replace(/-NO$/, "");
354354
return this.ws.watchOrderBook(marketTicker);
355355
}
356356

357-
async watchOrderBooks(ids: string[], limit?: number): Promise<Record<string, OrderBook>> {
357+
async watchOrderBooks(outcomeIds: string[], limit?: number): Promise<Record<string, OrderBook>> {
358358
const auth = this.ensureAuth();
359359
if (!this.ws) {
360360
const wsConfigWithUrl: KalshiWebSocketConfig = {
@@ -363,12 +363,12 @@ export class KalshiExchange extends PredictionMarketExchange {
363363
};
364364
this.ws = new KalshiWebSocket(auth, wsConfigWithUrl);
365365
}
366-
const marketTickers = ids.map((id) => id.replace(/-NO$/, ""));
366+
const marketTickers = outcomeIds.map((oid) => oid.replace(/-NO$/, ""));
367367
return this.ws.watchOrderBooks(marketTickers);
368368
}
369369

370370
async watchTrades(
371-
id: string,
371+
outcomeId: string,
372372
address?: string,
373373
since?: number,
374374
limit?: number,
@@ -381,7 +381,7 @@ export class KalshiExchange extends PredictionMarketExchange {
381381
};
382382
this.ws = new KalshiWebSocket(auth, wsConfigWithUrl);
383383
}
384-
const marketTicker = id.replace(/-NO$/, "");
384+
const marketTicker = outcomeId.replace(/-NO$/, "");
385385
return this.ws.watchTrades(marketTicker);
386386
}
387387

core/src/exchanges/limitless/index.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -194,21 +194,21 @@ export class LimitlessExchange extends PredictionMarketExchange {
194194
.filter((e): e is UnifiedEvent => e !== null);
195195
}
196196

197-
async fetchOHLCV(id: string, params: OHLCVParams): Promise<PriceCandle[]> {
198-
const slug = await this.resolveSlug(id);
197+
async fetchOHLCV(outcomeId: string, params: OHLCVParams): Promise<PriceCandle[]> {
198+
const slug = await this.resolveSlug(outcomeId);
199199
const rawPrices = await this.fetcher.fetchRawOHLCV!(slug, params);
200200
return this.normalizer.normalizeOHLCV!(rawPrices as any, params);
201201
}
202202

203-
async fetchOrderBook(id: string, side?: 'yes' | 'no'): Promise<OrderBook> {
204-
const slug = await this.resolveSlug(id);
203+
async fetchOrderBook(outcomeId: string, side?: 'yes' | 'no'): Promise<OrderBook> {
204+
const slug = await this.resolveSlug(outcomeId);
205205
const rawOrderBook = await this.fetcher.fetchRawOrderBook!(slug);
206-
const orderBook = this.normalizer.normalizeOrderBook!(rawOrderBook as any, id);
206+
const orderBook = this.normalizer.normalizeOrderBook!(rawOrderBook as any, outcomeId);
207207

208208
// The Limitless API always returns the Yes-side order book regardless
209209
// of which token is queried. If the caller asked for the No token,
210210
// flip: noBid = 1 - yesAsk, noAsk = 1 - yesBid.
211-
const isNoToken = side === 'no' || (!side && await this.isNoOutcome(id, slug));
211+
const isNoToken = side === 'no' || (!side && await this.isNoOutcome(outcomeId, slug));
212212
if (isNoToken) {
213213
return {
214214
bids: orderBook.asks.map((level) => ({ price: 1 - level.price, size: level.size }))
@@ -238,14 +238,15 @@ export class LimitlessExchange extends PredictionMarketExchange {
238238
return false;
239239
}
240240

241-
async fetchTrades(id: string, params: TradesParams | HistoryFilterParams): Promise<Trade[]> {
241+
async fetchTrades(outcomeId: string, params: TradesParams | HistoryFilterParams): Promise<Trade[]> {
242242
if ('resolution' in params && params.resolution !== undefined) {
243243
console.warn(
244244
'[pmxt] Warning: The "resolution" parameter is deprecated for fetchTrades() and will be ignored. ' +
245245
'It will be removed in v3.0.0. Please remove it from your code.'
246246
);
247247
}
248-
const rawTrades = await this.fetcher.fetchRawTrades!(id, params);
248+
const slug = await this.resolveSlug(outcomeId);
249+
const rawTrades = await this.fetcher.fetchRawTrades!(slug, params);
249250
return rawTrades.map((raw, i) => this.normalizer.normalizeTrade!(raw, i));
250251
}
251252

@@ -435,14 +436,16 @@ export class LimitlessExchange extends PredictionMarketExchange {
435436
// WebSocket
436437
// ------------------------------------------------------------------------
437438

438-
async watchOrderBook(id: string, limit?: number): Promise<OrderBook> {
439+
async watchOrderBook(outcomeId: string, limit?: number): Promise<OrderBook> {
440+
const slug = await this.resolveSlug(outcomeId);
439441
const ws = this.ensureWs();
440-
return ws.watchOrderBook(id);
442+
return ws.watchOrderBook(slug);
441443
}
442444

443-
async watchTrades(id: string, address?: string, since?: number, limit?: number): Promise<Trade[]> {
445+
async watchTrades(outcomeId: string, address?: string, since?: number, limit?: number): Promise<Trade[]> {
446+
const slug = await this.resolveSlug(outcomeId);
444447
const ws = this.ensureWs();
445-
return ws.watchTrades(id, address);
448+
return ws.watchTrades(slug, address);
446449
}
447450

448451
/**

0 commit comments

Comments
 (0)