polymarket-us 0.3.0

Unofficial Rust SDK for the Polymarket US Retail API
Documentation

polymarket-us

Unofficial Rust SDK for the Polymarket US Retail API.

Features

  • Resource-based API — Organized into focused clients (client.markets(), client.orders(), client.events(), etc.)
  • Ed25519 request signing — Automatic X-PM-* authentication headers
  • Typed async REST client — Markets, events, orders, portfolio, account, and search endpoints
  • Async WebSocket streaming — Market data and order updates with automatic reconnect
  • Order book & pricing data — Get order books, best bid/offer, settlement prices
  • Builder-based configuration — Base URLs, timeouts, custom HTTP client
  • Backward compatible — All legacy methods still work (deprecated)

Installation

This crate is currently easiest to consume from source or git:

[dependencies]
polymarket-us = { path = "../polymarket-us" }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Or via git:

[dependencies]
polymarket-us = { git = "https://github.com/mbordash/DRADIS", package = "polymarket-us" }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Authentication

Authenticated endpoints require:

  • POLYMARKET_US_KEY_ID
  • POLYMARKET_US_SECRET_KEY

POLYMARKET_US_SECRET_KEY must be Base64 that decodes to either:

  • 64 bytes (keypair format, first 32 bytes are used as signing seed), or
  • 32 bytes (raw Ed25519 seed).

Example:

export POLYMARKET_US_KEY_ID="your-key-id"
export POLYMARKET_US_SECRET_KEY="your-base64-secret"

Quick start

use polymarket_us::PolymarketUsClient;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let client = PolymarketUsClient::builder().build()?;

    // Health check
    let health = client.health().await?;
    println!("status: {}", health.status);

    // List markets
    let markets = client.markets().list().await?;
    println!("markets: {}", markets.markets.len());

    // Get order book for a market
    let book = client.markets().order_book("BTC-USD").await?;
    println!("bid/ask: {} orders", book.bids.len() + book.asks.len());

    Ok(())
}

Resource-Based API

The SDK is organized into focused resource clients for better discoverability and maintainability:

Markets

Market discovery, order books, and pricing data.

// List markets
let markets = client.markets().list().await?;

// List with filters
let query = [("limit", "10"), ("category", "politics")];
let page = client.markets().list_with_query(&query).await?;

// Order book and pricing
let book = client.markets().order_book("BTC-USD").await?;
let bbo = client.markets().bbo("BTC-USD").await?;           // Best bid/offer
let settlement = client.markets().settlement_price("BTC-USD").await?;

Events

Event-level metadata and context.

// List all events
let events = client.events().list().await?;

// Get event by ID or slug
let event = client.events().retrieve("event-123").await?;
let event = client.events().retrieve_by_slug("2024-us-election").await?;

Orders

Complete order lifecycle management. All operations are authenticated.

use polymarket_us::types;

let order_req = types::PlaceOrderRequest {
    symbol: "BTC-USD".to_string(),
    action: types::order_action::BUY.to_string(),
    outcome_side: types::outcome::LONG.to_string(),
    order_type: types::order_type::LIMIT.to_string(),
    price: types::Money { value: "0.50".to_string(), currency: "USD".to_string() },
    quantity: 100,
    tif: types::tif::GTC.to_string(),
    client_order_id: Some("my-order-1".to_string()),
    post_only: false,
    expires_at: None,
};

// Place order
let order = client.orders().create(&order_req).await?;

// Get open orders
let open = client.orders().open(None::<&()>).await?;

// Modify, cancel, preview
client.orders().modify(&order.order_id, &modify_req).await?;
client.orders().cancel(&order.order_id, &types::CancelOrderParams { quantity: None }).await?;
let estimate = client.orders().preview(&preview_req).await?;

// Close position
client.orders().close_position(&types::ClosePositionRequest {
    symbol: "BTC-USD".to_string(),
    quantity: 50,
}).await?;

Account

Account balances and buying power (authenticated).

let balances = client.account().balances().await?;
for balance in balances.balances {
    println!("{}; balance={}, buying_power={}",
        balance.currency,
        balance.current_balance,
        balance.buying_power
    );
}

Portfolio

Holdings and activity history (authenticated).

// Get positions
let positions = client.portfolio().positions().await?;

// Get activity with pagination
let query = [("limit", "50")];
let activities = client.portfolio().activities(&query).await?;

Search

Full-text search across markets and events.

let query = [("q", "bitcoin")];
let results = client.search().search(&query).await?;

// Search specific resource
let markets = client.search().markets(&query).await?;
let events = client.search().events(&query).await?;

Advanced market queries

Use list_with_query() for filters, cursors, and pagination:

use polymarket_us::PolymarketUsClient;
use serde::Serialize;

#[derive(Serialize)]
struct MarketsQuery<'a> {
    category: Option<&'a str>,
    limit: Option<u32>,
    cursor: Option<&'a str>,
}

