EIP-712 / py-clob-client

Polymarket Order Signing — EIP-712, py-clob-client, and Why Orders Reject Silently

Every Polymarket order is an EIP-712 typed-data signature against the exchange contract on Polygon. This is what actually happens during signing, the failure modes that silently reject orders, and how PredictEngine handles signing without exposing the user to it.

What Polymarket order signing actually is

A Polymarket order is not a message to a server. It is an EIP-712 typed-data signature produced by the trader's private key, signed against the exchange contract on Polygon. The signature commits the trader to the price, side, size, and expiration of a single order — and authorizes the exchange to move conditional-token balances on their behalf if the order fills.

When you call create_and_post_order() in py-clob-client (or the equivalent in @polymarket/clob-client), the SDK builds an Order struct, hashes it under the EIP-712 domain pinned to the Polygon chain ID and the exchange contract address, signs that hash with the trader's EOA private key, and posts the order plus signature to the CLOB API. The CLOB validates the signature, places the order in the book, and surfaces fills back via websocket.

This is the same signing recipe used by any EIP-712-compatible protocol — Uniswap permit2, OpenSea, Polymarket. The difference for Polymarket is that the order struct includes maker/taker amounts denominated in conditional-token shares, the nonce is tracked per trader-address, and the signature has to remain valid until the order fills or expires.

Why hand-rolling Polymarket order signing is hard

py-clob-client makes signing look like a one-liner — but only because the SDK is hiding ten edges where things break:

  • EIP-712 domain mismatches — Polygon mainnet is chain ID 137. The CLOB rejects orders signed against any other chain ID. Local testing on Mumbai used to fool many bots; mainnet rejections only show up under live capital.
  • Wrong exchange contract address — the verifyingContract field in the EIP-712 domain must exactly match the live exchange contract. Upgrades change it; hard-coded addresses go stale.
  • Nonce collision — every order carries a nonce. If two orders race and use the same nonce, one rejects. Multi-threaded bots have to maintain a monotonically-increasing per-address nonce counter.
  • Maker-amount integer precision — Polymarket amounts are denominated in 1e6 (USDC.e) units. Hand-rolling the math with floats introduces precision loss; the signed hash no longer matches the on-chain hash.
  • Order-type confusion — GTC, FOK, FAK, and GTD orders have different EIP-712 representations. Signing a GTC payload but submitting it as FOK silently rejects.
  • Wrong sign function — JSON-RPC eth_sign vs eth_signTypedData_v4 produce different signatures. The CLOB only accepts v4 typed-data signatures.
  • Signature replay protection — order hashes are stored on-chain after fill. Re-signing the exact same order parameters without bumping the nonce results in a rejected duplicate.
  • Key custody — to sign, you hold the private key in memory. The signing layer is also the highest-blast-radius part of any trading stack; mistakes here are unrecoverable.

The two ways to sign — and what they cost you

There are exactly two production paths for Polymarket order signing:

PathWhat you writeWhat you own
py-clob-client / @polymarket/clob-clientSDK call: client.create_and_post_order(order_args)Private key custody, EIP-712 domain config, nonce tracking, retry/backoff, error handling on rejection codes
PredictEngine no-codeStrategy description in plain English or visual formStrategy logic only — signing, nonce, retries, key encryption all handled by the platform

How PredictEngine handles order signing

PredictEngine produces EIP-712 signatures for every order using the same py-clob-client internals. Each user gets a Polygon deposit wallet on first sign-up; the private key is encrypted at rest with AES, decrypted in memory only at signing time, and never exposed in transit or in logs.

On top of the signing primitive, PredictEngine layers gasless execution via Polymarket's Builder relayer — so the user pays nothing in MATIC gas. Nonce tracking is centralized per wallet, retry/backoff on signature-rejection codes is automatic, and orders that reject for recoverable reasons (chain reorg, transient 5xx) are re-signed and re-posted without user intervention.

The result: you never see an EIP-712 typed-data payload, never debug a chain-ID mismatch, never lose a key. You describe the strategy; PredictEngine signs and posts the orders.

Common signing errors and what they mean

When orders reject, the CLOB returns one of a small set of error codes. The actionable ones:

  • "invalid_signature" — almost always wrong chain ID (use 137), wrong verifyingContract address, or signing with eth_sign instead of eth_signTypedData_v4.
  • "insufficient_balance" — the trader's USDC.e balance on Polygon is below the order's maker amount, or USDC.e approval against the exchange contract is too low. Set approval to MaxUint256 once.
  • "duplicate_order" — the order hash matches an existing on-book order. Bump the nonce or change order parameters.
  • "nonce_too_low" — another order at this nonce already filled or rejected. Increment the per-address counter.
  • "price_invalid" — Polymarket orders are denominated in cents (0.01 to 0.99). Orders at 0.00 or 1.00 reject.
  • "market_not_active" — the market resolved, paused, or closed for trading. Re-check market state before signing.

When to sign yourself vs let PredictEngine do it

You should own signing yourself when you need millisecond-latency execution (the round-trip to PredictEngine's hosted layer is fine for most strategies but loses to in-region servers for HFT), when you have non-standard order routing (cross-margin with another protocol, custom hedging), or when you have an existing custody/key-management setup that PredictEngine cannot integrate with.

PredictEngine handles signing for everyone else — including the long tail of strategy ideas that never ship because the founder did not want to learn EIP-712. Validate the strategy first, then decide whether ownership of the signing layer is worth carrying.

Never write an EIP-712 domain again.

PredictEngine produces every signature on your behalf — gasless, retry-aware, key-encrypted at rest. You describe the strategy.

Frequently Asked Questions

Related