pmxtjs 2.51.1 → 2.51.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 (144) hide show
  1. package/README.md +14 -5
  2. package/dist/esm/generated/src/apis/DefaultApi.d.ts +6 -0
  3. package/dist/esm/generated/src/apis/DefaultApi.js +6 -0
  4. package/dist/esm/generated/src/models/CreateOrderParams.d.ts +20 -0
  5. package/dist/esm/generated/src/models/CreateOrderParams.js +11 -0
  6. package/dist/esm/index.d.ts +3 -2
  7. package/dist/esm/index.js +3 -2
  8. package/dist/esm/pmxt/client.d.ts +61 -12
  9. package/dist/esm/pmxt/client.js +126 -21
  10. package/dist/esm/pmxt/escrow.js +109 -10
  11. package/dist/esm/pmxt/hosted-typed-data.d.ts +1 -1
  12. package/dist/esm/pmxt/hosted-typed-data.js +14 -0
  13. package/dist/esm/pmxt/models.d.ts +17 -0
  14. package/dist/esm/pmxt/server-manager.js +5 -3
  15. package/dist/generated/src/apis/DefaultApi.d.ts +6 -0
  16. package/dist/generated/src/apis/DefaultApi.js +6 -0
  17. package/dist/generated/src/models/CreateOrderParams.d.ts +20 -0
  18. package/dist/generated/src/models/CreateOrderParams.js +12 -1
  19. package/dist/index.d.ts +3 -2
  20. package/dist/index.js +3 -1
  21. package/dist/pmxt/client.d.ts +61 -12
  22. package/dist/pmxt/client.js +126 -20
  23. package/dist/pmxt/escrow.js +109 -10
  24. package/dist/pmxt/hosted-typed-data.d.ts +1 -1
  25. package/dist/pmxt/hosted-typed-data.js +14 -0
  26. package/dist/pmxt/models.d.ts +17 -0
  27. package/dist/pmxt/server-manager.js +5 -3
  28. package/generated/docs/ArbitrageOpportunity.md +0 -29
  29. package/generated/docs/Balance.md +0 -25
  30. package/generated/docs/BaseRequest.md +0 -21
  31. package/generated/docs/BaseResponse.md +0 -22
  32. package/generated/docs/BuildOrder200Response.md +0 -23
  33. package/generated/docs/BuildOrderRequest.md +0 -22
  34. package/generated/docs/BuiltOrder.md +0 -26
  35. package/generated/docs/BuiltOrderTx.md +0 -24
  36. package/generated/docs/CancelOrderRequest.md +0 -22
  37. package/generated/docs/CloseRequest.md +0 -22
  38. package/generated/docs/CompareMarketPrices200Response.md +0 -23
  39. package/generated/docs/CompareMarketPricesRequest.md +0 -22
  40. package/generated/docs/CreateOrder200Response.md +0 -23
  41. package/generated/docs/CreateOrderParams.md +2 -30
  42. package/generated/docs/CreateOrderRequest.md +0 -22
  43. package/generated/docs/DataFeedsApi.md +0 -329
  44. package/generated/docs/DefaultApi.md +3 -1438
  45. package/generated/docs/ErrorDetail.md +0 -25
  46. package/generated/docs/ErrorResponse.md +0 -22
  47. package/generated/docs/EventFetchParams.md +0 -35
  48. package/generated/docs/EventFilterCriteria.md +0 -26
  49. package/generated/docs/EventFilterCriteriaTotalVolume.md +0 -22
  50. package/generated/docs/EventMatchResult.md +0 -22
  51. package/generated/docs/ExchangeCredentials.md +0 -29
  52. package/generated/docs/ExchangeCredentialsSignatureType.md +0 -20
  53. package/generated/docs/ExchangeOptions.md +0 -27
  54. package/generated/docs/ExecutionPriceResult.md +0 -23
  55. package/generated/docs/FeedFetchHistoricalPrices200Response.md +0 -22
  56. package/generated/docs/FeedFetchOHLCV200Response.md +0 -22
  57. package/generated/docs/FeedFetchOracleHistory200Response.md +0 -22
  58. package/generated/docs/FeedFetchOracleRound200Response.md +0 -22
  59. package/generated/docs/FeedFetchOrderBook200Response.md +0 -22
  60. package/generated/docs/FeedFetchOrderBook200ResponseData.md +0 -25
  61. package/generated/docs/FeedFetchTicker200Response.md +0 -22
  62. package/generated/docs/FeedFetchTickers200Response.md +0 -22
  63. package/generated/docs/FeedList200Response.md +0 -22
  64. package/generated/docs/FeedLoadMarkets200Response.md +0 -22
  65. package/generated/docs/FeedMarket.md +0 -27
  66. package/generated/docs/FeedOracleRound.md +0 -28
  67. package/generated/docs/FeedTicker.md +0 -42
  68. package/generated/docs/FetchArbitrage200Response.md +0 -23
  69. package/generated/docs/FetchArbitrageParams.md +0 -24
  70. package/generated/docs/FetchBalance200Response.md +0 -23
  71. package/generated/docs/FetchEvent200Response.md +0 -23
  72. package/generated/docs/FetchEventMatches200Response.md +0 -23
  73. package/generated/docs/FetchEventMatchesParams.md +0 -29
  74. package/generated/docs/FetchEvents200Response.md +0 -23
  75. package/generated/docs/FetchEventsPaginated200Response.md +0 -23
  76. package/generated/docs/FetchMarket200Response.md +0 -23
  77. package/generated/docs/FetchMarketMatches200Response.md +0 -23
  78. package/generated/docs/FetchMarketMatchesParams.md +0 -32
  79. package/generated/docs/FetchMarkets200Response.md +0 -23
  80. package/generated/docs/FetchMarketsPaginated200Response.md +0 -23
  81. package/generated/docs/FetchMatchedMarkets200Response.md +0 -23
  82. package/generated/docs/FetchMatchedMarketsParams.md +0 -24
  83. package/generated/docs/FetchMyTrades200Response.md +0 -23
  84. package/generated/docs/FetchOHLCV200Response.md +0 -23
  85. package/generated/docs/FetchOpenOrders200Response.md +0 -23
  86. package/generated/docs/FetchOrderBook200Response.md +0 -23
  87. package/generated/docs/FetchOrderBookParams.md +0 -24
  88. package/generated/docs/FetchOrderBooks200Response.md +0 -23
  89. package/generated/docs/FetchOrderBooksRequest.md +0 -22
  90. package/generated/docs/FetchPositions200Response.md +0 -23
  91. package/generated/docs/FetchSeries200Response.md +0 -23
  92. package/generated/docs/FetchTrades200Response.md +0 -23
  93. package/generated/docs/FilterEventsRequest.md +0 -22
  94. package/generated/docs/FilterEventsRequestArgsInner.md +0 -26
  95. package/generated/docs/FilterMarketsRequest.md +0 -22
  96. package/generated/docs/FilterMarketsRequestArgsInner.md +0 -31
  97. package/generated/docs/GetExecutionPrice200Response.md +0 -23
  98. package/generated/docs/GetExecutionPriceDetailed200Response.md +0 -23
  99. package/generated/docs/GetExecutionPriceDetailedRequest.md +0 -22
  100. package/generated/docs/GetExecutionPriceRequest.md +0 -22
  101. package/generated/docs/GetExecutionPriceRequestArgsInner.md +0 -27
  102. package/generated/docs/HealthCheck200Response.md +0 -22
  103. package/generated/docs/HistoryFilterParams.md +0 -24
  104. package/generated/docs/LoadMarkets200Response.md +0 -23
  105. package/generated/docs/LoadMarketsRequest.md +0 -22
  106. package/generated/docs/MarketFilterCriteria.md +0 -31
  107. package/generated/docs/MarketFilterCriteriaLiquidity.md +0 -22
  108. package/generated/docs/MarketFilterCriteriaOpenInterest.md +0 -22
  109. package/generated/docs/MarketFilterCriteriaPrice.md +0 -23
  110. package/generated/docs/MarketFilterCriteriaResolutionDate.md +0 -22
  111. package/generated/docs/MarketFilterCriteriaVolume.md +0 -22
  112. package/generated/docs/MarketFilterCriteriaVolume24h.md +0 -22
  113. package/generated/docs/MarketFilterParams.md +0 -34
  114. package/generated/docs/MarketOutcome.md +0 -26
  115. package/generated/docs/MatchResult.md +0 -27
  116. package/generated/docs/MatchedMarketPair.md +0 -30
  117. package/generated/docs/MyTradesParams.md +0 -26
  118. package/generated/docs/OHLCVParams.md +0 -24
  119. package/generated/docs/Order.md +0 -37
  120. package/generated/docs/OrderBook.md +0 -27
  121. package/generated/docs/OrderHistoryParams.md +0 -25
  122. package/generated/docs/OrderLevel.md +0 -23
  123. package/generated/docs/PaginatedEventsResult.md +0 -23
  124. package/generated/docs/PaginatedMarketsResult.md +0 -23
  125. package/generated/docs/Position.md +0 -32
  126. package/generated/docs/PriceCandle.md +0 -26
  127. package/generated/docs/PriceComparison.md +0 -27
  128. package/generated/docs/SubmitOrderRequest.md +0 -22
  129. package/generated/docs/Trade.md +0 -26
  130. package/generated/docs/TradesParams.md +0 -23
  131. package/generated/docs/UnifiedEvent.md +0 -33
  132. package/generated/docs/UnifiedMarket.md +0 -44
  133. package/generated/docs/UnifiedSeries.md +0 -31
  134. package/generated/docs/UserTrade.md +0 -32
  135. package/generated/package.json +1 -1
  136. package/generated/src/apis/DefaultApi.ts +6 -0
  137. package/generated/src/models/CreateOrderParams.ts +25 -0
  138. package/index.ts +3 -2
  139. package/package.json +2 -2
  140. package/pmxt/client.ts +153 -28
  141. package/pmxt/escrow.ts +131 -11
  142. package/pmxt/hosted-typed-data.ts +17 -1
  143. package/pmxt/models.ts +20 -0
  144. package/pmxt/server-manager.ts +5 -3
