pmxtjs 2.51.1 → 2.51.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +3 -2
- package/dist/esm/pmxt/client.d.ts +21 -3
- package/dist/esm/pmxt/client.js +48 -18
- package/dist/esm/pmxt/escrow.js +109 -10
- package/dist/esm/pmxt/server-manager.js +5 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -1
- package/dist/pmxt/client.d.ts +21 -3
- package/dist/pmxt/client.js +48 -17
- package/dist/pmxt/escrow.js +109 -10
- package/dist/pmxt/server-manager.js +5 -3
- package/generated/package.json +1 -1
- package/index.ts +3 -2
- package/package.json +2 -2
- package/pmxt/client.ts +61 -17
- package/pmxt/escrow.ts +131 -11
- package/pmxt/server-manager.ts +5 -3
package/dist/esm/index.d.ts
CHANGED
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
* console.log(markets[0].title);
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
-
import { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Rain, Mock } from "./pmxt/client.js";
|
|
20
|
+
import { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Rain, Hunch, Mock } from "./pmxt/client.js";
|
|
21
21
|
import { Router } from "./pmxt/router.js";
|
|
22
22
|
import { ServerManager } from "./pmxt/server-manager.js";
|
|
23
23
|
import { FeedClient } from "./pmxt/feed-client.js";
|
|
24
24
|
import * as models from "./pmxt/models.js";
|
|
25
25
|
import * as errors from "./pmxt/errors.js";
|
|
26
|
-
export { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Mock, PolymarketOptions } from "./pmxt/client.js";
|
|
26
|
+
export { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Hunch, Mock, PolymarketOptions } from "./pmxt/client.js";
|
|
27
27
|
export { FeedClient } from "./pmxt/feed-client.js";
|
|
28
28
|
export type { Ticker, Tickers, OHLCV, Market as FeedMarket, OracleRound, FeedClientOptions } from "./pmxt/feed-client.js";
|
|
29
29
|
export { Router } from "./pmxt/router.js";
|
|
@@ -97,6 +97,7 @@ declare const pmxt: {
|
|
|
97
97
|
SuiBets: typeof SuiBets;
|
|
98
98
|
Suibets: typeof SuiBets;
|
|
99
99
|
Rain: typeof Rain;
|
|
100
|
+
Hunch: typeof Hunch;
|
|
100
101
|
Mock: typeof Mock;
|
|
101
102
|
Router: typeof Router;
|
|
102
103
|
ServerManager: typeof ServerManager;
|
package/dist/esm/index.js
CHANGED
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
* console.log(markets[0].title);
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
-
import { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Mock } from "./pmxt/client.js";
|
|
20
|
+
import { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Hunch, Mock } from "./pmxt/client.js";
|
|
21
21
|
import { Router } from "./pmxt/router.js";
|
|
22
22
|
import { ServerManager } from "./pmxt/server-manager.js";
|
|
23
23
|
import { FeedClient } from "./pmxt/feed-client.js";
|
|
24
24
|
import * as models from "./pmxt/models.js";
|
|
25
25
|
import * as errors from "./pmxt/errors.js";
|
|
26
|
-
export { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Mock } from "./pmxt/client.js";
|
|
26
|
+
export { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Hunch, Mock } from "./pmxt/client.js";
|
|
27
27
|
export { FeedClient } from "./pmxt/feed-client.js";
|
|
28
28
|
export { Router } from "./pmxt/router.js";
|
|
29
29
|
export { ServerManager } from "./pmxt/server-manager.js";
|
|
@@ -78,6 +78,7 @@ const pmxt = {
|
|
|
78
78
|
SuiBets,
|
|
79
79
|
Suibets,
|
|
80
80
|
Rain,
|
|
81
|
+
Hunch,
|
|
81
82
|
Mock,
|
|
82
83
|
Router,
|
|
83
84
|
ServerManager,
|
|
@@ -105,6 +105,7 @@ export declare abstract class Exchange {
|
|
|
105
105
|
private _wsClient;
|
|
106
106
|
/** Sticky flag: true if the sidecar /ws endpoint is unavailable. */
|
|
107
107
|
private _wsUnsupported;
|
|
108
|
+
private readonly _useSidecarLockBaseUrl;
|
|
108
109
|
constructor(exchangeName: string, options?: ExchangeOptions);
|
|
109
110
|
private initializeServer;
|
|
110
111
|
protected handleResponse(response: any): any;
|
|
@@ -125,9 +126,9 @@ export declare abstract class Exchange {
|
|
|
125
126
|
/**
|
|
126
127
|
* Resolve the current sidecar base URL.
|
|
127
128
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
129
|
+
* Hosted mode and explicit baseUrl clients keep the configured basePath.
|
|
130
|
+
* Default local clients re-read the lock-file port on every call so they
|
|
131
|
+
* pick up sidecar restarts that land on a different port.
|
|
131
132
|
*/
|
|
132
133
|
private resolveBaseUrl;
|
|
133
134
|
/**
|
|
@@ -576,6 +577,14 @@ export interface PolymarketOptions {
|
|
|
576
577
|
proxyAddress?: string;
|
|
577
578
|
/** Optional signature type */
|
|
578
579
|
signatureType?: 'eoa' | 'poly-proxy' | 'gnosis-safe' | number;
|
|
580
|
+
/**
|
|
581
|
+
* EVM wallet address used for hosted reads/writes.
|
|
582
|
+
*/
|
|
583
|
+
walletAddress?: string;
|
|
584
|
+
/**
|
|
585
|
+
* External signer used for hosted writes.
|
|
586
|
+
*/
|
|
587
|
+
signer?: Signer;
|
|
579
588
|
}
|
|
580
589
|
export declare class Polymarket extends Exchange {
|
|
581
590
|
constructor(options?: PolymarketOptions);
|
|
@@ -856,6 +865,15 @@ export declare const Suibets: typeof SuiBets;
|
|
|
856
865
|
export declare class Rain extends Exchange {
|
|
857
866
|
constructor(options?: ExchangeOptions);
|
|
858
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* Hunch exchange client.
|
|
870
|
+
*
|
|
871
|
+
* Hunch is a crypto-native prediction market. Reads are unauthenticated;
|
|
872
|
+
* trading requires an EVM private key.
|
|
873
|
+
*/
|
|
874
|
+
export declare class Hunch extends Exchange {
|
|
875
|
+
constructor(options?: ExchangeOptions);
|
|
876
|
+
}
|
|
859
877
|
/**
|
|
860
878
|
* Mock exchange client.
|
|
861
879
|
*
|
package/dist/esm/pmxt/client.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
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, InvalidOrder } from "./errors.js";
|
|
11
|
-
import { resolvePmxtBaseUrl } from "./constants.js";
|
|
10
|
+
import { PmxtError, fromServerError, InvalidOrder, NotSupported } from "./errors.js";
|
|
11
|
+
import { ENV, resolvePmxtBaseUrl } from "./constants.js";
|
|
12
12
|
import { SidecarWsClient } from "./ws-client.js";
|
|
13
13
|
import { logger } from "./logger.js";
|
|
14
14
|
// Hosted-mode trading dispatch.
|
|
@@ -190,6 +190,7 @@ export class Exchange {
|
|
|
190
190
|
_wsClient = null;
|
|
191
191
|
/** Sticky flag: true if the sidecar /ws endpoint is unavailable. */
|
|
192
192
|
_wsUnsupported = false;
|
|
193
|
+
_useSidecarLockBaseUrl;
|
|
193
194
|
constructor(exchangeName, options = {}) {
|
|
194
195
|
this.exchangeName = exchangeName.toLowerCase();
|
|
195
196
|
this.apiKey = options.apiKey;
|
|
@@ -207,6 +208,9 @@ export class Exchange {
|
|
|
207
208
|
const baseUrl = resolved.baseUrl;
|
|
208
209
|
this.pmxtApiKey = resolved.pmxtApiKey;
|
|
209
210
|
this.isHosted = resolved.isHosted;
|
|
211
|
+
const hasBaseUrlOverride = Boolean(options.baseUrl ||
|
|
212
|
+
(typeof process !== "undefined" && process.env[ENV.BASE_URL]));
|
|
213
|
+
this._useSidecarLockBaseUrl = !this.isHosted && !hasBaseUrlOverride;
|
|
210
214
|
// Hosted trading bridge: if the caller passed a privateKey but no
|
|
211
215
|
// explicit signer, lazily wrap it in an EthersSigner so that
|
|
212
216
|
// `pmxt.Polymarket(pmxtApiKey, privateKey)` just works without the
|
|
@@ -247,15 +251,17 @@ export class Exchange {
|
|
|
247
251
|
if (autoStartServer) {
|
|
248
252
|
try {
|
|
249
253
|
await this.serverManager.ensureServerRunning();
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
254
|
+
if (this._useSidecarLockBaseUrl) {
|
|
255
|
+
// Get the actual port the server is running on
|
|
256
|
+
// (may differ from default if default port was busy)
|
|
257
|
+
const actualPort = this.serverManager.getRunningPort();
|
|
258
|
+
const newBaseUrl = `http://localhost:${actualPort}`;
|
|
259
|
+
// Update API client with actual base URL
|
|
260
|
+
this.config = new Configuration({
|
|
261
|
+
basePath: newBaseUrl,
|
|
262
|
+
});
|
|
263
|
+
this.api = new DefaultApi(this.config);
|
|
264
|
+
}
|
|
259
265
|
}
|
|
260
266
|
catch (error) {
|
|
261
267
|
const msg = `Failed to start PMXT server: ${error instanceof Error ? error.message : error}\n\n` +
|
|
@@ -329,12 +335,12 @@ export class Exchange {
|
|
|
329
335
|
/**
|
|
330
336
|
* Resolve the current sidecar base URL.
|
|
331
337
|
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
338
|
+
* Hosted mode and explicit baseUrl clients keep the configured basePath.
|
|
339
|
+
* Default local clients re-read the lock-file port on every call so they
|
|
340
|
+
* pick up sidecar restarts that land on a different port.
|
|
335
341
|
*/
|
|
336
342
|
resolveBaseUrl() {
|
|
337
|
-
if (this.isHosted)
|
|
343
|
+
if (this.isHosted || !this._useSidecarLockBaseUrl)
|
|
338
344
|
return this.config.basePath;
|
|
339
345
|
const port = this.serverManager.getRunningPort();
|
|
340
346
|
return `http://localhost:${port}`;
|
|
@@ -1018,7 +1024,10 @@ export class Exchange {
|
|
|
1018
1024
|
const route = HOSTED_METHOD_ROUTES.get("fetchOrder");
|
|
1019
1025
|
const path = formatRoutePath(route, { order_id: orderId });
|
|
1020
1026
|
const data = await _tradingRequest(this, { method: route.method, path });
|
|
1021
|
-
|
|
1027
|
+
const payload = data && typeof data === "object" && "order" in data
|
|
1028
|
+
? data.order
|
|
1029
|
+
: data;
|
|
1030
|
+
return orderFromV0(payload);
|
|
1022
1031
|
}
|
|
1023
1032
|
try {
|
|
1024
1033
|
const args = [];
|
|
@@ -1124,6 +1133,9 @@ export class Exchange {
|
|
|
1124
1133
|
}
|
|
1125
1134
|
async fetchClosedOrders(params) {
|
|
1126
1135
|
await this.initPromise;
|
|
1136
|
+
if (this.isHosted) {
|
|
1137
|
+
throw new NotSupported("fetchClosedOrders is not available in hosted trading mode.");
|
|
1138
|
+
}
|
|
1127
1139
|
try {
|
|
1128
1140
|
const args = [];
|
|
1129
1141
|
if (params !== undefined)
|
|
@@ -1152,6 +1164,9 @@ export class Exchange {
|
|
|
1152
1164
|
}
|
|
1153
1165
|
async fetchAllOrders(params) {
|
|
1154
1166
|
await this.initPromise;
|
|
1167
|
+
if (this.isHosted) {
|
|
1168
|
+
throw new NotSupported("fetchAllOrders is not available in hosted trading mode.");
|
|
1169
|
+
}
|
|
1155
1170
|
try {
|
|
1156
1171
|
const args = [];
|
|
1157
1172
|
if (params !== undefined)
|
|
@@ -1185,7 +1200,9 @@ export class Exchange {
|
|
|
1185
1200
|
const route = HOSTED_METHOD_ROUTES.get("fetchPositions");
|
|
1186
1201
|
const path = formatRoutePath(route, { address: resolvedAddress });
|
|
1187
1202
|
const data = await _tradingRequest(this, { method: route.method, path });
|
|
1188
|
-
const list = Array.isArray(data)
|
|
1203
|
+
const list = Array.isArray(data)
|
|
1204
|
+
? data
|
|
1205
|
+
: (data && Array.isArray(data.positions) ? data.positions : []);
|
|
1189
1206
|
return list.map((p) => positionFromV0(p));
|
|
1190
1207
|
}
|
|
1191
1208
|
try {
|
|
@@ -1221,7 +1238,9 @@ export class Exchange {
|
|
|
1221
1238
|
const route = HOSTED_METHOD_ROUTES.get("fetchBalance");
|
|
1222
1239
|
const path = formatRoutePath(route, { address: resolvedAddress });
|
|
1223
1240
|
const data = await _tradingRequest(this, { method: route.method, path });
|
|
1224
|
-
const list = Array.isArray(data)
|
|
1241
|
+
const list = Array.isArray(data)
|
|
1242
|
+
? data
|
|
1243
|
+
: (data && Array.isArray(data.balances) ? data.balances : []);
|
|
1225
1244
|
return list.map((b) => balanceFromV0(b));
|
|
1226
1245
|
}
|
|
1227
1246
|
try {
|
|
@@ -3034,6 +3053,17 @@ export class Rain extends Exchange {
|
|
|
3034
3053
|
super("rain", options);
|
|
3035
3054
|
}
|
|
3036
3055
|
}
|
|
3056
|
+
/**
|
|
3057
|
+
* Hunch exchange client.
|
|
3058
|
+
*
|
|
3059
|
+
* Hunch is a crypto-native prediction market. Reads are unauthenticated;
|
|
3060
|
+
* trading requires an EVM private key.
|
|
3061
|
+
*/
|
|
3062
|
+
export class Hunch extends Exchange {
|
|
3063
|
+
constructor(options = {}) {
|
|
3064
|
+
super("hunch", options);
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3037
3067
|
/**
|
|
3038
3068
|
* Mock exchange client.
|
|
3039
3069
|
*
|
package/dist/esm/pmxt/escrow.js
CHANGED
|
@@ -9,6 +9,80 @@
|
|
|
9
9
|
* Mirrors `sdks/python/pmxt/escrow.py`.
|
|
10
10
|
*/
|
|
11
11
|
import { _tradingRequest, formatRoutePath, HOSTED_METHOD_ROUTES, resolveWalletAddress, } from "./hosted-routing.js";
|
|
12
|
+
import { ValidationError } from "./errors.js";
|
|
13
|
+
const APPROVAL_TOKENS = new Set(["usdc", "ctf"]);
|
|
14
|
+
const WITHDRAW_ACTIONS = new Set(["request", "claim", "cancel"]);
|
|
15
|
+
const USDC_SCALE = 1000000n;
|
|
16
|
+
function isDecimalTokenId(token) {
|
|
17
|
+
return /^[0-9]+$/.test(token);
|
|
18
|
+
}
|
|
19
|
+
function normalizeApprovalToken(token) {
|
|
20
|
+
if (typeof token !== "string") {
|
|
21
|
+
throw new ValidationError("token must be 'usdc', 'ctf', or a decimal CTF token_id string", "token");
|
|
22
|
+
}
|
|
23
|
+
const candidate = token.trim();
|
|
24
|
+
const normalized = candidate.toLowerCase();
|
|
25
|
+
if (APPROVAL_TOKENS.has(normalized)) {
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
if (isDecimalTokenId(candidate)) {
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
31
|
+
throw new ValidationError("token must be 'usdc', 'ctf', or a decimal CTF token_id string", "token");
|
|
32
|
+
}
|
|
33
|
+
function normalizeAmountWei(amountWei) {
|
|
34
|
+
if (amountWei === undefined) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
if (typeof amountWei !== "bigint") {
|
|
38
|
+
throw new ValidationError("amount_wei must be a non-negative integer", "amount_wei");
|
|
39
|
+
}
|
|
40
|
+
if (amountWei < 0n) {
|
|
41
|
+
throw new ValidationError("amount_wei must be non-negative", "amount_wei");
|
|
42
|
+
}
|
|
43
|
+
return amountWei.toString();
|
|
44
|
+
}
|
|
45
|
+
function validateUsdcDecimal(value, field) {
|
|
46
|
+
const match = /^([0-9]+)(?:\.([0-9]+))?$/.exec(value);
|
|
47
|
+
if (!match) {
|
|
48
|
+
throw new ValidationError(`${field} must be a finite positive number`, field);
|
|
49
|
+
}
|
|
50
|
+
const fractional = match[2] ?? "";
|
|
51
|
+
if (fractional.length > 6) {
|
|
52
|
+
throw new ValidationError(`${field} precision exceeds 6 decimals; max precision for USDC is 0.000001`, field);
|
|
53
|
+
}
|
|
54
|
+
const scaled = BigInt(match[1]) * USDC_SCALE + BigInt(fractional.padEnd(6, "0"));
|
|
55
|
+
if (scaled <= 0n) {
|
|
56
|
+
throw new ValidationError(`${field} must be positive`, field);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function normalizeUsdcAmount(value, field = "amount") {
|
|
60
|
+
if (typeof value === "bigint") {
|
|
61
|
+
if (value <= 0n) {
|
|
62
|
+
throw new ValidationError(`${field} must be positive`, field);
|
|
63
|
+
}
|
|
64
|
+
return value.toString();
|
|
65
|
+
}
|
|
66
|
+
if (typeof value === "number") {
|
|
67
|
+
if (!Number.isFinite(value)) {
|
|
68
|
+
throw new ValidationError(`${field} must be a finite positive number`, field);
|
|
69
|
+
}
|
|
70
|
+
validateUsdcDecimal(String(value), field);
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
if (typeof value === "string") {
|
|
74
|
+
const trimmed = value.trim();
|
|
75
|
+
validateUsdcDecimal(trimmed, field);
|
|
76
|
+
return trimmed;
|
|
77
|
+
}
|
|
78
|
+
throw new ValidationError(`${field} must be a finite positive number`, field);
|
|
79
|
+
}
|
|
80
|
+
function normalizeWithdrawAction(action) {
|
|
81
|
+
if (typeof action === "string" && WITHDRAW_ACTIONS.has(action)) {
|
|
82
|
+
return action;
|
|
83
|
+
}
|
|
84
|
+
throw new ValidationError("action must be 'request', 'claim', or 'cancel'", "action");
|
|
85
|
+
}
|
|
12
86
|
export class Escrow {
|
|
13
87
|
client;
|
|
14
88
|
constructor(client) {
|
|
@@ -19,13 +93,16 @@ export class Escrow {
|
|
|
19
93
|
* `amountWei` is omitted, the server returns an unlimited approval.
|
|
20
94
|
*/
|
|
21
95
|
async approveTx(token, amountWei) {
|
|
96
|
+
const address = resolveWalletAddress(this.client);
|
|
97
|
+
const approvalAmount = normalizeAmountWei(amountWei);
|
|
22
98
|
const route = HOSTED_METHOD_ROUTES.get("escrowApproveTx");
|
|
23
99
|
return _tradingRequest(this.client, {
|
|
24
100
|
method: route.method,
|
|
25
101
|
path: route.path,
|
|
26
102
|
body: {
|
|
27
|
-
token,
|
|
28
|
-
|
|
103
|
+
token: normalizeApprovalToken(token),
|
|
104
|
+
user_address: address,
|
|
105
|
+
...(approvalAmount === undefined ? {} : { amount_wei: approvalAmount }),
|
|
29
106
|
},
|
|
30
107
|
});
|
|
31
108
|
}
|
|
@@ -34,12 +111,15 @@ export class Escrow {
|
|
|
34
111
|
* grid). Accepts number, decimal string, or BigInt in micro-units.
|
|
35
112
|
*/
|
|
36
113
|
async depositTx(amount) {
|
|
114
|
+
const address = resolveWalletAddress(this.client);
|
|
37
115
|
const route = HOSTED_METHOD_ROUTES.get("escrowDepositTx");
|
|
38
116
|
return _tradingRequest(this.client, {
|
|
39
117
|
method: route.method,
|
|
40
118
|
path: route.path,
|
|
41
119
|
body: {
|
|
42
|
-
|
|
120
|
+
token: "usdc",
|
|
121
|
+
amount: normalizeUsdcAmount(amount),
|
|
122
|
+
user_address: address,
|
|
43
123
|
},
|
|
44
124
|
});
|
|
45
125
|
}
|
|
@@ -49,16 +129,31 @@ export class Escrow {
|
|
|
49
129
|
* the timelock, `cancel` aborts a pending request.
|
|
50
130
|
*/
|
|
51
131
|
async withdrawTx(action, amount) {
|
|
132
|
+
const normalizedAction = normalizeWithdrawAction(action);
|
|
133
|
+
const address = resolveWalletAddress(this.client);
|
|
52
134
|
const route = HOSTED_METHOD_ROUTES.get("escrowWithdrawTx");
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
135
|
+
if (normalizedAction === "request") {
|
|
136
|
+
if (amount === undefined) {
|
|
137
|
+
throw new ValidationError("amount is required when action='request'", "amount");
|
|
138
|
+
}
|
|
139
|
+
return _tradingRequest(this.client, {
|
|
140
|
+
method: route.method,
|
|
141
|
+
path: route.path,
|
|
142
|
+
body: {
|
|
143
|
+
action: normalizedAction,
|
|
144
|
+
token: "usdc",
|
|
145
|
+
amount: normalizeUsdcAmount(amount),
|
|
146
|
+
user_address: address,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
if (amount !== undefined) {
|
|
151
|
+
throw new ValidationError(`amount must be omitted when action='${normalizedAction}'`, "amount");
|
|
152
|
+
}
|
|
58
153
|
return _tradingRequest(this.client, {
|
|
59
154
|
method: route.method,
|
|
60
155
|
path: route.path,
|
|
61
|
-
body: { action,
|
|
156
|
+
body: { action: normalizedAction, token: "usdc", user_address: address },
|
|
62
157
|
});
|
|
63
158
|
}
|
|
64
159
|
/**
|
|
@@ -67,12 +162,16 @@ export class Escrow {
|
|
|
67
162
|
*/
|
|
68
163
|
async withdrawals(opts = {}) {
|
|
69
164
|
const address = resolveWalletAddress(this.client, opts.address);
|
|
165
|
+
const include = (opts.include ?? "pending,events").trim();
|
|
166
|
+
if (!include) {
|
|
167
|
+
throw new ValidationError("include must not be empty", "include");
|
|
168
|
+
}
|
|
70
169
|
const route = HOSTED_METHOD_ROUTES.get("escrowWithdrawals");
|
|
71
170
|
const path = formatRoutePath(route, { address });
|
|
72
171
|
return _tradingRequest(this.client, {
|
|
73
172
|
method: route.method,
|
|
74
173
|
path,
|
|
75
|
-
params: { include
|
|
174
|
+
params: { include },
|
|
76
175
|
});
|
|
77
176
|
}
|
|
78
177
|
}
|
|
@@ -81,12 +81,14 @@ export class ServerManager {
|
|
|
81
81
|
* Check if the server is running.
|
|
82
82
|
*/
|
|
83
83
|
async isServerRunning() {
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
const info = this.getServerInfo();
|
|
85
|
+
if (!info?.port) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
86
88
|
try {
|
|
87
89
|
// Use native fetch to check health on the actual running port
|
|
88
90
|
// This avoids issues where this.api is configured with the wrong port
|
|
89
|
-
const response = await fetch(`http://localhost:${port}/health`, {
|
|
91
|
+
const response = await fetch(`http://localhost:${info.port}/health`, {
|
|
90
92
|
signal: AbortSignal.timeout(5_000),
|
|
91
93
|
});
|
|
92
94
|
if (response.ok) {
|
package/dist/index.d.ts
CHANGED
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
* console.log(markets[0].title);
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
-
import { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Rain, Mock } from "./pmxt/client.js";
|
|
20
|
+
import { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Rain, Hunch, Mock } from "./pmxt/client.js";
|
|
21
21
|
import { Router } from "./pmxt/router.js";
|
|
22
22
|
import { ServerManager } from "./pmxt/server-manager.js";
|
|
23
23
|
import { FeedClient } from "./pmxt/feed-client.js";
|
|
24
24
|
import * as models from "./pmxt/models.js";
|
|
25
25
|
import * as errors from "./pmxt/errors.js";
|
|
26
|
-
export { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Mock, PolymarketOptions } from "./pmxt/client.js";
|
|
26
|
+
export { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Hunch, Mock, PolymarketOptions } from "./pmxt/client.js";
|
|
27
27
|
export { FeedClient } from "./pmxt/feed-client.js";
|
|
28
28
|
export type { Ticker, Tickers, OHLCV, Market as FeedMarket, OracleRound, FeedClientOptions } from "./pmxt/feed-client.js";
|
|
29
29
|
export { Router } from "./pmxt/router.js";
|
|
@@ -97,6 +97,7 @@ declare const pmxt: {
|
|
|
97
97
|
SuiBets: typeof SuiBets;
|
|
98
98
|
Suibets: typeof SuiBets;
|
|
99
99
|
Rain: typeof Rain;
|
|
100
|
+
Hunch: typeof Hunch;
|
|
100
101
|
Mock: typeof Mock;
|
|
101
102
|
Router: typeof Router;
|
|
102
103
|
ServerManager: typeof ServerManager;
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
55
55
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
56
56
|
};
|
|
57
57
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
|
-
exports.server = exports.MarketList = exports.LIMITLESS_VENUE_ESCROW_ADDRESSES = exports.VENUE_ESCROW_ADDRESSES = exports.PREFUNDED_ESCROW_ADDRESSES = exports.resolvePmxtBaseUrl = exports.ENV = exports.LOCAL_URL = exports.HOSTED_URL = exports.ServerManager = exports.Router = exports.FeedClient = exports.Mock = exports.Rain = exports.Suibets = exports.SuiBets = exports.Hyperliquid = exports.GeminiTitan = exports.PolymarketUS = exports.Smarkets = exports.Metaculus = exports.Opinion = exports.Baozi = exports.Probable = exports.Myriad = exports.Limitless = exports.KalshiDemo = exports.Kalshi = exports.Polymarket = exports.Exchange = void 0;
|
|
58
|
+
exports.server = exports.MarketList = exports.LIMITLESS_VENUE_ESCROW_ADDRESSES = exports.VENUE_ESCROW_ADDRESSES = exports.PREFUNDED_ESCROW_ADDRESSES = exports.resolvePmxtBaseUrl = exports.ENV = exports.LOCAL_URL = exports.HOSTED_URL = exports.ServerManager = exports.Router = exports.FeedClient = exports.Mock = exports.Hunch = exports.Rain = exports.Suibets = exports.SuiBets = exports.Hyperliquid = exports.GeminiTitan = exports.PolymarketUS = exports.Smarkets = exports.Metaculus = exports.Opinion = exports.Baozi = exports.Probable = exports.Myriad = exports.Limitless = exports.KalshiDemo = exports.Kalshi = exports.Polymarket = exports.Exchange = void 0;
|
|
59
59
|
const client_js_1 = require("./pmxt/client.js");
|
|
60
60
|
const router_js_1 = require("./pmxt/router.js");
|
|
61
61
|
const server_manager_js_1 = require("./pmxt/server-manager.js");
|
|
@@ -80,6 +80,7 @@ Object.defineProperty(exports, "Hyperliquid", { enumerable: true, get: function
|
|
|
80
80
|
Object.defineProperty(exports, "SuiBets", { enumerable: true, get: function () { return client_js_2.SuiBets; } });
|
|
81
81
|
Object.defineProperty(exports, "Suibets", { enumerable: true, get: function () { return client_js_2.Suibets; } });
|
|
82
82
|
Object.defineProperty(exports, "Rain", { enumerable: true, get: function () { return client_js_2.Rain; } });
|
|
83
|
+
Object.defineProperty(exports, "Hunch", { enumerable: true, get: function () { return client_js_2.Hunch; } });
|
|
83
84
|
Object.defineProperty(exports, "Mock", { enumerable: true, get: function () { return client_js_2.Mock; } });
|
|
84
85
|
var feed_client_js_2 = require("./pmxt/feed-client.js");
|
|
85
86
|
Object.defineProperty(exports, "FeedClient", { enumerable: true, get: function () { return feed_client_js_2.FeedClient; } });
|
|
@@ -146,6 +147,7 @@ const pmxt = {
|
|
|
146
147
|
SuiBets: client_js_1.SuiBets,
|
|
147
148
|
Suibets: client_js_1.Suibets,
|
|
148
149
|
Rain: client_js_1.Rain,
|
|
150
|
+
Hunch: client_js_1.Hunch,
|
|
149
151
|
Mock: client_js_1.Mock,
|
|
150
152
|
Router: router_js_1.Router,
|
|
151
153
|
ServerManager: server_manager_js_1.ServerManager,
|
package/dist/pmxt/client.d.ts
CHANGED
|
@@ -105,6 +105,7 @@ export declare abstract class Exchange {
|
|
|
105
105
|
private _wsClient;
|
|
106
106
|
/** Sticky flag: true if the sidecar /ws endpoint is unavailable. */
|
|
107
107
|
private _wsUnsupported;
|
|
108
|
+
private readonly _useSidecarLockBaseUrl;
|
|
108
109
|
constructor(exchangeName: string, options?: ExchangeOptions);
|
|
109
110
|
private initializeServer;
|
|
110
111
|
protected handleResponse(response: any): any;
|
|
@@ -125,9 +126,9 @@ export declare abstract class Exchange {
|
|
|
125
126
|
/**
|
|
126
127
|
* Resolve the current sidecar base URL.
|
|
127
128
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
129
|
+
* Hosted mode and explicit baseUrl clients keep the configured basePath.
|
|
130
|
+
* Default local clients re-read the lock-file port on every call so they
|
|
131
|
+
* pick up sidecar restarts that land on a different port.
|
|
131
132
|
*/
|
|
132
133
|
private resolveBaseUrl;
|
|
133
134
|
/**
|
|
@@ -576,6 +577,14 @@ export interface PolymarketOptions {
|
|
|
576
577
|
proxyAddress?: string;
|
|
577
578
|
/** Optional signature type */
|
|
578
579
|
signatureType?: 'eoa' | 'poly-proxy' | 'gnosis-safe' | number;
|
|
580
|
+
/**
|
|
581
|
+
* EVM wallet address used for hosted reads/writes.
|
|
582
|
+
*/
|
|
583
|
+
walletAddress?: string;
|
|
584
|
+
/**
|
|
585
|
+
* External signer used for hosted writes.
|
|
586
|
+
*/
|
|
587
|
+
signer?: Signer;
|
|
579
588
|
}
|
|
580
589
|
export declare class Polymarket extends Exchange {
|
|
581
590
|
constructor(options?: PolymarketOptions);
|
|
@@ -856,6 +865,15 @@ export declare const Suibets: typeof SuiBets;
|
|
|
856
865
|
export declare class Rain extends Exchange {
|
|
857
866
|
constructor(options?: ExchangeOptions);
|
|
858
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* Hunch exchange client.
|
|
870
|
+
*
|
|
871
|
+
* Hunch is a crypto-native prediction market. Reads are unauthenticated;
|
|
872
|
+
* trading requires an EVM private key.
|
|
873
|
+
*/
|
|
874
|
+
export declare class Hunch extends Exchange {
|
|
875
|
+
constructor(options?: ExchangeOptions);
|
|
876
|
+
}
|
|
859
877
|
/**
|
|
860
878
|
* Mock exchange client.
|
|
861
879
|
*
|
package/dist/pmxt/client.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* OpenAPI client, matching the Python API exactly.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.Mock = exports.Rain = exports.Suibets = exports.SuiBets = exports.Hyperliquid = exports.GeminiTitan = exports.PolymarketUS = exports.Smarkets = exports.Metaculus = exports.Opinion = exports.Baozi = exports.Probable = exports.Myriad = exports.KalshiDemo = exports.Limitless = exports.Kalshi = exports.Polymarket = exports.Exchange = void 0;
|
|
9
|
+
exports.Mock = exports.Hunch = exports.Rain = exports.Suibets = exports.SuiBets = exports.Hyperliquid = exports.GeminiTitan = exports.PolymarketUS = exports.Smarkets = exports.Metaculus = exports.Opinion = exports.Baozi = exports.Probable = exports.Myriad = exports.KalshiDemo = exports.Limitless = exports.Kalshi = exports.Polymarket = exports.Exchange = void 0;
|
|
10
10
|
const index_js_1 = require("../generated/src/index.js");
|
|
11
11
|
const models_js_1 = require("./models.js");
|
|
12
12
|
const server_manager_js_1 = require("./server-manager.js");
|
|
@@ -193,6 +193,7 @@ class Exchange {
|
|
|
193
193
|
_wsClient = null;
|
|
194
194
|
/** Sticky flag: true if the sidecar /ws endpoint is unavailable. */
|
|
195
195
|
_wsUnsupported = false;
|
|
196
|
+
_useSidecarLockBaseUrl;
|
|
196
197
|
constructor(exchangeName, options = {}) {
|
|
197
198
|
this.exchangeName = exchangeName.toLowerCase();
|
|
198
199
|
this.apiKey = options.apiKey;
|
|
@@ -210,6 +211,9 @@ class Exchange {
|
|
|
210
211
|
const baseUrl = resolved.baseUrl;
|
|
211
212
|
this.pmxtApiKey = resolved.pmxtApiKey;
|
|
212
213
|
this.isHosted = resolved.isHosted;
|
|
214
|
+
const hasBaseUrlOverride = Boolean(options.baseUrl ||
|
|
215
|
+
(typeof process !== "undefined" && process.env[constants_js_1.ENV.BASE_URL]));
|
|
216
|
+
this._useSidecarLockBaseUrl = !this.isHosted && !hasBaseUrlOverride;
|
|
213
217
|
// Hosted trading bridge: if the caller passed a privateKey but no
|
|
214
218
|
// explicit signer, lazily wrap it in an EthersSigner so that
|
|
215
219
|
// `pmxt.Polymarket(pmxtApiKey, privateKey)` just works without the
|
|
@@ -250,15 +254,17 @@ class Exchange {
|
|
|
250
254
|
if (autoStartServer) {
|
|
251
255
|
try {
|
|
252
256
|
await this.serverManager.ensureServerRunning();
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
257
|
+
if (this._useSidecarLockBaseUrl) {
|
|
258
|
+
// Get the actual port the server is running on
|
|
259
|
+
// (may differ from default if default port was busy)
|
|
260
|
+
const actualPort = this.serverManager.getRunningPort();
|
|
261
|
+
const newBaseUrl = `http://localhost:${actualPort}`;
|
|
262
|
+
// Update API client with actual base URL
|
|
263
|
+
this.config = new index_js_1.Configuration({
|
|
264
|
+
basePath: newBaseUrl,
|
|
265
|
+
});
|
|
266
|
+
this.api = new index_js_1.DefaultApi(this.config);
|
|
267
|
+
}
|
|
262
268
|
}
|
|
263
269
|
catch (error) {
|
|
264
270
|
const msg = `Failed to start PMXT server: ${error instanceof Error ? error.message : error}\n\n` +
|
|
@@ -332,12 +338,12 @@ class Exchange {
|
|
|
332
338
|
/**
|
|
333
339
|
* Resolve the current sidecar base URL.
|
|
334
340
|
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
341
|
+
* Hosted mode and explicit baseUrl clients keep the configured basePath.
|
|
342
|
+
* Default local clients re-read the lock-file port on every call so they
|
|
343
|
+
* pick up sidecar restarts that land on a different port.
|
|
338
344
|
*/
|
|
339
345
|
resolveBaseUrl() {
|
|
340
|
-
if (this.isHosted)
|
|
346
|
+
if (this.isHosted || !this._useSidecarLockBaseUrl)
|
|
341
347
|
return this.config.basePath;
|
|
342
348
|
const port = this.serverManager.getRunningPort();
|
|
343
349
|
return `http://localhost:${port}`;
|
|
@@ -1021,7 +1027,10 @@ class Exchange {
|
|
|
1021
1027
|
const route = hosted_routing_js_1.HOSTED_METHOD_ROUTES.get("fetchOrder");
|
|
1022
1028
|
const path = (0, hosted_routing_js_1.formatRoutePath)(route, { order_id: orderId });
|
|
1023
1029
|
const data = await (0, hosted_routing_js_1._tradingRequest)(this, { method: route.method, path });
|
|
1024
|
-
|
|
1030
|
+
const payload = data && typeof data === "object" && "order" in data
|
|
1031
|
+
? data.order
|
|
1032
|
+
: data;
|
|
1033
|
+
return (0, hosted_mappers_js_1.orderFromV0)(payload);
|
|
1025
1034
|
}
|
|
1026
1035
|
try {
|
|
1027
1036
|
const args = [];
|
|
@@ -1127,6 +1136,9 @@ class Exchange {
|
|
|
1127
1136
|
}
|
|
1128
1137
|
async fetchClosedOrders(params) {
|
|
1129
1138
|
await this.initPromise;
|
|
1139
|
+
if (this.isHosted) {
|
|
1140
|
+
throw new errors_js_1.NotSupported("fetchClosedOrders is not available in hosted trading mode.");
|
|
1141
|
+
}
|
|
1130
1142
|
try {
|
|
1131
1143
|
const args = [];
|
|
1132
1144
|
if (params !== undefined)
|
|
@@ -1155,6 +1167,9 @@ class Exchange {
|
|
|
1155
1167
|
}
|
|
1156
1168
|
async fetchAllOrders(params) {
|
|
1157
1169
|
await this.initPromise;
|
|
1170
|
+
if (this.isHosted) {
|
|
1171
|
+
throw new errors_js_1.NotSupported("fetchAllOrders is not available in hosted trading mode.");
|
|
1172
|
+
}
|
|
1158
1173
|
try {
|
|
1159
1174
|
const args = [];
|
|
1160
1175
|
if (params !== undefined)
|
|
@@ -1188,7 +1203,9 @@ class Exchange {
|
|
|
1188
1203
|
const route = hosted_routing_js_1.HOSTED_METHOD_ROUTES.get("fetchPositions");
|
|
1189
1204
|
const path = (0, hosted_routing_js_1.formatRoutePath)(route, { address: resolvedAddress });
|
|
1190
1205
|
const data = await (0, hosted_routing_js_1._tradingRequest)(this, { method: route.method, path });
|
|
1191
|
-
const list = Array.isArray(data)
|
|
1206
|
+
const list = Array.isArray(data)
|
|
1207
|
+
? data
|
|
1208
|
+
: (data && Array.isArray(data.positions) ? data.positions : []);
|
|
1192
1209
|
return list.map((p) => (0, hosted_mappers_js_1.positionFromV0)(p));
|
|
1193
1210
|
}
|
|
1194
1211
|
try {
|
|
@@ -1224,7 +1241,9 @@ class Exchange {
|
|
|
1224
1241
|
const route = hosted_routing_js_1.HOSTED_METHOD_ROUTES.get("fetchBalance");
|
|
1225
1242
|
const path = (0, hosted_routing_js_1.formatRoutePath)(route, { address: resolvedAddress });
|
|
1226
1243
|
const data = await (0, hosted_routing_js_1._tradingRequest)(this, { method: route.method, path });
|
|
1227
|
-
const list = Array.isArray(data)
|
|
1244
|
+
const list = Array.isArray(data)
|
|
1245
|
+
? data
|
|
1246
|
+
: (data && Array.isArray(data.balances) ? data.balances : []);
|
|
1228
1247
|
return list.map((b) => (0, hosted_mappers_js_1.balanceFromV0)(b));
|
|
1229
1248
|
}
|
|
1230
1249
|
try {
|
|
@@ -3053,6 +3072,18 @@ class Rain extends Exchange {
|
|
|
3053
3072
|
}
|
|
3054
3073
|
}
|
|
3055
3074
|
exports.Rain = Rain;
|
|
3075
|
+
/**
|
|
3076
|
+
* Hunch exchange client.
|
|
3077
|
+
*
|
|
3078
|
+
* Hunch is a crypto-native prediction market. Reads are unauthenticated;
|
|
3079
|
+
* trading requires an EVM private key.
|
|
3080
|
+
*/
|
|
3081
|
+
class Hunch extends Exchange {
|
|
3082
|
+
constructor(options = {}) {
|
|
3083
|
+
super("hunch", options);
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
exports.Hunch = Hunch;
|
|
3056
3087
|
/**
|
|
3057
3088
|
* Mock exchange client.
|
|
3058
3089
|
*
|
package/dist/pmxt/escrow.js
CHANGED
|
@@ -12,6 +12,80 @@
|
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.Escrow = void 0;
|
|
14
14
|
const hosted_routing_1 = require("./hosted-routing");
|
|
15
|
+
const errors_1 = require("./errors");
|
|
16
|
+
const APPROVAL_TOKENS = new Set(["usdc", "ctf"]);
|
|
17
|
+
const WITHDRAW_ACTIONS = new Set(["request", "claim", "cancel"]);
|
|
18
|
+
const USDC_SCALE = 1000000n;
|
|
19
|
+
function isDecimalTokenId(token) {
|
|
20
|
+
return /^[0-9]+$/.test(token);
|
|
21
|
+
}
|
|
22
|
+
function normalizeApprovalToken(token) {
|
|
23
|
+
if (typeof token !== "string") {
|
|
24
|
+
throw new errors_1.ValidationError("token must be 'usdc', 'ctf', or a decimal CTF token_id string", "token");
|
|
25
|
+
}
|
|
26
|
+
const candidate = token.trim();
|
|
27
|
+
const normalized = candidate.toLowerCase();
|
|
28
|
+
if (APPROVAL_TOKENS.has(normalized)) {
|
|
29
|
+
return normalized;
|
|
30
|
+
}
|
|
31
|
+
if (isDecimalTokenId(candidate)) {
|
|
32
|
+
return candidate;
|
|
33
|
+
}
|
|
34
|
+
throw new errors_1.ValidationError("token must be 'usdc', 'ctf', or a decimal CTF token_id string", "token");
|
|
35
|
+
}
|
|
36
|
+
function normalizeAmountWei(amountWei) {
|
|
37
|
+
if (amountWei === undefined) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
if (typeof amountWei !== "bigint") {
|
|
41
|
+
throw new errors_1.ValidationError("amount_wei must be a non-negative integer", "amount_wei");
|
|
42
|
+
}
|
|
43
|
+
if (amountWei < 0n) {
|
|
44
|
+
throw new errors_1.ValidationError("amount_wei must be non-negative", "amount_wei");
|
|
45
|
+
}
|
|
46
|
+
return amountWei.toString();
|
|
47
|
+
}
|
|
48
|
+
function validateUsdcDecimal(value, field) {
|
|
49
|
+
const match = /^([0-9]+)(?:\.([0-9]+))?$/.exec(value);
|
|
50
|
+
if (!match) {
|
|
51
|
+
throw new errors_1.ValidationError(`${field} must be a finite positive number`, field);
|
|
52
|
+
}
|
|
53
|
+
const fractional = match[2] ?? "";
|
|
54
|
+
if (fractional.length > 6) {
|
|
55
|
+
throw new errors_1.ValidationError(`${field} precision exceeds 6 decimals; max precision for USDC is 0.000001`, field);
|
|
56
|
+
}
|
|
57
|
+
const scaled = BigInt(match[1]) * USDC_SCALE + BigInt(fractional.padEnd(6, "0"));
|
|
58
|
+
if (scaled <= 0n) {
|
|
59
|
+
throw new errors_1.ValidationError(`${field} must be positive`, field);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function normalizeUsdcAmount(value, field = "amount") {
|
|
63
|
+
if (typeof value === "bigint") {
|
|
64
|
+
if (value <= 0n) {
|
|
65
|
+
throw new errors_1.ValidationError(`${field} must be positive`, field);
|
|
66
|
+
}
|
|
67
|
+
return value.toString();
|
|
68
|
+
}
|
|
69
|
+
if (typeof value === "number") {
|
|
70
|
+
if (!Number.isFinite(value)) {
|
|
71
|
+
throw new errors_1.ValidationError(`${field} must be a finite positive number`, field);
|
|
72
|
+
}
|
|
73
|
+
validateUsdcDecimal(String(value), field);
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
if (typeof value === "string") {
|
|
77
|
+
const trimmed = value.trim();
|
|
78
|
+
validateUsdcDecimal(trimmed, field);
|
|
79
|
+
return trimmed;
|
|
80
|
+
}
|
|
81
|
+
throw new errors_1.ValidationError(`${field} must be a finite positive number`, field);
|
|
82
|
+
}
|
|
83
|
+
function normalizeWithdrawAction(action) {
|
|
84
|
+
if (typeof action === "string" && WITHDRAW_ACTIONS.has(action)) {
|
|
85
|
+
return action;
|
|
86
|
+
}
|
|
87
|
+
throw new errors_1.ValidationError("action must be 'request', 'claim', or 'cancel'", "action");
|
|
88
|
+
}
|
|
15
89
|
class Escrow {
|
|
16
90
|
client;
|
|
17
91
|
constructor(client) {
|
|
@@ -22,13 +96,16 @@ class Escrow {
|
|
|
22
96
|
* `amountWei` is omitted, the server returns an unlimited approval.
|
|
23
97
|
*/
|
|
24
98
|
async approveTx(token, amountWei) {
|
|
99
|
+
const address = (0, hosted_routing_1.resolveWalletAddress)(this.client);
|
|
100
|
+
const approvalAmount = normalizeAmountWei(amountWei);
|
|
25
101
|
const route = hosted_routing_1.HOSTED_METHOD_ROUTES.get("escrowApproveTx");
|
|
26
102
|
return (0, hosted_routing_1._tradingRequest)(this.client, {
|
|
27
103
|
method: route.method,
|
|
28
104
|
path: route.path,
|
|
29
105
|
body: {
|
|
30
|
-
token,
|
|
31
|
-
|
|
106
|
+
token: normalizeApprovalToken(token),
|
|
107
|
+
user_address: address,
|
|
108
|
+
...(approvalAmount === undefined ? {} : { amount_wei: approvalAmount }),
|
|
32
109
|
},
|
|
33
110
|
});
|
|
34
111
|
}
|
|
@@ -37,12 +114,15 @@ class Escrow {
|
|
|
37
114
|
* grid). Accepts number, decimal string, or BigInt in micro-units.
|
|
38
115
|
*/
|
|
39
116
|
async depositTx(amount) {
|
|
117
|
+
const address = (0, hosted_routing_1.resolveWalletAddress)(this.client);
|
|
40
118
|
const route = hosted_routing_1.HOSTED_METHOD_ROUTES.get("escrowDepositTx");
|
|
41
119
|
return (0, hosted_routing_1._tradingRequest)(this.client, {
|
|
42
120
|
method: route.method,
|
|
43
121
|
path: route.path,
|
|
44
122
|
body: {
|
|
45
|
-
|
|
123
|
+
token: "usdc",
|
|
124
|
+
amount: normalizeUsdcAmount(amount),
|
|
125
|
+
user_address: address,
|
|
46
126
|
},
|
|
47
127
|
});
|
|
48
128
|
}
|
|
@@ -52,16 +132,31 @@ class Escrow {
|
|
|
52
132
|
* the timelock, `cancel` aborts a pending request.
|
|
53
133
|
*/
|
|
54
134
|
async withdrawTx(action, amount) {
|
|
135
|
+
const normalizedAction = normalizeWithdrawAction(action);
|
|
136
|
+
const address = (0, hosted_routing_1.resolveWalletAddress)(this.client);
|
|
55
137
|
const route = hosted_routing_1.HOSTED_METHOD_ROUTES.get("escrowWithdrawTx");
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
138
|
+
if (normalizedAction === "request") {
|
|
139
|
+
if (amount === undefined) {
|
|
140
|
+
throw new errors_1.ValidationError("amount is required when action='request'", "amount");
|
|
141
|
+
}
|
|
142
|
+
return (0, hosted_routing_1._tradingRequest)(this.client, {
|
|
143
|
+
method: route.method,
|
|
144
|
+
path: route.path,
|
|
145
|
+
body: {
|
|
146
|
+
action: normalizedAction,
|
|
147
|
+
token: "usdc",
|
|
148
|
+
amount: normalizeUsdcAmount(amount),
|
|
149
|
+
user_address: address,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (amount !== undefined) {
|
|
154
|
+
throw new errors_1.ValidationError(`amount must be omitted when action='${normalizedAction}'`, "amount");
|
|
155
|
+
}
|
|
61
156
|
return (0, hosted_routing_1._tradingRequest)(this.client, {
|
|
62
157
|
method: route.method,
|
|
63
158
|
path: route.path,
|
|
64
|
-
body: { action,
|
|
159
|
+
body: { action: normalizedAction, token: "usdc", user_address: address },
|
|
65
160
|
});
|
|
66
161
|
}
|
|
67
162
|
/**
|
|
@@ -70,12 +165,16 @@ class Escrow {
|
|
|
70
165
|
*/
|
|
71
166
|
async withdrawals(opts = {}) {
|
|
72
167
|
const address = (0, hosted_routing_1.resolveWalletAddress)(this.client, opts.address);
|
|
168
|
+
const include = (opts.include ?? "pending,events").trim();
|
|
169
|
+
if (!include) {
|
|
170
|
+
throw new errors_1.ValidationError("include must not be empty", "include");
|
|
171
|
+
}
|
|
73
172
|
const route = hosted_routing_1.HOSTED_METHOD_ROUTES.get("escrowWithdrawals");
|
|
74
173
|
const path = (0, hosted_routing_1.formatRoutePath)(route, { address });
|
|
75
174
|
return (0, hosted_routing_1._tradingRequest)(this.client, {
|
|
76
175
|
method: route.method,
|
|
77
176
|
path,
|
|
78
|
-
params: { include
|
|
177
|
+
params: { include },
|
|
79
178
|
});
|
|
80
179
|
}
|
|
81
180
|
}
|
|
@@ -117,12 +117,14 @@ class ServerManager {
|
|
|
117
117
|
* Check if the server is running.
|
|
118
118
|
*/
|
|
119
119
|
async isServerRunning() {
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
const info = this.getServerInfo();
|
|
121
|
+
if (!info?.port) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
122
124
|
try {
|
|
123
125
|
// Use native fetch to check health on the actual running port
|
|
124
126
|
// This avoids issues where this.api is configured with the wrong port
|
|
125
|
-
const response = await fetch(`http://localhost:${port}/health`, {
|
|
127
|
+
const response = await fetch(`http://localhost:${info.port}/health`, {
|
|
126
128
|
signal: AbortSignal.timeout(5_000),
|
|
127
129
|
});
|
|
128
130
|
if (response.ok) {
|
package/generated/package.json
CHANGED
package/index.ts
CHANGED
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
import { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Mock } from "./pmxt/client.js";
|
|
22
|
+
import { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Hunch, Mock } from "./pmxt/client.js";
|
|
23
23
|
import { Router } from "./pmxt/router.js";
|
|
24
24
|
import { ServerManager } from "./pmxt/server-manager.js";
|
|
25
25
|
import { FeedClient } from "./pmxt/feed-client.js";
|
|
26
26
|
import * as models from "./pmxt/models.js";
|
|
27
27
|
import * as errors from "./pmxt/errors.js";
|
|
28
28
|
|
|
29
|
-
export { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Mock, PolymarketOptions } from "./pmxt/client.js";
|
|
29
|
+
export { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Suibets, Rain, Hunch, Mock, PolymarketOptions } from "./pmxt/client.js";
|
|
30
30
|
export { FeedClient } from "./pmxt/feed-client.js";
|
|
31
31
|
export type { Ticker, Tickers, OHLCV, Market as FeedMarket, OracleRound, FeedClientOptions } from "./pmxt/feed-client.js";
|
|
32
32
|
export { Router } from "./pmxt/router.js";
|
|
@@ -98,6 +98,7 @@ const pmxt = {
|
|
|
98
98
|
SuiBets,
|
|
99
99
|
Suibets,
|
|
100
100
|
Rain,
|
|
101
|
+
Hunch,
|
|
101
102
|
Mock,
|
|
102
103
|
Router,
|
|
103
104
|
ServerManager,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxtjs",
|
|
3
|
-
"version": "2.51.
|
|
3
|
+
"version": "2.51.2",
|
|
4
4
|
"description": "Unified prediction market data API - The ccxt for prediction markets",
|
|
5
5
|
"author": "PMXT Contributors",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"unified"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"pmxt-core": "2.51.
|
|
46
|
+
"pmxt-core": "2.51.2",
|
|
47
47
|
"ws": "^8.18.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
package/pmxt/client.ts
CHANGED
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
import { ServerManager } from "./server-manager.js";
|
|
53
53
|
import { buildArgsWithOptionalOptions } from "./args.js";
|
|
54
54
|
import { PmxtError, fromServerError, InvalidOrder, NotSupported } from "./errors.js";
|
|
55
|
-
import { LOCAL_URL, resolvePmxtBaseUrl } from "./constants.js";
|
|
55
|
+
import { ENV, LOCAL_URL, resolvePmxtBaseUrl } from "./constants.js";
|
|
56
56
|
import { SidecarWsClient } from "./ws-client.js";
|
|
57
57
|
import { logger } from "./logger.js";
|
|
58
58
|
|
|
@@ -351,6 +351,7 @@ export abstract class Exchange {
|
|
|
351
351
|
private _wsClient: SidecarWsClient | null = null;
|
|
352
352
|
/** Sticky flag: true if the sidecar /ws endpoint is unavailable. */
|
|
353
353
|
private _wsUnsupported: boolean = false;
|
|
354
|
+
private readonly _useSidecarLockBaseUrl: boolean;
|
|
354
355
|
|
|
355
356
|
constructor(exchangeName: string, options: ExchangeOptions = {}) {
|
|
356
357
|
this.exchangeName = exchangeName.toLowerCase();
|
|
@@ -370,6 +371,11 @@ export abstract class Exchange {
|
|
|
370
371
|
const baseUrl = resolved.baseUrl;
|
|
371
372
|
this.pmxtApiKey = resolved.pmxtApiKey;
|
|
372
373
|
this.isHosted = resolved.isHosted;
|
|
374
|
+
const hasBaseUrlOverride = Boolean(
|
|
375
|
+
options.baseUrl ||
|
|
376
|
+
(typeof process !== "undefined" && process.env[ENV.BASE_URL]),
|
|
377
|
+
);
|
|
378
|
+
this._useSidecarLockBaseUrl = !this.isHosted && !hasBaseUrlOverride;
|
|
373
379
|
|
|
374
380
|
// Hosted trading bridge: if the caller passed a privateKey but no
|
|
375
381
|
// explicit signer, lazily wrap it in an EthersSigner so that
|
|
@@ -417,16 +423,18 @@ export abstract class Exchange {
|
|
|
417
423
|
try {
|
|
418
424
|
await this.serverManager.ensureServerRunning();
|
|
419
425
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
426
|
+
if (this._useSidecarLockBaseUrl) {
|
|
427
|
+
// Get the actual port the server is running on
|
|
428
|
+
// (may differ from default if default port was busy)
|
|
429
|
+
const actualPort = this.serverManager.getRunningPort();
|
|
430
|
+
const newBaseUrl = `http://localhost:${actualPort}`;
|
|
424
431
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
432
|
+
// Update API client with actual base URL
|
|
433
|
+
this.config = new Configuration({
|
|
434
|
+
basePath: newBaseUrl,
|
|
435
|
+
});
|
|
436
|
+
this.api = new DefaultApi(this.config);
|
|
437
|
+
}
|
|
430
438
|
} catch (error) {
|
|
431
439
|
const msg =
|
|
432
440
|
`Failed to start PMXT server: ${error instanceof Error ? error.message : error}\n\n` +
|
|
@@ -511,12 +519,12 @@ export abstract class Exchange {
|
|
|
511
519
|
/**
|
|
512
520
|
* Resolve the current sidecar base URL.
|
|
513
521
|
*
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
522
|
+
* Hosted mode and explicit baseUrl clients keep the configured basePath.
|
|
523
|
+
* Default local clients re-read the lock-file port on every call so they
|
|
524
|
+
* pick up sidecar restarts that land on a different port.
|
|
517
525
|
*/
|
|
518
526
|
private resolveBaseUrl(): string {
|
|
519
|
-
if (this.isHosted) return this.config.basePath;
|
|
527
|
+
if (this.isHosted || !this._useSidecarLockBaseUrl) return this.config.basePath;
|
|
520
528
|
const port = this.serverManager.getRunningPort();
|
|
521
529
|
return `http://localhost:${port}`;
|
|
522
530
|
}
|
|
@@ -1225,7 +1233,10 @@ export abstract class Exchange {
|
|
|
1225
1233
|
const route = HOSTED_METHOD_ROUTES.get("fetchOrder")!;
|
|
1226
1234
|
const path = formatRoutePath(route, { order_id: orderId });
|
|
1227
1235
|
const data = await _tradingRequest(this, { method: route.method, path });
|
|
1228
|
-
|
|
1236
|
+
const payload = data && typeof data === "object" && "order" in data
|
|
1237
|
+
? (data as { order: unknown }).order
|
|
1238
|
+
: data;
|
|
1239
|
+
return orderFromV0(payload as Record<string, unknown>);
|
|
1229
1240
|
}
|
|
1230
1241
|
try {
|
|
1231
1242
|
const args: any[] = [];
|
|
@@ -1325,6 +1336,9 @@ export abstract class Exchange {
|
|
|
1325
1336
|
|
|
1326
1337
|
async fetchClosedOrders(params?: OrderHistoryParams): Promise<Order[]> {
|
|
1327
1338
|
await this.initPromise;
|
|
1339
|
+
if (this.isHosted) {
|
|
1340
|
+
throw new NotSupported("fetchClosedOrders is not available in hosted trading mode.");
|
|
1341
|
+
}
|
|
1328
1342
|
try {
|
|
1329
1343
|
const args: any[] = [];
|
|
1330
1344
|
if (params !== undefined) args.push(params);
|
|
@@ -1351,6 +1365,9 @@ export abstract class Exchange {
|
|
|
1351
1365
|
|
|
1352
1366
|
async fetchAllOrders(params?: OrderHistoryParams): Promise<Order[]> {
|
|
1353
1367
|
await this.initPromise;
|
|
1368
|
+
if (this.isHosted) {
|
|
1369
|
+
throw new NotSupported("fetchAllOrders is not available in hosted trading mode.");
|
|
1370
|
+
}
|
|
1354
1371
|
try {
|
|
1355
1372
|
const args: any[] = [];
|
|
1356
1373
|
if (params !== undefined) args.push(params);
|
|
@@ -1382,7 +1399,9 @@ export abstract class Exchange {
|
|
|
1382
1399
|
const route = HOSTED_METHOD_ROUTES.get("fetchPositions")!;
|
|
1383
1400
|
const path = formatRoutePath(route, { address: resolvedAddress });
|
|
1384
1401
|
const data = await _tradingRequest(this, { method: route.method, path });
|
|
1385
|
-
const list = Array.isArray(data)
|
|
1402
|
+
const list: unknown[] = Array.isArray(data)
|
|
1403
|
+
? data
|
|
1404
|
+
: (data && Array.isArray((data as any).positions) ? (data as any).positions : []);
|
|
1386
1405
|
return list.map((p) => positionFromV0(p as Record<string, unknown>));
|
|
1387
1406
|
}
|
|
1388
1407
|
try {
|
|
@@ -1416,7 +1435,9 @@ export abstract class Exchange {
|
|
|
1416
1435
|
const route = HOSTED_METHOD_ROUTES.get("fetchBalance")!;
|
|
1417
1436
|
const path = formatRoutePath(route, { address: resolvedAddress });
|
|
1418
1437
|
const data = await _tradingRequest(this, { method: route.method, path });
|
|
1419
|
-
const list = Array.isArray(data)
|
|
1438
|
+
const list: unknown[] = Array.isArray(data)
|
|
1439
|
+
? data
|
|
1440
|
+
: (data && Array.isArray((data as any).balances) ? (data as any).balances : []);
|
|
1420
1441
|
return list.map((b) => balanceFromV0(b as Record<string, unknown>));
|
|
1421
1442
|
}
|
|
1422
1443
|
try {
|
|
@@ -3048,6 +3069,16 @@ export interface PolymarketOptions {
|
|
|
3048
3069
|
|
|
3049
3070
|
/** Optional signature type */
|
|
3050
3071
|
signatureType?: 'eoa' | 'poly-proxy' | 'gnosis-safe' | number;
|
|
3072
|
+
|
|
3073
|
+
/**
|
|
3074
|
+
* EVM wallet address used for hosted reads/writes.
|
|
3075
|
+
*/
|
|
3076
|
+
walletAddress?: string;
|
|
3077
|
+
|
|
3078
|
+
/**
|
|
3079
|
+
* External signer used for hosted writes.
|
|
3080
|
+
*/
|
|
3081
|
+
signer?: Signer;
|
|
3051
3082
|
}
|
|
3052
3083
|
|
|
3053
3084
|
export class Polymarket extends Exchange {
|
|
@@ -3402,6 +3433,19 @@ export class Rain extends Exchange {
|
|
|
3402
3433
|
}
|
|
3403
3434
|
}
|
|
3404
3435
|
|
|
3436
|
+
|
|
3437
|
+
/**
|
|
3438
|
+
* Hunch exchange client.
|
|
3439
|
+
*
|
|
3440
|
+
* Hunch is a crypto-native prediction market. Reads are unauthenticated;
|
|
3441
|
+
* trading requires an EVM private key.
|
|
3442
|
+
*/
|
|
3443
|
+
export class Hunch extends Exchange {
|
|
3444
|
+
constructor(options: ExchangeOptions = {}) {
|
|
3445
|
+
super("hunch", options);
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3405
3449
|
/**
|
|
3406
3450
|
* Mock exchange client.
|
|
3407
3451
|
*
|
package/pmxt/escrow.ts
CHANGED
|
@@ -16,6 +16,99 @@ import {
|
|
|
16
16
|
HostedClientLike,
|
|
17
17
|
resolveWalletAddress,
|
|
18
18
|
} from "./hosted-routing";
|
|
19
|
+
import { ValidationError } from "./errors";
|
|
20
|
+
|
|
21
|
+
const APPROVAL_TOKENS = new Set(["usdc", "ctf"]);
|
|
22
|
+
const WITHDRAW_ACTIONS = new Set(["request", "claim", "cancel"]);
|
|
23
|
+
const USDC_SCALE = 1_000_000n;
|
|
24
|
+
|
|
25
|
+
function isDecimalTokenId(token: string): boolean {
|
|
26
|
+
return /^[0-9]+$/.test(token);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeApprovalToken(token: string): string {
|
|
30
|
+
if (typeof token !== "string") {
|
|
31
|
+
throw new ValidationError(
|
|
32
|
+
"token must be 'usdc', 'ctf', or a decimal CTF token_id string",
|
|
33
|
+
"token",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const candidate = token.trim();
|
|
38
|
+
const normalized = candidate.toLowerCase();
|
|
39
|
+
if (APPROVAL_TOKENS.has(normalized)) {
|
|
40
|
+
return normalized;
|
|
41
|
+
}
|
|
42
|
+
if (isDecimalTokenId(candidate)) {
|
|
43
|
+
return candidate;
|
|
44
|
+
}
|
|
45
|
+
throw new ValidationError(
|
|
46
|
+
"token must be 'usdc', 'ctf', or a decimal CTF token_id string",
|
|
47
|
+
"token",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeAmountWei(amountWei: bigint | undefined): string | undefined {
|
|
52
|
+
if (amountWei === undefined) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
if (typeof amountWei !== "bigint") {
|
|
56
|
+
throw new ValidationError("amount_wei must be a non-negative integer", "amount_wei");
|
|
57
|
+
}
|
|
58
|
+
if (amountWei < 0n) {
|
|
59
|
+
throw new ValidationError("amount_wei must be non-negative", "amount_wei");
|
|
60
|
+
}
|
|
61
|
+
return amountWei.toString();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function validateUsdcDecimal(value: string, field: string): void {
|
|
65
|
+
const match = /^([0-9]+)(?:\.([0-9]+))?$/.exec(value);
|
|
66
|
+
if (!match) {
|
|
67
|
+
throw new ValidationError(`${field} must be a finite positive number`, field);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const fractional = match[2] ?? "";
|
|
71
|
+
if (fractional.length > 6) {
|
|
72
|
+
throw new ValidationError(
|
|
73
|
+
`${field} precision exceeds 6 decimals; max precision for USDC is 0.000001`,
|
|
74
|
+
field,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const scaled = BigInt(match[1]) * USDC_SCALE + BigInt(fractional.padEnd(6, "0"));
|
|
79
|
+
if (scaled <= 0n) {
|
|
80
|
+
throw new ValidationError(`${field} must be positive`, field);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function normalizeUsdcAmount(value: number | string | bigint, field: string = "amount"): number | string {
|
|
85
|
+
if (typeof value === "bigint") {
|
|
86
|
+
if (value <= 0n) {
|
|
87
|
+
throw new ValidationError(`${field} must be positive`, field);
|
|
88
|
+
}
|
|
89
|
+
return value.toString();
|
|
90
|
+
}
|
|
91
|
+
if (typeof value === "number") {
|
|
92
|
+
if (!Number.isFinite(value)) {
|
|
93
|
+
throw new ValidationError(`${field} must be a finite positive number`, field);
|
|
94
|
+
}
|
|
95
|
+
validateUsdcDecimal(String(value), field);
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
if (typeof value === "string") {
|
|
99
|
+
const trimmed = value.trim();
|
|
100
|
+
validateUsdcDecimal(trimmed, field);
|
|
101
|
+
return trimmed;
|
|
102
|
+
}
|
|
103
|
+
throw new ValidationError(`${field} must be a finite positive number`, field);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalizeWithdrawAction(action: string): "request" | "claim" | "cancel" {
|
|
107
|
+
if (typeof action === "string" && WITHDRAW_ACTIONS.has(action)) {
|
|
108
|
+
return action as "request" | "claim" | "cancel";
|
|
109
|
+
}
|
|
110
|
+
throw new ValidationError("action must be 'request', 'claim', or 'cancel'", "action");
|
|
111
|
+
}
|
|
19
112
|
|
|
20
113
|
export class Escrow {
|
|
21
114
|
constructor(private readonly client: HostedClientLike) {}
|
|
@@ -25,13 +118,16 @@ export class Escrow {
|
|
|
25
118
|
* `amountWei` is omitted, the server returns an unlimited approval.
|
|
26
119
|
*/
|
|
27
120
|
async approveTx(token: string, amountWei?: bigint): Promise<unknown> {
|
|
121
|
+
const address = resolveWalletAddress(this.client);
|
|
122
|
+
const approvalAmount = normalizeAmountWei(amountWei);
|
|
28
123
|
const route = HOSTED_METHOD_ROUTES.get("escrowApproveTx")!;
|
|
29
124
|
return _tradingRequest(this.client, {
|
|
30
125
|
method: route.method,
|
|
31
126
|
path: route.path,
|
|
32
127
|
body: {
|
|
33
|
-
token,
|
|
34
|
-
|
|
128
|
+
token: normalizeApprovalToken(token),
|
|
129
|
+
user_address: address,
|
|
130
|
+
...(approvalAmount === undefined ? {} : { amount_wei: approvalAmount }),
|
|
35
131
|
},
|
|
36
132
|
});
|
|
37
133
|
}
|
|
@@ -41,12 +137,15 @@ export class Escrow {
|
|
|
41
137
|
* grid). Accepts number, decimal string, or BigInt in micro-units.
|
|
42
138
|
*/
|
|
43
139
|
async depositTx(amount: number | string | bigint): Promise<unknown> {
|
|
140
|
+
const address = resolveWalletAddress(this.client);
|
|
44
141
|
const route = HOSTED_METHOD_ROUTES.get("escrowDepositTx")!;
|
|
45
142
|
return _tradingRequest(this.client, {
|
|
46
143
|
method: route.method,
|
|
47
144
|
path: route.path,
|
|
48
145
|
body: {
|
|
49
|
-
|
|
146
|
+
token: "usdc",
|
|
147
|
+
amount: normalizeUsdcAmount(amount),
|
|
148
|
+
user_address: address,
|
|
50
149
|
},
|
|
51
150
|
});
|
|
52
151
|
}
|
|
@@ -60,17 +159,34 @@ export class Escrow {
|
|
|
60
159
|
action: "request" | "claim" | "cancel",
|
|
61
160
|
amount?: number | string | bigint,
|
|
62
161
|
): Promise<unknown> {
|
|
162
|
+
const normalizedAction = normalizeWithdrawAction(action);
|
|
163
|
+
const address = resolveWalletAddress(this.client);
|
|
63
164
|
const route = HOSTED_METHOD_ROUTES.get("escrowWithdrawTx")!;
|
|
64
|
-
|
|
65
|
-
amount === undefined
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
165
|
+
if (normalizedAction === "request") {
|
|
166
|
+
if (amount === undefined) {
|
|
167
|
+
throw new ValidationError("amount is required when action='request'", "amount");
|
|
168
|
+
}
|
|
169
|
+
return _tradingRequest(this.client, {
|
|
170
|
+
method: route.method,
|
|
171
|
+
path: route.path,
|
|
172
|
+
body: {
|
|
173
|
+
action: normalizedAction,
|
|
174
|
+
token: "usdc",
|
|
175
|
+
amount: normalizeUsdcAmount(amount),
|
|
176
|
+
user_address: address,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (amount !== undefined) {
|
|
181
|
+
throw new ValidationError(
|
|
182
|
+
`amount must be omitted when action='${normalizedAction}'`,
|
|
183
|
+
"amount",
|
|
184
|
+
);
|
|
185
|
+
}
|
|
70
186
|
return _tradingRequest(this.client, {
|
|
71
187
|
method: route.method,
|
|
72
188
|
path: route.path,
|
|
73
|
-
body: { action,
|
|
189
|
+
body: { action: normalizedAction, token: "usdc", user_address: address },
|
|
74
190
|
});
|
|
75
191
|
}
|
|
76
192
|
|
|
@@ -82,12 +198,16 @@ export class Escrow {
|
|
|
82
198
|
opts: { include?: string; address?: string } = {},
|
|
83
199
|
): Promise<unknown> {
|
|
84
200
|
const address = resolveWalletAddress(this.client, opts.address);
|
|
201
|
+
const include = (opts.include ?? "pending,events").trim();
|
|
202
|
+
if (!include) {
|
|
203
|
+
throw new ValidationError("include must not be empty", "include");
|
|
204
|
+
}
|
|
85
205
|
const route = HOSTED_METHOD_ROUTES.get("escrowWithdrawals")!;
|
|
86
206
|
const path = formatRoutePath(route, { address });
|
|
87
207
|
return _tradingRequest(this.client, {
|
|
88
208
|
method: route.method,
|
|
89
209
|
path,
|
|
90
|
-
params: { include
|
|
210
|
+
params: { include },
|
|
91
211
|
});
|
|
92
212
|
}
|
|
93
213
|
}
|
package/pmxt/server-manager.ts
CHANGED
|
@@ -103,13 +103,15 @@ export class ServerManager {
|
|
|
103
103
|
* Check if the server is running.
|
|
104
104
|
*/
|
|
105
105
|
async isServerRunning(): Promise<boolean> {
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
const info = this.getServerInfo();
|
|
107
|
+
if (!info?.port) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
108
110
|
|
|
109
111
|
try {
|
|
110
112
|
// Use native fetch to check health on the actual running port
|
|
111
113
|
// This avoids issues where this.api is configured with the wrong port
|
|
112
|
-
const response = await fetch(`http://localhost:${port}/health`, {
|
|
114
|
+
const response = await fetch(`http://localhost:${info.port}/health`, {
|
|
113
115
|
signal: AbortSignal.timeout(5_000),
|
|
114
116
|
});
|
|
115
117
|
if (response.ok) {
|