pmxt-core 0.0.1 → 1.0.0-b3

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.
@@ -0,0 +1,515 @@
1
+ # Prediction Market API Reference
2
+
3
+ A unified interface for interacting with multiple prediction market exchanges (Kalshi, Polymarket) identically.
4
+
5
+ ## Installation & Usage
6
+
7
+ ```bash
8
+ npm install pmxtjs
9
+ ```
10
+
11
+ ### Basic Import (CommonJS)
12
+
13
+ ```typescript
14
+ import pmxt from 'pmxtjs';
15
+
16
+ // Both PascalCase and lowercase work
17
+ const poly = new pmxt.Polymarket();
18
+ const kalshi = new pmxt.Kalshi();
19
+
20
+ // Or lowercase if you prefer
21
+ const poly2 = new pmxt.polymarket();
22
+ const kalshi2 = new pmxt.kalshi();
23
+ ```
24
+
25
+ ### Note for ESM Users
26
+
27
+ **pmxt is currently CommonJS-only.** If you're using `"type": "module"` in your `package.json`, you have two options:
28
+
29
+ **Option 1: Default import (recommended)**
30
+ ```typescript
31
+ import pmxt from 'pmxtjs';
32
+ const poly = new pmxt.polymarket();
33
+ ```
34
+
35
+ **Option 2: Dynamic import**
36
+ ```typescript
37
+ const pmxt = await import('pmxtjs');
38
+ const poly = new pmxt.default.polymarket();
39
+ ```
40
+
41
+ **Note**: Named exports like `import { polymarket } from 'pmxtjs'` will **not work** in ESM projects.
42
+
43
+ ---
44
+
45
+ ## Core Methods
46
+
47
+ ### `fetchMarkets(params?)`
48
+ Get active markets from an exchange.
49
+
50
+ ```typescript
51
+ const markets = await polymarket.fetchMarkets({
52
+ limit: 20,
53
+ offset: 0,
54
+ sort: 'volume' // 'volume' | 'liquidity' | 'newest'
55
+ });
56
+ ```
57
+
58
+ ### `searchMarkets(query, params?)`
59
+ Search markets by keyword. By default, searches only in titles.
60
+
61
+ ```typescript
62
+ const results = await kalshi.searchMarkets('Fed rates', {
63
+ limit: 10,
64
+ searchIn: 'title' // 'title' (default) | 'description' | 'both'
65
+ });
66
+ ```
67
+
68
+ ### `getMarketsBySlug(slug)`
69
+ Fetch markets by URL slug/ticker.
70
+
71
+ ```typescript
72
+ // Polymarket: use URL slug
73
+ const polyMarkets = await polymarket.getMarketsBySlug('who-will-trump-nominate-as-fed-chair');
74
+
75
+ // Kalshi: use market ticker (auto-uppercased)
76
+ const kalshiMarkets = await kalshi.getMarketsBySlug('KXFEDCHAIRNOM-29');
77
+ ```
78
+
79
+ #### Universal Slug/Ticker Reference
80
+
81
+ | Platform | Example Market URL | What to extract (Slug/Ticker) | Logic |
82
+ |---|---|---|---|
83
+ | **Kalshi** | `kalshi.com/markets/kxfedchairnom/.../kxfedchairnom-29` | `KXFEDCHAIRNOM-29` | The **last** path segment of the URL. |
84
+ | **Polymarket** | `polymarket.com/event/who-will-trump-nominate-as-fed-chair` | `who-will-trump-nominate-as-fed-chair` | The slug immediately after `/event/`. |
85
+
86
+ ---
87
+
88
+ ## Deep-Dive Methods
89
+
90
+ ### `fetchOHLCV(outcomeId, params)`
91
+ Get historical price candles.
92
+
93
+ **CRITICAL**: Use `outcome.id`, not `market.id`.
94
+ - **Polymarket**: `outcome.id` is the CLOB Token ID
95
+ - **Kalshi**: `outcome.id` is the Market Ticker
96
+
97
+ ```typescript
98
+ const markets = await polymarket.searchMarkets('Trump');
99
+ const outcomeId = markets[0].outcomes[0].id; // Get the outcome ID
100
+
101
+ const candles = await polymarket.fetchOHLCV(outcomeId, {
102
+ resolution: '1h', // '1m' | '5m' | '15m' | '1h' | '6h' | '1d'
103
+ start: new Date('2024-01-01'),
104
+ end: new Date('2024-01-31'),
105
+ limit: 100
106
+ });
107
+ ```
108
+
109
+ ### `fetchOrderBook(outcomeId)`
110
+ Get current bids/asks.
111
+
112
+ ```typescript
113
+ const orderBook = await kalshi.fetchOrderBook('FED-25JAN');
114
+ console.log('Best bid:', orderBook.bids[0].price);
115
+ console.log('Best ask:', orderBook.asks[0].price);
116
+ ```
117
+
118
+ ### `fetchTrades(outcomeId, params)`
119
+ Get trade history.
120
+
121
+ **Note**: Polymarket requires API key. Use `fetchOHLCV` for public historical data.
122
+
123
+ ```typescript
124
+ const trades = await kalshi.fetchTrades('FED-25JAN', {
125
+ resolution: '1h',
126
+ limit: 100
127
+ });
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Data Models
133
+
134
+ ### `UnifiedMarket`
135
+ ```typescript
136
+ {
137
+ id: string; // Market ID
138
+ title: string;
139
+ description: string;
140
+ outcomes: MarketOutcome[]; // All tradeable outcomes
141
+
142
+ resolutionDate: Date;
143
+ volume24h: number; // USD
144
+ volume?: number; // Total volume (USD)
145
+ liquidity: number; // USD
146
+ openInterest?: number; // USD
147
+
148
+ url: string;
149
+ image?: string;
150
+ category?: string;
151
+ tags?: string[];
152
+ }
153
+ ```
154
+
155
+ ### `MarketOutcome`
156
+ ```typescript
157
+ {
158
+ id: string; // Use this for fetchOHLCV/fetchOrderBook/fetchTrades
159
+ // Polymarket: CLOB Token ID
160
+ // Kalshi: Market Ticker
161
+ label: string; // "Trump", "Yes", etc.
162
+ price: number; // 0.0 to 1.0 (probability)
163
+ priceChange24h?: number;
164
+ metadata?: {
165
+ clobTokenId?: string; // Polymarket only
166
+ }
167
+ }
168
+ ```
169
+
170
+ ### Other Types
171
+ ```typescript
172
+ interface PriceCandle {
173
+ timestamp: number; // Unix ms
174
+ open: number; // 0.0 to 1.0
175
+ high: number;
176
+ low: number;
177
+ close: number;
178
+ volume?: number;
179
+ }
180
+
181
+ interface OrderBook {
182
+ bids: OrderLevel[]; // Sorted high to low
183
+ asks: OrderLevel[]; // Sorted low to high
184
+ timestamp?: number;
185
+ }
186
+
187
+ interface OrderLevel {
188
+ price: number; // 0.0 to 1.0
189
+ size: number; // Contracts
190
+ }
191
+
192
+ interface Trade {
193
+ id: string;
194
+ timestamp: number; // Unix ms
195
+ price: number; // 0.0 to 1.0
196
+ amount: number;
197
+ side: 'buy' | 'sell' | 'unknown';
198
+ }
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Complete Workflow Example
204
+
205
+ ```typescript
206
+ // 1. Search for markets
207
+ const markets = await polymarket.searchMarkets('Fed Chair');
208
+ const market = markets[0];
209
+
210
+ // 2. Get outcome details
211
+ const outcome = market.outcomes[0];
212
+ console.log(`${outcome.label}: ${(outcome.price * 100).toFixed(1)}%`);
213
+
214
+ // 3. Fetch historical data (use outcome.id!)
215
+ const candles = await polymarket.fetchOHLCV(outcome.id, {
216
+ resolution: '1d',
217
+ limit: 30
218
+ });
219
+
220
+ // 4. Get current order book
221
+ const orderBook = await polymarket.fetchOrderBook(outcome.id);
222
+ const spread = orderBook.asks[0].price - orderBook.bids[0].price;
223
+ console.log(`Spread: ${(spread * 100).toFixed(2)}%`);
224
+ ```
225
+
226
+ ---
227
+
228
+ ## Exchange Differences
229
+
230
+ | Feature | Polymarket | Kalshi |
231
+ |---------|-----------|--------|
232
+ | **Sorting** | Server-side | Client-side (slower for large sets) |
233
+ | **Market ID** | UUID | Event Ticker (e.g., "PRES-2024") |
234
+ | **Outcome ID** | CLOB Token ID | Market Ticker (e.g., "FED-25JAN") |
235
+ | **OHLCV Quality** | Synthetic (O=H=L=C) | Native (distinct values) |
236
+ | **Auth Required** | Only for `fetchTrades()` | No (all public) |
237
+ | **Slug Format** | lowercase-with-hyphens | UPPERCASE (auto-normalized) |
238
+
239
+ ---
240
+
241
+ ## Error Handling
242
+
243
+ ```typescript
244
+ try {
245
+ const markets = await kalshi.getMarketsBySlug('INVALID-TICKER');
246
+ } catch (error) {
247
+ // Kalshi: "Event not found: INVALID-TICKER"
248
+ // Polymarket: Returns empty array []
249
+ console.error(error.message);
250
+ }
251
+ ```
252
+
253
+ **Common Errors**:
254
+ - `404`: Market/event doesn't exist
255
+ - `401`: Missing API key (Polymarket `fetchTrades`)
256
+ - `429`: Rate limited
257
+ - `500`: Exchange API issue
258
+
259
+ ---
260
+
261
+ ## Type Exports
262
+
263
+ ```typescript
264
+ import pmxt, {
265
+ UnifiedMarket,
266
+ MarketOutcome,
267
+ PriceCandle,
268
+ OrderBook,
269
+ Trade,
270
+ CandleInterval,
271
+ MarketFilterParams,
272
+ HistoryFilterParams
273
+ } from 'pmxtjs';
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Authentication & Trading
279
+
280
+ Both Polymarket and Kalshi support authenticated trading operations. You must provide credentials when initializing the exchange.
281
+
282
+ ### Polymarket Authentication
283
+
284
+ Requires your **Polygon Private Key**. See [Setup Guide](https://github.com/qoery-com/pmxt/blob/main/docs/SETUP_POLYMARKET.md) for details.
285
+
286
+ ```typescript
287
+ import pmxt from 'pmxtjs';
288
+
289
+ const polymarket = new pmxt.Polymarket({
290
+ privateKey: process.env.POLYMARKET_PRIVATE_KEY
291
+ });
292
+ ```
293
+
294
+ ### Kalshi Authentication
295
+
296
+ Requires **API Key** and **Private Key**.
297
+
298
+ ```typescript
299
+ import pmxt from 'pmxtjs';
300
+
301
+ const kalshi = new pmxt.Kalshi({
302
+ apiKey: process.env.KALSHI_API_KEY,
303
+ privateKey: process.env.KALSHI_PRIVATE_KEY
304
+ });
305
+ ```
306
+
307
+ ---
308
+
309
+ ## Account Methods
310
+
311
+ ### `fetchBalance()`
312
+ Get your account balance.
313
+
314
+ ```typescript
315
+ const balances = await polymarket.fetchBalance();
316
+ console.log(balances);
317
+ // [{ currency: 'USDC', total: 1000, available: 950, locked: 50 }]
318
+ ```
319
+
320
+ **Returns**: `Balance[]`
321
+ ```typescript
322
+ interface Balance {
323
+ currency: string; // e.g., 'USDC'
324
+ total: number; // Total balance
325
+ available: number; // Available for trading
326
+ locked: number; // Locked in open orders
327
+ }
328
+ ```
329
+
330
+ ### `fetchPositions()`
331
+ Get your current positions across all markets.
332
+
333
+ ```typescript
334
+ const positions = await kalshi.fetchPositions();
335
+ positions.forEach(pos => {
336
+ console.log(`${pos.outcomeLabel}: ${pos.size} @ $${pos.entryPrice}`);
337
+ console.log(`Unrealized P&L: $${pos.unrealizedPnL}`);
338
+ });
339
+ ```
340
+
341
+ **Returns**: `Position[]`
342
+ ```typescript
343
+ interface Position {
344
+ marketId: string;
345
+ outcomeId: string;
346
+ outcomeLabel: string;
347
+ size: number; // Positive for long, negative for short
348
+ entryPrice: number;
349
+ currentPrice: number;
350
+ unrealizedPnL: number;
351
+ realizedPnL?: number;
352
+ }
353
+ ```
354
+
355
+ ---
356
+
357
+ ## Trading Methods
358
+
359
+ ### `createOrder(params)`
360
+ Place a new order (market or limit).
361
+
362
+ **Limit Order Example**:
363
+ ```typescript
364
+ const order = await polymarket.createOrder({
365
+ marketId: '663583',
366
+ outcomeId: '10991849228756847439673778874175365458450913336396982752046655649803657501964',
367
+ side: 'buy',
368
+ type: 'limit',
369
+ amount: 10, // Number of contracts
370
+ price: 0.55 // Required for limit orders (0.0-1.0)
371
+ });
372
+
373
+ console.log(`Order ${order.id}: ${order.status}`);
374
+ ```
375
+
376
+ **Market Order Example**:
377
+ ```typescript
378
+ const order = await kalshi.createOrder({
379
+ marketId: 'FED-25JAN',
380
+ outcomeId: 'FED-25JAN-YES',
381
+ side: 'sell',
382
+ type: 'market',
383
+ amount: 5 // Price not needed for market orders
384
+ });
385
+ ```
386
+
387
+ **Parameters**: `CreateOrderParams`
388
+ ```typescript
389
+ interface CreateOrderParams {
390
+ marketId: string;
391
+ outcomeId: string; // Use outcome.id from market data
392
+ side: 'buy' | 'sell';
393
+ type: 'market' | 'limit';
394
+ amount: number; // Number of contracts/shares
395
+ price?: number; // Required for limit orders (0.0-1.0)
396
+ }
397
+ ```
398
+
399
+ **Returns**: `Order`
400
+ ```typescript
401
+ interface Order {
402
+ id: string;
403
+ marketId: string;
404
+ outcomeId: string;
405
+ side: 'buy' | 'sell';
406
+ type: 'market' | 'limit';
407
+ price?: number;
408
+ amount: number;
409
+ status: 'pending' | 'open' | 'filled' | 'cancelled' | 'rejected';
410
+ filled: number; // Amount filled so far
411
+ remaining: number; // Amount remaining
412
+ timestamp: number;
413
+ fee?: number;
414
+ }
415
+ ```
416
+
417
+ ### `cancelOrder(orderId)`
418
+ Cancel an open order.
419
+
420
+ ```typescript
421
+ const cancelledOrder = await polymarket.cancelOrder('order-123');
422
+ console.log(cancelledOrder.status); // 'cancelled'
423
+ ```
424
+
425
+ **Returns**: `Order` (with updated status)
426
+
427
+ ### `fetchOrder(orderId)`
428
+ Get details of a specific order.
429
+
430
+ ```typescript
431
+ const order = await kalshi.fetchOrder('order-456');
432
+ console.log(`Filled: ${order.filled}/${order.amount}`);
433
+ ```
434
+
435
+ **Returns**: `Order`
436
+
437
+ ### `fetchOpenOrders(marketId?)`
438
+ Get all open orders, optionally filtered by market.
439
+
440
+ ```typescript
441
+ // All open orders
442
+ const allOrders = await polymarket.fetchOpenOrders();
443
+
444
+ // Open orders for specific market
445
+ const marketOrders = await kalshi.fetchOpenOrders('FED-25JAN');
446
+
447
+ allOrders.forEach(order => {
448
+ console.log(`${order.side} ${order.amount} @ ${order.price}`);
449
+ });
450
+ ```
451
+
452
+ **Returns**: `Order[]`
453
+
454
+ ---
455
+
456
+ ## Complete Trading Workflow
457
+
458
+ ```typescript
459
+ import pmxt from 'pmxtjs';
460
+
461
+ const exchange = new pmxt.Polymarket({
462
+ privateKey: process.env.POLYMARKET_PRIVATE_KEY
463
+ });
464
+
465
+ // 1. Check balance
466
+ const [balance] = await exchange.fetchBalance();
467
+ console.log(`Available: $${balance.available}`);
468
+
469
+ // 2. Search for a market
470
+ const markets = await exchange.searchMarkets('Trump');
471
+ const market = markets[0];
472
+ const outcome = market.outcomes[0];
473
+
474
+ // 3. Place a limit order
475
+ const order = await exchange.createOrder({
476
+ marketId: market.id,
477
+ outcomeId: outcome.id,
478
+ side: 'buy',
479
+ type: 'limit',
480
+ amount: 10,
481
+ price: 0.50
482
+ });
483
+
484
+ console.log(`Order placed: ${order.id}`);
485
+
486
+ // 4. Check order status
487
+ const updatedOrder = await exchange.fetchOrder(order.id);
488
+ console.log(`Status: ${updatedOrder.status}`);
489
+ console.log(`Filled: ${updatedOrder.filled}/${updatedOrder.amount}`);
490
+
491
+ // 5. Cancel if needed
492
+ if (updatedOrder.status === 'open') {
493
+ await exchange.cancelOrder(order.id);
494
+ console.log('Order cancelled');
495
+ }
496
+
497
+ // 6. Check positions
498
+ const positions = await exchange.fetchPositions();
499
+ positions.forEach(pos => {
500
+ console.log(`${pos.outcomeLabel}: ${pos.unrealizedPnL > 0 ? '+' : ''}$${pos.unrealizedPnL.toFixed(2)}`);
501
+ });
502
+ ```
503
+
504
+ ---
505
+
506
+ ## Quick Reference
507
+
508
+ - **Prices**: Always 0.0-1.0 (multiply by 100 for %)
509
+ - **Timestamps**: Unix milliseconds
510
+ - **Volumes**: USD
511
+ - **IDs**: Use `outcome.id` for deep-dive methods, not `market.id`
512
+ - **Authentication**: Required for all trading and account methods
513
+
514
+ For more examples, see [`examples/`](examples/).
515
+
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 qoery.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PMXT Server Launcher
5
+ *
6
+ * This script ensures the PMXT sidecar server is running.
7
+ * It's designed to be called by SDKs in any language (Python, Java, C#, Go, etc.)
8
+ *
9
+ * Behavior:
10
+ * 1. Check if server is already running (via lock file)
11
+ * 2. If running, exit successfully
12
+ * 3. If not running, spawn the server and wait for health check
13
+ * 4. Exit with code 0 on success, 1 on failure
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const os = require('os');
19
+ const { spawn } = require('child_process');
20
+ const http = require('http');
21
+
22
+ const LOCK_FILE = path.join(os.homedir(), '.pmxt', 'server.lock');
23
+ const DEFAULT_PORT = 3847;
24
+ const HEALTH_CHECK_TIMEOUT = 10000; // 10 seconds
25
+ const HEALTH_CHECK_INTERVAL = 100; // 100ms
26
+
27
+ /**
28
+ * Check if the server is currently running
29
+ */
30
+ function isServerRunning() {
31
+ try {
32
+ if (!fs.existsSync(LOCK_FILE)) {
33
+ return false;
34
+ }
35
+
36
+ const lockData = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf-8'));
37
+ const { pid, port } = lockData;
38
+
39
+ // Check if process exists
40
+ try {
41
+ process.kill(pid, 0); // Signal 0 checks existence without killing
42
+ return { running: true, port };
43
+ } catch (err) {
44
+ // Process doesn't exist, remove stale lock file
45
+ fs.unlinkSync(LOCK_FILE);
46
+ return false;
47
+ }
48
+ } catch (err) {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Wait for server to respond to health check
55
+ */
56
+ function waitForHealth(port, timeout = HEALTH_CHECK_TIMEOUT) {
57
+ return new Promise((resolve, reject) => {
58
+ const startTime = Date.now();
59
+
60
+ const checkHealth = () => {
61
+ const req = http.get(`http://localhost:${port}/health`, (res) => {
62
+ if (res.statusCode === 200) {
63
+ resolve(true);
64
+ } else {
65
+ scheduleNextCheck();
66
+ }
67
+ });
68
+
69
+ req.on('error', () => {
70
+ scheduleNextCheck();
71
+ });
72
+
73
+ req.setTimeout(1000);
74
+ };
75
+
76
+ const scheduleNextCheck = () => {
77
+ if (Date.now() - startTime > timeout) {
78
+ reject(new Error('Server health check timeout'));
79
+ } else {
80
+ setTimeout(checkHealth, HEALTH_CHECK_INTERVAL);
81
+ }
82
+ };
83
+
84
+ checkHealth();
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Start the PMXT server
90
+ */
91
+ async function startServer() {
92
+ // 1. Try to find the server binary/script
93
+ let serverCmd = 'pmxt-server';
94
+ let args = [];
95
+
96
+ // Local dev search: Check for ../dist/server/index.js relative to this script
97
+ const localDistServer = path.join(__dirname, '..', 'dist', 'server', 'index.js');
98
+ const localBinServer = path.join(__dirname, 'pmxt-server');
99
+
100
+ if (fs.existsSync(localDistServer)) {
101
+ serverCmd = 'node';
102
+ args = [localDistServer];
103
+ } else if (fs.existsSync(localBinServer)) {
104
+ serverCmd = localBinServer;
105
+ }
106
+
107
+ // Spawn server as detached process
108
+ const serverProcess = spawn(serverCmd, args, {
109
+ detached: true,
110
+ stdio: 'ignore',
111
+ env: process.env
112
+ });
113
+
114
+ // Detach from parent process
115
+ serverProcess.unref();
116
+
117
+ // Wait for server to be ready
118
+ await waitForHealth(DEFAULT_PORT);
119
+ }
120
+
121
+ /**
122
+ * Main entry point
123
+ */
124
+ async function main() {
125
+ try {
126
+ // Check if server is already running
127
+ const serverStatus = isServerRunning();
128
+
129
+ if (serverStatus && serverStatus.running) {
130
+ // Server is running, verify it's healthy
131
+ try {
132
+ await waitForHealth(serverStatus.port, 2000);
133
+ process.exit(0);
134
+ } catch (err) {
135
+ // Server process exists but not responding, try to start fresh
136
+ console.error('Server process exists but not responding, starting fresh...');
137
+ }
138
+ }
139
+
140
+ // Start the server
141
+ await startServer();
142
+ process.exit(0);
143
+ } catch (err) {
144
+ console.error('Failed to ensure server is running:', err.message);
145
+ process.exit(1);
146
+ }
147
+ }
148
+
149
+ main();
@@ -1,3 +1,4 @@
1
1
  import { MarketFilterParams } from '../../BaseExchange';
