All articles
solanapolymarketrustprediction-marketsdefisdk

polymarket-rs: a typed Rust SDK for the Polymarket CLOB

polymarket-rs is a modern, type-safe Rust client for the Polymarket CLOB and Data APIs — covering market data, order placement, EIP-712 signing, and WebSocket streaming. How the client hierarchy works and how to go from zero to a live limit order.

Share

Polymarket runs one of the most actively traded prediction market order books in crypto. The CLOB lives on-chain but the matching engine is off-chain — accessible via a REST API with WebSocket subscriptions for real-time events. Most integrations are written in TypeScript. If you want Rust, polymarket-rs is the modern typed client.

The crate covers the full CLOB surface: unauthenticated market data, authenticated order placement, EIP-712 order signing via alloy, WebSocket streaming, and the Gamma/Data APIs for market metadata. It's a complete rewrite of earlier community clients — no generic type parameter soup, no panics, newtype IDs throughout.

Installation

toml
[dependencies]
polymarket-rs = "0.2"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

# For trading (order signing + placement)
# Enable the "secure" feature:
polymarket-rs = { version = "0.2", features = ["secure"] }

Client hierarchy

The crate exposes five client types. You pick based on what you need:

  • ClobClient — unauthenticated. Market data, prices, order books, spreads. No credentials required.
  • AuthenticatedClient — L1 (EIP-712) or L2 (HMAC) authenticated. Required for any write operation.
  • TradingClient — wraps AuthenticatedClient with order creation, posting, and management.
  • DataClient — trade history, positions, earnings.
  • GammaClient — Gamma Markets API for market discovery and metadata beyond the CLOB.

Reading market data (no auth)

rust
use polymarket_rs::client::ClobClient;
use polymarket_rs::types::{TokenId, Side};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let client = ClobClient::new("https://clob.polymarket.com");

    // Check the server is up
    let _ = client.get_ok().await?;

    // Fetch a specific market by slug
    let market = client
        .get_market_by_slug("will-the-fed-cut-rates-in-june-2026")
        .await?;

    println!("Market: {}", market.question);
    println!("Condition ID: {}", market.condition_id);

    // Get the order book for one outcome token
    let token_id = TokenId::from(&market.tokens[0].token_id);
    let book = client.get_order_book(&token_id).await?;

    println!(
        "Best bid: {} | Best ask: {}",
        book.bids.first().map(|b| b.price.to_string()).unwrap_or_default(),
        book.asks.first().map(|a| a.price.to_string()).unwrap_or_default(),
    );

    // Current midpoint price
    let mid = client.get_midpoint(&token_id).await?;
    println!("Midpoint: {}", mid.mid);

    // Bid/ask spread
    let spread = client.get_spread(&token_id).await?;
    println!("Spread: {}", spread.spread);

    Ok(())
}

Browsing markets with pagination

rust
use polymarket_rs::client::ClobClient;
use polymarket_rs::types::PaginationParams;

let client = ClobClient::new("https://clob.polymarket.com");

// Paginate through active markets
let mut cursor = None;
loop {
    let page = client
        .get_simplified_markets(cursor.map(|c| PaginationParams { next_cursor: c }))
        .await?;

    for market in &page.data {
        println!("{}: active={}", market.question, market.active);
    }

    match page.next_cursor.as_deref() {
        Some("LTE=") | None => break, // "LTE=" is Polymarket's end-of-results sentinel
        Some(c) => cursor = Some(c.to_string()),
    }
}

Historical prices

rust
// 1-day price history, 1-minute fidelity
let history = client
    .get_prices_history(
        &token_id,
        "1d",          // interval: "1m", "1h", "1d", "1w", "1mo", "max"
        None,          // start_ts (unix seconds)
        None,          // end_ts
        Some(60),      // fidelity in seconds
    )
    .await?;

for point in &history.history {
    println!("t={} price={}", point.t, point.p);
}

Placing a limit order

Order placement requires L1 (EIP-712) authentication. You need a private key for the Polygon wallet that holds your USDC position. The OrderBuilder creates and signs the order locally; TradingClient submits it.

rust
use polymarket_rs::{
    client::TradingClient,
    orders::OrderBuilder,
    signing::EthSigner,
    types::{ApiCreds, Side, OrderType, OrderArgs, TokenId},
};
use alloy::signers::local::PrivateKeySigner;
use rust_decimal::Decimal;
use std::str::FromStr;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Load your Polygon wallet
    let signer = PrivateKeySigner::from_str(&std::env::var("PRIVATE_KEY")?)?;

    // API credentials — generated via authenticated /auth/api-key endpoint
    let creds = ApiCreds {
        api_key: std::env::var("POLY_API_KEY")?,
        api_secret: std::env::var("POLY_API_SECRET")?,
        api_passphrase: std::env::var("POLY_PASSPHRASE")?,
    };

    let order_builder = OrderBuilder::new(signer.clone(), None, None);

    let client = TradingClient::new(
        "https://clob.polymarket.com",
        signer,
        137,   // Polygon chain ID
        creds,
        order_builder,
    );

    // Build a limit order: buy outcome token at 0.65 USDC ($0.65 per share)
    let token_id = TokenId::from("71321045679252212594626385532706912750332728571942532289631379312455583992563");

    let order = client
        .create_order(OrderArgs {
            token_id: token_id.clone(),
            price: Decimal::from_str("0.65")?,  // cents on the dollar
            size: Decimal::from_str("100.0")?,  // $100 notional
            side: Side::Buy,
            ..Default::default()
        })
        .await?;

    // Submit to the CLOB
    let response = client.post_order(order, OrderType::Gtc).await?;
    println!("Order ID: {}", response.order_id);
    println!("Status: {}", response.status);

    Ok(())
}

