pmxtjs 2.48.6 → 2.49.1

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 (129) 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/OrderLevel.d.ts +6 -0
  14. package/dist/esm/generated/src/models/OrderLevel.js +2 -0
  15. package/dist/esm/generated/src/models/Position.d.ts +33 -9
  16. package/dist/esm/generated/src/models/Position.js +12 -12
  17. package/dist/esm/generated/src/models/UnifiedSeries.d.ts +4 -4
  18. package/dist/esm/generated/src/models/UserTrade.d.ts +18 -0
  19. package/dist/esm/generated/src/models/UserTrade.js +6 -0
  20. package/dist/esm/generated/src/models/index.d.ts +1 -0
  21. package/dist/esm/generated/src/models/index.js +1 -0
  22. package/dist/esm/index.d.ts +2 -0
  23. package/dist/esm/index.js +1 -0
  24. package/dist/esm/pmxt/client.d.ts +106 -5
  25. package/dist/esm/pmxt/client.js +400 -6
  26. package/dist/esm/pmxt/constants.d.ts +11 -0
  27. package/dist/esm/pmxt/constants.js +13 -0
  28. package/dist/esm/pmxt/errors.d.ts +3 -0
  29. package/dist/esm/pmxt/errors.js +9 -0
  30. package/dist/esm/pmxt/escrow.d.ts +39 -0
  31. package/dist/esm/pmxt/escrow.js +78 -0
  32. package/dist/esm/pmxt/feed-client.d.ts +3 -0
  33. package/dist/esm/pmxt/feed-client.js +11 -2
  34. package/dist/esm/pmxt/hosted-errors.d.ts +84 -0
  35. package/dist/esm/pmxt/hosted-errors.js +186 -0
  36. package/dist/esm/pmxt/hosted-mappers.d.ts +45 -0
  37. package/dist/esm/pmxt/hosted-mappers.js +291 -0
  38. package/dist/esm/pmxt/hosted-routing.d.ts +69 -0
  39. package/dist/esm/pmxt/hosted-routing.js +119 -0
  40. package/dist/esm/pmxt/hosted-typed-data.d.ts +36 -0
  41. package/dist/esm/pmxt/hosted-typed-data.js +580 -0
  42. package/dist/esm/pmxt/models.d.ts +46 -8
  43. package/dist/esm/pmxt/server-manager.d.ts +4 -0
  44. package/dist/esm/pmxt/server-manager.js +6 -0
  45. package/dist/esm/pmxt/signers.d.ts +57 -0
  46. package/dist/esm/pmxt/signers.js +50 -0
  47. package/dist/esm/pmxt/ws-client.js +2 -1
  48. package/dist/generated/src/models/Balance.d.ts +6 -0
  49. package/dist/generated/src/models/Balance.js +2 -0
  50. package/dist/generated/src/models/BuiltOrder.d.ts +6 -0
  51. package/dist/generated/src/models/BuiltOrder.js +2 -0
  52. package/dist/generated/src/models/ErrorDetail.d.ts +58 -2
  53. package/dist/generated/src/models/ErrorDetail.js +38 -0
  54. package/dist/generated/src/models/ExchangeOptions.d.ts +70 -0
  55. package/dist/generated/src/models/ExchangeOptions.js +60 -0
  56. package/dist/generated/src/models/MatchedMarketPair.d.ts +1 -1
  57. package/dist/generated/src/models/Order.d.ts +18 -0
  58. package/dist/generated/src/models/Order.js +6 -0
  59. package/dist/generated/src/models/OrderLevel.d.ts +6 -0
  60. package/dist/generated/src/models/OrderLevel.js +2 -0
  61. package/dist/generated/src/models/Position.d.ts +33 -9
  62. package/dist/generated/src/models/Position.js +12 -12
  63. package/dist/generated/src/models/UnifiedSeries.d.ts +4 -4
  64. package/dist/generated/src/models/UserTrade.d.ts +18 -0
  65. package/dist/generated/src/models/UserTrade.js +6 -0
  66. package/dist/generated/src/models/index.d.ts +1 -0
  67. package/dist/generated/src/models/index.js +1 -0
  68. package/dist/index.d.ts +2 -0
  69. package/dist/index.js +1 -0
  70. package/dist/pmxt/client.d.ts +106 -5
  71. package/dist/pmxt/client.js +399 -5
  72. package/dist/pmxt/constants.d.ts +11 -0
  73. package/dist/pmxt/constants.js +14 -1
  74. package/dist/pmxt/errors.d.ts +3 -0
  75. package/dist/pmxt/errors.js +11 -1
  76. package/dist/pmxt/escrow.d.ts +39 -0
  77. package/dist/pmxt/escrow.js +82 -0
  78. package/dist/pmxt/feed-client.d.ts +3 -0
  79. package/dist/pmxt/feed-client.js +11 -2
  80. package/dist/pmxt/hosted-errors.d.ts +84 -0
  81. package/dist/pmxt/hosted-errors.js +201 -0
  82. package/dist/pmxt/hosted-mappers.d.ts +45 -0
  83. package/dist/pmxt/hosted-mappers.js +302 -0
  84. package/dist/pmxt/hosted-routing.d.ts +69 -0
  85. package/dist/pmxt/hosted-routing.js +126 -0
  86. package/dist/pmxt/hosted-typed-data.d.ts +36 -0
  87. package/dist/pmxt/hosted-typed-data.js +619 -0
  88. package/dist/pmxt/models.d.ts +46 -8
  89. package/dist/pmxt/server-manager.d.ts +4 -0
  90. package/dist/pmxt/server-manager.js +6 -0
  91. package/dist/pmxt/signers.d.ts +57 -0
  92. package/dist/pmxt/signers.js +55 -0
  93. package/dist/pmxt/ws-client.js +2 -1
  94. package/generated/.openapi-generator/FILES +2 -0
  95. package/generated/docs/Balance.md +2 -0
  96. package/generated/docs/BuiltOrder.md +2 -0
  97. package/generated/docs/ErrorDetail.md +9 -0
  98. package/generated/docs/ExchangeOptions.md +47 -0
  99. package/generated/docs/Order.md +6 -0
  100. package/generated/docs/OrderLevel.md +2 -0
  101. package/generated/docs/Position.md +9 -0
  102. package/generated/docs/UserTrade.md +6 -0
  103. package/generated/package.json +1 -1
  104. package/generated/src/models/Balance.ts +8 -0
  105. package/generated/src/models/BuiltOrder.ts +8 -0
  106. package/generated/src/models/ErrorDetail.ts +67 -2
  107. package/generated/src/models/ExchangeOptions.ts +115 -0
  108. package/generated/src/models/MatchedMarketPair.ts +1 -1
  109. package/generated/src/models/Order.ts +24 -0
  110. package/generated/src/models/OrderLevel.ts +8 -0
  111. package/generated/src/models/Position.ts +45 -17
  112. package/generated/src/models/UnifiedSeries.ts +4 -4
  113. package/generated/src/models/UserTrade.ts +24 -0
  114. package/generated/src/models/index.ts +1 -0
  115. package/index.ts +1 -0
  116. package/package.json +11 -2
  117. package/pmxt/client.ts +495 -9
  118. package/pmxt/constants.ts +15 -0
  119. package/pmxt/errors.ts +11 -0
  120. package/pmxt/escrow.ts +93 -0
  121. package/pmxt/feed-client.ts +14 -2
  122. package/pmxt/hosted-errors.ts +216 -0
  123. package/pmxt/hosted-mappers.ts +312 -0
  124. package/pmxt/hosted-routing.ts +165 -0
  125. package/pmxt/hosted-typed-data.ts +767 -0
  126. package/pmxt/models.ts +65 -8
  127. package/pmxt/server-manager.ts +7 -0
  128. package/pmxt/signers.ts +86 -0
  129. package/pmxt/ws-client.ts +2 -1
