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/errors.ts
CHANGED
|
@@ -116,6 +116,12 @@ export class ExchangeNotAvailable extends PmxtError {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
export class NotSupported extends PmxtError {
|
|
120
|
+
constructor(message: string, exchange?: string) {
|
|
121
|
+
super(message, "NOT_SUPPORTED", false, exchange);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
119
125
|
// Error code to class mapping
|
|
120
126
|
const ERROR_CODE_MAP: Record<string, new (...args: any[]) => PmxtError> = {
|
|
121
127
|
BAD_REQUEST: BadRequest,
|
|
@@ -162,3 +168,8 @@ export function fromServerError(errorData: unknown): PmxtError {
|
|
|
162
168
|
|
|
163
169
|
return new PmxtError(message, code, retryable, exchange);
|
|
164
170
|
}
|
|
171
|
+
|
|
172
|
+
// Hosted error classes live in ./hosted-errors. Re-exported from index.ts
|
|
173
|
+
// at the package root, NOT here — re-exporting from errors.ts creates a
|
|
174
|
+
// circular dependency (hosted-errors imports PmxtError from errors.ts)
|
|
175
|
+
// that crashes tsx/CJS module loaders even though tsc and ts-jest tolerate it.
|
package/pmxt/escrow.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosted-mode escrow namespace.
|
|
3
|
+
*
|
|
4
|
+
* Exposes the `/v0/escrow/*` endpoints of the pmxt hosted trading API as a
|
|
5
|
+
* small ergonomic helper class. Each method returns the raw upstream JSON
|
|
6
|
+
* payload as `unknown` — typed mappers can be layered on later without
|
|
7
|
+
* breaking the wire shape.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors `sdks/python/pmxt/escrow.py`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
_tradingRequest,
|
|
14
|
+
formatRoutePath,
|
|
15
|
+
HOSTED_METHOD_ROUTES,
|
|
16
|
+
HostedClientLike,
|
|
17
|
+
resolveWalletAddress,
|
|
18
|
+
} from "./hosted-routing";
|
|
19
|
+
|
|
20
|
+
export class Escrow {
|
|
21
|
+
constructor(private readonly client: HostedClientLike) {}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build an unsigned approve transaction for a given ERC-20 `token`. When
|
|
25
|
+
* `amountWei` is omitted, the server returns an unlimited approval.
|
|
26
|
+
*/
|
|
27
|
+
async approveTx(token: string, amountWei?: bigint): Promise<unknown> {
|
|
28
|
+
const route = HOSTED_METHOD_ROUTES.get("escrowApproveTx")!;
|
|
29
|
+
return _tradingRequest(this.client, {
|
|
30
|
+
method: route.method,
|
|
31
|
+
path: route.path,
|
|
32
|
+
body: {
|
|
33
|
+
token,
|
|
34
|
+
amount_wei: amountWei === undefined ? null : amountWei.toString(),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build an unsigned deposit transaction for `amount` (USDC, 6-decimal
|
|
41
|
+
* grid). Accepts number, decimal string, or BigInt in micro-units.
|
|
42
|
+
*/
|
|
43
|
+
async depositTx(amount: number | string | bigint): Promise<unknown> {
|
|
44
|
+
const route = HOSTED_METHOD_ROUTES.get("escrowDepositTx")!;
|
|
45
|
+
return _tradingRequest(this.client, {
|
|
46
|
+
method: route.method,
|
|
47
|
+
path: route.path,
|
|
48
|
+
body: {
|
|
49
|
+
amount: typeof amount === "bigint" ? amount.toString() : amount,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build an unsigned withdraw transaction. `action` selects the stage of
|
|
56
|
+
* the withdrawal lifecycle: `request` initiates, `claim` finalizes after
|
|
57
|
+
* the timelock, `cancel` aborts a pending request.
|
|
58
|
+
*/
|
|
59
|
+
async withdrawTx(
|
|
60
|
+
action: "request" | "claim" | "cancel",
|
|
61
|
+
amount?: number | string | bigint,
|
|
62
|
+
): Promise<unknown> {
|
|
63
|
+
const route = HOSTED_METHOD_ROUTES.get("escrowWithdrawTx")!;
|
|
64
|
+
const normalizedAmount =
|
|
65
|
+
amount === undefined
|
|
66
|
+
? null
|
|
67
|
+
: typeof amount === "bigint"
|
|
68
|
+
? amount.toString()
|
|
69
|
+
: amount;
|
|
70
|
+
return _tradingRequest(this.client, {
|
|
71
|
+
method: route.method,
|
|
72
|
+
path: route.path,
|
|
73
|
+
body: { action, amount: normalizedAmount },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* List the user's withdrawal records. `include` is forwarded verbatim and
|
|
79
|
+
* defaults to `"pending,events"` to match the Python SDK.
|
|
80
|
+
*/
|
|
81
|
+
async withdrawals(
|
|
82
|
+
opts: { include?: string; address?: string } = {},
|
|
83
|
+
): Promise<unknown> {
|
|
84
|
+
const address = resolveWalletAddress(this.client, opts.address);
|
|
85
|
+
const route = HOSTED_METHOD_ROUTES.get("escrowWithdrawals")!;
|
|
86
|
+
const path = formatRoutePath(route, { address });
|
|
87
|
+
return _tradingRequest(this.client, {
|
|
88
|
+
method: route.method,
|
|
89
|
+
path,
|
|
90
|
+
params: { include: opts.include ?? "pending,events" },
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
package/pmxt/feed-client.ts
CHANGED
|
@@ -80,6 +80,10 @@ export class FeedClient {
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
async listFeeds(): Promise<string[]> {
|
|
84
|
+
return this.getRoot<string[]>();
|
|
85
|
+
}
|
|
86
|
+
|
|
83
87
|
async loadMarkets(): Promise<Record<string, Market>> {
|
|
84
88
|
return this.get<Record<string, Market>>('loadMarkets', {});
|
|
85
89
|
}
|
|
@@ -118,14 +122,22 @@ export class FeedClient {
|
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
private async get<T>(method: string, params: Record<string, unknown>): Promise<T> {
|
|
125
|
+
return this.request<T>(`${this.baseUrl}/api/feeds/${this.feedName}/${method}`, params);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async getRoot<T>(params: Record<string, unknown> = {}): Promise<T> {
|
|
129
|
+
return this.request<T>(`${this.baseUrl}/api/feeds/`, params);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private async request<T>(url: string, params: Record<string, unknown>): Promise<T> {
|
|
121
133
|
const qs = Object.entries(params)
|
|
122
134
|
.filter(([, v]) => v !== undefined && v !== null)
|
|
123
135
|
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
|
|
124
136
|
.join('&');
|
|
125
137
|
|
|
126
|
-
const
|
|
138
|
+
const requestUrl = `${url}${qs ? '?' + qs : ''}`;
|
|
127
139
|
|
|
128
|
-
const response = await fetch(
|
|
140
|
+
const response = await fetch(requestUrl, {
|
|
129
141
|
headers: this.headers,
|
|
130
142
|
signal: AbortSignal.timeout(30_000),
|
|
131
143
|
});
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosted trading error hierarchy and upstream response mapping.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the Python implementation in `pmxt/_hosted_errors.py`. TypeScript
|
|
5
|
+
* does not support multiple inheritance, so each hosted error class extends
|
|
6
|
+
* the semantically-closest legacy parent (e.g. `InsufficientEscrowBalance`
|
|
7
|
+
* extends `InsufficientFunds`) so existing `instanceof` checks continue to
|
|
8
|
+
* match. The `static isHostedError = true` flag and the {@link isHostedError}
|
|
9
|
+
* helper provide a structural alternative for callers that want to detect
|
|
10
|
+
* hosted-mode failures regardless of the concrete subclass.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
AuthenticationError,
|
|
15
|
+
ExchangeNotAvailable,
|
|
16
|
+
InsufficientFunds,
|
|
17
|
+
InvalidOrder,
|
|
18
|
+
NotFoundError,
|
|
19
|
+
PmxtError,
|
|
20
|
+
ValidationError,
|
|
21
|
+
} from "./errors";
|
|
22
|
+
|
|
23
|
+
export class HostedTradingError extends PmxtError {
|
|
24
|
+
static readonly isHostedError = true;
|
|
25
|
+
readonly status: number;
|
|
26
|
+
readonly detail: string;
|
|
27
|
+
|
|
28
|
+
constructor(status: number, detail: string) {
|
|
29
|
+
super(detail);
|
|
30
|
+
this.status = status;
|
|
31
|
+
this.detail = detail;
|
|
32
|
+
this.name = this.constructor.name;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class InsufficientEscrowBalance extends InsufficientFunds {
|
|
37
|
+
static readonly isHostedError = true;
|
|
38
|
+
readonly status: number;
|
|
39
|
+
readonly detail: string;
|
|
40
|
+
|
|
41
|
+
constructor(status: number, detail: string) {
|
|
42
|
+
super(detail);
|
|
43
|
+
this.status = status;
|
|
44
|
+
this.detail = detail;
|
|
45
|
+
this.name = this.constructor.name;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class OrderSizeTooSmall extends InvalidOrder {
|
|
50
|
+
static readonly isHostedError = true;
|
|
51
|
+
readonly status: number;
|
|
52
|
+
readonly detail: string;
|
|
53
|
+
|
|
54
|
+
constructor(status: number, detail: string) {
|
|
55
|
+
super(detail);
|
|
56
|
+
this.status = status;
|
|
57
|
+
this.detail = detail;
|
|
58
|
+
this.name = this.constructor.name;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class InvalidApiKey extends AuthenticationError {
|
|
63
|
+
static readonly isHostedError = true;
|
|
64
|
+
readonly status: number;
|
|
65
|
+
readonly detail: string;
|
|
66
|
+
|
|
67
|
+
constructor(status: number, detail: string) {
|
|
68
|
+
super(detail);
|
|
69
|
+
this.status = status;
|
|
70
|
+
this.detail = detail;
|
|
71
|
+
this.name = this.constructor.name;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class OutcomeNotFound extends NotFoundError {
|
|
76
|
+
static readonly isHostedError = true;
|
|
77
|
+
readonly status: number;
|
|
78
|
+
readonly detail: string;
|
|
79
|
+
|
|
80
|
+
constructor(status: number, detail: string) {
|
|
81
|
+
super(detail);
|
|
82
|
+
this.status = status;
|
|
83
|
+
this.detail = detail;
|
|
84
|
+
this.name = this.constructor.name;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class CatalogUnavailable extends ExchangeNotAvailable {
|
|
89
|
+
static readonly isHostedError = true;
|
|
90
|
+
readonly status: number;
|
|
91
|
+
readonly detail: string;
|
|
92
|
+
|
|
93
|
+
constructor(status: number, detail: string) {
|
|
94
|
+
super(detail);
|
|
95
|
+
this.status = status;
|
|
96
|
+
this.detail = detail;
|
|
97
|
+
this.name = this.constructor.name;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export class BuiltOrderExpired extends InvalidOrder {
|
|
102
|
+
static readonly isHostedError = true;
|
|
103
|
+
readonly status: number;
|
|
104
|
+
readonly detail: string;
|
|
105
|
+
|
|
106
|
+
constructor(status: number, detail: string) {
|
|
107
|
+
super(detail);
|
|
108
|
+
this.status = status;
|
|
109
|
+
this.detail = detail;
|
|
110
|
+
this.name = this.constructor.name;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export class InvalidSignature extends AuthenticationError {
|
|
115
|
+
static readonly isHostedError = true;
|
|
116
|
+
readonly status: number;
|
|
117
|
+
readonly detail: string;
|
|
118
|
+
|
|
119
|
+
constructor(status: number, detail: string) {
|
|
120
|
+
super(detail);
|
|
121
|
+
this.status = status;
|
|
122
|
+
this.detail = detail;
|
|
123
|
+
this.name = this.constructor.name;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export class NoLiquidity extends InvalidOrder {
|
|
128
|
+
static readonly isHostedError = true;
|
|
129
|
+
readonly status: number;
|
|
130
|
+
readonly detail: string;
|
|
131
|
+
|
|
132
|
+
constructor(status: number, detail: string) {
|
|
133
|
+
super(detail);
|
|
134
|
+
this.status = status;
|
|
135
|
+
this.detail = detail;
|
|
136
|
+
this.name = this.constructor.name;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Local validation failure. Not a hosted-mode upstream error, so does NOT
|
|
142
|
+
* carry the `isHostedError` flag.
|
|
143
|
+
*/
|
|
144
|
+
export class MissingWalletAddress extends ValidationError {
|
|
145
|
+
constructor(message: string) {
|
|
146
|
+
super(message);
|
|
147
|
+
this.name = this.constructor.name;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Returns true if `e` is a hosted trading error — either an instance of
|
|
153
|
+
* {@link HostedTradingError} or any subclass that carries the
|
|
154
|
+
* `static isHostedError = true` marker.
|
|
155
|
+
*/
|
|
156
|
+
export function isHostedError(e: unknown): boolean {
|
|
157
|
+
if (e instanceof HostedTradingError) return true;
|
|
158
|
+
const ctor = (e as { constructor?: { isHostedError?: boolean } } | null)?.constructor;
|
|
159
|
+
return ctor != null && ctor.isHostedError === true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Read an upstream `Response` and throw the matching hosted trading error.
|
|
164
|
+
* Always throws — return type is `never`.
|
|
165
|
+
*/
|
|
166
|
+
export async function raiseFromResponse(response: Response): Promise<never> {
|
|
167
|
+
const status = response.status;
|
|
168
|
+
let detail = "";
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const body = await response.json();
|
|
172
|
+
if (body != null && typeof body === "object") {
|
|
173
|
+
const record = body as Record<string, unknown>;
|
|
174
|
+
const candidate =
|
|
175
|
+
(typeof record.detail === "string" ? record.detail : undefined) ||
|
|
176
|
+
(typeof record.message === "string" ? record.message : undefined) ||
|
|
177
|
+
(typeof record.error === "string" ? record.error : undefined);
|
|
178
|
+
detail = candidate || JSON.stringify(body);
|
|
179
|
+
} else {
|
|
180
|
+
detail = typeof body === "string" ? body : JSON.stringify(body);
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
try {
|
|
184
|
+
detail = await response.text();
|
|
185
|
+
} catch {
|
|
186
|
+
detail = `HTTP ${status}`;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (status === 401) {
|
|
191
|
+
throw new InvalidApiKey(status, detail);
|
|
192
|
+
}
|
|
193
|
+
if (detail.startsWith("Insufficient escrow balance")) {
|
|
194
|
+
throw new InsufficientEscrowBalance(status, detail);
|
|
195
|
+
}
|
|
196
|
+
if (detail.includes("below the minimum")) {
|
|
197
|
+
throw new OrderSizeTooSmall(status, detail);
|
|
198
|
+
}
|
|
199
|
+
if (detail.includes("catalog: no outcome")) {
|
|
200
|
+
throw new OutcomeNotFound(status, detail);
|
|
201
|
+
}
|
|
202
|
+
if (detail.startsWith("catalog:")) {
|
|
203
|
+
throw new CatalogUnavailable(status, detail);
|
|
204
|
+
}
|
|
205
|
+
if (detail.includes("built_order_id expired") || detail.includes("cancel_id expired")) {
|
|
206
|
+
throw new BuiltOrderExpired(status, detail);
|
|
207
|
+
}
|
|
208
|
+
if (detail.includes("Invalid signature")) {
|
|
209
|
+
throw new InvalidSignature(status, detail);
|
|
210
|
+
}
|
|
211
|
+
if (detail.includes("book has no resting asks") || detail.includes("book has no resting bids")) {
|
|
212
|
+
throw new NoLiquidity(status, detail);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
throw new HostedTradingError(status, detail);
|
|
216
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosted trading v0 response mappers.
|
|
3
|
+
*
|
|
4
|
+
* The hosted trading API exposes explicit `/v0/*` JSON shapes. These helpers
|
|
5
|
+
* translate those wire dictionaries to the SDK TypeScript interfaces.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors `sdks/python/pmxt/_hosted_mappers.py`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { InvalidOrder } from "./errors";
|
|
11
|
+
import { Balance, Order, Position, UserTrade } from "./models";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Precision helper
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
const SIX_DEC_SCALE = 1_000_000n;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Convert a decimal amount to integer micro-units (6-decimal grid).
|
|
21
|
+
*
|
|
22
|
+
* Rejects amounts whose fractional part has more than 6 digits. Pure integer
|
|
23
|
+
* math via BigInt — no float rounding involved.
|
|
24
|
+
*/
|
|
25
|
+
export function to6dec(amount: number | string | bigint): bigint {
|
|
26
|
+
const str = typeof amount === "bigint" ? `${amount}` : String(amount);
|
|
27
|
+
const negative = str.startsWith("-");
|
|
28
|
+
const cleaned = negative ? str.slice(1) : str;
|
|
29
|
+
const [intPart, fracPart = ""] = cleaned.split(".");
|
|
30
|
+
if (fracPart.length > 6) {
|
|
31
|
+
throw new InvalidOrder(`amount precision exceeds 6 decimals: ${amount}`);
|
|
32
|
+
}
|
|
33
|
+
if (!/^\d*$/.test(intPart) || !/^\d*$/.test(fracPart)) {
|
|
34
|
+
throw new InvalidOrder(`invalid amount: ${amount}`);
|
|
35
|
+
}
|
|
36
|
+
const padded = (fracPart + "000000").slice(0, 6);
|
|
37
|
+
const intMag = BigInt(intPart || "0");
|
|
38
|
+
const scaled = intMag * SIX_DEC_SCALE + BigInt(padded);
|
|
39
|
+
return negative ? -scaled : scaled;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Order mappers
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
/** Map an `OrderV0` JSON object to {@link Order}. */
|
|
47
|
+
export function orderFromV0(payload: Record<string, unknown>): Order {
|
|
48
|
+
const id = strOrEmpty(payload["id"]);
|
|
49
|
+
const marketId = strOrEmpty(payload["market_id"] ?? payload["marketId"]);
|
|
50
|
+
const outcomeId = strOrEmpty(payload["outcome_id"] ?? payload["outcomeId"]);
|
|
51
|
+
const status = strOrEmpty(payload["status"]);
|
|
52
|
+
const sideRaw = payload["side"];
|
|
53
|
+
const side: Order["side"] = sideRaw === "sell" ? "sell" : "buy";
|
|
54
|
+
const typeRaw = payload["type"];
|
|
55
|
+
const type: Order["type"] = typeRaw === "limit" ? "limit" : "market";
|
|
56
|
+
|
|
57
|
+
const order: Order = {
|
|
58
|
+
id,
|
|
59
|
+
marketId,
|
|
60
|
+
outcomeId,
|
|
61
|
+
side,
|
|
62
|
+
type,
|
|
63
|
+
amount: floatOrZero(payload["amount"]),
|
|
64
|
+
status,
|
|
65
|
+
filled: floatOrZero(payload["filled"]),
|
|
66
|
+
remaining: floatOrZero(payload["remaining"]),
|
|
67
|
+
timestamp: timestampToMs(payload["timestamp"]),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const price = floatOrUndefined(payload["price"]);
|
|
71
|
+
if (price !== undefined) order.price = price;
|
|
72
|
+
|
|
73
|
+
const fee = floatOrUndefined(payload["fee"]);
|
|
74
|
+
if (fee !== undefined) order.fee = fee;
|
|
75
|
+
|
|
76
|
+
return order;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Map an {@link Order} back to an `OrderV0` JSON object. */
|
|
80
|
+
export function orderToV0(order: Order): Record<string, unknown> {
|
|
81
|
+
const out: Record<string, unknown> = {
|
|
82
|
+
id: order.id,
|
|
83
|
+
market_id: order.marketId,
|
|
84
|
+
outcome_id: order.outcomeId,
|
|
85
|
+
side: order.side,
|
|
86
|
+
type: order.type,
|
|
87
|
+
amount: order.amount,
|
|
88
|
+
status: order.status,
|
|
89
|
+
filled: order.filled,
|
|
90
|
+
remaining: order.remaining,
|
|
91
|
+
timestamp: msToTimestamp(order.timestamp),
|
|
92
|
+
};
|
|
93
|
+
if (order.price !== undefined) out["price"] = order.price;
|
|
94
|
+
if (order.fee !== undefined) out["fee"] = order.fee;
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// UserTrade mappers
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
/** Map a `UserTradeV0` JSON object to {@link UserTrade}. */
|
|
103
|
+
export function userTradeFromV0(payload: Record<string, unknown>): UserTrade {
|
|
104
|
+
const sideRaw = payload["side"];
|
|
105
|
+
const side: UserTrade["side"] =
|
|
106
|
+
sideRaw === "buy" || sideRaw === "sell" ? sideRaw : "unknown";
|
|
107
|
+
|
|
108
|
+
const trade: UserTrade = {
|
|
109
|
+
id: strOrEmpty(payload["id"]),
|
|
110
|
+
price: floatOrZero(payload["price"]),
|
|
111
|
+
amount: floatOrZero(payload["amount"]),
|
|
112
|
+
side,
|
|
113
|
+
timestamp: timestampToMs(payload["timestamp"]),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const orderId = strOrUndefined(payload["order_id"] ?? payload["orderId"]);
|
|
117
|
+
if (orderId !== undefined) trade.orderId = orderId;
|
|
118
|
+
|
|
119
|
+
const outcomeId = strOrUndefined(payload["outcome_id"] ?? payload["outcomeId"]);
|
|
120
|
+
if (outcomeId !== undefined) trade.outcomeId = outcomeId;
|
|
121
|
+
|
|
122
|
+
const marketId = strOrUndefined(payload["market_id"] ?? payload["marketId"]);
|
|
123
|
+
if (marketId !== undefined) trade.marketId = marketId;
|
|
124
|
+
|
|
125
|
+
return trade;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Map a {@link UserTrade} back to a `UserTradeV0` JSON object. */
|
|
129
|
+
export function userTradeToV0(trade: UserTrade): Record<string, unknown> {
|
|
130
|
+
const out: Record<string, unknown> = {
|
|
131
|
+
id: trade.id,
|
|
132
|
+
side: trade.side,
|
|
133
|
+
amount: trade.amount,
|
|
134
|
+
price: trade.price,
|
|
135
|
+
timestamp: msToTimestamp(trade.timestamp),
|
|
136
|
+
};
|
|
137
|
+
if (trade.orderId !== undefined) out["order_id"] = trade.orderId;
|
|
138
|
+
if (trade.outcomeId !== undefined) out["outcome_id"] = trade.outcomeId;
|
|
139
|
+
if (trade.marketId !== undefined) out["market_id"] = trade.marketId;
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Position mappers
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Map a `PositionV0` JSON object to {@link Position}.
|
|
149
|
+
*
|
|
150
|
+
* Optional fields (`outcomeLabel`, `entryPrice`, `currentPrice`,
|
|
151
|
+
* `unrealizedPnL`, `realizedPnL`) surface as `undefined` when missing —
|
|
152
|
+
* **never** a fake `0` or `""`. This keeps the SDK honest about which data
|
|
153
|
+
* the server actually provided.
|
|
154
|
+
*/
|
|
155
|
+
export function positionFromV0(payload: Record<string, unknown>): Position {
|
|
156
|
+
const size = floatOrZero(payload["shares"] ?? payload["size"]);
|
|
157
|
+
const entryPrice = floatOrUndefined(payload["entry_price"] ?? payload["entryPrice"]);
|
|
158
|
+
const currentPrice = floatOrUndefined(
|
|
159
|
+
payload["current_price"] ?? payload["currentPrice"],
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
let unrealizedPnL = floatOrUndefined(
|
|
163
|
+
payload["unrealized_pnl"] ?? payload["unrealizedPnl"] ?? payload["unrealizedPnL"],
|
|
164
|
+
);
|
|
165
|
+
if (
|
|
166
|
+
unrealizedPnL === undefined &&
|
|
167
|
+
entryPrice !== undefined &&
|
|
168
|
+
currentPrice !== undefined
|
|
169
|
+
) {
|
|
170
|
+
unrealizedPnL = (currentPrice - entryPrice) * size;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Position requires marketId/outcomeId/outcomeLabel/entryPrice/currentPrice/unrealizedPnL
|
|
174
|
+
// in the current interface. We construct with safe defaults and only override
|
|
175
|
+
// when present. Per plan v5: the SDK should NOT fabricate financial data —
|
|
176
|
+
// when the server didn't supply entryPrice/currentPrice/outcomeLabel, we mark
|
|
177
|
+
// them via undefined (cast through a partial since the existing Position type
|
|
178
|
+
// hasn't yet been widened to Optional in this parallel-agent change).
|
|
179
|
+
const position = {
|
|
180
|
+
marketId: strOrEmpty(payload["market_id"] ?? payload["marketId"]),
|
|
181
|
+
outcomeId: strOrEmpty(payload["outcome_id"] ?? payload["outcomeId"]),
|
|
182
|
+
outcomeLabel:
|
|
183
|
+
strOrUndefined(payload["outcome_label"] ?? payload["outcomeLabel"]) as
|
|
184
|
+
| string
|
|
185
|
+
| undefined,
|
|
186
|
+
size,
|
|
187
|
+
entryPrice: entryPrice as number | undefined,
|
|
188
|
+
currentPrice: currentPrice as number | undefined,
|
|
189
|
+
unrealizedPnL: unrealizedPnL as number | undefined,
|
|
190
|
+
realizedPnL: floatOrUndefined(
|
|
191
|
+
payload["realized_pnl"] ?? payload["realizedPnl"] ?? payload["realizedPnL"],
|
|
192
|
+
),
|
|
193
|
+
} as unknown as Position;
|
|
194
|
+
|
|
195
|
+
return position;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Map a {@link Position} back to a `PositionV0` JSON object. */
|
|
199
|
+
export function positionToV0(position: Position): Record<string, unknown> {
|
|
200
|
+
const out: Record<string, unknown> = {
|
|
201
|
+
market_id: position.marketId,
|
|
202
|
+
outcome_id: position.outcomeId,
|
|
203
|
+
shares: position.size,
|
|
204
|
+
};
|
|
205
|
+
if (position.outcomeLabel !== undefined && position.outcomeLabel !== "") {
|
|
206
|
+
out["outcome_label"] = position.outcomeLabel;
|
|
207
|
+
}
|
|
208
|
+
if (position.entryPrice !== undefined) out["entry_price"] = position.entryPrice;
|
|
209
|
+
if (position.currentPrice !== undefined) out["current_price"] = position.currentPrice;
|
|
210
|
+
if (position.unrealizedPnL !== undefined) out["unrealized_pnl"] = position.unrealizedPnL;
|
|
211
|
+
if (position.realizedPnL !== undefined) out["realized_pnl"] = position.realizedPnL;
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// Balance mappers
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Map a `BalanceV0` JSON object to {@link Balance}.
|
|
221
|
+
*
|
|
222
|
+
* Hosted-mode semantic: PreFundedEscrow doesn't reserve funds for resting
|
|
223
|
+
* orders, so `available = total` and `locked = 0`. Concurrent limit orders
|
|
224
|
+
* may fail at fill time if cumulative cost exceeds the escrow balance.
|
|
225
|
+
*/
|
|
226
|
+
export function balanceFromV0(payload: Record<string, unknown>): Balance {
|
|
227
|
+
const total = floatOrZero(payload["amount"] ?? payload["total"]);
|
|
228
|
+
const currency = strOrUndefined(payload["currency"]) ?? "USDC";
|
|
229
|
+
return {
|
|
230
|
+
currency,
|
|
231
|
+
total,
|
|
232
|
+
available: total,
|
|
233
|
+
locked: 0,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Map a {@link Balance} back to a `BalanceV0` JSON object. */
|
|
238
|
+
export function balanceToV0(balance: Balance): Record<string, unknown> {
|
|
239
|
+
return {
|
|
240
|
+
currency: balance.currency,
|
|
241
|
+
amount: balance.total,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// Internal helpers
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
function strOrUndefined(value: unknown): string | undefined {
|
|
250
|
+
if (value === null || value === undefined) return undefined;
|
|
251
|
+
if (typeof value === "string") return value || undefined;
|
|
252
|
+
return String(value);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function strOrEmpty(value: unknown): string {
|
|
256
|
+
if (value === null || value === undefined) return "";
|
|
257
|
+
if (typeof value === "string") return value;
|
|
258
|
+
return String(value);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function floatOrUndefined(value: unknown): number | undefined {
|
|
262
|
+
if (value === null || value === undefined) return undefined;
|
|
263
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : undefined;
|
|
264
|
+
if (typeof value === "bigint") return Number(value);
|
|
265
|
+
if (typeof value === "string") {
|
|
266
|
+
if (!value) return undefined;
|
|
267
|
+
const parsed = Number(value);
|
|
268
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
269
|
+
}
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function floatOrZero(value: unknown): number {
|
|
274
|
+
const converted = floatOrUndefined(value);
|
|
275
|
+
return converted !== undefined ? converted : 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Parse an ISO-8601 string (or numeric) timestamp to milliseconds since epoch.
|
|
280
|
+
* Returns `0` if the input is null/undefined/empty/unparseable.
|
|
281
|
+
*/
|
|
282
|
+
function timestampToMs(value: unknown): number {
|
|
283
|
+
if (value === null || value === undefined) return 0;
|
|
284
|
+
if (typeof value === "number") {
|
|
285
|
+
return Number.isFinite(value) ? Math.trunc(value) : 0;
|
|
286
|
+
}
|
|
287
|
+
if (typeof value === "bigint") return Number(value);
|
|
288
|
+
if (typeof value === "string") {
|
|
289
|
+
if (!value) return 0;
|
|
290
|
+
const normalized = value.endsWith("Z")
|
|
291
|
+
? value
|
|
292
|
+
: /[+-]\d{2}:?\d{2}$/.test(value)
|
|
293
|
+
? value
|
|
294
|
+
: value + "Z";
|
|
295
|
+
const ms = Date.parse(normalized);
|
|
296
|
+
return Number.isFinite(ms) ? ms : 0;
|
|
297
|
+
}
|
|
298
|
+
return 0;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Convert milliseconds-since-epoch back to an ISO-8601 string. */
|
|
302
|
+
function msToTimestamp(value: unknown): string | undefined {
|
|
303
|
+
if (value === null || value === undefined) return undefined;
|
|
304
|
+
if (typeof value === "string") return value;
|
|
305
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
306
|
+
return new Date(value).toISOString();
|
|
307
|
+
}
|
|
308
|
+
if (typeof value === "bigint") {
|
|
309
|
+
return new Date(Number(value)).toISOString();
|
|
310
|
+
}
|
|
311
|
+
return undefined;
|
|
312
|
+
}
|