# polynode-sdk

TypeScript SDK for the [PolyNode](https://polynode.dev) real-time Polymarket API.

Stream settlements, trades, positions, deposits, oracle events, orderbook updates, and more through a single WebSocket connection. All events enriched with full market metadata.

**New in v0.9:** V2 order flow — place orders on the Polymarket V2 CLOB (`clob-v2.polymarket.com`) with pUSD collateral and builder attribution. See [`src/trading/V2_ORDER_FLOW.md`](src/trading/V2_ORDER_FLOW.md) for the exact wire format, required approvals, and fee math.

**In v0.4:** [Local Cache](#local-cache) — SQLite-backed local storage. Backfill wallet history in seconds, query trades and positions instantly with zero API calls.

## Install

```bash
npm install polynode-sdk ws

# For local cache (optional):
npm install better-sqlite3
```

Requires Node.js 18+.

## Quick Start

```typescript
import { PolyNode } from 'polynode-sdk';

const pn = new PolyNode({ apiKey: 'pn_live_...' });

// Fetch top markets
const markets = await pn.markets({ count: 10 });
console.log(`${markets.count} markets, ${markets.total} total`);

// Search
const results = await pn.search('bitcoin');
console.log(results.results[0].question);
```

## REST API

```typescript
// System
await pn.healthz();
await pn.status();
await pn.createKey('my-bot');

// Markets
await pn.markets({ count: 10 });
await pn.market(tokenId);
await pn.marketBySlug('bitcoin-100k');
await pn.marketByCondition(conditionId);
await pn.marketsList({ count: 20, sort: 'volume' });
await pn.search('ethereum', { limit: 5 });

// Pricing
await pn.candles(tokenId, { resolution: '1h', limit: 100 });
await pn.stats(tokenId);

// Settlements
await pn.recentSettlements({ count: 20 });
await pn.tokenSettlements(tokenId, { count: 10 });
await pn.walletSettlements(address, { count: 10 });

// Wallets
await pn.wallet(address);

// RPC (rpc.polynode.dev)
await pn.rpc('eth_blockNumber');
await pn.rpc('eth_getBlockByNumber', ['latest', false]);
```

## WebSocket Streaming

```typescript
const sub = await pn.ws.subscribe('settlements')
  .minSize(100)
  .status('pending')
  .snapshotCount(20)
  .send();

sub.on('settlement', (event) => {
  console.log(`${event.taker_side} $${event.taker_size} on ${event.market_title}`);
});

sub.on('status_update', (event) => {
  console.log(`Confirmed in ${event.latency_ms}ms`);
});

// Or use async iterator
for await (const event of sub) {
  if (event.event_type === 'settlement') {
    console.log(event.taker_wallet, event.taker_size);
  }
}
```

### Subscription Types

```typescript
pn.ws.subscribe('settlements');   // pending + confirmed settlements
pn.ws.subscribe('trades');        // all trade activity
pn.ws.subscribe('prices');        // price-moving events
pn.ws.subscribe('blocks');        // new Polygon blocks
pn.ws.subscribe('wallets');       // all wallet activity
pn.ws.subscribe('markets');       // all market activity
pn.ws.subscribe('large_trades');  // $1K+ trades
pn.ws.subscribe('oracle');        // UMA resolution events
pn.ws.subscribe('chainlink');     // real-time price feeds
```

### Subscription Filters

```typescript
pn.ws.subscribe('settlements')
  .wallets(['0xabc...'])
  .tokens(['21742633...'])
  .slugs(['bitcoin-100k'])
  .conditionIds(['0xabc...'])
  .side('BUY')
  .status('pending')
  .minSize(100)
  .maxSize(10000)
  .eventTypes(['settlement'])
  .snapshotCount(50)
  .feeds(['BTC/USD'])
  .send();
```

## Orderbook Streaming

```typescript
await pn.orderbook.subscribe(['token_id_1', 'token_id_2']);

pn.orderbook.on('snapshot', (snap) => {
  console.log(snap.asset_id, snap.bids.length, 'bids', snap.asks.length, 'asks');
});

pn.orderbook.on('update', (delta) => {
  console.log(delta.asset_id, delta.bids.length, 'bid changes');
});

pn.orderbook.on('price', (change) => {
  for (const asset of change.assets) {
    console.log(asset.outcome, asset.price);
  }
});
```

### LocalOrderbook

```typescript
import { LocalOrderbook } from 'polynode-sdk';

const book = new LocalOrderbook();

pn.orderbook.on('snapshot', (snap) => book.applySnapshot(snap));
pn.orderbook.on('update', (delta) => book.applyUpdate(delta));

const fullBook = book.getBook(tokenId);
const bestBid = book.getBestBid(tokenId);
const bestAsk = book.getBestAsk(tokenId);
const spread = book.getSpread(tokenId);
```

## OrderbookEngine

Higher-level orderbook client. One connection, shared state, filtered views for different parts of your app.

```typescript
import { OrderbookEngine } from 'polynode-sdk';

const engine = new OrderbookEngine({ apiKey: 'pn_live_...' });

// Subscribe with token IDs, slugs, or condition IDs
await engine.subscribe([tokenA, tokenB, tokenC]);

engine.on('ready', () => {
  // Query computed values from local state
  engine.midpoint(tokenA);  // 0.465
  engine.spread(tokenA);    // 0.01
  engine.bestBid(tokenA);   // { price: '0.46', size: '226.29' }
  engine.book(tokenA);      // { bids: [...], asks: [...] }

  // Create filtered views for different components
  const view = engine.view([tokenA]);
  view.on('update', (u) => console.log(u.asset_id, 'updated'));
  view.midpoint(tokenA);    // reads from shared state

  // Swap tokens or destroy views at any time
  view.setTokens([tokenD, tokenE]);
  view.destroy();
});

engine.close();
```

## Local Cache

Store trades and positions in a local SQLite database. Backfills recent history on startup, streams live updates, and serves all queries locally with zero API calls.

```typescript
import { PolyNode, PolyNodeCache } from 'polynode-sdk';

const pn = new PolyNode({ apiKey: 'pn_live_...' });
const cache = new PolyNodeCache(pn, {
  dbPath: './cache.db',
  watchlistPath: './polynode.watch.json',
  onBackfillProgress: (p) => console.log(`${p.label}: ${p.fetched} trades`),
});

await cache.start();

// Query locally — instant, no API calls
const trades = cache.walletTrades('0xabc...', { limit: 50, side: 'BUY' });
const positions = cache.walletPositions('0xabc...');
const multiPos = cache.multiWalletPositions(['0xabc...', '0xdef...']);
const marketTrades = cache.marketTrades('0xcondition...');

// Add wallets at runtime
cache.addToWatchlist([{ type: 'wallet', id: '0xnew...', label: 'whale' }]);

// Stats
const stats = cache.stats();
console.log(`${stats.trade_count} trades, ${(stats.db_size_bytes / 1024 / 1024).toFixed(1)} MB`);

await cache.stop();
```

**Watchlist** (`polynode.watch.json`):

```json
{
  "version": 1,
  "wallets": [
    { "address": "0xabc...", "label": "trader-1", "backfill": true }
  ],
  "settings": { "ttl_days": 30 }
}
```

**Backfill timing:** 1 request per wallet at 1 req/s. 10 wallets = 10 seconds. Up to 500 trades per wallet (configurable with `backfillPages`).

See [full documentation](https://docs.polynode.dev/sdks/local-cache) for all query methods, configuration options, and examples.

## Compression & Reconnection

Zlib compression is enabled by default (~50% bandwidth savings). All connections auto-reconnect with exponential backoff.

```typescript
// Compression is automatic — no config needed
// To disable (not recommended):
const ws = pn.configureWs({ compress: false });

ws.onConnect(() => console.log('connected'));
ws.onDisconnect((reason) => console.log('disconnected:', reason));
ws.onReconnect((attempt) => console.log('reconnected, attempt', attempt));
ws.onError((err) => console.error(err));
```

## Configuration

```typescript
const pn = new PolyNode({
  apiKey: 'pn_live_...',
  baseUrl: 'https://api.polynode.dev',
  wsUrl: 'wss://ws.polynode.dev/ws',
  obUrl: 'wss://ob.polynode.dev/ws',
  rpcUrl: 'https://rpc.polynode.dev',
  timeout: 10000,
});
```

## Error Handling

```typescript
import { PolyNode, ApiError, WsError } from 'polynode-sdk';

try {
  await pn.market('invalid-id');
} catch (err) {
  if (err instanceof ApiError) {
    console.log(err.status);
    console.log(err.message);
  }
}
```

## Cleanup

```typescript
sub.unsubscribe();           // remove one subscription
pn.ws.unsubscribeAll();      // remove all
pn.ws.disconnect();          // close event stream
pn.orderbook.unsubscribe();  // unsubscribe orderbook
pn.orderbook.disconnect();   // close orderbook stream
```

## Testing Utilities

The SDK includes helpers that return known-active Polymarket wallets for testing. Useful in examples, integration tests, and local development.

```typescript
import { getActiveTestWallet, getActiveTestWallets } from 'polynode-sdk';

// Get a single active wallet (instant, uses cached fallback)
const wallet = await getActiveTestWallet();

// Get multiple active wallets
const wallets = await getActiveTestWallets(5);

// Fetch a fresh wallet from live data
const fresh = await getActiveTestWallet({ fresh: true });
```

Combine with the cache for a zero-config quickstart:

```typescript
const wallet = await getActiveTestWallet();
cache.addToWatchlist([{ type: 'wallet', id: wallet, label: 'test' }]);
```

## Links

- [Documentation](https://docs.polynode.dev)
- [API Reference](https://docs.polynode.dev/api-reference)
- [Get an API Key](https://polynode.dev)
- [Rust SDK](https://crates.io/crates/polynode)

## License

MIT