2
2
  import { UnifiedMarket } from '../../types';
3
+ export declare function resetCache(): void;
3
4
  export declare function fetchMarkets(params?: MarketFilterParams): Promise<UnifiedMarket[]>;
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resetCache = resetCache;
6
7
  exports.fetchMarkets = fetchMarkets;
7
8
  const axios_1 = __importDefault(require("axios"));
8
9
  const utils_1 = require("./utils");
@@ -36,12 +37,17 @@ async function fetchActiveEvents(targetMarketCount) {
36
37
  totalMarketCount += (event.markets || []).length;
37
38
  }
38
39
  // Early termination: if we have enough markets, stop fetching
39
- if (totalMarketCount >= targetMarketCount * 2) {
40
+ // Use 1.5x multiplier to ensure we have enough for sorting/filtering
41
+ if (totalMarketCount >= targetMarketCount * 1.5) {
40
42
  break;
41
43
  }
42
44
  }
43
45
  cursor = response.data.cursor;
44
46
  page++;
47
+ // Additional safety: if no target specified, limit to reasonable number of pages
48
+ if (!targetMarketCount && page >= 10) {
49
+ break;
50
+ }
45
51
  }
46
52
  catch (e) {
47
53
  console.error(`Error fetching Kalshi page ${page}:`, e);
@@ -67,18 +73,44 @@ async function fetchSeriesMap() {
67
73
  return new Map();
68
74
  }
69
75
  }