async fn load_filtered_markets(client: &PolymarketUsClient) -> anyhow::Result<()> {
    let query = MarketsQuery {
        category: Some("politics"),
        limit: Some(25),
        cursor: None,
    };

    let page = client.markets().list_with_query(&query).await?;
    println!("filtered markets: {}", page.markets.len());
    Ok(())
}

If your account tier requires authenticated access for some filters, use list_authenticated_with_query():

Streaming market data

The SDK exposes an async WebSocket client via client.streaming(). It handles handshake signing, subscription frames, reconnects, and event parsing.

use polymarket_us::{PolymarketUsClient, StreamConnectConfig, StreamSubscription};

async fn watch_market(client: &PolymarketUsClient) -> anyhow::Result<()> {
    let stream_client = client.streaming();
    let config = StreamConnectConfig::default().with_responses_debounced(true);

    let mut stream = stream_client
        .connect_with_config(
            vec![StreamSubscription::market_data_lite("BTC-USD")],
            config,
        )
        .await?;

    while let Some(message) = stream.next().await {
        println!("{message:?}");
    }

    Ok(())
}

Endpoint coverage

Markets (client.markets()):

  • list() — List all markets
  • list_with_query(q) — List markets with filters/pagination
  • list_authenticated() — Authenticated market listing
  • list_authenticated_with_query(q) — Authenticated with filters
  • order_book(symbol) — Get market order book
  • bbo(symbol) — Get best bid/offer
  • settlement_price(symbol) — Get settlement price

Events (client.events()):

  • list() — List all events
  • list_with_query(q) — List events with filters
  • retrieve(id) — Get event by ID
  • retrieve_by_slug(slug) — Get event by slug

Orders (client.orders()):

  • create(req) — Create order
  • place(req) — Place order (alternative endpoint)
  • place_batch(req) — Place multiple orders atomically
  • open(q) — Get open orders
  • retrieve(id) — Get order by ID
  • cancel(id, params) — Cancel order
  • cancel_trading(id) — Cancel via trading endpoint
  • cancel_all(params) — Cancel all orders
  • modify(id, req) — Modify open order
  • preview(req) — Preview order estimate
  • close_position(req) — Close position

Account (client.account()):

  • balances() — Get account balances and buying power

Portfolio (client.portfolio()):

  • positions() — Get positions
  • activities(q) — Get activity with pagination

Search (client.search()):

  • search(q) — Full-text search across markets/events
  • markets(q) — Search markets
  • events(q) — Search events

Streaming (client.streaming()):

  • Async WebSocket client with automatic reconnect and subscription management

Backward Compatibility

All legacy methods (e.g., client.markets_list(), client.order_create()) are still available but deprecated. They're aliases to the new resource-based API. Your existing code will continue to work—migrate at your own pace:

// Old style (deprecated, but still works)
#[allow(deprecated)]
let markets = client.markets_list().await?;

// New style (preferred)
let markets = client.markets().list().await?;

Configuration

use polymarket_us::{PolymarketUsClient, UsAuth};
use std::time::Duration;

fn build_client(auth: UsAuth) -> Result<PolymarketUsClient, polymarket_us::PolymarketUsError> {
    PolymarketUsClient::builder()
        .auth(auth)
        .gateway_base_url("https://gateway.polymarket.us")
        .api_base_url("https://api.polymarket.us")
        .timeout(Duration::from_secs(30))
        .build()
}

Error handling

use polymarket_us::{PolymarketUsClient, PolymarketUsError};

async fn check_health(client: &PolymarketUsClient) {
    match client.health().await {
        Ok(h) => println!("ok: {}", h.status),
        Err(PolymarketUsError::RateLimited(msg)) => eprintln!("rate limited: {msg}"),
        Err(PolymarketUsError::Authentication(msg)) => eprintln!("auth failed: {msg}"),
        Err(e) => eprintln!("request failed: {e}"),
    }
}

Testing

The SDK includes comprehensive unit tests for all resource clients and type serialization/deserialization:

# Run all tests
cargo test

# Run with output
cargo test -- --nocapture

# Run specific test module
cargo test resources::tests

# Run a single test
cargo test resources::tests::place_order_request_serializes

Current test coverage includes:

  • ✅ Resource client creation and type checking (6 resources × 2 tests = 12 tests)
  • ✅ Request/Response serialization for all order types (8+ tests)
  • ✅ Type deserialization for markets, events, positions, balances (4+ tests)
  • ✅ Client resource accessor availability (2 tests)
  • ✅ Plus existing auth, streaming, and client tests (6 tests)

Total: 36 tests, all passing

Acknowledgements

Initial implementation originated in the DRADIS project and was extracted into this crate.

  • Project link: https://github.com/mbordash/DRADIS
  • Attribution is kept for provenance and maintenance history.