CCXT-style unified API for prediction markets
npm install @alango/dr-manhattanCCXT-style unified API for prediction markets in TypeScript.
> TypeScript port of guzus/dr-manhattan (Python)



| Exchange | REST | WebSocket | Chain |
|----------|------|-----------|-------|
| Polymarket | ✅ | ✅ | Polygon |
| Limitless | ✅ | ✅ | Base |
| Opinion | ✅ | ❌ | BNB |
| Kalshi | ✅ | ❌ | - |
| Predict.fun | ✅ | ❌ | BNB |
``bash`
npm install @alango/dr-manhattanor
pnpm add @alango/dr-manhattanor
yarn add @alango/dr-manhattan
`typescript
import { createExchange, listExchanges, MarketUtils } from '@alango/dr-manhattan';
// List available exchanges
console.log(listExchanges()); // ['polymarket', 'limitless', 'opinion', 'kalshi', 'predictfun']
// Create exchange instance (no auth required for public data)
const polymarket = createExchange('polymarket');
// Fetch markets
const markets = await polymarket.fetchMarkets({ limit: 10 });
for (const market of markets) {
console.log(${market.question}); Volume: $${market.volume.toLocaleString()}
console.log(); Binary: ${MarketUtils.isBinary(market)}
console.log(); Spread: ${MarketUtils.spread(market)?.toFixed(4)}
console.log();`
}
`typescript
import { Polymarket } from '@alango/dr-manhattan';
const polymarket = new Polymarket({
privateKey: process.env.PRIVATE_KEY,
funder: process.env.FUNDER_ADDRESS, // optional
chainId: 137, // Polygon (default)
});
// Create order
const order = await polymarket.createOrder({
marketId: 'market-condition-id',
outcome: 'Yes',
side: OrderSide.BUY,
price: 0.65,
size: 100,
tokenId: 'outcome-token-id',
});
// Fetch balance
const balance = await polymarket.fetchBalance();
console.log(USDC: ${balance.USDC});`
`typescript
import { Limitless } from '@alango/dr-manhattan';
const limitless = new Limitless({
privateKey: process.env.PRIVATE_KEY,
});
// Authentication happens automatically via EIP-191/EIP-712 signing
const positions = await limitless.fetchPositions();
`
`typescript
import { Opinion } from '@alango/dr-manhattan';
const opinion = new Opinion({
apiKey: process.env.OPINION_API_KEY,
privateKey: process.env.PRIVATE_KEY,
multiSigAddr: process.env.MULTI_SIG_ADDR,
});
`
`typescript
import { Kalshi } from '@alango/dr-manhattan';
// With RSA private key file
const kalshi = new Kalshi({
apiKeyId: process.env.KALSHI_API_KEY_ID,
privateKeyPath: '/path/to/kalshi_private_key.pem',
});
// Or with PEM content directly
const kalshi = new Kalshi({
apiKeyId: process.env.KALSHI_API_KEY_ID,
privateKeyPem: process.env.KALSHI_PRIVATE_KEY_PEM,
});
// Demo environment
const kalshiDemo = new Kalshi({
apiKeyId: process.env.KALSHI_API_KEY_ID,
privateKeyPath: '/path/to/private_key.pem',
demo: true,
});
// Fetch markets (no auth required)
const markets = await kalshi.fetchMarkets({ limit: 10 });
// Create order (auth required)
const order = await kalshi.createOrder({
marketId: 'INXD-24DEC31-B5000',
outcome: 'Yes',
side: OrderSide.BUY,
price: 0.55,
size: 10,
});
`
`typescript
import { PredictFun } from '@alango/dr-manhattan';
// Mainnet
const predictfun = new PredictFun({
apiKey: process.env.PREDICTFUN_API_KEY,
privateKey: process.env.PRIVATE_KEY,
});
// Testnet
const predictfunTestnet = new PredictFun({
apiKey: process.env.PREDICTFUN_API_KEY,
privateKey: process.env.PRIVATE_KEY,
testnet: true,
});
// Fetch markets (no auth required for public data)
const markets = await predictfun.fetchMarkets({ limit: 10 });
// Get orderbook
const orderbook = await predictfun.getOrderbook(marketId);
// Create order (auth required)
const order = await predictfun.createOrder({
marketId: '123',
outcome: 'Yes',
side: OrderSide.BUY,
price: 0.55,
size: 100,
});
// Fetch positions
const positions = await predictfun.fetchPositions();
// Fetch balance
const balance = await predictfun.fetchBalance();
console.log(USDT: ${balance.USDT});`
All exchanges implement these core methods:
`typescript
interface Exchange {
// Market data
fetchMarkets(params?: FetchMarketsParams): Promise
fetchMarket(marketId: string): Promise
// Orders (requires auth)
createOrder(params: CreateOrderParams): Promise
cancelOrder(orderId: string, marketId?: string): Promise
fetchOrder(orderId: string, marketId?: string): Promise
fetchOpenOrders(marketId?: string): Promise
// Account (requires auth)
fetchPositions(marketId?: string): Promise
fetchBalance(): Promise
// Utilities
describe(): { id: string; name: string; has: ExchangeCapabilities };
findTradeableMarket(options?: { binary?: boolean; minLiquidity?: number }): Promise
calculateSpread(market: Market): number | null;
}
`
`typescript
// Search markets by keyword
const markets = await polymarket.searchMarkets('bitcoin');
// Fetch by slug
const market = await polymarket.fetchMarketsBySlug('bitcoin-100k');
// Get orderbook
const orderbook = await polymarket.getOrderbook(tokenId);
// Fetch price history
const history = await polymarket.fetchPriceHistory(tokenId, '1d');
// Fetch public trades
const trades = await polymarket.fetchPublicTrades(tokenId, { limit: 50 });
// Find crypto hourly markets
const hourlyMarket = await polymarket.findCryptoHourlyMarket('BTC', 'higher');
`
#### Polymarket WebSocket
`typescript
import { PolymarketWebSocket, OrderbookUtils } from '@alango/dr-manhattan';
const ws = new PolymarketWebSocket();
ws.on('open', () => {
ws.subscribeToOrderbook([tokenId1, tokenId2]);
});
ws.on('orderbook', ({ tokenId, orderbook }) => {
const bid = OrderbookUtils.bestBid(orderbook);
const ask = OrderbookUtils.bestAsk(orderbook);
console.log([${tokenId}] Bid: ${bid} | Ask: ${ask});
});
ws.on('error', (err) => console.error(err));
ws.on('close', () => console.log('Disconnected'));
await ws.connect();
// Cleanup
await ws.disconnect();
`
#### Limitless WebSocket
`typescript
import { LimitlessWebSocket } from '@alango/dr-manhattan';
const ws = new LimitlessWebSocket();
ws.on('orderbook', ({ marketAddress, orderbook }) => {
console.log([${marketAddress}] Updated);
});
ws.on('price', ({ marketAddress, prices }) => {
console.log(Prices:, prices);
});
await ws.connect();
ws.subscribeToMarket(marketAddress);
`
`typescript
import { MarketUtils } from '@alango/dr-manhattan';
MarketUtils.isBinary(market); // Has exactly 2 outcomes
MarketUtils.isOpen(market); // Not closed, not resolved
MarketUtils.spread(market); // Price spread between outcomes
MarketUtils.getTokenIds(market); // Extract token IDs
`
`typescript
import { OrderbookUtils } from '@alango/dr-manhattan';
OrderbookUtils.bestBid(orderbook); // Highest bid price
OrderbookUtils.bestAsk(orderbook); // Lowest ask price
OrderbookUtils.spread(orderbook); // Ask - Bid
OrderbookUtils.midPrice(orderbook); // (Bid + Ask) / 2
OrderbookUtils.totalVolume(orderbook, 'bids'); // Sum of bid sizes
`
`typescript
import { PositionUtils, calculateDelta } from '@alango/dr-manhattan';
PositionUtils.totalValue(positions);
PositionUtils.totalPnl(positions);
PositionUtils.filterByMarket(positions, marketId);
// Calculate position delta
const delta = calculateDelta(positions, market);
// { yes: 100, no: -50, net: 50 }
`
`typescript
import { roundToTickSize, clampPrice, formatPrice, formatUsd } from '@alango/dr-manhattan';
roundToTickSize(0.6543, 0.01); // 0.65
clampPrice(1.5); // 1.0
formatPrice(0.6543); // "0.654"
formatUsd(1234567); // "$1,234,567"
`
`typescript
import {
DrManhattanError,
ExchangeError,
NetworkError,
RateLimitError,
AuthenticationError,
InsufficientFunds,
InvalidOrder,
MarketNotFound,
} from '@alango/dr-manhattan';
try {
await exchange.createOrder(params);
} catch (error) {
if (error instanceof RateLimitError) {
console.log(Rate limited, retry after ${error.retryAfter}ms);`
} else if (error instanceof InsufficientFunds) {
console.log('Not enough balance');
} else if (error instanceof InvalidOrder) {
console.log('Invalid order parameters');
}
}
`typescript
import type {
Market,
OutcomeToken,
Order,
CreateOrderParams,
Position,
DeltaInfo,
Orderbook,
PriceLevel,
FetchMarketsParams,
ExchangeConfig,
ExchangeCapabilities,
} from '@alango/dr-manhattan';
import { OrderSide, OrderStatus } from '@alango/dr-manhattan';
`
`typescript
import { Exchange, type ExchangeConfig } from '@alango/dr-manhattan';
class NewExchange extends Exchange {
readonly id = 'newexchange';
readonly name = 'New Exchange';
async fetchMarkets(params?: FetchMarketsParams): Promise
// Implement API call
}
async fetchMarket(marketId: string): Promise
// Implement
}
// ... implement other abstract methods
}
`
`typescript
interface ExchangeConfig {
// Authentication
apiKey?: string;
apiSecret?: string;
privateKey?: string;
funder?: string;
// Request settings
timeout?: number; // Request timeout in ms (default: 30000)
rateLimit?: number; // Max requests per second (default: 10)
maxRetries?: number; // Retry count for failed requests (default: 3)
retryDelay?: number; // Initial retry delay in ms (default: 1000)
retryBackoff?: number; // Backoff multiplier (default: 2)
// Debug
verbose?: boolean; // Log debug info (default: false)
}
`
See the examples/ directory:
| Example | Description | Exchanges |
|---------|-------------|-----------|
| list-markets.ts | Fetch and display markets from all exchanges | All |
| websocket-orderbook.ts | Real-time orderbook streaming via WebSocket | Polymarket |
| spread-strategy.ts | Market making strategy with inventory management | All |
| spike-strategy.ts | Mean reversion strategy - buys price spikes | All |
| weather-bot-strategy.ts | London temperature bucket mispricing strategy | Polymarket |
`bashList markets from all exchanges
npx tsx examples/list-markets.ts
- Node.js >= 20.0.0
- TypeScript >= 5.0 (for development)
MIT