pmxtjs 2.43.24 → 2.44.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.
@@ -150,9 +150,24 @@ export declare abstract class Exchange {
150
150
  * @internal — shared transport used by every generated read method.
151
151
  */
152
152
  protected sidecarReadRequest(methodName: string, query: Record<string, unknown>, args: unknown[]): Promise<any>;
153
+ /**
154
+ * Dispatch a sidecar POST method with positional args and credentials.
155
+ *
156
+ * @internal - shared transport for hand-maintained methods that should
157
+ * never use the GET read path.
158
+ */
159
+ protected sidecarPostRequest(methodName: string, args: unknown[]): Promise<any>;
160
+ /**
161
+ * Read a hosted catalog endpoint directly.
162
+ *
163
+ * Hosted-only Router APIs such as matched clusters are not part of the
164
+ * core sidecar method namespace. They live under /v0 and return their own
165
+ * response envelopes, so callers intentionally receive the raw JSON body.
166
+ */
167
+ protected catalogReadRequest(path: string, query?: Record<string, unknown>): Promise<any>;
153
168
  loadMarkets(reload?: boolean): Promise<Record<string, UnifiedMarket>>;
154
169
  fetchMarkets(params?: MarketFetchParams): Promise<UnifiedMarket[]>;
155
- fetchMarketsPaginated(params?: MarketFetchParams): Promise<PaginatedMarketsResult>;
170
+ fetchMarketsPaginated(params?: any): Promise<PaginatedMarketsResult>;
156
171
  fetchEvents(params?: EventFetchParams): Promise<UnifiedEvent[]>;
157
172
  fetchMarket(params?: MarketFetchParams): Promise<UnifiedMarket>;
158
173
  fetchEvent(params?: EventFetchParams): Promise<UnifiedEvent>;
@@ -480,6 +495,13 @@ export interface PolymarketOptions {
480
495
  }
481
496
  export declare class Polymarket extends Exchange {
482
497
  constructor(options?: PolymarketOptions);
498
+ /**
499
+ * Initialize Polymarket L2 API credentials for implicit API signing.
500
+ *
501
+ * Call this before private Polymarket implicit-API endpoints when the
502
+ * underlying CLOB credentials have not been created yet.
503
+ */
504
+ initAuth(): Promise<void>;
483
505
  }
484
506
  /**
485
507
  * Kalshi exchange client.
@@ -463,6 +463,50 @@ export class Exchange {
463
463
  }
464
464
  return response.json();
465
465
  }
466
+ /**
467
+ * Dispatch a sidecar POST method with positional args and credentials.
468
+ *
469
+ * @internal - shared transport for hand-maintained methods that should
470
+ * never use the GET read path.
471
+ */
472
+ async sidecarPostRequest(methodName, args) {
473
+ const response = await this.fetchWithRetry(`${this.resolveBaseUrl()}/api/${this.exchangeName}/${methodName}`, {
474
+ method: 'POST',
475
+ headers: { 'Content-Type': 'application/json', ...this.getAuthHeaders() },
476
+ body: JSON.stringify({ args, credentials: this.getCredentials() }),
477
+ });
478
+ if (!response.ok) {
479
+ const body = await response.json().catch(() => ({}));
480
+ if (body.error && typeof body.error === "object") {
481
+ throw fromServerError(body.error);
482
+ }
483
+ throw new PmxtError(body.error?.message || response.statusText);
484
+ }
485
+ return response.json();
486
+ }
487
+ /**
488
+ * Read a hosted catalog endpoint directly.
489
+ *
490
+ * Hosted-only Router APIs such as matched clusters are not part of the
491
+ * core sidecar method namespace. They live under /v0 and return their own
492
+ * response envelopes, so callers intentionally receive the raw JSON body.
493
+ */
494
+ async catalogReadRequest(path, query = {}) {
495
+ const qs = buildSidecarQueryString(query);
496
+ const url = `${this.resolveBaseUrl()}${path}${qs ? `?${qs}` : ''}`;
497
+ const response = await this.fetchWithRetry(url, {
498
+ method: 'GET',
499
+ headers: this.getAuthHeaders(),
500
+ });
501
+ if (!response.ok) {
502
+ const body = await response.json().catch(() => ({}));
503
+ if (body.error && typeof body.error === "object") {
504
+ throw fromServerError(body.error);
505
+ }
506
+ throw new PmxtError(body.error?.message || response.statusText);
507
+ }
508
+ return response.json();
509
+ }
466
510
  // BEGIN GENERATED METHODS
467
511
  async loadMarkets(reload = false) {
468
512
  await this.initPromise;
@@ -2219,6 +2263,24 @@ export class Polymarket extends Exchange {
2219
2263
  };
2220
2264
  super("polymarket", polyOptions);
2221
2265
  }
2266
+ /**
2267
+ * Initialize Polymarket L2 API credentials for implicit API signing.
2268
+ *
2269
+ * Call this before private Polymarket implicit-API endpoints when the
2270
+ * underlying CLOB credentials have not been created yet.
2271
+ */
2272
+ async initAuth() {
2273
+ await this.initPromise;
2274
+ try {
2275
+ const json = await this.sidecarPostRequest('initAuth', []);
2276
+ this.handleResponse(json);
2277
+ }
2278
+ catch (error) {
2279
+ if (error instanceof PmxtError)
2280
+ throw error;
2281
+ throw new PmxtError(`Failed to initAuth: ${error}`);
2282
+ }
2283
+ }
2222
2284
  }
2223
2285
  /**
2224
2286
  * Kalshi exchange client.
@@ -639,6 +639,96 @@ export interface EventMatchResult extends Readonly<UnifiedEvent> {
639
639
  /** Cross-venue market matches within this event. */
640
640
  marketMatches: MatchResult[];
641
641
  }
