pmxtjs 2.49.0 → 2.49.3

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 (62) hide show
  1. package/README.md +72 -10
  2. package/dist/esm/generated/src/models/Balance.d.ts +6 -0
  3. package/dist/esm/generated/src/models/Balance.js +2 -0
  4. package/dist/esm/generated/src/models/BuiltOrder.d.ts +6 -0
  5. package/dist/esm/generated/src/models/BuiltOrder.js +2 -0
  6. package/dist/esm/generated/src/models/ErrorDetail.d.ts +58 -2
  7. package/dist/esm/generated/src/models/ErrorDetail.js +37 -0
  8. package/dist/esm/generated/src/models/ExchangeOptions.d.ts +70 -0
  9. package/dist/esm/generated/src/models/ExchangeOptions.js +53 -0
  10. package/dist/esm/generated/src/models/MatchedMarketPair.d.ts +1 -1
  11. package/dist/esm/generated/src/models/Order.d.ts +18 -0
  12. package/dist/esm/generated/src/models/Order.js +6 -0
  13. package/dist/esm/generated/src/models/Position.d.ts +33 -9
  14. package/dist/esm/generated/src/models/Position.js +12 -12
  15. package/dist/esm/generated/src/models/UnifiedSeries.d.ts +4 -4
  16. package/dist/esm/generated/src/models/UserTrade.d.ts +18 -0
  17. package/dist/esm/generated/src/models/UserTrade.js +6 -0
  18. package/dist/esm/generated/src/models/index.d.ts +1 -0
  19. package/dist/esm/generated/src/models/index.js +1 -0
  20. package/dist/esm/pmxt/client.d.ts +3 -0
  21. package/dist/esm/pmxt/client.js +40 -5
  22. package/dist/generated/src/models/Balance.d.ts +6 -0
  23. package/dist/generated/src/models/Balance.js +2 -0
  24. package/dist/generated/src/models/BuiltOrder.d.ts +6 -0
  25. package/dist/generated/src/models/BuiltOrder.js +2 -0
  26. package/dist/generated/src/models/ErrorDetail.d.ts +58 -2
  27. package/dist/generated/src/models/ErrorDetail.js +38 -0
  28. package/dist/generated/src/models/ExchangeOptions.d.ts +70 -0
  29. package/dist/generated/src/models/ExchangeOptions.js +60 -0
  30. package/dist/generated/src/models/MatchedMarketPair.d.ts +1 -1
  31. package/dist/generated/src/models/Order.d.ts +18 -0
  32. package/dist/generated/src/models/Order.js +6 -0
  33. package/dist/generated/src/models/Position.d.ts +33 -9
  34. package/dist/generated/src/models/Position.js +12 -12
  35. package/dist/generated/src/models/UnifiedSeries.d.ts +4 -4
  36. package/dist/generated/src/models/UserTrade.d.ts +18 -0
  37. package/dist/generated/src/models/UserTrade.js +6 -0
  38. package/dist/generated/src/models/index.d.ts +1 -0
  39. package/dist/generated/src/models/index.js +1 -0
  40. package/dist/pmxt/client.d.ts +3 -0
  41. package/dist/pmxt/client.js +40 -5
  42. package/generated/.openapi-generator/FILES +2 -0
  43. package/generated/docs/Balance.md +2 -0
  44. package/generated/docs/BuiltOrder.md +2 -0
  45. package/generated/docs/ErrorDetail.md +9 -0
  46. package/generated/docs/ExchangeOptions.md +47 -0
  47. package/generated/docs/Order.md +6 -0
  48. package/generated/docs/Position.md +9 -0
  49. package/generated/docs/UserTrade.md +6 -0
  50. package/generated/package.json +1 -1
  51. package/generated/src/models/Balance.ts +8 -0
  52. package/generated/src/models/BuiltOrder.ts +8 -0
  53. package/generated/src/models/ErrorDetail.ts +67 -2
  54. package/generated/src/models/ExchangeOptions.ts +115 -0
  55. package/generated/src/models/MatchedMarketPair.ts +1 -1
  56. package/generated/src/models/Order.ts +24 -0
  57. package/generated/src/models/Position.ts +45 -17
  58. package/generated/src/models/UnifiedSeries.ts +4 -4
  59. package/generated/src/models/UserTrade.ts +24 -0
  60. package/generated/src/models/index.ts +1 -0
  61. package/package.json +2 -2
  62. package/pmxt/client.ts +47 -7
