pmxtjs 2.48.6 → 2.49.0
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.
- package/dist/esm/generated/src/models/OrderLevel.d.ts +6 -0
- package/dist/esm/generated/src/models/OrderLevel.js +2 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/pmxt/client.d.ts +106 -5
- package/dist/esm/pmxt/client.js +400 -6
- package/dist/esm/pmxt/constants.d.ts +11 -0
- package/dist/esm/pmxt/constants.js +13 -0
- package/dist/esm/pmxt/errors.d.ts +3 -0
- package/dist/esm/pmxt/errors.js +9 -0
- package/dist/esm/pmxt/escrow.d.ts +39 -0
- package/dist/esm/pmxt/escrow.js +78 -0
- package/dist/esm/pmxt/feed-client.d.ts +3 -0
- package/dist/esm/pmxt/feed-client.js +11 -2
- package/dist/esm/pmxt/hosted-errors.d.ts +84 -0
- package/dist/esm/pmxt/hosted-errors.js +186 -0
- package/dist/esm/pmxt/hosted-mappers.d.ts +45 -0
- package/dist/esm/pmxt/hosted-mappers.js +291 -0
- package/dist/esm/pmxt/hosted-routing.d.ts +69 -0
- package/dist/esm/pmxt/hosted-routing.js +119 -0
- package/dist/esm/pmxt/hosted-typed-data.d.ts +36 -0
- package/dist/esm/pmxt/hosted-typed-data.js +580 -0
- package/dist/esm/pmxt/models.d.ts +46 -8
- package/dist/esm/pmxt/server-manager.d.ts +4 -0
- package/dist/esm/pmxt/server-manager.js +6 -0
- package/dist/esm/pmxt/signers.d.ts +57 -0
- package/dist/esm/pmxt/signers.js +50 -0
- package/dist/esm/pmxt/ws-client.js +2 -1
- package/dist/generated/src/models/OrderLevel.d.ts +6 -0
- package/dist/generated/src/models/OrderLevel.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/pmxt/client.d.ts +106 -5
- package/dist/pmxt/client.js +399 -5
- package/dist/pmxt/constants.d.ts +11 -0
- package/dist/pmxt/constants.js +14 -1
- package/dist/pmxt/errors.d.ts +3 -0
- package/dist/pmxt/errors.js +11 -1
- package/dist/pmxt/escrow.d.ts +39 -0
- package/dist/pmxt/escrow.js +82 -0
- package/dist/pmxt/feed-client.d.ts +3 -0
- package/dist/pmxt/feed-client.js +11 -2
- package/dist/pmxt/hosted-errors.d.ts +84 -0
- package/dist/pmxt/hosted-errors.js +201 -0
- package/dist/pmxt/hosted-mappers.d.ts +45 -0
- package/dist/pmxt/hosted-mappers.js +302 -0
- package/dist/pmxt/hosted-routing.d.ts +69 -0
- package/dist/pmxt/hosted-routing.js +126 -0
- package/dist/pmxt/hosted-typed-data.d.ts +36 -0
- package/dist/pmxt/hosted-typed-data.js +619 -0
- package/dist/pmxt/models.d.ts +46 -8
- package/dist/pmxt/server-manager.d.ts +4 -0
- package/dist/pmxt/server-manager.js +6 -0
- package/dist/pmxt/signers.d.ts +57 -0
- package/dist/pmxt/signers.js +55 -0
- package/dist/pmxt/ws-client.js +2 -1
- package/generated/docs/OrderLevel.md +2 -0
- package/generated/package.json +1 -1
- package/generated/src/models/OrderLevel.ts +8 -0
- package/index.ts +1 -0
- package/package.json +11 -2
- package/pmxt/client.ts +495 -9
- package/pmxt/constants.ts +15 -0
- package/pmxt/errors.ts +11 -0
- package/pmxt/escrow.ts +93 -0
- package/pmxt/feed-client.ts +14 -2
- package/pmxt/hosted-errors.ts +216 -0
- package/pmxt/hosted-mappers.ts +312 -0
- package/pmxt/hosted-routing.ts +165 -0
- package/pmxt/hosted-typed-data.ts +767 -0
- package/pmxt/models.ts +65 -8
- package/pmxt/server-manager.ts +7 -0
- package/pmxt/signers.ts +86 -0
- package/pmxt/ws-client.ts +2 -1
package/pmxt/client.ts
CHANGED
|
@@ -50,10 +50,44 @@ import {
|
|
|
50
50
|
|
|
51
51
|
import { ServerManager } from "./server-manager.js";
|
|
52
52
|
import { buildArgsWithOptionalOptions } from "./args.js";
|
|
53
|
-
import { PmxtError, fromServerError } from "./errors.js";
|
|
53
|
+
import { PmxtError, fromServerError, InvalidOrder, NotSupported } from "./errors.js";
|
|
54
54
|
import { LOCAL_URL, resolvePmxtBaseUrl } from "./constants.js";
|
|
55
55
|
import { SidecarWsClient } from "./ws-client.js";
|
|
56
56
|
|
|
57
|
+
// Hosted-mode trading dispatch.
|
|
58
|
+
// These modules are introduced as part of the hosted trading mode rollout.
|
|
59
|
+
// Some of them may be authored by parallel agents; until they all land, the
|
|
60
|
+
// import names below are the conventional ones from the plan. Cross-module
|
|
61
|
+
// "Cannot find module" errors during the parallel landing window resolve
|
|
62
|
+
// once the matching files exist.
|
|
63
|
+
import {
|
|
64
|
+
HOSTED_TRADING_VENUES,
|
|
65
|
+
_tradingRequest,
|
|
66
|
+
resolveWalletAddress,
|
|
67
|
+
ensureHostedTradingSupported,
|
|
68
|
+
formatRoutePath,
|
|
69
|
+
HOSTED_METHOD_ROUTES,
|
|
70
|
+
} from "./hosted-routing.js";
|
|
71
|
+
import {
|
|
72
|
+
orderFromV0,
|
|
73
|
+
positionFromV0,
|
|
74
|
+
balanceFromV0,
|
|
75
|
+
userTradeFromV0,
|
|
76
|
+
to6dec,
|
|
77
|
+
} from "./hosted-mappers.js";
|
|
78
|
+
import {
|
|
79
|
+
validateTypedData,
|
|
80
|
+
validateEconomics,
|
|
81
|
+
verifySignature,
|
|
82
|
+
} from "./hosted-typed-data.js";
|
|
83
|
+
import type { Signer, TypedData } from "./signers.js";
|
|
84
|
+
import { signerFromPrivateKey, EthersSigner } from "./signers.js";
|
|
85
|
+
import { Escrow } from "./escrow.js";
|
|
86
|
+
import {
|
|
87
|
+
MissingWalletAddress,
|
|
88
|
+
InvalidSignature as HostedInvalidSignature,
|
|
89
|
+
} from "./hosted-errors.js";
|
|
90
|
+
|
|
57
91
|
interface RawWebSocketLike {
|
|
58
92
|
send(data: string): void;
|
|
59
93
|
}
|
|
@@ -236,6 +270,20 @@ export interface ExchangeOptions {
|
|
|
236
270
|
|
|
237
271
|
/** Optional signature type (0=EOA, 1=Proxy) */
|
|
238
272
|
signatureType?: number;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* EVM wallet address used for hosted reads/writes. Required for hosted
|
|
276
|
+
* endpoints that operate on a wallet (balances, positions, trades, open
|
|
277
|
+
* orders). When omitted, hosted reads raise {@link MissingWalletAddress}.
|
|
278
|
+
*/
|
|
279
|
+
walletAddress?: string;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* External signer used for hosted writes. When `privateKey` is supplied
|
|
283
|
+
* without `signer` in hosted mode, an internal {@link EthersSigner} is
|
|
284
|
+
* built from it lazily.
|
|
285
|
+
*/
|
|
286
|
+
signer?: Signer;
|
|
239
287
|
}
|
|
240
288
|
|
|
241
289
|
/**
|
|
@@ -252,10 +300,13 @@ export abstract class Exchange {
|
|
|
252
300
|
"opinion",
|
|
253
301
|
]);
|
|
254
302
|
|
|
255
|
-
|
|
303
|
+
// Public so structural interfaces like `HostedClientLike`
|
|
304
|
+
// (./hosted-routing) can read the venue name and hosted credentials
|
|
305
|
+
// without violating protected-access on this base class.
|
|
306
|
+
public exchangeName: string;
|
|
307
|
+
public pmxtApiKey?: string;
|
|
256
308
|
protected apiKey?: string;
|
|
257
309
|
protected privateKey?: string;
|
|
258
|
-
protected pmxtApiKey?: string;
|
|
259
310
|
protected proxyAddress?: string;
|
|
260
311
|
protected signatureType?: number;
|
|
261
312
|
protected api: DefaultApi;
|
|
@@ -263,6 +314,15 @@ export abstract class Exchange {
|
|
|
263
314
|
protected serverManager: ServerManager;
|
|
264
315
|
protected initPromise: Promise<void>;
|
|
265
316
|
protected isHosted: boolean;
|
|
317
|
+
|
|
318
|
+
/** Wallet address used for hosted endpoints that operate on a wallet. */
|
|
319
|
+
public walletAddress?: string;
|
|
320
|
+
|
|
321
|
+
/** External signer used for hosted writes. */
|
|
322
|
+
public signer?: Signer;
|
|
323
|
+
|
|
324
|
+
/** Escrow namespace — populated in hosted mode for trading-allowlisted venues. */
|
|
325
|
+
public escrow?: Escrow;
|
|
266
326
|
private _hostedAccount?: { depositWallet?: string; signatureType?: number };
|
|
267
327
|
private _accountDiscoveryPromise?: Promise<void>;
|
|
268
328
|
|
|
@@ -285,6 +345,8 @@ export abstract class Exchange {
|
|
|
285
345
|
this.privateKey = options.privateKey;
|
|
286
346
|
this.proxyAddress = options.proxyAddress;
|
|
287
347
|
this.signatureType = options.signatureType;
|
|
348
|
+
this.walletAddress = options.walletAddress;
|
|
349
|
+
this.signer = options.signer;
|
|
288
350
|
|
|
289
351
|
// Resolve base URL + hosted API key via the shared precedence
|
|
290
352
|
// rules. See constants.ts for the full resolution table.
|
|
@@ -296,6 +358,27 @@ export abstract class Exchange {
|
|
|
296
358
|
this.pmxtApiKey = resolved.pmxtApiKey;
|
|
297
359
|
this.isHosted = resolved.isHosted;
|
|
298
360
|
|
|
361
|
+
// Hosted trading bridge: if the caller passed a privateKey but no
|
|
362
|
+
// explicit signer, lazily wrap it in an EthersSigner so that
|
|
363
|
+
// `pmxt.Polymarket(pmxtApiKey, privateKey)` just works without the
|
|
364
|
+
// user touching `signer`. EthersSigner's constructor synchronously
|
|
365
|
+
// builds an ethers.Wallet, so this remains a synchronous bridge —
|
|
366
|
+
// the constructor stays sync.
|
|
367
|
+
if (this.pmxtApiKey && this.privateKey && !this.signer) {
|
|
368
|
+
try {
|
|
369
|
+
this.signer = new EthersSigner(this.privateKey);
|
|
370
|
+
} catch {
|
|
371
|
+
// ethers not installed — defer the error to the first
|
|
372
|
+
// hosted write that actually needs the signer. Read-only
|
|
373
|
+
// hosted callers don't need ethers.
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Instantiate Escrow namespace for hosted-trading-allowlisted venues.
|
|
378
|
+
if (this.pmxtApiKey && HOSTED_TRADING_VENUES.has(this.exchangeName)) {
|
|
379
|
+
this.escrow = new Escrow(this);
|
|
380
|
+
}
|
|
381
|
+
|
|
299
382
|
// auto_start_server defaults: true for local, false for hosted.
|
|
300
383
|
// An explicit value in the options always wins.
|
|
301
384
|
const autoStartServer = options.autoStartServer !== undefined
|
|
@@ -389,6 +472,29 @@ export abstract class Exchange {
|
|
|
389
472
|
return headers;
|
|
390
473
|
}
|
|
391
474
|
|
|
475
|
+
/**
|
|
476
|
+
* Returns true when this client should dispatch trading methods through
|
|
477
|
+
* the hosted PMXT trading API (`pmxtApiKey` set AND venue is on the
|
|
478
|
+
* hosted-trading allowlist). Used to gate every Group A method.
|
|
479
|
+
*/
|
|
480
|
+
protected isHostedTradingMode(): boolean {
|
|
481
|
+
return Boolean(this.pmxtApiKey) && HOSTED_TRADING_VENUES.has(this.exchangeName);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Require a configured signer for a hosted write. Returns the signer or
|
|
486
|
+
* throws {@link MissingWalletAddress} (consistent with the error class
|
|
487
|
+
* surfaced for missing wallet wiring on hosted writes).
|
|
488
|
+
*/
|
|
489
|
+
protected requireHostedSigner(): Signer {
|
|
490
|
+
if (!this.signer) {
|
|
491
|
+
throw new MissingWalletAddress(
|
|
492
|
+
"hosted write requires a signer (pass `signer` or `privateKey`)",
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
return this.signer;
|
|
496
|
+
}
|
|
497
|
+
|
|
392
498
|
/**
|
|
393
499
|
* Resolve the current sidecar base URL.
|
|
394
500
|
*
|
|
@@ -1012,6 +1118,9 @@ export abstract class Exchange {
|
|
|
1012
1118
|
}
|
|
1013
1119
|
|
|
1014
1120
|
async submitOrder(built: BuiltOrder): Promise<Order> {
|
|
1121
|
+
if (this.isHostedTradingMode()) {
|
|
1122
|
+
return this._hostedSubmitOrder(built);
|
|
1123
|
+
}
|
|
1015
1124
|
await this.initPromise;
|
|
1016
1125
|
try {
|
|
1017
1126
|
const args: any[] = [];
|
|
@@ -1037,7 +1146,88 @@ export abstract class Exchange {
|
|
|
1037
1146
|
}
|
|
1038
1147
|
}
|
|
1039
1148
|
|
|
1149
|
+
/**
|
|
1150
|
+
* Hosted-mode submitOrder: validate the stored build response, sign the
|
|
1151
|
+
* typed_data (and pull_typed_data for Opinion cross-chain sells), then
|
|
1152
|
+
* POST to `/v0/trade/submit-order`.
|
|
1153
|
+
*/
|
|
1154
|
+
private async _hostedSubmitOrder(built: BuiltOrder): Promise<Order> {
|
|
1155
|
+
const signer = this.requireHostedSigner();
|
|
1156
|
+
if (!this.walletAddress) {
|
|
1157
|
+
throw new MissingWalletAddress(
|
|
1158
|
+
"hosted submitOrder requires walletAddress",
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
// BuiltOrder is the SDK-side wrapper around the build response —
|
|
1162
|
+
// expect typed_data, optional pull_typed_data, built_order_id, and
|
|
1163
|
+
// the originating build_request to be present.
|
|
1164
|
+
const payload = built as unknown as Record<string, unknown>;
|
|
1165
|
+
const typedData = payload["typed_data"] as TypedData | undefined;
|
|
1166
|
+
if (!typedData) {
|
|
1167
|
+
throw new HostedInvalidSignature(0, "typed_data missing from built order");
|
|
1168
|
+
}
|
|
1169
|
+
const buildRequest = (payload["build_request"] as Record<string, unknown> | undefined)
|
|
1170
|
+
?? ((payload["params"] as Record<string, unknown> | undefined)?.["build_request"] as Record<string, unknown> | undefined);
|
|
1171
|
+
|
|
1172
|
+
const side = String(buildRequest?.["side"] ?? "buy");
|
|
1173
|
+
const primaryRoute = this._hostedTypedDataRoute(side, false);
|
|
1174
|
+
// Layer 1: schema, Layer 2: economics.
|
|
1175
|
+
validateTypedData(typedData, primaryRoute, this.walletAddress);
|
|
1176
|
+
if (buildRequest) {
|
|
1177
|
+
validateEconomics(typedData, primaryRoute, buildRequest, payload);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
const signature = await signer.signTypedData(typedData);
|
|
1181
|
+
// Layer 3: post-sign recovery + canonical check.
|
|
1182
|
+
verifySignature(typedData, signature, signer.address);
|
|
1183
|
+
|
|
1184
|
+
const body: Record<string, unknown> = {
|
|
1185
|
+
built_order_id: payload["built_order_id"],
|
|
1186
|
+
signature,
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
const pullTypedData = payload["pull_typed_data"] as TypedData | undefined;
|
|
1190
|
+
if (pullTypedData) {
|
|
1191
|
+
const pullRoute = this._hostedTypedDataRoute(side, true);
|
|
1192
|
+
if (pullRoute) {
|
|
1193
|
+
validateTypedData(pullTypedData, pullRoute, this.walletAddress);
|
|
1194
|
+
}
|
|
1195
|
+
const pullSig = await signer.signTypedData(pullTypedData);
|
|
1196
|
+
verifySignature(pullTypedData, pullSig, signer.address);
|
|
1197
|
+
body["pull_signature"] = pullSig;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
const route = HOSTED_METHOD_ROUTES.get("submitOrder")!;
|
|
1201
|
+
const data = await _tradingRequest(this, { method: route.method, path: route.path, body });
|
|
1202
|
+
return orderFromV0(data as Record<string, unknown>);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* Resolve the per-(venue, side, pull) typed-data schema route used by
|
|
1207
|
+
* `validateTypedData` / `validateEconomics`. Returns undefined for the
|
|
1208
|
+
* pull leg when a venue/side combo doesn't have one.
|
|
1209
|
+
*/
|
|
1210
|
+
private _hostedTypedDataRoute(side: string, isPull: boolean): string {
|
|
1211
|
+
const venue = this.exchangeName;
|
|
1212
|
+
const sideLower = side.toLowerCase();
|
|
1213
|
+
if (venue === "polymarket") {
|
|
1214
|
+
return sideLower === "sell" ? "polymarket_sell" : "polymarket_buy";
|
|
1215
|
+
}
|
|
1216
|
+
// opinion
|
|
1217
|
+
if (sideLower === "buy") return "opinion_buy";
|
|
1218
|
+
// sell — polygon, or BSC pull leg for cross-chain
|
|
1219
|
+
return isPull ? "opinion_sell_bsc_pull" : "opinion_sell_polygon";
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
private _hostedCancelTypedDataRoute(isPull: boolean): string {
|
|
1223
|
+
if (this.exchangeName === "polymarket") return "cancel_polymarket";
|
|
1224
|
+
return isPull ? "cancel_opinion_bsc_pull" : "cancel_opinion_polygon";
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1040
1227
|
async cancelOrder(orderId: string): Promise<Order> {
|
|
1228
|
+
if (this.isHostedTradingMode()) {
|
|
1229
|
+
return this._hostedCancelOrder(orderId);
|
|
1230
|
+
}
|
|
1041
1231
|
await this.initPromise;
|
|
1042
1232
|
try {
|
|
1043
1233
|
const args: any[] = [];
|
|
@@ -1063,7 +1253,56 @@ export abstract class Exchange {
|
|
|
1063
1253
|
}
|
|
1064
1254
|
}
|
|
1065
1255
|
|
|
1256
|
+
/**
|
|
1257
|
+
* Hosted-mode cancelOrder: build the cancel typed_data on the server,
|
|
1258
|
+
* validate + sign (dual-sign for Opinion cross-chain), then submit.
|
|
1259
|
+
*/
|
|
1260
|
+
private async _hostedCancelOrder(orderId: string): Promise<Order> {
|
|
1261
|
+
const signer = this.requireHostedSigner();
|
|
1262
|
+
if (!this.walletAddress) {
|
|
1263
|
+
throw new MissingWalletAddress(
|
|
1264
|
+
"hosted cancelOrder requires walletAddress",
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
const buildRoute = HOSTED_METHOD_ROUTES.get("cancelOrderBuild")!;
|
|
1269
|
+
const buildResp = await _tradingRequest(this, {
|
|
1270
|
+
method: buildRoute.method,
|
|
1271
|
+
path: buildRoute.path,
|
|
1272
|
+
body: { order_id: orderId },
|
|
1273
|
+
}) as Record<string, unknown>;
|
|
1274
|
+
|
|
1275
|
+
const typedData = buildResp["typed_data"] as TypedData | undefined;
|
|
1276
|
+
if (!typedData) {
|
|
1277
|
+
throw new HostedInvalidSignature(0, "typed_data missing from cancel build response");
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
validateTypedData(typedData, this._hostedCancelTypedDataRoute(false), this.walletAddress);
|
|
1281
|
+
const signature = await signer.signTypedData(typedData);
|
|
1282
|
+
verifySignature(typedData, signature, signer.address);
|
|
1283
|
+
|
|
1284
|
+
const body: Record<string, unknown> = {
|
|
1285
|
+
cancel_id: buildResp["cancel_id"],
|
|
1286
|
+
signature,
|
|
1287
|
+
};
|
|
1288
|
+
|
|
1289
|
+
const pullTypedData = buildResp["pull_typed_data"] as TypedData | undefined;
|
|
1290
|
+
if (pullTypedData) {
|
|
1291
|
+
validateTypedData(pullTypedData, this._hostedCancelTypedDataRoute(true), this.walletAddress);
|
|
1292
|
+
const pullSig = await signer.signTypedData(pullTypedData);
|
|
1293
|
+
verifySignature(pullTypedData, pullSig, signer.address);
|
|
1294
|
+
body["pull_signature"] = pullSig;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
const route = HOSTED_METHOD_ROUTES.get("cancelOrder")!;
|
|
1298
|
+
const data = await _tradingRequest(this, { method: route.method, path: route.path, body });
|
|
1299
|
+
return orderFromV0(data as Record<string, unknown>);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1066
1302
|
async fetchOrder(orderId: string): Promise<Order> {
|
|
1303
|
+
if (this.isHostedTradingMode()) {
|
|
1304
|
+
return this._hostedFetchOrder(orderId);
|
|
1305
|
+
}
|
|
1067
1306
|
await this.initPromise;
|
|
1068
1307
|
try {
|
|
1069
1308
|
const args: any[] = [];
|
|
@@ -1089,7 +1328,17 @@ export abstract class Exchange {
|
|
|
1089
1328
|
}
|
|
1090
1329
|
}
|
|
1091
1330
|
|
|
1331
|
+
private async _hostedFetchOrder(orderId: string): Promise<Order> {
|
|
1332
|
+
const route = HOSTED_METHOD_ROUTES.get("fetchOrder")!;
|
|
1333
|
+
const path = formatRoutePath(route, { order_id: orderId });
|
|
1334
|
+
const data = await _tradingRequest(this, { method: route.method, path });
|
|
1335
|
+
return orderFromV0(data as Record<string, unknown>);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1092
1338
|
async fetchOpenOrders(marketId?: string): Promise<Order[]> {
|
|
1339
|
+
if (this.isHostedTradingMode()) {
|
|
1340
|
+
return this._hostedFetchOpenOrders(marketId);
|
|
1341
|
+
}
|
|
1093
1342
|
await this.initPromise;
|
|
1094
1343
|
try {
|
|
1095
1344
|
const args: any[] = [];
|
|
@@ -1115,7 +1364,24 @@ export abstract class Exchange {
|
|
|
1115
1364
|
}
|
|
1116
1365
|
}
|
|
1117
1366
|
|
|
1367
|
+
private async _hostedFetchOpenOrders(marketId?: string): Promise<Order[]> {
|
|
1368
|
+
const address = resolveWalletAddress(this, undefined);
|
|
1369
|
+
const route = HOSTED_METHOD_ROUTES.get("fetchOpenOrders")!;
|
|
1370
|
+
const params: Record<string, string> = { address };
|
|
1371
|
+
if (marketId !== undefined) params["market_id"] = marketId;
|
|
1372
|
+
const data = await _tradingRequest(this, {
|
|
1373
|
+
method: route.method,
|
|
1374
|
+
path: route.path,
|
|
1375
|
+
params,
|
|
1376
|
+
});
|
|
1377
|
+
const items = (Array.isArray(data) ? data : (data as Record<string, unknown>)?.["orders"] ?? []) as unknown[];
|
|
1378
|
+
return (items as Record<string, unknown>[]).map(orderFromV0);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1118
1381
|
async fetchMyTrades(params?: MyTradesParams): Promise<UserTrade[]> {
|
|
1382
|
+
if (this.isHostedTradingMode()) {
|
|
1383
|
+
return this._hostedFetchMyTrades(params);
|
|
1384
|
+
}
|
|
1119
1385
|
await this.initPromise;
|
|
1120
1386
|
try {
|
|
1121
1387
|
const args: any[] = [];
|
|
@@ -1141,7 +1407,32 @@ export abstract class Exchange {
|
|
|
1141
1407
|
}
|
|
1142
1408
|
}
|
|
1143
1409
|
|
|
1410
|
+
private async _hostedFetchMyTrades(params?: MyTradesParams): Promise<UserTrade[]> {
|
|
1411
|
+
const address = resolveWalletAddress(this, undefined);
|
|
1412
|
+
const route = HOSTED_METHOD_ROUTES.get("fetchMyTrades")!;
|
|
1413
|
+
const path = formatRoutePath(route, { address });
|
|
1414
|
+
const q: Record<string, string> = {};
|
|
1415
|
+
if (params?.marketId) q["market_id"] = params.marketId;
|
|
1416
|
+
if (params?.outcomeId) q["outcome_id"] = params.outcomeId;
|
|
1417
|
+
if (params?.limit !== undefined) q["limit"] = String(params.limit);
|
|
1418
|
+
if (params?.cursor) q["cursor"] = params.cursor;
|
|
1419
|
+
if (params?.since) q["since"] = String(params.since.getTime());
|
|
1420
|
+
if (params?.until) q["until"] = String(params.until.getTime());
|
|
1421
|
+
const data = await _tradingRequest(this, {
|
|
1422
|
+
method: route.method,
|
|
1423
|
+
path,
|
|
1424
|
+
params: Object.keys(q).length ? q : undefined,
|
|
1425
|
+
});
|
|
1426
|
+
const items = (Array.isArray(data) ? data : (data as Record<string, unknown>)?.["trades"] ?? []) as unknown[];
|
|
1427
|
+
return (items as Record<string, unknown>[]).map(userTradeFromV0);
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1144
1430
|
async fetchClosedOrders(params?: OrderHistoryParams): Promise<Order[]> {
|
|
1431
|
+
if (this.isHostedTradingMode()) {
|
|
1432
|
+
throw new NotSupported(
|
|
1433
|
+
"Settled orders are modeled as trades — use fetchMyTrades().",
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1145
1436
|
await this.initPromise;
|
|
1146
1437
|
try {
|
|
1147
1438
|
const args: any[] = [];
|
|
@@ -1168,6 +1459,11 @@ export abstract class Exchange {
|
|
|
1168
1459
|
}
|
|
1169
1460
|
|
|
1170
1461
|
async fetchAllOrders(params?: OrderHistoryParams): Promise<Order[]> {
|
|
1462
|
+
if (this.isHostedTradingMode()) {
|
|
1463
|
+
throw new NotSupported(
|
|
1464
|
+
"Use fetchOpenOrders() and fetchMyTrades() separately.",
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1171
1467
|
await this.initPromise;
|
|
1172
1468
|
try {
|
|
1173
1469
|
const args: any[] = [];
|
|
@@ -1194,6 +1490,9 @@ export abstract class Exchange {
|
|
|
1194
1490
|
}
|
|
1195
1491
|
|
|
1196
1492
|
async fetchPositions(address?: string): Promise<Position[]> {
|
|
1493
|
+
if (this.isHostedTradingMode()) {
|
|
1494
|
+
return this._hostedFetchPositions(address);
|
|
1495
|
+
}
|
|
1197
1496
|
await this.initPromise;
|
|
1198
1497
|
try {
|
|
1199
1498
|
const args: any[] = [];
|
|
@@ -1219,7 +1518,19 @@ export abstract class Exchange {
|
|
|
1219
1518
|
}
|
|
1220
1519
|
}
|
|
1221
1520
|
|
|
1521
|
+
private async _hostedFetchPositions(address?: string): Promise<Position[]> {
|
|
1522
|
+
const resolvedAddr = resolveWalletAddress(this, address);
|
|
1523
|
+
const route = HOSTED_METHOD_ROUTES.get("fetchPositions")!;
|
|
1524
|
+
const path = formatRoutePath(route, { address: resolvedAddr });
|
|
1525
|
+
const data = await _tradingRequest(this, { method: route.method, path });
|
|
1526
|
+
const items = (Array.isArray(data) ? data : (data as Record<string, unknown>)?.["positions"] ?? []) as unknown[];
|
|
1527
|
+
return (items as Record<string, unknown>[]).map(positionFromV0);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1222
1530
|
async fetchBalance(address?: string): Promise<Balance[]> {
|
|
1531
|
+
if (this.isHostedTradingMode()) {
|
|
1532
|
+
return this._hostedFetchBalance(address);
|
|
1533
|
+
}
|
|
1223
1534
|
await this.initPromise;
|
|
1224
1535
|
try {
|
|
1225
1536
|
const args: any[] = [];
|
|
@@ -1245,6 +1556,19 @@ export abstract class Exchange {
|
|
|
1245
1556
|
}
|
|
1246
1557
|
}
|
|
1247
1558
|
|
|
1559
|
+
private async _hostedFetchBalance(address?: string): Promise<Balance[]> {
|
|
1560
|
+
const resolvedAddr = resolveWalletAddress(this, address);
|
|
1561
|
+
const route = HOSTED_METHOD_ROUTES.get("fetchBalance")!;
|
|
1562
|
+
const path = formatRoutePath(route, { address: resolvedAddr });
|
|
1563
|
+
const data = await _tradingRequest(this, { method: route.method, path });
|
|
1564
|
+
// Hosted balance is a single USDC escrow record; wrap in an array
|
|
1565
|
+
// to match the existing Balance[] return shape.
|
|
1566
|
+
if (Array.isArray(data)) {
|
|
1567
|
+
return (data as Record<string, unknown>[]).map(balanceFromV0);
|
|
1568
|
+
}
|
|
1569
|
+
return [balanceFromV0(data as Record<string, unknown>)];
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1248
1572
|
async unwatchOrderBook(outcomeId: string | MarketOutcome): Promise<void> {
|
|
1249
1573
|
await this.initPromise;
|
|
1250
1574
|
try {
|
|
@@ -1938,6 +2262,9 @@ export abstract class Exchange {
|
|
|
1938
2262
|
* ```
|
|
1939
2263
|
*/
|
|
1940
2264
|
async buildOrder(params: CreateOrderParams & { outcome?: MarketOutcome }): Promise<BuiltOrder> {
|
|
2265
|
+
if (this.isHostedTradingMode()) {
|
|
2266
|
+
return this._hostedBuildOrder(params);
|
|
2267
|
+
}
|
|
1941
2268
|
if (this.isHosted) {
|
|
1942
2269
|
throw new PmxtError(
|
|
1943
2270
|
"Trade execution is not available through the hosted API. " +
|
|
@@ -2069,6 +2396,122 @@ export abstract class Exchange {
|
|
|
2069
2396
|
return convertOrder(submitData);
|
|
2070
2397
|
}
|
|
2071
2398
|
|
|
2399
|
+
/**
|
|
2400
|
+
* Hosted-mode buildOrder: validate inputs locally, then POST to the
|
|
2401
|
+
* trading service's `build-order` endpoint and return a BuiltOrder
|
|
2402
|
+
* that carries the original build_request for Layer-2 economic checks
|
|
2403
|
+
* at submit time.
|
|
2404
|
+
*/
|
|
2405
|
+
private async _hostedBuildOrder(
|
|
2406
|
+
params: CreateOrderParams & { outcome?: MarketOutcome },
|
|
2407
|
+
): Promise<BuiltOrder> {
|
|
2408
|
+
const body = this._hostedBuildOrderBody(params);
|
|
2409
|
+
const route = HOSTED_METHOD_ROUTES.get("buildOrder")!;
|
|
2410
|
+
const data = await _tradingRequest(this, {
|
|
2411
|
+
method: route.method,
|
|
2412
|
+
path: route.path,
|
|
2413
|
+
body,
|
|
2414
|
+
}) as Record<string, unknown>;
|
|
2415
|
+
// Attach the originating build_request so submit can run economic
|
|
2416
|
+
// validation without an extra catalog round-trip.
|
|
2417
|
+
const built = { ...data, build_request: body } as unknown as BuiltOrder;
|
|
2418
|
+
return built;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
/**
|
|
2422
|
+
* Hosted-mode createOrder: build → sign → submit single-call wrapper.
|
|
2423
|
+
*/
|
|
2424
|
+
private async _hostedCreateOrder(params: any): Promise<Order> {
|
|
2425
|
+
const built = await this._hostedBuildOrder(params);
|
|
2426
|
+
return this._hostedSubmitOrder(built);
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
/**
|
|
2430
|
+
* Construct the hosted build-order request body and validate inputs
|
|
2431
|
+
* locally per the v0 contract (denom/side compatibility, > 6-decimal
|
|
2432
|
+
* precision rejected via {@link to6dec}).
|
|
2433
|
+
*/
|
|
2434
|
+
private _hostedBuildOrderBody(
|
|
2435
|
+
params: CreateOrderParams & { outcome?: MarketOutcome },
|
|
2436
|
+
): Record<string, unknown> {
|
|
2437
|
+
let marketId = params.marketId;
|
|
2438
|
+
let outcomeId = params.outcomeId;
|
|
2439
|
+
|
|
2440
|
+
if (params.outcome) {
|
|
2441
|
+
if (marketId !== undefined || outcomeId !== undefined) {
|
|
2442
|
+
throw new InvalidOrder(
|
|
2443
|
+
"cannot specify both 'outcome' and 'marketId'/'outcomeId'",
|
|
2444
|
+
);
|
|
2445
|
+
}
|
|
2446
|
+
const outcome: MarketOutcome = params.outcome;
|
|
2447
|
+
if (!outcome.marketId) {
|
|
2448
|
+
throw new InvalidOrder(
|
|
2449
|
+
"outcome.marketId is not set; ensure the outcome comes from a fetched market",
|
|
2450
|
+
);
|
|
2451
|
+
}
|
|
2452
|
+
marketId = outcome.marketId;
|
|
2453
|
+
outcomeId = outcome.outcomeId;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
const side = String(params.side);
|
|
2457
|
+
const orderType = String(params.type ?? "market");
|
|
2458
|
+
const denom = (params as unknown as Record<string, unknown>)["denom"] as
|
|
2459
|
+
| "usdc"
|
|
2460
|
+
| "shares"
|
|
2461
|
+
| undefined;
|
|
2462
|
+
|
|
2463
|
+
// denom/side compatibility per v0:
|
|
2464
|
+
// market buy -> denom='usdc'
|
|
2465
|
+
// market sell -> denom='shares'
|
|
2466
|
+
// any limit -> denom='shares'
|
|
2467
|
+
let resolvedDenom: "usdc" | "shares";
|
|
2468
|
+
if (orderType === "market") {
|
|
2469
|
+
if (side === "buy") {
|
|
2470
|
+
if (denom && denom !== "usdc") {
|
|
2471
|
+
throw new InvalidOrder("market buy requires denom='usdc'");
|
|
2472
|
+
}
|
|
2473
|
+
resolvedDenom = "usdc";
|
|
2474
|
+
} else if (side === "sell") {
|
|
2475
|
+
if (denom && denom !== "shares") {
|
|
2476
|
+
throw new InvalidOrder("market sell requires denom='shares'");
|
|
2477
|
+
}
|
|
2478
|
+
resolvedDenom = "shares";
|
|
2479
|
+
} else {
|
|
2480
|
+
throw new InvalidOrder(`unknown side: ${side}`);
|
|
2481
|
+
}
|
|
2482
|
+
} else {
|
|
2483
|
+
if (denom && denom !== "shares") {
|
|
2484
|
+
throw new InvalidOrder("limit orders require denom='shares'");
|
|
2485
|
+
}
|
|
2486
|
+
resolvedDenom = "shares";
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
if (!(Number(params.amount) > 0)) {
|
|
2490
|
+
throw new InvalidOrder("amount must be positive");
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
// to6dec throws InvalidOrder for sub-micro precision.
|
|
2494
|
+
const amount6dec = to6dec(params.amount as number).toString();
|
|
2495
|
+
|
|
2496
|
+
const body: Record<string, unknown> = {
|
|
2497
|
+
market_id: marketId,
|
|
2498
|
+
outcome_id: outcomeId,
|
|
2499
|
+
side,
|
|
2500
|
+
order_type: orderType,
|
|
2501
|
+
denom: resolvedDenom,
|
|
2502
|
+
amount: params.amount,
|
|
2503
|
+
amount_6dec: amount6dec,
|
|
2504
|
+
};
|
|
2505
|
+
|
|
2506
|
+
if (params.price !== undefined) body["price"] = params.price;
|
|
2507
|
+
const extra = params as unknown as Record<string, unknown>;
|
|
2508
|
+
if (extra["slippage_pct"] !== undefined) {
|
|
2509
|
+
body["slippage_pct"] = extra["slippage_pct"];
|
|
2510
|
+
}
|
|
2511
|
+
if (this.walletAddress) body["user_address"] = this.walletAddress;
|
|
2512
|
+
return body;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2072
2515
|
/**
|
|
2073
2516
|
* @example
|
|
2074
2517
|
* ```typescript
|
|
@@ -2082,11 +2525,16 @@ export abstract class Exchange {
|
|
|
2082
2525
|
* });
|
|
2083
2526
|
* ```
|
|
2084
2527
|
*/
|
|
2085
|
-
async createOrder(params:
|
|
2528
|
+
async createOrder(params: CreateOrderParams & { outcome?: MarketOutcome }): Promise<Order> {
|
|
2529
|
+
// SOR escape path (preserved): legacy hosted SOR flow uses a venue-side
|
|
2530
|
+
// SDK to execute the legs, only when a privateKey is present.
|
|
2531
|
+
if (this.isHosted && this.exchangeName === 'sor' && this.privateKey) {
|
|
2532
|
+
return this._executeSorOrder(params as any);
|
|
2533
|
+
}
|
|
2534
|
+
if (this.isHostedTradingMode()) {
|
|
2535
|
+
return this._hostedCreateOrder(params);
|
|
2536
|
+
}
|
|
2086
2537
|
if (this.isHosted) {
|
|
2087
|
-
if (this.exchangeName === 'sor' && this.privateKey) {
|
|
2088
|
-
return this._executeSorOrder(params);
|
|
2089
|
-
}
|
|
2090
2538
|
throw new PmxtError(
|
|
2091
2539
|
"Trade execution is not available through the hosted API. " +
|
|
2092
2540
|
"Use the local PMXT SDK with your venue credentials instead. " +
|
|
@@ -2821,18 +3269,56 @@ export class Hyperliquid extends Exchange {
|
|
|
2821
3269
|
}
|
|
2822
3270
|
}
|
|
2823
3271
|
|
|
3272
|
+
/**
|
|
3273
|
+
* Options for the SuiBets exchange client.
|
|
3274
|
+
*/
|
|
3275
|
+
export interface SuiBetsOptions extends ExchangeOptions {
|
|
3276
|
+
/**
|
|
3277
|
+
* Sui wallet address (0x + 64 hex chars).
|
|
3278
|
+
* Required for fetchPositions(). Can also be set via the
|
|
3279
|
+
* SUIBETS_WALLET_ADDRESS environment variable on the sidecar.
|
|
3280
|
+
*/
|
|
3281
|
+
walletAddress?: string;
|
|
3282
|
+
}
|
|
3283
|
+
|
|
2824
3284
|
/**
|
|
2825
3285
|
* SuiBets exchange client.
|
|
2826
3286
|
*
|
|
3287
|
+
* SuiBets is a decentralised P2P sports betting exchange on Sui mainnet.
|
|
3288
|
+
* No house edge. 2% platform fee.
|
|
3289
|
+
* Contract: 0xd51fe151bec66a15b086a67c1cfce9b05759ddac1d73fcd3e14324ad202b2e59
|
|
3290
|
+
*
|
|
2827
3291
|
* @example
|
|
2828
3292
|
* ```typescript
|
|
2829
3293
|
* const suibets = new SuiBets();
|
|
2830
|
-
* const markets = await suibets.fetchMarkets();
|
|
3294
|
+
* const markets = await suibets.fetchMarkets({ limit: 20 });
|
|
3295
|
+
*
|
|
3296
|
+
* // With wallet for fetchPositions()
|
|
3297
|
+
* const me = new SuiBets({ walletAddress: '0xabc...' });
|
|
3298
|
+
* const positions = await me.fetchPositions();
|
|
2831
3299
|
* ```
|
|
2832
3300
|
*/
|
|
2833
3301
|
export class SuiBets extends Exchange {
|
|
2834
|
-
|
|
3302
|
+
private readonly _walletAddress?: string;
|
|
3303
|
+
|
|
3304
|
+
constructor(options: SuiBetsOptions = {}) {
|
|
2835
3305
|
super("suibets", options);
|
|
3306
|
+
this._walletAddress = options.walletAddress;
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
/**
|
|
3310
|
+
* Includes walletAddress in the credentials sent to the sidecar so
|
|
3311
|
+
* that fetchPositions() can reach the /api/p2p/my endpoint.
|
|
3312
|
+
* Falls back to SUIBETS_WALLET_ADDRESS env var on the sidecar side
|
|
3313
|
+
* when walletAddress is not set here.
|
|
3314
|
+
*/
|
|
3315
|
+
protected override getCredentials(): ExchangeCredentials | undefined {
|
|
3316
|
+
const base = super.getCredentials();
|
|
3317
|
+
if (!this._walletAddress) return base;
|
|
3318
|
+
return {
|
|
3319
|
+
...(base ?? {}),
|
|
3320
|
+
walletAddress: this._walletAddress,
|
|
3321
|
+
} as ExchangeCredentials & { walletAddress: string };
|
|
2836
3322
|
}
|
|
2837
3323
|
}
|
|
2838
3324
|
|
package/pmxt/constants.ts
CHANGED
|
@@ -65,3 +65,18 @@ export function resolvePmxtBaseUrl(args: {
|
|
|
65
65
|
if (pmxtApiKey) return pick(HOSTED_URL);
|
|
66
66
|
return pick(LOCAL_URL);
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Lowercase 0x-prefixed escrow addresses that are pre-funded by pmxt for
|
|
71
|
+
* hosted trading. Orders routed through these addresses use the shared
|
|
72
|
+
* escrow balance rather than a per-venue deposit.
|
|
73
|
+
*/
|
|
74
|
+
export const PREFUNDED_ESCROW_ADDRESSES: ReadonlySet<string> = new Set([
|
|
75
|
+
"0x3ad326f78b1390b9a5dc5f00e7f62f8632de23e2",
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Lowercase 0x-prefixed escrow addresses that the hosted trading API treats
|
|
80
|
+
* as venue-owned escrows. Currently empty; populated as venues onboard.
|
|
81
|
+
*/
|
|
82
|
+
export const VENUE_ESCROW_ADDRESSES: ReadonlySet<string> = new Set<string>();
|