pmxtjs 2.51.0 → 2.51.2

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.
@@ -2601,6 +2601,7 @@ export const BuildOrderOperationExchangeEnum = {
2601
2601
  Hyperliquid: 'hyperliquid',
2602
2602
  Suibets: 'suibets',
2603
2603
  Rain: 'rain',
2604
+ Hunch: 'hunch',
2604
2605
  Mock: 'mock',
2605
2606
  Router: 'router'
2606
2607
  } as const;
@@ -2624,6 +2625,7 @@ export const CancelOrderOperationExchangeEnum = {
2624
2625
  Hyperliquid: 'hyperliquid',
2625
2626
  Suibets: 'suibets',
2626
2627
  Rain: 'rain',
2628
+ Hunch: 'hunch',
2627
2629
  Mock: 'mock',
2628
2630
  Router: 'router'
2629
2631
  } as const;
@@ -2647,6 +2649,7 @@ export const CloseOperationExchangeEnum = {
2647
2649
  Hyperliquid: 'hyperliquid',
2648
2650
  Suibets: 'suibets',
2649
2651
  Rain: 'rain',
2652
+ Hunch: 'hunch',
2650
2653
  Mock: 'mock',
2651
2654
  Router: 'router'
2652
2655
  } as const;
@@ -2677,6 +2680,7 @@ export const CreateOrderOperationExchangeEnum = {
2677
2680
  Hyperliquid: 'hyperliquid',
2678
2681
  Suibets: 'suibets',
2679
2682
  Rain: 'rain',
2683
+ Hunch: 'hunch',
2680
2684
  Mock: 'mock',
2681
2685
  Router: 'router'
2682
2686
  } as const;
@@ -2700,6 +2704,7 @@ export const FetchAllOrdersExchangeEnum = {
2700
2704
  Hyperliquid: 'hyperliquid',
2701
2705
  Suibets: 'suibets',
2702
2706
  Rain: 'rain',
2707
+ Hunch: 'hunch',
2703
2708
  Mock: 'mock',
2704
2709
  Router: 'router'
2705
2710
  } as const;
@@ -2742,6 +2747,7 @@ export const FetchBalanceExchangeEnum = {
2742
2747
  Hyperliquid: 'hyperliquid',
2743
2748
  Suibets: 'suibets',
2744
2749
  Rain: 'rain',
2750
+ Hunch: 'hunch',
2745
2751
  Mock: 'mock',
2746
2752
  Router: 'router'
2747
2753
  } as const;
@@ -2765,6 +2771,7 @@ export const FetchClosedOrdersExchangeEnum = {
2765
2771
  Hyperliquid: 'hyperliquid',
2766
2772
  Suibets: 'suibets',
2767
2773
  Rain: 'rain',
2774
+ Hunch: 'hunch',
2768
2775
  Mock: 'mock',
2769
2776
  Router: 'router'
2770
2777
  } as const;
@@ -2788,6 +2795,7 @@ export const FetchEventExchangeEnum = {
2788
2795
  Hyperliquid: 'hyperliquid',
2789
2796
  Suibets: 'suibets',
2790
2797
  Rain: 'rain',
2798
+ Hunch: 'hunch',
2791
2799
  Mock: 'mock',
2792
2800
  Router: 'router'
2793
2801
  } as const;
@@ -2858,6 +2866,7 @@ export const FetchEventsExchangeEnum = {
2858
2866
  Hyperliquid: 'hyperliquid',
2859
2867
  Suibets: 'suibets',
2860
2868
  Rain: 'rain',
2869
+ Hunch: 'hunch',
2861
2870
  Mock: 'mock',
2862
2871
  Router: 'router'
2863
2872
  } as const;
@@ -2909,6 +2918,7 @@ export const FetchEventsPaginatedExchangeEnum = {
2909
2918
  Hyperliquid: 'hyperliquid',
2910
2919
  Suibets: 'suibets',
2911
2920
  Rain: 'rain',
2921
+ Hunch: 'hunch',
2912
2922
  Mock: 'mock',
2913
2923
  Router: 'router'
2914
2924
  } as const;
