Skip to main content

Install

npm install polynode-sdk ws
Requires Node.js 18+. The ws package provides WebSocket support for Node.js.

Quick Start

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 Methods

Every REST endpoint has a typed method on the PolyNode client:
// 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);

// Enriched Data (1 req/sec rate limit)
await pn.leaderboard({ period: 'monthly', sort: 'profit' });
await pn.trending();
await pn.activity();
await pn.movers();
await pn.traderProfile('0xc2e7...');
await pn.traderPnl('0xc2e7...', { period: '1W' });
await pn.event('fed-decision-in-april');
await pn.marketsByCategory('crypto');

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

WebSocket Streaming

Subscribe to real-time events with a builder pattern:
const sub = await pn.ws.subscribe('settlements')
  .minSize(100)
  .status('pending')
  .snapshotCount(20)
  .send();

Event Callbacks

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`);
});

// Catch-all
sub.on('*', (event) => {
  console.log(event.event_type, event);
});

Async Iterator

for await (const event of sub) {
  if (event.event_type === 'settlement') {
    console.log(event.taker_wallet, event.taker_size);
  }
}

Subscription Filters

All filters from the Subscriptions & Filters page are supported:
pn.ws.subscribe('settlements')
  .wallets(['0xabc...'])          // by wallet
  .tokens(['21742633...'])        // by token ID
  .slugs(['bitcoin-100k'])        // by market slug
  .conditionIds(['0xabc...'])     // by condition ID
  .side('BUY')                    // BUY or SELL
  .status('pending')              // pending, confirmed, or all
  .minSize(100)                   // min USD size
  .maxSize(10000)                 // max USD size
  .eventTypes(['settlement'])     // override event types
  .snapshotCount(50)              // initial snapshot (max 200)
  .feeds(['BTC/USD'])             // chainlink feeds
  .send();

Subscription Types

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

Multiple Subscriptions

Subscriptions stack on the same connection:
const whales = await pn.ws.subscribe('large_trades')
  .minSize(5000).send();

const myWallet = await pn.ws.subscribe('wallets')
  .wallets(['0xabc...']).send();

// Both active simultaneously, events deduplicated

Compression

Zlib compression is enabled by default for all WebSocket connections (~50% bandwidth savings). No configuration needed. To disable compression (not recommended):
const ws = pn.configureWs({ compress: false });

Auto-Reconnect

Enabled by default. The SDK reconnects with exponential backoff and replays all active subscriptions:
const ws = pn.configureWs({
  autoReconnect: true,           // default: true
  maxReconnectAttempts: Infinity, // default: unlimited
  reconnectBaseDelay: 1000,      // default: 1s
  reconnectMaxDelay: 30000,      // default: 30s
});

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));

Cleanup

sub.unsubscribe();     // remove one subscription
pn.ws.unsubscribeAll(); // remove all
pn.ws.disconnect();     // close connection

Configuration

const pn = new PolyNode({
  apiKey: 'pn_live_...',                    // required
  baseUrl: 'https://api.polynode.dev',      // default
  wsUrl: 'wss://ws.polynode.dev/ws',        // default
  rpcUrl: 'https://rpc.polynode.dev',       // default
  timeout: 10000,                            // ms, default 10s
});

Error Handling

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

try {
  await pn.market('invalid-id');
} catch (err) {
  if (err instanceof ApiError) {
    console.log(err.status);  // 404
    console.log(err.message); // "Market not found"
  }
}

Orderbook Streaming

The SDK includes a dedicated orderbook client for real-time book data from ob.polynode.dev. This is a separate WebSocket connection from the event stream, with its own URL, protocol, and message format.

Subscribe

// Lazy-initialized, connects on first subscribe
await pn.orderbook.subscribe(['token_id_1', 'token_id_2']);

Event Handlers

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

pn.orderbook.on('update', (delta) => {
  // Incremental delta. A level with size "0" means removal.
  console.log(delta.asset_id, delta.bids.length, 'bid changes');
});

pn.orderbook.on('price', (change) => {
  // Summary price movement across assets in a market
  for (const asset of change.assets) {
    console.log(asset.outcome, asset.price);
  }
});

pn.orderbook.on('snapshots_done', (msg) => {
  console.log(`All ${msg.total} snapshots received`);
});

// Catch-all for snapshot, update, and price events
pn.orderbook.on('*', (update) => {
  console.log(update.type, update);
});

LocalOrderbook Helper

Maintain a sorted local copy of the book:
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));

// Query state
const fullBook = book.getBook(tokenId);   // { bids, asks }
const bestBid = book.getBestBid(tokenId);  // { price, size }
const bestAsk = book.getBestAsk(tokenId);  // { price, size }
const spread = book.getSpread(tokenId);    // number

Compression

Zlib compression is enabled by default (~50% bandwidth savings). No configuration needed.
// To disable (not recommended):
const ob = pn.configureOrderbook({ compress: false });

Configuration

const pn = new PolyNode({
  apiKey: 'pn_live_...',
  obUrl: 'wss://ob.polynode.dev/ws',  // default
});

// Or configure options separately
pn.configureOrderbook({
  compress: true,
  autoReconnect: true,
  maxReconnectAttempts: Infinity,
  reconnectBaseDelay: 1000,
  reconnectMaxDelay: 30000,
});

System Events

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

Cleanup

pn.orderbook.unsubscribe();   // unsubscribe from all markets
pn.orderbook.disconnect();     // close connection

OrderbookEngine

The OrderbookEngine is a higher-level wrapper around the orderbook WebSocket. It manages one connection, maintains local state for all subscribed tokens, and lets you create filtered views that only deliver updates for specific token subsets. This is useful when your app has multiple components that each need different slices of the orderbook — a trade page showing one market, a sidebar showing another, a portfolio view watching 20 positions. One connection, one shared state, multiple filtered outputs.

Create and Subscribe

import { OrderbookEngine } from 'polynode-sdk';

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

// Subscribe with token IDs, slugs, or condition IDs
await engine.subscribe([
  '114694726451307654528948558967898493662917070661203465131156925998487819889437',
  '66255671088804707681511323064315150986307471908131081808279119719218775249892',
]);

// Wait for all initial snapshots to load
engine.on('ready', () => {
  console.log(`${engine.size} books loaded`);
});

Query State

The engine exposes computed helpers that read from the shared local orderbook:
engine.midpoint(tokenId);   // number | undefined — (bestBid + bestAsk) / 2
engine.spread(tokenId);     // number | undefined — bestAsk - bestBid
engine.bestBid(tokenId);    // { price, size } | undefined
engine.bestAsk(tokenId);    // { price, size } | undefined
engine.book(tokenId);       // { bids: [...], asks: [...] } | undefined

Filtered Views

Create lightweight views that only receive updates for specific tokens. No extra connections are opened — views are just filters over the shared state.
const tradeView = engine.view([tokenA, tokenB]);
const portfolioView = engine.view(myPositionTokenIds);

// Callbacks only fire for this view's tokens
tradeView.on('update', (update) => {
  console.log(update.asset_id, 'book changed');
});

tradeView.on('price', (change) => {
  console.log('price moved:', change.assets);
});

// Views have the same query helpers as the engine
tradeView.midpoint(tokenA);
tradeView.spread(tokenA);
tradeView.book(tokenA);

Swap or Destroy Views

When a user navigates to a different page or the tracked tokens change:
// Swap to different tokens (keeps the view, changes the filter)
tradeView.setTokens([newTokenX, newTokenY]);

// Or destroy the view entirely (removes handlers, detaches from engine)
tradeView.destroy();

Engine-Level Events

Listen to all updates across all tokens (unfiltered):
engine.on('update', (update) => {
  // Fires for every book snapshot and delta, all tokens
});

engine.on('price', (change) => {
  // All price change events
});

engine.on('ready', () => {
  // All initial snapshots loaded
});

Connection Events

Access the underlying WebSocket for connection lifecycle events:
engine.connection.onConnect(() => console.log('connected'));
engine.connection.onDisconnect((reason) => console.log('disconnected:', reason));
engine.connection.onReconnect((attempt) => console.log('reconnected'));
engine.connection.onError((err) => console.error(err));

Cleanup

engine.close(); // disconnects WS, destroys all views, clears state

TypeScript Types

All event types are exported and fully typed:
import type {
  SettlementEvent,
  TradeEvent,
  StatusUpdateEvent,
  BlockEvent,
  PositionChangeEvent,
  DepositEvent,
  OracleEvent,
  PriceFeedEvent,
  PolyNodeEvent,     // union of all events

  // Orderbook types
  OrderbookLevel,
  BookSnapshot,
  BookUpdate,
  PriceChange,
  OrderbookUpdate,   // union of snapshot | update | price_change
  OrderbookOptions,

  // Enriched data types
  LeaderboardResponse,
  LeaderboardTrader,
  TrendingResponse,
  ActivityResponse,
  ActivityTrade,
  MoversResponse,
  MoverMarket,
  TraderProfile,
  TraderPnlResponse,
  EventDetailResponse,
  MarketsByCategoryResponse,
} from 'polynode-sdk';

Source

GitHub | NPM