@@ -7,9 +7,21 @@
7
7
  import { Configuration, DefaultApi, } from "../generated/src/index.js";
8
8
  import { MarketList, } from "./models.js";
9
9
  import { ServerManager } from "./server-manager.js";
10
- import { PmxtError, fromServerError } from "./errors.js";
10
+ import { PmxtError, fromServerError, InvalidOrder, NotSupported } from "./errors.js";
11
11
  import { resolvePmxtBaseUrl } from "./constants.js";
12
12
  import { SidecarWsClient } from "./ws-client.js";
13
+ // Hosted-mode trading dispatch.
14
+ // These modules are introduced as part of the hosted trading mode rollout.
15
+ // Some of them may be authored by parallel agents; until they all land, the
16
+ // import names below are the conventional ones from the plan. Cross-module
17
+ // "Cannot find module" errors during the parallel landing window resolve
18
+ // once the matching files exist.
19
+ import { HOSTED_TRADING_VENUES, _tradingRequest, resolveWalletAddress, formatRoutePath, HOSTED_METHOD_ROUTES, } from "./hosted-routing.js";
20
+ import { orderFromV0, positionFromV0, balanceFromV0, userTradeFromV0, to6dec, } from "./hosted-mappers.js";
21
+ import { validateTypedData, validateEconomics, verifySignature, } from "./hosted-typed-data.js";
22
+ import { EthersSigner } from "./signers.js";
23
+ import { Escrow } from "./escrow.js";
24
+ import { MissingWalletAddress, InvalidSignature as HostedInvalidSignature, } from "./hosted-errors.js";
13
25
  /**
14
26
  * Resolve a MarketOutcome shorthand to a plain outcome ID string.
15
27
  * Accepts either a raw string ID or a MarketOutcome object.
@@ -136,10 +148,13 @@ export class Exchange {
136
148
  "kalshi",
137
149
  "opinion",
138
150
  ]);
151
+ // Public so structural interfaces like `HostedClientLike`
152
+ // (./hosted-routing) can read the venue name and hosted credentials
153
+ // without violating protected-access on this base class.
139
154
  exchangeName;
155
+ pmxtApiKey;
140
156
  apiKey;
141
157
  privateKey;
142
- pmxtApiKey;
143
158
  proxyAddress;
144
159
  signatureType;
145
160
  api;
@@ -147,6 +162,12 @@ export class Exchange {
147
162
  serverManager;
148
163
  initPromise;
149
164
  isHosted;
165
+ /** Wallet address used for hosted endpoints that operate on a wallet. */
166
+ walletAddress;
167
+ /** External signer used for hosted writes. */
168
+ signer;
169
+ /** Escrow namespace — populated in hosted mode for trading-allowlisted venues. */
170
+ escrow;
150
171
  _hostedAccount;
