Skip to content

Commit 8895ba6

Browse files
committed
fix: resolve outcome aliases in fetchOrderBook
1 parent 1cf43c3 commit 8895ba6

18 files changed

Lines changed: 171 additions & 0 deletions

File tree

changelog.md

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

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

5+
## [2.43.13] - 2026-05-24
6+
7+
### Fixed
8+
9+
- **fetchOrderBook**: `params.outcome` now accepts `"yes"`/`"no"` outcome aliases when the first argument is a market ID. The alias is resolved to the venue's actual outcome token ID before fetching live or historical order books, while raw outcome token IDs continue to work unchanged.
10+
511
## [2.43.12] - 2026-05-23
612

713
### Fixed

core/src/BaseExchange.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ export interface FetchOrderBookParams {
174174
/** Outcome side: 'yes' or 'no'. Required for exchanges like Limitless
175175
* where the API returns a single orderbook per market. */
176176
side?: 'yes' | 'no';
177+
/** Outcome alias: 'yes' or 'no', or an outcome token ID. When set,
178+
* the first argument is treated as a market ID and this value selects
179+
* which outcome's order book to fetch. Accepts the literal strings
180+
* 'yes'/'no' (resolved via a market lookup) or a raw outcome token ID. */
181+
outcome?: string;
177182
/** Unix timestamp (ms) — fetch a historical snapshot at or before this
178183
* time, or the start of a range when combined with `until` (hosted API only). */
179184
since?: number;
@@ -816,6 +821,75 @@ export abstract class PredictionMarketExchange {
816821
return markets[0];
817822
}
818823

824+
private cacheMarketLookup(market: UnifiedMarket): void {
825+
this.markets[market.marketId] = market;
826+
if (market.slug) {
827+
this.marketsBySlug[market.slug] = market;
828+
}
829+
}
830+
831+
private marketMatchesIdentifier(market: UnifiedMarket, id: string): boolean {
832+
return market.marketId === id
833+
|| market.slug === id
834+
|| market.contractAddress === id
835+
|| (market as any).id === id;
836+
}
837+
838+
private getCachedMarketForOutcomeAlias(id: string): UnifiedMarket | undefined {
839+
const direct = this.markets[id] ?? this.marketsBySlug[id];
840+
if (direct && this.marketMatchesIdentifier(direct, id)) return direct;
841+
return Object.values(this.markets).find((market) => this.marketMatchesIdentifier(market, id));
842+
}
843+
844+
private async fetchMarketForOutcomeAlias(id: string): Promise<UnifiedMarket> {
845+
const cached = this.getCachedMarketForOutcomeAlias(id);
846+
if (cached) return cached;
847+
848+
for (const params of [{ marketId: id }, { slug: id }]) {
849+
const markets = await this.fetchMarkets(params);
850+
const exact = markets.find((market) => this.marketMatchesIdentifier(market, id));
851+
const market = exact ?? (markets.length === 1 ? markets[0] : undefined);
852+
if (market) {
853+
this.cacheMarketLookup(market);
854+
return market;
855+
}
856+
}
857+
858+
throw new MarketNotFound(id, this.name);
859+
}
860+
861+
/**
862+
* Resolve an outcome alias ('yes'/'no') to an actual outcome token ID.
863+
* When params.outcome is set, treats `id` as a market ID, looks up the
864+
* market, and returns the resolved outcome token ID plus cleaned params.
865+
* When params.outcome is already a token ID (not 'yes'/'no'), returns it
866+
* directly. When params.outcome is not set, returns `id` unchanged.
867+
*/
868+
protected async resolveOutcomeAlias(
869+
id: string,
870+
params?: FetchOrderBookParams,
871+
): Promise<{ outcomeId: string; params?: FetchOrderBookParams }> {
872+
if (!params?.outcome) return { outcomeId: id, params };
873+
874+
const outcome = String(params.outcome);
875+
const alias = outcome.toLowerCase();
876+
const { outcome: _, ...rest } = params;
877+
878+
if (alias === 'yes' || alias === 'no') {
879+
const market = await this.fetchMarketForOutcomeAlias(id);
880+
const selected = alias === 'yes' ? market.yes : market.no;
881+
if (!selected) {
882+
throw new Error(
883+
`Market "${id}" has no '${alias}' outcome on ${this.name}`,
884+
);
885+
}
886+
return { outcomeId: selected.outcomeId, params: rest };
887+
}
888+
889+
// outcome is already a raw token ID — use it directly
890+
return { outcomeId: outcome, params: rest };
891+
}
892+
819893
// ----------------------------------------------------------------------------
820894
// Implementation methods (to be overridden by exchanges)
821895
// ----------------------------------------------------------------------------

core/src/exchanges/baozi/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ export class BaoziExchange extends PredictionMarketExchange {
125125
}
126126

127127
async fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
128+
const resolved = await this.resolveOutcomeAlias(outcomeId, _params);
129+
outcomeId = resolved.outcomeId;
130+
_params = resolved.params;
128131
const rawMarket = await this.fetcher.fetchRawOrderBook(outcomeId);
129132
return this.normalizer.normalizeOrderBook(rawMarket, outcomeId);
130133
}