642
+ export type MatchedClusterSort = 'volume' | 'confidence';
643
+ /** Shared filters for matched market/event cluster discovery. */
644
+ export interface MatchedClusterFilterParams {
645
+ /** Text search across cluster titles. */
646
+ query?: string;
647
+ /** Filter both sides of matched edges by category. */
648
+ category?: string;
649
+ /** Comma-separated relation filter. */
650
+ relations?: MatchRelation | MatchRelation[] | string;
651
+ /** Single relation filter. Alias for relations. */
652
+ relation?: MatchRelation;
653
+ /** Minimum match confidence score (0.0 to 1.0). */
654
+ minConfidence?: number;
655
+ /** Venue allow-list. */
656
+ venues?: string | string[];
657
+ /** Venue deny-list. */
658
+ excludeVenues?: string | string[];
659
+ /** Minimum number of venues required in a cluster. */
660
+ minVenues?: number;
661
+ /** Require live orderbook coverage on matched edges. */
662
+ withOrderbook?: boolean;
663
+ /** Only include matches updated after this timestamp. */
664
+ updatedSince?: string | Date;
665
+ /** Include the pairwise match edges used to build each cluster. */
666
+ includeRawMatches?: boolean;
667
+ /** Cluster sort order. */
668
+ sort?: MatchedClusterSort;
669
+ /** Maximum clusters to return. */
670
+ limit?: number;
671
+ /** Pagination offset. */
672
+ offset?: number;
673
+ /** Maximum pairwise edges to scan before clustering. */
674
+ edgeLimit?: number;
675
+ }
676
+ /** Parameters for fetching matched market clusters. */
677
+ export interface FetchMatchedMarketClustersParams extends MatchedClusterFilterParams {
678
+ /** Pass a UnifiedMarket directly instead of marketId/slug/url. */
679
+ market?: UnifiedMarket;
680
+ /** Anchor the response to a specific market ID. */
681
+ marketId?: string;
682
+ /** Anchor the response to a specific market slug. */
683
+ slug?: string;
684
+ /** Anchor the response to a specific market URL. */
685
+ url?: string;
686
+ }
687
+ /** Parameters for fetching matched event clusters. */
688
+ export interface FetchMatchedEventClustersParams extends MatchedClusterFilterParams {
689
+ /** Pass a UnifiedEvent directly instead of eventId/slug/url. */
690
+ event?: UnifiedEvent;
691
+ /** Anchor the response to a specific event ID. */
692
+ eventId?: string;
693
+ /** Anchor the response to a specific event slug. */
694
+ slug?: string;
695
+ /** Anchor the response to a specific event URL. */
696
+ url?: string;
697
+ }
698
+ /** Pairwise edge used to build a matched market cluster. */
699
+ export interface MatchedMarketClusterEdge {
700
+ marketAId: string;
701
+ marketBId: string;
702
+ relation: MatchRelation;
703
+ confidence: number;
704
+ }
705
+ /** Pairwise edge used to build a matched event cluster. */
706
+ export interface MatchedEventClusterEdge extends MatchedMarketClusterEdge {
707
+ eventAId: string;
708
+ eventBId: string;
709
+ }
710
+ /** Connected cluster of semantically matched markets across venues. */
711
+ export interface MatchedMarketCluster {
712
+ clusterId: string;
713
+ canonicalTitle: string | null;
714
+ category?: string | null;
715
+ relations: MatchRelation[];
716
+ confidence: number;
717
+ volume24h: number;
718
+ markets: UnifiedMarket[];
719
+ rawMatches?: MatchedMarketClusterEdge[];
720
+ }
721
+ /** Connected cluster of semantically matched events across venues. */
722
+ export interface MatchedEventCluster {
723
+ clusterId: string;
724
+ canonicalTitle: string | null;
725
+ category?: string | null;
726
+ relations: MatchRelation[];
727
+ confidence: number;
728
+ volume24h: number;
729
+ events: UnifiedEvent[];
730
+ rawMatches?: MatchedEventClusterEdge[];
731
+ }
642
732
  /** Side-by-side price comparison for a matched market. */
