Polymarket Rust CLOB Client SDK

repository·main·Indexed 20 days ago

https://github.com/polymarket/rs-clob-client

An ergonomic Rust client for interacting with Polymarket services, primarily the Central Limit Order Book (CLOB). It features strongly typed request builders, support for the Alloy ecosystem, and dual authentication flows. The SDK provides modular feature flags for CLOB operations, WebSocket streaming (ws), Data API analytics (data), Gamma API discovery (gamma), and cross-chain bridge operations (bridge). Note: This repository is archived and non-functional; users should migrate to the V2 client.

Tokens
30.1K
Snippets
90
Records
124
Agent score
72%

What's inside polymarket-client-sdk

  1. Overview of Polymarket Rust Client capabilities

    main

    The polymarket-client-sdk is an ergonomic Rust client for interacting with Polymarket services, specifically the Central Limit Order Book (CLOB).

    Key features include:

    • Typed CLOB requests: Strongly typed builders for orders, trades, markets, balances, etc.
    • Dual authentication flows: Supports normal authenticated flows and Builder authentication.
    • Type-level state machine: Uses compile-time enforcement to prevent using authenticated endpoints before the authentication process is complete.
    • Signer support: Integrates with alloy::signers::Signer, including support for remote signers like AWS KMS.
    • Performance: Zero-cost abstractions with no dynamic dispatch in hot paths.
    • Async-first: Built on reqwest for asynchronous operations.
  2. How Proxy and Gnosis Safe wallets work with the SDK

    main

    For Proxy/Safe wallets (like Magic/email wallets or browser proxy contracts), the signing key is different from the address holding the funds. The SDK can automatically derive the correct funder address using CREATE2 when you specify the appropriate SignatureType.

    Automatic Derivation

    When using .signature_type(SignatureType::GnosisSafe) or .signature_type(SignatureType::Proxy), the SDK computes the deterministic wallet address.

    Manual Derivation

    You can also derive these addresses manually using the provided utility functions:

    • GnosisSafe (Browser wallets): derive_safe_wallet(signer.address(), POLYGON)
    • Proxy (Magic/Email wallets): derive_proxy_wallet(signer.address(), POLYGON)

    Overriding the Funder

    If you need to explicitly provide a funder address, use the .funder(address) method in the authentication builder.

    // Automatic derivation
    let client = Client::new("https://clob.polymarket.com", Config::default())?
        .authentication_builder(&signer)
        .signature_type(SignatureType::GnosisSafe)  // Funder auto-derived via CREATE2
        .authenticate()
        .await?;
    
    // Explicit override
    let client = Client::new("https://clob.polymarket.com", Config::default())?
        .authentication_builder(&signer)
        .funder(address!("<your-polymarket-wallet-address>"))
        .signature_type(SignatureType::GnosisSafe)
        .authenticate()
        .await?;
  3. Install the Polymarket Rust Client

    main

    Add the polymarket-client-sdk crate to your Cargo.toml to begin using the SDK. You can also use the cargo add command.

    Note: The current documentation refers to version 0.3 for installation.

    [dependencies]
    polymarket-client-sdk = "0.3"

    or

    cargo add polymarket-client-sdk
  4. Use WebSocket Streaming for real-time data

    main

    The SDK supports real-time streaming of orderbooks, prices, and user events via WebSockets. This requires the ws feature enabled in your Cargo.toml:

    polymarket-client-sdk = { version = "0.3", features = ["ws"] }

    Available Streams

    • subscribe_orderbook(): Bid/ask levels for specific assets.
    • subscribe_prices(): Price change events.
    • subscribe_midpoints(): Calculated midpoint prices.
    • subscribe_orders(): User order updates (requires authentication).
    • subscribe_trades(): User trade executions (requires authentication).
    use futures::StreamExt as _;
    use polymarket_client_sdk::clob::ws::Client;
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::default();
    
        // Subscribe to orderbook updates for specific assets
        let asset_ids = vec!["<asset-id>".to_owned()];
        let stream = client.subscribe_orderbook(asset_ids)?;
        let mut stream = Box::pin(stream);
    
        while let Some(book_result) = stream.next().await {
            let book = book_result?;
            println!("Orderbook update for {}: {} bids, {} asks",
                book.asset_id, book.bids.len(), book.asks.len());
        }
        Ok(())
    }
  5. Set token allowances for USDC and Conditional Tokens

    main

    MetaMask and EOA (Externally Owned Account) users must grant token allowances to the exchange contracts before Polymarket can move funds to execute trades. If you are using a proxy or a Safe-type wallet, you do not need to set these allowances.

    You must approve two types of tokens:

    1. USDC: Required for deposits and trading.
    2. Conditional Tokens: The specific outcome tokens you intend to trade.

    Allowances only need to be set once per wallet. After approval, you can trade freely without repeated permission steps.

    // Use examples/approvals.rs to approve the right contracts.
    // 1. Run once to approve USDC.
    // 2. Change the TOKEN_TO_APPROVE environment variable/constant and run for each conditional token.
  6. Use the RTDS Client for real-time data streaming

    main

    The RTDS (Real-Time Data Socket) client allows you to stream real-time Polymarket data, including cryptocurrency prices (Binance and Chainlink) and comment events.

    There are two primary client states:

    • Client<Unauthenticated>: Access to all streams and unauthenticated comment events.
    • Client<Authenticated<Normal>>: Access to all streams plus authenticated comment events via CLOB credentials.

    You can initialize a default client using Client::default() or create a custom one with Client::new(endpoint, config).

    To transition from an unauthenticated client to an authenticated one, use the .authenticate(address, credentials) method. To return to an unauthenticated state, use .deauthenticate() on an authenticated client. Note that these state-transition methods consume the client, meaning they require ownership of the instance.

    use polymarket_client_sdk::rtds::Client;
    use futures::StreamExt;
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::default();
    
        // Subscribe to BTC and ETH prices from Binance
        let symbols = vec!["btcusdt".to_owned(), "ethusdt".to_owned()];
        let stream = client.subscribe_crypto_prices(Some(symbols))?;
        let mut stream = Box::pin(stream);
    
        while let Some(price) = stream.next().await {
            println!("Price: {:?}", price?);
        }
    
        Ok(())
    }
  7. Calculate CTF IDs (Condition, Collection, and Position)

    main

    To manage positions in CTF (Conditional Token Framework) markets, you must derive specific identifiers. The SDK provides request types to calculate these IDs:

    1. Condition ID: Derived from the oracle address, question_id (hash), and outcome_slot_count.
    2. Collection ID: Derived from a parent_collection_id (use zero for top-level), the condition_id, and an index_set representing outcome slots.
    3. Position ID: The final ERC1155 token ID, generated from the collateral_token address and the collection_id.
    // Example conceptual flow for ID generation
    // Note: Actual calculation logic is handled by the client using these request types
    let condition_req = ConditionIdRequest::builder()
        .oracle(oracle_address)
        .question_id(question_hash)
        .outcome_slot_count(U256::from(2))
        .build();
  8. How CTF operations work

    main

    The CTF (Conditional Token Framework) client manages the lifecycle of market outcomes as ERC1155 tokens. The workflow typically follows these steps:

    1. ID Calculation: Use the client to derive condition_id, collection_id, and position_id to identify specific markets and tokens.
    2. Splitting: Convert collateral into outcome tokens via split_position to enter a market.
    3. Merging: Combine outcome tokens back into collateral via merge_positions.
    4. Redeeming: Once a condition is resolved, use redeem_positions (standard) or redeem_neg_risk (for negative risk markets) to recover collateral from winning tokens.
  9. Understand the `WsMessage` enum structure

    main

    All messages received via the Polymarket WebSocket are wrapped in the WsMessage enum. You can distinguish between market data and user-specific data using the following helper methods:

    • is_user(): Returns true if the message is a Trade or an Order (authenticated channel).
    • is_market(): Returns true if the message is market data (not a user message).

    Available WsMessage variants (mapped from event_type):

    • Book(BookUpdate): Full or incremental orderbook update.
    • PriceChange(PriceChange): Price change notification.
    • TickSizeChange(TickSizeChange): Tick size change notification.
    • LastTradePrice(LastTradePrice): Last trade price update.
    • BestBidAsk(BestBidAsk): Best bid/ask update (requires custom_feature_enabled).
    • NewMarket(NewMarket): New market created (requires custom_feature_enabled).
    • MarketResolved(MarketResolved): Market resolved (requires custom_feature_enabled).
    • Trade(TradeMessage): User trade execution (authenticated channel).
    • Order(OrderMessage): User order update (authenticated channel).
    match message {
        WsMessage::Book(update) => handle_book(update),
        WsMessage::Trade(trade) => handle_trade(trade),
        _ => {}
    }
  10. Understand Market identifiers in API responses

    main

    The Market enum is used to identify markets in various API responses. It can represent either a global market state or a specific market identified by a B256 condition ID.

    • Market::Global: Represents all markets (aliased from global or GLOBAL in JSON).
    • Market::Market(B256): Represents a specific market using its unique condition ID.
    pub enum Market {
        #[serde(alias = "global", alias = "GLOBAL")]
        Global,
        #[serde(untagged)]
        Market(B256),
    }
  11. How AuthenticationBuilder works

    main

    The AuthenticationBuilder is used to transition a Client<Unauthenticated> to a Client<Authenticated<K>>. It allows configuring several parameters before the authentication call is made:

    • .nonce(u32): Sets an optional nonce.
    • .credentials(Credentials): Supplies existing credentials instead of creating new ones.
    • .funder(Address): Sets the funder address. If set, signature_type must be Proxy or GnosisSafe. If not set, signature_type must be Eoa.
    • .signature_type(SignatureType): Specifies the type of signature used.
    • .salt_generator(fn() -> u64): Provides a custom salt/seed generator for SignableOrders.
  12. Configure Polymarket SDK Feature Flags

    main

    The SDK is modular. You must enable specific features in your Cargo.toml to access different Polymarket APIs. Use the following table to determine which features to enable:

    FeatureDescription
    clobCore CLOB client for order placement, market data, and authentication
    tracingStructured logging via tracing for HTTP requests, auth flows, and caching
    wsWebSocket client for real-time orderbook, price, and user event streaming
    rtdsReal-time data streams for crypto prices (Binance, Chainlink) and comments
    dataData API client for positions, trades, leaderboards, and analytics
    gammaGamma API client for market/event discovery, search, and metadata
    bridgeBridge API client for cross-chain deposits (EVM, Solana, Bitcoin)
    rfqRFQ API (within CLOB) for submitting and querying quotes
    heartbeatsClob feature that automatically sends heartbeat messages to the Polymarket server; if the client disconnects, all open orders will be cancelled
    ctfCTF API client to perform split/merge/redeem on binary and neg risk markets
    [dependencies]
    polymarket-client-sdk = { version = "0.3", features = ["ws", "data"] }