@@ -0,0 +1,115 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /**
4
+ * PMXT Sidecar API
5
+ * A unified local sidecar API for prediction markets (Polymarket, Kalshi, Limitless). This API acts as a JSON-RPC-style gateway. Each endpoint corresponds to a specific method on the generic exchange implementation.
6
+ *
7
+ * The version of the OpenAPI document: 0.4.4
8
+ *
9
+ *
10
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
11
+ * https://openapi-generator.tech
12
+ * Do not edit the class manually.
13
+ */
14
+
15
+ import { mapValues } from '../runtime';
16
+ /**
17
+ * Constructor-level options for venue clients (Polymarket, Kalshi, Opinion, etc.).
18
+ * Hosted mode is the default when pmxtApiKey is set; otherwise the SDK runs against
19
+ * a local sidecar with venue credentials.
20
+ * @export
21
+ * @interface ExchangeOptions
22
+ */
23
+ export interface ExchangeOptions {
24
+ /**
25
+ * PMXT customer API key. When set, the SDK routes to api.pmxt.dev (catalog) and trade.pmxt.dev (trading). Get one at pmxt.dev/dashboard.
26
+ * @type {string}
27
+ * @memberof ExchangeOptions
28
+ */
29
+ pmxtApiKey?: string;
30
+ /**
31
+ * EVM wallet address for hosted reads/writes. Required for endpoints that operate on a wallet (balances, positions, trades, open orders).
32
+ * @type {string}
33
+ * @memberof ExchangeOptions
34
+ */
35
+ walletAddress?: string | null;
36
+ /**
37
+ * Optional pre-built signer for hosted writes. If absent and privateKey is set, the SDK auto-wraps privateKey into a signer.
38
+ * @type {object}
39
+ * @memberof ExchangeOptions
40
+ */
41
+ signer?: object | null;
42
+ /**
43
+ * Private key. In hosted mode, used to derive an EIP-712 signer for writes (wraps into EthAccountSigner/EthersSigner). In self-hosted mode, used as the venue credential directly.
44
+ * @type {string}
45
+ * @memberof ExchangeOptions
46
+ */
47
+ privateKey?: string | null;
48
+ /**
49
+ * Explicit base URL override. When unset, the SDK uses api.pmxt.dev when pmxtApiKey is set, or the local sidecar otherwise.
50
+ * @type {string}
51
+ * @memberof ExchangeOptions
52
+ */
53
+ baseUrl?: string | null;
54
+ /**
55
+ * Venue-side API key (e.g. Polymarket CLOB key). Only relevant for self-hosted mode.
56
+ * @type {string}
57
+ * @memberof ExchangeOptions
58
+ */
59
+ apiKey?: string | null;
60
+ /**
61
+ * Auto-start the local sidecar when running self-hosted. Defaults to true when no pmxtApiKey is set, false when hosted.
62
+ * @type {boolean}
63
+ * @memberof ExchangeOptions
64
+ */
65
+ autoStartServer?: boolean | null;
66
+ }
67
+
68
+ /**
69
+ * Check if a given object implements the ExchangeOptions interface.
70
+ */
71
+ export function instanceOfExchangeOptions(value: object): value is ExchangeOptions {
72
+ return true;
73
+ }
74
+
75
+ export function ExchangeOptionsFromJSON(json: any): ExchangeOptions {
76
+ return ExchangeOptionsFromJSONTyped(json, false);
77
+ }
78
+
79
+ export function ExchangeOptionsFromJSONTyped(json: any, ignoreDiscriminator: boolean): ExchangeOptions {
80
+ if (json == null) {
81
+ return json;
82
+ }
83
+ return {
84
+
85
+ 'pmxtApiKey': json['pmxtApiKey'] == null ? undefined : json['pmxtApiKey'],
86
+ 'walletAddress': json['walletAddress'] == null ? undefined : json['walletAddress'],
87
+ 'signer': json['signer'] == null ? undefined : json['signer'],
88
+ 'privateKey': json['privateKey'] == null ? undefined : json['privateKey'],
89
+ 'baseUrl': json['baseUrl'] == null ? undefined : json['baseUrl'],
90
+ 'apiKey': json['apiKey'] == null ? undefined : json['apiKey'],
91
+ 'autoStartServer': json['autoStartServer'] == null ? undefined : json['autoStartServer'],
92
+ };
93
+ }
94
+
95
+ export function ExchangeOptionsToJSON(json: any): ExchangeOptions {
96
+ return ExchangeOptionsToJSONTyped(json, false);
97
+ }
98
+
99
+ export function ExchangeOptionsToJSONTyped(value?: ExchangeOptions | null, ignoreDiscriminator: boolean = false): any {
100
+ if (value == null) {
101
+ return value;
102
+ }
103
+
104
+ return {
105
+
106
+ 'pmxtApiKey': value['pmxtApiKey'],
107
+ 'walletAddress': value['walletAddress'],
108
+ 'signer': value['signer'],
109
+ 'privateKey': value['privateKey'],
110
+ 'baseUrl': value['baseUrl'],
111
+ 'apiKey': value['apiKey'],
112
+ 'autoStartServer': value['autoStartServer'],
113
+ };
114
+ }
115
+
@@ -82,7 +82,7 @@ export interface MatchedMarketPair {
82
82
  */
83
83
  confidence?: number;
84
84
  /**
85
- *
85
+ * Why the two markets were matched.
86
86
  * @type {string}
87
87
  * @memberof MatchedMarketPair
88
88
  */
@@ -103,6 +103,24 @@ export interface Order {
103
103
  * @memberof Order
104
104
  */
105
105
  feeRateBps?: number;
106
+ /**
107
+ * Populated in hosted mode after on-chain settlement; null for local-mode and for non-on-chain venues.
108
+ * @type {string}
109
+ * @memberof Order
110
+ */
111
+ txHash?: string | null;
112
+ /**
113
+ * Populated in hosted mode after on-chain settlement; null for local-mode and for non-on-chain venues.
114
+ * @type {string}
115
+ * @memberof Order
116
+ */
117
+ chain?: string | null;
118
+ /**
119
+ * Populated in hosted mode after on-chain settlement; null for local-mode and for non-on-chain venues.
120
+ * @type {number}
121
+ * @memberof Order
122
+ */
123
+ blockNumber?: number | null;
106
124
  }
107
125
 
108
126
 
@@ -178,6 +196,9 @@ export function OrderFromJSONTyped(json: any, ignoreDiscriminator: boolean): Ord
178
196
  'timestamp': json['timestamp'],
179
197
  'fee': json['fee'] == null ? undefined : json['fee'],
180
198
  'feeRateBps': json['feeRateBps'] == null ? undefined : json['feeRateBps'],
199
+ 'txHash': json['txHash'] == null ? undefined : json['txHash'],
200
+ 'chain': json['chain'] == null ? undefined : json['chain'],
201
+ 'blockNumber': json['blockNumber'] == null ? undefined : json['blockNumber'],
181
202
  };
