A low-latency C++20 SDK for Polymarket.
CLOB REST and WebSocket streaming, EIP-712 order signing, on-chain positions, and Polygon indexing.
Quick start · Features · Installation · Examples · Performance · Docs · Contributing
#include <polymarket/clob_client.hpp>
#include <cstdlib>
#include <iostream>
int main()
{
using namespace polymarket;
const char *private_key = std::getenv("PRIVATE_KEY");
if (!private_key) return 1;
// Derive L2 API credentials from the signer once, then trade with both.
const auto creds = ClobClient("https://clob.polymarket.com", 137, private_key)
.create_or_derive_api_key();
ClobClient client("https://clob.polymarket.com", 137, private_key, creds);
// Polymarket rejects orders from restricted regions; check before trading.
const auto geoblock = client.get_geoblock_status();
if (!geoblock || geoblock.value().blocked) return 1;
client.warm_connection(); // open TCP + TLS before the first order
CreateOrderParams order;
order.token_id = "<token id>";
order.price = 0.42;
order.size = 10;
order.side = OrderSide::BUY;
const auto response = client.create_and_post_order(order); // GTC by default
std::cout << (response.success ? response.order_id : response.error_msg) << "\n";
}Tick size and neg-risk metadata are resolved and cached for you. See Examples for streaming, user events, and on-chain positions.
| Area | What you get |
|---|---|
| Trading | CLOB V2 EIP-712 order signing (EOA, proxy, Safe, POLY_1271 deposit wallets), limit and market orders, batch posting, cancels, tick-size rounding |
| Market data | REST books, prices, midpoints, markets, trades; WebSocket orderbook streaming with reconnect, subscription replay, gap detection, backpressure |
| User stream | Authenticated UserStream with typed order/trade events, gap callbacks, and REST reconciliation hooks |
| On-chain positions | PositionClient split/merge/redeem for CTF and Protocol V2, trading approvals, gasless Safe operations through the Polymarket relayer |
| Polygon indexing | JSON-RPC HTTP catch-up + WebSocket subscriptions, persistent EvmEventIndexer, UMA and Conditional Tokens event decoders |
| Networking | HTTP/HTTPS/SOCKS proxies and VPN interface binding for REST and WebSockets, one process-wide route, fail-closed, geoblock eligibility check |
| Low latency | Warm keep-alive connections, heartbeat, TCP_NODELAY, DNS caching, metadata caches, per-request metrics |
| Errors | Opt-in Result<T> APIs with typed SdkError (transport, API, auth, rate limit, parse, signing) and request IDs |
| Neg-risk markets | Automatic exchange and collateral-adapter selection |
- CMake 3.22+ and a C++20 compiler
- libcurl, OpenSSL, zlib
- Linux or macOS (prebuilt releases: Linux x86-64, macOS 12+ arm64)
Other dependencies (nlohmann/json, IXWebSocket, secp256k1, keccak) are fetched and pinned by hash at configure time.
include(FetchContent)
FetchContent_Declare(
polymarket_client
GIT_REPOSITORY https://github.com/SebastianBoehler/polymarket-cpp-client.git
GIT_TAG v3.0.0 # or any release tag
)
FetchContent_MakeAvailable(polymarket_client)
target_link_libraries(your_target PRIVATE polymarket::client)Download an archive from Releases and keep it as a dedicated prefix, since it contains its pinned static dependencies and headers:
# macOS arm64 (use polymarket-cpp-client-linux-x64.tar.gz on Linux)
curl -LO https://github.com/SebastianBoehler/polymarket-cpp-client/releases/download/v3.0.0/polymarket-cpp-client-macos-arm64.tar.gz
mkdir -p polymarket-cpp-client-3.0.0
tar -xzf polymarket-cpp-client-macos-arm64.tar.gz -C polymarket-cpp-client-3.0.0# cmake -S . -B build -DCMAKE_PREFIX_PATH=/absolute/path/to/polymarket-cpp-client-3.0.0
find_package(polymarket_client REQUIRED)
target_link_libraries(your_target PRIVATE polymarket::client)./build.sh # configure, build examples and tests, run offline tests
# or step by step:
cmake -S . -B build -DPOLYMARKET_CLIENT_BUILD_EXAMPLES=ON -DPOLYMARKET_CLIENT_BUILD_TESTS=ON
cmake --build build --parallel
ctest --test-dir build --output-on-failure -LE live
cmake --install build --prefix <install_prefix>Upgrading from v1 or v2? See the migration guide. Check the
version at runtime with polymarket::version_string from <polymarket/version.hpp>.
#include <polymarket/websocket_client.hpp>
polymarket::WebSocketClient ws;
ws.set_url("wss://ws-subscriptions-clob.polymarket.com/ws/market");
polymarket::WebSocketOptions options;
options.message_queue_limit = 4096;
options.max_reconnect_attempts = 5;
ws.configure(options);
// Sent on connect and replayed after every reconnect.
ws.track_subscription(R"({"assets_ids":["<yes token>","<no token>"],"type":"market"})");
ws.on_typed_message([](const polymarket::TypedWebSocketMessage &msg) {
if (msg.event_type == "book" || msg.event_type == "price_change") {
// msg.asset_id changed
}
});
ws.connect();OrderbookManager builds on this: it keeps sorted books per token, sends real
subscribe/unsubscribe operations, and restores the token set after reconnect.
WebSocketStats counts reconnects, dropped messages, parse errors, and bytes.
polymarket::HttpClientOptions http;
http.timeout_ms = 2500;
http.connect_timeout_ms = 1000;
http.dns_cache_timeout_seconds = 120;
polymarket::ClobClient client("https://clob.polymarket.com", 137, private_key, creds,
polymarket::SignatureType::EOA, "", http);
client.warm_connection();
client.start_heartbeat(25);
auto response = client.create_and_post_order(params);
std::cout << "avg latency: " << client.get_connection_stats().avg_latency_ms << " ms\n";Order helpers round prices, sizes, and maker/taker amounts to the market's tick
size. Leave tick_size empty to resolve it from the client's metadata cache.
Environment holds every endpoint and contract for one deployment. Pass it to
ClobClient, or build stream and position configs from it:
#include <polymarket/environment.hpp>
const auto env = polymarket::Environment::preproduction(); // or production()
polymarket::ClobClient client(env, private_key, creds);
polymarket::UserStream stream(polymarket::Config::for_environment(env), creds);
auto position_config = polymarket::PositionClientConfig::for_environment(env);preproduction() uses separate CLOB, Gamma, Data API, and relayer hosts, but
it is not a testnet. It runs on Polygon mainnet with the production contracts,
RPC, and CLOB WebSocket hosts, so on-chain operations and settled orders use
real funds. API keys are per environment. Copy a preset and override fields to
target a fork or a local server. The string constructors
(ClobClient(base_url, 137, ...)) override only the production CLOB host and
accept only chain 137.
#include <polymarket/network.hpp>
// Every SDK connection, REST and WebSocket, now uses this route.
polymarket::set_default_network_route({.proxy_url = "socks5h://127.0.0.1:1080"});
// or bind to a VPN tunnel interface instead:
polymarket::set_default_network_route({.interface_name = "wg0"});Routes fail closed: if the proxy or interface is unavailable, connections fail
instead of going direct. Per-client overrides live in HttpClientOptions and
WebSocketOptions. See docs/networking.md.
Important
Proxies and VPNs change your network path, not your location. Polymarket's
Terms of Use prohibit using them to get around
geographic restrictions. Use get_geoblock_status() to check eligibility.
Convenience methods return std::optional, vectors, booleans, or
OrderResponse. Opt-in *_result methods return Result<T> with a typed error:
auto result = client.get_open_orders_result();
if (!result) {
const auto &error = result.error();
std::cerr << polymarket::sdk_error_code_to_string(error.code) << " "
<< error.http_status << " " << error.message << "\n";
}SdkError includes the endpoint, HTTP status, response excerpt, retryability,
and the request ID when the server returns one. Rejections also carry
retry_after_seconds and the Poly-RateLimit-* state when the server sends them.
Reads that get HTTP 429 are retried up to twice, each after exactly the
server's Retry-After delay (1 s when absent). A requested delay above 5 s
returns the error instead. Orders, cancellations, and other writes are never
retried; check retry_after_seconds on their error and decide yourself.
client.set_rate_limit_retry(polymarket::RateLimitRetry{1, std::chrono::seconds(2)});
client.set_rate_limit_retry(std::nullopt); // fail on the first 429
client.set_rate_limit_listener([](const polymarket::RateLimitUpdate &update) {
if (update.warning) std::cerr << "order rate would be rejected under enforcement\n";
});The listener receives the Poly-RateLimit-* headers from order and cancel
responses. PositionClientConfig::rate_limit_retry and Config::rate_limit_retry
set the same policy for PositionClient and MarketFetcher. Unlike the official
SDKs, CLOB reads are retried too; disable it for latency-critical paths.
#include <polymarket/position_client.hpp>
polymarket::PositionClientConfig config;
config.private_key = std::getenv("PRIVATE_KEY");
config.rpc_url = std::getenv("POLYGON_RPC_ENDPOINT");
config.wallet_type = polymarket::SignatureType::POLY_GNOSIS_SAFE; // or EOA
config.relayer_api_key = std::getenv("RELAYER_API_KEY");
polymarket::PositionClient positions(config);
positions.setup_trading_approvals(); // once per wallet; grants what is missing
positions.merge_positions(condition_id, "max").wait();See docs/position-operations.md for wallet types, batching and atomicity, errors, and the approval set.
create_order() detects neg-risk markets and signs against the matching
exchange; PositionClient picks the neg-risk collateral adapter the same way.
- Standard exchange:
0xE111180000d2663C0091e4f400237545B87B996B - Neg-risk exchange:
0xe2222d279d744050d28e00520010520000310F59
Build with -DPOLYMARKET_CLIENT_BUILD_EXAMPLES=ON and run from build/.
| Example | What it does |
|---|---|
rest_example |
Public markets and books; balances and open orders with credentials |
sign_example |
Signs a dummy order (PRIVATE_KEY) |
ws_example |
Streams market-channel orderbook messages |
user_stream_example |
Streams your own order and trade events (PRIVATE_KEY) |
position_example |
Split, merge, or redeem from an EOA or Safe; dry run unless --execute |
approvals_example |
Lists and grants missing trading approvals; dry run unless --execute |
uma_oracle_watch |
Streams UMA adapter lifecycle events over Polygon JSON-RPC |
condition_resolution_watch |
Streams Conditional Tokens resolution and redemption events |
evm_event_indexer_example |
Persistent HTTP catch-up + live WebSocket indexer with a cursor file |
feed_latency_benchmark |
Compares receive timing across the Polymarket market WS and a Polygon RPC WS |
polymarket_arb |
Analysis-only scan of complementary YES/NO books (--15m --symbol btc --fetch-only) |
Examples that call Polymarket services, and order_test, read
POLYMARKET_ENV (production by default, or preproduction).
polymarket_arb rejects --live: the CLOB batch endpoint processes orders
independently, so two complementary FOK orders are not an atomic trade.
Hot paths are measured by small benchmark targets
(-DPOLYMARKET_CLIENT_BUILD_BENCHMARKS=ON). Best of 7 runs, Release build,
Apple M4 Max, real-length 77-digit token IDs:
| Workload | Time per operation |
|---|---|
Parse + apply a 2-change price_change message |
3.4 µs |
| Parse + apply a 100-level book snapshot | 28.7 µs |
| Sign a CLOB V2 order (EIP-712, secp256k1) | 15.6 µs |
Methodology and how to compare branches: docs/benchmarks.md.
| Guide | Topic |
|---|---|
| Networking | Proxies, VPN interfaces, WebSocket routing, geoblock |
| Position operations | Split, merge, redeem, approvals, Safe relayer |
| Polygon indexing | JSON-RPC watchers, persistent indexer, reorg handling |
| CLOB V2 migration | V2 order signing and exchange contracts |
| Benchmarks | Benchmark targets and methodology |
| Migration guide | Upgrading between major versions |
| Protocol development | Rules for protocol, signing, and stream lifecycle work |
| Releasing | Release process for maintainers |
Contributions are welcome. Start with CONTRIBUTING.md for coding rules, commit format, and the checks CI runs. Coding agents (Claude Code, Codex, Cursor, Copilot) start with AGENTS.md.
./build.sh # build everything and run the offline test suiteBugs and feature requests go to Issues; security reports follow SECURITY.md.
This is an independent open-source project, not affiliated with or endorsed by Polymarket. Trading involves risk of loss; test with small sizes and read the code paths you depend on. You are responsible for complying with Polymarket's terms and the laws of your jurisdiction.