Lightweight C++ client for Polymarket APIs with REST and WebSocket support, designed for trading and market data access.
Polymarket C++ Client
Reusable C++20 client for Polymarket: REST, WebSocket streaming, and order signing (EIP-712) with examples and tests.
Features
- REST: market discovery, orderbook/price queries, auth key management, and trading endpoints.
- Transport controls: configurable libcurl timeouts, keepalive, connection reuse, proxy/user-agent, request metrics, and cumulative stats.
- WebSocket: orderbook streaming via IXWebSocket with reconnect, subscription replay, typed callbacks, and backpressure counters.
- Signing: CLOB V2 EIP-712 order signing (secp256k1, keccak).
- Decimal math: shared scaled-integer conversion for trading amounts.
- Structured errors: opt-in
Result<T>APIs with typed SDK error classification. - EVM JSON-RPC: Polygon HTTP catch-up and WebSocket subscriptions for logs, heads, and pending transaction hashes.
- Resolution Events: Decoders for UMA adapter and Conditional Tokens resolution/redemption logs.
- Proxy Support: HTTP/HTTPS proxy with authentication for geo-restricted access.
- Neg-Risk Markets: Automatic exchange selection for neg_risk markets.
- Examples: REST (
restexample), signing (signexample), WebSocket (ws_example), onchain watchers. - Benchmarks and tests: local benchmark targets plus
ctestcoverage.
Requirements
- CMake 3.16+
- C++20 compiler
- libcurl, OpenSSL
Installation
Option 1: CMake FetchContent (Recommended)
Add to your CMakeLists.txt:
include(FetchContent)
Fetch specific version
FetchContent_Declare(
polymarket_client
GIT_REPOSITORY https://github.com/SebastianBoehler/polymarket-cpp-client.git
GIT_TAG v1.3.0 # or any release tag
)
FetchContentMakeAvailable(polymarketclient)
Link to your target
targetlinklibraries(your_target PRIVATE polymarket::client)
Option 2: Pre-built Releases
Download pre-built binaries from Releases:
# macOS
curl -LO https://github.com/SebastianBoehler/polymarket-cpp-client/releases/download/v1.3.0/polymarket-cpp-client-macos-arm64.tar.gz
tar -xzf polymarket-cpp-client-macos-arm64.tar.gz -C /usr/local
Linux
curl -LO https://github.com/SebastianBoehler/polymarket-cpp-client/releases/download/v1.3.0/polymarket-cpp-client-linux-x64.tar.gz
tar -xzf polymarket-cpp-client-linux-x64.tar.gz -C /usr/local
Then in your CMake:
findpackage(polymarketclient REQUIRED)
targetlinklibraries(your_target PRIVATE polymarket::client)
Option 3: Build from Source
cmake -S . -B build -DPOLYMARKETCLIENTBUILDEXAMPLES=ON -DPOLYMARKETCLIENTBUILDTESTS=ON
cmake --build build --parallel
optional tests
ctest --test-dir build
optional benchmarks
cmake -S . -B build -DPOLYMARKETCLIENTBUILD_BENCHMARKS=ON
install (into system or a prefix you configure)
cmake --install build --prefix <install_prefix>
Version Info
Check library version at runtime:
#include <polymarket/version.hpp>
#include <iostream>
int main() { std::cout << "polymarket-cpp-client v" << polymarket::version_string << "\n"; // Or access individual components: // polymarket::versionmajor, versionminor, version_patch }
Examples
rest_example: fetch markets from CLOB RESTsignexample: sign a dummy order (requiresPRIVATEKEY)ws_example: connect to Polymarket WS and subscribe to orderbook aggumaoraclewatch: stream UMA adapter lifecycle events over Polygon JSON-RPCconditionresolutionwatch: stream Conditional Tokens resolution/redemption eventsevmeventindexer_example: persistent HTTP catch-up + live WS indexer with a cursor filefeedlatencybenchmark: compare local receive timing across Polymarket market WS and Polygon RPC WS
POLYMARKETCLIENTBUILD_EXAMPLES=ON and run from build/.
Local benchmark targets are documented in docs/benchmarks.md.
CLOB V2 Status
Polymarket production CLOB uses V2 at https://clob.polymarket.com. The authenticated order path signs V2 EIP-712 orders, posts V2 order payloads, uses the V2 exchange contracts, and supports the V2 POLY_1271 deposit-wallet signature wrapper. See docs/clob-v2-migration.md for implementation notes.
Structured Errors
Existing convenience methods still return std::optional, vectors, booleans, or OrderResponse. New opt-in Result<T> methods expose structured failures for callers that need to inspect transport errors, API responses, auth failures, rate limits, parse errors, signing errors, and invalid arguments.
auto result = client.getopenorders_result();
if (!result) {
const auto& error = result.error();
std::cerr << polymarket::sdkerrorcodetostring(error.code)
<< " " << error.http_status
<< " " << error.message << "\n";
}
SdkError includes endpoint, HTTP status, response body excerpt, retryability, and request id when the server returns a recognizable request-id header.
Polygon JSON-RPC Watchers
The client includes provider-neutral EVM JSON-RPC helpers for users who want to build their own low-latency indexer instead of depending on a third-party Polymarket data feed.
# Live UMA adapter events
POLYMARKETPOLYGONRPC_WS=wss://your-polygon-rpc \
POLYMARKETUMACTF_ADAPTER=0x... \
./build/umaoraclewatch
Catch up historical CTF logs first, then stream live logs
./build/conditionresolutionwatch \
--rpc-http https://your-polygon-rpc \
--rpc-ws wss://your-polygon-rpc \
--ctf 0x... \
--from-block 0x39f0000 \
--to-block latest
Optional flags:
--pending: also subscribe tonewPendingTransactionsif the RPC provider or node exposes mempool data.--heads: also stream new block headers.--duration-seconds <n>: stop automatically afternseconds.--timeout-ms <n>: override the HTTP JSON-RPC timeout used during catch-up.
For persistent indexing, combine ethgetLogs catch-up (--from-block) with live ethsubscribe logs and store the last processed block in your own application. A normal hosted WebSocket RPC is enough for confirmed logs. Complete pre-confirmation mempool visibility is not guaranteed by public RPC; for that you typically need a provider that exposes pending transactions at scale or your own Polygon node with txpool/mempool access.
The reusable EvmEventIndexer handles the catch-up/live handoff for any EvmLogFilter:
./build/evmeventindexer_example \
--rpc-http https://your-polygon-rpc \
--rpc-ws wss://your-polygon-rpc \
--ctf 0x... \
--cursor-file ./polymarket-cursors.json \
--start-block 0x51b0000 \
--live \
--duration-seconds 60
If no cursor exists and --start-block is omitted, the example starts at the current latest block to avoid accidental full-history backfills. Pass --start-block explicitly when you want historical replay.
Tests
testutils exercises basic utility helpers. testevmevents covers EVM topic hashing, log filter serialization, and UMA/CTF event decoding. testevmeventindexer covers block range planning and file-backed cursors. Transport, order execution, typed error, signing, and WebSocket resilience tests are included when POLYMARKETCLIENTBUILD_TESTS=ON. Run via ctest --test-dir build.
testoraclewatcher validates in-memory normalization against synthetic UMA/CTF fixtures. testoraclewatcher_historical performs a live historical smoke check using defaults:
POLYMARKETPOLYGONRPC_HTTP=https://polygon-bor.publicnode.comPOLYMARKETUMACTF_ADAPTER=0x6a9d222616c90fca5754cd1333cfd9b7fb6a4f74POLYMARKETCONDITIONALTOKENS=0x4d97dcd97ec945f40cf65f87097ace5ea0476045
Key components
include/headers for client APIsrc/http_client.cpp: libcurl HTTP clientsrc/sdk_error.cpp: typed SDK error helperssrc/websocket_client.cpp: IXWebSocket wrappersrc/websocketclientresilience.cpp: reconnect, queue, and subscription helperssrc/jsonrpcclient.cpp: EVM HTTP/WS JSON-RPC helperssrc/evmeventindexer.cpp: persistent log catch-up and live indexingsrc/evm_utils.cpp: ABI/log utilitiessrc/polymarket_events.cpp: UMA/CTF event decoderssrc/order_signer.cpp: EIP-712 signing (secp256k1, keccak)src/clob_client.cpp: REST endpoints and parsingsrc/cloborderexecution.cpp: trading/order execution endpointssrc/orderbook.cpp: WS orderbook management
Proxy Configuration
Configure HTTP proxy for geo-restricted access:
#include "clob_client.hpp"
polymarket::ClobClient client("https://clob.polymarket.com", 137);
// Set proxy (supports authentication) client.set_proxy("http://user:pass@proxy.example.com:8080");
// Optional: set custom user agent client.setuseragent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...");
Low-Latency Trading (Keep TCP/TLS Hot)
Keep TCP/TLS connections warm by configuring transport options once, pre-warming the connection, and optionally running a heartbeat:
#include "clob_client.hpp"
polymarket::HttpClientOptions http_options; httpoptions.timeoutms = 2500; httpoptions.connecttimeout_ms = 1000; httpoptions.dnscachetimeoutseconds = 120; httpoptions.tcpnodelay = true; httpoptions.tcpkeepalive = true;
polymarket::ClobClient client("https://clob.polymarket.com", 137, private_key, creds); client.configuretransport(httpoptions); client.warm_connection(); client.start_heartbeat(25);
auto response = client.createandpost_order(params); auto stats = client.getconnectionstats(); std::cout << "Avg latency: " << stats.avglatencyms << "ms\n";
client.stop_heartbeat();
Order Precision
Limit and market order helpers round prices, share sizes, and maker/taker amounts with Polymarket's tick-size precision rules. CreateOrderParams and CreateMarketOrderParams default to tick_size = "0.01"; set this from cached market metadata when trading markets with a different minimum tick size.
WebSocket Resilience
WebSocketClient supports additive production-safety options for market-data consumers: automatic reconnect backoff, ping interval, bounded message queue, subscription replay, typed message callbacks, and counters for reconnects, dropped messages, parse errors, last message time, messages, and bytes.
polymarket::WebSocketClient ws;
polymarket::WebSocketOptions options;
options.messagequeuelimit = 4096;
options.minbackoffms = 250;
options.maxbackoffms = 5000;
ws.configure(options);
ws.on_message([](const std::string& raw) { // Raw callback remains available. }); ws.ontypedmessage([](const polymarket::TypedWebSocketMessage& msg) { if (msg.topic == "clobmarket" && msg.type == "aggorderbook") { // Use msg.asset_id and msg.payload. } });
Call tracksubscription(subscriptionjson) after sending a subscription if you use WebSocketClient directly. OrderbookManager tracks its subscription message and restores it automatically after reconnect.
Neg-Risk Markets
The client automatically detects neg_risk markets and uses the appropriate exchange address for order signing:
- Standard markets:
0xE111180000d2663C0091e4f400237545B87B996B - Neg-risk markets:
0xe2222d279d744050d28e00520010520000310F59
create_order() - no manual intervention needed.
GitHub Actions
- build.yml: Debug and Release builds on Linux and macOS, with examples, tests, benchmark targets, and
ctest. - release.yml: Automated releases when you push a version tag
License
MIT