@@ -2960,6 +2970,7 @@ export const FetchMarketExchangeEnum = {
2960
2970
  Hyperliquid: 'hyperliquid',
2961
2971
  Suibets: 'suibets',
2962
2972
  Rain: 'rain',
2973
+ Hunch: 'hunch',
2963
2974
  Mock: 'mock',
2964
2975
  Router: 'router'
2965
2976
  } as const;
@@ -3039,6 +3050,7 @@ export const FetchMarketsExchangeEnum = {
3039
3050
  Hyperliquid: 'hyperliquid',
3040
3051
  Suibets: 'suibets',
3041
3052
  Rain: 'rain',
3053
+ Hunch: 'hunch',
3042
3054
  Mock: 'mock',
3043
3055
  Router: 'router'
3044
3056
  } as const;
@@ -3090,6 +3102,7 @@ export const FetchMarketsPaginatedExchangeEnum = {
3090
3102
  Hyperliquid: 'hyperliquid',
3091
3103
  Suibets: 'suibets',
3092
3104
  Rain: 'rain',
3105
+ Hunch: 'hunch',
3093
3106
  Mock: 'mock',
3094
3107
  Router: 'router'
3095
3108
  } as const;
@@ -3151,6 +3164,7 @@ export const FetchMyTradesExchangeEnum = {
3151
3164
  Hyperliquid: 'hyperliquid',
3152
3165
  Suibets: 'suibets',
3153
3166
  Rain: 'rain',
3167
+ Hunch: 'hunch',
3154
3168
  Mock: 'mock',
3155
3169
  Router: 'router'
3156
3170
  } as const;
@@ -3174,6 +3188,7 @@ export const FetchOHLCVExchangeEnum = {
3174
3188
  Hyperliquid: 'hyperliquid',
3175
3189
  Suibets: 'suibets',
3176
3190
  Rain: 'rain',
3191
+ Hunch: 'hunch',
3177
3192
  Mock: 'mock',
3178
3193
  Router: 'router'
3179
3194
  } as const;
@@ -3197,6 +3212,7 @@ export const FetchOpenOrdersExchangeEnum = {
3197
3212
  Hyperliquid: 'hyperliquid',
3198
3213
  Suibets: 'suibets',
3199
3214
  Rain: 'rain',
3215
+ Hunch: 'hunch',
3200
3216
  Mock: 'mock',
3201
3217
  Router: 'router'
3202
3218
  } as const;
@@ -3220,6 +3236,7 @@ export const FetchOrderExchangeEnum = {
3220
3236
  Hyperliquid: 'hyperliquid',
3221
3237
  Suibets: 'suibets',
3222
3238
  Rain: 'rain',
3239
+ Hunch: 'hunch',
3223
3240
  Mock: 'mock',
3224
3241
  Router: 'router'
3225
3242
  } as const;
@@ -3243,6 +3260,7 @@ export const FetchOrderBookExchangeEnum = {
3243
3260
  Hyperliquid: 'hyperliquid',
3244
3261
  Suibets: 'suibets',
3245
3262
  Rain: 'rain',
3263
+ Hunch: 'hunch',
3246
3264
  Mock: 'mock',
3247
3265
  Router: 'router'
3248
3266
  } as const;
@@ -3274,6 +3292,7 @@ export const FetchOrderBooksOperationExchangeEnum = {
3274
3292
  Hyperliquid: 'hyperliquid',
3275
3293
  Suibets: 'suibets',
3276
3294
  Rain: 'rain',
3295
+ Hunch: 'hunch',
3277
3296
  Mock: 'mock',
3278
3297
  Router: 'router'
3279
3298
  } as const;
@@ -3297,6 +3316,7 @@ export const FetchPositionsExchangeEnum = {
3297
3316
  Hyperliquid: 'hyperliquid',
3298
3317
  Suibets: 'suibets',
3299
3318
  Rain: 'rain',
3319
+ Hunch: 'hunch',
3300
3320
  Mock: 'mock',
3301
3321
  Router: 'router'
3302
3322
  } as const;
