polynode 0.8.0

Rust SDK for the PolyNode API — real-time Polymarket data
Documentation
use std::time::Duration;
use polynode::{OrderbookEngine, EngineOptions, PolyNodeClient};

const DEFAULT_KEY: &str = "pn_live_test_session_tracking_51eca107e9b347b589f5b0a04f98eb1d";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = std::env::args().nth(1).unwrap_or_else(|| DEFAULT_KEY.to_string());

    println!("=== PolyNode SDK — Batch Orderbook API ===\n");

    // Pull a handful of live token IDs from REST so we have something real to subscribe to.
    let client = PolyNodeClient::new(&api_key)?;
    let markets = client.markets(Some(8)).await?;
    let token_ids: Vec<String> = markets
        .markets
        .iter()
        .filter_map(|m| m.token_id.clone())
        .take(5)
        .collect();

    if token_ids.is_empty() {
        return Err("no token IDs returned from /v1/markets — cannot exercise orderbook batch API".into());
    }

    println!("subscribing to {} tokens:", token_ids.len());
    for t in &token_ids {
        println!("  {}", t);
    }

    let engine = OrderbookEngine::connect(&api_key, EngineOptions::default()).await?;
    engine.subscribe(token_ids.clone()).await?;

    println!("\nwaiting 3s for snapshots...");
    tokio::time::sleep(Duration::from_secs(3)).await;

    println!("\ntracked_tokens: {}", engine.tracked_tokens().await.len());

    let mids_all = engine.midpoints_all().await;
    println!("\nmidpoints_all ({} entries):", mids_all.len());
    for (id, m) in &mids_all {
        println!("  {}  midpoint={:.4}", short(id), m);
    }

    let spreads_all = engine.spreads_all().await;
    println!("\nspreads_all ({} entries):", spreads_all.len());
    for (id, s) in &spreads_all {
        println!("  {}  spread={:.4}", short(id), s);
    }

    let bids_for_3 = engine.best_bids(&token_ids[..3.min(token_ids.len())]).await;
    println!("\nbest_bids(first 3) -> {} entries", bids_for_3.len());

    let books_one = engine.books(&token_ids[..1]).await;
    println!("\nbooks(first 1):");
    if let Some((bids, asks)) = books_one.values().next() {
        println!("  bids: {} levels (top: {:?})", bids.len(), bids.first());
        println!("  asks: {} levels (top: {:?})", asks.len(), asks.first());
    } else {
        println!("  (no book yet — snapshot still in flight)");
    }

    let stale_short = engine.inactive_since(Duration::from_secs(10)).await;
    println!("\ninactive_since(10s): {} (expected 0 right after fresh snapshots)", stale_short.len());

    let stale_zero = engine.inactive_since(Duration::from_millis(1)).await;
    println!("inactive_since(1ms): {} (expected ~all)", stale_zero.len());

    // Direct state handle — read lock held for as long as the user wants.
    println!("\nstate() handle — direct read lock:");
    let state = engine.state();
    let guard = state.read().await;
    println!("  tracked: {}", guard.len());
    for token in guard.tracked_tokens().iter().take(3) {
        if let Some(ts) = guard.last_change(token) {
            println!("  {}  last_change={:?} ago", short(token), ts.elapsed());
        }
    }
    drop(guard);

    engine.close().await?;
    println!("\ndone.");
    Ok(())
}

fn short(id: &str) -> String {
    if id.len() > 16 {
        format!("{}{}", &id[..8], &id[id.len() - 8..])
    } else {
        id.to_string()
    }
}