182
203
  }
183
204
 
@@ -206,6 +227,9 @@ export function OrderToJSONTyped(value?: Order | null, ignoreDiscriminator: bool
206
227
  'timestamp': value['timestamp'],
207
228
  'fee': value['fee'],
208
229
  'feeRateBps': value['feeRateBps'],
230
+ 'txHash': value['txHash'],
231
+ 'chain': value['chain'],
232
+ 'blockNumber': value['blockNumber'],
209
233
  };
210
234
  }
211
235
 
@@ -14,7 +14,7 @@
14
14
 
15
15
  import { mapValues } from '../runtime';
16
16
  /**
17
- *
17
+ * A current position in a market. In hosted mode, `outcomeLabel`, `entryPrice`, `currentPrice` and `unrealizedPnL` may be null when the server cannot derive them (e.g. `with_mtm=false` or no fill history). Venue-direct callers continue to populate every field.
18
18
  * @export
19
19
  * @interface Position
20
20
  */
@@ -32,11 +32,11 @@ export interface Position {
32
32
  */
33
33
  outcomeId: string;
34
34
  /**
35
- * Human-readable label for the outcome held.
35
+ * Human-readable label for the outcome held. Optional in hosted mode.
36
36
  * @type {string}
37
37
  * @memberof Position
38
38
  */
39
- outcomeLabel: string;
39
+ outcomeLabel?: string | null;
40
40
  /**
41
41
  * Positive for long, negative for short
42
42
  * @type {number}
@@ -44,29 +44,53 @@ export interface Position {
44
44
  */
45
45
  size: number;
46
46
  /**
47
- * Average entry price for the position (probability between 0.0 and 1.0).
47
+ * Average entry price for the position (probability between 0.0 and 1.0). Optional in hosted mode when no fill history is available.
48
+ * @type {number}
49
+ * @memberof Position
50
+ */
51
+ entryPrice?: number | null;
52
+ /**
53
+ * Current mark price for the position (probability between 0.0 and 1.0). Optional in hosted mode when mark-to-market data is unavailable.
48
54
  * @type {number}
49
55
  * @memberof Position
50
56
  */
51
- entryPrice: number;
57
+ currentPrice?: number | null;
52
58
  /**
53
- * Current mark price for the position (probability between 0.0 and 1.0).
59
+ * Current market value of the position (size * currentPrice). Null when currentPrice is unavailable.
54
60
  * @type {number}
55
61
  * @memberof Position
56
62
  */
57
- currentPrice: number;
63
+ currentValue?: number | null;
58
64
  /**
59
- * Unrealized profit or loss at the current price (USD).
65
+ * Unrealized profit or loss at the current price (USD). Optional in hosted mode when mark-to-market data is unavailable.
60
66
  * @type {number}
61
67
  * @memberof Position
62
68
  */
63
- unrealizedPnL: number;
69
+ unrealizedPnL?: number | null;
64
70
  /**
65
71
  * Realized profit or loss booked so far (USD).
66
72
  * @type {number}
67
73
  * @memberof Position
68
74
  */
69
75
  realizedPnL?: number;
76
+ /**
77
+ * Populated in hosted mode after on-chain settlement (from the last fill); null for local-mode and for non-on-chain venues.
78
+ * @type {string}
79
+ * @memberof Position
80
+ */
81
+ txHash?: string | null;
82
+ /**
83
+ * Populated in hosted mode after on-chain settlement (from the last fill); null for local-mode and for non-on-chain venues.
84
+ * @type {string}
85
+ * @memberof Position
86
+ */
87
+ chain?: string | null;
88
+ /**
89
+ * Populated in hosted mode after on-chain settlement (from the last fill); null for local-mode and for non-on-chain venues.
90
+ * @type {number}
91
+ * @memberof Position
92
+ */
93
+ blockNumber?: number | null;
70
94
  }
