polynode-sdk
v0.10.19
Published
TypeScript SDK for the PolyNode real-time Polymarket API
Maintainers
Readme
polynode-sdk
TypeScript SDK for the PolyNode 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.10.19: POLY_1271 V2 order signatures now normalize the ERC-7739 TypedDataSign recovery byte to Ethereum v=27/28 for on-chain ERC-1271 validation.
In v0.10.18: Polymarket V2 deposit-wallet trading fixes. ensureReady() detects deployed POLY_1271 wallets correctly, V2 type-3 orders use the deposit wallet as both maker and signer, and existing local credentials can be repaired by rerunning ensureReady().
In v0.10.17: WebSocket subscription resilience — event subscriptions preserve frames that arrive before the subscribe ack, orderbook subscribe() waits for the server ack, reconnects replay subscriptions in place, and orderbook clients can partially unsubscribe without closing the stream.
In v0.10.13: V3 historical data and Polymarket profile helpers — use pn.v3 for wallet P&L, enriched positions, builder analytics, market search, and username setup while existing top-level methods keep their legacy behavior.
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 for the exact wire format, required approvals, and fee math.
In v0.4: Local Cache — SQLite-backed local storage. Backfill wallet history in seconds, query trades and positions instantly with zero API calls.
Install
npm install polynode-sdk ws
# For local cache (optional):
npm install better-sqlite3Requires Node.js 18+.
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 API
// 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);
await pn.resolve('Fredi9999');
await pn.walletOnchainPositions(address, { tagSlug: 'Crypto' });
// V3 historical data
await pn.v3.wallet(address);
await pn.v3.walletTrades(address, { groupBy: 'user_trade', limit: 100 });
await pn.v3.walletPositions(address, { status: 'open', sort: 'size' });
await pn.v3.searchMarkets({ query: 'bitcoin', limit: 10 });
await pn.v3.builderTrades(builderCode, { eventSlug: 'who-will-win-the-2026-world-cup' });
// V3 Polymarket profiles
await pn.v3.polymarketUsernameAvailable('alice123');
const challenge = await pn.v3.createPolymarketUsernameChallenge({
address: userEoa,
username: 'alice123',
});
await pn.v3.completePolymarketUsername({
challenge_id: challenge.challenge_id,
address: userEoa,
username: 'alice123',
polymarket_signature: '0x...',
consent_signature: '0x...',
});
// RPC (rpc.polynode.dev)
await pn.rpc('eth_blockNumber');
await pn.rpc('eth_getBlockByNumber', ['latest', false]);WebSocket Streaming
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
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 feedsSubscription Filters
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
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);
}
});subscribe() resolves after the orderbook server acknowledges the subscription. In v0.10.17+, reconnects reuse the same handlers and replay the active token list automatically.
To remove only some tokens, pass them to unsubscribe():
pn.orderbook.unsubscribe(['token_id_1']); // remove one token
pn.orderbook.unsubscribe(); // remove all tokensLocalOrderbook
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.
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.
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):
{
"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 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.
// Compression is automatic — no config needed
// To disable (not recommended):
const ws = pn.configureWs({ compress: false });
const orderbook = pn.configureOrderbook({ subscribeTimeoutMs: 30000 });
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
const pn = new PolyNode({
apiKey: 'pn_live_...',
baseUrl: 'https://api.polynode.dev',
v3BaseUrl: '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
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
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 streamTesting Utilities
The SDK includes helpers that return known-active Polymarket wallets for testing. Useful in examples, integration tests, and local development.
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:
const wallet = await getActiveTestWallet();
cache.addToWatchlist([{ type: 'wallet', id: wallet, label: 'test' }]);Links
License
MIT