76
+ // Simple in-memory cache to avoid redundant API calls within a short period
77
+ let cachedEvents = null;
78
+ let cachedSeriesMap = null;
79
+ let lastCacheTime = 0;
80
+ const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
81
+ // Export a function to reset the cache (useful for testing)
82
+ function resetCache() {
83
+ cachedEvents = null;
84
+ cachedSeriesMap = null;
85
+ lastCacheTime = 0;
86
+ }
70
87
  async function fetchMarkets(params) {
71
88
  const limit = params?.limit || 50;
89
+ const now = Date.now();
72
90
  try {
73
- // Fetch active events with nested markets
74
- // We also fetch Series metadata to get tags (tags are on Series, not Event)
75
- const [allEvents, seriesMap] = await Promise.all([
76
- fetchActiveEvents(limit),
77
- fetchSeriesMap()
78
- ]);
91
+ let events;
92
+ let seriesMap;
93
+ if (cachedEvents && cachedSeriesMap && (now - lastCacheTime < CACHE_TTL)) {
94
+ events = cachedEvents;
95
+ seriesMap = cachedSeriesMap;
96
+ }
97
+ else {
98
+ // Fetch active events with nested markets
99
+ // We also fetch Series metadata to get tags (tags are on Series, not Event)
100
+ const [allEvents, fetchedSeriesMap] = await Promise.all([
101
+ fetchActiveEvents(limit),
102
+ fetchSeriesMap()
103
+ ]);
104
+ events = allEvents;
105
+ seriesMap = fetchedSeriesMap;
106
+ cachedEvents = allEvents;
107
+ cachedSeriesMap = fetchedSeriesMap;
108
+ lastCacheTime = now;
109
+ }
79
110
  // Extract ALL markets from all events
80
111
  const allMarkets = [];
81
- for (const event of allEvents) {
112
+ // ... rest of the logic
113
+ for (const event of events) {
82
114
  // Enrich event with tags from Series
83
115
  if (event.series_ticker && seriesMap.has(event.series_ticker)) {
84
116
  // If event has no tags or empty tags, use series tags
@@ -8,8 +8,8 @@ const express_1 = __importDefault(require("express"));
8
8
  const cors_1 = __importDefault(require("cors"));
9
9
  const polymarket_1 = require("../exchanges/polymarket");
10
10
  const kalshi_1 = require("../exchanges/kalshi");
11
- // Singleton instances for local usage
12
- const exchanges = {
11
+ // Singleton instances for local usage (when no credentials provided)
12
+ const defaultExchanges = {
13
13
  polymarket: null,
14
14
  kalshi: null
15
15
  };
@@ -22,17 +22,26 @@ async function startServer(port) {
22
22
  res.json({ status: 'ok', timestamp: Date.now() });
23
23
  });
24
24
  // API endpoint: POST /api/:exchange/:method
25
- // Body: { args: any[] }
25
+ // Body: { args: any[], credentials?: ExchangeCredentials }
26
26
  app.post('/api/:exchange/:method', async (req, res, next) => {
27
27
  try {
28
28
  const exchangeName = req.params.exchange.toLowerCase();
29
29
  const methodName = req.params.method;
30
30
  const args = Array.isArray(req.body.args) ? req.body.args : [];
31
+ const credentials = req.body.credentials;
31
32
  // 1. Get or Initialize Exchange
32
- if (!exchanges[exchangeName]) {
33
- exchanges[exchangeName] = createExchange(exchangeName);
33
+ // If credentials are provided, create a new instance for this request
34
+ // Otherwise, use the singleton instance
35
+ let exchange;
36
+ if (credentials && (credentials.privateKey || credentials.apiKey)) {
37
+ exchange = createExchange(exchangeName, credentials);
38
+ }
39
+ else {
40
+ if (!defaultExchanges[exchangeName]) {
41
+ defaultExchanges[exchangeName] = createExchange(exchangeName);
42
+ }
43
+ exchange = defaultExchanges[exchangeName];
34
44
  }
35
- const exchange = exchanges[exchangeName];
36
45
  // 2. Validate Method
37
46
  if (typeof exchange[methodName] !== 'function') {
38
47
  res.status(404).json({ success: false, error: `Method '${methodName}' not found on ${exchangeName}` });
@@ -59,19 +68,19 @@ async function startServer(port) {
59
68
  });
60
69
  return app.listen(port);
61
70
  }
62
- function createExchange(name) {
71
+ function createExchange(name, credentials) {
63
72
  switch (name) {
64
73
  case 'polymarket':
65
74
  return new polymarket_1.PolymarketExchange({
66
- privateKey: process.env.POLYMARKET_PK || process.env.POLYMARKET_PRIVATE_KEY,
67
- apiKey: process.env.POLYMARKET_API_KEY,
68
- apiSecret: process.env.POLYMARKET_API_SECRET,
69
- passphrase: process.env.POLYMARKET_PASSPHRASE
75
+ privateKey: credentials?.privateKey || process.env.POLYMARKET_PK || process.env.POLYMARKET_PRIVATE_KEY,
76
+ apiKey: credentials?.apiKey || process.env.POLYMARKET_API_KEY,
77
+ apiSecret: credentials?.apiSecret || process.env.POLYMARKET_API_SECRET,
78
+ passphrase: credentials?.passphrase || process.env.POLYMARKET_PASSPHRASE
70
79
  });
71
80
  case 'kalshi':
72
81
  return new kalshi_1.KalshiExchange({
73
- apiKey: process.env.KALSHI_API_KEY,
74
- privateKey: process.env.KALSHI_PRIVATE_KEY
82
+ apiKey: credentials?.apiKey || process.env.KALSHI_API_KEY,
83
+ privateKey: credentials?.privateKey || process.env.KALSHI_PRIVATE_KEY
75
84
  });
76
85
  default:
77
86
  throw new Error(`Unknown exchange: ${name}`);
package/package.json CHANGED
@@ -1,11 +1,62 @@
1
1
  {
2
2
  "name": "pmxt-core",
3
- "version": "0.0.1",
4
- "description": "Core library for Prediction Markets Aggregator",
5
- "main": "index.js",
3
+ "version": "1.0.0-b3",
4
+ "description": "pmxt is a unified prediction market data API. The ccxt for prediction markets.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/pmxt-dev/pmxt.git"
10
+ },
11
+ "bin": {
12
+ "pmxt-server": "./dist/server/index.js",
13
+ "pmxt-ensure-server": "./bin/pmxt-ensure-server"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "bin",
18
+ "API_REFERENCE.md"
19
+ ],
20
+ "directories": {
21
+ "example": "examples",
22
+ "test": "test"
23
+ },
6
24
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
25
+ "clean": "rm -rf dist",
26
+ "prebuild": "npm run clean",
27
+ "build": "tsc",
28
+ "test": "jest -c jest.config.js",
29
+ "server": "tsx watch src/server/index.ts",
30
+ "server:prod": "node dist/server/index.js",
31
+ "generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=1.0.0b3,library=urllib3",
32
+ "generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=1.0.0-b3,supportsES6=true,typescriptThreePlus=true",
33
+ "generate:docs": "node ../scripts/generate-api-docs.js",
34
+ "generate:sdk:all": "npm run generate:sdk:python && npm run generate:sdk:typescript && npm run generate:docs"
8
35
  },
36
+ "keywords": [],
9
37
  "author": "",
10
- "license": "ISC"
11
- }
38
+ "license": "MIT",
39
+ "type": "commonjs",
40
+ "dependencies": {
41
+ "@polymarket/clob-client": "^5.2.0",
42
+ "@types/express": "^5.0.6",
43
+ "axios": "^1.7.9",
44
+ "cors": "^2.8.5",
45
+ "dotenv": "^17.2.3",
46
+ "ethers": "^5.8.0",
47
+ "express": "^5.2.1",
48
+ "jest": "^30.2.0",
49
+ "tsx": "^4.21.0"
50
+ },
51
+ "devDependencies": {
52
+ "@openapitools/openapi-generator-cli": "^2.27.0",
53
+ "@types/cors": "^2.8.19",
54
+ "@types/jest": "^30.0.0",
55
+ "@types/node": "^25.0.3",
56
+ "handlebars": "^4.7.8",
57
+ "identity-obj-proxy": "^3.0.0",
58
+ "js-yaml": "^3.14.2",
59
+ "ts-jest": "^29.4.6",
60
+ "typescript": "^5.9.3"
61
+ }
62
+ }
package/.env DELETED
@@ -1,5 +0,0 @@
1
- # Polymarket
2
- POLYMARKET_PRIVATE_KEY=0x331917d429ce4dbbf046c6a29b76b1818b975e6479df4feb396c5881509d6577
3
-
4
- # Kalshi
5
- KALSHI_API_KEY=0x/NA
package/index.js DELETED
@@ -1 +0,0 @@
1
- console.log("pmxt-core placeholder");
Binary file