@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.js
CHANGED
|
@@ -31,8 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
APIError: () => APIError,
|
|
34
|
-
|
|
35
|
-
Authenticator: () => Authenticator,
|
|
34
|
+
AuthenticationError: () => AuthenticationError,
|
|
36
35
|
BASE_SEPOLIA_CHAIN_ID: () => BASE_SEPOLIA_CHAIN_ID,
|
|
37
36
|
CONTRACT_ADDRESSES: () => CONTRACT_ADDRESSES,
|
|
38
37
|
ConsoleLogger: () => ConsoleLogger,
|
|
@@ -40,15 +39,16 @@ __export(index_exports, {
|
|
|
40
39
|
DEFAULT_CHAIN_ID: () => DEFAULT_CHAIN_ID,
|
|
41
40
|
DEFAULT_WS_URL: () => DEFAULT_WS_URL,
|
|
42
41
|
HttpClient: () => HttpClient,
|
|
42
|
+
Market: () => Market,
|
|
43
43
|
MarketFetcher: () => MarketFetcher,
|
|
44
|
-
MarketType: () => MarketType,
|
|
45
|
-
MessageSigner: () => MessageSigner,
|
|
46
44
|
NoOpLogger: () => NoOpLogger,
|
|
47
45
|
OrderBuilder: () => OrderBuilder,
|
|
48
46
|
OrderClient: () => OrderClient,
|
|
49
47
|
OrderSigner: () => OrderSigner,
|
|
50
48
|
OrderType: () => OrderType,
|
|
49
|
+
OrderValidationError: () => OrderValidationError,
|
|
51
50
|
PortfolioFetcher: () => PortfolioFetcher,
|
|
51
|
+
RateLimitError: () => RateLimitError,
|
|
52
52
|
RetryConfig: () => RetryConfig,
|
|
53
53
|
RetryableClient: () => RetryableClient,
|
|
54
54
|
SIGNING_MESSAGE_TEMPLATE: () => SIGNING_MESSAGE_TEMPLATE,
|
|
@@ -115,16 +115,11 @@ var Side = /* @__PURE__ */ ((Side2) => {
|
|
|
115
115
|
Side2[Side2["SELL"] = 1] = "SELL";
|
|
116
116
|
return Side2;
|
|
117
117
|
})(Side || {});
|
|
118
|
-
var OrderType = /* @__PURE__ */ ((
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return
|
|
118
|
+
var OrderType = /* @__PURE__ */ ((OrderType2) => {
|
|
119
|
+
OrderType2["FOK"] = "FOK";
|
|
120
|
+
OrderType2["GTC"] = "GTC";
|
|
121
|
+
return OrderType2;
|
|
122
122
|
})(OrderType || {});
|
|
123
|
-
var MarketType = /* @__PURE__ */ ((MarketType3) => {
|
|
124
|
-
MarketType3["CLOB"] = "CLOB";
|
|
125
|
-
MarketType3["NEGRISK"] = "NEGRISK";
|
|
126
|
-
return MarketType3;
|
|
127
|
-
})(MarketType || {});
|
|
128
123
|
var SignatureType = /* @__PURE__ */ ((SignatureType2) => {
|
|
129
124
|
SignatureType2[SignatureType2["EOA"] = 0] = "EOA";
|
|
130
125
|
SignatureType2[SignatureType2["POLY_PROXY"] = 1] = "POLY_PROXY";
|
|
@@ -142,250 +137,92 @@ var WebSocketState = /* @__PURE__ */ ((WebSocketState2) => {
|
|
|
142
137
|
return WebSocketState2;
|
|
143
138
|
})(WebSocketState || {});
|
|
144
139
|
|
|
145
|
-
// src/
|
|
146
|
-
var
|
|
147
|
-
var MessageSigner = class {
|
|
148
|
-
/**
|
|
149
|
-
* Creates a new message signer instance.
|
|
150
|
-
*
|
|
151
|
-
* @param wallet - Ethers wallet instance for signing
|
|
152
|
-
*/
|
|
153
|
-
constructor(wallet) {
|
|
154
|
-
this.wallet = wallet;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Creates authentication headers for API requests.
|
|
158
|
-
*
|
|
159
|
-
* @param signingMessage - Message to sign from the API
|
|
160
|
-
* @returns Promise resolving to signature headers
|
|
161
|
-
*
|
|
162
|
-
* @example
|
|
163
|
-
* ```typescript
|
|
164
|
-
* const signer = new MessageSigner(wallet);
|
|
165
|
-
* const headers = await signer.createAuthHeaders(message);
|
|
166
|
-
* ```
|
|
167
|
-
*/
|
|
168
|
-
async createAuthHeaders(signingMessage) {
|
|
169
|
-
const hexMessage = this.stringToHex(signingMessage);
|
|
170
|
-
const signature = await this.wallet.signMessage(signingMessage);
|
|
171
|
-
const address = this.wallet.address;
|
|
172
|
-
const recoveredAddress = import_ethers.ethers.verifyMessage(signingMessage, signature);
|
|
173
|
-
if (address.toLowerCase() !== recoveredAddress.toLowerCase()) {
|
|
174
|
-
throw new Error("Signature verification failed: address mismatch");
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
"x-account": address,
|
|
178
|
-
"x-signing-message": hexMessage,
|
|
179
|
-
"x-signature": signature
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Signs EIP-712 typed data.
|
|
184
|
-
*
|
|
185
|
-
* @param domain - EIP-712 domain
|
|
186
|
-
* @param types - EIP-712 types
|
|
187
|
-
* @param value - Value to sign
|
|
188
|
-
* @returns Promise resolving to signature string
|
|
189
|
-
*
|
|
190
|
-
* @example
|
|
191
|
-
* ```typescript
|
|
192
|
-
* const signature = await signer.signTypedData(domain, types, order);
|
|
193
|
-
* ```
|
|
194
|
-
*/
|
|
195
|
-
async signTypedData(domain, types, value) {
|
|
196
|
-
return await this.wallet.signTypedData(domain, types, value);
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Gets the wallet address.
|
|
200
|
-
*
|
|
201
|
-
* @returns Ethereum address
|
|
202
|
-
*/
|
|
203
|
-
getAddress() {
|
|
204
|
-
return this.wallet.address;
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Converts a string to hex format.
|
|
208
|
-
*
|
|
209
|
-
* @param text - String to convert
|
|
210
|
-
* @returns Hex string with 0x prefix
|
|
211
|
-
* @internal
|
|
212
|
-
*/
|
|
213
|
-
stringToHex(text) {
|
|
214
|
-
return import_ethers.ethers.hexlify(import_ethers.ethers.toUtf8Bytes(text));
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
// src/auth/authenticator.ts
|
|
219
|
-
var Authenticator = class {
|
|
140
|
+
// src/types/market-class.ts
|
|
141
|
+
var Market = class _Market {
|
|
220
142
|
/**
|
|
221
|
-
* Creates a
|
|
222
|
-
*
|
|
223
|
-
* @param httpClient - HTTP client for API requests
|
|
224
|
-
* @param signer - Message signer for wallet operations
|
|
225
|
-
* @param logger - Optional logger for debugging and monitoring (default: no logging)
|
|
143
|
+
* Creates a Market instance.
|
|
226
144
|
*
|
|
227
|
-
* @
|
|
228
|
-
*
|
|
229
|
-
* // Without logging (default)
|
|
230
|
-
* const authenticator = new Authenticator(httpClient, signer);
|
|
231
|
-
*
|
|
232
|
-
* // With logging
|
|
233
|
-
* import { ConsoleLogger } from '@limitless-exchange/sdk';
|
|
234
|
-
* const logger = new ConsoleLogger('debug');
|
|
235
|
-
* const authenticator = new Authenticator(httpClient, signer, logger);
|
|
236
|
-
* ```
|
|
145
|
+
* @param data - Market data from API
|
|
146
|
+
* @param httpClient - HTTP client for making requests
|
|
237
147
|
*/
|
|
238
|
-
constructor(
|
|
148
|
+
constructor(data, httpClient) {
|
|
149
|
+
Object.assign(this, data);
|
|
239
150
|
this.httpClient = httpClient;
|
|
240
|
-
|
|
241
|
-
|
|
151
|
+
if (data.markets && Array.isArray(data.markets)) {
|
|
152
|
+
this.markets = data.markets.map((m) => new _Market(m, httpClient));
|
|
153
|
+
}
|
|
242
154
|
}
|
|
243
155
|
/**
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
* @returns Promise resolving to signing message string
|
|
247
|
-
* @throws Error if API request fails
|
|
156
|
+
* Get user's orders for this market.
|
|
248
157
|
*
|
|
249
|
-
* @
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* console.log(message);
|
|
253
|
-
* ```
|
|
254
|
-
*/
|
|
255
|
-
async getSigningMessage() {
|
|
256
|
-
this.logger.debug("Requesting signing message from API");
|
|
257
|
-
const message = await this.httpClient.get("/auth/signing-message");
|
|
258
|
-
this.logger.debug("Received signing message", { length: message.length });
|
|
259
|
-
return message;
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Authenticates with the API and obtains session cookie.
|
|
158
|
+
* @remarks
|
|
159
|
+
* Fetches all orders placed by the authenticated user for this specific market.
|
|
160
|
+
* Uses the http_client from the MarketFetcher that created this Market instance.
|
|
263
161
|
*
|
|
264
|
-
* @
|
|
265
|
-
* @
|
|
266
|
-
* @throws Error if authentication fails or smart wallet is required but not provided
|
|
162
|
+
* @returns Promise resolving to array of user orders
|
|
163
|
+
* @throws Error if market wasn't fetched via MarketFetcher
|
|
267
164
|
*
|
|
268
165
|
* @example
|
|
269
166
|
* ```typescript
|
|
270
|
-
* //
|
|
271
|
-
* const
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
* const result = await authenticator.authenticate({
|
|
275
|
-
* client: 'etherspot',
|
|
276
|
-
* smartWallet: '0x...'
|
|
277
|
-
* });
|
|
167
|
+
* // Clean fluent API
|
|
168
|
+
* const market = await marketFetcher.getMarket("bitcoin-2024");
|
|
169
|
+
* const orders = await market.getUserOrders();
|
|
170
|
+
* console.log(`You have ${orders.length} orders in ${market.title}`);
|
|
278
171
|
* ```
|
|
279
172
|
*/
|
|
280
|
-
async
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
hasSmartWallet: !!options.smartWallet
|
|
285
|
-
});
|
|
286
|
-
if (client === "etherspot" && !options.smartWallet) {
|
|
287
|
-
this.logger.error("Smart wallet address required for ETHERSPOT client");
|
|
288
|
-
throw new Error("Smart wallet address is required for ETHERSPOT client");
|
|
289
|
-
}
|
|
290
|
-
try {
|
|
291
|
-
const signingMessage = await this.getSigningMessage();
|
|
292
|
-
this.logger.debug("Creating signature headers");
|
|
293
|
-
const headers = await this.signer.createAuthHeaders(signingMessage);
|
|
294
|
-
this.logger.debug("Sending authentication request", { client });
|
|
295
|
-
const response = await this.httpClient.postWithResponse(
|
|
296
|
-
"/auth/login",
|
|
297
|
-
{ client, smartWallet: options.smartWallet },
|
|
298
|
-
{
|
|
299
|
-
headers,
|
|
300
|
-
validateStatus: (status) => status < 500
|
|
301
|
-
}
|
|
173
|
+
async getUserOrders() {
|
|
174
|
+
if (!this.httpClient) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
"This Market instance has no httpClient attached. Make sure to fetch the market via MarketFetcher.getMarket() to use this method."
|
|
302
177
|
);
|
|
303
|
-
this.logger.debug("Extracting session cookie from response");
|
|
304
|
-
const cookies = this.httpClient.extractCookies(response);
|
|
305
|
-
const sessionCookie = cookies["limitless_session"];
|
|
306
|
-
if (!sessionCookie) {
|
|
307
|
-
this.logger.error("Session cookie not found in response headers");
|
|
308
|
-
throw new Error("Failed to obtain session cookie from response");
|
|
309
|
-
}
|
|
310
|
-
this.httpClient.setSessionCookie(sessionCookie);
|
|
311
|
-
this.logger.info("Authentication successful", {
|
|
312
|
-
account: response.data.account,
|
|
313
|
-
client: response.data.client
|
|
314
|
-
});
|
|
315
|
-
return {
|
|
316
|
-
sessionCookie,
|
|
317
|
-
profile: response.data
|
|
318
|
-
};
|
|
319
|
-
} catch (error) {
|
|
320
|
-
this.logger.error("Authentication failed", error, {
|
|
321
|
-
client
|
|
322
|
-
});
|
|
323
|
-
throw error;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Verifies the current authentication status.
|
|
328
|
-
*
|
|
329
|
-
* @param sessionCookie - Session cookie to verify
|
|
330
|
-
* @returns Promise resolving to user's Ethereum address
|
|
331
|
-
* @throws Error if session is invalid
|
|
332
|
-
*
|
|
333
|
-
* @example
|
|
334
|
-
* ```typescript
|
|
335
|
-
* const address = await authenticator.verifyAuth(sessionCookie);
|
|
336
|
-
* console.log(`Authenticated as: ${address}`);
|
|
337
|
-
* ```
|
|
338
|
-
*/
|
|
339
|
-
async verifyAuth(sessionCookie) {
|
|
340
|
-
this.logger.debug("Verifying authentication session");
|
|
341
|
-
const originalCookie = this.httpClient["sessionCookie"];
|
|
342
|
-
this.httpClient.setSessionCookie(sessionCookie);
|
|
343
|
-
try {
|
|
344
|
-
const address = await this.httpClient.get("/auth/verify-auth");
|
|
345
|
-
this.logger.info("Session verified", { address });
|
|
346
|
-
return address;
|
|
347
|
-
} catch (error) {
|
|
348
|
-
this.logger.error("Session verification failed", error);
|
|
349
|
-
throw error;
|
|
350
|
-
} finally {
|
|
351
|
-
if (originalCookie) {
|
|
352
|
-
this.httpClient.setSessionCookie(originalCookie);
|
|
353
|
-
} else {
|
|
354
|
-
this.httpClient.clearSessionCookie();
|
|
355
|
-
}
|
|
356
178
|
}
|
|
179
|
+
const response = await this.httpClient.get(`/markets/${this.slug}/user-orders`);
|
|
180
|
+
const orders = Array.isArray(response) ? response : response.orders || [];
|
|
181
|
+
return orders;
|
|
357
182
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
this.httpClient.clearSessionCookie();
|
|
385
|
-
}
|
|
386
|
-
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// src/api/http.ts
|
|
186
|
+
var import_axios = __toESM(require("axios"));
|
|
187
|
+
var import_http = __toESM(require("http"));
|
|
188
|
+
var import_https = __toESM(require("https"));
|
|
189
|
+
|
|
190
|
+
// src/utils/constants.ts
|
|
191
|
+
var DEFAULT_API_URL = "https://api.limitless.exchange";
|
|
192
|
+
var DEFAULT_WS_URL = "wss://ws.limitless.exchange";
|
|
193
|
+
var DEFAULT_CHAIN_ID = 8453;
|
|
194
|
+
var BASE_SEPOLIA_CHAIN_ID = 84532;
|
|
195
|
+
var SIGNING_MESSAGE_TEMPLATE = "Welcome to Limitless.exchange! Please sign this message to verify your identity.\n\nNonce: {NONCE}";
|
|
196
|
+
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
197
|
+
var CONTRACT_ADDRESSES = {
|
|
198
|
+
// Base mainnet (chainId: 8453)
|
|
199
|
+
8453: {
|
|
200
|
+
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
201
|
+
// Native USDC on Base
|
|
202
|
+
CTF: "0xC9c98965297Bc527861c898329Ee280632B76e18"
|
|
203
|
+
// Conditional Token Framework
|
|
204
|
+
},
|
|
205
|
+
// Base Sepolia testnet (chainId: 84532)
|
|
206
|
+
84532: {
|
|
207
|
+
USDC: "0x...",
|
|
208
|
+
CTF: "0x..."
|
|
387
209
|
}
|
|
388
210
|
};
|
|
211
|
+
function getContractAddress(contractType, chainId = DEFAULT_CHAIN_ID) {
|
|
212
|
+
const addresses = CONTRACT_ADDRESSES[chainId];
|
|
213
|
+
if (!addresses) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`No contract addresses configured for chainId ${chainId}. Supported chains: ${Object.keys(CONTRACT_ADDRESSES).join(", ")}`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
const address = addresses[contractType];
|
|
219
|
+
if (!address || address === "0x...") {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Contract address for ${contractType} not available on chainId ${chainId}. Please configure the address in constants.ts or use environment variables.`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
return address;
|
|
225
|
+
}
|
|
389
226
|
|
|
390
227
|
// src/api/errors.ts
|
|
391
228
|
var APIError = class _APIError extends Error {
|
|
@@ -431,111 +268,33 @@ var APIError = class _APIError extends Error {
|
|
|
431
268
|
return this.status === 401 || this.status === 403;
|
|
432
269
|
}
|
|
433
270
|
};
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
* @param config - Configuration for authenticated client
|
|
441
|
-
*/
|
|
442
|
-
constructor(config) {
|
|
443
|
-
this.httpClient = config.httpClient;
|
|
444
|
-
this.authenticator = config.authenticator;
|
|
445
|
-
this.client = config.client;
|
|
446
|
-
this.smartWallet = config.smartWallet;
|
|
447
|
-
this.logger = config.logger || new NoOpLogger();
|
|
448
|
-
this.maxRetries = config.maxRetries ?? 1;
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* Executes a function with automatic retry on authentication errors.
|
|
452
|
-
*
|
|
453
|
-
* @param fn - Function to execute with auth retry
|
|
454
|
-
* @returns Promise resolving to the function result
|
|
455
|
-
* @throws Error if max retries exceeded or non-auth error occurs
|
|
456
|
-
*
|
|
457
|
-
* @example
|
|
458
|
-
* ```typescript
|
|
459
|
-
* // Automatic retry on 401/403
|
|
460
|
-
* const positions = await authClient.withRetry(() =>
|
|
461
|
-
* portfolioFetcher.getPositions()
|
|
462
|
-
* );
|
|
463
|
-
*
|
|
464
|
-
* // Works with any async operation
|
|
465
|
-
* const order = await authClient.withRetry(() =>
|
|
466
|
-
* orderClient.createOrder({ ... })
|
|
467
|
-
* );
|
|
468
|
-
* ```
|
|
469
|
-
*/
|
|
470
|
-
async withRetry(fn) {
|
|
471
|
-
let attempts = 0;
|
|
472
|
-
while (attempts <= this.maxRetries) {
|
|
473
|
-
try {
|
|
474
|
-
return await fn();
|
|
475
|
-
} catch (error) {
|
|
476
|
-
if (error instanceof APIError && error.isAuthError() && attempts < this.maxRetries) {
|
|
477
|
-
this.logger.info("Authentication expired, re-authenticating...", {
|
|
478
|
-
attempt: attempts + 1,
|
|
479
|
-
maxRetries: this.maxRetries
|
|
480
|
-
});
|
|
481
|
-
await this.reauthenticate();
|
|
482
|
-
attempts++;
|
|
483
|
-
continue;
|
|
484
|
-
}
|
|
485
|
-
throw error;
|
|
486
|
-
}
|
|
271
|
+
var RateLimitError = class _RateLimitError extends APIError {
|
|
272
|
+
constructor(message = "Rate limit exceeded", status = 429, data = null, url, method) {
|
|
273
|
+
super(message, status, data, url, method);
|
|
274
|
+
this.name = "RateLimitError";
|
|
275
|
+
if (Error.captureStackTrace) {
|
|
276
|
+
Error.captureStackTrace(this, _RateLimitError);
|
|
487
277
|
}
|
|
488
|
-
throw new Error("Unexpected error: exceeded max retries");
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Re-authenticates with the API.
|
|
492
|
-
*
|
|
493
|
-
* @internal
|
|
494
|
-
*/
|
|
495
|
-
async reauthenticate() {
|
|
496
|
-
this.logger.debug("Re-authenticating with API");
|
|
497
|
-
await this.authenticator.authenticate({
|
|
498
|
-
client: this.client,
|
|
499
|
-
smartWallet: this.smartWallet
|
|
500
|
-
});
|
|
501
|
-
this.logger.info("Re-authentication successful");
|
|
502
278
|
}
|
|
503
279
|
};
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
var DEFAULT_API_URL = "https://api.limitless.exchange";
|
|
512
|
-
var DEFAULT_WS_URL = "wss://ws.limitless.exchange";
|
|
513
|
-
var DEFAULT_CHAIN_ID = 8453;
|
|
514
|
-
var BASE_SEPOLIA_CHAIN_ID = 84532;
|
|
515
|
-
var SIGNING_MESSAGE_TEMPLATE = "Welcome to Limitless.exchange! Please sign this message to verify your identity.\n\nNonce: {NONCE}";
|
|
516
|
-
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
517
|
-
var CONTRACT_ADDRESSES = {
|
|
518
|
-
// Base mainnet (chainId: 8453)
|
|
519
|
-
8453: {
|
|
520
|
-
CLOB: "0xa4409D988CA2218d956BeEFD3874100F444f0DC3",
|
|
521
|
-
NEGRISK: "0x5a38afc17F7E97ad8d6C547ddb837E40B4aEDfC6"
|
|
522
|
-
},
|
|
523
|
-
// Base Sepolia testnet (chainId: 84532)
|
|
524
|
-
84532: {
|
|
525
|
-
CLOB: "0x...",
|
|
526
|
-
// Add testnet addresses when available
|
|
527
|
-
NEGRISK: "0x..."
|
|
280
|
+
var AuthenticationError = class _AuthenticationError extends APIError {
|
|
281
|
+
constructor(message = "Authentication failed", status = 401, data = null, url, method) {
|
|
282
|
+
super(message, status, data, url, method);
|
|
283
|
+
this.name = "AuthenticationError";
|
|
284
|
+
if (Error.captureStackTrace) {
|
|
285
|
+
Error.captureStackTrace(this, _AuthenticationError);
|
|
286
|
+
}
|
|
528
287
|
}
|
|
529
288
|
};
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
289
|
+
var ValidationError = class _ValidationError extends APIError {
|
|
290
|
+
constructor(message, status = 400, data = null, url, method) {
|
|
291
|
+
super(message, status, data, url, method);
|
|
292
|
+
this.name = "ValidationError";
|
|
293
|
+
if (Error.captureStackTrace) {
|
|
294
|
+
Error.captureStackTrace(this, _ValidationError);
|
|
295
|
+
}
|
|
536
296
|
}
|
|
537
|
-
|
|
538
|
-
}
|
|
297
|
+
};
|
|
539
298
|
|
|
540
299
|
// src/api/http.ts
|
|
541
300
|
var HttpClient = class {
|
|
@@ -545,8 +304,13 @@ var HttpClient = class {
|
|
|
545
304
|
* @param config - Configuration options for the HTTP client
|
|
546
305
|
*/
|
|
547
306
|
constructor(config = {}) {
|
|
548
|
-
this.
|
|
307
|
+
this.apiKey = config.apiKey || process.env.LIMITLESS_API_KEY;
|
|
549
308
|
this.logger = config.logger || new NoOpLogger();
|
|
309
|
+
if (!this.apiKey) {
|
|
310
|
+
this.logger.warn(
|
|
311
|
+
"API key not set. Authenticated endpoints will fail. Set LIMITLESS_API_KEY environment variable or pass apiKey parameter."
|
|
312
|
+
);
|
|
313
|
+
}
|
|
550
314
|
const keepAlive = config.keepAlive !== false;
|
|
551
315
|
const maxSockets = config.maxSockets || 50;
|
|
552
316
|
const maxFreeSockets = config.maxFreeSockets || 10;
|
|
@@ -589,13 +353,17 @@ var HttpClient = class {
|
|
|
589
353
|
setupInterceptors() {
|
|
590
354
|
this.client.interceptors.request.use(
|
|
591
355
|
(config) => {
|
|
592
|
-
if (this.
|
|
593
|
-
config.headers["
|
|
356
|
+
if (this.apiKey) {
|
|
357
|
+
config.headers["X-API-Key"] = this.apiKey;
|
|
594
358
|
}
|
|
595
359
|
const fullUrl = `${config.baseURL || ""}${config.url || ""}`;
|
|
596
360
|
const method = config.method?.toUpperCase() || "GET";
|
|
361
|
+
const logHeaders = { ...config.headers };
|
|
362
|
+
if (logHeaders["X-API-Key"]) {
|
|
363
|
+
logHeaders["X-API-Key"] = "***";
|
|
364
|
+
}
|
|
597
365
|
this.logger.debug(`\u2192 ${method} ${fullUrl}`, {
|
|
598
|
-
headers:
|
|
366
|
+
headers: logHeaders,
|
|
599
367
|
body: config.data
|
|
600
368
|
});
|
|
601
369
|
return config;
|
|
@@ -636,7 +404,15 @@ var HttpClient = class {
|
|
|
636
404
|
message = String(data);
|
|
637
405
|
}
|
|
638
406
|
}
|
|
639
|
-
|
|
407
|
+
if (status === 429) {
|
|
408
|
+
throw new RateLimitError(message, status, data, url, method);
|
|
409
|
+
} else if (status === 401 || status === 403) {
|
|
410
|
+
throw new AuthenticationError(message, status, data, url, method);
|
|
411
|
+
} else if (status === 400) {
|
|
412
|
+
throw new ValidationError(message, status, data, url, method);
|
|
413
|
+
} else {
|
|
414
|
+
throw new APIError(message, status, data, url, method);
|
|
415
|
+
}
|
|
640
416
|
} else if (error.request) {
|
|
641
417
|
throw new Error("No response received from API");
|
|
642
418
|
} else {
|
|
@@ -646,18 +422,18 @@ var HttpClient = class {
|
|
|
646
422
|
);
|
|
647
423
|
}
|
|
648
424
|
/**
|
|
649
|
-
* Sets the
|
|
425
|
+
* Sets the API key for authenticated requests.
|
|
650
426
|
*
|
|
651
|
-
* @param
|
|
427
|
+
* @param apiKey - API key value
|
|
652
428
|
*/
|
|
653
|
-
|
|
654
|
-
this.
|
|
429
|
+
setApiKey(apiKey) {
|
|
430
|
+
this.apiKey = apiKey;
|
|
655
431
|
}
|
|
656
432
|
/**
|
|
657
|
-
* Clears the
|
|
433
|
+
* Clears the API key.
|
|
658
434
|
*/
|
|
659
|
-
|
|
660
|
-
this.
|
|
435
|
+
clearApiKey() {
|
|
436
|
+
this.apiKey = void 0;
|
|
661
437
|
}
|
|
662
438
|
/**
|
|
663
439
|
* Performs a GET request.
|
|
@@ -682,19 +458,6 @@ var HttpClient = class {
|
|
|
682
458
|
const response = await this.client.post(url, data, config);
|
|
683
459
|
return response.data;
|
|
684
460
|
}
|
|
685
|
-
/**
|
|
686
|
-
* Performs a POST request and returns the full response object.
|
|
687
|
-
* Useful when you need access to response headers (e.g., for cookie extraction).
|
|
688
|
-
*
|
|
689
|
-
* @param url - Request URL
|
|
690
|
-
* @param data - Request body data
|
|
691
|
-
* @param config - Additional request configuration
|
|
692
|
-
* @returns Promise resolving to the full AxiosResponse object
|
|
693
|
-
* @internal
|
|
694
|
-
*/
|
|
695
|
-
async postWithResponse(url, data, config) {
|
|
696
|
-
return await this.client.post(url, data, config);
|
|
697
|
-
}
|
|
698
461
|
/**
|
|
699
462
|
* Performs a DELETE request.
|
|
700
463
|
*
|
|
@@ -717,26 +480,6 @@ var HttpClient = class {
|
|
|
717
480
|
const response = await this.client.delete(url, deleteConfig);
|
|
718
481
|
return response.data;
|
|
719
482
|
}
|
|
720
|
-
/**
|
|
721
|
-
* Extracts cookies from response headers.
|
|
722
|
-
*
|
|
723
|
-
* @param response - Axios response object
|
|
724
|
-
* @returns Object containing parsed cookies
|
|
725
|
-
* @internal
|
|
726
|
-
*/
|
|
727
|
-
extractCookies(response) {
|
|
728
|
-
const setCookie = response.headers["set-cookie"];
|
|
729
|
-
if (!setCookie) return {};
|
|
730
|
-
const cookies = {};
|
|
731
|
-
const cookieStrings = Array.isArray(setCookie) ? setCookie : [setCookie];
|
|
732
|
-
for (const cookieString of cookieStrings) {
|
|
733
|
-
const parts = cookieString.split(";")[0].split("=");
|
|
734
|
-
if (parts.length === 2) {
|
|
735
|
-
cookies[parts[0].trim()] = parts[1].trim();
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
return cookies;
|
|
739
|
-
}
|
|
740
483
|
};
|
|
741
484
|
|
|
742
485
|
// src/api/retry.ts
|
|
@@ -921,7 +664,7 @@ var RetryableClient = class {
|
|
|
921
664
|
};
|
|
922
665
|
|
|
923
666
|
// src/orders/builder.ts
|
|
924
|
-
var
|
|
667
|
+
var import_ethers = require("ethers");
|
|
925
668
|
var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
|
|
926
669
|
var DEFAULT_PRICE_TICK = 1e-3;
|
|
927
670
|
var OrderBuilder = class {
|
|
@@ -960,8 +703,7 @@ var OrderBuilder = class {
|
|
|
960
703
|
* const fokOrder = builder.buildOrder({
|
|
961
704
|
* tokenId: '123456',
|
|
962
705
|
* makerAmount: 50, // 50 USDC to spend
|
|
963
|
-
* side: Side.BUY
|
|
964
|
-
* marketType: MarketType.CLOB
|
|
706
|
+
* side: Side.BUY
|
|
965
707
|
* });
|
|
966
708
|
*
|
|
967
709
|
* // GTC order (price + size)
|
|
@@ -969,8 +711,7 @@ var OrderBuilder = class {
|
|
|
969
711
|
* tokenId: '123456',
|
|
970
712
|
* price: 0.38,
|
|
971
713
|
* size: 22.123, // Will be rounded to tick-aligned: 22.123 shares
|
|
972
|
-
* side: Side.BUY
|
|
973
|
-
* marketType: MarketType.CLOB
|
|
714
|
+
* side: Side.BUY
|
|
974
715
|
* });
|
|
975
716
|
* ```
|
|
976
717
|
*/
|
|
@@ -1005,7 +746,7 @@ var OrderBuilder = class {
|
|
|
1005
746
|
* @internal
|
|
1006
747
|
*/
|
|
1007
748
|
isFOKOrder(args) {
|
|
1008
|
-
return "
|
|
749
|
+
return "makerAmount" in args;
|
|
1009
750
|
}
|
|
1010
751
|
/**
|
|
1011
752
|
* Generates a unique salt using timestamp + nano-offset pattern.
|
|
@@ -1159,7 +900,7 @@ var OrderBuilder = class {
|
|
|
1159
900
|
}
|
|
1160
901
|
}
|
|
1161
902
|
const amountFormatted = makerAmount.toFixed(DECIMALS);
|
|
1162
|
-
const amountScaled =
|
|
903
|
+
const amountScaled = import_ethers.ethers.parseUnits(amountFormatted, DECIMALS);
|
|
1163
904
|
const collateralAmount = Number(amountScaled);
|
|
1164
905
|
return {
|
|
1165
906
|
makerAmount: collateralAmount,
|
|
@@ -1179,7 +920,7 @@ var OrderBuilder = class {
|
|
|
1179
920
|
if (!args.tokenId || args.tokenId === "0") {
|
|
1180
921
|
throw new Error("Invalid tokenId: tokenId is required.");
|
|
1181
922
|
}
|
|
1182
|
-
if (args.taker && !
|
|
923
|
+
if (args.taker && !import_ethers.ethers.isAddress(args.taker)) {
|
|
1183
924
|
throw new Error(`Invalid taker address: ${args.taker}`);
|
|
1184
925
|
}
|
|
1185
926
|
if (this.isFOKOrder(args)) {
|
|
@@ -1190,7 +931,7 @@ var OrderBuilder = class {
|
|
|
1190
931
|
throw new Error(`Invalid makerAmount: ${args.makerAmount}. Maker amount must be positive.`);
|
|
1191
932
|
}
|
|
1192
933
|
} else {
|
|
1193
|
-
if (args.price < 0 || args.price > 1) {
|
|
934
|
+
if (args.price == null || args.price < 0 || args.price > 1) {
|
|
1194
935
|
throw new Error(`Invalid price: ${args.price}. Price must be between 0 and 1.`);
|
|
1195
936
|
}
|
|
1196
937
|
if (args.size <= 0) {
|
|
@@ -1245,8 +986,7 @@ var OrderSigner = class {
|
|
|
1245
986
|
* ```typescript
|
|
1246
987
|
* const signature = await signer.signOrder(unsignedOrder, {
|
|
1247
988
|
* chainId: 8453,
|
|
1248
|
-
* contractAddress: '0x...'
|
|
1249
|
-
* marketType: MarketType.CLOB
|
|
989
|
+
* contractAddress: '0x...'
|
|
1250
990
|
* });
|
|
1251
991
|
* ```
|
|
1252
992
|
*/
|
|
@@ -1254,7 +994,7 @@ var OrderSigner = class {
|
|
|
1254
994
|
this.logger.debug("Signing order with EIP-712", {
|
|
1255
995
|
tokenId: order.tokenId,
|
|
1256
996
|
side: order.side,
|
|
1257
|
-
|
|
997
|
+
verifyingContract: config.contractAddress
|
|
1258
998
|
});
|
|
1259
999
|
const walletAddress = await this.wallet.getAddress();
|
|
1260
1000
|
if (walletAddress.toLowerCase() !== order.signer.toLowerCase()) {
|
|
@@ -1280,11 +1020,11 @@ var OrderSigner = class {
|
|
|
1280
1020
|
signatureType: order.signatureType
|
|
1281
1021
|
};
|
|
1282
1022
|
this.logger.debug("EIP-712 Order Value", orderValue);
|
|
1283
|
-
|
|
1023
|
+
this.logger.debug("Full signing payload", {
|
|
1284
1024
|
domain,
|
|
1285
1025
|
types: this.getTypes(),
|
|
1286
1026
|
value: orderValue
|
|
1287
|
-
}
|
|
1027
|
+
});
|
|
1288
1028
|
try {
|
|
1289
1029
|
const signature = await this.wallet.signTypedData(domain, types, orderValue);
|
|
1290
1030
|
this.logger.info("Successfully generated EIP-712 signature", {
|
|
@@ -1344,11 +1084,11 @@ var OrderSigner = class {
|
|
|
1344
1084
|
};
|
|
1345
1085
|
|
|
1346
1086
|
// src/orders/validator.ts
|
|
1347
|
-
var
|
|
1348
|
-
var
|
|
1087
|
+
var import_ethers2 = require("ethers");
|
|
1088
|
+
var OrderValidationError = class extends Error {
|
|
1349
1089
|
constructor(message) {
|
|
1350
1090
|
super(message);
|
|
1351
|
-
this.name = "
|
|
1091
|
+
this.name = "OrderValidationError";
|
|
1352
1092
|
}
|
|
1353
1093
|
};
|
|
1354
1094
|
function isFOKOrder(args) {
|
|
@@ -1356,418 +1096,149 @@ function isFOKOrder(args) {
|
|
|
1356
1096
|
}
|
|
1357
1097
|
function validateOrderArgs(args) {
|
|
1358
1098
|
if (!args.tokenId) {
|
|
1359
|
-
throw new
|
|
1099
|
+
throw new OrderValidationError("TokenId is required");
|
|
1360
1100
|
}
|
|
1361
1101
|
if (args.tokenId === "0") {
|
|
1362
|
-
throw new
|
|
1102
|
+
throw new OrderValidationError("TokenId cannot be zero");
|
|
1363
1103
|
}
|
|
1364
1104
|
if (!/^\d+$/.test(args.tokenId)) {
|
|
1365
|
-
throw new
|
|
1105
|
+
throw new OrderValidationError(`Invalid tokenId format: ${args.tokenId}`);
|
|
1366
1106
|
}
|
|
1367
|
-
if (args.taker && !
|
|
1368
|
-
throw new
|
|
1107
|
+
if (args.taker && !import_ethers2.ethers.isAddress(args.taker)) {
|
|
1108
|
+
throw new OrderValidationError(`Invalid taker address: ${args.taker}`);
|
|
1369
1109
|
}
|
|
1370
1110
|
if (args.expiration !== void 0) {
|
|
1371
1111
|
if (!/^\d+$/.test(args.expiration)) {
|
|
1372
|
-
throw new
|
|
1112
|
+
throw new OrderValidationError(`Invalid expiration format: ${args.expiration}`);
|
|
1373
1113
|
}
|
|
1374
1114
|
}
|
|
1375
1115
|
if (args.nonce !== void 0) {
|
|
1376
1116
|
if (!Number.isInteger(args.nonce) || args.nonce < 0) {
|
|
1377
|
-
throw new
|
|
1117
|
+
throw new OrderValidationError(`Invalid nonce: ${args.nonce}`);
|
|
1378
1118
|
}
|
|
1379
1119
|
}
|
|
1380
1120
|
if (isFOKOrder(args)) {
|
|
1381
1121
|
if (typeof args.makerAmount !== "number" || isNaN(args.makerAmount)) {
|
|
1382
|
-
throw new
|
|
1122
|
+
throw new OrderValidationError("Amount must be a valid number");
|
|
1383
1123
|
}
|
|
1384
1124
|
if (args.makerAmount <= 0) {
|
|
1385
|
-
throw new
|
|
1125
|
+
throw new OrderValidationError(`Amount must be positive, got: ${args.makerAmount}`);
|
|
1386
1126
|
}
|
|
1387
1127
|
const amountStr = args.makerAmount.toString();
|
|
1388
1128
|
const decimalIndex = amountStr.indexOf(".");
|
|
1389
1129
|
if (decimalIndex !== -1) {
|
|
1390
1130
|
const decimalPlaces = amountStr.length - decimalIndex - 1;
|
|
1391
1131
|
if (decimalPlaces > 2) {
|
|
1392
|
-
throw new
|
|
1132
|
+
throw new OrderValidationError(
|
|
1393
1133
|
`Amount must have max 2 decimal places, got: ${args.makerAmount} (${decimalPlaces} decimals)`
|
|
1394
1134
|
);
|
|
1395
1135
|
}
|
|
1396
1136
|
}
|
|
1397
1137
|
} else {
|
|
1398
1138
|
if (typeof args.price !== "number" || isNaN(args.price)) {
|
|
1399
|
-
throw new
|
|
1139
|
+
throw new OrderValidationError("Price must be a valid number");
|
|
1400
1140
|
}
|
|
1401
1141
|
if (args.price < 0 || args.price > 1) {
|
|
1402
|
-
throw new
|
|
1142
|
+
throw new OrderValidationError(`Price must be between 0 and 1, got: ${args.price}`);
|
|
1403
1143
|
}
|
|
1404
1144
|
if (typeof args.size !== "number" || isNaN(args.size)) {
|
|
1405
|
-
throw new
|
|
1145
|
+
throw new OrderValidationError("Size must be a valid number");
|
|
1406
1146
|
}
|
|
1407
1147
|
if (args.size <= 0) {
|
|
1408
|
-
throw new
|
|
1148
|
+
throw new OrderValidationError(`Size must be positive, got: ${args.size}`);
|
|
1409
1149
|
}
|
|
1410
1150
|
}
|
|
1411
1151
|
}
|
|
1412
1152
|
function validateUnsignedOrder(order) {
|
|
1413
|
-
if (!
|
|
1414
|
-
throw new
|
|
1153
|
+
if (!import_ethers2.ethers.isAddress(order.maker)) {
|
|
1154
|
+
throw new OrderValidationError(`Invalid maker address: ${order.maker}`);
|
|
1415
1155
|
}
|
|
1416
|
-
if (!
|
|
1417
|
-
throw new
|
|
1156
|
+
if (!import_ethers2.ethers.isAddress(order.signer)) {
|
|
1157
|
+
throw new OrderValidationError(`Invalid signer address: ${order.signer}`);
|
|
1418
1158
|
}
|
|
1419
|
-
if (!
|
|
1420
|
-
throw new
|
|
1159
|
+
if (!import_ethers2.ethers.isAddress(order.taker)) {
|
|
1160
|
+
throw new OrderValidationError(`Invalid taker address: ${order.taker}`);
|
|
1421
1161
|
}
|
|
1422
1162
|
if (!order.makerAmount || order.makerAmount === 0) {
|
|
1423
|
-
throw new
|
|
1163
|
+
throw new OrderValidationError("MakerAmount must be greater than zero");
|
|
1424
1164
|
}
|
|
1425
1165
|
if (!order.takerAmount || order.takerAmount === 0) {
|
|
1426
|
-
throw new
|
|
1166
|
+
throw new OrderValidationError("TakerAmount must be greater than zero");
|
|
1427
1167
|
}
|
|
1428
1168
|
if (typeof order.makerAmount !== "number" || order.makerAmount <= 0) {
|
|
1429
|
-
throw new
|
|
1169
|
+
throw new OrderValidationError(`Invalid makerAmount: ${order.makerAmount}`);
|
|
1430
1170
|
}
|
|
1431
1171
|
if (typeof order.takerAmount !== "number" || order.takerAmount <= 0) {
|
|
1432
|
-
throw new
|
|
1172
|
+
throw new OrderValidationError(`Invalid takerAmount: ${order.takerAmount}`);
|
|
1433
1173
|
}
|
|
1434
1174
|
if (!/^\d+$/.test(order.tokenId)) {
|
|
1435
|
-
throw new
|
|
1175
|
+
throw new OrderValidationError(`Invalid tokenId format: ${order.tokenId}`);
|
|
1436
1176
|
}
|
|
1437
1177
|
if (!/^\d+$/.test(order.expiration)) {
|
|
1438
|
-
throw new
|
|
1178
|
+
throw new OrderValidationError(`Invalid expiration format: ${order.expiration}`);
|
|
1439
1179
|
}
|
|
1440
1180
|
if (!Number.isInteger(order.salt) || order.salt <= 0) {
|
|
1441
|
-
throw new
|
|
1181
|
+
throw new OrderValidationError(`Invalid salt: ${order.salt}`);
|
|
1442
1182
|
}
|
|
1443
1183
|
if (!Number.isInteger(order.nonce) || order.nonce < 0) {
|
|
1444
|
-
throw new
|
|
1184
|
+
throw new OrderValidationError(`Invalid nonce: ${order.nonce}`);
|
|
1445
1185
|
}
|
|
1446
1186
|
if (!Number.isInteger(order.feeRateBps) || order.feeRateBps < 0) {
|
|
1447
|
-
throw new
|
|
1187
|
+
throw new OrderValidationError(`Invalid feeRateBps: ${order.feeRateBps}`);
|
|
1448
1188
|
}
|
|
1449
1189
|
if (order.side !== 0 && order.side !== 1) {
|
|
1450
|
-
throw new
|
|
1190
|
+
throw new OrderValidationError(`Invalid side: ${order.side}. Must be 0 (BUY) or 1 (SELL)`);
|
|
1451
1191
|
}
|
|
1452
1192
|
if (!Number.isInteger(order.signatureType) || order.signatureType < 0) {
|
|
1453
|
-
throw new
|
|
1193
|
+
throw new OrderValidationError(`Invalid signatureType: ${order.signatureType}`);
|
|
1454
1194
|
}
|
|
1455
1195
|
if (order.price !== void 0) {
|
|
1456
1196
|
if (typeof order.price !== "number" || isNaN(order.price)) {
|
|
1457
|
-
throw new
|
|
1197
|
+
throw new OrderValidationError("Price must be a valid number");
|
|
1458
1198
|
}
|
|
1459
1199
|
if (order.price < 0 || order.price > 1) {
|
|
1460
|
-
throw new
|
|
1200
|
+
throw new OrderValidationError(`Price must be between 0 and 1, got: ${order.price}`);
|
|
1461
1201
|
}
|
|
1462
1202
|
}
|
|
1463
1203
|
}
|
|
1464
1204
|
function validateSignedOrder(order) {
|
|
1465
1205
|
validateUnsignedOrder(order);
|
|
1466
1206
|
if (!order.signature) {
|
|
1467
|
-
throw new
|
|
1207
|
+
throw new OrderValidationError("Signature is required");
|
|
1468
1208
|
}
|
|
1469
1209
|
if (!order.signature.startsWith("0x")) {
|
|
1470
|
-
throw new
|
|
1210
|
+
throw new OrderValidationError("Signature must start with 0x");
|
|
1471
1211
|
}
|
|
1472
1212
|
if (order.signature.length !== 132) {
|
|
1473
|
-
throw new
|
|
1213
|
+
throw new OrderValidationError(
|
|
1474
1214
|
`Invalid signature length: ${order.signature.length}. Expected 132 characters.`
|
|
1475
1215
|
);
|
|
1476
1216
|
}
|
|
1477
1217
|
if (!/^0x[0-9a-fA-F]{130}$/.test(order.signature)) {
|
|
1478
|
-
throw new
|
|
1218
|
+
throw new OrderValidationError("Signature must be valid hex string");
|
|
1479
1219
|
}
|
|
1480
1220
|
}
|
|
1481
1221
|
|
|
1482
|
-
// src/
|
|
1483
|
-
var
|
|
1222
|
+
// src/markets/fetcher.ts
|
|
1223
|
+
var MarketFetcher = class {
|
|
1484
1224
|
/**
|
|
1485
|
-
* Creates a new
|
|
1225
|
+
* Creates a new market fetcher instance.
|
|
1486
1226
|
*
|
|
1487
|
-
* @param
|
|
1227
|
+
* @param httpClient - HTTP client for API requests
|
|
1228
|
+
* @param logger - Optional logger for debugging (default: no logging)
|
|
1488
1229
|
*
|
|
1489
|
-
* @
|
|
1230
|
+
* @example
|
|
1231
|
+
* ```typescript
|
|
1232
|
+
* const fetcher = new MarketFetcher(httpClient);
|
|
1233
|
+
* ```
|
|
1490
1234
|
*/
|
|
1491
|
-
constructor(
|
|
1492
|
-
this.httpClient =
|
|
1493
|
-
this.logger =
|
|
1494
|
-
this.
|
|
1495
|
-
const feeRateBps = config.userData.feeRateBps;
|
|
1496
|
-
this.orderBuilder = new OrderBuilder(config.wallet.address, feeRateBps, 1e-3);
|
|
1497
|
-
this.orderSigner = new OrderSigner(config.wallet, this.logger);
|
|
1498
|
-
if (config.signingConfig) {
|
|
1499
|
-
this.signingConfig = config.signingConfig;
|
|
1500
|
-
} else if (config.marketType) {
|
|
1501
|
-
const chainId = parseInt(process.env.CHAIN_ID || "8453");
|
|
1502
|
-
const contractAddress = config.marketType === "NEGRISK" /* NEGRISK */ ? process.env.NEGRISK_CONTRACT_ADDRESS || getContractAddress("NEGRISK", chainId) : process.env.CLOB_CONTRACT_ADDRESS || getContractAddress("CLOB", chainId);
|
|
1503
|
-
this.signingConfig = {
|
|
1504
|
-
chainId,
|
|
1505
|
-
contractAddress,
|
|
1506
|
-
marketType: config.marketType
|
|
1507
|
-
};
|
|
1508
|
-
this.logger.info("Auto-configured signing", {
|
|
1509
|
-
chainId,
|
|
1510
|
-
contractAddress,
|
|
1511
|
-
marketType: config.marketType
|
|
1512
|
-
});
|
|
1513
|
-
} else {
|
|
1514
|
-
const chainId = parseInt(process.env.CHAIN_ID || "8453");
|
|
1515
|
-
const contractAddress = process.env.CLOB_CONTRACT_ADDRESS || getContractAddress("CLOB", chainId);
|
|
1516
|
-
this.signingConfig = {
|
|
1517
|
-
chainId,
|
|
1518
|
-
contractAddress,
|
|
1519
|
-
marketType: "CLOB" /* CLOB */
|
|
1520
|
-
};
|
|
1521
|
-
this.logger.debug("Using default CLOB configuration", {
|
|
1522
|
-
chainId,
|
|
1523
|
-
contractAddress
|
|
1524
|
-
});
|
|
1525
|
-
}
|
|
1235
|
+
constructor(httpClient, logger) {
|
|
1236
|
+
this.httpClient = httpClient;
|
|
1237
|
+
this.logger = logger || new NoOpLogger();
|
|
1238
|
+
this.venueCache = /* @__PURE__ */ new Map();
|
|
1526
1239
|
}
|
|
1527
1240
|
/**
|
|
1528
|
-
*
|
|
1529
|
-
*
|
|
1530
|
-
* @remarks
|
|
1531
|
-
* This method handles the complete order creation flow:
|
|
1532
|
-
* 1. Build unsigned order
|
|
1533
|
-
* 2. Sign with EIP-712
|
|
1534
|
-
* 3. Submit to API
|
|
1535
|
-
*
|
|
1536
|
-
* @param params - Order parameters
|
|
1537
|
-
* @returns Promise resolving to order response
|
|
1538
|
-
*
|
|
1539
|
-
* @throws Error if order creation fails
|
|
1540
|
-
*
|
|
1541
|
-
* @example
|
|
1542
|
-
* ```typescript
|
|
1543
|
-
* const order = await orderClient.createOrder({
|
|
1544
|
-
* tokenId: '123456',
|
|
1545
|
-
* price: 0.65,
|
|
1546
|
-
* size: 100,
|
|
1547
|
-
* side: Side.BUY,
|
|
1548
|
-
* orderType: OrderType.GTC,
|
|
1549
|
-
* marketSlug: 'market-slug'
|
|
1550
|
-
* });
|
|
1551
|
-
*
|
|
1552
|
-
* console.log(`Order created: ${order.order.id}`);
|
|
1553
|
-
* ```
|
|
1554
|
-
*/
|
|
1555
|
-
async createOrder(params) {
|
|
1556
|
-
this.logger.info("Creating order", {
|
|
1557
|
-
side: params.side,
|
|
1558
|
-
orderType: params.orderType,
|
|
1559
|
-
marketSlug: params.marketSlug
|
|
1560
|
-
});
|
|
1561
|
-
const unsignedOrder = this.orderBuilder.buildOrder(params);
|
|
1562
|
-
this.logger.debug("Built unsigned order", {
|
|
1563
|
-
salt: unsignedOrder.salt,
|
|
1564
|
-
makerAmount: unsignedOrder.makerAmount,
|
|
1565
|
-
takerAmount: unsignedOrder.takerAmount
|
|
1566
|
-
});
|
|
1567
|
-
const signature = await this.orderSigner.signOrder(
|
|
1568
|
-
unsignedOrder,
|
|
1569
|
-
this.signingConfig
|
|
1570
|
-
);
|
|
1571
|
-
const payload = {
|
|
1572
|
-
order: {
|
|
1573
|
-
...unsignedOrder,
|
|
1574
|
-
signature
|
|
1575
|
-
},
|
|
1576
|
-
orderType: params.orderType,
|
|
1577
|
-
marketSlug: params.marketSlug,
|
|
1578
|
-
ownerId: this.ownerId
|
|
1579
|
-
};
|
|
1580
|
-
this.logger.debug("Submitting order to API");
|
|
1581
|
-
console.log("[OrderClient] Full API request payload:", JSON.stringify(payload, null, 2));
|
|
1582
|
-
const apiResponse = await this.httpClient.post(
|
|
1583
|
-
"/orders",
|
|
1584
|
-
payload
|
|
1585
|
-
);
|
|
1586
|
-
this.logger.info("Order created successfully", {
|
|
1587
|
-
orderId: apiResponse.order.id
|
|
1588
|
-
});
|
|
1589
|
-
return this.transformOrderResponse(apiResponse);
|
|
1590
|
-
}
|
|
1591
|
-
/**
|
|
1592
|
-
* Transforms raw API response to clean OrderResponse DTO.
|
|
1593
|
-
*
|
|
1594
|
-
* @param apiResponse - Raw API response with nested objects
|
|
1595
|
-
* @returns Clean OrderResponse with only essential fields
|
|
1596
|
-
*
|
|
1597
|
-
* @internal
|
|
1598
|
-
*/
|
|
1599
|
-
transformOrderResponse(apiResponse) {
|
|
1600
|
-
const cleanOrder = {
|
|
1601
|
-
order: {
|
|
1602
|
-
id: apiResponse.order.id,
|
|
1603
|
-
createdAt: apiResponse.order.createdAt,
|
|
1604
|
-
makerAmount: apiResponse.order.makerAmount,
|
|
1605
|
-
takerAmount: apiResponse.order.takerAmount,
|
|
1606
|
-
expiration: apiResponse.order.expiration,
|
|
1607
|
-
signatureType: apiResponse.order.signatureType,
|
|
1608
|
-
salt: apiResponse.order.salt,
|
|
1609
|
-
maker: apiResponse.order.maker,
|
|
1610
|
-
signer: apiResponse.order.signer,
|
|
1611
|
-
taker: apiResponse.order.taker,
|
|
1612
|
-
tokenId: apiResponse.order.tokenId,
|
|
1613
|
-
side: apiResponse.order.side,
|
|
1614
|
-
feeRateBps: apiResponse.order.feeRateBps,
|
|
1615
|
-
nonce: apiResponse.order.nonce,
|
|
1616
|
-
signature: apiResponse.order.signature,
|
|
1617
|
-
orderType: apiResponse.order.orderType,
|
|
1618
|
-
price: apiResponse.order.price,
|
|
1619
|
-
marketId: apiResponse.order.marketId
|
|
1620
|
-
}
|
|
1621
|
-
};
|
|
1622
|
-
if (apiResponse.makerMatches && apiResponse.makerMatches.length > 0) {
|
|
1623
|
-
cleanOrder.makerMatches = apiResponse.makerMatches.map((match) => ({
|
|
1624
|
-
id: match.id,
|
|
1625
|
-
createdAt: match.createdAt,
|
|
1626
|
-
matchedSize: match.matchedSize,
|
|
1627
|
-
orderId: match.orderId
|
|
1628
|
-
}));
|
|
1629
|
-
}
|
|
1630
|
-
return cleanOrder;
|
|
1631
|
-
}
|
|
1632
|
-
/**
|
|
1633
|
-
* Cancels an existing order by ID.
|
|
1634
|
-
*
|
|
1635
|
-
* @param orderId - Order ID to cancel
|
|
1636
|
-
* @returns Promise resolving to cancellation message
|
|
1637
|
-
*
|
|
1638
|
-
* @throws Error if cancellation fails
|
|
1639
|
-
*
|
|
1640
|
-
* @example
|
|
1641
|
-
* ```typescript
|
|
1642
|
-
* const result = await orderClient.cancel('order-id-123');
|
|
1643
|
-
* console.log(result.message); // "Order canceled successfully"
|
|
1644
|
-
* ```
|
|
1645
|
-
*/
|
|
1646
|
-
async cancel(orderId) {
|
|
1647
|
-
this.logger.info("Cancelling order", { orderId });
|
|
1648
|
-
const response = await this.httpClient.delete(
|
|
1649
|
-
`/orders/${orderId}`
|
|
1650
|
-
);
|
|
1651
|
-
this.logger.info("Order cancellation response", {
|
|
1652
|
-
orderId,
|
|
1653
|
-
message: response.message
|
|
1654
|
-
});
|
|
1655
|
-
return response;
|
|
1656
|
-
}
|
|
1657
|
-
/**
|
|
1658
|
-
* Cancels all orders for a specific market.
|
|
1659
|
-
*
|
|
1660
|
-
* @param marketSlug - Market slug to cancel all orders for
|
|
1661
|
-
* @returns Promise resolving to cancellation message
|
|
1662
|
-
*
|
|
1663
|
-
* @throws Error if cancellation fails
|
|
1664
|
-
*
|
|
1665
|
-
* @example
|
|
1666
|
-
* ```typescript
|
|
1667
|
-
* const result = await orderClient.cancelAll('market-slug-123');
|
|
1668
|
-
* console.log(result.message); // "Orders canceled successfully"
|
|
1669
|
-
* ```
|
|
1670
|
-
*/
|
|
1671
|
-
async cancelAll(marketSlug) {
|
|
1672
|
-
this.logger.info("Cancelling all orders for market", { marketSlug });
|
|
1673
|
-
const response = await this.httpClient.delete(
|
|
1674
|
-
`/orders/all/${marketSlug}`
|
|
1675
|
-
);
|
|
1676
|
-
this.logger.info("All orders cancellation response", {
|
|
1677
|
-
marketSlug,
|
|
1678
|
-
message: response.message
|
|
1679
|
-
});
|
|
1680
|
-
return response;
|
|
1681
|
-
}
|
|
1682
|
-
/**
|
|
1683
|
-
* @deprecated Use `cancel()` instead
|
|
1684
|
-
*/
|
|
1685
|
-
async cancelOrder(orderId) {
|
|
1686
|
-
await this.cancel(orderId);
|
|
1687
|
-
}
|
|
1688
|
-
/**
|
|
1689
|
-
* Gets an order by ID.
|
|
1690
|
-
*
|
|
1691
|
-
* @param orderId - Order ID to fetch
|
|
1692
|
-
* @returns Promise resolving to order details
|
|
1693
|
-
*
|
|
1694
|
-
* @throws Error if order not found
|
|
1695
|
-
*
|
|
1696
|
-
* @example
|
|
1697
|
-
* ```typescript
|
|
1698
|
-
* const order = await orderClient.getOrder('order-id-123');
|
|
1699
|
-
* console.log(order.order.side);
|
|
1700
|
-
* ```
|
|
1701
|
-
*/
|
|
1702
|
-
async getOrder(orderId) {
|
|
1703
|
-
this.logger.debug("Fetching order", { orderId });
|
|
1704
|
-
const response = await this.httpClient.get(
|
|
1705
|
-
`/orders/${orderId}`
|
|
1706
|
-
);
|
|
1707
|
-
return response;
|
|
1708
|
-
}
|
|
1709
|
-
/**
|
|
1710
|
-
* Builds an unsigned order without submitting.
|
|
1711
|
-
*
|
|
1712
|
-
* @remarks
|
|
1713
|
-
* Useful for advanced use cases where you need the unsigned order
|
|
1714
|
-
* before signing and submission.
|
|
1715
|
-
*
|
|
1716
|
-
* @param params - Order parameters
|
|
1717
|
-
* @returns Unsigned order
|
|
1718
|
-
*
|
|
1719
|
-
* @example
|
|
1720
|
-
* ```typescript
|
|
1721
|
-
* const unsignedOrder = orderClient.buildUnsignedOrder({
|
|
1722
|
-
* tokenId: '123456',
|
|
1723
|
-
* price: 0.65,
|
|
1724
|
-
* size: 100,
|
|
1725
|
-
* side: Side.BUY
|
|
1726
|
-
* });
|
|
1727
|
-
* ```
|
|
1728
|
-
*/
|
|
1729
|
-
buildUnsignedOrder(params) {
|
|
1730
|
-
return this.orderBuilder.buildOrder(params);
|
|
1731
|
-
}
|
|
1732
|
-
/**
|
|
1733
|
-
* Signs an unsigned order without submitting.
|
|
1734
|
-
*
|
|
1735
|
-
* @remarks
|
|
1736
|
-
* Useful for advanced use cases where you need to inspect
|
|
1737
|
-
* the signature before submission.
|
|
1738
|
-
*
|
|
1739
|
-
* @param order - Unsigned order to sign
|
|
1740
|
-
* @returns Promise resolving to signature
|
|
1741
|
-
*
|
|
1742
|
-
* @example
|
|
1743
|
-
* ```typescript
|
|
1744
|
-
* const signature = await orderClient.signOrder(unsignedOrder);
|
|
1745
|
-
* ```
|
|
1746
|
-
*/
|
|
1747
|
-
async signOrder(order) {
|
|
1748
|
-
return await this.orderSigner.signOrder(order, this.signingConfig);
|
|
1749
|
-
}
|
|
1750
|
-
};
|
|
1751
|
-
|
|
1752
|
-
// src/markets/fetcher.ts
|
|
1753
|
-
var MarketFetcher = class {
|
|
1754
|
-
/**
|
|
1755
|
-
* Creates a new market fetcher instance.
|
|
1756
|
-
*
|
|
1757
|
-
* @param httpClient - HTTP client for API requests
|
|
1758
|
-
* @param logger - Optional logger for debugging (default: no logging)
|
|
1759
|
-
*
|
|
1760
|
-
* @example
|
|
1761
|
-
* ```typescript
|
|
1762
|
-
* const fetcher = new MarketFetcher(httpClient);
|
|
1763
|
-
* ```
|
|
1764
|
-
*/
|
|
1765
|
-
constructor(httpClient, logger) {
|
|
1766
|
-
this.httpClient = httpClient;
|
|
1767
|
-
this.logger = logger || new NoOpLogger();
|
|
1768
|
-
}
|
|
1769
|
-
/**
|
|
1770
|
-
* Gets active markets with query parameters and pagination support.
|
|
1241
|
+
* Gets active markets with query parameters and pagination support.
|
|
1771
1242
|
*
|
|
1772
1243
|
* @param params - Query parameters for filtering and pagination
|
|
1773
1244
|
* @returns Promise resolving to active markets response
|
|
@@ -1806,13 +1277,18 @@ var MarketFetcher = class {
|
|
|
1806
1277
|
this.logger.debug("Fetching active markets", { params });
|
|
1807
1278
|
try {
|
|
1808
1279
|
const response = await this.httpClient.get(endpoint);
|
|
1280
|
+
const markets = response.data.map((marketData) => new Market(marketData, this.httpClient));
|
|
1281
|
+
const result = {
|
|
1282
|
+
data: markets,
|
|
1283
|
+
totalMarketsCount: response.totalMarketsCount
|
|
1284
|
+
};
|
|
1809
1285
|
this.logger.info("Active markets fetched successfully", {
|
|
1810
|
-
count:
|
|
1286
|
+
count: markets.length,
|
|
1811
1287
|
total: response.totalMarketsCount,
|
|
1812
1288
|
sortBy: params?.sortBy,
|
|
1813
1289
|
page: params?.page
|
|
1814
1290
|
});
|
|
1815
|
-
return
|
|
1291
|
+
return result;
|
|
1816
1292
|
} catch (error) {
|
|
1817
1293
|
this.logger.error("Failed to fetch active markets", error, { params });
|
|
1818
1294
|
throw error;
|
|
@@ -1821,20 +1297,48 @@ var MarketFetcher = class {
|
|
|
1821
1297
|
/**
|
|
1822
1298
|
* Gets a single market by slug.
|
|
1823
1299
|
*
|
|
1300
|
+
* @remarks
|
|
1301
|
+
* Automatically caches venue information for efficient order signing.
|
|
1302
|
+
* Always call this method before creating orders to ensure venue data
|
|
1303
|
+
* is available and avoid additional API requests.
|
|
1304
|
+
*
|
|
1824
1305
|
* @param slug - Market slug identifier
|
|
1825
1306
|
* @returns Promise resolving to market details
|
|
1826
1307
|
* @throws Error if API request fails or market not found
|
|
1827
1308
|
*
|
|
1828
1309
|
* @example
|
|
1829
1310
|
* ```typescript
|
|
1311
|
+
* // Get market
|
|
1830
1312
|
* const market = await fetcher.getMarket('bitcoin-price-2024');
|
|
1831
1313
|
* console.log(`Market: ${market.title}`);
|
|
1314
|
+
*
|
|
1315
|
+
* // Fluent API - get user orders for this market (clean!)
|
|
1316
|
+
* const orders = await market.getUserOrders();
|
|
1317
|
+
* console.log(`You have ${orders.length} orders`);
|
|
1318
|
+
*
|
|
1319
|
+
* // Venue is now cached for order signing
|
|
1320
|
+
* await orderClient.createOrder({
|
|
1321
|
+
* marketSlug: 'bitcoin-price-2024',
|
|
1322
|
+
* ...
|
|
1323
|
+
* });
|
|
1832
1324
|
* ```
|
|
1833
1325
|
*/
|
|
1834
1326
|
async getMarket(slug) {
|
|
1835
1327
|
this.logger.debug("Fetching market", { slug });
|
|
1836
1328
|
try {
|
|
1837
|
-
const
|
|
1329
|
+
const response = await this.httpClient.get(`/markets/${slug}`);
|
|
1330
|
+
const market = new Market(response, this.httpClient);
|
|
1331
|
+
if (market.venue) {
|
|
1332
|
+
this.venueCache.set(slug, market.venue);
|
|
1333
|
+
this.logger.debug("Venue cached for order signing", {
|
|
1334
|
+
slug,
|
|
1335
|
+
exchange: market.venue.exchange,
|
|
1336
|
+
adapter: market.venue.adapter,
|
|
1337
|
+
cacheSize: this.venueCache.size
|
|
1338
|
+
});
|
|
1339
|
+
} else {
|
|
1340
|
+
this.logger.warn("Market has no venue data", { slug });
|
|
1341
|
+
}
|
|
1838
1342
|
this.logger.info("Market fetched successfully", {
|
|
1839
1343
|
slug,
|
|
1840
1344
|
title: market.title
|
|
@@ -1845,6 +1349,36 @@ var MarketFetcher = class {
|
|
|
1845
1349
|
throw error;
|
|
1846
1350
|
}
|
|
1847
1351
|
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Gets cached venue information for a market.
|
|
1354
|
+
*
|
|
1355
|
+
* @remarks
|
|
1356
|
+
* Returns venue data previously cached by getMarket() call.
|
|
1357
|
+
* Used internally by OrderClient for efficient order signing.
|
|
1358
|
+
*
|
|
1359
|
+
* @param slug - Market slug identifier
|
|
1360
|
+
* @returns Cached venue information, or undefined if not in cache
|
|
1361
|
+
*
|
|
1362
|
+
* @example
|
|
1363
|
+
* ```typescript
|
|
1364
|
+
* const venue = fetcher.getVenue('bitcoin-price-2024');
|
|
1365
|
+
* if (venue) {
|
|
1366
|
+
* console.log(`Exchange: ${venue.exchange}`);
|
|
1367
|
+
* }
|
|
1368
|
+
* ```
|
|
1369
|
+
*/
|
|
1370
|
+
getVenue(slug) {
|
|
1371
|
+
const venue = this.venueCache.get(slug);
|
|
1372
|
+
if (venue) {
|
|
1373
|
+
this.logger.debug("Venue cache hit", {
|
|
1374
|
+
slug,
|
|
1375
|
+
exchange: venue.exchange
|
|
1376
|
+
});
|
|
1377
|
+
} else {
|
|
1378
|
+
this.logger.debug("Venue cache miss", { slug });
|
|
1379
|
+
}
|
|
1380
|
+
return venue;
|
|
1381
|
+
}
|
|
1848
1382
|
/**
|
|
1849
1383
|
* Gets the orderbook for a CLOB market.
|
|
1850
1384
|
*
|
|
@@ -1876,335 +1410,473 @@ var MarketFetcher = class {
|
|
|
1876
1410
|
throw error;
|
|
1877
1411
|
}
|
|
1878
1412
|
}
|
|
1413
|
+
};
|
|
1414
|
+
|
|
1415
|
+
// src/portfolio/fetcher.ts
|
|
1416
|
+
var PortfolioFetcher = class {
|
|
1417
|
+
/**
|
|
1418
|
+
* Creates a new portfolio fetcher instance.
|
|
1419
|
+
*
|
|
1420
|
+
* @param httpClient - Authenticated HTTP client for API requests
|
|
1421
|
+
* @param logger - Optional logger for debugging (default: no logging)
|
|
1422
|
+
*
|
|
1423
|
+
* @example
|
|
1424
|
+
* ```typescript
|
|
1425
|
+
* // Create authenticated client
|
|
1426
|
+
* const httpClient = new HttpClient({ baseURL: API_URL });
|
|
1427
|
+
* await authenticator.authenticate({ client: 'eoa' });
|
|
1428
|
+
*
|
|
1429
|
+
* // Create portfolio fetcher
|
|
1430
|
+
* const portfolioFetcher = new PortfolioFetcher(httpClient);
|
|
1431
|
+
* ```
|
|
1432
|
+
*/
|
|
1433
|
+
constructor(httpClient, logger) {
|
|
1434
|
+
this.httpClient = httpClient;
|
|
1435
|
+
this.logger = logger || new NoOpLogger();
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Gets user profile for a specific wallet address.
|
|
1439
|
+
*
|
|
1440
|
+
* @remarks
|
|
1441
|
+
* Returns user profile data including user ID and fee rate.
|
|
1442
|
+
* Used internally by OrderClient to fetch user data.
|
|
1443
|
+
*
|
|
1444
|
+
* @param address - Wallet address to fetch profile for
|
|
1445
|
+
* @returns Promise resolving to user profile data
|
|
1446
|
+
* @throws Error if API request fails or user is not authenticated
|
|
1447
|
+
*
|
|
1448
|
+
* @example
|
|
1449
|
+
* ```typescript
|
|
1450
|
+
* const profile = await portfolioFetcher.getProfile('0x1234...');
|
|
1451
|
+
* console.log(`User ID: ${profile.id}`);
|
|
1452
|
+
* console.log(`Account: ${profile.account}`);
|
|
1453
|
+
* console.log(`Fee Rate: ${profile.rank?.feeRateBps}`);
|
|
1454
|
+
* ```
|
|
1455
|
+
*/
|
|
1456
|
+
async getProfile(address) {
|
|
1457
|
+
this.logger.debug("Fetching user profile", { address });
|
|
1458
|
+
try {
|
|
1459
|
+
const response = await this.httpClient.get(`/profiles/${address}`);
|
|
1460
|
+
this.logger.info("User profile fetched successfully", { address });
|
|
1461
|
+
return response;
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
this.logger.error("Failed to fetch user profile", error, { address });
|
|
1464
|
+
throw error;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Gets raw portfolio positions response from API.
|
|
1469
|
+
*
|
|
1470
|
+
* @returns Promise resolving to portfolio positions response with CLOB and AMM positions
|
|
1471
|
+
* @throws Error if API request fails or user is not authenticated
|
|
1472
|
+
*
|
|
1473
|
+
* @example
|
|
1474
|
+
* ```typescript
|
|
1475
|
+
* const response = await portfolioFetcher.getPositions();
|
|
1476
|
+
* console.log(`CLOB positions: ${response.clob.length}`);
|
|
1477
|
+
* console.log(`AMM positions: ${response.amm.length}`);
|
|
1478
|
+
* console.log(`Total points: ${response.accumulativePoints}`);
|
|
1479
|
+
* ```
|
|
1480
|
+
*/
|
|
1481
|
+
async getPositions() {
|
|
1482
|
+
this.logger.debug("Fetching user positions");
|
|
1483
|
+
try {
|
|
1484
|
+
const response = await this.httpClient.get("/portfolio/positions");
|
|
1485
|
+
this.logger.info("Positions fetched successfully", {
|
|
1486
|
+
clobCount: response.clob?.length || 0,
|
|
1487
|
+
ammCount: response.amm?.length || 0
|
|
1488
|
+
});
|
|
1489
|
+
return response;
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
this.logger.error("Failed to fetch positions", error);
|
|
1492
|
+
throw error;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Gets CLOB positions only.
|
|
1497
|
+
*
|
|
1498
|
+
* @returns Promise resolving to array of CLOB positions
|
|
1499
|
+
* @throws Error if API request fails
|
|
1500
|
+
*
|
|
1501
|
+
* @example
|
|
1502
|
+
* ```typescript
|
|
1503
|
+
* const clobPositions = await portfolioFetcher.getCLOBPositions();
|
|
1504
|
+
* clobPositions.forEach(pos => {
|
|
1505
|
+
* console.log(`${pos.market.title}: YES ${pos.positions.yes.unrealizedPnl} P&L`);
|
|
1506
|
+
* });
|
|
1507
|
+
* ```
|
|
1508
|
+
*/
|
|
1509
|
+
async getCLOBPositions() {
|
|
1510
|
+
const response = await this.getPositions();
|
|
1511
|
+
return response.clob || [];
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* Gets AMM positions only.
|
|
1515
|
+
*
|
|
1516
|
+
* @returns Promise resolving to array of AMM positions
|
|
1517
|
+
* @throws Error if API request fails
|
|
1518
|
+
*
|
|
1519
|
+
* @example
|
|
1520
|
+
* ```typescript
|
|
1521
|
+
* const ammPositions = await portfolioFetcher.getAMMPositions();
|
|
1522
|
+
* ammPositions.forEach(pos => {
|
|
1523
|
+
* console.log(`${pos.market.title}: ${pos.unrealizedPnl} P&L`);
|
|
1524
|
+
* });
|
|
1525
|
+
* ```
|
|
1526
|
+
*/
|
|
1527
|
+
async getAMMPositions() {
|
|
1528
|
+
const response = await this.getPositions();
|
|
1529
|
+
return response.amm || [];
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Gets paginated history of user actions.
|
|
1533
|
+
*
|
|
1534
|
+
* Includes AMM trades, CLOB trades, Negrisk trades & conversions.
|
|
1535
|
+
*
|
|
1536
|
+
* @param page - Page number (starts at 1)
|
|
1537
|
+
* @param limit - Number of items per page
|
|
1538
|
+
* @returns Promise resolving to paginated history response
|
|
1539
|
+
* @throws Error if API request fails or user is not authenticated
|
|
1540
|
+
*
|
|
1541
|
+
* @example
|
|
1542
|
+
* ```typescript
|
|
1543
|
+
* // Get first page
|
|
1544
|
+
* const response = await portfolioFetcher.getUserHistory(1, 20);
|
|
1545
|
+
* console.log(`Found ${response.data.length} of ${response.totalCount} entries`);
|
|
1546
|
+
*
|
|
1547
|
+
* // Process history entries
|
|
1548
|
+
* for (const entry of response.data) {
|
|
1549
|
+
* console.log(`Type: ${entry.type}`);
|
|
1550
|
+
* console.log(`Market: ${entry.marketSlug}`);
|
|
1551
|
+
* }
|
|
1552
|
+
*
|
|
1553
|
+
* // Get next page
|
|
1554
|
+
* const page2 = await portfolioFetcher.getUserHistory(2, 20);
|
|
1555
|
+
* ```
|
|
1556
|
+
*/
|
|
1557
|
+
async getUserHistory(page = 1, limit = 10) {
|
|
1558
|
+
this.logger.debug("Fetching user history", { page, limit });
|
|
1559
|
+
try {
|
|
1560
|
+
const params = new URLSearchParams({
|
|
1561
|
+
page: page.toString(),
|
|
1562
|
+
limit: limit.toString()
|
|
1563
|
+
});
|
|
1564
|
+
const response = await this.httpClient.get(
|
|
1565
|
+
`/portfolio/history?${params.toString()}`
|
|
1566
|
+
);
|
|
1567
|
+
this.logger.info("User history fetched successfully");
|
|
1568
|
+
return response;
|
|
1569
|
+
} catch (error) {
|
|
1570
|
+
this.logger.error("Failed to fetch user history", error, { page, limit });
|
|
1571
|
+
throw error;
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
|
|
1576
|
+
// src/orders/client.ts
|
|
1577
|
+
var OrderClient = class {
|
|
1879
1578
|
/**
|
|
1880
|
-
*
|
|
1579
|
+
* Creates a new order client instance.
|
|
1580
|
+
*
|
|
1581
|
+
* @param config - Order client configuration
|
|
1582
|
+
*/
|
|
1583
|
+
constructor(config) {
|
|
1584
|
+
this.httpClient = config.httpClient;
|
|
1585
|
+
this.wallet = config.wallet;
|
|
1586
|
+
this.logger = config.logger || new NoOpLogger();
|
|
1587
|
+
this.orderSigner = new OrderSigner(config.wallet, this.logger);
|
|
1588
|
+
this.marketFetcher = config.marketFetcher || new MarketFetcher(config.httpClient, this.logger);
|
|
1589
|
+
if (config.signingConfig) {
|
|
1590
|
+
this.signingConfig = config.signingConfig;
|
|
1591
|
+
} else {
|
|
1592
|
+
const chainId = parseInt(process.env.CHAIN_ID || "8453");
|
|
1593
|
+
const contractAddress = ZERO_ADDRESS;
|
|
1594
|
+
this.signingConfig = {
|
|
1595
|
+
chainId,
|
|
1596
|
+
contractAddress
|
|
1597
|
+
};
|
|
1598
|
+
this.logger.info("Auto-configured signing (contract address from venue)", {
|
|
1599
|
+
chainId
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Ensures user data is loaded and cached.
|
|
1605
|
+
* Fetches from profile API on first call, then caches for subsequent calls.
|
|
1606
|
+
*
|
|
1607
|
+
* @returns Promise resolving to cached user data
|
|
1608
|
+
* @internal
|
|
1609
|
+
*/
|
|
1610
|
+
async ensureUserData() {
|
|
1611
|
+
if (!this.cachedUserData) {
|
|
1612
|
+
this.logger.info("Fetching user profile for order client initialization...", {
|
|
1613
|
+
walletAddress: this.wallet.address
|
|
1614
|
+
});
|
|
1615
|
+
const portfolioFetcher = new PortfolioFetcher(this.httpClient);
|
|
1616
|
+
const profile = await portfolioFetcher.getProfile(this.wallet.address);
|
|
1617
|
+
this.cachedUserData = {
|
|
1618
|
+
userId: profile.id,
|
|
1619
|
+
feeRateBps: profile.rank?.feeRateBps || 300
|
|
1620
|
+
};
|
|
1621
|
+
this.orderBuilder = new OrderBuilder(
|
|
1622
|
+
this.wallet.address,
|
|
1623
|
+
this.cachedUserData.feeRateBps,
|
|
1624
|
+
1e-3
|
|
1625
|
+
);
|
|
1626
|
+
this.logger.info("Order Client initialized", {
|
|
1627
|
+
walletAddress: profile.account,
|
|
1628
|
+
userId: this.cachedUserData.userId,
|
|
1629
|
+
feeRate: `${this.cachedUserData.feeRateBps / 100}%`
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
return this.cachedUserData;
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Creates and submits a new order.
|
|
1636
|
+
*
|
|
1637
|
+
* @remarks
|
|
1638
|
+
* This method handles the complete order creation flow:
|
|
1639
|
+
* 1. Resolve venue address (from cache or API)
|
|
1640
|
+
* 2. Build unsigned order
|
|
1641
|
+
* 3. Sign with EIP-712 using venue.exchange as verifyingContract
|
|
1642
|
+
* 4. Submit to API
|
|
1881
1643
|
*
|
|
1882
|
-
*
|
|
1883
|
-
*
|
|
1884
|
-
*
|
|
1644
|
+
* Performance best practice: Always call marketFetcher.getMarket(marketSlug)
|
|
1645
|
+
* before createOrder() to cache venue data and avoid additional API requests.
|
|
1646
|
+
*
|
|
1647
|
+
* @param params - Order parameters
|
|
1648
|
+
* @returns Promise resolving to order response
|
|
1649
|
+
*
|
|
1650
|
+
* @throws Error if order creation fails or venue not found
|
|
1885
1651
|
*
|
|
1886
1652
|
* @example
|
|
1887
1653
|
* ```typescript
|
|
1888
|
-
*
|
|
1889
|
-
*
|
|
1654
|
+
* // Best practice: fetch market first to cache venue
|
|
1655
|
+
* const market = await marketFetcher.getMarket('bitcoin-2024');
|
|
1656
|
+
*
|
|
1657
|
+
* const order = await orderClient.createOrder({
|
|
1658
|
+
* tokenId: market.tokens.yes,
|
|
1659
|
+
* price: 0.65,
|
|
1660
|
+
* size: 100,
|
|
1661
|
+
* side: Side.BUY,
|
|
1662
|
+
* orderType: OrderType.GTC,
|
|
1663
|
+
* marketSlug: 'bitcoin-2024'
|
|
1664
|
+
* });
|
|
1665
|
+
*
|
|
1666
|
+
* console.log(`Order created: ${order.order.id}`);
|
|
1890
1667
|
* ```
|
|
1891
1668
|
*/
|
|
1892
|
-
async
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1669
|
+
async createOrder(params) {
|
|
1670
|
+
const userData = await this.ensureUserData();
|
|
1671
|
+
this.logger.info("Creating order", {
|
|
1672
|
+
side: params.side,
|
|
1673
|
+
orderType: params.orderType,
|
|
1674
|
+
marketSlug: params.marketSlug
|
|
1675
|
+
});
|
|
1676
|
+
let venue = this.marketFetcher.getVenue(params.marketSlug);
|
|
1677
|
+
if (!venue) {
|
|
1678
|
+
this.logger.warn(
|
|
1679
|
+
"Venue not cached, fetching market details. For better performance, call marketFetcher.getMarket() before createOrder().",
|
|
1680
|
+
{ marketSlug: params.marketSlug }
|
|
1681
|
+
);
|
|
1682
|
+
const market = await this.marketFetcher.getMarket(params.marketSlug);
|
|
1683
|
+
if (!market.venue) {
|
|
1684
|
+
throw new Error(
|
|
1685
|
+
`Market ${params.marketSlug} does not have venue information. Venue data is required for order signing.`
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
venue = market.venue;
|
|
1904
1689
|
}
|
|
1690
|
+
const dynamicSigningConfig = {
|
|
1691
|
+
...this.signingConfig,
|
|
1692
|
+
contractAddress: venue.exchange
|
|
1693
|
+
};
|
|
1694
|
+
this.logger.debug("Using venue for order signing", {
|
|
1695
|
+
marketSlug: params.marketSlug,
|
|
1696
|
+
exchange: venue.exchange,
|
|
1697
|
+
adapter: venue.adapter
|
|
1698
|
+
});
|
|
1699
|
+
const unsignedOrder = this.orderBuilder.buildOrder(params);
|
|
1700
|
+
this.logger.debug("Built unsigned order", {
|
|
1701
|
+
salt: unsignedOrder.salt,
|
|
1702
|
+
makerAmount: unsignedOrder.makerAmount,
|
|
1703
|
+
takerAmount: unsignedOrder.takerAmount
|
|
1704
|
+
});
|
|
1705
|
+
const signature = await this.orderSigner.signOrder(unsignedOrder, dynamicSigningConfig);
|
|
1706
|
+
const payload = {
|
|
1707
|
+
order: {
|
|
1708
|
+
...unsignedOrder,
|
|
1709
|
+
signature
|
|
1710
|
+
},
|
|
1711
|
+
orderType: params.orderType,
|
|
1712
|
+
marketSlug: params.marketSlug,
|
|
1713
|
+
ownerId: userData.userId
|
|
1714
|
+
};
|
|
1715
|
+
this.logger.debug("Submitting order to API", payload);
|
|
1716
|
+
const apiResponse = await this.httpClient.post("/orders", payload);
|
|
1717
|
+
this.logger.info("Order created successfully", {
|
|
1718
|
+
orderId: apiResponse.order.id
|
|
1719
|
+
});
|
|
1720
|
+
return this.transformOrderResponse(apiResponse);
|
|
1905
1721
|
}
|
|
1906
|
-
};
|
|
1907
|
-
|
|
1908
|
-
// src/portfolio/fetcher.ts
|
|
1909
|
-
var PortfolioFetcher = class {
|
|
1910
1722
|
/**
|
|
1911
|
-
*
|
|
1912
|
-
*
|
|
1913
|
-
* @param httpClient - Authenticated HTTP client for API requests
|
|
1914
|
-
* @param logger - Optional logger for debugging (default: no logging)
|
|
1723
|
+
* Transforms raw API response to clean OrderResponse DTO.
|
|
1915
1724
|
*
|
|
1916
|
-
* @
|
|
1917
|
-
*
|
|
1918
|
-
* // Create authenticated client
|
|
1919
|
-
* const httpClient = new HttpClient({ baseURL: API_URL });
|
|
1920
|
-
* await authenticator.authenticate({ client: 'eoa' });
|
|
1725
|
+
* @param apiResponse - Raw API response with nested objects
|
|
1726
|
+
* @returns Clean OrderResponse with only essential fields
|
|
1921
1727
|
*
|
|
1922
|
-
*
|
|
1923
|
-
* const portfolioFetcher = new PortfolioFetcher(httpClient);
|
|
1924
|
-
* ```
|
|
1728
|
+
* @internal
|
|
1925
1729
|
*/
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1730
|
+
transformOrderResponse(apiResponse) {
|
|
1731
|
+
const cleanOrder = {
|
|
1732
|
+
order: {
|
|
1733
|
+
id: apiResponse.order.id,
|
|
1734
|
+
createdAt: apiResponse.order.createdAt,
|
|
1735
|
+
makerAmount: apiResponse.order.makerAmount,
|
|
1736
|
+
takerAmount: apiResponse.order.takerAmount,
|
|
1737
|
+
expiration: apiResponse.order.expiration,
|
|
1738
|
+
signatureType: apiResponse.order.signatureType,
|
|
1739
|
+
salt: apiResponse.order.salt,
|
|
1740
|
+
maker: apiResponse.order.maker,
|
|
1741
|
+
signer: apiResponse.order.signer,
|
|
1742
|
+
taker: apiResponse.order.taker,
|
|
1743
|
+
tokenId: apiResponse.order.tokenId,
|
|
1744
|
+
side: apiResponse.order.side,
|
|
1745
|
+
feeRateBps: apiResponse.order.feeRateBps,
|
|
1746
|
+
nonce: apiResponse.order.nonce,
|
|
1747
|
+
signature: apiResponse.order.signature,
|
|
1748
|
+
orderType: apiResponse.order.orderType,
|
|
1749
|
+
price: apiResponse.order.price,
|
|
1750
|
+
marketId: apiResponse.order.marketId
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
if (apiResponse.makerMatches && apiResponse.makerMatches.length > 0) {
|
|
1754
|
+
cleanOrder.makerMatches = apiResponse.makerMatches.map((match) => ({
|
|
1755
|
+
id: match.id,
|
|
1756
|
+
createdAt: match.createdAt,
|
|
1757
|
+
matchedSize: match.matchedSize,
|
|
1758
|
+
orderId: match.orderId
|
|
1759
|
+
}));
|
|
1760
|
+
}
|
|
1761
|
+
return cleanOrder;
|
|
1929
1762
|
}
|
|
1930
1763
|
/**
|
|
1931
|
-
*
|
|
1764
|
+
* Cancels an existing order by ID.
|
|
1932
1765
|
*
|
|
1933
|
-
* @
|
|
1934
|
-
* @
|
|
1766
|
+
* @param orderId - Order ID to cancel
|
|
1767
|
+
* @returns Promise resolving to cancellation message
|
|
1768
|
+
*
|
|
1769
|
+
* @throws Error if cancellation fails
|
|
1935
1770
|
*
|
|
1936
1771
|
* @example
|
|
1937
1772
|
* ```typescript
|
|
1938
|
-
* const
|
|
1939
|
-
* console.log(
|
|
1940
|
-
* console.log(`AMM positions: ${response.amm.length}`);
|
|
1941
|
-
* console.log(`Total points: ${response.accumulativePoints}`);
|
|
1773
|
+
* const result = await orderClient.cancel('order-id-123');
|
|
1774
|
+
* console.log(result.message); // "Order canceled successfully"
|
|
1942
1775
|
* ```
|
|
1943
1776
|
*/
|
|
1944
|
-
async
|
|
1945
|
-
this.logger.
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
ammCount: response.amm?.length || 0
|
|
1953
|
-
});
|
|
1954
|
-
return response;
|
|
1955
|
-
} catch (error) {
|
|
1956
|
-
this.logger.error("Failed to fetch positions", error);
|
|
1957
|
-
throw error;
|
|
1958
|
-
}
|
|
1777
|
+
async cancel(orderId) {
|
|
1778
|
+
this.logger.info("Cancelling order", { orderId });
|
|
1779
|
+
const response = await this.httpClient.delete(`/orders/${orderId}`);
|
|
1780
|
+
this.logger.info("Order cancellation response", {
|
|
1781
|
+
orderId,
|
|
1782
|
+
message: response.message
|
|
1783
|
+
});
|
|
1784
|
+
return response;
|
|
1959
1785
|
}
|
|
1960
1786
|
/**
|
|
1961
|
-
*
|
|
1787
|
+
* Cancels all orders for a specific market.
|
|
1962
1788
|
*
|
|
1963
|
-
* @
|
|
1964
|
-
* @
|
|
1789
|
+
* @param marketSlug - Market slug to cancel all orders for
|
|
1790
|
+
* @returns Promise resolving to cancellation message
|
|
1791
|
+
*
|
|
1792
|
+
* @throws Error if cancellation fails
|
|
1965
1793
|
*
|
|
1966
1794
|
* @example
|
|
1967
1795
|
* ```typescript
|
|
1968
|
-
* const
|
|
1969
|
-
*
|
|
1970
|
-
* console.log(`${pos.market.title}: YES ${pos.positions.yes.unrealizedPnl} P&L`);
|
|
1971
|
-
* });
|
|
1796
|
+
* const result = await orderClient.cancelAll('market-slug-123');
|
|
1797
|
+
* console.log(result.message); // "Orders canceled successfully"
|
|
1972
1798
|
* ```
|
|
1973
1799
|
*/
|
|
1974
|
-
async
|
|
1975
|
-
|
|
1976
|
-
|
|
1800
|
+
async cancelAll(marketSlug) {
|
|
1801
|
+
this.logger.info("Cancelling all orders for market", { marketSlug });
|
|
1802
|
+
const response = await this.httpClient.delete(`/orders/all/${marketSlug}`);
|
|
1803
|
+
this.logger.info("All orders cancellation response", {
|
|
1804
|
+
marketSlug,
|
|
1805
|
+
message: response.message
|
|
1806
|
+
});
|
|
1807
|
+
return response;
|
|
1977
1808
|
}
|
|
1978
1809
|
/**
|
|
1979
|
-
*
|
|
1810
|
+
* Builds an unsigned order without submitting.
|
|
1980
1811
|
*
|
|
1981
|
-
* @
|
|
1982
|
-
*
|
|
1812
|
+
* @remarks
|
|
1813
|
+
* Useful for advanced use cases where you need the unsigned order
|
|
1814
|
+
* before signing and submission.
|
|
1815
|
+
*
|
|
1816
|
+
* @param params - Order parameters
|
|
1817
|
+
* @returns Promise resolving to unsigned order
|
|
1983
1818
|
*
|
|
1984
1819
|
* @example
|
|
1985
1820
|
* ```typescript
|
|
1986
|
-
* const
|
|
1987
|
-
*
|
|
1988
|
-
*
|
|
1821
|
+
* const unsignedOrder = await orderClient.buildUnsignedOrder({
|
|
1822
|
+
* tokenId: '123456',
|
|
1823
|
+
* price: 0.65,
|
|
1824
|
+
* size: 100,
|
|
1825
|
+
* side: Side.BUY
|
|
1989
1826
|
* });
|
|
1990
1827
|
* ```
|
|
1991
1828
|
*/
|
|
1992
|
-
async
|
|
1993
|
-
|
|
1994
|
-
return
|
|
1829
|
+
async buildUnsignedOrder(params) {
|
|
1830
|
+
await this.ensureUserData();
|
|
1831
|
+
return this.orderBuilder.buildOrder(params);
|
|
1995
1832
|
}
|
|
1996
1833
|
/**
|
|
1997
|
-
*
|
|
1834
|
+
* Signs an unsigned order without submitting.
|
|
1998
1835
|
*
|
|
1999
1836
|
* @remarks
|
|
2000
|
-
*
|
|
2001
|
-
*
|
|
1837
|
+
* Useful for advanced use cases where you need to inspect
|
|
1838
|
+
* the signature before submission.
|
|
2002
1839
|
*
|
|
2003
|
-
* @
|
|
2004
|
-
* @
|
|
1840
|
+
* @param order - Unsigned order to sign
|
|
1841
|
+
* @returns Promise resolving to signature
|
|
2005
1842
|
*
|
|
2006
1843
|
* @example
|
|
2007
1844
|
* ```typescript
|
|
2008
|
-
* const
|
|
2009
|
-
* positions.forEach(pos => {
|
|
2010
|
-
* const pnlPercent = (pos.unrealizedPnl / pos.costBasis) * 100;
|
|
2011
|
-
* console.log(`${pos.market.title} (${pos.side}): ${pnlPercent.toFixed(2)}% P&L`);
|
|
2012
|
-
* });
|
|
1845
|
+
* const signature = await orderClient.signOrder(unsignedOrder);
|
|
2013
1846
|
* ```
|
|
2014
1847
|
*/
|
|
2015
|
-
async
|
|
2016
|
-
|
|
2017
|
-
const positions = [];
|
|
2018
|
-
for (const clobPos of response.clob || []) {
|
|
2019
|
-
const yesCost = parseFloat(clobPos.positions.yes.cost);
|
|
2020
|
-
const yesValue = parseFloat(clobPos.positions.yes.marketValue);
|
|
2021
|
-
if (yesCost > 0 || yesValue > 0) {
|
|
2022
|
-
positions.push({
|
|
2023
|
-
type: "CLOB",
|
|
2024
|
-
market: clobPos.market,
|
|
2025
|
-
side: "YES",
|
|
2026
|
-
costBasis: yesCost,
|
|
2027
|
-
marketValue: yesValue,
|
|
2028
|
-
unrealizedPnl: parseFloat(clobPos.positions.yes.unrealizedPnl),
|
|
2029
|
-
realizedPnl: parseFloat(clobPos.positions.yes.realisedPnl),
|
|
2030
|
-
currentPrice: clobPos.latestTrade?.latestYesPrice ?? 0,
|
|
2031
|
-
avgPrice: yesCost > 0 ? parseFloat(clobPos.positions.yes.fillPrice) / 1e6 : 0,
|
|
2032
|
-
tokenBalance: parseFloat(clobPos.tokensBalance.yes)
|
|
2033
|
-
});
|
|
2034
|
-
}
|
|
2035
|
-
const noCost = parseFloat(clobPos.positions.no.cost);
|
|
2036
|
-
const noValue = parseFloat(clobPos.positions.no.marketValue);
|
|
2037
|
-
if (noCost > 0 || noValue > 0) {
|
|
2038
|
-
positions.push({
|
|
2039
|
-
type: "CLOB",
|
|
2040
|
-
market: clobPos.market,
|
|
2041
|
-
side: "NO",
|
|
2042
|
-
costBasis: noCost,
|
|
2043
|
-
marketValue: noValue,
|
|
2044
|
-
unrealizedPnl: parseFloat(clobPos.positions.no.unrealizedPnl),
|
|
2045
|
-
realizedPnl: parseFloat(clobPos.positions.no.realisedPnl),
|
|
2046
|
-
currentPrice: clobPos.latestTrade?.latestNoPrice ?? 0,
|
|
2047
|
-
avgPrice: noCost > 0 ? parseFloat(clobPos.positions.no.fillPrice) / 1e6 : 0,
|
|
2048
|
-
tokenBalance: parseFloat(clobPos.tokensBalance.no)
|
|
2049
|
-
});
|
|
2050
|
-
}
|
|
2051
|
-
}
|
|
2052
|
-
for (const ammPos of response.amm || []) {
|
|
2053
|
-
const cost = parseFloat(ammPos.totalBuysCost);
|
|
2054
|
-
const value = parseFloat(ammPos.collateralAmount);
|
|
2055
|
-
if (cost > 0 || value > 0) {
|
|
2056
|
-
positions.push({
|
|
2057
|
-
type: "AMM",
|
|
2058
|
-
market: ammPos.market,
|
|
2059
|
-
side: ammPos.outcomeIndex === 0 ? "YES" : "NO",
|
|
2060
|
-
costBasis: cost,
|
|
2061
|
-
marketValue: value,
|
|
2062
|
-
unrealizedPnl: parseFloat(ammPos.unrealizedPnl),
|
|
2063
|
-
realizedPnl: parseFloat(ammPos.realizedPnl),
|
|
2064
|
-
currentPrice: ammPos.latestTrade ? parseFloat(ammPos.latestTrade.outcomeTokenPrice) : 0,
|
|
2065
|
-
avgPrice: parseFloat(ammPos.averageFillPrice),
|
|
2066
|
-
tokenBalance: parseFloat(ammPos.outcomeTokenAmount)
|
|
2067
|
-
});
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
this.logger.debug("Flattened positions", { count: positions.length });
|
|
2071
|
-
return positions;
|
|
1848
|
+
async signOrder(order) {
|
|
1849
|
+
return await this.orderSigner.signOrder(order, this.signingConfig);
|
|
2072
1850
|
}
|
|
2073
1851
|
/**
|
|
2074
|
-
*
|
|
1852
|
+
* Gets the wallet address.
|
|
2075
1853
|
*
|
|
2076
|
-
* @
|
|
2077
|
-
* @returns Portfolio summary with totals and statistics
|
|
1854
|
+
* @returns Ethereum address of the wallet
|
|
2078
1855
|
*
|
|
2079
1856
|
* @example
|
|
2080
1857
|
* ```typescript
|
|
2081
|
-
* const
|
|
2082
|
-
*
|
|
2083
|
-
*
|
|
2084
|
-
* console.log(`Total Portfolio Value: $${(summary.totalValue / 1e6).toFixed(2)}`);
|
|
2085
|
-
* console.log(`Total P&L: ${summary.totalUnrealizedPnlPercent.toFixed(2)}%`);
|
|
2086
|
-
* console.log(`CLOB Positions: ${summary.breakdown.clob.positions}`);
|
|
2087
|
-
* console.log(`AMM Positions: ${summary.breakdown.amm.positions}`);
|
|
1858
|
+
* const address = orderClient.walletAddress;
|
|
1859
|
+
* console.log(`Wallet: ${address}`);
|
|
2088
1860
|
* ```
|
|
2089
1861
|
*/
|
|
2090
|
-
|
|
2091
|
-
this.
|
|
2092
|
-
clobCount: response.clob?.length || 0,
|
|
2093
|
-
ammCount: response.amm?.length || 0
|
|
2094
|
-
});
|
|
2095
|
-
let totalValue = 0;
|
|
2096
|
-
let totalCostBasis = 0;
|
|
2097
|
-
let totalUnrealizedPnl = 0;
|
|
2098
|
-
let totalRealizedPnl = 0;
|
|
2099
|
-
let clobPositions = 0;
|
|
2100
|
-
let clobValue = 0;
|
|
2101
|
-
let clobPnl = 0;
|
|
2102
|
-
let ammPositions = 0;
|
|
2103
|
-
let ammValue = 0;
|
|
2104
|
-
let ammPnl = 0;
|
|
2105
|
-
for (const clobPos of response.clob || []) {
|
|
2106
|
-
const yesCost = parseFloat(clobPos.positions.yes.cost);
|
|
2107
|
-
const yesValue = parseFloat(clobPos.positions.yes.marketValue);
|
|
2108
|
-
const yesUnrealizedPnl = parseFloat(clobPos.positions.yes.unrealizedPnl);
|
|
2109
|
-
const yesRealizedPnl = parseFloat(clobPos.positions.yes.realisedPnl);
|
|
2110
|
-
if (yesCost > 0 || yesValue > 0) {
|
|
2111
|
-
clobPositions++;
|
|
2112
|
-
totalCostBasis += yesCost;
|
|
2113
|
-
totalValue += yesValue;
|
|
2114
|
-
totalUnrealizedPnl += yesUnrealizedPnl;
|
|
2115
|
-
totalRealizedPnl += yesRealizedPnl;
|
|
2116
|
-
clobValue += yesValue;
|
|
2117
|
-
clobPnl += yesUnrealizedPnl;
|
|
2118
|
-
}
|
|
2119
|
-
const noCost = parseFloat(clobPos.positions.no.cost);
|
|
2120
|
-
const noValue = parseFloat(clobPos.positions.no.marketValue);
|
|
2121
|
-
const noUnrealizedPnl = parseFloat(clobPos.positions.no.unrealizedPnl);
|
|
2122
|
-
const noRealizedPnl = parseFloat(clobPos.positions.no.realisedPnl);
|
|
2123
|
-
if (noCost > 0 || noValue > 0) {
|
|
2124
|
-
clobPositions++;
|
|
2125
|
-
totalCostBasis += noCost;
|
|
2126
|
-
totalValue += noValue;
|
|
2127
|
-
totalUnrealizedPnl += noUnrealizedPnl;
|
|
2128
|
-
totalRealizedPnl += noRealizedPnl;
|
|
2129
|
-
clobValue += noValue;
|
|
2130
|
-
clobPnl += noUnrealizedPnl;
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
for (const ammPos of response.amm || []) {
|
|
2134
|
-
const cost = parseFloat(ammPos.totalBuysCost);
|
|
2135
|
-
const value = parseFloat(ammPos.collateralAmount);
|
|
2136
|
-
const unrealizedPnl = parseFloat(ammPos.unrealizedPnl);
|
|
2137
|
-
const realizedPnl = parseFloat(ammPos.realizedPnl);
|
|
2138
|
-
if (cost > 0 || value > 0) {
|
|
2139
|
-
ammPositions++;
|
|
2140
|
-
totalCostBasis += cost;
|
|
2141
|
-
totalValue += value;
|
|
2142
|
-
totalUnrealizedPnl += unrealizedPnl;
|
|
2143
|
-
totalRealizedPnl += realizedPnl;
|
|
2144
|
-
ammValue += value;
|
|
2145
|
-
ammPnl += unrealizedPnl;
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
const totalUnrealizedPnlPercent = totalCostBasis > 0 ? totalUnrealizedPnl / totalCostBasis * 100 : 0;
|
|
2149
|
-
const uniqueMarkets = /* @__PURE__ */ new Set();
|
|
2150
|
-
for (const pos of response.clob || []) {
|
|
2151
|
-
uniqueMarkets.add(pos.market.id);
|
|
2152
|
-
}
|
|
2153
|
-
for (const pos of response.amm || []) {
|
|
2154
|
-
uniqueMarkets.add(pos.market.id);
|
|
2155
|
-
}
|
|
2156
|
-
const summary = {
|
|
2157
|
-
totalValue,
|
|
2158
|
-
totalCostBasis,
|
|
2159
|
-
totalUnrealizedPnl,
|
|
2160
|
-
totalRealizedPnl,
|
|
2161
|
-
totalUnrealizedPnlPercent,
|
|
2162
|
-
positionCount: clobPositions + ammPositions,
|
|
2163
|
-
marketCount: uniqueMarkets.size,
|
|
2164
|
-
breakdown: {
|
|
2165
|
-
clob: {
|
|
2166
|
-
positions: clobPositions,
|
|
2167
|
-
value: clobValue,
|
|
2168
|
-
pnl: clobPnl
|
|
2169
|
-
},
|
|
2170
|
-
amm: {
|
|
2171
|
-
positions: ammPositions,
|
|
2172
|
-
value: ammValue,
|
|
2173
|
-
pnl: ammPnl
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
};
|
|
2177
|
-
this.logger.debug("Portfolio summary calculated", summary);
|
|
2178
|
-
return summary;
|
|
1862
|
+
get walletAddress() {
|
|
1863
|
+
return this.wallet.address;
|
|
2179
1864
|
}
|
|
2180
1865
|
/**
|
|
2181
|
-
* Gets
|
|
1866
|
+
* Gets the owner ID (user ID from profile).
|
|
2182
1867
|
*
|
|
2183
|
-
* @returns
|
|
2184
|
-
* @throws Error if API request fails or user is not authenticated
|
|
1868
|
+
* @returns Owner ID from user profile, or undefined if not yet loaded
|
|
2185
1869
|
*
|
|
2186
1870
|
* @example
|
|
2187
1871
|
* ```typescript
|
|
2188
|
-
* const
|
|
2189
|
-
*
|
|
2190
|
-
*
|
|
2191
|
-
*
|
|
2192
|
-
* console.log(` Total P&L: $${(summary.totalUnrealizedPnl / 1e6).toFixed(2)}`);
|
|
2193
|
-
* console.log(` P&L %: ${summary.totalUnrealizedPnlPercent.toFixed(2)}%`);
|
|
2194
|
-
* console.log(`\nCLOB Positions: ${response.clob.length}`);
|
|
2195
|
-
* console.log(`AMM Positions: ${response.amm.length}`);
|
|
1872
|
+
* const ownerId = orderClient.ownerId;
|
|
1873
|
+
* if (ownerId) {
|
|
1874
|
+
* console.log(`Owner ID: ${ownerId}`);
|
|
1875
|
+
* }
|
|
2196
1876
|
* ```
|
|
2197
1877
|
*/
|
|
2198
|
-
|
|
2199
|
-
this.
|
|
2200
|
-
const response = await this.getPositions();
|
|
2201
|
-
const summary = this.calculateSummary(response);
|
|
2202
|
-
this.logger.info("Portfolio fetched with summary", {
|
|
2203
|
-
positionCount: summary.positionCount,
|
|
2204
|
-
totalValueUSDC: summary.totalValue / 1e6,
|
|
2205
|
-
pnlPercent: summary.totalUnrealizedPnlPercent
|
|
2206
|
-
});
|
|
2207
|
-
return { response, summary };
|
|
1878
|
+
get ownerId() {
|
|
1879
|
+
return this.cachedUserData?.userId;
|
|
2208
1880
|
}
|
|
2209
1881
|
};
|
|
2210
1882
|
|
|
@@ -2225,7 +1897,7 @@ var WebSocketClient = class {
|
|
|
2225
1897
|
this.pendingListeners = [];
|
|
2226
1898
|
this.config = {
|
|
2227
1899
|
url: config.url || DEFAULT_WS_URL,
|
|
2228
|
-
|
|
1900
|
+
apiKey: config.apiKey || process.env.LIMITLESS_API_KEY || "",
|
|
2229
1901
|
autoReconnect: config.autoReconnect ?? true,
|
|
2230
1902
|
reconnectDelay: config.reconnectDelay || 1e3,
|
|
2231
1903
|
maxReconnectAttempts: config.maxReconnectAttempts || Infinity,
|
|
@@ -2250,18 +1922,29 @@ var WebSocketClient = class {
|
|
|
2250
1922
|
return this.state === "connected" /* CONNECTED */ && this.socket?.connected === true;
|
|
2251
1923
|
}
|
|
2252
1924
|
/**
|
|
2253
|
-
* Sets the
|
|
1925
|
+
* Sets the API key for authentication.
|
|
2254
1926
|
*
|
|
2255
|
-
* @param
|
|
1927
|
+
* @param apiKey - API key value
|
|
1928
|
+
*
|
|
1929
|
+
* @remarks
|
|
1930
|
+
* API key is required for authenticated subscriptions (positions, transactions).
|
|
1931
|
+
* If already connected, this will trigger a reconnection with the new API key.
|
|
2256
1932
|
*/
|
|
2257
|
-
|
|
2258
|
-
this.config.
|
|
1933
|
+
setApiKey(apiKey) {
|
|
1934
|
+
this.config.apiKey = apiKey;
|
|
2259
1935
|
if (this.socket?.connected) {
|
|
2260
|
-
this.logger.info("
|
|
2261
|
-
this.
|
|
2262
|
-
this.connect();
|
|
1936
|
+
this.logger.info("API key updated, reconnecting...");
|
|
1937
|
+
this.reconnectWithNewAuth();
|
|
2263
1938
|
}
|
|
2264
1939
|
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Reconnects with new authentication credentials.
|
|
1942
|
+
* @internal
|
|
1943
|
+
*/
|
|
1944
|
+
async reconnectWithNewAuth() {
|
|
1945
|
+
await this.disconnect();
|
|
1946
|
+
await this.connect();
|
|
1947
|
+
}
|
|
2265
1948
|
/**
|
|
2266
1949
|
* Connects to the WebSocket server.
|
|
2267
1950
|
*
|
|
@@ -2275,8 +1958,8 @@ var WebSocketClient = class {
|
|
|
2275
1958
|
* ```
|
|
2276
1959
|
*/
|
|
2277
1960
|
async connect() {
|
|
2278
|
-
if (this.socket?.connected) {
|
|
2279
|
-
this.logger.info("Already connected");
|
|
1961
|
+
if (this.socket?.connected || this.state === "connecting" /* CONNECTING */) {
|
|
1962
|
+
this.logger.info("Already connected or connecting");
|
|
2280
1963
|
return;
|
|
2281
1964
|
}
|
|
2282
1965
|
this.logger.info("Connecting to WebSocket", { url: this.config.url });
|
|
@@ -2285,18 +1968,26 @@ var WebSocketClient = class {
|
|
|
2285
1968
|
const timeout = setTimeout(() => {
|
|
2286
1969
|
reject(new Error(`Connection timeout after ${this.config.timeout}ms`));
|
|
2287
1970
|
}, this.config.timeout);
|
|
2288
|
-
const wsUrl = this.config.url
|
|
2289
|
-
|
|
1971
|
+
const wsUrl = this.config.url;
|
|
1972
|
+
const socketOptions = {
|
|
2290
1973
|
transports: ["websocket"],
|
|
2291
1974
|
// Use WebSocket transport only
|
|
2292
|
-
extraHeaders: {
|
|
2293
|
-
cookie: `limitless_session=${this.config.sessionCookie}`
|
|
2294
|
-
},
|
|
2295
1975
|
reconnection: this.config.autoReconnect,
|
|
2296
1976
|
reconnectionDelay: this.config.reconnectDelay,
|
|
2297
|
-
|
|
1977
|
+
reconnectionDelayMax: Math.min(this.config.reconnectDelay * 32, 6e4),
|
|
1978
|
+
// Max 60s
|
|
1979
|
+
reconnectionAttempts: this.config.maxReconnectAttempts === Infinity ? 0 : this.config.maxReconnectAttempts,
|
|
1980
|
+
// 0 = infinite
|
|
1981
|
+
randomizationFactor: 0.2,
|
|
1982
|
+
// Add jitter to prevent thundering herd
|
|
2298
1983
|
timeout: this.config.timeout
|
|
2299
|
-
}
|
|
1984
|
+
};
|
|
1985
|
+
if (this.config.apiKey) {
|
|
1986
|
+
socketOptions.extraHeaders = {
|
|
1987
|
+
"X-API-Key": this.config.apiKey
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
this.socket = (0, import_socket.io)(wsUrl + "/markets", socketOptions);
|
|
2300
1991
|
this.attachPendingListeners();
|
|
2301
1992
|
this.setupEventHandlers();
|
|
2302
1993
|
this.socket.once("connect", () => {
|
|
@@ -2318,12 +2009,14 @@ var WebSocketClient = class {
|
|
|
2318
2009
|
/**
|
|
2319
2010
|
* Disconnects from the WebSocket server.
|
|
2320
2011
|
*
|
|
2012
|
+
* @returns Promise that resolves when disconnected
|
|
2013
|
+
*
|
|
2321
2014
|
* @example
|
|
2322
2015
|
* ```typescript
|
|
2323
|
-
* wsClient.disconnect();
|
|
2016
|
+
* await wsClient.disconnect();
|
|
2324
2017
|
* ```
|
|
2325
2018
|
*/
|
|
2326
|
-
disconnect() {
|
|
2019
|
+
async disconnect() {
|
|
2327
2020
|
if (!this.socket) {
|
|
2328
2021
|
return;
|
|
2329
2022
|
}
|
|
@@ -2338,13 +2031,13 @@ var WebSocketClient = class {
|
|
|
2338
2031
|
*
|
|
2339
2032
|
* @param channel - Channel to subscribe to
|
|
2340
2033
|
* @param options - Subscription options
|
|
2341
|
-
* @returns Promise that resolves
|
|
2034
|
+
* @returns Promise that resolves immediately (kept async for API compatibility)
|
|
2342
2035
|
* @throws Error if not connected
|
|
2343
2036
|
*
|
|
2344
2037
|
* @example
|
|
2345
2038
|
* ```typescript
|
|
2346
2039
|
* // Subscribe to orderbook for a specific market
|
|
2347
|
-
* await wsClient.subscribe('orderbook', {
|
|
2040
|
+
* await wsClient.subscribe('orderbook', { marketSlugs: ['market-123'] });
|
|
2348
2041
|
*
|
|
2349
2042
|
* // Subscribe to all trades
|
|
2350
2043
|
* await wsClient.subscribe('trades');
|
|
@@ -2357,21 +2050,20 @@ var WebSocketClient = class {
|
|
|
2357
2050
|
if (!this.isConnected()) {
|
|
2358
2051
|
throw new Error("WebSocket not connected. Call connect() first.");
|
|
2359
2052
|
}
|
|
2053
|
+
const authenticatedChannels = [
|
|
2054
|
+
"subscribe_positions",
|
|
2055
|
+
"subscribe_transactions"
|
|
2056
|
+
];
|
|
2057
|
+
if (authenticatedChannels.includes(channel) && !this.config.apiKey) {
|
|
2058
|
+
throw new Error(
|
|
2059
|
+
`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`
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2360
2062
|
const subscriptionKey = this.getSubscriptionKey(channel, options);
|
|
2361
2063
|
this.subscriptions.set(subscriptionKey, options);
|
|
2362
2064
|
this.logger.info("Subscribing to channel", { channel, options });
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
if (response?.error) {
|
|
2366
|
-
this.logger.error("Subscription failed", response.error);
|
|
2367
|
-
this.subscriptions.delete(subscriptionKey);
|
|
2368
|
-
reject(new Error(response.error));
|
|
2369
|
-
} else {
|
|
2370
|
-
this.logger.info("Subscribed successfully", { channel, options });
|
|
2371
|
-
resolve();
|
|
2372
|
-
}
|
|
2373
|
-
});
|
|
2374
|
-
});
|
|
2065
|
+
this.socket.emit(channel, options);
|
|
2066
|
+
this.logger.info("Subscription request sent", { channel, options });
|
|
2375
2067
|
}
|
|
2376
2068
|
/**
|
|
2377
2069
|
* Unsubscribes from a channel.
|
|
@@ -2379,10 +2071,11 @@ var WebSocketClient = class {
|
|
|
2379
2071
|
* @param channel - Channel to unsubscribe from
|
|
2380
2072
|
* @param options - Subscription options (must match subscribe call)
|
|
2381
2073
|
* @returns Promise that resolves when unsubscribed
|
|
2074
|
+
* @throws Error if not connected or unsubscribe fails
|
|
2382
2075
|
*
|
|
2383
2076
|
* @example
|
|
2384
2077
|
* ```typescript
|
|
2385
|
-
* await wsClient.unsubscribe('orderbook', {
|
|
2078
|
+
* await wsClient.unsubscribe('orderbook', { marketSlugs: ['market-123'] });
|
|
2386
2079
|
* ```
|
|
2387
2080
|
*/
|
|
2388
2081
|
async unsubscribe(channel, options = {}) {
|
|
@@ -2392,17 +2085,19 @@ var WebSocketClient = class {
|
|
|
2392
2085
|
const subscriptionKey = this.getSubscriptionKey(channel, options);
|
|
2393
2086
|
this.subscriptions.delete(subscriptionKey);
|
|
2394
2087
|
this.logger.info("Unsubscribing from channel", { channel, options });
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2088
|
+
try {
|
|
2089
|
+
const unsubscribeData = { channel, ...options };
|
|
2090
|
+
const response = await this.socket.timeout(5e3).emitWithAck("unsubscribe", unsubscribeData);
|
|
2091
|
+
if (response && typeof response === "object" && "error" in response) {
|
|
2092
|
+
const errorMsg = response.error;
|
|
2093
|
+
this.logger.error("Unsubscribe failed", new Error(errorMsg), { error: errorMsg });
|
|
2094
|
+
throw new Error(`Unsubscribe failed: ${errorMsg}`);
|
|
2095
|
+
}
|
|
2096
|
+
this.logger.info("Unsubscribed successfully", { channel, options });
|
|
2097
|
+
} catch (error) {
|
|
2098
|
+
this.logger.error("Unsubscribe error", error, { channel });
|
|
2099
|
+
throw error;
|
|
2100
|
+
}
|
|
2406
2101
|
}
|
|
2407
2102
|
/**
|
|
2408
2103
|
* Registers an event listener.
|
|
@@ -2445,14 +2140,27 @@ var WebSocketClient = class {
|
|
|
2445
2140
|
* Removes an event listener.
|
|
2446
2141
|
*
|
|
2447
2142
|
* @param event - Event name
|
|
2448
|
-
* @param handler - Event handler to remove
|
|
2143
|
+
* @param handler - Event handler to remove (if undefined, removes all handlers for event)
|
|
2449
2144
|
* @returns This client for chaining
|
|
2145
|
+
*
|
|
2146
|
+
* @example
|
|
2147
|
+
* ```typescript
|
|
2148
|
+
* // Remove specific handler
|
|
2149
|
+
* wsClient.off('orderbookUpdate', myHandler);
|
|
2150
|
+
*
|
|
2151
|
+
* // Remove all handlers for event
|
|
2152
|
+
* wsClient.off('orderbookUpdate');
|
|
2153
|
+
* ```
|
|
2450
2154
|
*/
|
|
2451
2155
|
off(event, handler) {
|
|
2452
2156
|
if (!this.socket) {
|
|
2453
2157
|
return this;
|
|
2454
2158
|
}
|
|
2455
|
-
|
|
2159
|
+
if (handler === void 0) {
|
|
2160
|
+
this.socket.removeAllListeners(event);
|
|
2161
|
+
} else {
|
|
2162
|
+
this.socket.off(event, handler);
|
|
2163
|
+
}
|
|
2456
2164
|
return this;
|
|
2457
2165
|
}
|
|
2458
2166
|
/**
|
|
@@ -2545,8 +2253,7 @@ var WebSocketClient = class {
|
|
|
2545
2253
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2546
2254
|
0 && (module.exports = {
|
|
2547
2255
|
APIError,
|
|
2548
|
-
|
|
2549
|
-
Authenticator,
|
|
2256
|
+
AuthenticationError,
|
|
2550
2257
|
BASE_SEPOLIA_CHAIN_ID,
|
|
2551
2258
|
CONTRACT_ADDRESSES,
|
|
2552
2259
|
ConsoleLogger,
|
|
@@ -2554,15 +2261,16 @@ var WebSocketClient = class {
|
|
|
2554
2261
|
DEFAULT_CHAIN_ID,
|
|
2555
2262
|
DEFAULT_WS_URL,
|
|
2556
2263
|
HttpClient,
|
|
2264
|
+
Market,
|
|
2557
2265
|
MarketFetcher,
|
|
2558
|
-
MarketType,
|
|
2559
|
-
MessageSigner,
|
|
2560
2266
|
NoOpLogger,
|
|
2561
2267
|
OrderBuilder,
|
|
2562
2268
|
OrderClient,
|
|
2563
2269
|
OrderSigner,
|
|
2564
2270
|
OrderType,
|
|
2271
|
+
OrderValidationError,
|
|
2565
2272
|
PortfolioFetcher,
|
|
2273
|
+
RateLimitError,
|
|
2566
2274
|
RetryConfig,
|
|
2567
2275
|
RetryableClient,
|
|
2568
2276
|
SIGNING_MESSAGE_TEMPLATE,
|