71
95
 
72
96
  /**
@@ -75,11 +99,7 @@ export interface Position {
75
99
  export function instanceOfPosition(value: object): value is Position {
76
100
  if (!('marketId' in value) || value['marketId'] === undefined) return false;
77
101
  if (!('outcomeId' in value) || value['outcomeId'] === undefined) return false;
78
- if (!('outcomeLabel' in value) || value['outcomeLabel'] === undefined) return false;
79
102
  if (!('size' in value) || value['size'] === undefined) return false;
80
- if (!('entryPrice' in value) || value['entryPrice'] === undefined) return false;
81
- if (!('currentPrice' in value) || value['currentPrice'] === undefined) return false;
82
- if (!('unrealizedPnL' in value) || value['unrealizedPnL'] === undefined) return false;
83
103
  return true;
84
104
  }
85
105
 
@@ -95,12 +115,16 @@ export function PositionFromJSONTyped(json: any, ignoreDiscriminator: boolean):
95
115
 
96
116
  'marketId': json['marketId'],
97
117
  'outcomeId': json['outcomeId'],
98
- 'outcomeLabel': json['outcomeLabel'],
118
+ 'outcomeLabel': json['outcomeLabel'] == null ? undefined : json['outcomeLabel'],
99
119
  'size': json['size'],
100
- 'entryPrice': json['entryPrice'],
101
- 'currentPrice': json['currentPrice'],
102
- 'unrealizedPnL': json['unrealizedPnL'],
120
+ 'entryPrice': json['entryPrice'] == null ? undefined : json['entryPrice'],
121
+ 'currentPrice': json['currentPrice'] == null ? undefined : json['currentPrice'],
122
+ 'currentValue': json['currentValue'] == null ? undefined : json['currentValue'],
123
+ 'unrealizedPnL': json['unrealizedPnL'] == null ? undefined : json['unrealizedPnL'],
103
124
  'realizedPnL': json['realizedPnL'] == null ? undefined : json['realizedPnL'],
125
+ 'txHash': json['txHash'] == null ? undefined : json['txHash'],
126
+ 'chain': json['chain'] == null ? undefined : json['chain'],
127
+ 'blockNumber': json['blockNumber'] == null ? undefined : json['blockNumber'],
104
128
  };
105
129
  }
106
130
 
@@ -121,8 +145,12 @@ export function PositionToJSONTyped(value?: Position | null, ignoreDiscriminator
121
145
  'size': value['size'],
122
146
  'entryPrice': value['entryPrice'],
123
147
  'currentPrice': value['currentPrice'],
148
+ 'currentValue': value['currentValue'],
124
149
  'unrealizedPnL': value['unrealizedPnL'],
125
150
  'realizedPnL': value['realizedPnL'],
151
+ 'txHash': value['txHash'],
152
+ 'chain': value['chain'],
153
+ 'blockNumber': value['blockNumber'],
126
154
  };
127
155
  }
128
156
 
@@ -52,13 +52,13 @@ export interface UnifiedSeries {
52
52
  */