@@ -3348,6 +3368,7 @@ export const FetchSeriesExchangeEnum = {
3348
3368
  Hyperliquid: 'hyperliquid',
3349
3369
  Suibets: 'suibets',
3350
3370
  Rain: 'rain',
3371
+ Hunch: 'hunch',
3351
3372
  Mock: 'mock',
3352
3373
  Router: 'router'
3353
3374
  } as const;
@@ -3371,6 +3392,7 @@ export const FetchTradesExchangeEnum = {
3371
3392
  Hyperliquid: 'hyperliquid',
3372
3393
  Suibets: 'suibets',
3373
3394
  Rain: 'rain',
3395
+ Hunch: 'hunch',
3374
3396
  Mock: 'mock',
3375
3397
  Router: 'router'
3376
3398
  } as const;
@@ -3394,6 +3416,7 @@ export const FilterEventsOperationExchangeEnum = {
3394
3416
  Hyperliquid: 'hyperliquid',
3395
3417
  Suibets: 'suibets',
3396
3418
  Rain: 'rain',
3419
+ Hunch: 'hunch',
3397
3420
  Mock: 'mock',
3398
3421
  Router: 'router'
3399
3422
  } as const;
@@ -3417,6 +3440,7 @@ export const FilterMarketsOperationExchangeEnum = {
3417
3440
  Hyperliquid: 'hyperliquid',
3418
3441
  Suibets: 'suibets',
3419
3442
  Rain: 'rain',
3443
+ Hunch: 'hunch',
3420
3444
  Mock: 'mock',
3421
3445
  Router: 'router'
3422
3446
  } as const;
@@ -3440,6 +3464,7 @@ export const GetExecutionPriceOperationExchangeEnum = {
3440
3464
  Hyperliquid: 'hyperliquid',
3441
3465
  Suibets: 'suibets',
3442
3466
  Rain: 'rain',
3467
+ Hunch: 'hunch',
3443
3468
  Mock: 'mock',
3444
3469
  Router: 'router'
3445
3470
  } as const;
@@ -3463,6 +3488,7 @@ export const GetExecutionPriceDetailedOperationExchangeEnum = {
3463
3488
  Hyperliquid: 'hyperliquid',
3464
3489
  Suibets: 'suibets',
3465
3490
  Rain: 'rain',
3491
+ Hunch: 'hunch',
3466
3492
  Mock: 'mock',
3467
3493
  Router: 'router'
3468
3494
  } as const;
@@ -3486,6 +3512,7 @@ export const LoadMarketsOperationExchangeEnum = {
3486
3512
  Hyperliquid: 'hyperliquid',
3487
3513
  Suibets: 'suibets',
3488
3514
  Rain: 'rain',
3515
+ Hunch: 'hunch',
3489
3516
  Mock: 'mock',
3490
3517
  Router: 'router'
3491
3518
  } as const;
@@ -3509,6 +3536,7 @@ export const SubmitOrderOperationExchangeEnum = {
3509
3536
  Hyperliquid: 'hyperliquid',
3510
3537
  Suibets: 'suibets',
3511
3538
  Rain: 'rain',
3539
+ Hunch: 'hunch',
3512
3540
  Mock: 'mock',
3513
3541
  Router: 'router'
3514
3542
  } as const;
package/index.ts CHANGED
@@ -19,14 +19,14 @@
19
19
  */
20
20
 
21
21
 
22
- import { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Mock } from "./pmxt/client.js";
22
+ import { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Hunch, Mock } from "./pmxt/client.js";
23
23
  import { Router } from "./pmxt/router.js";
24
24
  import { ServerManager } from "./pmxt/server-manager.js";
25
25
  import { FeedClient } from "./pmxt/feed-client.js";
26
26
  import * as models from "./pmxt/models.js";
27
27
  import * as errors from "./pmxt/errors.js";
28
28
 
29
- export { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Mock, PolymarketOptions } from "./pmxt/client.js";
29
+ export { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Hunch, Mock, PolymarketOptions } from "./pmxt/client.js";
30
30
  export { FeedClient } from "./pmxt/feed-client.js";
