polynode 0.7.3

Rust SDK for the PolyNode API — real-time Polymarket data
Documentation
//! Auto-rotating streams for Polymarket short-form crypto markets (5m, 15m, 1h).
//!
//! Discovers current markets via Gamma proxy, subscribes via WebSocket,
//! and automatically rotates to the next window at expiry. Each market
//! includes zero-cost enrichments (odds, liquidity, volume) and the
//! Chainlink opening price (price-to-beat).
//!
//! # Example
//! ```rust,no_run
//! use polynode::{PolyNodeClient, ShortFormInterval, Coin};
//!
//! #[tokio::main]
//! async fn main() -> polynode::Result<()> {
//!     let client = PolyNodeClient::new("pn_live_...")?;
//!     let mut stream = client
//!         .short_form(ShortFormInterval::FifteenMin)
//!         .coins(&[Coin::Btc, Coin::Eth])
//!         .start()
//!         .await?;
//!
//!     while let Some(msg) = stream.next().await {
//!         match msg {
//!             ShortFormMessage::Event(e) => println!("{:?}", e),
//!             ShortFormMessage::Rotation(r) => {
//!                 for m in &r.markets {
//!                     println!("{}: beat ${:?} | {:.0}% up | {}s left",
//!                         m.coin.id(), m.price_to_beat, m.up_odds * 100.0, r.time_remaining);
//!                 }
//!             }
//!             ShortFormMessage::Error(e) => eprintln!("Error: {}", e),
//!         }
//!     }
//!     Ok(())
//! }
//! ```

mod discovery;
mod stream;

pub use discovery::{ShortFormMarket, discover_markets};
pub use stream::ShortFormStream;

use std::fmt;

/// Short-form market interval.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShortFormInterval {
    FiveMin,
    FifteenMin,
    Hourly,
}

impl ShortFormInterval {
    /// Window duration in seconds.
    pub fn window_seconds(self) -> i64 {
        match self {
            Self::FiveMin => 300,
            Self::FifteenMin => 900,
            Self::Hourly => 3600,
        }
    }

    /// Slug suffix (e.g. "5m", "15m").
    pub fn slug_suffix(self) -> &'static str {
        match self {
            Self::FiveMin => "5m",
            Self::FifteenMin => "15m",
            Self::Hourly => "hourly",
        }
    }

    /// Variant string for the crypto-price API.
    pub fn price_variant(self) -> &'static str {
        match self {
            Self::FiveMin => "fiveminute",
            Self::FifteenMin => "fifteen",
            Self::Hourly => "hourly",
        }
    }
}

impl fmt::Display for ShortFormInterval {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::FiveMin => write!(f, "5m"),
            Self::FifteenMin => write!(f, "15m"),
            Self::Hourly => write!(f, "1h"),
        }
    }
}

/// Supported coins for short-form markets.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Coin {
    Btc,
    Eth,
    Sol,
    Xrp,
    Doge,
    Hype,
    Bnb,
}

impl Coin {
    /// Short lowercase identifier (slug prefix).
    pub fn id(self) -> &'static str {
        match self {
            Self::Btc => "btc",
            Self::Eth => "eth",
            Self::Sol => "sol",
            Self::Xrp => "xrp",
            Self::Doge => "doge",
            Self::Hype => "hype",
            Self::Bnb => "bnb",
        }
    }

    /// Uppercase symbol for the crypto-price API.
    pub fn symbol(self) -> &'static str {
        match self {
            Self::Btc => "BTC",
            Self::Eth => "ETH",
            Self::Sol => "SOL",
            Self::Xrp => "XRP",
            Self::Doge => "DOGE",
            Self::Hype => "HYPE",
            Self::Bnb => "BNB",
        }
    }

    /// Full lowercase name for hourly slug construction.
    pub fn full_name(self) -> &'static str {
        match self {
            Self::Btc => "bitcoin",
            Self::Eth => "ethereum",
            Self::Sol => "solana",
            Self::Xrp => "xrp",
            Self::Doge => "dogecoin",
            Self::Hype => "hype",
            Self::Bnb => "bnb",
        }
    }

    /// All supported coins.
    pub fn all() -> &'static [Coin] {
        &[Self::Btc, Self::Eth, Self::Sol, Self::Xrp, Self::Doge, Self::Hype, Self::Bnb]
    }
}

/// Message received from a short-form stream.
#[derive(Debug)]
pub enum ShortFormMessage {
    /// A decoded event (settlement, trade, etc.).
    Event(crate::types::events::PolyNodeEvent),
    /// Window rotation occurred — new markets discovered.
    Rotation(RotationInfo),
    /// Non-fatal error (e.g. discovery failed for a coin).
    Error(String),
}

/// Info about a window rotation.
#[derive(Debug, Clone)]
pub struct RotationInfo {
    pub interval: ShortFormInterval,
    pub markets: Vec<ShortFormMarket>,
    pub window_start: i64,
    pub window_end: i64,
    /// Seconds remaining in this window (computed client-side at rotation time).
    pub time_remaining: i64,
}

/// Builder for configuring a short-form stream.
pub struct ShortFormBuilder<'a> {
    pub(crate) client: &'a crate::PolyNodeClient,
    pub(crate) interval: ShortFormInterval,
    pub(crate) coins: Option<Vec<Coin>>,
    pub(crate) rotation_buffer: u64,
}

impl<'a> ShortFormBuilder<'a> {
    /// Filter to specific coins. Defaults to all 7.
    pub fn coins(mut self, coins: &[Coin]) -> Self {
        self.coins = Some(coins.to_vec());
        self
    }

    /// Seconds to wait after window end before discovering next window. Default: 3.
    pub fn rotation_buffer(mut self, seconds: u64) -> Self {
        self.rotation_buffer = seconds;
        self
    }

    /// Start the stream: discover markets, subscribe, begin auto-rotation.
    pub async fn start(self) -> crate::Result<ShortFormStream> {
        ShortFormStream::start(
            self.client,
            self.interval,
            self.coins.unwrap_or_else(|| Coin::all().to_vec()),
            self.rotation_buffer,
        )
        .await
    }
}