polynode 0.3.0

Rust SDK for the PolyNode API — real-time Polymarket data
Documentation

polynode

Rust 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.

Install

[dependencies]
polynode = "0.2"
tokio = { version = "1", features = ["rt", "macros"] }

Quick Start

use polynode::PolyNodeClient;

#[tokio::main]
async fn main() -> polynode::Result<()> {
    let client = PolyNodeClient::new("pn_live_...")?;

    // Fetch top markets
    let markets = client.markets(Some(10)).await?;
    println!("{} markets, {} total", markets.count, markets.total);

    // Search
    let results = client.search("bitcoin", Some(5), None).await?;
    for r in &results.results {
        println!("{}", r.question.as_deref().unwrap_or("?"));
    }

    Ok(())
}

REST API

// System
client.healthz().await?;
client.status().await?;
client.create_key(Some("my-bot")).await?;

// Markets
client.markets(Some(10)).await?;
client.market("token_id").await?;
client.market_by_slug("bitcoin-100k").await?;
client.market_by_condition("0xabc...").await?;
client.list_markets(&ListMarketsParams {
    count: Some(20),
    sort: Some("volume".into()),
    ..Default::default()
}).await?;
client.search("ethereum", Some(5), None).await?;

// Pricing
client.candles("token_id", Some(CandleResolution::OneHour), Some(100)).await?;
client.stats("token_id").await?;

// Settlements
client.recent_settlements(Some(20)).await?;
client.token_settlements("token_id", Some(10)).await?;
client.wallet_settlements("0xabc...", Some(10)).await?;

// Wallets
client.wallet("0xabc...").await?;

// RPC (rpc.polynode.dev)
client.rpc_call("eth_blockNumber", serde_json::json!([])).await?;

WebSocket Streaming

use polynode::ws::{Subscription, SubscriptionType, StreamOptions};
use polynode::WsMessage;

let mut stream = client.stream(StreamOptions {
    compress: true,
    auto_reconnect: true,
    ..Default::default()
}).await?;

stream.subscribe(
    Subscription::new(SubscriptionType::Settlements)
        .min_size(100.0)
        .status("pending")
        .snapshot_count(20)
).await?;

while let Some(msg) = stream.next().await {
    match msg? {
        WsMessage::Event(event) => {
            match event {
                polynode::PolyNodeEvent::Settlement(s) => {
                    println!("{} ${:.2} on {}",
                        s.taker_side, s.taker_size,
                        s.market_title.as_deref().unwrap_or("unknown"));
                }
                polynode::PolyNodeEvent::StatusUpdate(u) => {
                    println!("Confirmed in {}ms", u.latency_ms);
                }
                _ => {}
            }
        }
        WsMessage::Snapshot(events) => {
            println!("Snapshot: {} events", events.len());
        }
        _ => {}
    }
}

Subscription Types

SubscriptionType::Settlements   // pending + confirmed settlements
SubscriptionType::Trades        // all trade activity
SubscriptionType::Prices        // price-moving events
SubscriptionType::Blocks        // new Polygon blocks
SubscriptionType::Wallets       // all wallet activity
SubscriptionType::Markets       // all market activity
SubscriptionType::LargeTrades   // $1K+ trades
SubscriptionType::Oracle        // UMA resolution events
SubscriptionType::Chainlink     // real-time price feeds

Subscription Filters

Subscription::new(SubscriptionType::Settlements)
    .wallets(vec!["0xabc...".into()])
    .tokens(vec!["21742633...".into()])
    .slugs(vec!["bitcoin-100k".into()])
    .condition_ids(vec!["0xabc...".into()])
    .side("BUY")
    .status("pending")
    .min_size(100.0)
    .max_size(10000.0)
    .event_types(vec!["settlement".into()])
    .snapshot_count(50)
    .feeds(vec!["BTC/USD".into()])

Orderbook Streaming

use polynode::{ObStreamOptions, ObMessage, OrderbookUpdate, LocalOrderbook};

let mut stream = client.orderbook_stream(ObStreamOptions::default()).await?;
stream.subscribe(vec!["token_id_1".into(), "token_id_2".into()]).await?;

let mut book = LocalOrderbook::new();

while let Some(msg) = stream.next().await {
    match msg? {
        ObMessage::Update(OrderbookUpdate::Snapshot(snap)) => {
            book.apply_snapshot(&snap);
            println!("{}: {} bids, {} asks", snap.asset_id, snap.bids.len(), snap.asks.len());
        }
        ObMessage::Update(OrderbookUpdate::Update(delta)) => {
            book.apply_update(&delta);
        }
        ObMessage::Update(OrderbookUpdate::PriceChange(change)) => {
            for asset in &change.assets {
                println!("{} {}: {}", change.market, asset.outcome, asset.price);
            }
        }
        _ => {}
    }
}

// Query local state
let best_bid = book.best_bid("token_id");
let best_ask = book.best_ask("token_id");
let spread = book.spread("token_id");

OrderbookEngine

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

use polynode::orderbook::engine::{OrderbookEngine, EngineOptions};

let engine = OrderbookEngine::connect("pn_live_...", EngineOptions::default()).await?;

// Subscribe with token IDs, slugs, or condition IDs
engine.subscribe(vec![token_a.into(), token_b.into()]).await?;

// Query computed values from local state
engine.midpoint(&token_a).await;   // Some(0.465)
engine.spread(&token_a).await;     // Some(0.01)
engine.best_bid(&token_a).await;   // Some(OrderbookLevel { price: "0.46", size: "226.29" })
engine.book(&token_a).await;       // Some((bids, asks))

// Create filtered views for different components
let mut view = engine.view(vec![token_a.into()]);
view.midpoint(&token_a).await;     // reads from shared state

// Receive only this view's updates
while let Some(update) = view.next().await {
    // only token_a updates arrive here
}

engine.close().await?;

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

Configuration

let client = PolyNodeClient::builder("pn_live_...")
    .base_url("https://api.polynode.dev")
    .ws_url("wss://ws.polynode.dev/ws")
    .ob_url("wss://ob.polynode.dev/ws")
    .rpc_url("https://rpc.polynode.dev")
    .timeout(Duration::from_secs(10))
    .build()?;

Error Handling

use polynode::Error;

match client.market("invalid-id").await {
    Ok(detail) => println!("{:?}", detail),
    Err(Error::NotFound(msg)) => println!("Not found: {}", msg),
    Err(Error::Auth(msg)) => println!("Auth failed: {}", msg),
    Err(Error::RateLimited(msg)) => println!("Rate limited: {}", msg),
    Err(e) => println!("Other error: {}", e),
}

Links

License

MIT