@alango/dr-manhattan
v0.1.6
Published
CCXT-style unified API for prediction markets
Maintainers
Readme
dr-manhattan
CCXT-style unified API for prediction markets in TypeScript.
TypeScript port of guzus/dr-manhattan (Python)
Supported Exchanges
| Exchange | REST | WebSocket | Chain | |----------|------|-----------|-------| | Polymarket | ✅ | ✅ | Polygon | | Limitless | ✅ | ✅ | Base | | Opinion | ✅ | ❌ | BNB | | Kalshi | ✅ | ❌ | - | | Predict.fun | ✅ | ❌ | BNB |
Installation
npm install @alango/dr-manhattan
# or
pnpm add @alango/dr-manhattan
# or
yarn add @alango/dr-manhattanQuick Start
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}`);
console.log(` Volume: $${market.volume.toLocaleString()}`);
console.log(` Binary: ${MarketUtils.isBinary(market)}`);
console.log(` Spread: ${MarketUtils.spread(market)?.toFixed(4)}`);
}Authentication
Polymarket
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}`);Limitless
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();Opinion
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,
});Kalshi
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,
});Predict.fun
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}`);API Reference
Exchange Methods
All exchanges implement these core methods:
interface Exchange {
// Market data
fetchMarkets(params?: FetchMarketsParams): Promise<Market[]>;
fetchMarket(marketId: string): Promise<Market>;
// Orders (requires auth)
createOrder(params: CreateOrderParams): Promise<Order>;
cancelOrder(orderId: string, marketId?: string): Promise<Order>;
fetchOrder(orderId: string, marketId?: string): Promise<Order>;
fetchOpenOrders(marketId?: string): Promise<Order[]>;
// Account (requires auth)
fetchPositions(marketId?: string): Promise<Position[]>;
fetchBalance(): Promise<Record<string, number>>;
// Utilities
describe(): { id: string; name: string; has: ExchangeCapabilities };
findTradeableMarket(options?: { binary?: boolean; minLiquidity?: number }): Promise<Market | null>;
calculateSpread(market: Market): number | null;
}Polymarket-specific Methods
// 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');WebSocket Streaming
Polymarket WebSocket
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
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);Utilities
Market Utilities
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 IDsOrderbook Utilities
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 sizesPosition Utilities
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 }Price Utilities
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"Error Handling
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');
}
}Types
import type {
Market,
OutcomeToken,
Order,
CreateOrderParams,
Position,
DeltaInfo,
Orderbook,
PriceLevel,
FetchMarketsParams,
ExchangeConfig,
ExchangeCapabilities,
} from '@alango/dr-manhattan';
import { OrderSide, OrderStatus } from '@alango/dr-manhattan';Adding New Exchanges
import { Exchange, type ExchangeConfig } from '@alango/dr-manhattan';
class NewExchange extends Exchange {
readonly id = 'newexchange';
readonly name = 'New Exchange';
async fetchMarkets(params?: FetchMarketsParams): Promise<Market[]> {
// Implement API call
}
async fetchMarket(marketId: string): Promise<Market> {
// Implement
}
// ... implement other abstract methods
}Configuration Options
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)
}Examples
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 |
Running Examples
# List markets from all exchanges
npx tsx examples/list-markets.ts
# WebSocket orderbook streaming (Polymarket)
npx tsx examples/websocket-orderbook.ts
# Spread strategy - works with any exchange
# Polymarket (WebSocket)
EXCHANGE=polymarket PRIVATE_KEY=0x... npx tsx examples/spread-strategy.ts
# Limitless (WebSocket)
EXCHANGE=limitless PRIVATE_KEY=0x... npx tsx examples/spread-strategy.ts
# Kalshi (REST polling)
EXCHANGE=kalshi KALSHI_API_KEY_ID=... KALSHI_PRIVATE_KEY_PATH=./key.pem npx tsx examples/spread-strategy.ts
# Predict.fun (REST polling)
EXCHANGE=predictfun PREDICTFUN_API_KEY=... PRIVATE_KEY=0x... npx tsx examples/spread-strategy.ts
# Opinion (REST polling)
EXCHANGE=opinion OPINION_API_KEY=... PRIVATE_KEY=0x... npx tsx examples/spread-strategy.ts
# Simulation mode (no credentials = no real trades)
EXCHANGE=polymarket npx tsx examples/spread-strategy.ts
# Spike strategy - mean reversion (buys dips)
EXCHANGE=polymarket PRIVATE_KEY=0x... npx tsx examples/spike-strategy.ts
# Spike strategy with custom parameters
npx tsx examples/spike-strategy.ts --spike-threshold 0.02 --profit-target 0.03 --stop-loss 0.02
# Weather bot strategy - London temperature bucket mispricing
npx tsx examples/weather-bot-strategy.ts --dry-run
# Weather bot with live trading
PRIVATE_KEY=0x... npx tsx examples/weather-bot-strategy.ts --order-size 5Requirements
- Node.js >= 20.0.0
- TypeScript >= 5.0 (for development)
License
MIT