Market orders

Market orders traverse the book at whatever price is available. The SDK fetches the current book to compute fill price before submitting:

rust
use polymarket_rs::types::MarketOrderArgs;

let order = client
    .create_market_order(MarketOrderArgs {
        token_id: token_id.clone(),
        amount: Decimal::from_str("50.0")?,  // USDC to spend
        side: Side::Buy,
        ..Default::default()
    })
    .await?;

let response = client.post_order(order, OrderType::Fok).await?;

Order management

rust
// List open orders
let orders = client.get_orders(None).await?;
for o in &orders {
    println!("{}: {} @ {} ({})", o.id, o.side, o.price, o.status);
}

// Cancel a specific order
client.cancel(&order_id).await?;

// Cancel all open orders
client.cancel_all().await?;

// Cancel all orders for a market
client.cancel_market_orders(Some(&condition_id), None).await?;

// Check if an order is scoring (contributing to LP rewards)
let scoring = client.is_order_scoring(&order_id).await?;
println!("Scoring: {}", scoring);

Trade history

rust
use polymarket_rs::types::TradeParams;

let trades = client
    .get_trades(Some(TradeParams {
        market: Some(condition_id.clone()),
        ..Default::default()
    }))
    .await?;

for trade in &trades {
    println!(
        "{} {} shares @ {} — {}",
        trade.side, trade.size, trade.price, trade.created_at
    );
}

WebSocket streaming

Two WebSocket clients: MarketWsClient for anonymous market feed (order book updates, trades), and UserWsClient for authenticated user events (order fills, status changes):

rust
use polymarket_rs::websocket::{MarketWsClient, WsMessage};

let ws = MarketWsClient::new("wss://ws-subscriptions-clob.polymarket.com/ws/market");

// Subscribe to order book updates for a token
ws.subscribe(&[token_id.clone()]).await?;

loop {
    match ws.next().await? {
        WsMessage::PriceChange { asset_id, changes } => {
            for change in changes {
                println!(
                    "Book update — side: {} price: {} size: {}",
                    change.side, change.price, change.size
                );
            }
        }
        WsMessage::LastTradePrice { asset_id, price } => {
            println!("Last trade: {}", price);
        }
        WsMessage::Tick(tick) => {
            println!("Tick — bid: {} ask: {}", tick.bid_price, tick.ask_price);
        }
        _ => {}
    }
}

The Gamma client for market discovery

The CLOB API returns minimal market metadata. The Gamma API has richer data — tags, resolution criteria, related markets:

rust
use polymarket_rs::client::GammaClient;

let gamma = GammaClient::new("https://gamma-api.polymarket.com");

// Fetch markets with full metadata
let markets = gamma.get_markets(None).await?;
for m in &markets {
    println!("{}: slug={} end_date={:?}", m.title, m.slug, m.end_date);
}

// Look up by condition ID
let market = gamma
    .get_market_by_condition_id(&condition_id)
    .await?;
println!("Tags: {:?}", market.tags);

Authentication: L1 vs L2

Polymarket has two auth tiers:

  • L1 (EIP-712) — wallet-level auth. Required for generating API keys and for operations that touch on-chain funds. Signs a structured message with your Polygon private key.
  • L2 (HMAC) — API-key auth. Used for most order operations after you've bootstrapped credentials via L1. Faster — no async signing round-trip.

TradingClient handles both transparently. You pass your signer and ApiCreds at construction; the client picks the right scheme per endpoint.

Type safety model

The crate wraps all IDs as newtypes — TokenId, OrderId, ConditionId — so you can't accidentally pass a condition ID where a token ID is expected. All amounts use rust_decimal::Decimal rather than f64, avoiding float precision bugs in price arithmetic. Error types are exhaustive enums — no stringly-typed errors.

Relation to Polymarket on Solana

Polymarket's expansion to Solana changes the settlement layer but not the CLOB API surface — orders still go through the same REST endpoints. If Polymarket migrates matching to Solana infrastructure, the SDK interface stays stable; only the chain where positions settle changes. For builders integrating from Rust today, the workflow above works regardless of the settlement chain.

Keep reading

Get new articles in your inbox

Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.

polymarket-rs: a typed Rust SDK for the Polymarket CLOB | devrels.xyz