core/src/exchanges/gemini-titan/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ export class GeminiTitanExchange extends PredictionMarketExchange {
107107
}
108108

109109
async fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
110+
const resolved = await this.resolveOutcomeAlias(outcomeId, _params);
111+
outcomeId = resolved.outcomeId;
112+
_params = resolved.params;
110113
const { instrumentSymbol } = fromOutcomeId(outcomeId);
111114
const raw = await this.fetcher.fetchRawOrderBook(instrumentSymbol);
112115
if (!raw) {

core/src/exchanges/hyperliquid/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ export class HyperliquidExchange extends PredictionMarketExchange {
129129
}
130130

131131
async fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
132+
const resolved = await this.resolveOutcomeAlias(outcomeId, _params);
133+
outcomeId = resolved.outcomeId;
134+
_params = resolved.params;
132135
const raw = await this.fetcher.fetchRawOrderBook(outcomeId);
133136
return this.normalizer.normalizeOrderBook(raw, outcomeId);
134137
}

core/src/exchanges/kalshi/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ export class KalshiExchange extends PredictionMarketExchange {
195195
}
196196

197197
async fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
198+
const resolved = await this.resolveOutcomeAlias(outcomeId, _params);
199+
outcomeId = resolved.outcomeId;
200+
_params = resolved.params;
198201
const raw = await this.fetcher.fetchRawOrderBook(outcomeId);
199202
return this.normalizer.normalizeOrderBook(raw, outcomeId);
200203
}

core/src/exchanges/limitless/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ export class LimitlessExchange extends PredictionMarketExchange {
202202
}
203203

204204
async fetchOrderBook(outcomeId: string, limit?: number, params?: Record<string, any>): Promise<OrderBook> {
205+
const resolved = await this.resolveOutcomeAlias(outcomeId, params);
206+
outcomeId = resolved.outcomeId;
207+
params = resolved.params;
205208
const slug = await this.resolveSlug(outcomeId);
206209
const rawOrderBook = await this.fetcher.fetchRawOrderBook!(slug);
207210
const orderBook = this.normalizer.normalizeOrderBook!(rawOrderBook as any, outcomeId);

core/src/exchanges/mock/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ export class MockExchange extends PredictionMarketExchange {
318318
}
319319

320320
override async fetchOrderBook(id: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
321+
const resolved = await this.resolveOutcomeAlias(id, _params);
322+
id = resolved.outcomeId;
321323
const f = new SeededRng(id);
322324
const midPrice = round(f.float(0.1, 0.9), 3);
323325
const spread = round(f.float(0.005, 0.03), 3);

core/src/exchanges/myriad/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ export class MyriadExchange extends PredictionMarketExchange {
110110
}
111111

112112
async fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
113+
const resolved = await this.resolveOutcomeAlias(outcomeId, _params);
114+
outcomeId = resolved.outcomeId;
115+
_params = resolved.params;
113116
const parts = outcomeId.split(':');
114117
if (parts.length >= 3) {
115118
const [networkId, marketId, oid] = parts;

core/src/exchanges/opinion/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ export class OpinionExchange extends PredictionMarketExchange {
208208
}
209209

210210
async fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
211+
const resolved = await this.resolveOutcomeAlias(outcomeId, _params);
212+
outcomeId = resolved.outcomeId;
213+
_params = resolved.params;
211214
const raw = await this.fetcher.fetchRawOrderBook(outcomeId);
212215
return this.normalizer.normalizeOrderBook(raw, outcomeId);
213216
}

0 commit comments

Comments
 (0)