643
733
  export interface PriceComparison {
644
734
  /** The matched market. */
@@ -5,7 +5,7 @@
5
5
  * every venue PMXT supports. Only requires a PMXT API key.
6
6
  */
7
7
  import { Exchange } from "./client.js";
8
- import { MatchResult, MatchRelation, EventMatchResult, PriceComparison, ArbitrageOpportunity, UnifiedMarket, UnifiedEvent } from "./models.js";
8
+ import { MatchResult, MatchRelation, EventMatchResult, FetchMatchedEventClustersParams, FetchMatchedMarketClustersParams, MatchedEventCluster, MatchedMarketCluster, PriceComparison, ArbitrageOpportunity, UnifiedMarket, UnifiedEvent } from "./models.js";
9
9
  /** Options for creating a Router. */
10
10
  export interface RouterOptions {
11
11
  /** PMXT API key (required for hosted mode). */
@@ -78,6 +78,20 @@ export declare class Router extends Exchange {
78
78
  limit?: number;
79
79
  includePrices?: boolean;
80
80
  }): Promise<EventMatchResult[]>;
81
+ /**
82
+ * Fetch connected clusters of semantically matched markets across venues.
83
+ *
84
+ * @param marketOrParams - A UnifiedMarket, or an options object.
85
+ */
86
+ fetchMatchedMarketClusters(market: UnifiedMarket): Promise<MatchedMarketCluster[]>;
87
+ fetchMatchedMarketClusters(params?: FetchMatchedMarketClustersParams): Promise<MatchedMarketCluster[]>;
88
+ /**
89
+ * Fetch connected clusters of semantically matched events across venues.
90
+ *
91
+ * @param eventOrParams - A UnifiedEvent, or an options object.
92
+ */
93
+ fetchMatchedEventClusters(event: UnifiedEvent): Promise<MatchedEventCluster[]>;
94
+ fetchMatchedEventClusters(params?: FetchMatchedEventClustersParams): Promise<MatchedEventCluster[]>;
81
95
  /**
82
96
  * Compare prices for the same market across venues.
83
97
  *
@@ -13,6 +13,8 @@ function convertMarket(raw) {
13
13
  label: o.label,
14
14
  price: o.price,
15
15
  priceChange24h: o.priceChange24h,
16
+ bestBid: o.bestBid,
17
+ bestAsk: o.bestAsk,
16
18
  metadata: o.metadata,
17
19
  }));
18
20
  const convertOutcome = (o) => o ? ({
@@ -21,6 +23,8 @@ function convertMarket(raw) {
21
23
  label: o.label,
22
24
  price: o.price,
23
25
  priceChange24h: o.priceChange24h,
26
+ bestBid: o.bestBid,
27
+ bestAsk: o.bestAsk,
24
28
  metadata: o.metadata,
25
29
  }) : undefined;
26
30
  return {
@@ -79,6 +83,58 @@ function parseMatchResult(raw) {
79
83
  sourceMarket: raw.sourceMarket ? convertMarket(raw.sourceMarket) : undefined,
80
84
  };
81
85
  }
86
+ function normalizeQueryValue(value) {
87
+ if (value instanceof Date)
88
+ return value.toISOString();
89
+ return value;
90
+ }
91
+ function appendQuery(query, key, value) {
92
+ if (value !== undefined && value !== null && value !== '') {
93
+ query[key] = normalizeQueryValue(value);
94
+ }
95
+ }
96
+ function addClusterFilters(query, params) {
97
+ appendQuery(query, 'query', params.query);
98
+ appendQuery(query, 'category', params.category);
99
+ appendQuery(query, 'relations', params.relations);
100
+ appendQuery(query, 'relation', params.relation);
101
+ appendQuery(query, 'minConfidence', params.minConfidence);
102
+ appendQuery(query, 'venues', params.venues);
103
+ appendQuery(query, 'excludeVenues', params.excludeVenues);
104
+ appendQuery(query, 'minVenues', params.minVenues);
105
+ appendQuery(query, 'withOrderbook', params.withOrderbook);
106
+ appendQuery(query, 'updatedSince', params.updatedSince);
107
+ appendQuery(query, 'includeRawMatches', params.includeRawMatches);
108
+ appendQuery(query, 'sort', params.sort);
109
+ appendQuery(query, 'limit', params.limit);
110
+ appendQuery(query, 'offset', params.offset);
111
+ appendQuery(query, 'edgeLimit', params.edgeLimit);
112
+ }
113
+ function extractCatalogArray(raw) {
114
+ if (Array.isArray(raw))
115
+ return raw;
116
+ if (Array.isArray(raw?.data))
117
+ return raw.data;
118
+ return [];
119
+ }
120
+ function isUnifiedMarket(value) {
121
+ return Boolean(value && 'marketId' in value && 'title' in value);
122
+ }
123
+ function isUnifiedEvent(value) {
124
+ return Boolean(value && 'id' in value && 'title' in value && 'markets' in value);
125
+ }
126
+ function parseMatchedMarketCluster(raw) {
127
+ return {
128
+ ...raw,
129
+ markets: (raw.markets || []).map(convertMarket),
130
+ };
131
+ }
132
+ function parseMatchedEventCluster(raw) {
133
+ return {
134
+ ...raw,
135
+ events: (raw.events || []).map(convertEvent),
136
+ };
137
+ }
82
138
  /**
83
139
  * Cross-venue intelligence layer.
84
140
  *
@@ -172,6 +228,52 @@ export class Router extends Exchange {
172
228
  throw new Error(`Failed to fetchEventMatches: ${error}`);
173
229
  }
174
230
  }
231
+ async fetchMatchedMarketClusters(marketOrParams = {}) {
232
+ const params = isUnifiedMarket(marketOrParams)
233
+ ? { market: marketOrParams }
234
+ : marketOrParams;
235
+ await this.initPromise;
236
+ const query = {};
237
+ const marketId = params.marketId ?? params.market?.marketId;
238
+ const slug = params.slug ?? (!marketId ? params.market?.slug : undefined);
239
+ const url = params.url ?? (!marketId && !slug ? params.market?.url : undefined);
240
+ appendQuery(query, 'marketId', marketId);
241
+ appendQuery(query, 'slug', slug);
242
+ appendQuery(query, 'url', url);
243
+ addClusterFilters(query, params);
244
+ try {
245
+ const raw = await this.catalogReadRequest('/v0/matched-market-clusters', query);
246
+ return extractCatalogArray(raw).map(parseMatchedMarketCluster);
247
+ }
248
+ catch (error) {
249
+ if (error instanceof Error)
250
+ throw error;
251
+ throw new Error(`Failed to fetchMatchedMarketClusters: ${error}`);
252
+ }
253
+ }
254
+ async fetchMatchedEventClusters(eventOrParams = {}) {
255
+ const params = isUnifiedEvent(eventOrParams)
256
+ ? { event: eventOrParams }
257
+ : eventOrParams;
258
+ await this.initPromise;
259
+ const query = {};
260
+ const eventId = params.eventId ?? params.event?.id;
261
+ const slug = params.slug ?? (!eventId ? params.event?.slug : undefined);
262
+ const url = params.url ?? (!eventId && !slug ? params.event?.url : undefined);
263
+ appendQuery(query, 'eventId', eventId);
264
+ appendQuery(query, 'slug', slug);
265
+ appendQuery(query, 'url', url);
266
+ addClusterFilters(query, params);
267
+ try {
268
+ const raw = await this.catalogReadRequest('/v0/matched-event-clusters', query);
269
+ return extractCatalogArray(raw).map(parseMatchedEventCluster);
270
+ }
271
+ catch (error) {
272
+ if (error instanceof Error)
273
+ throw error;
274
+ throw new Error(`Failed to fetchMatchedEventClusters: ${error}`);
275
+ }
276
+ }
175
277
  async compareMarketPrices(marketOrParams = {}) {
176
278
  const params = 'title' in marketOrParams ? { market: marketOrParams } : marketOrParams;
177
279
  await this.initPromise;
@@ -150,9 +150,24 @@ export declare abstract class Exchange {
150
150
  * @internal — shared transport used by every generated read method.
151
151
  */
152
152
  protected sidecarReadRequest(methodName: string, query: Record<string, unknown>, args: unknown[]): Promise<any>;
153
+ /**
154
+ * Dispatch a sidecar POST method with positional args and credentials.
155
+ *
156
+ * @internal - shared transport for hand-maintained methods that should
157
+ * never use the GET read path.
158
+ */
159
+ protected sidecarPostRequest(methodName: string, args: unknown[]): Promise<any>;
160
+ /**
161
+ * Read a hosted catalog endpoint directly.
162
+ *
163
+ * Hosted-only Router APIs such as matched clusters are not part of the
164
+ * core sidecar method namespace. They live under /v0 and return their own
165
+ * response envelopes, so callers intentionally receive the raw JSON body.
166
+ */
167
+ protected catalogReadRequest(path: string, query?: Record<string, unknown>): Promise<any>;
153
168
  loadMarkets(reload?: boolean): Promise<Record<string, UnifiedMarket>>;
154
169
  fetchMarkets(params?: MarketFetchParams): Promise<UnifiedMarket[]>;
155
- fetchMarketsPaginated(params?: MarketFetchParams): Promise<PaginatedMarketsResult>;
170
+ fetchMarketsPaginated(params?: any): Promise<PaginatedMarketsResult>;
156
171
  fetchEvents(params?: EventFetchParams): Promise<UnifiedEvent[]>;
157
172
  fetchMarket(params?: MarketFetchParams): Promise<UnifiedMarket>;
158
173
  fetchEvent(params?: EventFetchParams): Promise<UnifiedEvent>;
@@ -480,6 +495,13 @@ export interface PolymarketOptions {
480
495
  }
481
496
  export declare class Polymarket extends Exchange {
482
497
  constructor(options?: PolymarketOptions);
498
+ /**
499
+ * Initialize Polymarket L2 API credentials for implicit API signing.
500
+ *
501
+ * Call this before private Polymarket implicit-API endpoints when the
502
+ * underlying CLOB credentials have not been created yet.
503
+ */
504
+ initAuth(): Promise<void>;
483
505
  }
484
506
  /**
485
507
  * Kalshi exchange client.
@@ -466,6 +466,50 @@ class Exchange {
466
466
  }
467
467
  return response.json();
468
468
  }
469
+ /**
470
+ * Dispatch a sidecar POST method with positional args and credentials.
471
+ *
472
+ * @internal - shared transport for hand-maintained methods that should
473
+ * never use the GET read path.
474
+ */
475
+ async sidecarPostRequest(methodName, args) {
476
+ const response = await this.fetchWithRetry(`${this.resolveBaseUrl()}/api/${this.exchangeName}/${methodName}`, {
477
+ method: 'POST',
478
+ headers: { 'Content-Type': 'application/json', ...this.getAuthHeaders() },
479
+ body: JSON.stringify({ args, credentials: this.getCredentials() }),
480
+ });
481
+ if (!response.ok) {
482
+ const body = await response.json().catch(() => ({}));
483
+ if (body.error && typeof body.error === "object") {
484
+ throw (0, errors_js_1.fromServerError)(body.error);
485
+ }
486
+ throw new errors_js_1.PmxtError(body.error?.message || response.statusText);
487
+ }
488
+ return response.json();
489
+ }
490
+ /**
491
+ * Read a hosted catalog endpoint directly.
492
+ *
493
+ * Hosted-only Router APIs such as matched clusters are not part of the
494
+ * core sidecar method namespace. They live under /v0 and return their own
495
+ * response envelopes, so callers intentionally receive the raw JSON body.
496
+ */
497
+ async catalogReadRequest(path, query = {}) {
498
+ const qs = buildSidecarQueryString(query);
499
+ const url = `${this.resolveBaseUrl()}${path}${qs ? `?${qs}` : ''}`;
500
+ const response = await this.fetchWithRetry(url, {
501
+ method: 'GET',
502
+ headers: this.getAuthHeaders(),
503
+ });
504
+ if (!response.ok) {
505
+ const body = await response.json().catch(() => ({}));
506
+ if (body.error && typeof body.error === "object") {
507
+ throw (0, errors_js_1.fromServerError)(body.error);
508
+ }
509
+ throw new errors_js_1.PmxtError(body.error?.message || response.statusText);
510
+ }
511
+ return response.json();
512
+ }
469
513
  // BEGIN GENERATED METHODS
470
514
  async loadMarkets(reload = false) {
471
515
  await this.initPromise;
@@ -2223,6 +2267,24 @@ class Polymarket extends Exchange {
2223
2267
  };
2224
2268
  super("polymarket", polyOptions);
2225
2269
  }
2270
+ /**
2271
+ * Initialize Polymarket L2 API credentials for implicit API signing.
2272
+ *
2273
+ * Call this before private Polymarket implicit-API endpoints when the
2274
+ * underlying CLOB credentials have not been created yet.
2275
+ */
2276
+ async initAuth() {
2277
+ await this.initPromise;
2278
+ try {
2279
+ const json = await this.sidecarPostRequest('initAuth', []);
2280
+ this.handleResponse(json);
2281
+ }
2282
+ catch (error) {
2283
+ if (error instanceof errors_js_1.PmxtError)
2284
+ throw error;
2285
+ throw new errors_js_1.PmxtError(`Failed to initAuth: ${error}`);
2286
+ }
2287
+ }
2226
2288
  }
