@@ -36,12 +36,14 @@ import {
3636 PaginatedMarketsResult ,
3737 PaginatedEventsResult ,
3838 Position ,
39+ SeriesFetchParams ,
3940 PriceCandle ,
4041 SubscribedAddressSnapshot ,
4142 SubscriptionOption ,
4243 Trade ,
4344 UnifiedEvent ,
4445 UnifiedMarket ,
46+ UnifiedSeries ,
4547 UserTrade ,
4648 FirehoseEvent ,
4749} from "./models.js" ;
@@ -61,6 +63,7 @@ interface SidecarWsClientInternals {
6163 ws : RawWebSocketLike | null ;
6264 activeSubs : Map < string , string > ;
6365 subscriptions : Map < string , { reject : ( ( error : Error ) => void ) | null } > ;
66+ dataQueues : Map < string , any [ ] > ;
6467 dataStore : Map < string , any > ;
6568}
6669
@@ -171,6 +174,10 @@ function convertEvent(raw: any): UnifiedEvent {
171174 return { ...raw , markets } ;
172175}
173176
177+ function convertSeries ( raw : any ) : UnifiedSeries {
178+ const events = Array . isArray ( raw . events ) ? raw . events . map ( convertEvent ) : undefined ;
179+ return { ...raw , ...( events !== undefined ? { events } : { } ) } ;
180+ }
174181
175182function convertSubscriptionSnapshot ( raw : any ) : SubscribedAddressSnapshot {
176183 return {
@@ -238,6 +245,13 @@ export interface ExchangeOptions {
238245 * prediction market platforms (Polymarket, Kalshi, etc.).
239246 */
240247export abstract class Exchange {
248+ private static readonly OBDATA_WATCH_ALL_SOURCES = new Set ( [
249+ "polymarket" ,
250+ "limitless" ,
251+ "kalshi" ,
252+ "opinion" ,
253+ ] ) ;
254+
241255 protected exchangeName : string ;
242256 protected apiKey ?: string ;
243257 protected privateKey ?: string ;
@@ -508,6 +522,13 @@ export abstract class Exchange {
508522 && / c o n n e c t i o n f a i l e d | n o w e b s o c k e t | w e b s o c k e t .* n o t c o n n e c t e d / i. test ( error . message ) ;
509523 }
510524
525+ private defaultWatchAllOrderBookVenues ( ) : string [ ] | undefined {
526+ if ( Exchange . OBDATA_WATCH_ALL_SOURCES . has ( this . exchangeName ) ) {
527+ return [ this . exchangeName ] ;
528+ }
529+ return undefined ;
530+ }
531+
511532 private getWsInternals ( ws : SidecarWsClient ) : SidecarWsClientInternals {
512533 return ws as unknown as SidecarWsClientInternals ;
513534 }
@@ -538,6 +559,7 @@ export abstract class Exchange {
538559
539560 internals . activeSubs . delete ( subKey ) ;
540561 internals . subscriptions . delete ( requestId ) ;
562+ internals . dataQueues . delete ( requestId ) ;
541563 internals . dataStore . delete ( requestId ) ;
542564
543565 const firstArg = args [ 0 ] ?? "" ;
@@ -847,6 +869,32 @@ export abstract class Exchange {
847869 }
848870 }
849871
872+ async fetchSeries ( params ?: SeriesFetchParams ) : Promise < UnifiedSeries [ ] > {
873+ await this . initPromise ;
874+ try {
875+ const args : any [ ] = [ ] ;
876+ if ( params !== undefined ) args . push ( params ) ;
877+ const response = await this . fetchWithRetry ( `${ this . resolveBaseUrl ( ) } /api/${ this . exchangeName } /fetchSeries` , {
878+ method : 'POST' ,
879+ headers : { 'Content-Type' : 'application/json' , ...this . getAuthHeaders ( ) } ,
880+ body : JSON . stringify ( { args, credentials : this . getCredentials ( ) } ) ,
881+ } ) ;
882+ if ( ! response . ok ) {
883+ const body = await response . json ( ) . catch ( ( ) => ( { } ) ) ;
884+ if ( body . error && typeof body . error === "object" ) {
885+ throw fromServerError ( body . error ) ;
886+ }
887+ throw new PmxtError ( body . error ?. message || response . statusText ) ;
888+ }
889+ const json = await response . json ( ) ;
890+ const data = this . handleResponse ( json ) ;
891+ return data . map ( convertSeries ) ;
892+ } catch ( error ) {
893+ if ( error instanceof PmxtError ) throw error ;
894+ throw new PmxtError ( `Failed to fetchSeries: ${ error } ` ) ;
895+ }
896+ }
897+
850898 async fetchMarket ( params ?: MarketFetchParams ) : Promise < UnifiedMarket > {
851899 await this . initPromise ;
852900 try {
@@ -1199,32 +1247,24 @@ export abstract class Exchange {
11991247
12001248 async unwatchOrderBook ( outcomeId : string | MarketOutcome ) : Promise < void > {
12011249 await this . initPromise ;
1202- const resolvedOutcomeId = resolveOutcomeId ( outcomeId ) ;
1203- const args : any [ ] = [ resolvedOutcomeId ] ;
12041250 try {
1205- const ws = await this . getOrCreateWs ( ) ;
1206- if ( ! ws ) {
1207- throw this . wsTransportUnavailableError ( "unwatchOrderBook" ) ;
1251+ const args : any [ ] = [ ] ;
1252+ args . push ( resolveOutcomeId ( outcomeId ) ) ;
1253+ const response = await this . fetchWithRetry ( `${ this . resolveBaseUrl ( ) } /api/${ this . exchangeName } /unwatchOrderBook` , {
1254+ method : 'POST' ,
1255+ headers : { 'Content-Type' : 'application/json' , ...this . getAuthHeaders ( ) } ,
1256+ body : JSON . stringify ( { args, credentials : this . getCredentials ( ) } ) ,
1257+ } ) ;
1258+ if ( ! response . ok ) {
1259+ const body = await response . json ( ) . catch ( ( ) => ( { } ) ) ;
1260+ if ( body . error && typeof body . error === "object" ) {
1261+ throw fromServerError ( body . error ) ;
1262+ }
1263+ throw new PmxtError ( body . error ?. message || response . statusText ) ;
12081264 }
1209-
1210- const requestId = this . getWsSubscriptionId ( ws , "watchOrderBook" , args )
1211- ?? `req-${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 14 ) } ` ;
1212-
1213- await this . sendWsMessage (
1214- ws ,
1215- {
1216- id : requestId ,
1217- action : "unsubscribe" ,
1218- exchange : this . exchangeName ,
1219- method : "unwatchOrderBook" ,
1220- args,
1221- } ,
1222- ) ;
1223- this . clearWsSubscription ( ws , "watchOrderBook" , args ) ;
1265+ const json = await response . json ( ) ;
1266+ this . handleResponse ( json ) ;
12241267 } catch ( error ) {
1225- if ( this . isWsTransportUnavailableError ( error ) ) {
1226- throw this . wsTransportUnavailableError ( "unwatchOrderBook" ) ;
1227- }
12281268 if ( error instanceof PmxtError ) throw error ;
12291269 throw new PmxtError ( `Failed to unwatchOrderBook: ${ error } ` ) ;
12301270 }
@@ -1718,7 +1758,8 @@ export abstract class Exchange {
17181758 * Call repeatedly in a loop to stream updates (CCXT Pro pattern).
17191759 * Requires hosted mode (`pmxtApiKey` set).
17201760 *
1721- * @param venues - Optional venue filter (e.g. ["polymarket", "limitless"])
1761+ * @param venues - Optional venue filter. Defaults to this exchange's venue
1762+ * for venue clients (e.g. Kalshi -> ["kalshi"]); Router defaults to all venues.
17221763 * @returns Next event with source, symbol, and orderbook
17231764 *
17241765 * @example
@@ -1737,7 +1778,8 @@ export abstract class Exchange {
17371778 throw new PmxtError ( "watchAllOrderBooks() requires hosted mode (set pmxtApiKey)" ) ;
17381779 }
17391780
1740- const args : any [ ] = venues ? [ venues ] : [ ] ;
1781+ const effectiveVenues = venues ?? this . defaultWatchAllOrderBookVenues ( ) ;
1782+ const args : any [ ] = effectiveVenues ?. length ? [ effectiveVenues ] : [ ] ;
17411783 const wsData = await this . watchViaWs ( "watchAllOrderBooks" , args ) ;
17421784 if ( wsData !== null ) {
17431785 return {
0 commit comments