@alango/dr-manhattan 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +365 -0
- package/dist/index.d.ts +696 -0
- package/dist/index.js +2537 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Taegeon Go (Alan)
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# dr-manhattan
|
|
2
|
+
|
|
3
|
+
CCXT-style unified API for prediction markets in TypeScript.
|
|
4
|
+
|
|
5
|
+
> TypeScript port of [guzus/dr-manhattan](https://github.com/guzus/dr-manhattan) (Python)
|
|
6
|
+
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
[](https://www.typescriptlang.org)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
|
|
11
|
+
## Supported Exchanges
|
|
12
|
+
|
|
13
|
+
| Exchange | REST | WebSocket | Chain |
|
|
14
|
+
|----------|------|-----------|-------|
|
|
15
|
+
| [Polymarket](https://polymarket.com) | ✅ | ✅ | Polygon |
|
|
16
|
+
| [Limitless](https://limitless.exchange) | ✅ | ✅ | Base |
|
|
17
|
+
| [Opinion](https://opinion.trade) | ✅ | ❌ | BNB |
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @alango/dr-manhattan
|
|
23
|
+
# or
|
|
24
|
+
pnpm add @alango/dr-manhattan
|
|
25
|
+
# or
|
|
26
|
+
yarn add @alango/dr-manhattan
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createExchange, listExchanges, MarketUtils } from '@alango/dr-manhattan';
|
|
33
|
+
|
|
34
|
+
// List available exchanges
|
|
35
|
+
console.log(listExchanges()); // ['polymarket', 'limitless', 'opinion']
|
|
36
|
+
|
|
37
|
+
// Create exchange instance (no auth required for public data)
|
|
38
|
+
const polymarket = createExchange('polymarket');
|
|
39
|
+
|
|
40
|
+
// Fetch markets
|
|
41
|
+
const markets = await polymarket.fetchMarkets({ limit: 10 });
|
|
42
|
+
|
|
43
|
+
for (const market of markets) {
|
|
44
|
+
console.log(`${market.question}`);
|
|
45
|
+
console.log(` Volume: $${market.volume.toLocaleString()}`);
|
|
46
|
+
console.log(` Binary: ${MarketUtils.isBinary(market)}`);
|
|
47
|
+
console.log(` Spread: ${MarketUtils.spread(market)?.toFixed(4)}`);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Authentication
|
|
52
|
+
|
|
53
|
+
### Polymarket
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { Polymarket } from '@alango/dr-manhattan';
|
|
57
|
+
|
|
58
|
+
const polymarket = new Polymarket({
|
|
59
|
+
privateKey: process.env.PRIVATE_KEY,
|
|
60
|
+
funder: process.env.FUNDER_ADDRESS, // optional
|
|
61
|
+
chainId: 137, // Polygon (default)
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Create order
|
|
65
|
+
const order = await polymarket.createOrder({
|
|
66
|
+
marketId: 'market-condition-id',
|
|
67
|
+
outcome: 'Yes',
|
|
68
|
+
side: OrderSide.BUY,
|
|
69
|
+
price: 0.65,
|
|
70
|
+
size: 100,
|
|
71
|
+
tokenId: 'outcome-token-id',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Fetch balance
|
|
75
|
+
const balance = await polymarket.fetchBalance();
|
|
76
|
+
console.log(`USDC: ${balance.USDC}`);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Limitless
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { Limitless } from '@alango/dr-manhattan';
|
|
83
|
+
|
|
84
|
+
const limitless = new Limitless({
|
|
85
|
+
privateKey: process.env.PRIVATE_KEY,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Authentication happens automatically via EIP-191/EIP-712 signing
|
|
89
|
+
const positions = await limitless.fetchPositions();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Opinion
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { Opinion } from '@alango/dr-manhattan';
|
|
96
|
+
|
|
97
|
+
const opinion = new Opinion({
|
|
98
|
+
apiKey: process.env.OPINION_API_KEY,
|
|
99
|
+
privateKey: process.env.PRIVATE_KEY,
|
|
100
|
+
multiSigAddr: process.env.MULTI_SIG_ADDR,
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## API Reference
|
|
105
|
+
|
|
106
|
+
### Exchange Methods
|
|
107
|
+
|
|
108
|
+
All exchanges implement these core methods:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
interface Exchange {
|
|
112
|
+
// Market data
|
|
113
|
+
fetchMarkets(params?: FetchMarketsParams): Promise<Market[]>;
|
|
114
|
+
fetchMarket(marketId: string): Promise<Market>;
|
|
115
|
+
|
|
116
|
+
// Orders (requires auth)
|
|
117
|
+
createOrder(params: CreateOrderParams): Promise<Order>;
|
|
118
|
+
cancelOrder(orderId: string, marketId?: string): Promise<Order>;
|
|
119
|
+
fetchOrder(orderId: string, marketId?: string): Promise<Order>;
|
|
120
|
+
fetchOpenOrders(marketId?: string): Promise<Order[]>;
|
|
121
|
+
|
|
122
|
+
// Account (requires auth)
|
|
123
|
+
fetchPositions(marketId?: string): Promise<Position[]>;
|
|
124
|
+
fetchBalance(): Promise<Record<string, number>>;
|
|
125
|
+
|
|
126
|
+
// Utilities
|
|
127
|
+
describe(): { id: string; name: string; has: ExchangeCapabilities };
|
|
128
|
+
findTradeableMarket(options?: { binary?: boolean; minLiquidity?: number }): Promise<Market | null>;
|
|
129
|
+
calculateSpread(market: Market): number | null;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Polymarket-specific Methods
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// Search markets by keyword
|
|
137
|
+
const markets = await polymarket.searchMarkets('bitcoin');
|
|
138
|
+
|
|
139
|
+
// Fetch by slug
|
|
140
|
+
const market = await polymarket.fetchMarketsBySlug('bitcoin-100k');
|
|
141
|
+
|
|
142
|
+
// Get orderbook
|
|
143
|
+
const orderbook = await polymarket.getOrderbook(tokenId);
|
|
144
|
+
|
|
145
|
+
// Fetch price history
|
|
146
|
+
const history = await polymarket.fetchPriceHistory(tokenId, '1d');
|
|
147
|
+
|
|
148
|
+
// Fetch public trades
|
|
149
|
+
const trades = await polymarket.fetchPublicTrades(tokenId, { limit: 50 });
|
|
150
|
+
|
|
151
|
+
// Find crypto hourly markets
|
|
152
|
+
const hourlyMarket = await polymarket.findCryptoHourlyMarket('BTC', 'higher');
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### WebSocket Streaming
|
|
156
|
+
|
|
157
|
+
#### Polymarket WebSocket
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { PolymarketWebSocket, OrderbookUtils } from '@alango/dr-manhattan';
|
|
161
|
+
|
|
162
|
+
const ws = new PolymarketWebSocket();
|
|
163
|
+
|
|
164
|
+
ws.on('open', () => {
|
|
165
|
+
ws.subscribeToOrderbook([tokenId1, tokenId2]);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
ws.on('orderbook', ({ tokenId, orderbook }) => {
|
|
169
|
+
const bid = OrderbookUtils.bestBid(orderbook);
|
|
170
|
+
const ask = OrderbookUtils.bestAsk(orderbook);
|
|
171
|
+
console.log(`[${tokenId}] Bid: ${bid} | Ask: ${ask}`);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
ws.on('error', (err) => console.error(err));
|
|
175
|
+
ws.on('close', () => console.log('Disconnected'));
|
|
176
|
+
|
|
177
|
+
await ws.connect();
|
|
178
|
+
|
|
179
|
+
// Cleanup
|
|
180
|
+
await ws.disconnect();
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### Limitless WebSocket
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { Limitless } from '@alango/dr-manhattan';
|
|
187
|
+
import { LimitlessWebSocket } from '@alango/dr-manhattan/exchanges/limitless';
|
|
188
|
+
|
|
189
|
+
const ws = new LimitlessWebSocket();
|
|
190
|
+
|
|
191
|
+
ws.on('orderbook', ({ marketAddress, orderbook }) => {
|
|
192
|
+
console.log(`[${marketAddress}] Updated`);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
ws.on('price', ({ marketAddress, prices }) => {
|
|
196
|
+
console.log(`Prices:`, prices);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
await ws.connect();
|
|
200
|
+
ws.subscribeToMarket(marketAddress);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Utilities
|
|
204
|
+
|
|
205
|
+
### Market Utilities
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { MarketUtils } from '@alango/dr-manhattan';
|
|
209
|
+
|
|
210
|
+
MarketUtils.isBinary(market); // Has exactly 2 outcomes
|
|
211
|
+
MarketUtils.isOpen(market); // Not closed, not resolved
|
|
212
|
+
MarketUtils.spread(market); // Price spread between outcomes
|
|
213
|
+
MarketUtils.getTokenIds(market); // Extract token IDs
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Orderbook Utilities
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { OrderbookUtils } from '@alango/dr-manhattan';
|
|
220
|
+
|
|
221
|
+
OrderbookUtils.bestBid(orderbook); // Highest bid price
|
|
222
|
+
OrderbookUtils.bestAsk(orderbook); // Lowest ask price
|
|
223
|
+
OrderbookUtils.spread(orderbook); // Ask - Bid
|
|
224
|
+
OrderbookUtils.midPrice(orderbook); // (Bid + Ask) / 2
|
|
225
|
+
OrderbookUtils.totalVolume(orderbook, 'bids'); // Sum of bid sizes
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Position Utilities
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { PositionUtils, calculateDelta } from '@alango/dr-manhattan';
|
|
232
|
+
|
|
233
|
+
PositionUtils.totalValue(positions);
|
|
234
|
+
PositionUtils.totalPnl(positions);
|
|
235
|
+
PositionUtils.filterByMarket(positions, marketId);
|
|
236
|
+
|
|
237
|
+
// Calculate position delta
|
|
238
|
+
const delta = calculateDelta(positions, market);
|
|
239
|
+
// { yes: 100, no: -50, net: 50 }
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Price Utilities
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { roundToTickSize, clampPrice, formatPrice, formatUsd } from '@alango/dr-manhattan';
|
|
246
|
+
|
|
247
|
+
roundToTickSize(0.6543, 0.01); // 0.65
|
|
248
|
+
clampPrice(1.5); // 1.0
|
|
249
|
+
formatPrice(0.6543); // "0.654"
|
|
250
|
+
formatUsd(1234567); // "$1,234,567"
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Error Handling
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import {
|
|
257
|
+
DrManhattanError,
|
|
258
|
+
ExchangeError,
|
|
259
|
+
NetworkError,
|
|
260
|
+
RateLimitError,
|
|
261
|
+
AuthenticationError,
|
|
262
|
+
InsufficientFunds,
|
|
263
|
+
InvalidOrder,
|
|
264
|
+
MarketNotFound,
|
|
265
|
+
} from '@alango/dr-manhattan';
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
await exchange.createOrder(params);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (error instanceof RateLimitError) {
|
|
271
|
+
console.log(`Rate limited, retry after ${error.retryAfter}ms`);
|
|
272
|
+
} else if (error instanceof InsufficientFunds) {
|
|
273
|
+
console.log('Not enough balance');
|
|
274
|
+
} else if (error instanceof InvalidOrder) {
|
|
275
|
+
console.log('Invalid order parameters');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Types
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import type {
|
|
284
|
+
Market,
|
|
285
|
+
OutcomeToken,
|
|
286
|
+
Order,
|
|
287
|
+
CreateOrderParams,
|
|
288
|
+
Position,
|
|
289
|
+
DeltaInfo,
|
|
290
|
+
Orderbook,
|
|
291
|
+
PriceLevel,
|
|
292
|
+
FetchMarketsParams,
|
|
293
|
+
ExchangeConfig,
|
|
294
|
+
ExchangeCapabilities,
|
|
295
|
+
} from '@alango/dr-manhattan';
|
|
296
|
+
|
|
297
|
+
import { OrderSide, OrderStatus } from '@alango/dr-manhattan';
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Adding New Exchanges
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
import { Exchange, type ExchangeConfig } from '@alango/dr-manhattan';
|
|
304
|
+
|
|
305
|
+
class NewExchange extends Exchange {
|
|
306
|
+
readonly id = 'newexchange';
|
|
307
|
+
readonly name = 'New Exchange';
|
|
308
|
+
|
|
309
|
+
async fetchMarkets(params?: FetchMarketsParams): Promise<Market[]> {
|
|
310
|
+
// Implement API call
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async fetchMarket(marketId: string): Promise<Market> {
|
|
314
|
+
// Implement
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ... implement other abstract methods
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Configuration Options
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
interface ExchangeConfig {
|
|
325
|
+
// Authentication
|
|
326
|
+
apiKey?: string;
|
|
327
|
+
apiSecret?: string;
|
|
328
|
+
privateKey?: string;
|
|
329
|
+
funder?: string;
|
|
330
|
+
|
|
331
|
+
// Request settings
|
|
332
|
+
timeout?: number; // Request timeout in ms (default: 30000)
|
|
333
|
+
rateLimit?: number; // Max requests per second (default: 10)
|
|
334
|
+
maxRetries?: number; // Retry count for failed requests (default: 3)
|
|
335
|
+
retryDelay?: number; // Initial retry delay in ms (default: 1000)
|
|
336
|
+
retryBackoff?: number; // Backoff multiplier (default: 2)
|
|
337
|
+
|
|
338
|
+
// Debug
|
|
339
|
+
verbose?: boolean; // Log debug info (default: false)
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Examples
|
|
344
|
+
|
|
345
|
+
See the [examples/](examples/) directory:
|
|
346
|
+
|
|
347
|
+
- **list-markets.ts** - Fetch and display markets
|
|
348
|
+
- **websocket-orderbook.ts** - Real-time orderbook streaming
|
|
349
|
+
- **spread-strategy.ts** - Market making strategy with inventory management
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
# Run examples
|
|
353
|
+
npx tsx examples/list-markets.ts
|
|
354
|
+
npx tsx examples/websocket-orderbook.ts
|
|
355
|
+
npx tsx examples/spread-strategy.ts # Requires PRIVATE_KEY env var for real trades
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Requirements
|
|
359
|
+
|
|
360
|
+
- Node.js >= 20.0.0
|
|
361
|
+
- TypeScript >= 5.0 (for development)
|
|
362
|
+
|
|
363
|
+
## License
|
|
364
|
+
|
|
365
|
+
MIT
|