2227
2289
  exports.Polymarket = Polymarket;
2228
2290
  /**
@@ -639,6 +639,96 @@ export interface EventMatchResult extends Readonly<UnifiedEvent> {
639
639
  /** Cross-venue market matches within this event. */
640
640
  marketMatches: MatchResult[];
641
641
  }
642
+ export type MatchedClusterSort = 'volume' | 'confidence';
643
+ /** Shared filters for matched market/event cluster discovery. */
644
+ export interface MatchedClusterFilterParams {
645
+ /** Text search across cluster titles. */
646
+ query?: string;
647
+ /** Filter both sides of matched edges by category. */
648
+ category?: string;
649
+ /** Comma-separated relation filter. */
650
+ relations?: MatchRelation | MatchRelation[] | string;
651
+ /** Single relation filter. Alias for relations. */
652
+ relation?: MatchRelation;
653
+ /** Minimum match confidence score (0.0 to 1.0). */
654
+ minConfidence?: number;
655
+ /** Venue allow-list. */
656
+ venues?: string | string[];
657
+ /** Venue deny-list. */
658
+ excludeVenues?: string | string[];
659
+ /** Minimum number of venues required in a cluster. */
660
+ minVenues?: number;
661
+ /** Require live orderbook coverage on matched edges. */
662
+ withOrderbook?: boolean;
663
+ /** Only include matches updated after this timestamp. */
664
+ updatedSince?: string | Date;
665
+ /** Include the pairwise match edges used to build each cluster. */
666
+ includeRawMatches?: boolean;
667
+ /** Cluster sort order. */
668
+ sort?: MatchedClusterSort;
669
+ /** Maximum clusters to return. */
670
+ limit?: number;
671
+ /** Pagination offset. */
672
+ offset?: number;
673
+ /** Maximum pairwise edges to scan before clustering. */
674
+ edgeLimit?: number;
675
+ }
676
+ /** Parameters for fetching matched market clusters. */
677
+ export interface FetchMatchedMarketClustersParams extends MatchedClusterFilterParams {
678
+ /** Pass a UnifiedMarket directly instead of marketId/slug/url. */
679
+ market?: UnifiedMarket;
680
+ /** Anchor the response to a specific market ID. */
681
+ marketId?: string;
682
+ /** Anchor the response to a specific market slug. */
683
+ slug?: string;
684
+ /** Anchor the response to a specific market URL. */
685
+ url?: string;
686
+ }
687
+ /** Parameters for fetching matched event clusters. */
688
+ export interface FetchMatchedEventClustersParams extends MatchedClusterFilterParams {
689
+ /** Pass a UnifiedEvent directly instead of eventId/slug/url. */
690
+ event?: UnifiedEvent;
691
+ /** Anchor the response to a specific event ID. */
692
+ eventId?: string;
693
+ /** Anchor the response to a specific event slug. */
694
+ slug?: string;
695
+ /** Anchor the response to a specific event URL. */
696
+ url?: string;
697
+ }
698
+ /** Pairwise edge used to build a matched market cluster. */
699
+ export interface MatchedMarketClusterEdge {
700
+ marketAId: string;
701
+ marketBId: string;
702
+ relation: MatchRelation;
703
+ confidence: number;
704
+ }
705
+ /** Pairwise edge used to build a matched event cluster. */
706
+ export interface MatchedEventClusterEdge extends MatchedMarketClusterEdge {
707
+ eventAId: string;
708
+ eventBId: string;
709
+ }
710
+ /** Connected cluster of semantically matched markets across venues. */
711
+ export interface MatchedMarketCluster {
712
+ clusterId: string;
713
+ canonicalTitle: string | null;
714
+ category?: string | null;
715
+ relations: MatchRelation[];
716
+ confidence: number;
717
+ volume24h: number;
718
+ markets: UnifiedMarket[];
719
+ rawMatches?: MatchedMarketClusterEdge[];
720
+ }
721
+ /** Connected cluster of semantically matched events across venues. */
722
+ export interface MatchedEventCluster {
723
+ clusterId: string;
724
+ canonicalTitle: string | null;
725
+ category?: string | null;
726
+ relations: MatchRelation[];
727
+ confidence: number;
728
+ volume24h: number;
729
+ events: UnifiedEvent[];
730
+ rawMatches?: MatchedEventClusterEdge[];
731
+ }
642
732
  /** Side-by-side price comparison for a matched market. */
