Building a Polymarket CLOB Client in Elixir: Architecture and Cryptography
{{Polymarket}}'s {{CLOB}} uses a two-layer auth scheme — {{EIP-712}} wallet signatures (L1) to mint API credentials, then {{HMAC-SHA256}} headers (L2) per request — plus per-order {{EIP-712}} signatures for non-custodial settlement on {{Polygon}}. No {{Elixir}} client exists as of May 2026, but {{signet}}, {{ex_eip712}} and {{eip712}} provide the cryptographic primitives needed to build one.
Polymarket's Central Limit Order Book (CLOB) is a hybrid-decentralized exchange: an off-chain operator matches orders, but settlement is non-custodial — every trade is executed on-chain via the Exchange contract on Polygon, using signed order messages the operator cannot tamper with. Building a client in Elixir requires reproducing the auth scheme, the order-signing payload, and the ABI encoding that the Exchange contract expects. ## Authentication: two layers, not three The official docs describe **two** auth levels (the stub previously claimed three; public read endpoints are simply unauthenticated): - **L1 — wallet auth**: prove key ownership by signing an EIP-712 typed-data message under the `ClobAuthDomain` domain (name `"ClobAuthDomain"`, version `"1"`, `chainId` 137 for Polygon). The signature is an ECDSA signature over secp256k1, the same curve Ethereum uses. L1 is called to mint API credentials (`apiKey`, `secret`, `passphrase`). - **L2 — per-request HMAC**: every trading request carries five headers — `POLY_ADDRESS`, `POLY_SIGNATURE` (HMAC-SHA256 of method + path + body + timestamp, keyed with the L2 secret), `POLY_TIMESTAMP`, `POLY_API_KEY`, `POLY_PASSPHRASE`. - **Plus per-order signing**: even with valid L2 headers, *creating* an order requires an additional EIP-712 signature over the order struct itself. L2 only authenticates the request envelope; the order payload is signed separately so the Exchange contract can verify it on-chain at settlement. The three roles exist for separation of concerns: L1 proves you own the funding wallet (rare, expensive operation), L2 authenticates high-frequency API traffic without re-signing with the cold key, and per-order signatures bind a specific trade to the user's authority in a way the on-chain contract can verify without trusting the operator. ## The order struct A V1 order (still widely deployed; V2 migration in progress) is the EIP-712 typed struct: - `salt` (uint256) — entropy - `maker` (address) — funds source - `signer` (address) — who signed (may differ from maker for proxy wallets) - `taker` (address) — `0x0` for public orders - `tokenId` (uint256) — the ERC-1155 Conditional Tokens Framework (CTF) outcome token ID - `makerAmount` / `takerAmount` (uint256) — quantities - `expiration`, `nonce` (uint256) — onchain cancellation handles - `feeRateBps` (uint256), `side` (uint8: BUY/SELL), `signatureType` (uint8: EOA, POLY_PROXY, POLY_GNOSIS_SAFE) The client computes `keccak256("\x19\x01" || domainSeparator || structHash)` per EIP-712, signs it with secp256k1, and submits the order plus signature over REST. keccak256 is Ethereum's hash function (a variant of SHA-3); domain separation prevents signatures intended for one contract from being replayed on another. ## Market resolution and the full lifecycle Orders bind to ERC-1155 outcome tokens minted by the Conditional Tokens Framework (CTF) — locking $1 of USDC mints one YES and one NO token. Markets resolve via UMA's Optimistic Oracle: a proposer asserts the outcome, a two-hour challenge window opens, undisputed answers settle automatically, disputed ones escalate to UMA token-holder voting through the Data Verification Mechanism. Recent high-profile contested resolutions (the $7M Russia/Ukraine market in late 2025) have exposed governance risk in this layer — see UMA Optimistic Oracle dispute risk in prediction markets for the structural problem. ## Elixir packages available (May 2026) No official or community Elixir Polymarket client exists on hex.pm or GitHub — official SDKs are limited to TypeScript (`@polymarket/clob-client`), Python (`py-clob-client`, with V2 in migration), and Rust (`rs-clob-client`). The Elixir building blocks all exist on hex.pm: - `signet` (Hayes G. McCullough) — full-featured Ethereum library with EIP-191 and EIP-712 signing, transaction building, RPC client, and pluggable signing backends (secp256k1 local keys or Google Cloud KMS). - `ex_eip712` — Rustler NIF wrapper around a Rust EIP-712 implementation; minimal API focused on signature creation. - `eip712` — pure-Elixir typed-data encoder/hasher; useful when you want to avoid Rust toolchain dependencies. - `ex_secp256k1` / `lib_secp256k1` — raw secp256k1 primitives for ECDSA signing and key recovery. A practical Elixir client would use `signet` for L1 wallet signing and order signing, `:crypto.mac/4` from OTP for the L2 HMAC-SHA256 headers, and Req or Finch for HTTPS transport. Oban fits naturally for asynchronous order placement and reconciliation. ## Why the architecture looks this way The pattern — off-chain matching, on-chain settlement, EIP-712-signed orders — is the same one pioneered by 0x Protocol (v3 and v4) and reused by 1inch Limit Order Protocol, dYdX v3, and Hyperliquid. Storing orders on-chain is prohibitively expensive; storing them off-chain but having the Exchange contract verify the user's signature at settlement gives you a centralized-feeling UX (sub-second order placement, free cancellations) with non-custodial guarantees. Polygon's low gas costs (~$0.0063 average per transaction as of March 2025, vs ~$1.72 on Ethereum mainnet) make on-chain settlement economically viable even for small trades. The security model leans on standard primitives: ECDSA over secp256k1 for signatures, keccak256 for hashing, EIP-712 domain separation to prevent cross-protocol replay, HMAC-SHA256 for symmetric request auth, and proper nonce handling. The same care that CSPRNGs require — see CSPRNGs: Why Cryptographically Secure Random Numbers Are Essential for Security — applies to private key generation for the funding wallet.