@limitless-exchange/sdk 0.0.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +216 -77
- package/dist/index.d.mts +1103 -1123
- package/dist/index.d.ts +1103 -1123
- package/dist/index.js +747 -1039
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +743 -1035
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -46,16 +46,11 @@ var Side = /* @__PURE__ */ ((Side2) => {
|
|
|
46
46
|
Side2[Side2["SELL"] = 1] = "SELL";
|
|
47
47
|
return Side2;
|
|
48
48
|
})(Side || {});
|
|
49
|
-
var OrderType = /* @__PURE__ */ ((
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return
|
|
49
|
+
var OrderType = /* @__PURE__ */ ((OrderType2) => {
|
|
50
|
+
OrderType2["FOK"] = "FOK";
|
|
51
|
+
OrderType2["GTC"] = "GTC";
|
|
52
|
+
return OrderType2;
|
|
53
53
|
})(OrderType || {});
|
|
54
|
-
var MarketType = /* @__PURE__ */ ((MarketType3) => {
|
|
55
|
-
MarketType3["CLOB"] = "CLOB";
|
|
56
|
-
MarketType3["NEGRISK"] = "NEGRISK";
|
|
57
|
-
return MarketType3;
|
|
58
|
-
})(MarketType || {});
|
|
59
54
|
var SignatureType = /* @__PURE__ */ ((SignatureType2) => {
|
|
60
55
|
SignatureType2[SignatureType2["EOA"] = 0] = "EOA";
|
|
61
56
|
SignatureType2[SignatureType2["POLY_PROXY"] = 1] = "POLY_PROXY";
|
|
@@ -73,250 +68,92 @@ var WebSocketState = /* @__PURE__ */ ((WebSocketState2) => {
|
|
|
73
68
|
return WebSocketState2;
|
|
74
69
|
})(WebSocketState || {});
|
|
75
70
|
|
|
76
|
-
// src/
|
|
77
|
-
|
|
78
|
-
var MessageSigner = class {
|
|
79
|
-
/**
|
|
80
|
-
* Creates a new message signer instance.
|
|
81
|
-
*
|
|
82
|
-
* @param wallet - Ethers wallet instance for signing
|
|
83
|
-
*/
|
|
84
|
-
constructor(wallet) {
|
|
85
|
-
this.wallet = wallet;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Creates authentication headers for API requests.
|
|
89
|
-
*
|
|
90
|
-
* @param signingMessage - Message to sign from the API
|
|
91
|
-
* @returns Promise resolving to signature headers
|
|
92
|
-
*
|
|
93
|
-
* @example
|
|
94
|
-
* ```typescript
|
|
95
|
-
* const signer = new MessageSigner(wallet);
|
|
96
|
-
* const headers = await signer.createAuthHeaders(message);
|
|
97
|
-
* ```
|
|
98
|
-
*/
|
|
99
|
-
async createAuthHeaders(signingMessage) {
|
|
100
|
-
const hexMessage = this.stringToHex(signingMessage);
|
|
101
|
-
const signature = await this.wallet.signMessage(signingMessage);
|
|
102
|
-
const address = this.wallet.address;
|
|
103
|
-
const recoveredAddress = ethers.verifyMessage(signingMessage, signature);
|
|
104
|
-
if (address.toLowerCase() !== recoveredAddress.toLowerCase()) {
|
|
105
|
-
throw new Error("Signature verification failed: address mismatch");
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
"x-account": address,
|
|
109
|
-
"x-signing-message": hexMessage,
|
|
110
|
-
"x-signature": signature
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Signs EIP-712 typed data.
|
|
115
|
-
*
|
|
116
|
-
* @param domain - EIP-712 domain
|
|
117
|
-
* @param types - EIP-712 types
|
|
118
|
-
* @param value - Value to sign
|
|
119
|
-
* @returns Promise resolving to signature string
|
|
120
|
-
*
|
|
121
|
-
* @example
|
|
122
|
-
* ```typescript
|
|
123
|
-
* const signature = await signer.signTypedData(domain, types, order);
|
|
124
|
-
* ```
|
|
125
|
-
*/
|
|
126
|
-
async signTypedData(domain, types, value) {
|
|
127
|
-
return await this.wallet.signTypedData(domain, types, value);
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Gets the wallet address.
|
|
131
|
-
*
|
|
132
|
-
* @returns Ethereum address
|
|
133
|
-
*/
|
|
134
|
-
getAddress() {
|
|
135
|
-
return this.wallet.address;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Converts a string to hex format.
|
|
139
|
-
*
|
|
140
|
-
* @param text - String to convert
|
|
141
|
-
* @returns Hex string with 0x prefix
|
|
142
|
-
* @internal
|
|
143
|
-
*/
|
|
144
|
-
stringToHex(text) {
|
|
145
|
-
return ethers.hexlify(ethers.toUtf8Bytes(text));
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// src/auth/authenticator.ts
|
|
150
|
-
var Authenticator = class {
|
|
71
|
+
// src/types/market-class.ts
|
|
72
|
+
var Market = class _Market {
|
|
151
73
|
/**
|
|
152
|
-
* Creates a
|
|
153
|
-
*
|
|
154
|
-
* @param httpClient - HTTP client for API requests
|
|
155
|
-
* @param signer - Message signer for wallet operations
|
|
156
|
-
* @param logger - Optional logger for debugging and monitoring (default: no logging)
|
|
74
|
+
* Creates a Market instance.
|
|
157
75
|
*
|
|
158
|
-
* @
|
|
159
|
-
*
|
|
160
|
-
* // Without logging (default)
|
|
161
|
-
* const authenticator = new Authenticator(httpClient, signer);
|
|
162
|
-
*
|
|
163
|
-
* // With logging
|
|
164
|
-
* import { ConsoleLogger } from '@limitless-exchange/sdk';
|
|
165
|
-
* const logger = new ConsoleLogger('debug');
|
|
166
|
-
* const authenticator = new Authenticator(httpClient, signer, logger);
|
|
167
|
-
* ```
|
|
76
|
+
* @param data - Market data from API
|
|
77
|
+
* @param httpClient - HTTP client for making requests
|
|
168
78
|
*/
|
|
169
|
-
constructor(
|
|
79
|
+
constructor(data, httpClient) {
|
|
80
|
+
Object.assign(this, data);
|
|
170
81
|
this.httpClient = httpClient;
|
|
171
|
-
|
|
172
|
-
|
|
82
|
+
if (data.markets && Array.isArray(data.markets)) {
|
|
83
|
+
this.markets = data.markets.map((m) => new _Market(m, httpClient));
|
|
84
|
+
}
|
|
173
85
|
}
|
|
174
86
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* @returns Promise resolving to signing message string
|
|
178
|
-
* @throws Error if API request fails
|
|
87
|
+
* Get user's orders for this market.
|
|
179
88
|
*
|
|
180
|
-
* @
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* console.log(message);
|
|
184
|
-
* ```
|
|
185
|
-
*/
|
|
186
|
-
async getSigningMessage() {
|
|
187
|
-
this.logger.debug("Requesting signing message from API");
|
|
188
|
-
const message = await this.httpClient.get("/auth/signing-message");
|
|
189
|
-
this.logger.debug("Received signing message", { length: message.length });
|
|
190
|
-
return message;
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Authenticates with the API and obtains session cookie.
|
|
89
|
+
* @remarks
|
|
90
|
+
* Fetches all orders placed by the authenticated user for this specific market.
|
|
91
|
+
* Uses the http_client from the MarketFetcher that created this Market instance.
|
|
194
92
|
*
|
|
195
|
-
* @
|
|
196
|
-
* @
|
|
197
|
-
* @throws Error if authentication fails or smart wallet is required but not provided
|
|
93
|
+
* @returns Promise resolving to array of user orders
|
|
94
|
+
* @throws Error if market wasn't fetched via MarketFetcher
|
|
198
95
|
*
|
|
199
96
|
* @example
|
|
200
97
|
* ```typescript
|
|
201
|
-
* //
|
|
202
|
-
* const
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
* const result = await authenticator.authenticate({
|
|
206
|
-
* client: 'etherspot',
|
|
207
|
-
* smartWallet: '0x...'
|
|
208
|
-
* });
|
|
98
|
+
* // Clean fluent API
|
|
99
|
+
* const market = await marketFetcher.getMarket("bitcoin-2024");
|
|
100
|
+
* const orders = await market.getUserOrders();
|
|
101
|
+
* console.log(`You have ${orders.length} orders in ${market.title}`);
|
|
209
102
|
* ```
|
|
210
103
|
*/
|
|
211
|
-
async
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
hasSmartWallet: !!options.smartWallet
|
|
216
|
-
});
|
|
217
|
-
if (client === "etherspot" && !options.smartWallet) {
|
|
218
|
-
this.logger.error("Smart wallet address required for ETHERSPOT client");
|
|
219
|
-
throw new Error("Smart wallet address is required for ETHERSPOT client");
|
|
220
|
-
}
|
|
221
|
-
try {
|
|
222
|
-
const signingMessage = await this.getSigningMessage();
|
|
223
|
-
this.logger.debug("Creating signature headers");
|
|
224
|
-
const headers = await this.signer.createAuthHeaders(signingMessage);
|
|
225
|
-
this.logger.debug("Sending authentication request", { client });
|
|
226
|
-
const response = await this.httpClient.postWithResponse(
|
|
227
|
-
"/auth/login",
|
|
228
|
-
{ client, smartWallet: options.smartWallet },
|
|
229
|
-
{
|
|
230
|
-
headers,
|
|
231
|
-
validateStatus: (status) => status < 500
|
|
232
|
-
}
|
|
104
|
+
async getUserOrders() {
|
|
105
|
+
if (!this.httpClient) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
"This Market instance has no httpClient attached. Make sure to fetch the market via MarketFetcher.getMarket() to use this method."
|
|
233
108
|
);
|
|
234
|
-
this.logger.debug("Extracting session cookie from response");
|
|
235
|
-
const cookies = this.httpClient.extractCookies(response);
|
|
236
|
-
const sessionCookie = cookies["limitless_session"];
|
|
237
|
-
if (!sessionCookie) {
|
|
238
|
-
this.logger.error("Session cookie not found in response headers");
|
|
239
|
-
throw new Error("Failed to obtain session cookie from response");
|
|
240
|
-
}
|
|
241
|
-
this.httpClient.setSessionCookie(sessionCookie);
|
|
242
|
-
this.logger.info("Authentication successful", {
|
|
243
|
-
account: response.data.account,
|
|
244
|
-
client: response.data.client
|
|
245
|
-
});
|
|
246
|
-
return {
|
|
247
|
-
sessionCookie,
|
|
248
|
-
profile: response.data
|
|
249
|
-
};
|
|
250
|
-
} catch (error) {
|
|
251
|
-
this.logger.error("Authentication failed", error, {
|
|
252
|
-
client
|
|
253
|
-
});
|
|
254
|
-
throw error;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Verifies the current authentication status.
|
|
259
|
-
*
|
|
260
|
-
* @param sessionCookie - Session cookie to verify
|
|
261
|
-
* @returns Promise resolving to user's Ethereum address
|
|
262
|
-
* @throws Error if session is invalid
|
|
263
|
-
*
|
|
264
|
-
* @example
|
|
265
|
-
* ```typescript
|
|
266
|
-
* const address = await authenticator.verifyAuth(sessionCookie);
|
|
267
|
-
* console.log(`Authenticated as: ${address}`);
|
|
268
|
-
* ```
|
|
269
|
-
*/
|
|
270
|
-
async verifyAuth(sessionCookie) {
|
|
271
|
-
this.logger.debug("Verifying authentication session");
|
|
272
|
-
const originalCookie = this.httpClient["sessionCookie"];
|
|
273
|
-
this.httpClient.setSessionCookie(sessionCookie);
|
|
274
|
-
try {
|
|
275
|
-
const address = await this.httpClient.get("/auth/verify-auth");
|
|
276
|
-
this.logger.info("Session verified", { address });
|
|
277
|
-
return address;
|
|
278
|
-
} catch (error) {
|
|
279
|
-
this.logger.error("Session verification failed", error);
|
|
280
|
-
throw error;
|
|
281
|
-
} finally {
|
|
282
|
-
if (originalCookie) {
|
|
283
|
-
this.httpClient.setSessionCookie(originalCookie);
|
|
284
|
-
} else {
|
|
285
|
-
this.httpClient.clearSessionCookie();
|
|
286
|
-
}
|
|
287
109
|
}
|
|
110
|
+
const response = await this.httpClient.get(`/markets/${this.slug}/user-orders`);
|
|
111
|
+
const orders = Array.isArray(response) ? response : response.orders || [];
|
|
112
|
+
return orders;
|
|
288
113
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
this.httpClient.clearSessionCookie();
|
|
316
|
-
}
|
|
317
|
-
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/api/http.ts
|
|
117
|
+
import axios from "axios";
|
|
118
|
+
import http from "http";
|
|
119
|
+
import https from "https";
|
|
120
|
+
|
|
121
|
+
// src/utils/constants.ts
|
|
122
|
+
var DEFAULT_API_URL = "https://api.limitless.exchange";
|
|
123
|
+
var DEFAULT_WS_URL = "wss://ws.limitless.exchange";
|
|
124
|
+
var DEFAULT_CHAIN_ID = 8453;
|
|
125
|
+
var BASE_SEPOLIA_CHAIN_ID = 84532;
|
|
126
|
+
var SIGNING_MESSAGE_TEMPLATE = "Welcome to Limitless.exchange! Please sign this message to verify your identity.\n\nNonce: {NONCE}";
|
|
127
|
+
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
128
|
+
var CONTRACT_ADDRESSES = {
|
|
129
|
+
// Base mainnet (chainId: 8453)
|
|
130
|
+
8453: {
|
|
131
|
+
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
132
|
+
// Native USDC on Base
|
|
133
|
+
CTF: "0xC9c98965297Bc527861c898329Ee280632B76e18"
|
|
134
|
+
// Conditional Token Framework
|
|
135
|
+
},
|
|
136
|
+
// Base Sepolia testnet (chainId: 84532)
|
|
137
|
+
84532: {
|
|
138
|
+
USDC: "0x...",
|
|
139
|
+
CTF: "0x..."
|
|
318
140
|
}
|
|
319
141
|
};
|
|
142
|
+
function getContractAddress(contractType, chainId = DEFAULT_CHAIN_ID) {
|
|
143
|
+
const addresses = CONTRACT_ADDRESSES[chainId];
|
|
144
|
+
if (!addresses) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`No contract addresses configured for chainId ${chainId}. Supported chains: ${Object.keys(CONTRACT_ADDRESSES).join(", ")}`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const address = addresses[contractType];
|
|
150
|
+
if (!address || address === "0x...") {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Contract address for ${contractType} not available on chainId ${chainId}. Please configure the address in constants.ts or use environment variables.`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return address;
|
|
156
|
+
}
|
|
320
157
|
|
|
321
158
|
// src/api/errors.ts
|
|
322
159
|
var APIError = class _APIError extends Error {
|
|
@@ -362,111 +199,33 @@ var APIError = class _APIError extends Error {
|
|
|
362
199
|
return this.status === 401 || this.status === 403;
|
|
363
200
|
}
|
|
364
201
|
};
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
* @param config - Configuration for authenticated client
|
|
372
|
-
*/
|
|
373
|
-
constructor(config) {
|
|
374
|
-
this.httpClient = config.httpClient;
|
|
375
|
-
this.authenticator = config.authenticator;
|
|
376
|
-
this.client = config.client;
|
|
377
|
-
this.smartWallet = config.smartWallet;
|
|
378
|
-
this.logger = config.logger || new NoOpLogger();
|
|
379
|
-
this.maxRetries = config.maxRetries ?? 1;
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* Executes a function with automatic retry on authentication errors.
|
|
383
|
-
*
|
|
384
|
-
* @param fn - Function to execute with auth retry
|
|
385
|
-
* @returns Promise resolving to the function result
|
|
386
|
-
* @throws Error if max retries exceeded or non-auth error occurs
|
|
387
|
-
*
|
|
388
|
-
* @example
|
|
389
|
-
* ```typescript
|
|
390
|
-
* // Automatic retry on 401/403
|
|
391
|
-
* const positions = await authClient.withRetry(() =>
|
|
392
|
-
* portfolioFetcher.getPositions()
|
|
393
|
-
* );
|
|
394
|
-
*
|
|
395
|
-
* // Works with any async operation
|
|
396
|
-
* const order = await authClient.withRetry(() =>
|
|
397
|
-
* orderClient.createOrder({ ... })
|
|
398
|
-
* );
|
|
399
|
-
* ```
|
|
400
|
-
*/
|
|
401
|
-
async withRetry(fn) {
|
|
402
|
-
let attempts = 0;
|
|
403
|
-
while (attempts <= this.maxRetries) {
|
|
404
|
-
try {
|
|
405
|
-
return await fn();
|
|
406
|
-
} catch (error) {
|
|
407
|
-
if (error instanceof APIError && error.isAuthError() && attempts < this.maxRetries) {
|
|
408
|
-
this.logger.info("Authentication expired, re-authenticating...", {
|
|
409
|
-
attempt: attempts + 1,
|
|
410
|
-
maxRetries: this.maxRetries
|
|
411
|
-
});
|
|
412
|
-
await this.reauthenticate();
|
|
413
|
-
attempts++;
|
|
414
|
-
continue;
|
|
415
|
-
}
|
|
416
|
-
throw error;
|
|
417
|
-
}
|
|
202
|
+
var RateLimitError = class _RateLimitError extends APIError {
|
|
203
|
+
constructor(message = "Rate limit exceeded", status = 429, data = null, url, method) {
|
|
204
|
+
super(message, status, data, url, method);
|
|
205
|
+
this.name = "RateLimitError";
|
|
206
|
+
if (Error.captureStackTrace) {
|
|
207
|
+
Error.captureStackTrace(this, _RateLimitError);
|
|
418
208
|
}
|
|
419
|
-
throw new Error("Unexpected error: exceeded max retries");
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* Re-authenticates with the API.
|
|
423
|
-
*
|
|
424
|
-
* @internal
|
|
425
|
-
*/
|
|
426
|
-
async reauthenticate() {
|
|
427
|
-
this.logger.debug("Re-authenticating with API");
|
|
428
|
-
await this.authenticator.authenticate({
|
|
429
|
-
client: this.client,
|
|
430
|
-
smartWallet: this.smartWallet
|
|
431
|
-
});
|
|
432
|
-
this.logger.info("Re-authentication successful");
|
|
433
209
|
}
|
|
434
210
|
};
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
var DEFAULT_API_URL = "https://api.limitless.exchange";
|
|
443
|
-
var DEFAULT_WS_URL = "wss://ws.limitless.exchange";
|
|
444
|
-
var DEFAULT_CHAIN_ID = 8453;
|
|
445
|
-
var BASE_SEPOLIA_CHAIN_ID = 84532;
|
|
446
|
-
var SIGNING_MESSAGE_TEMPLATE = "Welcome to Limitless.exchange! Please sign this message to verify your identity.\n\nNonce: {NONCE}";
|
|
447
|
-
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
448
|
-
var CONTRACT_ADDRESSES = {
|
|
449
|
-
// Base mainnet (chainId: 8453)
|
|
450
|
-
8453: {
|
|
451
|
-
CLOB: "0xa4409D988CA2218d956BeEFD3874100F444f0DC3",
|
|
452
|
-
NEGRISK: "0x5a38afc17F7E97ad8d6C547ddb837E40B4aEDfC6"
|
|
453
|
-
},
|
|
454
|
-
// Base Sepolia testnet (chainId: 84532)
|
|
455
|
-
84532: {
|
|
456
|
-
CLOB: "0x...",
|
|
457
|
-
// Add testnet addresses when available
|
|
458
|
-
NEGRISK: "0x..."
|
|
211
|
+
var AuthenticationError = class _AuthenticationError extends APIError {
|
|
212
|
+
constructor(message = "Authentication failed", status = 401, data = null, url, method) {
|
|
213
|
+
super(message, status, data, url, method);
|
|
214
|
+
this.name = "AuthenticationError";
|
|
215
|
+
if (Error.captureStackTrace) {
|
|
216
|
+
Error.captureStackTrace(this, _AuthenticationError);
|
|
217
|
+
}
|
|
459
218
|
}
|
|
460
219
|
};
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
220
|
+
var ValidationError = class _ValidationError extends APIError {
|
|
221
|
+
constructor(message, status = 400, data = null, url, method) {
|
|
222
|
+
super(message, status, data, url, method);
|
|
223
|
+
this.name = "ValidationError";
|
|
224
|
+
if (Error.captureStackTrace) {
|
|
225
|
+
Error.captureStackTrace(this, _ValidationError);
|
|
226
|
+
}
|
|
467
227
|
}
|
|
468
|
-
|
|
469
|
-
}
|
|
228
|
+
};
|
|
470
229
|
|
|
471
230
|
// src/api/http.ts
|
|
472
231
|
var HttpClient = class {
|
|
@@ -476,8 +235,13 @@ var HttpClient = class {
|
|
|
476
235
|
* @param config - Configuration options for the HTTP client
|
|
477
236
|
*/
|
|
478
237
|
constructor(config = {}) {
|
|
479
|
-
this.
|
|
238
|
+
this.apiKey = config.apiKey || process.env.LIMITLESS_API_KEY;
|
|
480
239
|
this.logger = config.logger || new NoOpLogger();
|
|
240
|
+
if (!this.apiKey) {
|
|
241
|
+
this.logger.warn(
|
|
242
|
+
"API key not set. Authenticated endpoints will fail. Set LIMITLESS_API_KEY environment variable or pass apiKey parameter."
|
|
243
|
+
);
|
|
244
|
+
}
|
|
481
245
|
const keepAlive = config.keepAlive !== false;
|
|
482
246
|
const maxSockets = config.maxSockets || 50;
|
|
483
247
|
const maxFreeSockets = config.maxFreeSockets || 10;
|
|
@@ -520,13 +284,17 @@ var HttpClient = class {
|
|
|
520
284
|
setupInterceptors() {
|
|
521
285
|
this.client.interceptors.request.use(
|
|
522
286
|
(config) => {
|
|
523
|
-
if (this.
|
|
524
|
-
config.headers["
|
|
287
|
+
if (this.apiKey) {
|
|
288
|
+
config.headers["X-API-Key"] = this.apiKey;
|
|
525
289
|
}
|
|
526
290
|
const fullUrl = `${config.baseURL || ""}${config.url || ""}`;
|
|
527
291
|
const method = config.method?.toUpperCase() || "GET";
|
|
292
|
+
const logHeaders = { ...config.headers };
|
|
293
|
+
if (logHeaders["X-API-Key"]) {
|
|
294
|
+
logHeaders["X-API-Key"] = "***";
|
|
295
|
+
}
|
|
528
296
|
this.logger.debug(`\u2192 ${method} ${fullUrl}`, {
|
|
529
|
-
headers:
|
|
297
|
+
headers: logHeaders,
|
|
530
298
|
body: config.data
|
|
531
299
|
});
|
|
532
300
|
return config;
|
|
@@ -567,7 +335,15 @@ var HttpClient = class {
|
|
|
567
335
|
message = String(data);
|
|
568
336
|
}
|
|
569
337
|
}
|
|
570
|
-
|
|
338
|
+
if (status === 429) {
|
|
339
|
+
throw new RateLimitError(message, status, data, url, method);
|
|
340
|
+
} else if (status === 401 || status === 403) {
|
|
341
|
+
throw new AuthenticationError(message, status, data, url, method);
|
|
342
|
+
} else if (status === 400) {
|
|
343
|
+
throw new ValidationError(message, status, data, url, method);
|
|
344
|
+
} else {
|
|
345
|
+
throw new APIError(message, status, data, url, method);
|
|
346
|
+
}
|
|
571
347
|
} else if (error.request) {
|
|
572
348
|
throw new Error("No response received from API");
|
|
573
349
|
} else {
|
|
@@ -577,18 +353,18 @@ var HttpClient = class {
|
|
|
577
353
|
);
|
|
578
354
|
}
|
|
579
355
|
/**
|
|
580
|
-
* Sets the
|
|
356
|
+
* Sets the API key for authenticated requests.
|
|
581
357
|
*
|
|
582
|
-
* @param
|
|
358
|
+
* @param apiKey - API key value
|
|
583
359
|
*/
|
|
584
|
-
|
|
585
|
-
this.
|
|
360
|
+
setApiKey(apiKey) {
|
|
361
|
+
this.apiKey = apiKey;
|
|
586
362
|
}
|
|
587
363
|
/**
|
|
588
|
-
* Clears the
|
|
364
|
+
* Clears the API key.
|
|
589
365
|
*/
|
|
590
|
-
|
|
591
|
-
this.
|
|
366
|
+
clearApiKey() {
|
|
367
|
+
this.apiKey = void 0;
|
|
592
368
|
}
|
|
593
369
|
/**
|
|
594
370
|
* Performs a GET request.
|
|
@@ -613,19 +389,6 @@ var HttpClient = class {
|
|
|
613
389
|
const response = await this.client.post(url, data, config);
|
|
614
390
|
return response.data;
|
|
615
391
|
}
|
|
616
|
-
/**
|
|
617
|
-
* Performs a POST request and returns the full response object.
|
|
618
|
-
* Useful when you need access to response headers (e.g., for cookie extraction).
|
|
619
|
-
*
|
|
620
|
-
* @param url - Request URL
|
|
621
|
-
* @param data - Request body data
|
|
622
|
-
* @param config - Additional request configuration
|
|
623
|
-
* @returns Promise resolving to the full AxiosResponse object
|
|
624
|
-
* @internal
|
|
625
|
-
*/
|
|
626
|
-
async postWithResponse(url, data, config) {
|
|
627
|
-
return await this.client.post(url, data, config);
|
|
628
|
-
}
|
|
629
392
|
/**
|
|
630
393
|
* Performs a DELETE request.
|
|
631
394
|
*
|
|
@@ -648,26 +411,6 @@ var HttpClient = class {
|
|
|
648
411
|
const response = await this.client.delete(url, deleteConfig);
|
|
649
412
|
return response.data;
|
|
650
413
|
}
|
|
651
|
-
/**
|
|
652
|
-
* Extracts cookies from response headers.
|
|
653
|
-
*
|
|
654
|
-
* @param response - Axios response object
|
|
655
|
-
* @returns Object containing parsed cookies
|
|
656
|
-
* @internal
|
|
657
|
-
*/
|
|
658
|
-
extractCookies(response) {
|
|
659
|
-
const setCookie = response.headers["set-cookie"];
|
|
660
|
-
if (!setCookie) return {};
|
|
661
|
-
const cookies = {};
|
|
662
|
-
const cookieStrings = Array.isArray(setCookie) ? setCookie : [setCookie];
|
|
663
|
-
for (const cookieString of cookieStrings) {
|
|
664
|
-
const parts = cookieString.split(";")[0].split("=");
|
|
665
|
-
if (parts.length === 2) {
|
|
666
|
-
cookies[parts[0].trim()] = parts[1].trim();
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
return cookies;
|
|
670
|
-
}
|
|
671
414
|
};
|
|
672
415
|
|
|
673
416
|
// src/api/retry.ts
|
|
@@ -852,7 +595,7 @@ var RetryableClient = class {
|
|
|
852
595
|
};
|
|
853
596
|
|
|
854
597
|
// src/orders/builder.ts
|
|
855
|
-
import { ethers
|
|
598
|
+
import { ethers } from "ethers";
|
|
856
599
|
var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
|
|
857
600
|
var DEFAULT_PRICE_TICK = 1e-3;
|
|
858
601
|
var OrderBuilder = class {
|
|
@@ -891,8 +634,7 @@ var OrderBuilder = class {
|
|
|
891
634
|
* const fokOrder = builder.buildOrder({
|
|
892
635
|
* tokenId: '123456',
|
|
893
636
|
* makerAmount: 50, // 50 USDC to spend
|
|
894
|
-
* side: Side.BUY
|
|
895
|
-
* marketType: MarketType.CLOB
|
|
637
|
+
* side: Side.BUY
|
|
896
638
|
* });
|
|
897
639
|
*
|
|
898
640
|
* // GTC order (price + size)
|
|
@@ -900,8 +642,7 @@ var OrderBuilder = class {
|
|
|
900
642
|
* tokenId: '123456',
|
|
901
643
|
* price: 0.38,
|
|
902
644
|
* size: 22.123, // Will be rounded to tick-aligned: 22.123 shares
|
|
903
|
-
* side: Side.BUY
|
|
904
|
-
* marketType: MarketType.CLOB
|
|
645
|
+
* side: Side.BUY
|
|
905
646
|
* });
|
|
906
647
|
* ```
|
|
907
648
|
*/
|
|
@@ -936,7 +677,7 @@ var OrderBuilder = class {
|
|
|
936
677
|
* @internal
|
|
937
678
|
*/
|
|
938
679
|
isFOKOrder(args) {
|
|
939
|
-
return "
|
|
680
|
+
return "makerAmount" in args;
|
|
940
681
|
}
|
|
941
682
|
/**
|
|
942
683
|
* Generates a unique salt using timestamp + nano-offset pattern.
|
|
@@ -1090,7 +831,7 @@ var OrderBuilder = class {
|
|
|
1090
831
|
}
|
|
1091
832
|
}
|
|
1092
833
|
const amountFormatted = makerAmount.toFixed(DECIMALS);
|
|
1093
|
-
const amountScaled =
|
|
834
|
+
const amountScaled = ethers.parseUnits(amountFormatted, DECIMALS);
|
|
1094
835
|
const collateralAmount = Number(amountScaled);
|
|
1095
836
|
return {
|
|
1096
837
|
makerAmount: collateralAmount,
|
|
@@ -1110,7 +851,7 @@ var OrderBuilder = class {
|
|
|
1110
851
|
if (!args.tokenId || args.tokenId === "0") {
|
|
1111
852
|
throw new Error("Invalid tokenId: tokenId is required.");
|
|
1112
853
|
}
|
|
1113
|
-
if (args.taker && !
|
|
854
|
+
if (args.taker && !ethers.isAddress(args.taker)) {
|
|
1114
855
|
throw new Error(`Invalid taker address: ${args.taker}`);
|
|
1115
856
|
}
|
|
1116
857
|
if (this.isFOKOrder(args)) {
|
|
@@ -1121,7 +862,7 @@ var OrderBuilder = class {
|
|
|
1121
862
|
throw new Error(`Invalid makerAmount: ${args.makerAmount}. Maker amount must be positive.`);
|
|
1122
863
|
}
|
|
1123
864
|
} else {
|
|
1124
|
-
if (args.price < 0 || args.price > 1) {
|
|
865
|
+
if (args.price == null || args.price < 0 || args.price > 1) {
|
|
1125
866
|
throw new Error(`Invalid price: ${args.price}. Price must be between 0 and 1.`);
|
|
1126
867
|
}
|
|
1127
868
|
if (args.size <= 0) {
|
|
@@ -1176,8 +917,7 @@ var OrderSigner = class {
|
|
|
1176
917
|
* ```typescript
|
|
1177
918
|
* const signature = await signer.signOrder(unsignedOrder, {
|
|
1178
919
|
* chainId: 8453,
|
|
1179
|
-
* contractAddress: '0x...'
|
|
1180
|
-
* marketType: MarketType.CLOB
|
|
920
|
+
* contractAddress: '0x...'
|
|
1181
921
|
* });
|
|
1182
922
|
* ```
|
|
1183
923
|
*/
|
|
@@ -1185,7 +925,7 @@ var OrderSigner = class {
|
|
|
1185
925
|
this.logger.debug("Signing order with EIP-712", {
|
|
1186
926
|
tokenId: order.tokenId,
|
|
1187
927
|
side: order.side,
|
|
1188
|
-
|
|
928
|
+
verifyingContract: config.contractAddress
|
|
1189
929
|
});
|
|
1190
930
|
const walletAddress = await this.wallet.getAddress();
|
|
1191
931
|
if (walletAddress.toLowerCase() !== order.signer.toLowerCase()) {
|
|
@@ -1211,11 +951,11 @@ var OrderSigner = class {
|
|
|
1211
951
|
signatureType: order.signatureType
|
|
1212
952
|
};
|
|
1213
953
|
this.logger.debug("EIP-712 Order Value", orderValue);
|
|
1214
|
-
|
|
954
|
+
this.logger.debug("Full signing payload", {
|
|
1215
955
|
domain,
|
|
1216
956
|
types: this.getTypes(),
|
|
1217
957
|
value: orderValue
|
|
1218
|
-
}
|
|
958
|
+
});
|
|
1219
959
|
try {
|
|
1220
960
|
const signature = await this.wallet.signTypedData(domain, types, orderValue);
|
|
1221
961
|
this.logger.info("Successfully generated EIP-712 signature", {
|
|
@@ -1275,11 +1015,11 @@ var OrderSigner = class {
|
|
|
1275
1015
|
};
|
|
1276
1016
|
|
|
1277
1017
|
// src/orders/validator.ts
|
|
1278
|
-
import { ethers as
|
|
1279
|
-
var
|
|
1018
|
+
import { ethers as ethers2 } from "ethers";
|
|
1019
|
+
var OrderValidationError = class extends Error {
|
|
1280
1020
|
constructor(message) {
|
|
1281
1021
|
super(message);
|
|
1282
|
-
this.name = "
|
|
1022
|
+
this.name = "OrderValidationError";
|
|
1283
1023
|
}
|
|
1284
1024
|
};
|
|
1285
1025
|
function isFOKOrder(args) {
|
|
@@ -1287,418 +1027,149 @@ function isFOKOrder(args) {
|
|
|
1287
1027
|
}
|
|
1288
1028
|
function validateOrderArgs(args) {
|
|
1289
1029
|
if (!args.tokenId) {
|
|
1290
|
-
throw new
|
|
1030
|
+
throw new OrderValidationError("TokenId is required");
|
|
1291
1031
|
}
|
|
1292
1032
|
if (args.tokenId === "0") {
|
|
1293
|
-
throw new
|
|
1033
|
+
throw new OrderValidationError("TokenId cannot be zero");
|
|
1294
1034
|
}
|
|
1295
1035
|
if (!/^\d+$/.test(args.tokenId)) {
|
|
1296
|
-
throw new
|
|
1036
|
+
throw new OrderValidationError(`Invalid tokenId format: ${args.tokenId}`);
|
|
1297
1037
|
}
|
|
1298
|
-
if (args.taker && !
|
|
1299
|
-
throw new
|
|
1038
|
+
if (args.taker && !ethers2.isAddress(args.taker)) {
|
|
1039
|
+
throw new OrderValidationError(`Invalid taker address: ${args.taker}`);
|
|
1300
1040
|
}
|
|
1301
1041
|
if (args.expiration !== void 0) {
|
|
1302
1042
|
if (!/^\d+$/.test(args.expiration)) {
|
|
1303
|
-
throw new
|
|
1043
|
+
throw new OrderValidationError(`Invalid expiration format: ${args.expiration}`);
|
|
1304
1044
|
}
|
|
1305
1045
|
}
|
|
1306
1046
|
if (args.nonce !== void 0) {
|
|
1307
1047
|
if (!Number.isInteger(args.nonce) || args.nonce < 0) {
|
|
1308
|
-
throw new
|
|
1048
|
+
throw new OrderValidationError(`Invalid nonce: ${args.nonce}`);
|
|
1309
1049
|
}
|
|
1310
1050
|
}
|
|
1311
1051
|
if (isFOKOrder(args)) {
|
|
1312
1052
|
if (typeof args.makerAmount !== "number" || isNaN(args.makerAmount)) {
|
|
1313
|
-
throw new
|
|
1053
|
+
throw new OrderValidationError("Amount must be a valid number");
|
|
1314
1054
|
}
|
|
1315
1055
|
if (args.makerAmount <= 0) {
|
|
1316
|
-
throw new
|
|
1056
|
+
throw new OrderValidationError(`Amount must be positive, got: ${args.makerAmount}`);
|
|
1317
1057
|
}
|
|
1318
1058
|
const amountStr = args.makerAmount.toString();
|
|
1319
1059
|
const decimalIndex = amountStr.indexOf(".");
|
|
1320
1060
|
if (decimalIndex !== -1) {
|
|
1321
1061
|
const decimalPlaces = amountStr.length - decimalIndex - 1;
|
|
1322
1062
|
if (decimalPlaces > 2) {
|
|
1323
|
-
throw new
|
|
1063
|
+
throw new OrderValidationError(
|
|
1324
1064
|
`Amount must have max 2 decimal places, got: ${args.makerAmount} (${decimalPlaces} decimals)`
|
|
1325
1065
|
);
|
|
1326
1066
|
}
|
|
1327
1067
|
}
|
|
1328
1068
|
} else {
|
|
1329
1069
|
if (typeof args.price !== "number" || isNaN(args.price)) {
|
|
1330
|
-
throw new
|
|
1070
|
+
throw new OrderValidationError("Price must be a valid number");
|
|
1331
1071
|
}
|
|
1332
1072
|
if (args.price < 0 || args.price > 1) {
|
|
1333
|
-
throw new
|
|
1073
|
+
throw new OrderValidationError(`Price must be between 0 and 1, got: ${args.price}`);
|
|
1334
1074
|
}
|
|
1335
1075
|
if (typeof args.size !== "number" || isNaN(args.size)) {
|
|
1336
|
-
throw new
|
|
1076
|
+
throw new OrderValidationError("Size must be a valid number");
|
|
1337
1077
|
}
|
|
1338
1078
|
if (args.size <= 0) {
|
|
1339
|
-
throw new
|
|
1079
|
+
throw new OrderValidationError(`Size must be positive, got: ${args.size}`);
|
|
1340
1080
|
}
|
|
1341
1081
|
}
|
|
1342
1082
|
}
|
|
1343
1083
|
function validateUnsignedOrder(order) {
|
|
1344
|
-
if (!
|
|
1345
|
-
throw new
|
|
1084
|
+
if (!ethers2.isAddress(order.maker)) {
|
|
1085
|
+
throw new OrderValidationError(`Invalid maker address: ${order.maker}`);
|
|
1346
1086
|
}
|
|
1347
|
-
if (!
|
|
1348
|
-
throw new
|
|
1087
|
+
if (!ethers2.isAddress(order.signer)) {
|
|
1088
|
+
throw new OrderValidationError(`Invalid signer address: ${order.signer}`);
|
|
1349
1089
|
}
|
|
1350
|
-
if (!
|
|
1351
|
-
throw new
|
|
1090
|
+
if (!ethers2.isAddress(order.taker)) {
|
|
1091
|
+
throw new OrderValidationError(`Invalid taker address: ${order.taker}`);
|
|
1352
1092
|
}
|
|
1353
1093
|
if (!order.makerAmount || order.makerAmount === 0) {
|
|
1354
|
-
throw new
|
|
1094
|
+
throw new OrderValidationError("MakerAmount must be greater than zero");
|
|
1355
1095
|
}
|
|
1356
1096
|
if (!order.takerAmount || order.takerAmount === 0) {
|
|
1357
|
-
throw new
|
|
1097
|
+
throw new OrderValidationError("TakerAmount must be greater than zero");
|
|
1358
1098
|
}
|
|
1359
1099
|
if (typeof order.makerAmount !== "number" || order.makerAmount <= 0) {
|
|
1360
|
-
throw new
|
|
1100
|
+
throw new OrderValidationError(`Invalid makerAmount: ${order.makerAmount}`);
|
|
1361
1101
|
}
|
|
1362
1102
|
if (typeof order.takerAmount !== "number" || order.takerAmount <= 0) {
|
|
1363
|
-
throw new
|
|
1103
|
+
throw new OrderValidationError(`Invalid takerAmount: ${order.takerAmount}`);
|
|
1364
1104
|
}
|
|
1365
1105
|
if (!/^\d+$/.test(order.tokenId)) {
|
|
1366
|
-
throw new
|
|
1106
|
+
throw new OrderValidationError(`Invalid tokenId format: ${order.tokenId}`);
|
|
1367
1107
|
}
|
|
1368
1108
|
if (!/^\d+$/.test(order.expiration)) {
|
|
1369
|
-
throw new
|
|
1109
|
+
throw new OrderValidationError(`Invalid expiration format: ${order.expiration}`);
|
|
1370
1110
|
}
|
|
1371
1111
|
if (!Number.isInteger(order.salt) || order.salt <= 0) {
|
|
1372
|
-
throw new
|
|
1112
|
+
throw new OrderValidationError(`Invalid salt: ${order.salt}`);
|
|
1373
1113
|
}
|
|
1374
1114
|
if (!Number.isInteger(order.nonce) || order.nonce < 0) {
|
|
1375
|
-
throw new
|
|
1115
|
+
throw new OrderValidationError(`Invalid nonce: ${order.nonce}`);
|
|
1376
1116
|
}
|
|
1377
1117
|
if (!Number.isInteger(order.feeRateBps) || order.feeRateBps < 0) {
|
|
1378
|
-
throw new
|
|
1118
|
+
throw new OrderValidationError(`Invalid feeRateBps: ${order.feeRateBps}`);
|
|
1379
1119
|
}
|
|
1380
1120
|
if (order.side !== 0 && order.side !== 1) {
|
|
1381
|
-
throw new
|
|
1121
|
+
throw new OrderValidationError(`Invalid side: ${order.side}. Must be 0 (BUY) or 1 (SELL)`);
|
|
1382
1122
|
}
|
|
1383
1123
|
if (!Number.isInteger(order.signatureType) || order.signatureType < 0) {
|
|
1384
|
-
throw new
|
|
1124
|
+
throw new OrderValidationError(`Invalid signatureType: ${order.signatureType}`);
|
|
1385
1125
|
}
|
|
1386
1126
|
if (order.price !== void 0) {
|
|
1387
1127
|
if (typeof order.price !== "number" || isNaN(order.price)) {
|
|
1388
|
-
throw new
|
|
1128
|
+
throw new OrderValidationError("Price must be a valid number");
|
|
1389
1129
|
}
|
|
1390
1130
|
if (order.price < 0 || order.price > 1) {
|
|
1391
|
-
throw new
|
|
1131
|
+
throw new OrderValidationError(`Price must be between 0 and 1, got: ${order.price}`);
|
|
1392
1132
|
}
|
|
1393
1133
|
}
|
|
1394
1134
|
}
|
|
1395
1135
|
function validateSignedOrder(order) {
|
|
1396
1136
|
validateUnsignedOrder(order);
|
|
1397
1137
|
if (!order.signature) {
|
|
1398
|
-
throw new
|
|
1138
|
+
throw new OrderValidationError("Signature is required");
|
|
1399
1139
|
}
|
|
1400
1140
|
if (!order.signature.startsWith("0x")) {
|
|
1401
|
-
throw new
|
|
1141
|
+
throw new OrderValidationError("Signature must start with 0x");
|
|
1402
1142
|
}
|
|
1403
1143
|
if (order.signature.length !== 132) {
|
|
1404
|
-
throw new
|
|
1144
|
+
throw new OrderValidationError(
|
|
1405
1145
|
`Invalid signature length: ${order.signature.length}. Expected 132 characters.`
|
|
1406
1146
|
);
|
|
1407
1147
|
}
|
|
1408
1148
|
if (!/^0x[0-9a-fA-F]{130}$/.test(order.signature)) {
|
|
1409
|
-
throw new
|
|
1149
|
+
throw new OrderValidationError("Signature must be valid hex string");
|
|
1410
1150
|
}
|
|
1411
1151
|
}
|
|
1412
1152
|
|
|
1413
|
-
// src/
|
|
1414
|
-
var
|
|
1153
|
+
// src/markets/fetcher.ts
|
|
1154
|
+
var MarketFetcher = class {
|
|
1415
1155
|
/**
|
|
1416
|
-
* Creates a new
|
|
1156
|
+
* Creates a new market fetcher instance.
|
|
1417
1157
|
*
|
|
1418
|
-
* @param
|
|
1158
|
+
* @param httpClient - HTTP client for API requests
|
|
1159
|
+
* @param logger - Optional logger for debugging (default: no logging)
|
|
1419
1160
|
*
|
|
1420
|
-
* @
|
|
1161
|
+
* @example
|
|
1162
|
+
* ```typescript
|
|
1163
|
+
* const fetcher = new MarketFetcher(httpClient);
|
|
1164
|
+
* ```
|
|
1421
1165
|
*/
|
|
1422
|
-
constructor(
|
|
1423
|
-
this.httpClient =
|
|
1424
|
-
this.logger =
|
|
1425
|
-
this.
|
|
1426
|
-
const feeRateBps = config.userData.feeRateBps;
|
|
1427
|
-
this.orderBuilder = new OrderBuilder(config.wallet.address, feeRateBps, 1e-3);
|
|
1428
|
-
this.orderSigner = new OrderSigner(config.wallet, this.logger);
|
|
1429
|
-
if (config.signingConfig) {
|
|
1430
|
-
this.signingConfig = config.signingConfig;
|
|
1431
|
-
} else if (config.marketType) {
|
|
1432
|
-
const chainId = parseInt(process.env.CHAIN_ID || "8453");
|
|
1433
|
-
const contractAddress = config.marketType === "NEGRISK" /* NEGRISK */ ? process.env.NEGRISK_CONTRACT_ADDRESS || getContractAddress("NEGRISK", chainId) : process.env.CLOB_CONTRACT_ADDRESS || getContractAddress("CLOB", chainId);
|
|
1434
|
-
this.signingConfig = {
|
|
1435
|
-
chainId,
|
|
1436
|
-
contractAddress,
|
|
1437
|
-
marketType: config.marketType
|
|
1438
|
-
};
|
|
1439
|
-
this.logger.info("Auto-configured signing", {
|
|
1440
|
-
chainId,
|
|
1441
|
-
contractAddress,
|
|
1442
|
-
marketType: config.marketType
|
|
1443
|
-
});
|
|
1444
|
-
} else {
|
|
1445
|
-
const chainId = parseInt(process.env.CHAIN_ID || "8453");
|
|
1446
|
-
const contractAddress = process.env.CLOB_CONTRACT_ADDRESS || getContractAddress("CLOB", chainId);
|
|
1447
|
-
this.signingConfig = {
|
|
1448
|
-
chainId,
|
|
1449
|
-
contractAddress,
|
|
1450
|
-
marketType: "CLOB" /* CLOB */
|
|
1451
|
-
};
|
|
1452
|
-
this.logger.debug("Using default CLOB configuration", {
|
|
1453
|
-
chainId,
|
|
1454
|
-
contractAddress
|
|
1455
|
-
});
|
|
1456
|
-
}
|
|
1166
|
+
constructor(httpClient, logger) {
|
|
1167
|
+
this.httpClient = httpClient;
|
|
1168
|
+
this.logger = logger || new NoOpLogger();
|
|
1169
|
+
this.venueCache = /* @__PURE__ */ new Map();
|
|
1457
1170
|
}
|
|
1458
1171
|
/**
|
|
1459
|
-
*
|
|
1460
|
-
*
|
|
1461
|
-
* @remarks
|
|
1462
|
-
* This method handles the complete order creation flow:
|
|
1463
|
-
* 1. Build unsigned order
|
|
1464
|
-
* 2. Sign with EIP-712
|
|
1465
|
-
* 3. Submit to API
|
|
1466
|
-
*
|
|
1467
|
-
* @param params - Order parameters
|
|
1468
|
-
* @returns Promise resolving to order response
|
|
1469
|
-
*
|
|
1470
|
-
* @throws Error if order creation fails
|
|
1471
|
-
*
|
|
1472
|
-
* @example
|
|
1473
|
-
* ```typescript
|
|
1474
|
-
* const order = await orderClient.createOrder({
|
|
1475
|
-
* tokenId: '123456',
|
|
1476
|
-
* price: 0.65,
|
|
1477
|
-
* size: 100,
|
|
1478
|
-
* side: Side.BUY,
|
|
1479
|
-
* orderType: OrderType.GTC,
|
|
1480
|
-
* marketSlug: 'market-slug'
|
|
1481
|
-
* });
|
|
1482
|
-
*
|
|
1483
|
-
* console.log(`Order created: ${order.order.id}`);
|
|
1484
|
-
* ```
|
|
1485
|
-
*/
|
|
1486
|
-
async createOrder(params) {
|
|
1487
|
-
this.logger.info("Creating order", {
|
|
1488
|
-
side: params.side,
|
|
1489
|
-
orderType: params.orderType,
|
|
1490
|
-
marketSlug: params.marketSlug
|
|
1491
|
-
});
|
|
1492
|
-
const unsignedOrder = this.orderBuilder.buildOrder(params);
|
|
1493
|
-
this.logger.debug("Built unsigned order", {
|
|
1494
|
-
salt: unsignedOrder.salt,
|
|
1495
|
-
makerAmount: unsignedOrder.makerAmount,
|
|
1496
|
-
takerAmount: unsignedOrder.takerAmount
|
|
1497
|
-
});
|
|
1498
|
-
const signature = await this.orderSigner.signOrder(
|
|
1499
|
-
unsignedOrder,
|
|
1500
|
-
this.signingConfig
|
|
1501
|
-
);
|
|
1502
|
-
const payload = {
|
|
1503
|
-
order: {
|
|
1504
|
-
...unsignedOrder,
|
|
1505
|
-
signature
|
|
1506
|
-
},
|
|
1507
|
-
orderType: params.orderType,
|
|
1508
|
-
marketSlug: params.marketSlug,
|
|
1509
|
-
ownerId: this.ownerId
|
|
1510
|
-
};
|
|
1511
|
-
this.logger.debug("Submitting order to API");
|
|
1512
|
-
console.log("[OrderClient] Full API request payload:", JSON.stringify(payload, null, 2));
|
|
1513
|
-
const apiResponse = await this.httpClient.post(
|
|
1514
|
-
"/orders",
|
|
1515
|
-
payload
|
|
1516
|
-
);
|
|
1517
|
-
this.logger.info("Order created successfully", {
|
|
1518
|
-
orderId: apiResponse.order.id
|
|
1519
|
-
});
|
|
1520
|
-
return this.transformOrderResponse(apiResponse);
|
|
1521
|
-
}
|
|
1522
|
-
/**
|
|
1523
|
-
* Transforms raw API response to clean OrderResponse DTO.
|
|
1524
|
-
*
|
|
1525
|
-
* @param apiResponse - Raw API response with nested objects
|
|
1526
|
-
* @returns Clean OrderResponse with only essential fields
|
|
1527
|
-
*
|
|
1528
|
-
* @internal
|
|
1529
|
-
*/
|
|
1530
|
-
transformOrderResponse(apiResponse) {
|
|
1531
|
-
const cleanOrder = {
|
|
1532
|
-
order: {
|
|
1533
|
-
id: apiResponse.order.id,
|
|
1534
|
-
createdAt: apiResponse.order.createdAt,
|
|
1535
|
-
makerAmount: apiResponse.order.makerAmount,
|
|
1536
|
-
takerAmount: apiResponse.order.takerAmount,
|
|
1537
|
-
expiration: apiResponse.order.expiration,
|
|
1538
|
-
signatureType: apiResponse.order.signatureType,
|
|
1539
|
-
salt: apiResponse.order.salt,
|
|
1540
|
-
maker: apiResponse.order.maker,
|
|
1541
|
-
signer: apiResponse.order.signer,
|
|
1542
|
-
taker: apiResponse.order.taker,
|
|
1543
|
-
tokenId: apiResponse.order.tokenId,
|
|
1544
|
-
side: apiResponse.order.side,
|
|
1545
|
-
feeRateBps: apiResponse.order.feeRateBps,
|
|
1546
|
-
nonce: apiResponse.order.nonce,
|
|
1547
|
-
signature: apiResponse.order.signature,
|
|
1548
|
-
orderType: apiResponse.order.orderType,
|
|
1549
|
-
price: apiResponse.order.price,
|
|
1550
|
-
marketId: apiResponse.order.marketId
|
|
1551
|
-
}
|
|
1552
|
-
};
|
|
1553
|
-
if (apiResponse.makerMatches && apiResponse.makerMatches.length > 0) {
|
|
1554
|
-
cleanOrder.makerMatches = apiResponse.makerMatches.map((match) => ({
|
|
1555
|
-
id: match.id,
|
|
1556
|
-
createdAt: match.createdAt,
|
|
1557
|
-
matchedSize: match.matchedSize,
|
|
1558
|
-
orderId: match.orderId
|
|
1559
|
-
}));
|
|
1560
|
-
}
|
|
1561
|
-
return cleanOrder;
|
|
1562
|
-
}
|
|
1563
|
-
/**
|
|
1564
|
-
* Cancels an existing order by ID.
|
|
1565
|
-
*
|
|
1566
|
-
* @param orderId - Order ID to cancel
|
|
1567
|
-
* @returns Promise resolving to cancellation message
|
|
1568
|
-
*
|
|
1569
|
-
* @throws Error if cancellation fails
|
|
1570
|
-
*
|
|
1571
|
-
* @example
|
|
1572
|
-
* ```typescript
|
|
1573
|
-
* const result = await orderClient.cancel('order-id-123');
|
|
1574
|
-
* console.log(result.message); // "Order canceled successfully"
|
|
1575
|
-
* ```
|
|
1576
|
-
*/
|
|
1577
|
-
async cancel(orderId) {
|
|
1578
|
-
this.logger.info("Cancelling order", { orderId });
|
|
1579
|
-
const response = await this.httpClient.delete(
|
|
1580
|
-
`/orders/${orderId}`
|
|
1581
|
-
);
|
|
1582
|
-
this.logger.info("Order cancellation response", {
|
|
1583
|
-
orderId,
|
|
1584
|
-
message: response.message
|
|
1585
|
-
});
|
|
1586
|
-
return response;
|
|
1587
|
-
}
|
|
1588
|
-
/**
|
|
1589
|
-
* Cancels all orders for a specific market.
|
|
1590
|
-
*
|
|
1591
|
-
* @param marketSlug - Market slug to cancel all orders for
|
|
1592
|
-
* @returns Promise resolving to cancellation message
|
|
1593
|
-
*
|
|
1594
|
-
* @throws Error if cancellation fails
|
|
1595
|
-
*
|
|
1596
|
-
* @example
|
|
1597
|
-
* ```typescript
|
|
1598
|
-
* const result = await orderClient.cancelAll('market-slug-123');
|
|
1599
|
-
* console.log(result.message); // "Orders canceled successfully"
|
|
1600
|
-
* ```
|
|
1601
|
-
*/
|
|
1602
|
-
async cancelAll(marketSlug) {
|
|
1603
|
-
this.logger.info("Cancelling all orders for market", { marketSlug });
|
|
1604
|
-
const response = await this.httpClient.delete(
|
|
1605
|
-
`/orders/all/${marketSlug}`
|
|
1606
|
-
);
|
|
1607
|
-
this.logger.info("All orders cancellation response", {
|
|
1608
|
-
marketSlug,
|
|
1609
|
-
message: response.message
|
|
1610
|
-
});
|
|
1611
|
-
return response;
|
|
1612
|
-
}
|
|
1613
|
-
/**
|
|
1614
|
-
* @deprecated Use `cancel()` instead
|
|
1615
|
-
*/
|
|
1616
|
-
async cancelOrder(orderId) {
|
|
1617
|
-
await this.cancel(orderId);
|
|
1618
|
-
}
|
|
1619
|
-
/**
|
|
1620
|
-
* Gets an order by ID.
|
|
1621
|
-
*
|
|
1622
|
-
* @param orderId - Order ID to fetch
|
|
1623
|
-
* @returns Promise resolving to order details
|
|
1624
|
-
*
|
|
1625
|
-
* @throws Error if order not found
|
|
1626
|
-
*
|
|
1627
|
-
* @example
|
|
1628
|
-
* ```typescript
|
|
1629
|
-
* const order = await orderClient.getOrder('order-id-123');
|
|
1630
|
-
* console.log(order.order.side);
|
|
1631
|
-
* ```
|
|
1632
|
-
*/
|
|
1633
|
-
async getOrder(orderId) {
|
|
1634
|
-
this.logger.debug("Fetching order", { orderId });
|
|
1635
|
-
const response = await this.httpClient.get(
|
|
1636
|
-
`/orders/${orderId}`
|
|
1637
|
-
);
|
|
1638
|
-
return response;
|
|
1639
|
-
}
|
|
1640
|
-
/**
|
|
1641
|
-
* Builds an unsigned order without submitting.
|
|
1642
|
-
*
|
|
1643
|
-
* @remarks
|
|
1644
|
-
* Useful for advanced use cases where you need the unsigned order
|
|
1645
|
-
* before signing and submission.
|
|
1646
|
-
*
|
|
1647
|
-
* @param params - Order parameters
|
|
1648
|
-
* @returns Unsigned order
|
|
1649
|
-
*
|
|
1650
|
-
* @example
|
|
1651
|
-
* ```typescript
|
|
1652
|
-
* const unsignedOrder = orderClient.buildUnsignedOrder({
|
|
1653
|
-
* tokenId: '123456',
|
|
1654
|
-
* price: 0.65,
|
|
1655
|
-
* size: 100,
|
|
1656
|
-
* side: Side.BUY
|
|
1657
|
-
* });
|
|
1658
|
-
* ```
|
|
1659
|
-
*/
|
|
1660
|
-
buildUnsignedOrder(params) {
|
|
1661
|
-
return this.orderBuilder.buildOrder(params);
|
|
1662
|
-
}
|
|
1663
|
-
/**
|
|
1664
|
-
* Signs an unsigned order without submitting.
|
|
1665
|
-
*
|
|
1666
|
-
* @remarks
|
|
1667
|
-
* Useful for advanced use cases where you need to inspect
|
|
1668
|
-
* the signature before submission.
|
|
1669
|
-
*
|
|
1670
|
-
* @param order - Unsigned order to sign
|
|
1671
|
-
* @returns Promise resolving to signature
|
|
1672
|
-
*
|
|
1673
|
-
* @example
|
|
1674
|
-
* ```typescript
|
|
1675
|
-
* const signature = await orderClient.signOrder(unsignedOrder);
|
|
1676
|
-
* ```
|
|
1677
|
-
*/
|
|
1678
|
-
async signOrder(order) {
|
|
1679
|
-
return await this.orderSigner.signOrder(order, this.signingConfig);
|
|
1680
|
-
}
|
|
1681
|
-
};
|
|
1682
|
-
|
|
1683
|
-
// src/markets/fetcher.ts
|
|
1684
|
-
var MarketFetcher = class {
|
|
1685
|
-
/**
|
|
1686
|
-
* Creates a new market fetcher instance.
|
|
1687
|
-
*
|
|
1688
|
-
* @param httpClient - HTTP client for API requests
|
|
1689
|
-
* @param logger - Optional logger for debugging (default: no logging)
|
|
1690
|
-
*
|
|
1691
|
-
* @example
|
|
1692
|
-
* ```typescript
|
|
1693
|
-
* const fetcher = new MarketFetcher(httpClient);
|
|
1694
|
-
* ```
|
|
1695
|
-
*/
|
|
1696
|
-
constructor(httpClient, logger) {
|
|
1697
|
-
this.httpClient = httpClient;
|
|
1698
|
-
this.logger = logger || new NoOpLogger();
|
|
1699
|
-
}
|
|
1700
|
-
/**
|
|
1701
|
-
* Gets active markets with query parameters and pagination support.
|
|
1172
|
+
* Gets active markets with query parameters and pagination support.
|
|
1702
1173
|
*
|
|
1703
1174
|
* @param params - Query parameters for filtering and pagination
|
|
1704
1175
|
* @returns Promise resolving to active markets response
|
|
@@ -1737,13 +1208,18 @@ var MarketFetcher = class {
|
|
|
1737
1208
|
this.logger.debug("Fetching active markets", { params });
|
|
1738
1209
|
try {
|
|
1739
1210
|
const response = await this.httpClient.get(endpoint);
|
|
1211
|
+
const markets = response.data.map((marketData) => new Market(marketData, this.httpClient));
|
|
1212
|
+
const result = {
|
|
1213
|
+
data: markets,
|
|
1214
|
+
totalMarketsCount: response.totalMarketsCount
|
|
1215
|
+
};
|
|
1740
1216
|
this.logger.info("Active markets fetched successfully", {
|
|
1741
|
-
count:
|
|
1217
|
+
count: markets.length,
|
|
1742
1218
|
total: response.totalMarketsCount,
|
|
1743
1219
|
sortBy: params?.sortBy,
|
|
1744
1220
|
page: params?.page
|
|
1745
1221
|
});
|
|
1746
|
-
return
|
|
1222
|
+
return result;
|
|
1747
1223
|
} catch (error) {
|
|
1748
1224
|
this.logger.error("Failed to fetch active markets", error, { params });
|
|
1749
1225
|
throw error;
|
|
@@ -1752,20 +1228,48 @@ var MarketFetcher = class {
|
|
|
1752
1228
|
/**
|
|
1753
1229
|
* Gets a single market by slug.
|
|
1754
1230
|
*
|
|
1231
|
+
* @remarks
|
|
1232
|
+
* Automatically caches venue information for efficient order signing.
|
|
1233
|
+
* Always call this method before creating orders to ensure venue data
|
|
1234
|
+
* is available and avoid additional API requests.
|
|
1235
|
+
*
|
|
1755
1236
|
* @param slug - Market slug identifier
|
|
1756
1237
|
* @returns Promise resolving to market details
|
|
1757
1238
|
* @throws Error if API request fails or market not found
|
|
1758
1239
|
*
|
|
1759
1240
|
* @example
|
|
1760
1241
|
* ```typescript
|
|
1242
|
+
* // Get market
|
|
1761
1243
|
* const market = await fetcher.getMarket('bitcoin-price-2024');
|
|
1762
1244
|
* console.log(`Market: ${market.title}`);
|
|
1245
|
+
*
|
|
1246
|
+
* // Fluent API - get user orders for this market (clean!)
|
|
1247
|
+
* const orders = await market.getUserOrders();
|
|
1248
|
+
* console.log(`You have ${orders.length} orders`);
|
|
1249
|
+
*
|
|
1250
|
+
* // Venue is now cached for order signing
|
|
1251
|
+
* await orderClient.createOrder({
|
|
1252
|
+
* marketSlug: 'bitcoin-price-2024',
|
|
1253
|
+
* ...
|
|
1254
|
+
* });
|
|
1763
1255
|
* ```
|
|
1764
1256
|
*/
|
|
1765
1257
|
async getMarket(slug) {
|
|
1766
1258
|
this.logger.debug("Fetching market", { slug });
|
|
1767
1259
|
try {
|
|
1768
|
-
const
|
|
1260
|
+
const response = await this.httpClient.get(`/markets/${slug}`);
|
|
1261
|
+
const market = new Market(response, this.httpClient);
|
|
1262
|
+
if (market.venue) {
|
|
1263
|
+
this.venueCache.set(slug, market.venue);
|
|
1264
|
+
this.logger.debug("Venue cached for order signing", {
|
|
1265
|
+
slug,
|
|
1266
|
+
exchange: market.venue.exchange,
|
|
1267
|
+
adapter: market.venue.adapter,
|
|
1268
|
+
cacheSize: this.venueCache.size
|
|
1269
|
+
});
|
|
1270
|
+
} else {
|
|
1271
|
+
this.logger.warn("Market has no venue data", { slug });
|
|
1272
|
+
}
|
|
1769
1273
|
this.logger.info("Market fetched successfully", {
|
|
1770
1274
|
slug,
|
|
1771
1275
|
title: market.title
|
|
@@ -1776,6 +1280,36 @@ var MarketFetcher = class {
|
|
|
1776
1280
|
throw error;
|
|
1777
1281
|
}
|
|
1778
1282
|
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Gets cached venue information for a market.
|
|
1285
|
+
*
|
|
1286
|
+
* @remarks
|
|
1287
|
+
* Returns venue data previously cached by getMarket() call.
|
|
1288
|
+
* Used internally by OrderClient for efficient order signing.
|
|
1289
|
+
*
|
|
1290
|
+
* @param slug - Market slug identifier
|
|
1291
|
+
* @returns Cached venue information, or undefined if not in cache
|
|
1292
|
+
*
|
|
1293
|
+
* @example
|
|
1294
|
+
* ```typescript
|
|
1295
|
+
* const venue = fetcher.getVenue('bitcoin-price-2024');
|
|
1296
|
+
* if (venue) {
|
|
1297
|
+
* console.log(`Exchange: ${venue.exchange}`);
|
|
1298
|
+
* }
|
|
1299
|
+
* ```
|
|
1300
|
+
*/
|
|
1301
|
+
getVenue(slug) {
|
|
1302
|
+
const venue = this.venueCache.get(slug);
|
|
1303
|
+
if (venue) {
|
|
1304
|
+
this.logger.debug("Venue cache hit", {
|
|
1305
|
+
slug,
|
|
1306
|
+
exchange: venue.exchange
|
|
1307
|
+
});
|
|
1308
|
+
} else {
|
|
1309
|
+
this.logger.debug("Venue cache miss", { slug });
|
|
1310
|
+
}
|
|
1311
|
+
return venue;
|
|
1312
|
+
}
|
|
1779
1313
|
/**
|
|
1780
1314
|
* Gets the orderbook for a CLOB market.
|
|
1781
1315
|
*
|
|
@@ -1807,335 +1341,473 @@ var MarketFetcher = class {
|
|
|
1807
1341
|
throw error;
|
|
1808
1342
|
}
|
|
1809
1343
|
}
|
|
1344
|
+
};
|
|
1345
|
+
|
|
1346
|
+
// src/portfolio/fetcher.ts
|
|
1347
|
+
var PortfolioFetcher = class {
|
|
1348
|
+
/**
|
|
1349
|
+
* Creates a new portfolio fetcher instance.
|
|
1350
|
+
*
|
|
1351
|
+
* @param httpClient - Authenticated HTTP client for API requests
|
|
1352
|
+
* @param logger - Optional logger for debugging (default: no logging)
|
|
1353
|
+
*
|
|
1354
|
+
* @example
|
|
1355
|
+
* ```typescript
|
|
1356
|
+
* // Create authenticated client
|
|
1357
|
+
* const httpClient = new HttpClient({ baseURL: API_URL });
|
|
1358
|
+
* await authenticator.authenticate({ client: 'eoa' });
|
|
1359
|
+
*
|
|
1360
|
+
* // Create portfolio fetcher
|
|
1361
|
+
* const portfolioFetcher = new PortfolioFetcher(httpClient);
|
|
1362
|
+
* ```
|
|
1363
|
+
*/
|
|
1364
|
+
constructor(httpClient, logger) {
|
|
1365
|
+
this.httpClient = httpClient;
|
|
1366
|
+
this.logger = logger || new NoOpLogger();
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Gets user profile for a specific wallet address.
|
|
1370
|
+
*
|
|
1371
|
+
* @remarks
|
|
1372
|
+
* Returns user profile data including user ID and fee rate.
|
|
1373
|
+
* Used internally by OrderClient to fetch user data.
|
|
1374
|
+
*
|
|
1375
|
+
* @param address - Wallet address to fetch profile for
|
|
1376
|
+
* @returns Promise resolving to user profile data
|
|
1377
|
+
* @throws Error if API request fails or user is not authenticated
|
|
1378
|
+
*
|
|
1379
|
+
* @example
|
|
1380
|
+
* ```typescript
|
|
1381
|
+
* const profile = await portfolioFetcher.getProfile('0x1234...');
|
|
1382
|
+
* console.log(`User ID: ${profile.id}`);
|
|
1383
|
+
* console.log(`Account: ${profile.account}`);
|
|
1384
|
+
* console.log(`Fee Rate: ${profile.rank?.feeRateBps}`);
|
|
1385
|
+
* ```
|
|
1386
|
+
*/
|
|
1387
|
+
async getProfile(address) {
|
|
1388
|
+
this.logger.debug("Fetching user profile", { address });
|
|
1389
|
+
try {
|
|
1390
|
+
const response = await this.httpClient.get(`/profiles/${address}`);
|
|
1391
|
+
this.logger.info("User profile fetched successfully", { address });
|
|
1392
|
+
return response;
|
|
1393
|
+
} catch (error) {
|
|
1394
|
+
this.logger.error("Failed to fetch user profile", error, { address });
|
|
1395
|
+
throw error;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Gets raw portfolio positions response from API.
|
|
1400
|
+
*
|
|
1401
|
+
* @returns Promise resolving to portfolio positions response with CLOB and AMM positions
|
|
1402
|
+
* @throws Error if API request fails or user is not authenticated
|
|
1403
|
+
*
|
|
1404
|
+
* @example
|
|
1405
|
+
* ```typescript
|
|
1406
|
+
* const response = await portfolioFetcher.getPositions();
|
|
1407
|
+
* console.log(`CLOB positions: ${response.clob.length}`);
|
|
1408
|
+
* console.log(`AMM positions: ${response.amm.length}`);
|
|
1409
|
+
* console.log(`Total points: ${response.accumulativePoints}`);
|
|
1410
|
+
* ```
|
|
1411
|
+
*/
|
|
1412
|
+
async getPositions() {
|
|
1413
|
+
this.logger.debug("Fetching user positions");
|
|
1414
|
+
try {
|
|
1415
|
+
const response = await this.httpClient.get("/portfolio/positions");
|
|
1416
|
+
this.logger.info("Positions fetched successfully", {
|
|
1417
|
+
clobCount: response.clob?.length || 0,
|
|
1418
|
+
ammCount: response.amm?.length || 0
|
|
1419
|
+
});
|
|
1420
|
+
return response;
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
this.logger.error("Failed to fetch positions", error);
|
|
1423
|
+
throw error;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Gets CLOB positions only.
|
|
1428
|
+
*
|
|
1429
|
+
* @returns Promise resolving to array of CLOB positions
|
|
1430
|
+
* @throws Error if API request fails
|
|
1431
|
+
*
|
|
1432
|
+
* @example
|
|
1433
|
+
* ```typescript
|
|
1434
|
+
* const clobPositions = await portfolioFetcher.getCLOBPositions();
|
|
1435
|
+
* clobPositions.forEach(pos => {
|
|
1436
|
+
* console.log(`${pos.market.title}: YES ${pos.positions.yes.unrealizedPnl} P&L`);
|
|
1437
|
+
* });
|
|
1438
|
+
* ```
|
|
1439
|
+
*/
|
|
1440
|
+
async getCLOBPositions() {
|
|
1441
|
+
const response = await this.getPositions();
|
|
1442
|
+
return response.clob || [];
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Gets AMM positions only.
|
|
1446
|
+
*
|
|
1447
|
+
* @returns Promise resolving to array of AMM positions
|
|
1448
|
+
* @throws Error if API request fails
|
|
1449
|
+
*
|
|
1450
|
+
* @example
|
|
1451
|
+
* ```typescript
|
|
1452
|
+
* const ammPositions = await portfolioFetcher.getAMMPositions();
|
|
1453
|
+
* ammPositions.forEach(pos => {
|
|
1454
|
+
* console.log(`${pos.market.title}: ${pos.unrealizedPnl} P&L`);
|
|
1455
|
+
* });
|
|
1456
|
+
* ```
|
|
1457
|
+
*/
|
|
1458
|
+
async getAMMPositions() {
|
|
1459
|
+
const response = await this.getPositions();
|
|
1460
|
+
return response.amm || [];
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Gets paginated history of user actions.
|
|
1464
|
+
*
|
|
1465
|
+
* Includes AMM trades, CLOB trades, Negrisk trades & conversions.
|
|
1466
|
+
*
|
|
1467
|
+
* @param page - Page number (starts at 1)
|
|
1468
|
+
* @param limit - Number of items per page
|
|
1469
|
+
* @returns Promise resolving to paginated history response
|
|
1470
|
+
* @throws Error if API request fails or user is not authenticated
|
|
1471
|
+
*
|
|
1472
|
+
* @example
|
|
1473
|
+
* ```typescript
|
|
1474
|
+
* // Get first page
|
|
1475
|
+
* const response = await portfolioFetcher.getUserHistory(1, 20);
|
|
1476
|
+
* console.log(`Found ${response.data.length} of ${response.totalCount} entries`);
|
|
1477
|
+
*
|
|
1478
|
+
* // Process history entries
|
|
1479
|
+
* for (const entry of response.data) {
|
|
1480
|
+
* console.log(`Type: ${entry.type}`);
|
|
1481
|
+
* console.log(`Market: ${entry.marketSlug}`);
|
|
1482
|
+
* }
|
|
1483
|
+
*
|
|
1484
|
+
* // Get next page
|
|
1485
|
+
* const page2 = await portfolioFetcher.getUserHistory(2, 20);
|
|
1486
|
+
* ```
|
|
1487
|
+
*/
|
|
1488
|
+
async getUserHistory(page = 1, limit = 10) {
|
|
1489
|
+
this.logger.debug("Fetching user history", { page, limit });
|
|
1490
|
+
try {
|
|
1491
|
+
const params = new URLSearchParams({
|
|
1492
|
+
page: page.toString(),
|
|
1493
|
+
limit: limit.toString()
|
|
1494
|
+
});
|
|
1495
|
+
const response = await this.httpClient.get(
|
|
1496
|
+
`/portfolio/history?${params.toString()}`
|
|
1497
|
+
);
|
|
1498
|
+
this.logger.info("User history fetched successfully");
|
|
1499
|
+
return response;
|
|
1500
|
+
} catch (error) {
|
|
1501
|
+
this.logger.error("Failed to fetch user history", error, { page, limit });
|
|
1502
|
+
throw error;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
|
|
1507
|
+
// src/orders/client.ts
|
|
1508
|
+
var OrderClient = class {
|
|
1810
1509
|
/**
|
|
1811
|
-
*
|
|
1510
|
+
* Creates a new order client instance.
|
|
1511
|
+
*
|
|
1512
|
+
* @param config - Order client configuration
|
|
1513
|
+
*/
|
|
1514
|
+
constructor(config) {
|
|
1515
|
+
this.httpClient = config.httpClient;
|
|
1516
|
+
this.wallet = config.wallet;
|
|
1517
|
+
this.logger = config.logger || new NoOpLogger();
|
|
1518
|
+
this.orderSigner = new OrderSigner(config.wallet, this.logger);
|
|
1519
|
+
this.marketFetcher = config.marketFetcher || new MarketFetcher(config.httpClient, this.logger);
|
|
1520
|
+
if (config.signingConfig) {
|
|
1521
|
+
this.signingConfig = config.signingConfig;
|
|
1522
|
+
} else {
|
|
1523
|
+
const chainId = parseInt(process.env.CHAIN_ID || "8453");
|
|
1524
|
+
const contractAddress = ZERO_ADDRESS;
|
|
1525
|
+
this.signingConfig = {
|
|
1526
|
+
chainId,
|
|
1527
|
+
contractAddress
|
|
1528
|
+
};
|
|
1529
|
+
this.logger.info("Auto-configured signing (contract address from venue)", {
|
|
1530
|
+
chainId
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Ensures user data is loaded and cached.
|
|
1536
|
+
* Fetches from profile API on first call, then caches for subsequent calls.
|
|
1537
|
+
*
|
|
1538
|
+
* @returns Promise resolving to cached user data
|
|
1539
|
+
* @internal
|
|
1540
|
+
*/
|
|
1541
|
+
async ensureUserData() {
|
|
1542
|
+
if (!this.cachedUserData) {
|
|
1543
|
+
this.logger.info("Fetching user profile for order client initialization...", {
|
|
1544
|
+
walletAddress: this.wallet.address
|
|
1545
|
+
});
|
|
1546
|
+
const portfolioFetcher = new PortfolioFetcher(this.httpClient);
|
|
1547
|
+
const profile = await portfolioFetcher.getProfile(this.wallet.address);
|
|
1548
|
+
this.cachedUserData = {
|
|
1549
|
+
userId: profile.id,
|
|
1550
|
+
feeRateBps: profile.rank?.feeRateBps || 300
|
|
1551
|
+
};
|
|
1552
|
+
this.orderBuilder = new OrderBuilder(
|
|
1553
|
+
this.wallet.address,
|
|
1554
|
+
this.cachedUserData.feeRateBps,
|
|
1555
|
+
1e-3
|
|
1556
|
+
);
|
|
1557
|
+
this.logger.info("Order Client initialized", {
|
|
1558
|
+
walletAddress: profile.account,
|
|
1559
|
+
userId: this.cachedUserData.userId,
|
|
1560
|
+
feeRate: `${this.cachedUserData.feeRateBps / 100}%`
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
return this.cachedUserData;
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Creates and submits a new order.
|
|
1567
|
+
*
|
|
1568
|
+
* @remarks
|
|
1569
|
+
* This method handles the complete order creation flow:
|
|
1570
|
+
* 1. Resolve venue address (from cache or API)
|
|
1571
|
+
* 2. Build unsigned order
|
|
1572
|
+
* 3. Sign with EIP-712 using venue.exchange as verifyingContract
|
|
1573
|
+
* 4. Submit to API
|
|
1812
1574
|
*
|
|
1813
|
-
*
|
|
1814
|
-
*
|
|
1815
|
-
*
|
|
1575
|
+
* Performance best practice: Always call marketFetcher.getMarket(marketSlug)
|
|
1576
|
+
* before createOrder() to cache venue data and avoid additional API requests.
|
|
1577
|
+
*
|
|
1578
|
+
* @param params - Order parameters
|
|
1579
|
+
* @returns Promise resolving to order response
|
|
1580
|
+
*
|
|
1581
|
+
* @throws Error if order creation fails or venue not found
|
|
1816
1582
|
*
|
|
1817
1583
|
* @example
|
|
1818
1584
|
* ```typescript
|
|
1819
|
-
*
|
|
1820
|
-
*
|
|
1585
|
+
* // Best practice: fetch market first to cache venue
|
|
1586
|
+
* const market = await marketFetcher.getMarket('bitcoin-2024');
|
|
1587
|
+
*
|
|
1588
|
+
* const order = await orderClient.createOrder({
|
|
1589
|
+
* tokenId: market.tokens.yes,
|
|
1590
|
+
* price: 0.65,
|
|
1591
|
+
* size: 100,
|
|
1592
|
+
* side: Side.BUY,
|
|
1593
|
+
* orderType: OrderType.GTC,
|
|
1594
|
+
* marketSlug: 'bitcoin-2024'
|
|
1595
|
+
* });
|
|
1596
|
+
*
|
|
1597
|
+
* console.log(`Order created: ${order.order.id}`);
|
|
1821
1598
|
* ```
|
|
1822
1599
|
*/
|
|
1823
|
-
async
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1600
|
+
async createOrder(params) {
|
|
1601
|
+
const userData = await this.ensureUserData();
|
|
1602
|
+
this.logger.info("Creating order", {
|
|
1603
|
+
side: params.side,
|
|
1604
|
+
orderType: params.orderType,
|
|
1605
|
+
marketSlug: params.marketSlug
|
|
1606
|
+
});
|
|
1607
|
+
let venue = this.marketFetcher.getVenue(params.marketSlug);
|
|
1608
|
+
if (!venue) {
|
|
1609
|
+
this.logger.warn(
|
|
1610
|
+
"Venue not cached, fetching market details. For better performance, call marketFetcher.getMarket() before createOrder().",
|
|
1611
|
+
{ marketSlug: params.marketSlug }
|
|
1612
|
+
);
|
|
1613
|
+
const market = await this.marketFetcher.getMarket(params.marketSlug);
|
|
1614
|
+
if (!market.venue) {
|
|
1615
|
+
throw new Error(
|
|
1616
|
+
`Market ${params.marketSlug} does not have venue information. Venue data is required for order signing.`
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
venue = market.venue;
|
|
1835
1620
|
}
|
|
1621
|
+
const dynamicSigningConfig = {
|
|
1622
|
+
...this.signingConfig,
|
|
1623
|
+
contractAddress: venue.exchange
|
|
1624
|
+
};
|
|
1625
|
+
this.logger.debug("Using venue for order signing", {
|
|
1626
|
+
marketSlug: params.marketSlug,
|
|
1627
|
+
exchange: venue.exchange,
|
|
1628
|
+
adapter: venue.adapter
|
|
1629
|
+
});
|
|
1630
|
+
const unsignedOrder = this.orderBuilder.buildOrder(params);
|
|
1631
|
+
this.logger.debug("Built unsigned order", {
|
|
1632
|
+
salt: unsignedOrder.salt,
|
|
1633
|
+
makerAmount: unsignedOrder.makerAmount,
|
|
1634
|
+
takerAmount: unsignedOrder.takerAmount
|
|
1635
|
+
});
|
|
1636
|
+
const signature = await this.orderSigner.signOrder(unsignedOrder, dynamicSigningConfig);
|
|
1637
|
+
const payload = {
|
|
1638
|
+
order: {
|
|
1639
|
+
...unsignedOrder,
|
|
1640
|
+
signature
|
|
1641
|
+
},
|
|
1642
|
+
orderType: params.orderType,
|
|
1643
|
+
marketSlug: params.marketSlug,
|
|
1644
|
+
ownerId: userData.userId
|
|
1645
|
+
};
|
|
1646
|
+
this.logger.debug("Submitting order to API", payload);
|
|
1647
|
+
const apiResponse = await this.httpClient.post("/orders", payload);
|
|
1648
|
+
this.logger.info("Order created successfully", {
|
|
1649
|
+
orderId: apiResponse.order.id
|
|
1650
|
+
});
|
|
1651
|
+
return this.transformOrderResponse(apiResponse);
|
|
1836
1652
|
}
|
|
1837
|
-
};
|
|
1838
|
-
|
|
1839
|
-
// src/portfolio/fetcher.ts
|
|
1840
|
-
var PortfolioFetcher = class {
|
|
1841
1653
|
/**
|
|
1842
|
-
*
|
|
1843
|
-
*
|
|
1844
|
-
* @param httpClient - Authenticated HTTP client for API requests
|
|
1845
|
-
* @param logger - Optional logger for debugging (default: no logging)
|
|
1654
|
+
* Transforms raw API response to clean OrderResponse DTO.
|
|
1846
1655
|
*
|
|
1847
|
-
* @
|
|
1848
|
-
*
|
|
1849
|
-
* // Create authenticated client
|
|
1850
|
-
* const httpClient = new HttpClient({ baseURL: API_URL });
|
|
1851
|
-
* await authenticator.authenticate({ client: 'eoa' });
|
|
1656
|
+
* @param apiResponse - Raw API response with nested objects
|
|
1657
|
+
* @returns Clean OrderResponse with only essential fields
|
|
1852
1658
|
*
|
|
1853
|
-
*
|
|
1854
|
-
* const portfolioFetcher = new PortfolioFetcher(httpClient);
|
|
1855
|
-
* ```
|
|
1659
|
+
* @internal
|
|
1856
1660
|
*/
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1661
|
+
transformOrderResponse(apiResponse) {
|
|
1662
|
+
const cleanOrder = {
|
|
1663
|
+
order: {
|
|
1664
|
+
id: apiResponse.order.id,
|
|
1665
|
+
createdAt: apiResponse.order.createdAt,
|
|
1666
|
+
makerAmount: apiResponse.order.makerAmount,
|
|
1667
|
+
takerAmount: apiResponse.order.takerAmount,
|
|
1668
|
+
expiration: apiResponse.order.expiration,
|
|
1669
|
+
signatureType: apiResponse.order.signatureType,
|
|
1670
|
+
salt: apiResponse.order.salt,
|
|
1671
|
+
maker: apiResponse.order.maker,
|
|
1672
|
+
signer: apiResponse.order.signer,
|
|
1673
|
+
taker: apiResponse.order.taker,
|
|
1674
|
+
tokenId: apiResponse.order.tokenId,
|
|
1675
|
+
side: apiResponse.order.side,
|
|
1676
|
+
feeRateBps: apiResponse.order.feeRateBps,
|
|
1677
|
+
nonce: apiResponse.order.nonce,
|
|
1678
|
+
signature: apiResponse.order.signature,
|
|
1679
|
+
orderType: apiResponse.order.orderType,
|
|
1680
|
+
price: apiResponse.order.price,
|
|
1681
|
+
marketId: apiResponse.order.marketId
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
if (apiResponse.makerMatches && apiResponse.makerMatches.length > 0) {
|
|
1685
|
+
cleanOrder.makerMatches = apiResponse.makerMatches.map((match) => ({
|
|
1686
|
+
id: match.id,
|
|
1687
|
+
createdAt: match.createdAt,
|
|
1688
|
+
matchedSize: match.matchedSize,
|
|
1689
|
+
orderId: match.orderId
|
|
1690
|
+
}));
|
|
1691
|
+
}
|
|
1692
|
+
return cleanOrder;
|
|
1860
1693
|
}
|
|
1861
1694
|
/**
|
|
1862
|
-
*
|
|
1695
|
+
* Cancels an existing order by ID.
|
|
1863
1696
|
*
|
|
1864
|
-
* @
|
|
1865
|
-
* @
|
|
1697
|
+
* @param orderId - Order ID to cancel
|
|
1698
|
+
* @returns Promise resolving to cancellation message
|
|
1699
|
+
*
|
|
1700
|
+
* @throws Error if cancellation fails
|
|
1866
1701
|
*
|
|
1867
1702
|
* @example
|
|
1868
1703
|
* ```typescript
|
|
1869
|
-
* const
|
|
1870
|
-
* console.log(
|
|
1871
|
-
* console.log(`AMM positions: ${response.amm.length}`);
|
|
1872
|
-
* console.log(`Total points: ${response.accumulativePoints}`);
|
|
1704
|
+
* const result = await orderClient.cancel('order-id-123');
|
|
1705
|
+
* console.log(result.message); // "Order canceled successfully"
|
|
1873
1706
|
* ```
|
|
1874
1707
|
*/
|
|
1875
|
-
async
|
|
1876
|
-
this.logger.
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
ammCount: response.amm?.length || 0
|
|
1884
|
-
});
|
|
1885
|
-
return response;
|
|
1886
|
-
} catch (error) {
|
|
1887
|
-
this.logger.error("Failed to fetch positions", error);
|
|
1888
|
-
throw error;
|
|
1889
|
-
}
|
|
1708
|
+
async cancel(orderId) {
|
|
1709
|
+
this.logger.info("Cancelling order", { orderId });
|
|
1710
|
+
const response = await this.httpClient.delete(`/orders/${orderId}`);
|
|
1711
|
+
this.logger.info("Order cancellation response", {
|
|
1712
|
+
orderId,
|
|
1713
|
+
message: response.message
|
|
1714
|
+
});
|
|
1715
|
+
return response;
|
|
1890
1716
|
}
|
|
1891
1717
|
/**
|
|
1892
|
-
*
|
|
1718
|
+
* Cancels all orders for a specific market.
|
|
1893
1719
|
*
|
|
1894
|
-
* @
|
|
1895
|
-
* @
|
|
1720
|
+
* @param marketSlug - Market slug to cancel all orders for
|
|
1721
|
+
* @returns Promise resolving to cancellation message
|
|
1722
|
+
*
|
|
1723
|
+
* @throws Error if cancellation fails
|
|
1896
1724
|
*
|
|
1897
1725
|
* @example
|
|
1898
1726
|
* ```typescript
|
|
1899
|
-
* const
|
|
1900
|
-
*
|
|
1901
|
-
* console.log(`${pos.market.title}: YES ${pos.positions.yes.unrealizedPnl} P&L`);
|
|
1902
|
-
* });
|
|
1727
|
+
* const result = await orderClient.cancelAll('market-slug-123');
|
|
1728
|
+
* console.log(result.message); // "Orders canceled successfully"
|
|
1903
1729
|
* ```
|
|
1904
1730
|
*/
|
|
1905
|
-
async
|
|
1906
|
-
|
|
1907
|
-
|
|
1731
|
+
async cancelAll(marketSlug) {
|
|
1732
|
+
this.logger.info("Cancelling all orders for market", { marketSlug });
|
|
1733
|
+
const response = await this.httpClient.delete(`/orders/all/${marketSlug}`);
|
|
1734
|
+
this.logger.info("All orders cancellation response", {
|
|
1735
|
+
marketSlug,
|
|
1736
|
+
message: response.message
|
|
1737
|
+
});
|
|
1738
|
+
return response;
|
|
1908
1739
|
}
|
|
1909
1740
|
/**
|
|
1910
|
-
*
|
|
1741
|
+
* Builds an unsigned order without submitting.
|
|
1911
1742
|
*
|
|
1912
|
-
* @
|
|
1913
|
-
*
|
|
1743
|
+
* @remarks
|
|
1744
|
+
* Useful for advanced use cases where you need the unsigned order
|
|
1745
|
+
* before signing and submission.
|
|
1746
|
+
*
|
|
1747
|
+
* @param params - Order parameters
|
|
1748
|
+
* @returns Promise resolving to unsigned order
|
|
1914
1749
|
*
|
|
1915
1750
|
* @example
|
|
1916
1751
|
* ```typescript
|
|
1917
|
-
* const
|
|
1918
|
-
*
|
|
1919
|
-
*
|
|
1752
|
+
* const unsignedOrder = await orderClient.buildUnsignedOrder({
|
|
1753
|
+
* tokenId: '123456',
|
|
1754
|
+
* price: 0.65,
|
|
1755
|
+
* size: 100,
|
|
1756
|
+
* side: Side.BUY
|
|
1920
1757
|
* });
|
|
1921
1758
|
* ```
|
|
1922
1759
|
*/
|
|
1923
|
-
async
|
|
1924
|
-
|
|
1925
|
-
return
|
|
1760
|
+
async buildUnsignedOrder(params) {
|
|
1761
|
+
await this.ensureUserData();
|
|
1762
|
+
return this.orderBuilder.buildOrder(params);
|
|
1926
1763
|
}
|
|
1927
1764
|
/**
|
|
1928
|
-
*
|
|
1765
|
+
* Signs an unsigned order without submitting.
|
|
1929
1766
|
*
|
|
1930
1767
|
* @remarks
|
|
1931
|
-
*
|
|
1932
|
-
*
|
|
1768
|
+
* Useful for advanced use cases where you need to inspect
|
|
1769
|
+
* the signature before submission.
|
|
1933
1770
|
*
|
|
1934
|
-
* @
|
|
1935
|
-
* @
|
|
1771
|
+
* @param order - Unsigned order to sign
|
|
1772
|
+
* @returns Promise resolving to signature
|
|
1936
1773
|
*
|
|
1937
1774
|
* @example
|
|
1938
1775
|
* ```typescript
|
|
1939
|
-
* const
|
|
1940
|
-
* positions.forEach(pos => {
|
|
1941
|
-
* const pnlPercent = (pos.unrealizedPnl / pos.costBasis) * 100;
|
|
1942
|
-
* console.log(`${pos.market.title} (${pos.side}): ${pnlPercent.toFixed(2)}% P&L`);
|
|
1943
|
-
* });
|
|
1776
|
+
* const signature = await orderClient.signOrder(unsignedOrder);
|
|
1944
1777
|
* ```
|
|
1945
1778
|
*/
|
|
1946
|
-
async
|
|
1947
|
-
|
|
1948
|
-
const positions = [];
|
|
1949
|
-
for (const clobPos of response.clob || []) {
|
|
1950
|
-
const yesCost = parseFloat(clobPos.positions.yes.cost);
|
|
1951
|
-
const yesValue = parseFloat(clobPos.positions.yes.marketValue);
|
|
1952
|
-
if (yesCost > 0 || yesValue > 0) {
|
|
1953
|
-
positions.push({
|
|
1954
|
-
type: "CLOB",
|
|
1955
|
-
market: clobPos.market,
|
|
1956
|
-
side: "YES",
|
|
1957
|
-
costBasis: yesCost,
|
|
1958
|
-
marketValue: yesValue,
|
|
1959
|
-
unrealizedPnl: parseFloat(clobPos.positions.yes.unrealizedPnl),
|
|
1960
|
-
realizedPnl: parseFloat(clobPos.positions.yes.realisedPnl),
|
|
1961
|
-
currentPrice: clobPos.latestTrade?.latestYesPrice ?? 0,
|
|
1962
|
-
avgPrice: yesCost > 0 ? parseFloat(clobPos.positions.yes.fillPrice) / 1e6 : 0,
|
|
1963
|
-
tokenBalance: parseFloat(clobPos.tokensBalance.yes)
|
|
1964
|
-
});
|
|
1965
|
-
}
|
|
1966
|
-
const noCost = parseFloat(clobPos.positions.no.cost);
|
|
1967
|
-
const noValue = parseFloat(clobPos.positions.no.marketValue);
|
|
1968
|
-
if (noCost > 0 || noValue > 0) {
|
|
1969
|
-
positions.push({
|
|
1970
|
-
type: "CLOB",
|
|
1971
|
-
market: clobPos.market,
|
|
1972
|
-
side: "NO",
|
|
1973
|
-
costBasis: noCost,
|
|
1974
|
-
marketValue: noValue,
|
|
1975
|
-
unrealizedPnl: parseFloat(clobPos.positions.no.unrealizedPnl),
|
|
1976
|
-
realizedPnl: parseFloat(clobPos.positions.no.realisedPnl),
|
|
1977
|
-
currentPrice: clobPos.latestTrade?.latestNoPrice ?? 0,
|
|
1978
|
-
avgPrice: noCost > 0 ? parseFloat(clobPos.positions.no.fillPrice) / 1e6 : 0,
|
|
1979
|
-
tokenBalance: parseFloat(clobPos.tokensBalance.no)
|
|
1980
|
-
});
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
for (const ammPos of response.amm || []) {
|
|
1984
|
-
const cost = parseFloat(ammPos.totalBuysCost);
|
|
1985
|
-
const value = parseFloat(ammPos.collateralAmount);
|
|
1986
|
-
if (cost > 0 || value > 0) {
|
|
1987
|
-
positions.push({
|
|
1988
|
-
type: "AMM",
|
|
1989
|
-
market: ammPos.market,
|
|
1990
|
-
side: ammPos.outcomeIndex === 0 ? "YES" : "NO",
|
|
1991
|
-
costBasis: cost,
|
|
1992
|
-
marketValue: value,
|
|
1993
|
-
unrealizedPnl: parseFloat(ammPos.unrealizedPnl),
|
|
1994
|
-
realizedPnl: parseFloat(ammPos.realizedPnl),
|
|
1995
|
-
currentPrice: ammPos.latestTrade ? parseFloat(ammPos.latestTrade.outcomeTokenPrice) : 0,
|
|
1996
|
-
avgPrice: parseFloat(ammPos.averageFillPrice),
|
|
1997
|
-
tokenBalance: parseFloat(ammPos.outcomeTokenAmount)
|
|
1998
|
-
});
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
this.logger.debug("Flattened positions", { count: positions.length });
|
|
2002
|
-
return positions;
|
|
1779
|
+
async signOrder(order) {
|
|
1780
|
+
return await this.orderSigner.signOrder(order, this.signingConfig);
|
|
2003
1781
|
}
|
|
2004
1782
|
/**
|
|
2005
|
-
*
|
|
1783
|
+
* Gets the wallet address.
|
|
2006
1784
|
*
|
|
2007
|
-
* @
|
|
2008
|
-
* @returns Portfolio summary with totals and statistics
|
|
1785
|
+
* @returns Ethereum address of the wallet
|
|
2009
1786
|
*
|
|
2010
1787
|
* @example
|
|
2011
1788
|
* ```typescript
|
|
2012
|
-
* const
|
|
2013
|
-
*
|
|
2014
|
-
*
|
|
2015
|
-
* console.log(`Total Portfolio Value: $${(summary.totalValue / 1e6).toFixed(2)}`);
|
|
2016
|
-
* console.log(`Total P&L: ${summary.totalUnrealizedPnlPercent.toFixed(2)}%`);
|
|
2017
|
-
* console.log(`CLOB Positions: ${summary.breakdown.clob.positions}`);
|
|
2018
|
-
* console.log(`AMM Positions: ${summary.breakdown.amm.positions}`);
|
|
1789
|
+
* const address = orderClient.walletAddress;
|
|
1790
|
+
* console.log(`Wallet: ${address}`);
|
|
2019
1791
|
* ```
|
|
2020
1792
|
*/
|
|
2021
|
-
|
|
2022
|
-
this.
|
|
2023
|
-
clobCount: response.clob?.length || 0,
|
|
2024
|
-
ammCount: response.amm?.length || 0
|
|
2025
|
-
});
|
|
2026
|
-
let totalValue = 0;
|
|
2027
|
-
let totalCostBasis = 0;
|
|
2028
|
-
let totalUnrealizedPnl = 0;
|
|
2029
|
-
let totalRealizedPnl = 0;
|
|
2030
|
-
let clobPositions = 0;
|
|
2031
|
-
let clobValue = 0;
|
|
2032
|
-
let clobPnl = 0;
|
|
2033
|
-
let ammPositions = 0;
|
|
2034
|
-
let ammValue = 0;
|
|
2035
|
-
let ammPnl = 0;
|
|
2036
|
-
for (const clobPos of response.clob || []) {
|
|
2037
|
-
const yesCost = parseFloat(clobPos.positions.yes.cost);
|
|
2038
|
-
const yesValue = parseFloat(clobPos.positions.yes.marketValue);
|
|
2039
|
-
const yesUnrealizedPnl = parseFloat(clobPos.positions.yes.unrealizedPnl);
|
|
2040
|
-
const yesRealizedPnl = parseFloat(clobPos.positions.yes.realisedPnl);
|
|
2041
|
-
if (yesCost > 0 || yesValue > 0) {
|
|
2042
|
-
clobPositions++;
|
|
2043
|
-
totalCostBasis += yesCost;
|
|
2044
|
-
totalValue += yesValue;
|
|
2045
|
-
totalUnrealizedPnl += yesUnrealizedPnl;
|
|
2046
|
-
totalRealizedPnl += yesRealizedPnl;
|
|
2047
|
-
clobValue += yesValue;
|
|
2048
|
-
clobPnl += yesUnrealizedPnl;
|
|
2049
|
-
}
|
|
2050
|
-
const noCost = parseFloat(clobPos.positions.no.cost);
|
|
2051
|
-
const noValue = parseFloat(clobPos.positions.no.marketValue);
|
|
2052
|
-
const noUnrealizedPnl = parseFloat(clobPos.positions.no.unrealizedPnl);
|
|
2053
|
-
const noRealizedPnl = parseFloat(clobPos.positions.no.realisedPnl);
|
|
2054
|
-
if (noCost > 0 || noValue > 0) {
|
|
2055
|
-
clobPositions++;
|
|
2056
|
-
totalCostBasis += noCost;
|
|
2057
|
-
totalValue += noValue;
|
|
2058
|
-
totalUnrealizedPnl += noUnrealizedPnl;
|
|
2059
|
-
totalRealizedPnl += noRealizedPnl;
|
|
2060
|
-
clobValue += noValue;
|
|
2061
|
-
clobPnl += noUnrealizedPnl;
|
|
2062
|
-
}
|
|
2063
|
-
}
|
|
2064
|
-
for (const ammPos of response.amm || []) {
|
|
2065
|
-
const cost = parseFloat(ammPos.totalBuysCost);
|
|
2066
|
-
const value = parseFloat(ammPos.collateralAmount);
|
|
2067
|
-
const unrealizedPnl = parseFloat(ammPos.unrealizedPnl);
|
|
2068
|
-
const realizedPnl = parseFloat(ammPos.realizedPnl);
|
|
2069
|
-
if (cost > 0 || value > 0) {
|
|
2070
|
-
ammPositions++;
|
|
2071
|
-
totalCostBasis += cost;
|
|
2072
|
-
totalValue += value;
|
|
2073
|
-
totalUnrealizedPnl += unrealizedPnl;
|
|
2074
|
-
totalRealizedPnl += realizedPnl;
|
|
2075
|
-
ammValue += value;
|
|
2076
|
-
ammPnl += unrealizedPnl;
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
|
-
const totalUnrealizedPnlPercent = totalCostBasis > 0 ? totalUnrealizedPnl / totalCostBasis * 100 : 0;
|
|
2080
|
-
const uniqueMarkets = /* @__PURE__ */ new Set();
|
|
2081
|
-
for (const pos of response.clob || []) {
|
|
2082
|
-
uniqueMarkets.add(pos.market.id);
|
|
2083
|
-
}
|
|
2084
|
-
for (const pos of response.amm || []) {
|
|
2085
|
-
uniqueMarkets.add(pos.market.id);
|
|
2086
|
-
}
|
|
2087
|
-
const summary = {
|
|
2088
|
-
totalValue,
|
|
2089
|
-
totalCostBasis,
|
|
2090
|
-
totalUnrealizedPnl,
|
|
2091
|
-
totalRealizedPnl,
|
|
2092
|
-
totalUnrealizedPnlPercent,
|
|
2093
|
-
positionCount: clobPositions + ammPositions,
|
|
2094
|
-
marketCount: uniqueMarkets.size,
|
|
2095
|
-
breakdown: {
|
|
2096
|
-
clob: {
|
|
2097
|
-
positions: clobPositions,
|
|
2098
|
-
value: clobValue,
|
|
2099
|
-
pnl: clobPnl
|
|
2100
|
-
},
|
|
2101
|
-
amm: {
|
|
2102
|
-
positions: ammPositions,
|
|
2103
|
-
value: ammValue,
|
|
2104
|
-
pnl: ammPnl
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
};
|
|
2108
|
-
this.logger.debug("Portfolio summary calculated", summary);
|
|
2109
|
-
return summary;
|
|
1793
|
+
get walletAddress() {
|
|
1794
|
+
return this.wallet.address;
|
|
2110
1795
|
}
|
|
2111
1796
|
/**
|
|
2112
|
-
* Gets
|
|
1797
|
+
* Gets the owner ID (user ID from profile).
|
|
2113
1798
|
*
|
|
2114
|
-
* @returns
|
|
2115
|
-
* @throws Error if API request fails or user is not authenticated
|
|
1799
|
+
* @returns Owner ID from user profile, or undefined if not yet loaded
|
|
2116
1800
|
*
|
|
2117
1801
|
* @example
|
|
2118
1802
|
* ```typescript
|
|
2119
|
-
* const
|
|
2120
|
-
*
|
|
2121
|
-
*
|
|
2122
|
-
*
|
|
2123
|
-
* console.log(` Total P&L: $${(summary.totalUnrealizedPnl / 1e6).toFixed(2)}`);
|
|
2124
|
-
* console.log(` P&L %: ${summary.totalUnrealizedPnlPercent.toFixed(2)}%`);
|
|
2125
|
-
* console.log(`\nCLOB Positions: ${response.clob.length}`);
|
|
2126
|
-
* console.log(`AMM Positions: ${response.amm.length}`);
|
|
1803
|
+
* const ownerId = orderClient.ownerId;
|
|
1804
|
+
* if (ownerId) {
|
|
1805
|
+
* console.log(`Owner ID: ${ownerId}`);
|
|
1806
|
+
* }
|
|
2127
1807
|
* ```
|
|
2128
1808
|
*/
|
|
2129
|
-
|
|
2130
|
-
this.
|
|
2131
|
-
const response = await this.getPositions();
|
|
2132
|
-
const summary = this.calculateSummary(response);
|
|
2133
|
-
this.logger.info("Portfolio fetched with summary", {
|
|
2134
|
-
positionCount: summary.positionCount,
|
|
2135
|
-
totalValueUSDC: summary.totalValue / 1e6,
|
|
2136
|
-
pnlPercent: summary.totalUnrealizedPnlPercent
|
|
2137
|
-
});
|
|
2138
|
-
return { response, summary };
|
|
1809
|
+
get ownerId() {
|
|
1810
|
+
return this.cachedUserData?.userId;
|
|
2139
1811
|
}
|
|
2140
1812
|
};
|
|
2141
1813
|
|
|
@@ -2156,7 +1828,7 @@ var WebSocketClient = class {
|
|
|
2156
1828
|
this.pendingListeners = [];
|
|
2157
1829
|
this.config = {
|
|
2158
1830
|
url: config.url || DEFAULT_WS_URL,
|
|
2159
|
-
|
|
1831
|
+
apiKey: config.apiKey || process.env.LIMITLESS_API_KEY || "",
|
|
2160
1832
|
autoReconnect: config.autoReconnect ?? true,
|
|
2161
1833
|
reconnectDelay: config.reconnectDelay || 1e3,
|
|
2162
1834
|
maxReconnectAttempts: config.maxReconnectAttempts || Infinity,
|
|
@@ -2181,18 +1853,29 @@ var WebSocketClient = class {
|
|
|
2181
1853
|
return this.state === "connected" /* CONNECTED */ && this.socket?.connected === true;
|
|
2182
1854
|
}
|
|
2183
1855
|
/**
|
|
2184
|
-
* Sets the
|
|
1856
|
+
* Sets the API key for authentication.
|
|
2185
1857
|
*
|
|
2186
|
-
* @param
|
|
1858
|
+
* @param apiKey - API key value
|
|
1859
|
+
*
|
|
1860
|
+
* @remarks
|
|
1861
|
+
* API key is required for authenticated subscriptions (positions, transactions).
|
|
1862
|
+
* If already connected, this will trigger a reconnection with the new API key.
|
|
2187
1863
|
*/
|
|
2188
|
-
|
|
2189
|
-
this.config.
|
|
1864
|
+
setApiKey(apiKey) {
|
|
1865
|
+
this.config.apiKey = apiKey;
|
|
2190
1866
|
if (this.socket?.connected) {
|
|
2191
|
-
this.logger.info("
|
|
2192
|
-
this.
|
|
2193
|
-
this.connect();
|
|
1867
|
+
this.logger.info("API key updated, reconnecting...");
|
|
1868
|
+
this.reconnectWithNewAuth();
|
|
2194
1869
|
}
|
|
2195
1870
|
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Reconnects with new authentication credentials.
|
|
1873
|
+
* @internal
|
|
1874
|
+
*/
|
|
1875
|
+
async reconnectWithNewAuth() {
|
|
1876
|
+
await this.disconnect();
|
|
1877
|
+
await this.connect();
|
|
1878
|
+
}
|
|
2196
1879
|
/**
|
|
2197
1880
|
* Connects to the WebSocket server.
|
|
2198
1881
|
*
|
|
@@ -2206,8 +1889,8 @@ var WebSocketClient = class {
|
|
|
2206
1889
|
* ```
|
|
2207
1890
|
*/
|
|
2208
1891
|
async connect() {
|
|
2209
|
-
if (this.socket?.connected) {
|
|
2210
|
-
this.logger.info("Already connected");
|
|
1892
|
+
if (this.socket?.connected || this.state === "connecting" /* CONNECTING */) {
|
|
1893
|
+
this.logger.info("Already connected or connecting");
|
|
2211
1894
|
return;
|
|
2212
1895
|
}
|
|
2213
1896
|
this.logger.info("Connecting to WebSocket", { url: this.config.url });
|
|
@@ -2216,18 +1899,26 @@ var WebSocketClient = class {
|
|
|
2216
1899
|
const timeout = setTimeout(() => {
|
|
2217
1900
|
reject(new Error(`Connection timeout after ${this.config.timeout}ms`));
|
|
2218
1901
|
}, this.config.timeout);
|
|
2219
|
-
const wsUrl = this.config.url
|
|
2220
|
-
|
|
1902
|
+
const wsUrl = this.config.url;
|
|
1903
|
+
const socketOptions = {
|
|
2221
1904
|
transports: ["websocket"],
|
|
2222
1905
|
// Use WebSocket transport only
|
|
2223
|
-
extraHeaders: {
|
|
2224
|
-
cookie: `limitless_session=${this.config.sessionCookie}`
|
|
2225
|
-
},
|
|
2226
1906
|
reconnection: this.config.autoReconnect,
|
|
2227
1907
|
reconnectionDelay: this.config.reconnectDelay,
|
|
2228
|
-
|
|
1908
|
+
reconnectionDelayMax: Math.min(this.config.reconnectDelay * 32, 6e4),
|
|
1909
|
+
// Max 60s
|
|
1910
|
+
reconnectionAttempts: this.config.maxReconnectAttempts === Infinity ? 0 : this.config.maxReconnectAttempts,
|
|
1911
|
+
// 0 = infinite
|
|
1912
|
+
randomizationFactor: 0.2,
|
|
1913
|
+
// Add jitter to prevent thundering herd
|
|
2229
1914
|
timeout: this.config.timeout
|
|
2230
|
-
}
|
|
1915
|
+
};
|
|
1916
|
+
if (this.config.apiKey) {
|
|
1917
|
+
socketOptions.extraHeaders = {
|
|
1918
|
+
"X-API-Key": this.config.apiKey
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
this.socket = io(wsUrl + "/markets", socketOptions);
|
|
2231
1922
|
this.attachPendingListeners();
|
|
2232
1923
|
this.setupEventHandlers();
|
|
2233
1924
|
this.socket.once("connect", () => {
|
|
@@ -2249,12 +1940,14 @@ var WebSocketClient = class {
|
|
|
2249
1940
|
/**
|
|
2250
1941
|
* Disconnects from the WebSocket server.
|
|
2251
1942
|
*
|
|
1943
|
+
* @returns Promise that resolves when disconnected
|
|
1944
|
+
*
|
|
2252
1945
|
* @example
|
|
2253
1946
|
* ```typescript
|
|
2254
|
-
* wsClient.disconnect();
|
|
1947
|
+
* await wsClient.disconnect();
|
|
2255
1948
|
* ```
|
|
2256
1949
|
*/
|
|
2257
|
-
disconnect() {
|
|
1950
|
+
async disconnect() {
|
|
2258
1951
|
if (!this.socket) {
|
|
2259
1952
|
return;
|
|
2260
1953
|
}
|
|
@@ -2269,13 +1962,13 @@ var WebSocketClient = class {
|
|
|
2269
1962
|
*
|
|
2270
1963
|
* @param channel - Channel to subscribe to
|
|
2271
1964
|
* @param options - Subscription options
|
|
2272
|
-
* @returns Promise that resolves
|
|
1965
|
+
* @returns Promise that resolves immediately (kept async for API compatibility)
|
|
2273
1966
|
* @throws Error if not connected
|
|
2274
1967
|
*
|
|
2275
1968
|
* @example
|
|
2276
1969
|
* ```typescript
|
|
2277
1970
|
* // Subscribe to orderbook for a specific market
|
|
2278
|
-
* await wsClient.subscribe('orderbook', {
|
|
1971
|
+
* await wsClient.subscribe('orderbook', { marketSlugs: ['market-123'] });
|
|
2279
1972
|
*
|
|
2280
1973
|
* // Subscribe to all trades
|
|
2281
1974
|
* await wsClient.subscribe('trades');
|
|
@@ -2288,21 +1981,20 @@ var WebSocketClient = class {
|
|
|
2288
1981
|
if (!this.isConnected()) {
|
|
2289
1982
|
throw new Error("WebSocket not connected. Call connect() first.");
|
|
2290
1983
|
}
|
|
1984
|
+
const authenticatedChannels = [
|
|
1985
|
+
"subscribe_positions",
|
|
1986
|
+
"subscribe_transactions"
|
|
1987
|
+
];
|
|
1988
|
+
if (authenticatedChannels.includes(channel) && !this.config.apiKey) {
|
|
1989
|
+
throw new Error(
|
|
1990
|
+
`API key is required for '${channel}' subscription. Please provide an API key in the constructor or set LIMITLESS_API_KEY environment variable. You can generate an API key at https://limitless.exchange`
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
2291
1993
|
const subscriptionKey = this.getSubscriptionKey(channel, options);
|
|
2292
1994
|
this.subscriptions.set(subscriptionKey, options);
|
|
2293
1995
|
this.logger.info("Subscribing to channel", { channel, options });
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
if (response?.error) {
|
|
2297
|
-
this.logger.error("Subscription failed", response.error);
|
|
2298
|
-
this.subscriptions.delete(subscriptionKey);
|
|
2299
|
-
reject(new Error(response.error));
|
|
2300
|
-
} else {
|
|
2301
|
-
this.logger.info("Subscribed successfully", { channel, options });
|
|
2302
|
-
resolve();
|
|
2303
|
-
}
|
|
2304
|
-
});
|
|
2305
|
-
});
|
|
1996
|
+
this.socket.emit(channel, options);
|
|
1997
|
+
this.logger.info("Subscription request sent", { channel, options });
|
|
2306
1998
|
}
|
|
2307
1999
|
/**
|
|
2308
2000
|
* Unsubscribes from a channel.
|
|
@@ -2310,10 +2002,11 @@ var WebSocketClient = class {
|
|
|
2310
2002
|
* @param channel - Channel to unsubscribe from
|
|
2311
2003
|
* @param options - Subscription options (must match subscribe call)
|
|
2312
2004
|
* @returns Promise that resolves when unsubscribed
|
|
2005
|
+
* @throws Error if not connected or unsubscribe fails
|
|
2313
2006
|
*
|
|
2314
2007
|
* @example
|
|
2315
2008
|
* ```typescript
|
|
2316
|
-
* await wsClient.unsubscribe('orderbook', {
|
|
2009
|
+
* await wsClient.unsubscribe('orderbook', { marketSlugs: ['market-123'] });
|
|
2317
2010
|
* ```
|
|
2318
2011
|
*/
|
|
2319
2012
|
async unsubscribe(channel, options = {}) {
|
|
@@ -2323,17 +2016,19 @@ var WebSocketClient = class {
|
|
|
2323
2016
|
const subscriptionKey = this.getSubscriptionKey(channel, options);
|
|
2324
2017
|
this.subscriptions.delete(subscriptionKey);
|
|
2325
2018
|
this.logger.info("Unsubscribing from channel", { channel, options });
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2019
|
+
try {
|
|
2020
|
+
const unsubscribeData = { channel, ...options };
|
|
2021
|
+
const response = await this.socket.timeout(5e3).emitWithAck("unsubscribe", unsubscribeData);
|
|
2022
|
+
if (response && typeof response === "object" && "error" in response) {
|
|
2023
|
+
const errorMsg = response.error;
|
|
2024
|
+
this.logger.error("Unsubscribe failed", new Error(errorMsg), { error: errorMsg });
|
|
2025
|
+
throw new Error(`Unsubscribe failed: ${errorMsg}`);
|
|
2026
|
+
}
|
|
2027
|
+
this.logger.info("Unsubscribed successfully", { channel, options });
|
|
2028
|
+
} catch (error) {
|
|
2029
|
+
this.logger.error("Unsubscribe error", error, { channel });
|
|
2030
|
+
throw error;
|
|
2031
|
+
}
|
|
2337
2032
|
}
|
|
2338
2033
|
/**
|
|
2339
2034
|
* Registers an event listener.
|
|
@@ -2376,14 +2071,27 @@ var WebSocketClient = class {
|
|
|
2376
2071
|
* Removes an event listener.
|
|
2377
2072
|
*
|
|
2378
2073
|
* @param event - Event name
|
|
2379
|
-
* @param handler - Event handler to remove
|
|
2074
|
+
* @param handler - Event handler to remove (if undefined, removes all handlers for event)
|
|
2380
2075
|
* @returns This client for chaining
|
|
2076
|
+
*
|
|
2077
|
+
* @example
|
|
2078
|
+
* ```typescript
|
|
2079
|
+
* // Remove specific handler
|
|
2080
|
+
* wsClient.off('orderbookUpdate', myHandler);
|
|
2081
|
+
*
|
|
2082
|
+
* // Remove all handlers for event
|
|
2083
|
+
* wsClient.off('orderbookUpdate');
|
|
2084
|
+
* ```
|
|
2381
2085
|
*/
|
|
2382
2086
|
off(event, handler) {
|
|
2383
2087
|
if (!this.socket) {
|
|
2384
2088
|
return this;
|
|
2385
2089
|
}
|
|
2386
|
-
|
|
2090
|
+
if (handler === void 0) {
|
|
2091
|
+
this.socket.removeAllListeners(event);
|
|
2092
|
+
} else {
|
|
2093
|
+
this.socket.off(event, handler);
|
|
2094
|
+
}
|
|
2387
2095
|
return this;
|
|
2388
2096
|
}
|
|
2389
2097
|
/**
|
|
@@ -2475,8 +2183,7 @@ var WebSocketClient = class {
|
|
|
2475
2183
|
};
|
|
2476
2184
|
export {
|
|
2477
2185
|
APIError,
|
|
2478
|
-
|
|
2479
|
-
Authenticator,
|
|
2186
|
+
AuthenticationError,
|
|
2480
2187
|
BASE_SEPOLIA_CHAIN_ID,
|
|
2481
2188
|
CONTRACT_ADDRESSES,
|
|
2482
2189
|
ConsoleLogger,
|
|
@@ -2484,15 +2191,16 @@ export {
|
|
|
2484
2191
|
DEFAULT_CHAIN_ID,
|
|
2485
2192
|
DEFAULT_WS_URL,
|
|
2486
2193
|
HttpClient,
|
|
2194
|
+
Market,
|
|
2487
2195
|
MarketFetcher,
|
|
2488
|
-
MarketType,
|
|
2489
|
-
MessageSigner,
|
|
2490
2196
|
NoOpLogger,
|
|
2491
2197
|
OrderBuilder,
|
|
2492
2198
|
OrderClient,
|
|
2493
2199
|
OrderSigner,
|
|
2494
2200
|
OrderType,
|
|
2201
|
+
OrderValidationError,
|
|
2495
2202
|
PortfolioFetcher,
|
|
2203
|
+
RateLimitError,
|
|
2496
2204
|
RetryConfig,
|
|
2497
2205
|
RetryableClient,
|
|
2498
2206
|
SIGNING_MESSAGE_TEMPLATE,
|