643
733
  export interface PriceComparison {
644
734
  /** The matched market. */
@@ -5,7 +5,7 @@
5
5
  * every venue PMXT supports. Only requires a PMXT API key.
6
6
  */
7
7
  import { Exchange } from "./client.js";
8
- import { MatchResult, MatchRelation, EventMatchResult, PriceComparison, ArbitrageOpportunity, UnifiedMarket, UnifiedEvent } from "./models.js";
8
+ import { MatchResult, MatchRelation, EventMatchResult, FetchMatchedEventClustersParams, FetchMatchedMarketClustersParams, MatchedEventCluster, MatchedMarketCluster, PriceComparison, ArbitrageOpportunity, UnifiedMarket, UnifiedEvent } from "./models.js";
9
9
  /** Options for creating a Router. */
10
10
  export interface RouterOptions {
11
11
  /** PMXT API key (required for hosted mode). */
@@ -78,6 +78,20 @@ export declare class Router extends Exchange {
78
78
  limit?: number;
79
79
  includePrices?: boolean;
80
80
  }): Promise<EventMatchResult[]>;
81
+ /**
82
+ * Fetch connected clusters of semantically matched markets across venues.
83
+ *
84
+ * @param marketOrParams - A UnifiedMarket, or an options object.
85
+ */
86
+ fetchMatchedMarketClusters(market: UnifiedMarket): Promise<MatchedMarketCluster[]>;
87
+ fetchMatchedMarketClusters(params?: FetchMatchedMarketClustersParams): Promise<MatchedMarketCluster[]>;
88
+ /**
89
+ * Fetch connected clusters of semantically matched events across venues.
90
+ *
91
+ * @param eventOrParams - A UnifiedEvent, or an options object.
92
+ */
93
+ fetchMatchedEventClusters(event: UnifiedEvent): Promise<MatchedEventCluster[]>;
94
+ fetchMatchedEventClusters(params?: FetchMatchedEventClustersParams): Promise<MatchedEventCluster[]>;
81
95
  /**
82
96
  * Compare prices for the same market across venues.
83
97
  *
@@ -49,6 +49,8 @@ function convertMarket(raw) {
49
49
  label: o.label,
50
50
  price: o.price,
51
51
  priceChange24h: o.priceChange24h,
52
+ bestBid: o.bestBid,
53
+ bestAsk: o.bestAsk,
52
54
  metadata: o.metadata,
53
55
  }));
54
56
  const convertOutcome = (o) => o ? ({
@@ -57,6 +59,8 @@ function convertMarket(raw) {
57
59
  label: o.label,
58
60
  price: o.price,
59
61
  priceChange24h: o.priceChange24h,
62
+ bestBid: o.bestBid,
63
+ bestAsk: o.bestAsk,
60
64
  metadata: o.metadata,
61
65
  }) : undefined;
62
66
  return {
@@ -115,6 +119,58 @@ function parseMatchResult(raw) {
115
119
  sourceMarket: raw.sourceMarket ? convertMarket(raw.sourceMarket) : undefined,
116
120
  };
117
121
  }
122
+ function normalizeQueryValue(value) {
123
+ if (value instanceof Date)
124
+ return value.toISOString();
125
+ return value;
126
+ }
127
+ function appendQuery(query, key, value) {
128
+ if (value !== undefined && value !== null && value !== '') {
129
+ query[key] = normalizeQueryValue(value);
130
+ }
131
+ }
132
+ function addClusterFilters(query, params) {
133
+ appendQuery(query, 'query', params.query);
134
+ appendQuery(query, 'category', params.category);
135
+ appendQuery(query, 'relations', params.relations);
136
+ appendQuery(query, 'relation', params.relation);
137
+ appendQuery(query, 'minConfidence', params.minConfidence);
138
+ appendQuery(query, 'venues', params.venues);
139
+ appendQuery(query, 'excludeVenues', params.excludeVenues);
140
+ appendQuery(query, 'minVenues', params.minVenues);
141
+ appendQuery(query, 'withOrderbook', params.withOrderbook);
142
+ appendQuery(query, 'updatedSince', params.updatedSince);
143
+ appendQuery(query, 'includeRawMatches', params.includeRawMatches);
144
+ appendQuery(query, 'sort', params.sort);
145
+ appendQuery(query, 'limit', params.limit);
146
+ appendQuery(query, 'offset', params.offset);
147
+ appendQuery(query, 'edgeLimit', params.edgeLimit);
148
+ }
149
+ function extractCatalogArray(raw) {
150
+ if (Array.isArray(raw))
151
+ return raw;
152
+ if (Array.isArray(raw?.data))
153
+ return raw.data;
154
+ return [];
155
+ }
156
+ function isUnifiedMarket(value) {
157
+ return Boolean(value && 'marketId' in value && 'title' in value);
158
+ }
159
+ function isUnifiedEvent(value) {
160
+ return Boolean(value && 'id' in value && 'title' in value && 'markets' in value);
161
+ }
162
+ function parseMatchedMarketCluster(raw) {
163
+ return {
164
+ ...raw,
165
+ markets: (raw.markets || []).map(convertMarket),
166
+ };
167
+ }
168
+ function parseMatchedEventCluster(raw) {
169
+ return {
170
+ ...raw,
171
+ events: (raw.events || []).map(convertEvent),
172
+ };
173
+ }
118
174
  /**
119
175
  * Cross-venue intelligence layer.
120
176
  *
@@ -208,6 +264,52 @@ class Router extends client_js_1.Exchange {
208
264
  throw new Error(`Failed to fetchEventMatches: ${error}`);
209
265
  }
210
266
  }
267
+ async fetchMatchedMarketClusters(marketOrParams = {}) {
268
+ const params = isUnifiedMarket(marketOrParams)
269
+ ? { market: marketOrParams }
270
+ : marketOrParams;
271
+ await this.initPromise;
272
+ const query = {};
273
+ const marketId = params.marketId ?? params.market?.marketId;
274
+ const slug = params.slug ?? (!marketId ? params.market?.slug : undefined);
275
+ const url = params.url ?? (!marketId && !slug ? params.market?.url : undefined);
276
+ appendQuery(query, 'marketId', marketId);
277
+ appendQuery(query, 'slug', slug);
278
+ appendQuery(query, 'url', url);
279
+ addClusterFilters(query, params);
280
+ try {
281
+ const raw = await this.catalogReadRequest('/v0/matched-market-clusters', query);
282
+ return extractCatalogArray(raw).map(parseMatchedMarketCluster);
283
+ }
284
+ catch (error) {
285
+ if (error instanceof Error)
286
+ throw error;
287
+ throw new Error(`Failed to fetchMatchedMarketClusters: ${error}`);
288
+ }
289
+ }
290
+ async fetchMatchedEventClusters(eventOrParams = {}) {
291
+ const params = isUnifiedEvent(eventOrParams)
292
+ ? { event: eventOrParams }
293
+ : eventOrParams;
294
+ await this.initPromise;
295
+ const query = {};
296
+ const eventId = params.eventId ?? params.event?.id;
297
+ const slug = params.slug ?? (!eventId ? params.event?.slug : undefined);
298
+ const url = params.url ?? (!eventId && !slug ? params.event?.url : undefined);
299
+ appendQuery(query, 'eventId', eventId);
300
+ appendQuery(query, 'slug', slug);
301
+ appendQuery(query, 'url', url);
302
+ addClusterFilters(query, params);
303
+ try {
304
+ const raw = await this.catalogReadRequest('/v0/matched-event-clusters', query);
305
+ return extractCatalogArray(raw).map(parseMatchedEventCluster);
306
+ }
307
+ catch (error) {
308
+ if (error instanceof Error)
309
+ throw error;
310
+ throw new Error(`Failed to fetchMatchedEventClusters: ${error}`);
311
+ }
312
+ }
211
313
  async compareMarketPrices(marketOrParams = {}) {
212
314
  const params = 'title' in marketOrParams ? { market: marketOrParams } : marketOrParams;
213
315
  await this.initPromise;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxtjs",
3
- "version": "2.43.24",
3
+ "version": "2.44.0",
4
4
  "description": "OpenAPI client for pmxtjs",
5
5
  "author": "OpenAPI-Generator",
6
6
  "repository": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxtjs",
3
- "version": "2.43.24",
3
+ "version": "2.44.0",
4
4
  "description": "Unified prediction market data API - The ccxt for prediction markets",
5
5
  "author": "PMXT Contributors",
6
6
  "repository": {
@@ -43,7 +43,7 @@
43
43
  "unified"
44
44
  ],
45
45
  "dependencies": {
46
- "pmxt-core": "2.43.24",
46
+ "pmxt-core": "2.44.0",
47
47
  "ws": "^8.18.0"
48
48
  },
49
49
  "devDependencies": {
package/pmxt/client.ts CHANGED
@@ -610,6 +610,52 @@ export abstract class Exchange {
610
610
  return response.json();
611
611
  }
612
612
 
613
+ /**
614
+ * Dispatch a sidecar POST method with positional args and credentials.
615
+ *
616
+ * @internal - shared transport for hand-maintained methods that should
617
+ * never use the GET read path.
618
+ */
619
+ protected async sidecarPostRequest(methodName: string, args: unknown[]): Promise<any> {
620
+ const response = await this.fetchWithRetry(`${this.resolveBaseUrl()}/api/${this.exchangeName}/${methodName}`, {
621
+ method: 'POST',
622
+ headers: { 'Content-Type': 'application/json', ...this.getAuthHeaders() },
623
+ body: JSON.stringify({ args, credentials: this.getCredentials() }),
624
+ });
625
+ if (!response.ok) {
626
+ const body = await response.json().catch(() => ({}));
627
+ if (body.error && typeof body.error === "object") {
628
+ throw fromServerError(body.error);
629
+ }
630
+ throw new PmxtError(body.error?.message || response.statusText);
631
+ }
632
+ return response.json();
633
+ }
634
+
635
+ /**
636
+ * Read a hosted catalog endpoint directly.
637
+ *
638
+ * Hosted-only Router APIs such as matched clusters are not part of the
639
+ * core sidecar method namespace. They live under /v0 and return their own
640
+ * response envelopes, so callers intentionally receive the raw JSON body.
641
+ */
642
+ protected async catalogReadRequest(path: string, query: Record<string, unknown> = {}): Promise<any> {
643
+ const qs = buildSidecarQueryString(query);
644
+ const url = `${this.resolveBaseUrl()}${path}${qs ? `?${qs}` : ''}`;
645
+ const response = await this.fetchWithRetry(url, {
646
+ method: 'GET',
647
+ headers: this.getAuthHeaders(),
648
+ });
649
+ if (!response.ok) {
650
+ const body = await response.json().catch(() => ({}));
651
+ if (body.error && typeof body.error === "object") {
652
+ throw fromServerError(body.error);
653
+ }
654
+ throw new PmxtError(body.error?.message || response.statusText);
655
+ }
656
+ return response.json();
657
+ }
658
+
613
659
  // BEGIN GENERATED METHODS
614
660
 
615
661
  async loadMarkets(reload: boolean = false): Promise<Record<string, UnifiedMarket>> {
@@ -668,7 +714,7 @@ export abstract class Exchange {
668
714
  }
669
715
  }
670
716
 
671
- async fetchMarketsPaginated(params?: MarketFetchParams): Promise<PaginatedMarketsResult> {
717
+ async fetchMarketsPaginated(params?: any): Promise<PaginatedMarketsResult> {
672
718
  await this.initPromise;
673
719
  try {
674
720
  const args: any[] = [];
@@ -2449,6 +2495,23 @@ export class Polymarket extends Exchange {
2449
2495
  };
2450
2496
  super("polymarket", polyOptions as ExchangeOptions);
2451
2497
  }
2498
+
2499
+ /**
2500
+ * Initialize Polymarket L2 API credentials for implicit API signing.
2501
+ *
2502
+ * Call this before private Polymarket implicit-API endpoints when the
2503
+ * underlying CLOB credentials have not been created yet.
2504
+ */
2505
+ async initAuth(): Promise<void> {
2506
+ await this.initPromise;
2507
+ try {
2508
+ const json = await this.sidecarPostRequest('initAuth', []);
2509
+ this.handleResponse(json);
2510
+ } catch (error) {
2511
+ if (error instanceof PmxtError) throw error;
2512
+ throw new PmxtError(`Failed to initAuth: ${error}`);
2513
+ }
2514
+ }
2452
2515
  }
2453
2516
 
2454
2517
  /**
package/pmxt/models.ts CHANGED
@@ -861,6 +861,124 @@ export interface EventMatchResult extends Readonly<UnifiedEvent> {
861
861
  marketMatches: MatchResult[];
862
862
  }
863
863
 
864
+ export type MatchedClusterSort = 'volume' | 'confidence';
865
+
866
+ /** Shared filters for matched market/event cluster discovery. */
867
+ export interface MatchedClusterFilterParams {
868
+ /** Text search across cluster titles. */
869
+ query?: string;
870
+
871
+ /** Filter both sides of matched edges by category. */
872
+ category?: string;
873
+
874
+ /** Comma-separated relation filter. */
875
+ relations?: MatchRelation | MatchRelation[] | string;
876
+
877
+ /** Single relation filter. Alias for relations. */
878
+ relation?: MatchRelation;
879
+
880
+ /** Minimum match confidence score (0.0 to 1.0). */
881
+ minConfidence?: number;
882
+
883
+ /** Venue allow-list. */
884
+ venues?: string | string[];
885
+
886
+ /** Venue deny-list. */
887
+ excludeVenues?: string | string[];
888
+
889
+ /** Minimum number of venues required in a cluster. */
890
+ minVenues?: number;
891
+
892
+ /** Require live orderbook coverage on matched edges. */
893
+ withOrderbook?: boolean;
894
+
895
+ /** Only include matches updated after this timestamp. */
896
+ updatedSince?: string | Date;
897
+
898
+ /** Include the pairwise match edges used to build each cluster. */
899
+ includeRawMatches?: boolean;
900
+
901
+ /** Cluster sort order. */
902
+ sort?: MatchedClusterSort;
903
+
904
+ /** Maximum clusters to return. */
905
+ limit?: number;
906
+
907
+ /** Pagination offset. */
908
+ offset?: number;
909
+
910
+ /** Maximum pairwise edges to scan before clustering. */
911
+ edgeLimit?: number;
912
+ }
913
+
914
+ /** Parameters for fetching matched market clusters. */
915
+ export interface FetchMatchedMarketClustersParams extends MatchedClusterFilterParams {
916
+ /** Pass a UnifiedMarket directly instead of marketId/slug/url. */
917
+ market?: UnifiedMarket;
918
+
919
+ /** Anchor the response to a specific market ID. */
920
+ marketId?: string;
921
+
922
+ /** Anchor the response to a specific market slug. */
923
+ slug?: string;
924
+
925
+ /** Anchor the response to a specific market URL. */
926
+ url?: string;
927
+ }
928
+
929
+ /** Parameters for fetching matched event clusters. */
930
+ export interface FetchMatchedEventClustersParams extends MatchedClusterFilterParams {
931
+ /** Pass a UnifiedEvent directly instead of eventId/slug/url. */
932
+ event?: UnifiedEvent;
933
+
934
+ /** Anchor the response to a specific event ID. */
935
+ eventId?: string;
936
+
937
+ /** Anchor the response to a specific event slug. */
938
+ slug?: string;
939
+
940
+ /** Anchor the response to a specific event URL. */
941
+ url?: string;
942
+ }
943
+
944
+ /** Pairwise edge used to build a matched market cluster. */
945
+ export interface MatchedMarketClusterEdge {
946
+ marketAId: string;
947
+ marketBId: string;
948
+ relation: MatchRelation;
949
+ confidence: number;
950
+ }
951
+
952
+ /** Pairwise edge used to build a matched event cluster. */
953
+ export interface MatchedEventClusterEdge extends MatchedMarketClusterEdge {
954
+ eventAId: string;
955
+ eventBId: string;
956
+ }
957
+
958
+ /** Connected cluster of semantically matched markets across venues. */
959
+ export interface MatchedMarketCluster {
960
+ clusterId: string;
961
+ canonicalTitle: string | null;
962
+ category?: string | null;
963
+ relations: MatchRelation[];
964
+ confidence: number;
965
+ volume24h: number;
966
+ markets: UnifiedMarket[];
967
+ rawMatches?: MatchedMarketClusterEdge[];
968
+ }
969
+
970
+ /** Connected cluster of semantically matched events across venues. */
971
+ export interface MatchedEventCluster {
972
+ clusterId: string;
973
+ canonicalTitle: string | null;
974
+ category?: string | null;
975
+ relations: MatchRelation[];
976
+ confidence: number;
977
+ volume24h: number;
978
+ events: UnifiedEvent[];
979
+ rawMatches?: MatchedEventClusterEdge[];
980
+ }
981
+
864
982
  /** Side-by-side price comparison for a matched market. */
865
983
  export interface PriceComparison {
866
984
  /** The matched market. */
package/pmxt/router.ts CHANGED
@@ -11,6 +11,10 @@ import {
11
11
  MatchResult,
12
12
  MatchRelation,
13
13
  EventMatchResult,
14
+ FetchMatchedEventClustersParams,
15
+ FetchMatchedMarketClustersParams,
16
+ MatchedEventCluster,
17
+ MatchedMarketCluster,
14
18
  PriceComparison,
15
19
  ArbitrageOpportunity,
16
20
  UnifiedMarket,
@@ -24,6 +28,8 @@ function convertMarket(raw: any): UnifiedMarket {
24
28
  label: o.label,
25
29
  price: o.price,
26
30
  priceChange24h: o.priceChange24h,
31
+ bestBid: o.bestBid,
32
+ bestAsk: o.bestAsk,
27
33
  metadata: o.metadata,
28
34
  }));
29
35
 
@@ -33,6 +39,8 @@ function convertMarket(raw: any): UnifiedMarket {
33
39
  label: o.label,
34
40
  price: o.price,
35
41
  priceChange24h: o.priceChange24h,
42
+ bestBid: o.bestBid,
43
+ bestAsk: o.bestAsk,
36
44
  metadata: o.metadata,
37
45
  }) : undefined;
38
46
 
@@ -95,6 +103,79 @@ function parseMatchResult(raw: any): MatchResult {
95
103
  };
96
104
  }
