pmxtjs 2.49.10 → 2.50.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.
Files changed (42) hide show
  1. package/dist/esm/generated/src/apis/DefaultApi.d.ts +28 -0
  2. package/dist/esm/generated/src/apis/DefaultApi.js +28 -0
  3. package/dist/esm/index.d.ts +4 -3
  4. package/dist/esm/index.js +4 -3
  5. package/dist/esm/pmxt/client.d.ts +37 -25
  6. package/dist/esm/pmxt/client.js +117 -202
  7. package/dist/esm/pmxt/constants.d.ts +4 -0
  8. package/dist/esm/pmxt/constants.js +6 -0
  9. package/dist/esm/pmxt/hosted-routing.js +1 -0
  10. package/dist/esm/pmxt/hosted-typed-data.d.ts +1 -1
  11. package/dist/esm/pmxt/hosted-typed-data.js +53 -12
  12. package/dist/esm/pmxt/models.d.ts +1 -0
  13. package/dist/esm/pmxt/router.js +12 -3
  14. package/dist/esm/pmxt/signers.d.ts +7 -0
  15. package/dist/esm/pmxt/signers.js +30 -8
  16. package/dist/generated/src/apis/DefaultApi.d.ts +28 -0
  17. package/dist/generated/src/apis/DefaultApi.js +28 -0
  18. package/dist/index.d.ts +4 -3
  19. package/dist/index.js +6 -1
  20. package/dist/pmxt/client.d.ts +37 -25
  21. package/dist/pmxt/client.js +116 -200
  22. package/dist/pmxt/constants.d.ts +4 -0
  23. package/dist/pmxt/constants.js +7 -1
  24. package/dist/pmxt/hosted-routing.js +1 -0
  25. package/dist/pmxt/hosted-typed-data.d.ts +1 -1
  26. package/dist/pmxt/hosted-typed-data.js +53 -12
  27. package/dist/pmxt/models.d.ts +1 -0
  28. package/dist/pmxt/router.js +12 -3
  29. package/dist/pmxt/signers.d.ts +7 -0
  30. package/dist/pmxt/signers.js +31 -8
  31. package/generated/docs/DefaultApi.md +56 -56
  32. package/generated/package.json +1 -1
  33. package/generated/src/apis/DefaultApi.ts +28 -0
  34. package/index.ts +12 -3
  35. package/package.json +2 -2
  36. package/pmxt/client.ts +123 -219
  37. package/pmxt/constants.ts +7 -0
  38. package/pmxt/hosted-routing.ts +1 -0
  39. package/pmxt/hosted-typed-data.ts +67 -17
  40. package/pmxt/models.ts +3 -0
  41. package/pmxt/router.ts +14 -3
  42. package/pmxt/signers.ts +28 -7
package/pmxt/client.ts CHANGED
@@ -46,6 +46,7 @@ import {
46
46
  UnifiedSeries,
47
47
  UserTrade,
48
48
  FirehoseEvent,
49
+ FetchMatchedMarketClustersParams,
49
50
  } from "./models.js";
50
51
 
51
52
  import { ServerManager } from "./server-manager.js";
@@ -1131,9 +1132,6 @@ export abstract class Exchange {
1131
1132
  }
1132
1133
 