package/pmxt/client.ts CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  import {
18
18
  Balance,
19
19
  BuiltOrder,
20
- CreateOrderParams,
20
+ CreateOrderInput,
21
21
  EventFetchParams,
22
22
  EventFilterCriteria,
23
23
  EventFilterFunction,
@@ -52,7 +52,7 @@ import {
52
52
  import { ServerManager } from "./server-manager.js";
53
53
  import { buildArgsWithOptionalOptions } from "./args.js";
54
54
  import { PmxtError, fromServerError, InvalidOrder, NotSupported } from "./errors.js";
55
- import { LOCAL_URL, resolvePmxtBaseUrl } from "./constants.js";
55
+ import { ENV, LOCAL_URL, resolvePmxtBaseUrl } from "./constants.js";
56
56
  import { SidecarWsClient } from "./ws-client.js";
57
57
  import { logger } from "./logger.js";
58
58
 
@@ -82,6 +82,7 @@ import {
82
82
  validateEconomics,
83
83
  verifySignature,
84
84
  } from "./hosted-typed-data.js";
85
+ import type { HostedRoute } from "./hosted-typed-data.js";
85
86
  import type { Signer, TypedData } from "./signers.js";
86
87
  import { signerFromPrivateKey, EthersSigner } from "./signers.js";
87
88
  import { Escrow } from "./escrow.js";
@@ -351,6 +352,7 @@ export abstract class Exchange {
351
352
  private _wsClient: SidecarWsClient | null = null;
352
353
  /** Sticky flag: true if the sidecar /ws endpoint is unavailable. */
353
354
  private _wsUnsupported: boolean = false;
355
+ private readonly _useSidecarLockBaseUrl: boolean;
354
356
 
355
357
  constructor(exchangeName: string, options: ExchangeOptions = {}) {
356
358
  this.exchangeName = exchangeName.toLowerCase();
@@ -370,6 +372,11 @@ export abstract class Exchange {
370
372
  const baseUrl = resolved.baseUrl;
371
373
  this.pmxtApiKey = resolved.pmxtApiKey;
372
374
  this.isHosted = resolved.isHosted;
375
+ const hasBaseUrlOverride = Boolean(
376
+ options.baseUrl ||
377
+ (typeof process !== "undefined" && process.env[ENV.BASE_URL]),
378
+ );
379
+ this._useSidecarLockBaseUrl = !this.isHosted && !hasBaseUrlOverride;
373
380
 
374
381
  // Hosted trading bridge: if the caller passed a privateKey but no
375
382
  // explicit signer, lazily wrap it in an EthersSigner so that
@@ -417,16 +424,18 @@ export abstract class Exchange {
417
424
  try {
418
425
  await this.serverManager.ensureServerRunning();
419
426
 
420
- // Get the actual port the server is running on
421
- // (may differ from default if default port was busy)
422
- const actualPort = this.serverManager.getRunningPort();
423
- const newBaseUrl = `http://localhost:${actualPort}`;
427
+ if (this._useSidecarLockBaseUrl) {
428
+ // Get the actual port the server is running on
429
+ // (may differ from default if default port was busy)
430
+ const actualPort = this.serverManager.getRunningPort();
431
+ const newBaseUrl = `http://localhost:${actualPort}`;
424
432
 
425
- // Update API client with actual base URL
426
- this.config = new Configuration({
427
- basePath: newBaseUrl,
428
- });
429
- this.api = new DefaultApi(this.config);
433
+ // Update API client with actual base URL
434
+ this.config = new Configuration({
435
+ basePath: newBaseUrl,
436
+ });
437
+ this.api = new DefaultApi(this.config);
438
+ }
430
439
  } catch (error) {
431
440
  const msg =
432
441
  `Failed to start PMXT server: ${error instanceof Error ? error.message : error}\n\n` +
@@ -511,12 +520,12 @@ export abstract class Exchange {
511
520
  /**
512
521
  * Resolve the current sidecar base URL.
513
522
  *
514
- * For hosted mode the configured basePath is returned as-is.
515
- * For local mode the port is re-read from the lock file on every
516
- * call so we pick up sidecar restarts that land on a different port.
523
+ * Hosted mode and explicit baseUrl clients keep the configured basePath.
524
+ * Default local clients re-read the lock-file port on every call so they
525
+ * pick up sidecar restarts that land on a different port.
517
526
  */
518
527
  private resolveBaseUrl(): string {
519
- if (this.isHosted) return this.config.basePath;
528
+ if (this.isHosted || !this._useSidecarLockBaseUrl) return this.config.basePath;
520
529
  const port = this.serverManager.getRunningPort();
521
530
  return `http://localhost:${port}`;
522
531
  }
@@ -1225,7 +1234,10 @@ export abstract class Exchange {
1225
1234
  const route = HOSTED_METHOD_ROUTES.get("fetchOrder")!;
1226
1235
  const path = formatRoutePath(route, { order_id: orderId });
1227
1236
  const data = await _tradingRequest(this, { method: route.method, path });
1228
- return orderFromV0(data as Record<string, unknown>);
1237
+ const payload = data && typeof data === "object" && "order" in data
1238
+ ? (data as { order: unknown }).order
1239
+ : data;
1240
+ return orderFromV0(payload as Record<string, unknown>);
1229
1241
  }
1230
1242
  try {
1231
1243
  const args: any[] = [];
@@ -1325,6 +1337,9 @@ export abstract class Exchange {
1325
1337
 
1326
1338
  async fetchClosedOrders(params?: OrderHistoryParams): Promise<Order[]> {
1327
1339
  await this.initPromise;
1340
+ if (this.isHosted) {
1341
+ throw new NotSupported("fetchClosedOrders is not available in hosted trading mode.");
1342
+ }
1328
1343
  try {
1329
1344
  const args: any[] = [];
1330
1345
  if (params !== undefined) args.push(params);
@@ -1351,6 +1366,9 @@ export abstract class Exchange {
1351
1366
 
1352
1367
  async fetchAllOrders(params?: OrderHistoryParams): Promise<Order[]> {
1353
1368
  await this.initPromise;
1369
+ if (this.isHosted) {
1370
+ throw new NotSupported("fetchAllOrders is not available in hosted trading mode.");
1371
+ }
1354
1372
  try {
1355
1373
  const args: any[] = [];
1356
1374
  if (params !== undefined) args.push(params);
@@ -1382,7 +1400,9 @@ export abstract class Exchange {
1382
1400
  const route = HOSTED_METHOD_ROUTES.get("fetchPositions")!;
1383
1401
  const path = formatRoutePath(route, { address: resolvedAddress });
1384
1402
  const data = await _tradingRequest(this, { method: route.method, path });
1385
- const list = Array.isArray(data) ? data : [];
1403
+ const list: unknown[] = Array.isArray(data)
1404
+ ? data
1405
+ : (data && Array.isArray((data as any).positions) ? (data as any).positions : []);
1386
1406
  return list.map((p) => positionFromV0(p as Record<string, unknown>));
1387
1407
  }
1388
1408
  try {
@@ -1416,7 +1436,9 @@ export abstract class Exchange {
1416
1436
  const route = HOSTED_METHOD_ROUTES.get("fetchBalance")!;
1417
1437
  const path = formatRoutePath(route, { address: resolvedAddress });
1418
1438
  const data = await _tradingRequest(this, { method: route.method, path });
1419
- const list = Array.isArray(data) ? data : [];
1439
+ const list: unknown[] = Array.isArray(data)
1440
+ ? data
1441
+ : (data && Array.isArray((data as any).balances) ? (data as any).balances : []);
1420
1442
  return list.map((b) => balanceFromV0(b as Record<string, unknown>));
1421
1443
  }
1422
1444
  try {
@@ -1990,7 +2012,13 @@ export abstract class Exchange {
1990
2012
  throw this.wsTransportUnavailableError("watchAllOrderBooks");
1991
2013
  }
1992
2014
 
1993
- /** @deprecated Use {@link watchAllOrderBooks} instead. */
2015
+ /**
2016
+ * Stream all orderbook updates across venues.
2017
+ *
2018
+ * @deprecated Use {@link watchAllOrderBooks} instead.
2019
+ * @param venues - Optional venue filter
2020
+ * @returns Next event with source, symbol, and orderbook
2021
+ */
1994
2022
  async firehose(venues?: string[]): Promise<FirehoseEvent> {
1995
2023
  return this.watchAllOrderBooks(venues);
1996
2024
  }
@@ -2127,15 +2155,18 @@ export abstract class Exchange {
2127
2155
  * const order = await exchange.submitOrder(built);
2128
2156
  *
2129
2157
  * // Using outcome shorthand:
2158
+ * const yes = market.yes;
2159
+ * if (!yes) throw new Error("Market has no YES outcome");
2160
+ *
2130
2161
  * const built2 = await exchange.buildOrder({
2131
- * outcome: market.yes,
2162
+ * outcome: yes,
2132
2163
  * side: "buy",
2133
2164
  * type: "market",
2134
2165
  * amount: 10
2135
2166
  * });
2136
2167
  * ```
2137
2168
  */
2138
- async buildOrder(params: CreateOrderParams & { outcome?: MarketOutcome }): Promise<BuiltOrder> {
2169
+ async buildOrder(params: CreateOrderInput): Promise<BuiltOrder> {
2139
2170
  if (this.isHostedTradingMode()) {
2140
2171
  return this._hostedBuildOrder(params);
2141
2172
  }
@@ -2289,7 +2320,7 @@ export abstract class Exchange {
2289
2320
  * at submit time.
2290
2321
  */
2291
2322
  private async _hostedBuildOrder(
2292
- params: CreateOrderParams & { outcome?: MarketOutcome },
2323
+ params: CreateOrderInput,
2293
2324
  ): Promise<BuiltOrder> {
2294
2325
  const body = this._hostedBuildOrderBody(params);
2295
2326
  const route = HOSTED_METHOD_ROUTES.get("buildOrder")!;
@@ -2307,7 +2338,7 @@ export abstract class Exchange {
2307
2338
  /**
2308
2339
  * Hosted-mode createOrder: build → sign → submit single-call wrapper.
2309
2340
  */
2310
- private async _hostedCreateOrder(params: any): Promise<Order> {
2341
+ private async _hostedCreateOrder(params: CreateOrderInput): Promise<Order> {
2311
2342
  const built = await this._hostedBuildOrder(params);
2312
2343
  return this._hostedSubmitOrder(built);
2313
2344
  }
@@ -2371,7 +2402,7 @@ export abstract class Exchange {
2371
2402
  * `validateTypedData` / `validateEconomics`. Returns the cross-chain
2372
2403
  * pull-leg route name for Opinion sells and Limitless cross-chain orders.
2373
2404
  */
2374
- private _hostedTypedDataRoute(side: string, isPull: boolean): string {
2405
+ private _hostedTypedDataRoute(side: string, isPull: boolean): HostedRoute {
2375
2406
  const venue = this.exchangeName;
2376
2407
  const sideLower = side.toLowerCase();
2377
2408
  if (venue === "polymarket") {
@@ -2436,10 +2467,12 @@ export abstract class Exchange {
2436
2467
  return orderFromV0(data as Record<string, unknown>);
2437
2468
  }
2438
2469
 
2439
- private _hostedCancelTypedDataRoute(isPull: boolean): string {
2470
+ private _hostedCancelTypedDataRoute(isPull: boolean): HostedRoute {
2440
2471
  const venue = this.exchangeName;
2441
2472
  if (venue === "polymarket") return "cancel_polymarket";
2442
- if (venue === "limitless") return isPull ? "cancel_limitless_base_pull" : "cancel_limitless_polygon";
2473
+ if (venue === "limitless") {
2474
+ return isPull ? "cancel_limitless_base_pull" : "cancel_limitless_polygon";
2475
+ }
2443
2476
  return isPull ? "cancel_opinion_bsc_pull" : "cancel_opinion_polygon";
2444
2477
  }
2445
2478
 
@@ -2449,7 +2482,7 @@ export abstract class Exchange {
2449
2482
  * precision rejected via {@link to6dec}).
2450
2483
  */
2451
2484
  private _hostedBuildOrderBody(
2452
- params: CreateOrderParams & { outcome?: MarketOutcome },
2485
+ params: CreateOrderInput,
2453
2486
  ): Record<string, unknown> {
2454
2487
  let marketId: string | undefined = params.marketId;
2455
2488
  let outcomeId: string | undefined = params.outcomeId;
@@ -2571,7 +2604,7 @@ export abstract class Exchange {
2571
2604
  * });
2572
2605
  * ```
2573
2606
  */
2574
- async createOrder(params: CreateOrderParams & { outcome?: MarketOutcome }): Promise<Order> {
2607
+ async createOrder(params: CreateOrderInput): Promise<Order> {
2575
2608
  // SOR escape path (preserved): legacy hosted SOR flow uses a venue-side
2576
2609
  // SDK to execute the legs, only when a privateKey is present.
2577
2610
  if (this.isHosted && this.exchangeName === 'sor' && this.privateKey) {
@@ -3048,6 +3081,16 @@ export interface PolymarketOptions {
3048
3081
 
3049
3082
  /** Optional signature type */
3050
3083
  signatureType?: 'eoa' | 'poly-proxy' | 'gnosis-safe' | number;
3084
+
3085
+ /**
3086
+ * EVM wallet address used for hosted reads/writes.
3087
+ */
3088
+ walletAddress?: string;
3089
+
3090
+ /**
3091
+ * External signer used for hosted writes.
3092
+ */
3093
+ signer?: Signer;
3051
3094
  }
3052
3095
 
3053
3096
  export class Polymarket extends Exchange {
@@ -3122,6 +3165,75 @@ export class Limitless extends Exchange {
3122
3165
  constructor(options: ExchangeOptions = {}) {
3123
3166
  super("limitless", options);
3124
3167
  }
3168
+
3169
+ /**
3170
+ * Watch real-time AMM price updates via WebSocket.
3171
+ *
3172
+ * @param marketAddress - Limitless market contract address
3173
+ * @param callback - Optional callback invoked with the returned update
3174
+ * @returns Next price update
3175
+ */
3176
+ async watchPrices(
3177
+ marketAddress: string,
3178
+ callback?: (data: Record<string, any>) => void,
3179
+ ): Promise<Record<string, any>> {
3180
+ await this.initPromise;
3181
+ try {
3182
+ const json = await this.sidecarPostRequest("watchPrices", [marketAddress]);
3183
+ const data = this.handleResponse(json);
3184
+ callback?.(data);
3185
+ return data;
3186
+ } catch (error) {
3187
+ if (error instanceof PmxtError) throw error;
3188
+ throw new PmxtError(`Failed to watch prices: ${error}`);
3189
+ }
3190
+ }
3191
+
3192
+ /**
3193
+ * Watch real-time Limitless user position updates.
3194
+ *
3195
+ * Requires API key authentication.
3196
+ *
3197
+ * @param callback - Optional callback invoked with the returned positions
3198
+ * @returns Next position update payload
3199
+ */
3200
+ async watchUserPositions(
3201
+ callback?: (data: Position[]) => void,
3202
+ ): Promise<Position[]> {
3203
+ await this.initPromise;
3204
+ try {
3205
+ const json = await this.sidecarPostRequest("watchUserPositions", []);
3206
+ const data = this.handleResponse(json).map(convertPosition);
3207
+ callback?.(data);
3208
+ return data;
3209
+ } catch (error) {
3210
+ if (error instanceof PmxtError) throw error;
3211
+ throw new PmxtError(`Failed to watch user positions: ${error}`);
3212
+ }
3213
+ }
3214
+
3215
+ /**
3216
+ * Watch real-time Limitless user transaction updates.
3217
+ *
3218
+ * Requires API key authentication.
3219
+ *
3220
+ * @param callback - Optional callback invoked with the returned update
3221
+ * @returns Next transaction update
3222
+ */
3223
+ async watchUserTransactions(
3224
+ callback?: (data: Record<string, any>) => void,
3225
+ ): Promise<Record<string, any>> {
3226
+ await this.initPromise;
3227
+ try {
3228
+ const json = await this.sidecarPostRequest("watchUserTransactions", []);
3229
+ const data = this.handleResponse(json);
3230
+ callback?.(data);
3231
+ return data;
3232
+ } catch (error) {
3233
+ if (error instanceof PmxtError) throw error;
3234
+ throw new PmxtError(`Failed to watch user transactions: ${error}`);
3235
+ }
3236
+ }
3125
3237
  }
3126
3238
 
3127
3239
  /**
@@ -3402,6 +3514,19 @@ export class Rain extends Exchange {
3402
3514
  }
3403
3515
  }
3404
3516
 
3517
+
3518
+ /**
3519
+ * Hunch exchange client.
3520
+ *
3521
+ * Hunch is a crypto-native prediction market. Reads are unauthenticated;
3522
+ * trading requires an EVM private key.
3523
+ */
3524
+ export class Hunch extends Exchange {
3525
+ constructor(options: ExchangeOptions = {}) {
3526
+ super("hunch", options);
3527
+ }
3528
+ }
3529
+
3405
3530
  /**
3406
3531
  * Mock exchange client.
3407
3532
  *
package/pmxt/escrow.ts CHANGED
@@ -16,6 +16,99 @@ import {
16
16
  HostedClientLike,
17
17
  resolveWalletAddress,
18
18
  } from "./hosted-routing";
19
+ import { ValidationError } from "./errors";
20
+
21
+ const APPROVAL_TOKENS = new Set(["usdc", "ctf"]);
22
+ const WITHDRAW_ACTIONS = new Set(["request", "claim", "cancel"]);
23
+ const USDC_SCALE = 1_000_000n;
24
+
25
+ function isDecimalTokenId(token: string): boolean {
26
+ return /^[0-9]+$/.test(token);
27
+ }
28
+
29
+ function normalizeApprovalToken(token: string): string {
30
+ if (typeof token !== "string") {
31
+ throw new ValidationError(
32
+ "token must be 'usdc', 'ctf', or a decimal CTF token_id string",
33
+ "token",
34
+ );
35
+ }
36
+
37
+ const candidate = token.trim();
38
+ const normalized = candidate.toLowerCase();
39
+ if (APPROVAL_TOKENS.has(normalized)) {
40
+ return normalized;
41
+ }
42
+ if (isDecimalTokenId(candidate)) {
43
+ return candidate;
44
+ }
45
+ throw new ValidationError(
46
+ "token must be 'usdc', 'ctf', or a decimal CTF token_id string",
47
+ "token",
48
+ );
49
+ }
50
+
51
+ function normalizeAmountWei(amountWei: bigint | undefined): string | undefined {
52
+ if (amountWei === undefined) {
53
+ return undefined;
54
+ }
55
+ if (typeof amountWei !== "bigint") {
56
+ throw new ValidationError("amount_wei must be a non-negative integer", "amount_wei");
57
+ }
58
+ if (amountWei < 0n) {
59
+ throw new ValidationError("amount_wei must be non-negative", "amount_wei");
60
+ }
61
+ return amountWei.toString();
62
+ }
63
+
64
+ function validateUsdcDecimal(value: string, field: string): void {
65
+ const match = /^([0-9]+)(?:\.([0-9]+))?$/.exec(value);
66
+ if (!match) {
67
+ throw new ValidationError(`${field} must be a finite positive number`, field);
68
+ }
69
+
70
+ const fractional = match[2] ?? "";
71
+ if (fractional.length > 6) {
72
+ throw new ValidationError(
73
+ `${field} precision exceeds 6 decimals; max precision for USDC is 0.000001`,
74
+ field,
75
+ );
76
+ }
77
+
78
+ const scaled = BigInt(match[1]) * USDC_SCALE + BigInt(fractional.padEnd(6, "0"));
79
+ if (scaled <= 0n) {
80
+ throw new ValidationError(`${field} must be positive`, field);
81
+ }
82
+ }
83
+
84
+ function normalizeUsdcAmount(value: number | string | bigint, field: string = "amount"): number | string {
85
+ if (typeof value === "bigint") {
86
+ if (value <= 0n) {
87
+ throw new ValidationError(`${field} must be positive`, field);
88
+ }
89
+ return value.toString();
90
+ }
91
+ if (typeof value === "number") {
92
+ if (!Number.isFinite(value)) {
93
+ throw new ValidationError(`${field} must be a finite positive number`, field);
94
+ }
95
+ validateUsdcDecimal(String(value), field);
96
+ return value;
97
+ }
98
+ if (typeof value === "string") {
99
+ const trimmed = value.trim();
100
+ validateUsdcDecimal(trimmed, field);
101
+ return trimmed;
102
+ }
103
+ throw new ValidationError(`${field} must be a finite positive number`, field);
104
+ }
105
+
106
+ function normalizeWithdrawAction(action: string): "request" | "claim" | "cancel" {
107
+ if (typeof action === "string" && WITHDRAW_ACTIONS.has(action)) {
108
+ return action as "request" | "claim" | "cancel";
109
+ }
110
+ throw new ValidationError("action must be 'request', 'claim', or 'cancel'", "action");
111
+ }
19
112
 
20
113
  export class Escrow {
21
114
  constructor(private readonly client: HostedClientLike) {}
@@ -25,13 +118,16 @@ export class Escrow {
25
118
  * `amountWei` is omitted, the server returns an unlimited approval.
26
119
  */
27
120
  async approveTx(token: string, amountWei?: bigint): Promise<unknown> {
121
+ const address = resolveWalletAddress(this.client);
122
+ const approvalAmount = normalizeAmountWei(amountWei);
28
123
  const route = HOSTED_METHOD_ROUTES.get("escrowApproveTx")!;
29
124
  return _tradingRequest(this.client, {
30
125
  method: route.method,
31
126
  path: route.path,
32
127
  body: {
33
- token,
34
- amount_wei: amountWei === undefined ? null : amountWei.toString(),
128
+ token: normalizeApprovalToken(token),
129
+ user_address: address,
130
+ ...(approvalAmount === undefined ? {} : { amount_wei: approvalAmount }),
35
131
  },
36
132
  });
37
133
  }
@@ -41,12 +137,15 @@ export class Escrow {
41
137
  * grid). Accepts number, decimal string, or BigInt in micro-units.
42
138
  */
43
139
  async depositTx(amount: number | string | bigint): Promise<unknown> {
140
+ const address = resolveWalletAddress(this.client);
44
141
  const route = HOSTED_METHOD_ROUTES.get("escrowDepositTx")!;
45
142
  return _tradingRequest(this.client, {
46
143
  method: route.method,
47
144
  path: route.path,
48
145
  body: {
49
- amount: typeof amount === "bigint" ? amount.toString() : amount,
146
+ token: "usdc",
147
+ amount: normalizeUsdcAmount(amount),
148
+ user_address: address,
50
149
  },
51
150
  });
52
151
  }
@@ -60,17 +159,34 @@ export class Escrow {
60
159
  action: "request" | "claim" | "cancel",
61
160
  amount?: number | string | bigint,
62
161
  ): Promise<unknown> {
162
+ const normalizedAction = normalizeWithdrawAction(action);
163
+ const address = resolveWalletAddress(this.client);
63
164
  const route = HOSTED_METHOD_ROUTES.get("escrowWithdrawTx")!;
64
- const normalizedAmount =
65
- amount === undefined
66
- ? null
67
- : typeof amount === "bigint"
68
- ? amount.toString()
69
- : amount;
165
+ if (normalizedAction === "request") {
166
+ if (amount === undefined) {
167
+ throw new ValidationError("amount is required when action='request'", "amount");
168
+ }
169
+ return _tradingRequest(this.client, {
170
+ method: route.method,
171
+ path: route.path,
172
+ body: {
173
+ action: normalizedAction,
174
+ token: "usdc",
175
+ amount: normalizeUsdcAmount(amount),
176
+ user_address: address,
177
+ },
178
+ });
179
+ }
180
+ if (amount !== undefined) {
181
+ throw new ValidationError(
182
+ `amount must be omitted when action='${normalizedAction}'`,
183
+ "amount",
184
+ );
185
+ }
70
186
  return _tradingRequest(this.client, {
71
187
  method: route.method,
72
188
  path: route.path,
73
- body: { action, amount: normalizedAmount },
189
+ body: { action: normalizedAction, token: "usdc", user_address: address },
74
190
  });
75
191
  }
76
192
 
@@ -82,12 +198,16 @@ export class Escrow {
82
198
  opts: { include?: string; address?: string } = {},
83
199
  ): Promise<unknown> {
84
200
  const address = resolveWalletAddress(this.client, opts.address);
201
+ const include = (opts.include ?? "pending,events").trim();
202
+ if (!include) {
203
+ throw new ValidationError("include must not be empty", "include");
204
+ }
85
205
  const route = HOSTED_METHOD_ROUTES.get("escrowWithdrawals")!;
86
206
  const path = formatRoutePath(route, { address });
87
207
  return _tradingRequest(this.client, {
88
208
  method: route.method,
89
209
  path,
90
- params: { include: opts.include ?? "pending,events" },
210
+ params: { include },
91
211
  });
92
212
  }
93
213
  }
@@ -141,7 +141,9 @@ export type HostedRoute =
141
141
  | "opinion_sell_bsc_pull"
142
142
  | "cancel_polymarket"
143
143
  | "cancel_opinion_polygon"
144
- | "cancel_opinion_bsc_pull";
144
+ | "cancel_opinion_bsc_pull"
145
+ | "cancel_limitless_polygon"
146
+ | "cancel_limitless_base_pull";
145
147
 
146
148
  const SCHEMAS: Readonly<Record<HostedRoute, TypedDataSchema>> = {
147
149
  polymarket_buy: {
@@ -221,6 +223,20 @@ const SCHEMAS: Readonly<Record<HostedRoute, TypedDataSchema>> = {
221
223
  messageKeys: messageKeysFromFields(CANCEL_PULL_FIELDS),
222
224
  walletField: "user",
223
225
  },
226
+ cancel_limitless_polygon: {
227
+ primaryType: "CancelOrder",
228
+ domain: PREFUNDED_DOMAIN,
229
+ fields: CANCEL_ORDER_FIELDS,
230
+ messageKeys: messageKeysFromFields(CANCEL_ORDER_FIELDS),
231
+ walletField: "user",
232
+ },
233
+ cancel_limitless_base_pull: {
234
+ primaryType: "CancelPull",
235
+ domain: LIMITLESS_VENUE_DOMAIN,
236
+ fields: CANCEL_PULL_FIELDS,
237
+ messageKeys: messageKeysFromFields(CANCEL_PULL_FIELDS),
238
+ walletField: "user",
239
+ },
224
240
  };
225
241
 
226
242
  // secp256k1 group order / 2, for canonical low-s check.
package/pmxt/models.ts CHANGED
@@ -549,6 +549,12 @@ export interface CreateOrderParams {
549
549
  /** Limit price (required for limit orders, 0.0-1.0) */
550
550
  price?: number;
551
551
 
552
+ /** Hosted mode: unit that amount is denominated in. Market buys use usdc; market sells and limit orders use shares. */
553
+ denom?: "usdc" | "shares";
554
+
555
+ /** Hosted mode: maximum slippage percentage for market orders. */
556
+ slippage_pct?: number;
557
+
552
558
  /** Optional fee rate (e.g., 1000 for 0.1%) */
553
559
  fee?: number;
554
560
 
@@ -562,6 +568,20 @@ export interface CreateOrderParams {
562
568
  onBehalfOf?: number;
563
569
  }
564
570
 
571
+ /**
572
+ * Public order input accepted by createOrder/buildOrder.
573
+ *
574
+ * Callers can either pass explicit marketId/outcomeId fields, or pass an
575
+ * outcome object returned by fetchMarkets and let the SDK infer those ids.
576
+ */
577
+ export type CreateOrderInput =
578
+ | (CreateOrderParams & { outcome?: never })
579
+ | (Omit<CreateOrderParams, 'marketId' | 'outcomeId'> & {
580
+ outcome: MarketOutcome;
581
+ marketId?: never;
582
+ outcomeId?: never;
583
+ });
584
+
565
585
  /** Alias matching the core MarketFetchParams name. */
566
586
  export type MarketFetchParams = MarketFilterParams;
567
587
 
@@ -103,13 +103,15 @@ export class ServerManager {
103
103
  * Check if the server is running.
104
104
  */
105
105
  async isServerRunning(): Promise<boolean> {
106
- // Read lock file to get current port
107
- const port = this.getRunningPort();
106
+ const info = this.getServerInfo();
107
+ if (!info?.port) {
108
+ return false;
109
+ }
108
110
 
109
111
  try {
110
112
  // Use native fetch to check health on the actual running port
111
113
  // This avoids issues where this.api is configured with the wrong port
112
- const response = await fetch(`http://localhost:${port}/health`, {
114
+ const response = await fetch(`http://localhost:${info.port}/health`, {
113
115
  signal: AbortSignal.timeout(5_000),
114
116
  });
115
117
  if (response.ok) {