151
172
  _accountDiscoveryPromise;
152
173
  /**
@@ -166,6 +187,8 @@ export class Exchange {
166
187
  this.privateKey = options.privateKey;
167
188
  this.proxyAddress = options.proxyAddress;
168
189
  this.signatureType = options.signatureType;
190
+ this.walletAddress = options.walletAddress;
191
+ this.signer = options.signer;
169
192
  // Resolve base URL + hosted API key via the shared precedence
170
193
  // rules. See constants.ts for the full resolution table.
171
194
  const resolved = resolvePmxtBaseUrl({
@@ -175,6 +198,26 @@ export class Exchange {
175
198
  const baseUrl = resolved.baseUrl;
176
199
  this.pmxtApiKey = resolved.pmxtApiKey;
177
200
  this.isHosted = resolved.isHosted;
201
+ // Hosted trading bridge: if the caller passed a privateKey but no
202
+ // explicit signer, lazily wrap it in an EthersSigner so that
203
+ // `pmxt.Polymarket(pmxtApiKey, privateKey)` just works without the
204
+ // user touching `signer`. EthersSigner's constructor synchronously
205
+ // builds an ethers.Wallet, so this remains a synchronous bridge —
206
+ // the constructor stays sync.
207
+ if (this.pmxtApiKey && this.privateKey && !this.signer) {
208
+ try {
209
+ this.signer = new EthersSigner(this.privateKey);
210
+ }
211
+ catch {
212
+ // ethers not installed — defer the error to the first
213
+ // hosted write that actually needs the signer. Read-only
214
+ // hosted callers don't need ethers.
215
+ }
216
+ }
217
+ // Instantiate Escrow namespace for hosted-trading-allowlisted venues.
218
+ if (this.pmxtApiKey && HOSTED_TRADING_VENUES.has(this.exchangeName)) {
219
+ this.escrow = new Escrow(this);
220
+ }
178
221
  // auto_start_server defaults: true for local, false for hosted.
179
222
  // An explicit value in the options always wins.
180
223
  const autoStartServer = options.autoStartServer !== undefined
@@ -255,6 +298,25 @@ export class Exchange {
255
298
  }
256
299
  return headers;
257
300
  }
301
+ /**
302
+ * Returns true when this client should dispatch trading methods through
303
+ * the hosted PMXT trading API (`pmxtApiKey` set AND venue is on the
304
+ * hosted-trading allowlist). Used to gate every Group A method.
305
+ */
306
+ isHostedTradingMode() {
307
+ return Boolean(this.pmxtApiKey) && HOSTED_TRADING_VENUES.has(this.exchangeName);
308
+ }
309
+ /**
310
+ * Require a configured signer for a hosted write. Returns the signer or
311
+ * throws {@link MissingWalletAddress} (consistent with the error class
312
+ * surfaced for missing wallet wiring on hosted writes).
313
+ */
314
+ requireHostedSigner() {
315
+ if (!this.signer) {
316
+ throw new MissingWalletAddress("hosted write requires a signer (pass `signer` or `privateKey`)");
317
+ }
318
+ return this.signer;
319
+ }
258
320
  /**
259
321
  * Resolve the current sidecar base URL.
260
322
  *
@@ -849,6 +911,9 @@ export class Exchange {
849
911
  }
850
912
  }
851
913
  async submitOrder(built) {
914
+ if (this.isHostedTradingMode()) {
915
+ return this._hostedSubmitOrder(built);
916
+ }
852
917
  await this.initPromise;
853
918
  try {
854
919
  const args = [];
@@ -875,7 +940,80 @@ export class Exchange {
875
940
  throw new PmxtError(`Failed to submitOrder: ${error}`);
876
941
  }
877
942
  }
943
+ /**
944
+ * Hosted-mode submitOrder: validate the stored build response, sign the
945
+ * typed_data (and pull_typed_data for Opinion cross-chain sells), then
946
+ * POST to `/v0/trade/submit-order`.
947
+ */
948
+ async _hostedSubmitOrder(built) {
949
+ const signer = this.requireHostedSigner();
950
+ if (!this.walletAddress) {
951
+ throw new MissingWalletAddress("hosted submitOrder requires walletAddress");
952
+ }
953
+ // BuiltOrder is the SDK-side wrapper around the build response —
954
+ // expect typed_data, optional pull_typed_data, built_order_id, and
955
+ // the originating build_request to be present.
956
+ const payload = built;
957
+ const typedData = payload["typed_data"];
958
+ if (!typedData) {
959
+ throw new HostedInvalidSignature(0, "typed_data missing from built order");
960
+ }
961
+ const buildRequest = payload["build_request"]
962
+ ?? payload["params"]?.["build_request"];
963
+ const side = String(buildRequest?.["side"] ?? "buy");
964
+ const primaryRoute = this._hostedTypedDataRoute(side, false);
965
+ // Layer 1: schema, Layer 2: economics.
966
+ validateTypedData(typedData, primaryRoute, this.walletAddress);
967
+ if (buildRequest) {
968
+ validateEconomics(typedData, primaryRoute, buildRequest, payload);
969
+ }
970
+ const signature = await signer.signTypedData(typedData);
971
+ // Layer 3: post-sign recovery + canonical check.
972
+ verifySignature(typedData, signature, signer.address);
973
+ const body = {
974
+ built_order_id: payload["built_order_id"],
975
+ signature,
976
+ };
977
+ const pullTypedData = payload["pull_typed_data"];
978
+ if (pullTypedData) {
979
+ const pullRoute = this._hostedTypedDataRoute(side, true);
980
+ if (pullRoute) {
981
+ validateTypedData(pullTypedData, pullRoute, this.walletAddress);
982
+ }
983
+ const pullSig = await signer.signTypedData(pullTypedData);
984
+ verifySignature(pullTypedData, pullSig, signer.address);
985
+ body["pull_signature"] = pullSig;
986
+ }
987
+ const route = HOSTED_METHOD_ROUTES.get("submitOrder");
988
+ const data = await _tradingRequest(this, { method: route.method, path: route.path, body });
989
+ return orderFromV0(data);
990
+ }
991
+ /**
992
+ * Resolve the per-(venue, side, pull) typed-data schema route used by
993
+ * `validateTypedData` / `validateEconomics`. Returns undefined for the
994
+ * pull leg when a venue/side combo doesn't have one.
995
+ */
996
+ _hostedTypedDataRoute(side, isPull) {
997
+ const venue = this.exchangeName;
998
+ const sideLower = side.toLowerCase();
999
+ if (venue === "polymarket") {
1000
+ return sideLower === "sell" ? "polymarket_sell" : "polymarket_buy";
1001
+ }
1002
+ // opinion
1003
+ if (sideLower === "buy")
1004
+ return "opinion_buy";
1005
+ // sell — polygon, or BSC pull leg for cross-chain
1006
+ return isPull ? "opinion_sell_bsc_pull" : "opinion_sell_polygon";
1007
+ }
1008
+ _hostedCancelTypedDataRoute(isPull) {
1009
+ if (this.exchangeName === "polymarket")
1010
+ return "cancel_polymarket";
1011
+ return isPull ? "cancel_opinion_bsc_pull" : "cancel_opinion_polygon";
1012
+ }
878
1013
  async cancelOrder(orderId) {
1014
+ if (this.isHostedTradingMode()) {
1015
+ return this._hostedCancelOrder(orderId);
1016
+ }
879
1017
  await this.initPromise;
880
1018
  try {
881
1019
  const args = [];
@@ -902,7 +1040,47 @@ export class Exchange {
902
1040
  throw new PmxtError(`Failed to cancelOrder: ${error}`);
903
1041
  }
904
1042
  }
1043
+ /**
1044
+ * Hosted-mode cancelOrder: build the cancel typed_data on the server,
1045
+ * validate + sign (dual-sign for Opinion cross-chain), then submit.
1046
+ */
1047
+ async _hostedCancelOrder(orderId) {
1048
+ const signer = this.requireHostedSigner();
1049
+ if (!this.walletAddress) {
1050
+ throw new MissingWalletAddress("hosted cancelOrder requires walletAddress");
1051
+ }
1052
+ const buildRoute = HOSTED_METHOD_ROUTES.get("cancelOrderBuild");
1053
+ const buildResp = await _tradingRequest(this, {
1054
+ method: buildRoute.method,
1055
+ path: buildRoute.path,
1056
+ body: { order_id: orderId },
1057
+ });
1058
+ const typedData = buildResp["typed_data"];
1059
+ if (!typedData) {
1060
+ throw new HostedInvalidSignature(0, "typed_data missing from cancel build response");
1061
+ }
1062
+ validateTypedData(typedData, this._hostedCancelTypedDataRoute(false), this.walletAddress);
1063
+ const signature = await signer.signTypedData(typedData);
1064
+ verifySignature(typedData, signature, signer.address);
1065
+ const body = {
1066
+ cancel_id: buildResp["cancel_id"],
1067
+ signature,
1068
+ };
1069
+ const pullTypedData = buildResp["pull_typed_data"];
1070
+ if (pullTypedData) {
1071
+ validateTypedData(pullTypedData, this._hostedCancelTypedDataRoute(true), this.walletAddress);
1072
+ const pullSig = await signer.signTypedData(pullTypedData);
1073
+ verifySignature(pullTypedData, pullSig, signer.address);
1074
+ body["pull_signature"] = pullSig;
1075
+ }
1076
+ const route = HOSTED_METHOD_ROUTES.get("cancelOrder");
1077
+ const data = await _tradingRequest(this, { method: route.method, path: route.path, body });
1078
+ return orderFromV0(data);
1079
+ }
905
1080
  async fetchOrder(orderId) {
1081
+ if (this.isHostedTradingMode()) {
1082
+ return this._hostedFetchOrder(orderId);
1083
+ }
906
1084
  await this.initPromise;
907
1085
  try {
908
1086
  const args = [];
@@ -929,7 +1107,16 @@ export class Exchange {
929
1107
  throw new PmxtError(`Failed to fetchOrder: ${error}`);
930
1108
  }
931
1109
  }
1110
+ async _hostedFetchOrder(orderId) {
1111
+ const route = HOSTED_METHOD_ROUTES.get("fetchOrder");
1112
+ const path = formatRoutePath(route, { order_id: orderId });
1113
+ const data = await _tradingRequest(this, { method: route.method, path });
1114
+ return orderFromV0(data);
1115
+ }
932
1116
  async fetchOpenOrders(marketId) {
1117
+ if (this.isHostedTradingMode()) {
1118
+ return this._hostedFetchOpenOrders(marketId);
1119
+ }
933
1120
  await this.initPromise;
934
1121
  try {
935
1122
  const args = [];
@@ -957,7 +1144,24 @@ export class Exchange {
957
1144
  throw new PmxtError(`Failed to fetchOpenOrders: ${error}`);
958
1145
  }
959
1146
  }
1147
+ async _hostedFetchOpenOrders(marketId) {
1148
+ const address = resolveWalletAddress(this, undefined);
1149
+ const route = HOSTED_METHOD_ROUTES.get("fetchOpenOrders");
1150
+ const params = { address };
1151
+ if (marketId !== undefined)
1152
+ params["market_id"] = marketId;
1153
+ const data = await _tradingRequest(this, {
1154
+ method: route.method,
1155
+ path: route.path,
1156
+ params,
1157
+ });
1158
+ const items = (Array.isArray(data) ? data : data?.["orders"] ?? []);
1159
+ return items.map(orderFromV0);
1160
+ }
960
1161
  async fetchMyTrades(params) {
1162
+ if (this.isHostedTradingMode()) {
1163
+ return this._hostedFetchMyTrades(params);
1164
+ }
961
1165
  await this.initPromise;
962
1166
  try {
963
1167
  const args = [];
@@ -985,7 +1189,35 @@ export class Exchange {
985
1189
  throw new PmxtError(`Failed to fetchMyTrades: ${error}`);
986
1190
  }
987
1191
  }
1192
+ async _hostedFetchMyTrades(params) {
1193
+ const address = resolveWalletAddress(this, undefined);
1194
+ const route = HOSTED_METHOD_ROUTES.get("fetchMyTrades");
1195
+ const path = formatRoutePath(route, { address });
1196
+ const q = {};
1197
+ if (params?.marketId)
1198
+ q["market_id"] = params.marketId;
1199
+ if (params?.outcomeId)
1200
+ q["outcome_id"] = params.outcomeId;
1201
+ if (params?.limit !== undefined)
1202
+ q["limit"] = String(params.limit);
1203
+ if (params?.cursor)
1204
+ q["cursor"] = params.cursor;
1205
+ if (params?.since)
1206
+ q["since"] = String(params.since.getTime());
1207
+ if (params?.until)
1208
+ q["until"] = String(params.until.getTime());
1209
+ const data = await _tradingRequest(this, {
1210
+ method: route.method,
1211
+ path,
1212
+ params: Object.keys(q).length ? q : undefined,
1213
+ });
1214
+ const items = (Array.isArray(data) ? data : data?.["trades"] ?? []);
1215
+ return items.map(userTradeFromV0);
1216
+ }
988
1217
  async fetchClosedOrders(params) {
1218
+ if (this.isHostedTradingMode()) {
1219
+ throw new NotSupported("Settled orders are modeled as trades — use fetchMyTrades().");
1220
+ }
989
1221
  await this.initPromise;
990
1222
  try {
991
1223
  const args = [];
@@ -1014,6 +1246,9 @@ export class Exchange {
1014
1246
  }
1015
1247
  }
1016
1248
  async fetchAllOrders(params) {
1249
+ if (this.isHostedTradingMode()) {
1250
+ throw new NotSupported("Use fetchOpenOrders() and fetchMyTrades() separately.");
1251
+ }
1017
1252
  await this.initPromise;
1018
1253
  try {
1019
1254
  const args = [];
@@ -1042,6 +1277,9 @@ export class Exchange {
1042
1277
  }
1043
1278
  }
1044
1279
  async fetchPositions(address) {
1280
+ if (this.isHostedTradingMode()) {
1281
+ return this._hostedFetchPositions(address);
1282
+ }
1045
1283
  await this.initPromise;
1046
1284
  try {
1047
1285
  const args = [];
@@ -1069,7 +1307,18 @@ export class Exchange {
1069
1307
  throw new PmxtError(`Failed to fetchPositions: ${error}`);
1070
1308
  }
1071
1309
  }
1310
+ async _hostedFetchPositions(address) {
1311
+ const resolvedAddr = resolveWalletAddress(this, address);
1312
+ const route = HOSTED_METHOD_ROUTES.get("fetchPositions");
1313
+ const path = formatRoutePath(route, { address: resolvedAddr });
1314
+ const data = await _tradingRequest(this, { method: route.method, path });
1315
+ const items = (Array.isArray(data) ? data : data?.["positions"] ?? []);
1316
+ return items.map(positionFromV0);
1317
+ }
1072
1318
  async fetchBalance(address) {
1319
+ if (this.isHostedTradingMode()) {
1320
+ return this._hostedFetchBalance(address);
1321
+ }
1073
1322
  await this.initPromise;
1074
1323
  try {
1075
1324
  const args = [];
@@ -1097,6 +1346,18 @@ export class Exchange {
1097
1346
  throw new PmxtError(`Failed to fetchBalance: ${error}`);
1098
1347
  }
1099
1348
  }
1349
+ async _hostedFetchBalance(address) {
1350
+ const resolvedAddr = resolveWalletAddress(this, address);
1351
+ const route = HOSTED_METHOD_ROUTES.get("fetchBalance");
1352
+ const path = formatRoutePath(route, { address: resolvedAddr });
1353
+ const data = await _tradingRequest(this, { method: route.method, path });
1354
+ // Hosted balance is a single USDC escrow record; wrap in an array
1355
+ // to match the existing Balance[] return shape.
1356
+ if (Array.isArray(data)) {
1357
+ return data.map(balanceFromV0);
1358
+ }
1359
+ return [balanceFromV0(data)];
1360
+ }
1100
1361
  async unwatchOrderBook(outcomeId) {
1101
1362
  await this.initPromise;
1102
1363
  try {
@@ -1769,6 +2030,9 @@ export class Exchange {
1769
2030
  * ```
1770
2031
  */
1771
2032
  async buildOrder(params) {
2033
+ if (this.isHostedTradingMode()) {
2034
+ return this._hostedBuildOrder(params);
2035
+ }
1772
2036
  if (this.isHosted) {
1773
2037
  throw new PmxtError("Trade execution is not available through the hosted API. " +
1774
2038
  "Use the local PMXT SDK with your venue credentials instead. " +
@@ -1899,6 +2163,106 @@ export class Exchange {
1899
2163
  }
1900
2164
  return convertOrder(submitData);
1901
2165
  }
2166
+ /**
2167
+ * Hosted-mode buildOrder: validate inputs locally, then POST to the
2168
+ * trading service's `build-order` endpoint and return a BuiltOrder
2169
+ * that carries the original build_request for Layer-2 economic checks
2170
+ * at submit time.
2171
+ */
2172
+ async _hostedBuildOrder(params) {
2173
+ const body = this._hostedBuildOrderBody(params);
2174
+ const route = HOSTED_METHOD_ROUTES.get("buildOrder");
2175
+ const data = await _tradingRequest(this, {
2176
+ method: route.method,
2177
+ path: route.path,
2178
+ body,
2179
+ });
2180
+ // Attach the originating build_request so submit can run economic
2181
+ // validation without an extra catalog round-trip.
2182
+ const built = { ...data, build_request: body };
2183
+ return built;
2184
+ }
2185
+ /**
2186
+ * Hosted-mode createOrder: build → sign → submit single-call wrapper.
2187
+ */
2188
+ async _hostedCreateOrder(params) {
2189
+ const built = await this._hostedBuildOrder(params);
2190
+ return this._hostedSubmitOrder(built);
2191
+ }
2192
+ /**
2193
+ * Construct the hosted build-order request body and validate inputs
2194
+ * locally per the v0 contract (denom/side compatibility, > 6-decimal
2195
+ * precision rejected via {@link to6dec}).
2196
+ */
2197
+ _hostedBuildOrderBody(params) {
2198
+ let marketId = params.marketId;
2199
+ let outcomeId = params.outcomeId;
2200
+ if (params.outcome) {
2201
+ if (marketId !== undefined || outcomeId !== undefined) {
2202
+ throw new InvalidOrder("cannot specify both 'outcome' and 'marketId'/'outcomeId'");
2203
+ }
2204
+ const outcome = params.outcome;
2205
+ if (!outcome.marketId) {
2206
+ throw new InvalidOrder("outcome.marketId is not set; ensure the outcome comes from a fetched market");
2207
+ }
2208
+ marketId = outcome.marketId;
2209
+ outcomeId = outcome.outcomeId;
2210
+ }
2211
+ const side = String(params.side);
2212
+ const orderType = String(params.type ?? "market");
2213
+ const denom = params["denom"];
2214
+ // denom/side compatibility per v0:
2215
+ // market buy -> denom='usdc'
2216
+ // market sell -> denom='shares'
2217
+ // any limit -> denom='shares'
2218
+ let resolvedDenom;
2219
+ if (orderType === "market") {
2220
+ if (side === "buy") {
2221
+ if (denom && denom !== "usdc") {
2222
+ throw new InvalidOrder("market buy requires denom='usdc'");
2223
+ }
2224
+ resolvedDenom = "usdc";
2225
+ }
2226
+ else if (side === "sell") {
2227
+ if (denom && denom !== "shares") {
2228
+ throw new InvalidOrder("market sell requires denom='shares'");
2229
+ }
2230
+ resolvedDenom = "shares";
2231
+ }
2232
+ else {
2233
+ throw new InvalidOrder(`unknown side: ${side}`);
2234
+ }
2235
+ }
2236
+ else {
2237
+ if (denom && denom !== "shares") {
2238
+ throw new InvalidOrder("limit orders require denom='shares'");
2239
+ }
2240
+ resolvedDenom = "shares";
2241
+ }
2242
+ if (!(Number(params.amount) > 0)) {
2243
+ throw new InvalidOrder("amount must be positive");
2244
+ }
2245
+ // to6dec throws InvalidOrder for sub-micro precision.
2246
+ const amount6dec = to6dec(params.amount).toString();
2247
+ const body = {
2248
+ market_id: marketId,
2249
+ outcome_id: outcomeId,
2250
+ side,
2251
+ order_type: orderType,
2252
+ denom: resolvedDenom,
2253
+ amount: params.amount,
2254
+ amount_6dec: amount6dec,
2255
+ };
2256
+ if (params.price !== undefined)
2257
+ body["price"] = params.price;
2258
+ const extra = params;
2259
+ if (extra["slippage_pct"] !== undefined) {
2260
+ body["slippage_pct"] = extra["slippage_pct"];
2261
+ }
2262
+ if (this.walletAddress)
2263
+ body["user_address"] = this.walletAddress;
2264
+ return body;
2265
+ }
1902
2266
  /**
1903
2267
  * @example
1904
2268
  * ```typescript
@@ -1913,10 +2277,15 @@ export class Exchange {
1913
2277
  * ```
1914
2278
  */
1915
2279
  async createOrder(params) {
2280
+ // SOR escape path (preserved): legacy hosted SOR flow uses a venue-side
2281
+ // SDK to execute the legs, only when a privateKey is present.
2282
+ if (this.isHosted && this.exchangeName === 'sor' && this.privateKey) {
2283
+ return this._executeSorOrder(params);
2284
+ }
2285
+ if (this.isHostedTradingMode()) {
2286
+ return this._hostedCreateOrder(params);
2287
+ }
1916
2288
  if (this.isHosted) {
1917
- if (this.exchangeName === 'sor' && this.privateKey) {
1918
- return this._executeSorOrder(params);
1919
- }
1920
2289
  throw new PmxtError("Trade execution is not available through the hosted API. " +
1921
2290
  "Use the local PMXT SDK with your venue credentials instead. " +
1922
2291
  "See https://pmxt.dev/docs/quickstart for setup instructions.");
@@ -2548,15 +2917,40 @@ export class Hyperliquid extends Exchange {
2548
2917
  /**
2549
2918
  * SuiBets exchange client.
2550
2919
  *
2920
+ * SuiBets is a decentralised P2P sports betting exchange on Sui mainnet.
2921
+ * No house edge. 2% platform fee.
2922
+ * Contract: 0xd51fe151bec66a15b086a67c1cfce9b05759ddac1d73fcd3e14324ad202b2e59
2923
+ *
2551
2924
  * @example
2552
2925
  * ```typescript
2553
2926
  * const suibets = new SuiBets();
2554
- * const markets = await suibets.fetchMarkets();
2927
+ * const markets = await suibets.fetchMarkets({ limit: 20 });
2928
+ *
2929
+ * // With wallet for fetchPositions()
2930
+ * const me = new SuiBets({ walletAddress: '0xabc...' });
2931
+ * const positions = await me.fetchPositions();
2555
2932
  * ```
2556
2933
  */
2557
2934
  export class SuiBets extends Exchange {
2935
+ _walletAddress;
2558
2936
  constructor(options = {}) {
2559
2937
  super("suibets", options);
2938
+ this._walletAddress = options.walletAddress;
2939
+ }
2940
+ /**
2941
+ * Includes walletAddress in the credentials sent to the sidecar so
2942
+ * that fetchPositions() can reach the /api/p2p/my endpoint.
2943
+ * Falls back to SUIBETS_WALLET_ADDRESS env var on the sidecar side
2944
+ * when walletAddress is not set here.
2945
+ */
2946
+ getCredentials() {
2947
+ const base = super.getCredentials();
2948
+ if (!this._walletAddress)
2949
+ return base;
2950
+ return {
2951
+ ...(base ?? {}),
2952
+ walletAddress: this._walletAddress,
2953
+ };
2560
2954
  }
2561
2955
  }
2562
2956
  /**
@@ -51,3 +51,14 @@ export declare function resolvePmxtBaseUrl(args: {
51
51
  pmxtApiKey?: string;
52
52
  isHosted: boolean;
53
53
  };
54
+ /**
55
+ * Lowercase 0x-prefixed escrow addresses that are pre-funded by pmxt for
56
+ * hosted trading. Orders routed through these addresses use the shared
57
+ * escrow balance rather than a per-venue deposit.
58
+ */
59
+ export declare const PREFUNDED_ESCROW_ADDRESSES: ReadonlySet<string>;
60
+ /**
61
+ * Lowercase 0x-prefixed escrow addresses that the hosted trading API treats
62
+ * as venue-owned escrows. Currently empty; populated as venues onboard.
63
+ */
64
+ export declare const VENUE_ESCROW_ADDRESSES: ReadonlySet<string>;
@@ -58,3 +58,16 @@ export function resolvePmxtBaseUrl(args) {
58
58
  return pick(HOSTED_URL);
59
59
  return pick(LOCAL_URL);
60
60
  }
61
+ /**
62
+ * Lowercase 0x-prefixed escrow addresses that are pre-funded by pmxt for
63
+ * hosted trading. Orders routed through these addresses use the shared
64
+ * escrow balance rather than a per-venue deposit.
65
+ */
66
+ export const PREFUNDED_ESCROW_ADDRESSES = new Set([
67
+ "0x3ad326f78b1390b9a5dc5f00e7f62f8632de23e2",
68
+ ]);
69
+ /**
70
+ * Lowercase 0x-prefixed escrow addresses that the hosted trading API treats
71
+ * as venue-owned escrows. Currently empty; populated as venues onboard.
72
+ */
73
+ export const VENUE_ESCROW_ADDRESSES = new Set();
@@ -51,5 +51,8 @@ export declare class NetworkError extends PmxtError {
51
51
  export declare class ExchangeNotAvailable extends PmxtError {
52
52
  constructor(message: string, exchange?: string);
53
53
  }
54
+ export declare class NotSupported extends PmxtError {
55
+ constructor(message: string, exchange?: string);
56
+ }
54
57
  /** Convert a server error response object into a typed PmxtError. */
55
58
  export declare function fromServerError(errorData: unknown): PmxtError;
@@ -90,6 +90,11 @@ export class ExchangeNotAvailable extends PmxtError {
90
90
  super(message, "EXCHANGE_NOT_AVAILABLE", true, exchange);
91
91
  }
92
92
  }
93
+ export class NotSupported extends PmxtError {
94
+ constructor(message, exchange) {
95
+ super(message, "NOT_SUPPORTED", false, exchange);
96
+ }
97
+ }
93
98
  // Error code to class mapping
94
99
  const ERROR_CODE_MAP = {
95
100
  BAD_REQUEST: BadRequest,
@@ -130,3 +135,7 @@ export function fromServerError(errorData) {
130
135
  }
131
136
  return new PmxtError(message, code, retryable, exchange);
132
137
  }
138
+ // Hosted error classes live in ./hosted-errors. Re-exported from index.ts
139
+ // at the package root, NOT here — re-exporting from errors.ts creates a
140
+ // circular dependency (hosted-errors imports PmxtError from errors.ts)
141
+ // that crashes tsx/CJS module loaders even though tsc and ts-jest tolerate it.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Hosted-mode escrow namespace.
3
+ *
4
+ * Exposes the `/v0/escrow/*` endpoints of the pmxt hosted trading API as a
5
+ * small ergonomic helper class. Each method returns the raw upstream JSON
6
+ * payload as `unknown` — typed mappers can be layered on later without
7
+ * breaking the wire shape.
8
+ *
9
+ * Mirrors `sdks/python/pmxt/escrow.py`.
10
+ */
11
+ import { HostedClientLike } from "./hosted-routing.js";
12
+ export declare class Escrow {
13
+ private readonly client;
14
+ constructor(client: HostedClientLike);
15
+ /**
16
+ * Build an unsigned approve transaction for a given ERC-20 `token`. When
17
+ * `amountWei` is omitted, the server returns an unlimited approval.
18
+ */
19
+ approveTx(token: string, amountWei?: bigint): Promise<unknown>;
20
+ /**
21
+ * Build an unsigned deposit transaction for `amount` (USDC, 6-decimal
22
+ * grid). Accepts number, decimal string, or BigInt in micro-units.
23
+ */
24
+ depositTx(amount: number | string | bigint): Promise<unknown>;
25
+ /**
26
+ * Build an unsigned withdraw transaction. `action` selects the stage of
27
+ * the withdrawal lifecycle: `request` initiates, `claim` finalizes after
28
+ * the timelock, `cancel` aborts a pending request.
29
+ */
30
+ withdrawTx(action: "request" | "claim" | "cancel", amount?: number | string | bigint): Promise<unknown>;
31
+ /**
32
+ * List the user's withdrawal records. `include` is forwarded verbatim and
33
+ * defaults to `"pending,events"` to match the Python SDK.
34
+ */
35
+ withdrawals(opts?: {
36
+ include?: string;
37
+ address?: string;
38
+ }): Promise<unknown>;
39
+ }