1133
1134
  async submitOrder(built: BuiltOrder): Promise<Order> {
1134
- if (this.isHostedTradingMode()) {
1135
- return this._hostedSubmitOrder(built);
1136
- }
1137
1135
  await this.initPromise;
1138
1136
  if (this.isHosted) {
1139
1137
  throw new PmxtError("submitOrder is not available in hosted mode. Use createOrder instead.");
@@ -1162,88 +1160,7 @@ export abstract class Exchange {
1162
1160
  }
1163
1161
  }
1164
1162
 
1165
- /**
1166
- * Hosted-mode submitOrder: validate the stored build response, sign the
1167
- * typed_data (and pull_typed_data for Opinion cross-chain sells), then
1168
- * POST to `/v0/trade/submit-order`.
1169
- */
1170
- private async _hostedSubmitOrder(built: BuiltOrder): Promise<Order> {
1171
- const signer = this.requireHostedSigner();
1172
- if (!this.walletAddress) {
1173
- throw new MissingWalletAddress(
1174
- "hosted submitOrder requires walletAddress",
1175
- );
1176
- }
1177
- // BuiltOrder is the SDK-side wrapper around the build response —
1178
- // expect typed_data, optional pull_typed_data, built_order_id, and
1179
- // the originating build_request to be present.
1180
- const payload = built as unknown as Record<string, unknown>;
1181
- const typedData = payload["typed_data"] as TypedData | undefined;
1182
- if (!typedData) {
1183
- throw new HostedInvalidSignature(0, "typed_data missing from built order");
1184
- }
1185
- const buildRequest = (payload["build_request"] as Record<string, unknown> | undefined)
1186
- ?? ((payload["params"] as Record<string, unknown> | undefined)?.["build_request"] as Record<string, unknown> | undefined);
1187
-
1188
- const side = String(buildRequest?.["side"] ?? "buy");
1189
- const primaryRoute = this._hostedTypedDataRoute(side, false);
1190
- // Layer 1: schema, Layer 2: economics.
1191
- validateTypedData(typedData, primaryRoute, this.walletAddress);
1192
- if (buildRequest) {
1193
- validateEconomics(typedData, primaryRoute, buildRequest, payload);
1194
- }
1195
-
1196
- const signature = await signer.signTypedData(typedData);
1197
- // Layer 3: post-sign recovery + canonical check.
1198
- verifySignature(typedData, signature, signer.address);
1199
-
1200
- const body: Record<string, unknown> = {
1201
- built_order_id: payload["built_order_id"],
1202
- signature,
1203
- };
1204
-
1205
- const pullTypedData = payload["pull_typed_data"] as TypedData | undefined;
1206
- if (pullTypedData) {
1207
- const pullRoute = this._hostedTypedDataRoute(side, true);
1208
- if (pullRoute) {
1209
- validateTypedData(pullTypedData, pullRoute, this.walletAddress);
1210
- }
1211
- const pullSig = await signer.signTypedData(pullTypedData);
1212
- verifySignature(pullTypedData, pullSig, signer.address);
1213
- body["pull_signature"] = pullSig;
1214
- }
1215
-
1216
- const route = HOSTED_METHOD_ROUTES.get("submitOrder")!;
1217
- const data = await _tradingRequest(this, { method: route.method, path: route.path, body });
1218
- return orderFromV0(data as Record<string, unknown>);
1219
- }
1220
-
1221
- /**
1222
- * Resolve the per-(venue, side, pull) typed-data schema route used by
1223
- * `validateTypedData` / `validateEconomics`. Returns undefined for the
1224
- * pull leg when a venue/side combo doesn't have one.
1225
- */
1226
- private _hostedTypedDataRoute(side: string, isPull: boolean): string {
1227
- const venue = this.exchangeName;
1228
- const sideLower = side.toLowerCase();
1229
- if (venue === "polymarket") {
1230
- return sideLower === "sell" ? "polymarket_sell" : "polymarket_buy";
1231
- }
1232
- // opinion
1233
- if (sideLower === "buy") return "opinion_buy";
1234
- // sell — polygon, or BSC pull leg for cross-chain
1235
- return isPull ? "opinion_sell_bsc_pull" : "opinion_sell_polygon";
1236
- }
1237
-
1238
- private _hostedCancelTypedDataRoute(isPull: boolean): string {
1239
- if (this.exchangeName === "polymarket") return "cancel_polymarket";
1240
- return isPull ? "cancel_opinion_bsc_pull" : "cancel_opinion_polygon";
1241
- }
1242
-
1243
1163
  async cancelOrder(orderId: string): Promise<Order> {
1244
- if (this.isHostedTradingMode()) {
1245
- return this._hostedCancelOrder(orderId);
1246
- }
1247
1164
  await this.initPromise;
1248
1165
  try {
1249
1166
  const args: any[] = [];
@@ -1269,56 +1186,7 @@ export abstract class Exchange {
1269
1186
  }
1270
1187
  }
1271
1188
 
1272
- /**
1273
- * Hosted-mode cancelOrder: build the cancel typed_data on the server,
1274
- * validate + sign (dual-sign for Opinion cross-chain), then submit.
1275
- */
1276
- private async _hostedCancelOrder(orderId: string): Promise<Order> {
1277
- const signer = this.requireHostedSigner();
1278
- if (!this.walletAddress) {
1279
- throw new MissingWalletAddress(
1280
- "hosted cancelOrder requires walletAddress",
1281
- );
1282
- }
1283
-
1284
- const buildRoute = HOSTED_METHOD_ROUTES.get("cancelOrderBuild")!;
1285
- const buildResp = await _tradingRequest(this, {
1286
- method: buildRoute.method,
1287
- path: buildRoute.path,
1288
- body: { order_id: orderId },
1289
- }) as Record<string, unknown>;
1290
-
1291
- const typedData = buildResp["typed_data"] as TypedData | undefined;
1292
- if (!typedData) {
1293
- throw new HostedInvalidSignature(0, "typed_data missing from cancel build response");
1294
- }
1295
-
1296
- validateTypedData(typedData, this._hostedCancelTypedDataRoute(false), this.walletAddress);
1297
- const signature = await signer.signTypedData(typedData);
1298
- verifySignature(typedData, signature, signer.address);
1299
-
1300
- const body: Record<string, unknown> = {
1301
- cancel_id: buildResp["cancel_id"],
1302
- signature,
1303
- };
1304
-
1305
- const pullTypedData = buildResp["pull_typed_data"] as TypedData | undefined;
1306
- if (pullTypedData) {
1307
- validateTypedData(pullTypedData, this._hostedCancelTypedDataRoute(true), this.walletAddress);
1308
- const pullSig = await signer.signTypedData(pullTypedData);
1309
- verifySignature(pullTypedData, pullSig, signer.address);
1310
- body["pull_signature"] = pullSig;
1311
- }
1312
-
1313
- const route = HOSTED_METHOD_ROUTES.get("cancelOrder")!;
1314
- const data = await _tradingRequest(this, { method: route.method, path: route.path, body });
1315
- return orderFromV0(data as Record<string, unknown>);
1316
- }
1317
-
1318
1189
  async fetchOrder(orderId: string): Promise<Order> {
1319
- if (this.isHostedTradingMode()) {
1320
- return this._hostedFetchOrder(orderId);
1321
- }
1322
1190
  await this.initPromise;
1323
1191
  try {
1324
1192
  const args: any[] = [];
@@ -1344,17 +1212,7 @@ export abstract class Exchange {
1344
1212
  }
1345
1213
  }
1346
1214
 
1347
- private async _hostedFetchOrder(orderId: string): Promise<Order> {
1348
- const route = HOSTED_METHOD_ROUTES.get("fetchOrder")!;
1349
- const path = formatRoutePath(route, { order_id: orderId });
1350
- const data = await _tradingRequest(this, { method: route.method, path });
1351
- return orderFromV0(data as Record<string, unknown>);
1352
- }
1353
-
1354
1215
  async fetchOpenOrders(marketId?: string): Promise<Order[]> {
1355
- if (this.isHostedTradingMode()) {
1356
- return this._hostedFetchOpenOrders(marketId);
1357
- }
1358
1216
  await this.initPromise;
1359
1217
  try {
1360
1218
  const args: any[] = [];
@@ -1380,24 +1238,7 @@ export abstract class Exchange {
1380
1238
  }
1381
1239
  }
1382
1240
 
1383
- private async _hostedFetchOpenOrders(marketId?: string): Promise<Order[]> {
1384
- const address = resolveWalletAddress(this, undefined);
1385
- const route = HOSTED_METHOD_ROUTES.get("fetchOpenOrders")!;
1386
- const params: Record<string, string> = { address };
1387
- if (marketId !== undefined) params["market_id"] = marketId;
1388
- const data = await _tradingRequest(this, {
1389
- method: route.method,
1390
- path: route.path,
1391
- params,
1392
- });
1393
- const items = (Array.isArray(data) ? data : (data as Record<string, unknown>)?.["orders"] ?? []) as unknown[];
1394
- return (items as Record<string, unknown>[]).map(orderFromV0);
1395
- }
1396
-
1397
1241
  async fetchMyTrades(params?: MyTradesParams): Promise<UserTrade[]> {
1398
- if (this.isHostedTradingMode()) {
1399
- return this._hostedFetchMyTrades(params);
1400
- }
1401
1242
  await this.initPromise;
1402
1243
  try {
1403
1244
  const args: any[] = [];
@@ -1423,32 +1264,7 @@ export abstract class Exchange {
1423
1264
  }
1424
1265
  }
1425
1266
 
1426
- private async _hostedFetchMyTrades(params?: MyTradesParams): Promise<UserTrade[]> {
1427
- const address = resolveWalletAddress(this, undefined);
1428
- const route = HOSTED_METHOD_ROUTES.get("fetchMyTrades")!;
1429
- const path = formatRoutePath(route, { address });
1430
- const q: Record<string, string> = {};
1431
- if (params?.marketId) q["market_id"] = params.marketId;
1432
- if (params?.outcomeId) q["outcome_id"] = params.outcomeId;
1433
- if (params?.limit !== undefined) q["limit"] = String(params.limit);
1434
- if (params?.cursor) q["cursor"] = params.cursor;
1435
- if (params?.since) q["since"] = String(params.since.getTime());
1436
- if (params?.until) q["until"] = String(params.until.getTime());
1437
- const data = await _tradingRequest(this, {
1438
- method: route.method,
1439
- path,
1440
- params: Object.keys(q).length ? q : undefined,
1441
- });
1442
- const items = (Array.isArray(data) ? data : (data as Record<string, unknown>)?.["trades"] ?? []) as unknown[];
1443
- return (items as Record<string, unknown>[]).map(userTradeFromV0);
1444
- }
1445
-
1446
1267
  async fetchClosedOrders(params?: OrderHistoryParams): Promise<Order[]> {
1447
- if (this.isHostedTradingMode()) {
1448
- throw new NotSupported(
1449
- "Settled orders are modeled as trades — use fetchMyTrades().",
1450
- );
1451
- }
1452
1268
  await this.initPromise;
1453
1269
  try {
1454
1270
  const args: any[] = [];
@@ -1475,11 +1291,6 @@ export abstract class Exchange {
1475
1291
  }
1476
1292
 
1477
1293
  async fetchAllOrders(params?: OrderHistoryParams): Promise<Order[]> {
1478
- if (this.isHostedTradingMode()) {
1479
- throw new NotSupported(
1480
- "Use fetchOpenOrders() and fetchMyTrades() separately.",
1481
- );
1482
- }
1483
1294
  await this.initPromise;
1484
1295
  try {
1485
1296
  const args: any[] = [];
@@ -1506,9 +1317,6 @@ export abstract class Exchange {
1506
1317
  }
1507
1318
 
1508
1319
  async fetchPositions(address?: string): Promise<Position[]> {
1509
- if (this.isHostedTradingMode()) {
1510
- return this._hostedFetchPositions(address);
1511
- }
1512
1320
  await this.initPromise;
1513
1321
  try {
1514
1322
  const args: any[] = [];
@@ -1534,19 +1342,7 @@ export abstract class Exchange {
1534
1342
  }
1535
1343
  }
1536
1344
 
1537
- private async _hostedFetchPositions(address?: string): Promise<Position[]> {
1538
- const resolvedAddr = resolveWalletAddress(this, address);
1539
- const route = HOSTED_METHOD_ROUTES.get("fetchPositions")!;
1540
- const path = formatRoutePath(route, { address: resolvedAddr });
1541
- const data = await _tradingRequest(this, { method: route.method, path });
1542
- const items = (Array.isArray(data) ? data : (data as Record<string, unknown>)?.["positions"] ?? []) as unknown[];
1543
- return (items as Record<string, unknown>[]).map(positionFromV0);
1544
- }
1545
-
1546
1345
  async fetchBalance(address?: string): Promise<Balance[]> {
1547
- if (this.isHostedTradingMode()) {
1548
- return this._hostedFetchBalance(address);
1549
- }
1550
1346
  await this.initPromise;
1551
1347
  try {
1552
1348
  const args: any[] = [];
@@ -1572,19 +1368,6 @@ export abstract class Exchange {
1572
1368
  }
1573
1369
  }
1574
1370
 
1575
- private async _hostedFetchBalance(address?: string): Promise<Balance[]> {
1576
- const resolvedAddr = resolveWalletAddress(this, address);
1577
- const route = HOSTED_METHOD_ROUTES.get("fetchBalance")!;
1578
- const path = formatRoutePath(route, { address: resolvedAddr });
1579
- const data = await _tradingRequest(this, { method: route.method, path });
1580
- // Hosted balance is a single USDC escrow record; wrap in an array
1581
- // to match the existing Balance[] return shape.
1582
- if (Array.isArray(data)) {
1583
- return (data as Record<string, unknown>[]).map(balanceFromV0);
1584
- }
1585
- return [balanceFromV0(data as Record<string, unknown>)];
1586
- }
1587
-
1588
1371
  async unwatchOrderBook(outcomeId: string | MarketOutcome): Promise<void> {
1589
1372
  await this.initPromise;
1590
1373
  try {
@@ -1784,7 +1567,7 @@ export abstract class Exchange {
1784
1567
  }
1785
1568
  }
1786
1569
 
1787
- async fetchMatchedMarkets(params?: any): Promise<any[]> {
1570
+ async fetchMatchedMarkets(params?: FetchMatchedMarketClustersParams): Promise<any[]> {
1788
1571
  await this.initPromise;
1789
1572
  try {
1790
1573
  const args: any[] = [];
@@ -2322,6 +2105,15 @@ export abstract class Exchange {
2322
2105
  if (params.fee !== undefined) {
2323
2106
  paramsDict.fee = params.fee;
2324
2107
  }
2108
+ if (params.tickSize !== undefined) {
2109
+ paramsDict.tickSize = params.tickSize;
2110
+ }
2111
+ if (params.negRisk !== undefined) {
2112
+ paramsDict.negRisk = params.negRisk;
2113
+ }
2114
+ if (params.onBehalfOf !== undefined) {
2115
+ paramsDict.onBehalfOf = params.onBehalfOf;
2116
+ }
2325
2117
 
2326
2118
  const response = await this.fetchWithRetry(`${this.resolveBaseUrl()}/api/${this.exchangeName}/buildOrder`, {
2327
2119
  method: 'POST',
@@ -2445,6 +2237,87 @@ export abstract class Exchange {
2445
2237
  return this._hostedSubmitOrder(built);
2446
2238
  }
2447
2239
 
2240
+ /**
2241
+ * Hosted-mode submitOrder: validate the stored build response, sign the
2242
+ * typed_data (and pull_typed_data for cross-chain venue sells), then
2243
+ * POST to `/v0/trade/submit-order`.
2244
+ *
2245
+ * Restored after PR #1058 (Limitless hosted wire-up) accidentally
2246
+ * removed it but left the call site + signing imports intact.
2247
+ */
2248
+ private async _hostedSubmitOrder(built: BuiltOrder): Promise<Order> {
2249
+ const signer = this.requireHostedSigner();
2250
+ if (!this.walletAddress) {
2251
+ throw new MissingWalletAddress(
2252
+ "hosted submitOrder requires walletAddress",
2253
+ );
2254
+ }
2255
+ const payload = built as unknown as Record<string, unknown>;
2256
+ const typedData = payload["typed_data"] as TypedData | undefined;
2257
+ if (!typedData) {
2258
+ throw new HostedInvalidSignature(0, "typed_data missing from built order");
2259
+ }
2260
+ const buildRequest = (payload["build_request"] as Record<string, unknown> | undefined)
2261
+ ?? ((payload["params"] as Record<string, unknown> | undefined)?.["build_request"] as Record<string, unknown> | undefined);
2262
+
2263
+ const side = String(buildRequest?.["side"] ?? "buy");
2264
+ const primaryRoute = this._hostedTypedDataRoute(side, false);
2265
+ validateTypedData(typedData, primaryRoute, this.walletAddress);
2266
+ if (buildRequest) {
2267
+ validateEconomics(typedData, primaryRoute, buildRequest, payload);
2268
+ }
2269
+
2270
+ const signature = await signer.signTypedData(typedData);
2271
+ verifySignature(typedData, signature, signer.address);
2272
+
2273
+ const body: Record<string, unknown> = {
2274
+ built_order_id: payload["built_order_id"],
2275
+ signature,
2276
+ };
2277
+
2278
+ const pullTypedData = payload["pull_typed_data"] as TypedData | undefined;
2279
+ if (pullTypedData) {
2280
+ const pullRoute = this._hostedTypedDataRoute(side, true);
2281
+ if (pullRoute) {
2282
+ validateTypedData(pullTypedData, pullRoute, this.walletAddress);
2283
+ }
2284
+ const pullSig = await signer.signTypedData(pullTypedData);
2285
+ verifySignature(pullTypedData, pullSig, signer.address);
2286
+ body["pull_signature"] = pullSig;
2287
+ }
2288
+
2289
+ const route = HOSTED_METHOD_ROUTES.get("submitOrder")!;
2290
+ const data = await _tradingRequest(this, { method: route.method, path: route.path, body });
2291
+ return orderFromV0(data as Record<string, unknown>);
2292
+ }
2293
+
2294
+ /**
2295
+ * Resolve the per-(venue, side, pull) typed-data schema route used by
2296
+ * `validateTypedData` / `validateEconomics`. Returns the cross-chain
2297
+ * pull-leg route name for Opinion sells and Limitless cross-chain orders.
2298
+ */
2299
+ private _hostedTypedDataRoute(side: string, isPull: boolean): string {
2300
+ const venue = this.exchangeName;
2301
+ const sideLower = side.toLowerCase();
2302
+ if (venue === "polymarket") {
2303
+ return sideLower === "sell" ? "polymarket_sell" : "polymarket_buy";
2304
+ }
2305
+ if (venue === "limitless") {
2306
+ if (sideLower === "buy") return "limitless_buy";
2307
+ return isPull ? "limitless_sell_base_pull" : "limitless_sell_polygon";
2308
+ }
2309
+ // opinion
2310
+ if (sideLower === "buy") return "opinion_buy";
2311
+ return isPull ? "opinion_sell_bsc_pull" : "opinion_sell_polygon";
2312
+ }
2313
+
2314
+ private _hostedCancelTypedDataRoute(isPull: boolean): string {
2315
+ const venue = this.exchangeName;
2316
+ if (venue === "polymarket") return "cancel_polymarket";
2317
+ if (venue === "limitless") return isPull ? "cancel_limitless_base_pull" : "cancel_limitless_polygon";
2318
+ return isPull ? "cancel_opinion_bsc_pull" : "cancel_opinion_polygon";
2319
+ }
2320
+
2448
2321
  /**
2449
2322
  * Construct the hosted build-order request body and validate inputs
2450
2323
  * locally per the v0 contract (denom/side compatibility, > 6-decimal
@@ -2624,6 +2497,15 @@ export abstract class Exchange {
2624
2497
  if (params.fee !== undefined) {
2625
2498
  paramsDict.fee = params.fee;
2626
2499
  }
2500
+ if (params.tickSize !== undefined) {
2501
+ paramsDict.tickSize = params.tickSize;
2502
+ }
2503
+ if (params.negRisk !== undefined) {
2504
+ paramsDict.negRisk = params.negRisk;
2505
+ }
2506
+ if (params.onBehalfOf !== undefined) {
2507
+ paramsDict.onBehalfOf = params.onBehalfOf;
2508
+ }
2627
2509
 
2628
2510
  const response = await this.fetchWithRetry(`${this.resolveBaseUrl()}/api/${this.exchangeName}/createOrder`, {
2629
2511
  method: 'POST',
@@ -3373,6 +3255,28 @@ export class SuiBets extends Exchange {
3373
3255
  // Backwards-compatible casing alias matching the Python SDK export.
3374
3256
  export const Suibets = SuiBets;
3375
3257
 
3258
+ /**
3259
+ * Rain exchange client.
3260
+ *
3261
+ * Rain is a permissionless AMM-plus-orderbook prediction market on Arbitrum One.
3262
+ * Reads are unauthenticated; trading requires an EVM privateKey.
3263
+ *
3264
+ * @example
3265
+ * ```typescript
3266
+ * const rain = new Rain();
3267
+ * const markets = await rain.fetchMarkets({ limit: 20 });
3268
+ *
3269
+ * // With wallet for trading
3270
+ * const me = new Rain({ privateKey: '0x...' });
3271
+ * await me.createOrder({ marketId, outcomeId, side: 'buy', type: 'market', amount: 10 });
3272
+ * ```
3273
+ */
3274
+ export class Rain extends Exchange {
3275
+ constructor(options: ExchangeOptions = {}) {
3276
+ super("rain", options);
3277
+ }
3278
+ }
3279
+
3376
3280
  /**
3377
3281
  * Mock exchange client.
3378
3282
  *
package/pmxt/constants.ts CHANGED
@@ -80,3 +80,10 @@ export const PREFUNDED_ESCROW_ADDRESSES: ReadonlySet<string> = new Set([
80
80
  * as venue-owned escrows. Currently empty; populated as venues onboard.
81
81
  */
82
82
  export const VENUE_ESCROW_ADDRESSES: ReadonlySet<string> = new Set<string>();
83
+
84
+ /**
85
+ * Limitless VenueEscrow contract addresses on Base (chain 8453).
86
+ */
87
+ export const LIMITLESS_VENUE_ESCROW_ADDRESSES: ReadonlySet<string> = new Set([
88
+ "0x34c42d01aad6ded00f1a6830d90b0e9204db7855",
89
+ ]);
@@ -24,6 +24,7 @@ export const HOSTED_TRADING_BASE_URL = "https://trade.pmxt.dev";
24
24
  export const HOSTED_TRADING_VENUES: ReadonlySet<string> = new Set([
25
25
  "polymarket",
26
26
  "opinion",
27
+ "limitless",
27
28
  ]);
28
29
 
29
30
  export interface HostedRoute {
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { InvalidSignature } from "./hosted-errors";
13
13
  import { to6dec } from "./hosted-mappers";
14
- import { TypedData } from "./signers";
14
+ import { TypedData, loadEthers } from "./signers";
15
15
 
16
16
  // The constants module is updated in a parallel-agent change to add these
17
17
  // allowlists. We import them at runtime so we don't hard-fail if the change
@@ -123,10 +123,19 @@ const VENUE_DOMAIN: DomainSchema = {
123
123
  chainId: 56,
124
124
  allowlistKey: "venue",
125
125
  };
126
+ const LIMITLESS_VENUE_DOMAIN: DomainSchema = {
127
+ name: "VenueEscrow",
128
+ version: "1",
129
+ chainId: 8453,
130
+ allowlistKey: "venue",
131
+ };
126
132
 
127
133
  export type HostedRoute =
128
134
  | "polymarket_buy"
129
135
  | "polymarket_sell"
136
+ | "limitless_buy"
137
+ | "limitless_sell_polygon"
138
+ | "limitless_sell_base_pull"
130
139
  | "opinion_buy"
131
140
  | "opinion_sell_polygon"
132
141
  | "opinion_sell_bsc_pull"
@@ -142,6 +151,27 @@ const SCHEMAS: Readonly<Record<HostedRoute, TypedDataSchema>> = {
142
151
  messageKeys: messageKeysFromFields(ORDER_PARAMS_FIELDS),
143
152
  walletField: "user",
144
153
  },
154
+ limitless_buy: {
155
+ primaryType: "CrossChainOrderParams",
156
+ domain: PREFUNDED_DOMAIN,
157
+ fields: CROSS_CHAIN_ORDER_PARAMS_FIELDS,
158
+ messageKeys: messageKeysFromFields(CROSS_CHAIN_ORDER_PARAMS_FIELDS),
159
+ walletField: "user",
160
+ },
161
+ limitless_sell_polygon: {
162
+ primaryType: "CrossChainSellPayParams",
163
+ domain: PREFUNDED_DOMAIN,
164
+ fields: CROSS_CHAIN_SELL_PAY_PARAMS_FIELDS,
165
+ messageKeys: messageKeysFromFields(CROSS_CHAIN_SELL_PAY_PARAMS_FIELDS),
166
+ walletField: "user",
167
+ },
168
+ limitless_sell_base_pull: {
169
+ primaryType: "CrossChainSellPullParams",
170
+ domain: LIMITLESS_VENUE_DOMAIN,
171
+ fields: CROSS_CHAIN_SELL_PULL_PARAMS_FIELDS,
172
+ messageKeys: messageKeysFromFields(CROSS_CHAIN_SELL_PULL_PARAMS_FIELDS),
173
+ walletField: "user",
174
+ },
145
175
  polymarket_sell: {
146
176
  primaryType: "SellOrderParams",
147
177
  domain: PREFUNDED_DOMAIN,
@@ -508,7 +538,33 @@ function validateOpinionMarketId(
508
538
  message: Record<string, unknown>,
509
539
  buildResponse: any,
510
540
  ): void {
511
- const expected = firstPresent(
541
+ // The current trading-API Opinion message schema does not embed
542
+ // opinion_market_id — the signed economic identity is the outcome
543
+ // tokenId. Validate that against the build response's resolved token.
544
+ // The legacy opinion_market_id check only applies when the message
545
+ // actually carries the field (older API versions).
546
+ const expectedToken = firstPresent(
547
+ getPath(buildResponse, "resolved", "token_id"),
548
+ getPath(buildResponse, "resolved", "tokenId"),
549
+ getField(buildResponse, "token_id"),
550
+ getField(buildResponse, "tokenId"),
551
+ );
552
+ const actualToken = firstPresent(
553
+ getField(message, "tokenId"),
554
+ getField(message, "token_id"),
555
+ );
556
+ if (actualToken === MISSING) economicFail("message.tokenId missing");
557
+ if (expectedToken !== MISSING && idValue(actualToken) !== idValue(expectedToken)) {
558
+ economicFail(`tokenId expected ${expectedToken} got ${actualToken}`);
559
+ }
560
+
561
+ const actualMarketId = firstPresent(
562
+ getField(message, "opinion_market_id"),
563
+ getField(message, "opinionMarketId"),
564
+ );
565
+ if (actualMarketId === MISSING) return; // current schema: tokenId-only message
566
+
567
+ const expectedMarketId = firstPresent(
512
568
  getPath(buildResponse, "resolved", "opinion_market_id"),
513
569
  getPath(buildResponse, "resolved", "opinionMarketId"),
514
570
  getField(buildResponse, "opinion_market_id"),
@@ -516,16 +572,9 @@ function validateOpinionMarketId(
516
572
  getPath(buildResponse, "params", "opinion_market_id"),
517
573
  getPath(buildResponse, "params", "opinionMarketId"),
518
574
  );
519
- if (expected === MISSING) economicFail("resolved.opinion_market_id missing");
520
-
521
- const actual = firstPresent(
522
- getField(message, "opinion_market_id"),
523
- getField(message, "opinionMarketId"),
524
- );
525
- if (actual === MISSING) economicFail("message.opinion_market_id missing");
526
-
527
- if (idValue(actual) !== idValue(expected)) {
528
- economicFail(`opinion_market_id expected ${expected} got ${actual}`);
575
+ if (expectedMarketId === MISSING) economicFail("resolved.opinion_market_id missing");
576
+ if (idValue(actualMarketId) !== idValue(expectedMarketId)) {
577
+ economicFail(`opinion_market_id expected ${expectedMarketId} got ${actualMarketId}`);
529
578
  }
530
579
  }
531
580
 
@@ -575,12 +624,11 @@ export function verifySignature(
575
624
 
576
625
  let ethers: any;
577
626
  try {
578
- // eslint-disable-next-line @typescript-eslint/no-require-imports
579
- ethers = require("ethers");
580
- } catch {
627
+ ethers = loadEthers("ethers is required for hosted signature verification");
628
+ } catch (err) {
581
629
  throw new InvalidSignature(
582
630
  0,
583
- "ethers is required for hosted signature verification",
631
+ err instanceof Error ? err.message : "ethers is required for hosted signature verification",
584
632
  );
585
633
  }
586
634
 
@@ -680,7 +728,9 @@ function allowedAddresses(
680
728
  const raw =
681
729
  key === "prefunded"
682
730
  ? (constants as any).PREFUNDED_ESCROW_ADDRESSES
683
- : (constants as any).VENUE_ESCROW_ADDRESSES;
731
+ : chainId === 8453
732
+ ? (constants as any).LIMITLESS_VENUE_ESCROW_ADDRESSES
733
+ : (constants as any).VENUE_ESCROW_ADDRESSES;
684
734
  const list: unknown[] = [];
685
735
  if (raw == null) {
686
736
  // empty
package/pmxt/models.ts CHANGED
@@ -1048,6 +1048,9 @@ export interface FetchMatchedMarketClustersParams extends MatchedClusterFilterPa
1048
1048
  url?: string;
1049
1049
  }
1050
1050
 
1051
+ // Alias for SDK consistency with Python
1052
+ export type MatchedMarketClusterParams = FetchMatchedMarketClustersParams;
1053
+
1051
1054
  /** Parameters for fetching matched event clusters. */
1052
1055
  export interface FetchMatchedEventClustersParams extends MatchedClusterFilterParams {
1053
1056
  /** Pass a UnifiedEvent directly instead of eventId/slug/url. */
package/pmxt/router.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  EventMatchResult,
14
14
  FetchMatchedEventClustersParams,
15
15
  FetchMatchedMarketClustersParams,
16
+ MatchedMarketClusterParams,
16
17
  MatchedEventCluster,
17
18
  MatchedMarketCluster,
18
19
  PriceComparison,
@@ -21,6 +22,15 @@ import {
21
22
  UnifiedEvent,
22
23
  } from "./models.js";
23
24
 
25
+ function withQuestionAlias<T extends UnifiedMarket>(market: T): T {
26
+ Object.defineProperty(market, 'question', {
27
+ get() { return this.title; },
28
+ enumerable: false,
29
+ configurable: true,
30
+ });
31
+ return market;
32
+ }
33
+
24
34
  function convertMarket(raw: any): UnifiedMarket {
25
35
  const outcomes = (raw.outcomes || []).map((o: any) => ({
26
36
  outcomeId: o.outcomeId,
@@ -44,7 +54,7 @@ function convertMarket(raw: any): UnifiedMarket {
44
54
  metadata: o.metadata,
45
55
  }) : undefined;
46
56
 
47
- return {
57
+ return withQuestionAlias({
48
58
  marketId: raw.marketId,
49
59
  title: raw.title,
50
60
  slug: raw.slug,
@@ -68,7 +78,7 @@ function convertMarket(raw: any): UnifiedMarket {
68
78
  no: convertOutcome(raw.no),
69
79
  up: convertOutcome(raw.up),
70
80
  down: convertOutcome(raw.down),
71
- };
81
+ });
72
82
  }
73
83
 
74
84
  function convertEvent(raw: any): UnifiedEvent {
@@ -91,7 +101,7 @@ function convertEvent(raw: any): UnifiedEvent {
91
101
  function parseMatchResult(raw: any): MatchResult {
92
102
  const marketData = raw.market || {};
93
103
  const market = convertMarket(marketData);
94
- return {
104
+ const result: MatchResult = {
95
105
  ...market,
96
106
  market,
97
107
  relation: raw.relation || 'identity',
@@ -101,6 +111,7 @@ function parseMatchResult(raw: any): MatchResult {
101
111
  bestAsk: raw.bestAsk ?? marketData.bestAsk,
102
112
  sourceMarket: raw.sourceMarket ? convertMarket(raw.sourceMarket) : undefined,
103
113
  };
114
+ return withQuestionAlias(result);
104
115
  }
105
116
 
106
117
  function normalizeQueryValue(value: unknown): unknown {