97
105
 
106
+ function normalizeQueryValue(value: unknown): unknown {
107
+ if (value instanceof Date) return value.toISOString();
108
+ return value;
109
+ }
110
+
111
+ function appendQuery(query: Record<string, unknown>, key: string, value: unknown): void {
112
+ if (value !== undefined && value !== null && value !== '') {
113
+ query[key] = normalizeQueryValue(value);
114
+ }
115
+ }
116
+
117
+ function addClusterFilters(query: Record<string, unknown>, params: {
118
+ query?: string;
119
+ category?: string;
120
+ relations?: MatchRelation | MatchRelation[] | string;
121
+ relation?: MatchRelation;
122
+ minConfidence?: number;
123
+ venues?: string | string[];
124
+ excludeVenues?: string | string[];
125
+ minVenues?: number;
126
+ withOrderbook?: boolean;
127
+ updatedSince?: string | Date;
128
+ includeRawMatches?: boolean;
129
+ sort?: string;
130
+ limit?: number;
131
+ offset?: number;
132
+ edgeLimit?: number;
133
+ }): void {
134
+ appendQuery(query, 'query', params.query);
135
+ appendQuery(query, 'category', params.category);
136
+ appendQuery(query, 'relations', params.relations);
137
+ appendQuery(query, 'relation', params.relation);
138
+ appendQuery(query, 'minConfidence', params.minConfidence);
139
+ appendQuery(query, 'venues', params.venues);
140
+ appendQuery(query, 'excludeVenues', params.excludeVenues);
141
+ appendQuery(query, 'minVenues', params.minVenues);
142
+ appendQuery(query, 'withOrderbook', params.withOrderbook);
143
+ appendQuery(query, 'updatedSince', params.updatedSince);
144
+ appendQuery(query, 'includeRawMatches', params.includeRawMatches);
145
+ appendQuery(query, 'sort', params.sort);
146
+ appendQuery(query, 'limit', params.limit);
147
+ appendQuery(query, 'offset', params.offset);
148
+ appendQuery(query, 'edgeLimit', params.edgeLimit);
149
+ }
150
+
151
+ function extractCatalogArray(raw: any): any[] {
152
+ if (Array.isArray(raw)) return raw;
153
+ if (Array.isArray(raw?.data)) return raw.data;
154
+ return [];
155
+ }
156
+
157
+ function isUnifiedMarket(value: UnifiedMarket | FetchMatchedMarketClustersParams): value is UnifiedMarket {
158
+ return Boolean(value && 'marketId' in value && 'title' in value);
159
+ }
160
+
161
+ function isUnifiedEvent(value: UnifiedEvent | FetchMatchedEventClustersParams): value is UnifiedEvent {
162
+ return Boolean(value && 'id' in value && 'title' in value && 'markets' in value);
163
+ }
164
+
165
+ function parseMatchedMarketCluster(raw: any): MatchedMarketCluster {
166
+ return {
167
+ ...raw,
168
+ markets: (raw.markets || []).map(convertMarket),
169
+ };
170
+ }
171
+
172
+ function parseMatchedEventCluster(raw: any): MatchedEventCluster {
173
+ return {
174
+ ...raw,
175
+ events: (raw.events || []).map(convertEvent),
176
+ };
177
+ }
178
+
98
179
  /** Options for creating a Router. */
