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.
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
[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)
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
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
// 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.
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:
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
// 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
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):
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:
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
Polymarket is not a betting site. It's the closest thing to a global consensus machine the internet has produced. When millions of dollars are behind a probability estimate, that estimate is load-bearing in a way no poll or pundit can match. The API exposes it. Here are 10 things you can build with it.
Polymarket's expansion to Solana isn't strategy theatre — it's UX. Sub-second order matching needs sub-second confirmation, and Ethereum L2s still can't deliver it cheaply.
An audit report is worthless if you can't confirm the deployed bytecode is what was audited. Solana verified builds fix that: a Docker-pinned toolchain produces a deterministic .so, its hash is compared to the on-chain program data, and the result is written to a PDA anyone can read. Solana Explorer shows a verified badge. Here's the full workflow.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.