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