99
180
  export interface RouterOptions {
100
181
  /** PMXT API key (required for hosted mode). */
@@ -262,6 +343,72 @@ export class Router extends Exchange {
262
343
  }
263
344
  }
264
345
 
346
+ /**
347
+ * Fetch connected clusters of semantically matched markets across venues.
348
+ *
349
+ * @param marketOrParams - A UnifiedMarket, or an options object.
350
+ */
351
+ async fetchMatchedMarketClusters(market: UnifiedMarket): Promise<MatchedMarketCluster[]>;
352
+ async fetchMatchedMarketClusters(params?: FetchMatchedMarketClustersParams): Promise<MatchedMarketCluster[]>;
353
+ async fetchMatchedMarketClusters(
354
+ marketOrParams: UnifiedMarket | FetchMatchedMarketClustersParams = {},
355
+ ): Promise<MatchedMarketCluster[]> {
356
+ const params: FetchMatchedMarketClustersParams = isUnifiedMarket(marketOrParams)
357
+ ? { market: marketOrParams }
358
+ : marketOrParams;
359
+ await this.initPromise;
360
+
361
+ const query: Record<string, unknown> = {};
362
+ const marketId = params.marketId ?? params.market?.marketId;
363
+ const slug = params.slug ?? (!marketId ? params.market?.slug : undefined);
364
+ const url = params.url ?? (!marketId && !slug ? params.market?.url : undefined);
365
+ appendQuery(query, 'marketId', marketId);
366
+ appendQuery(query, 'slug', slug);
367
+ appendQuery(query, 'url', url);
368
+ addClusterFilters(query, params);
369
+
370
+ try {
371
+ const raw = await this.catalogReadRequest('/v0/matched-market-clusters', query);
372
+ return extractCatalogArray(raw).map(parseMatchedMarketCluster);
373
+ } catch (error) {
374
+ if (error instanceof Error) throw error;
375
+ throw new Error(`Failed to fetchMatchedMarketClusters: ${error}`);
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Fetch connected clusters of semantically matched events across venues.
381
+ *
382
+ * @param eventOrParams - A UnifiedEvent, or an options object.
383
+ */
384
+ async fetchMatchedEventClusters(event: UnifiedEvent): Promise<MatchedEventCluster[]>;
385
+ async fetchMatchedEventClusters(params?: FetchMatchedEventClustersParams): Promise<MatchedEventCluster[]>;
386
+ async fetchMatchedEventClusters(
387
+ eventOrParams: UnifiedEvent | FetchMatchedEventClustersParams = {},
388
+ ): Promise<MatchedEventCluster[]> {
389
+ const params: FetchMatchedEventClustersParams = isUnifiedEvent(eventOrParams)
390
+ ? { event: eventOrParams }
391
+ : eventOrParams;
392
+ await this.initPromise;
393
+
394
+ const query: Record<string, unknown> = {};
395
+ const eventId = params.eventId ?? params.event?.id;
396
+ const slug = params.slug ?? (!eventId ? params.event?.slug : undefined);
397
+ const url = params.url ?? (!eventId && !slug ? params.event?.url : undefined);
398
+ appendQuery(query, 'eventId', eventId);
399
+ appendQuery(query, 'slug', slug);
400
+ appendQuery(query, 'url', url);
401
+ addClusterFilters(query, params);
402
+
403
+ try {
404
+ const raw = await this.catalogReadRequest('/v0/matched-event-clusters', query);
405
+ return extractCatalogArray(raw).map(parseMatchedEventCluster);
406
+ } catch (error) {
407
+ if (error instanceof Error) throw error;
408
+ throw new Error(`Failed to fetchMatchedEventClusters: ${error}`);
409
+ }
410
+ }
411
+
265
412
  // ------------------------------------------------------------------
266
413
  // Price comparison
267
414
  // ------------------------------------------------------------------