31
31
  export type { Ticker, Tickers, OHLCV, Market as FeedMarket, OracleRound, FeedClientOptions } from "./pmxt/feed-client.js";
32
32
  export { Router } from "./pmxt/router.js";
@@ -98,6 +98,7 @@ const pmxt = {
98
98
  SuiBets,
99
99
  Suibets,
100
100
  Rain,
101
+ Hunch,
101
102
  Mock,
102
103
  Router,
103
104
  ServerManager,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxtjs",
3
- "version": "2.51.0",
3
+ "version": "2.51.2",
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.51.0",
46
+ "pmxt-core": "2.51.2",
47
47
  "ws": "^8.18.0"
48
48
  },
49
49
  "peerDependencies": {
package/pmxt/client.ts CHANGED
@@ -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
 
@@ -351,6 +351,7 @@ export abstract class Exchange {
351
351
  private _wsClient: SidecarWsClient | null = null;
352
352
  /** Sticky flag: true if the sidecar /ws endpoint is unavailable. */
353
353
  private _wsUnsupported: boolean = false;
354
+ private readonly _useSidecarLockBaseUrl: boolean;
354
355
 
355
356
  constructor(exchangeName: string, options: ExchangeOptions = {}) {
356
357
  this.exchangeName = exchangeName.toLowerCase();
@@ -370,6 +371,11 @@ export abstract class Exchange {
370
371
  const baseUrl = resolved.baseUrl;
371
372
  this.pmxtApiKey = resolved.pmxtApiKey;
372
373
  this.isHosted = resolved.isHosted;
374
+ const hasBaseUrlOverride = Boolean(
375
+ options.baseUrl ||
376
+ (typeof process !== "undefined" && process.env[ENV.BASE_URL]),
377
+ );
378
+ this._useSidecarLockBaseUrl = !this.isHosted && !hasBaseUrlOverride;
373
379
 
374
380
  // Hosted trading bridge: if the caller passed a privateKey but no
375
381
  // explicit signer, lazily wrap it in an EthersSigner so that
@@ -417,16 +423,18 @@ export abstract class Exchange {
417
423
  try {
418
424
  await this.serverManager.ensureServerRunning();
419
425
 
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}`;
426
+ if (this._useSidecarLockBaseUrl) {
427
+ // Get the actual port the server is running on
428
+ // (may differ from default if default port was busy)
429
+ const actualPort = this.serverManager.getRunningPort();
430
+ const newBaseUrl = `http://localhost:${actualPort}`;
424
431
 
425
- // Update API client with actual base URL
426
- this.config = new Configuration({
427
- basePath: newBaseUrl,
428
- });
429
- this.api = new DefaultApi(this.config);
432
+ // Update API client with actual base URL
433
+ this.config = new Configuration({
434
+ basePath: newBaseUrl,
435
+ });
436
+ this.api = new DefaultApi(this.config);
437
+ }
430
438
  } catch (error) {
431
439
  const msg =
432
440
  `Failed to start PMXT server: ${error instanceof Error ? error.message : error}\n\n` +
@@ -511,12 +519,12 @@ export abstract class Exchange {
511
519
  /**
512
520
  * Resolve the current sidecar base URL.
513
521
  *
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.
522
+ * Hosted mode and explicit baseUrl clients keep the configured basePath.
523
+ * Default local clients re-read the lock-file port on every call so they
524
+ * pick up sidecar restarts that land on a different port.
517
525
  */
518
526
  private resolveBaseUrl(): string {
519
- if (this.isHosted) return this.config.basePath;
527
+ if (this.isHosted || !this._useSidecarLockBaseUrl) return this.config.basePath;
520
528
  const port = this.serverManager.getRunningPort();
521
529
  return `http://localhost:${port}`;
522
530
  }
@@ -1225,7 +1233,10 @@ export abstract class Exchange {
1225
1233
  const route = HOSTED_METHOD_ROUTES.get("fetchOrder")!;
1226
1234
  const path = formatRoutePath(route, { order_id: orderId });
1227
1235
  const data = await _tradingRequest(this, { method: route.method, path });
1228
- return orderFromV0(data as Record<string, unknown>);
1236
+ const payload = data && typeof data === "object" && "order" in data
1237
+ ? (data as { order: unknown }).order
1238
+ : data;
1239
+ return orderFromV0(payload as Record<string, unknown>);
1229
1240
  }
1230
1241
  try {
1231
1242
  const args: any[] = [];
@@ -1325,6 +1336,9 @@ export abstract class Exchange {
1325
1336
 
1326
1337
  async fetchClosedOrders(params?: OrderHistoryParams): Promise<Order[]> {
1327
1338
  await this.initPromise;
1339
+ if (this.isHosted) {
1340
+ throw new NotSupported("fetchClosedOrders is not available in hosted trading mode.");
1341
+ }
1328
1342
  try {
1329
1343
  const args: any[] = [];
1330
1344
  if (params !== undefined) args.push(params);
@@ -1351,6 +1365,9 @@ export abstract class Exchange {
1351
1365
 
1352
1366
  async fetchAllOrders(params?: OrderHistoryParams): Promise<Order[]> {
1353
1367
  await this.initPromise;
1368
+ if (this.isHosted) {
1369
+ throw new NotSupported("fetchAllOrders is not available in hosted trading mode.");
1370
+ }
1354
1371
  try {
1355
1372
  const args: any[] = [];
1356
1373
  if (params !== undefined) args.push(params);
@@ -1382,7 +1399,9 @@ export abstract class Exchange {
1382
1399
  const route = HOSTED_METHOD_ROUTES.get("fetchPositions")!;
1383
1400
  const path = formatRoutePath(route, { address: resolvedAddress });
1384
1401
  const data = await _tradingRequest(this, { method: route.method, path });
1385
- const list = Array.isArray(data) ? data : [];
1402
+ const list: unknown[] = Array.isArray(data)
1403
+ ? data
1404
+ : (data && Array.isArray((data as any).positions) ? (data as any).positions : []);
1386
1405
  return list.map((p) => positionFromV0(p as Record<string, unknown>));
1387
1406
  }
1388
1407
  try {
@@ -1416,7 +1435,9 @@ export abstract class Exchange {
1416
1435
  const route = HOSTED_METHOD_ROUTES.get("fetchBalance")!;
1417
1436
  const path = formatRoutePath(route, { address: resolvedAddress });
1418
1437
  const data = await _tradingRequest(this, { method: route.method, path });
1419
- const list = Array.isArray(data) ? data : [];
1438
+ const list: unknown[] = Array.isArray(data)
1439
+ ? data
1440
+ : (data && Array.isArray((data as any).balances) ? (data as any).balances : []);
1420
1441
  return list.map((b) => balanceFromV0(b as Record<string, unknown>));
1421
1442
  }
1422
1443
  try {
@@ -3048,6 +3069,16 @@ export interface PolymarketOptions {
3048
3069
 
3049
3070
  /** Optional signature type */
3050
3071
  signatureType?: 'eoa' | 'poly-proxy' | 'gnosis-safe' | number;
3072
+
3073
+ /**
3074
+ * EVM wallet address used for hosted reads/writes.
3075
+ */
3076
+ walletAddress?: string;
3077
+
3078
+ /**
3079
+ * External signer used for hosted writes.
3080
+ */
3081
+ signer?: Signer;
3051
3082
  }
3052
3083
 
3053
3084
  export class Polymarket extends Exchange {
@@ -3402,6 +3433,19 @@ export class Rain extends Exchange {
3402
3433
  }
3403
3434
  }
3404
3435
 
3436
+
3437
+ /**
3438
+ * Hunch exchange client.
3439
+ *
3440
+ * Hunch is a crypto-native prediction market. Reads are unauthenticated;
3441
+ * trading requires an EVM private key.
3442
+ */
3443
+ export class Hunch extends Exchange {
3444
+ constructor(options: ExchangeOptions = {}) {
3445
+ super("hunch", options);
3446
+ }
3447
+ }
3448
+
3405
3449
  /**
3406
3450
  * Mock exchange client.
3407
3451
  *
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
  }
@@ -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) {