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.
devrels.xyz/a/113short linkPolymarket 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.
One SDK surface for quoting and executing value across Solana and 20+ other chains — intent solver, money market, bridge — plus agent skills.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.