53
53
  title: string;
54
54
  /**
55
- *
55
+ * Long-form series description.
56
56
  * @type {string}
57
57
  * @memberof UnifiedSeries
58
58
  */
59
59
  description?: string | null;
60
60
  /**
61
- *
61
+ * Recurrence cadence the venue reports ('daily', 'weekly', 'annual', ...).
62
62
  * @type {string}
63
63
  * @memberof UnifiedSeries
64
64
  */
@@ -70,13 +70,13 @@ export interface UnifiedSeries {
70
70
  */
71
71
  events?: Array<UnifiedEvent>;
72
72
  /**
73
- *
73
+ * Canonical venue URL for the series.
74
74
  * @type {string}
75
75
  * @memberof UnifiedSeries
76
76
  */
77
77
  url?: string | null;
78
78
  /**
79
- *
79
+ * Venue-hosted image.
80
80
  * @type {string}
81
81
  * @memberof UnifiedSeries
82
82
  */
@@ -61,6 +61,24 @@ export interface UserTrade {
61
61
  * @memberof UserTrade
62
62
  */
63
63
  orderId?: string;
64
+ /**
65
+ * Populated in hosted mode after on-chain settlement; null for local-mode and for non-on-chain venues.
66
+ * @type {string}
67
+ * @memberof UserTrade
68
+ */
69
+ txHash?: string | null;
70
+ /**
71
+ * Populated in hosted mode after on-chain settlement; null for local-mode and for non-on-chain venues.
72
+ * @type {string}
73
+ * @memberof UserTrade
74
+ */
75
+ chain?: string | null;
76
+ /**
77
+ * Populated in hosted mode after on-chain settlement; null for local-mode and for non-on-chain venues.
78
+ * @type {number}
79
+ * @memberof UserTrade
80
+ */
81
+ blockNumber?: number | null;
64
82
  }
65
83
 
66
84
 
@@ -104,6 +122,9 @@ export function UserTradeFromJSONTyped(json: any, ignoreDiscriminator: boolean):
104
122
  'side': json['side'],
105
123
  'outcomeId': json['outcomeId'] == null ? undefined : json['outcomeId'],
106
124
  'orderId': json['orderId'] == null ? undefined : json['orderId'],
125
+ 'txHash': json['txHash'] == null ? undefined : json['txHash'],
126
+ 'chain': json['chain'] == null ? undefined : json['chain'],
127
+ 'blockNumber': json['blockNumber'] == null ? undefined : json['blockNumber'],
107
128
  };
108
129
  }
109
130
 
@@ -125,6 +146,9 @@ export function UserTradeToJSONTyped(value?: UserTrade | null, ignoreDiscriminat
125
146
  'side': value['side'],
126
147
  'outcomeId': value['outcomeId'],
127
148
  'orderId': value['orderId'],
149
+ 'txHash': value['txHash'],
150
+ 'chain': value['chain'],
151
+ 'blockNumber': value['blockNumber'],
128
152
  };
129
153
  }
130
154
 
@@ -23,6 +23,7 @@ export * from './EventFilterCriteriaTotalVolume';
23
23
  export * from './EventMatchResult';
24
24
  export * from './ExchangeCredentials';
25
25
  export * from './ExchangeCredentialsSignatureType';
26
+ export * from './ExchangeOptions';
26
27
  export * from './ExecutionPriceResult';
27
28
  export * from './FeedFetchHistoricalPrices200Response';
28
29
  export * from './FeedFetchOHLCV200Response';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxtjs",
3
- "version": "2.49.0",
3
+ "version": "2.49.3",
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.49.0",
46
+ "pmxt-core": "2.49.3",
47
47
  "ws": "^8.18.0"
48
48
  },
49
49
  "peerDependencies": {
package/pmxt/client.ts CHANGED
@@ -300,6 +300,17 @@ export abstract class Exchange {
300
300
  "opinion",
301
301
  ]);
302
302
 
303
+ // Match a canonical 8-4-4-4-12 UUID string. The hosted catalog emits
304
+ // UUIDs in this exact shape, so a regex is both faster and stricter
305
+ // than `crypto.randomUUID()`-style parsing.
306
+ private static readonly _UUID_RE =
307
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
308
+
309
+ /** True iff `value` parses as a canonical catalog UUID string. */
310
+ protected static _isCatalogUuid(value: string): boolean {
311
+ return Exchange._UUID_RE.test(value);
312
+ }
313
+
303
314
  // Public so structural interfaces like `HostedClientLike`
304
315
  // (./hosted-routing) can read the venue name and hosted credentials
305
316
  // without violating protected-access on this base class.
@@ -2434,8 +2445,8 @@ export abstract class Exchange {
2434
2445
  private _hostedBuildOrderBody(
2435
2446
  params: CreateOrderParams & { outcome?: MarketOutcome },
2436
2447
  ): Record<string, unknown> {
2437
- let marketId = params.marketId;
2438
- let outcomeId = params.outcomeId;
2448
+ let marketId: string | undefined = params.marketId;
2449
+ let outcomeId: string | undefined = params.outcomeId;
2439
2450
 
2440
2451
  if (params.outcome) {
2441
2452
  if (marketId !== undefined || outcomeId !== undefined) {
@@ -2444,15 +2455,23 @@ export abstract class Exchange {
2444
2455
  );
2445
2456
  }
2446
2457
  const outcome: MarketOutcome = params.outcome;
2447
- if (!outcome.marketId) {
2458
+ if (!outcome.outcomeId) {
2448
2459
  throw new InvalidOrder(
2449
- "outcome.marketId is not set; ensure the outcome comes from a fetched market",
2460
+ "outcome.outcomeId is not set; ensure the outcome comes from a fetched market",
2450
2461
  );
2451
2462
  }
2452
- marketId = outcome.marketId;
2463
+ // marketId is optional in hosted mode -- backend derives it from
2464
+ // outcomeId (catalog UUID). Forward it when present for backcompat.
2465
+ marketId = outcome.marketId || undefined;
2453
2466
  outcomeId = outcome.outcomeId;
2454
2467
  }
2455
2468
 
2469
+ if (!outcomeId) {
2470
+ throw new InvalidOrder(
2471
+ "outcomeId is required (or pass an 'outcome' from a fetched market)",
2472
+ );
2473
+ }
2474
+
2456
2475
  const side = String(params.side);
2457
2476
  const orderType = String(params.type ?? "market");
2458
2477
  const denom = (params as unknown as Record<string, unknown>)["denom"] as
@@ -2493,15 +2512,36 @@ export abstract class Exchange {
2493
2512
  // to6dec throws InvalidOrder for sub-micro precision.
2494
2513
  const amount6dec = to6dec(params.amount as number).toString();
2495
2514
 
2515
+ // The supplied outcomeId may be a catalog UUID OR a venue-native id
2516
+ // (e.g. a Polymarket tokenId or an Opinion market hash). Catalog
2517
+ // UUIDs are forwarded as `outcome_id`; venue-native ids are
2518
+ // forwarded as `(venue, venue_outcome_id)` so the backend resolver
2519
+ // picks the right path. Either shape is accepted by the v0 trading
2520
+ // API.
2496
2521
  const body: Record<string, unknown> = {
2497
- market_id: marketId,
2498
- outcome_id: outcomeId,
2499
2522
  side,
2500
2523
  order_type: orderType,
2501
2524
  denom: resolvedDenom,
2502
2525
  amount: params.amount,
2503
2526
  amount_6dec: amount6dec,
2504
2527
  };
2528
+ if (Exchange._isCatalogUuid(outcomeId)) {
2529
+ body["outcome_id"] = outcomeId;
2530
+ // market_id is optional in hosted mode: backend derives it
2531
+ // from outcome_id (UUID) when omitted. Forward only when the
2532
+ // caller supplied a non-empty UUID -- "absent" and "null" are
2533
+ // not equivalent under some Pydantic configs on the backend.
2534
+ if (marketId && Exchange._isCatalogUuid(marketId)) {
2535
+ body["market_id"] = marketId;
2536
+ }
2537
+ } else {
2538
+ // Venue-native form: backend resolves the row from
2539
+ // (source_exchange, pmxt_id). marketId from a venue client is
2540
+ // itself venue-native and would fail backend UUID validation
2541
+ // if forwarded -- suppress it.
2542
+ body["venue"] = this.exchangeName;
2543
+ body["venue_outcome_id"] = outcomeId;
2544
+ }
2505
2545
 
2506
2546
  if (params.price !== undefined) body["price"] = params.price;
2507
2547
  const extra = params as unknown as Record<string, unknown>;