Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.polynode.dev/llms.txt

Use this file to discover all available pages before exploring further.

V2 cutover: April 28, 2026 at ~11am UTC. Polymarket announced the cutover date on April 18, 2026 (pushed from April 22 for extra builder testing time). The V2 test environment is open to all now at clob-v2.polymarket.com. V2 mainnet contracts have been processing real trades since April 3, 2026. polynode is V2-ready today — set exchange_version: V2 in your SDK config to start placing V2 orders now, or keep V1 until cutover. After April 28, V1 orders will be rejected with order_version_mismatch.
Polymarket is deploying a V2 exchange system on Polygon mainnet. polynode has identified, decoded, and tested against the V2 contracts deployed today. Our settlement stream and trading SDK are built to handle V2 alongside V1. No action is required from existing settlement stream users. When V2 settlements begin on-chain, they will flow through the same WebSocket feed automatically.

Before Cutover: Your Checklist

If you place orders on Polymarket, here’s exactly what to do before April 28:
  1. Upgrade your SDK to the V2-ready version. Older versions won’t generate the correct V2 payload.
    • Rust: polynode >= 0.12.0. Install with cargo add polynode@0.12.0.
    • TypeScript: polynode-sdk >= 0.9.0. Install with npm install polynode-sdk@^0.9.0.
    • Python: polynode >= 0.9.0. Install with pip install "polynode>=0.9.0".
  2. Flip the exchange version flag in your TraderConfig. See The Switch below. This is the only code change required.
  3. Mint a V2 builder code at polymarket.com/settings?tab=builder if you want attribution on your orders. Optional, orders still work without one.
  4. Cancel V1 resting orders you don’t want cleared at cutover. Open V1 orders get wiped when Polymarket flips the switch.
  5. Fund PolyUSD. V2 orders settle in PolyUSD (pUSD), not USDC.e. If your Safe holds USDC.e, call trader.wrap_to_polyusd(amount) (Python) / trader.wrapToPolyUsd(amount) (TS) / trader.wrap_to_polyusd(amount) (Rust) to convert it via the Collateral Onramp. amount is in raw 6-decimal units (1_000_000 = $1; TS takes a bigint — use 1_000_000n). Existing V1 pUSD balance carries over.
  6. Test against V2 now. Point your SDK at V2 (step 2) and place a small order against clob-v2.polymarket.com. ensure_ready / ensureReady sets the V2 approvals and refreshes the CLOB’s cached balance/allowance view for you.
  7. Verify V2 confirmation. Your settlement stream tags V2 trades with "exchange_version": "v2" on every event.

The Switch

V2 activation is one line in your SDK config. Flip it before April 28 and orders route through the V2 exchange automatically. Leave it as V1 and you keep working against V1 until cutover. After that, V1 orders will be rejected.
let config = TraderConfig {
    exchange_version: ExchangeVersion::V2,  // ← this line
    ..Default::default()
};
That’s it. No other code changes required. The SDK handles:
  • New contract addresses (V2 CTF Exchange + NegRisk A/B)
  • New EIP-712 domain (version "2", new verifyingContract)
  • New Order struct (adds builder, metadata, timestamp; drops nonce, feeRateBps from the signed hash)
  • CLOB host swap to clob-v2.polymarket.com
  • PolyUSD collateral wrapping via the Onramp contract

What We’ve Verified

Tested against live V2 contracts on Polygon mainnet:
  • V2 settlement decoding verified against real on-chain transactions
  • V2 trade events (OrderFilled, OrdersMatched) decoded from block receipts
  • PolyUSD wrapping and unwrapping tested and detected in real time
  • V2 order placement tested live on the V2 CLOB
  • Full order lifecycle verified: place, check status, cancel, re-place
  • All existing market data, token IDs, and enrichment confirmed identical between V1 and V2
  • Order hash verification: our EIP-712 order hash computation produces byte-for-byte identical results to the live V2 exchange contract’s hashOrder() function on Polygon mainnet — cryptographic proof that our implementation is correct

V2 Contract Addresses

ContractAddress
PolyUSD0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb
Collateral Onramp0x93070a847efef7f70739046a929d47a521f5b8ee
Collateral Offramp0x2957922eb93258b93368531d39facca3b4dc5854
CTFCollateralAdapter0xAdA100Db00Ca00073811820692005400218FcE1f
NegRiskCTFCollateralAdapter0xadA2005600Dec949baf300f4C6120000bDB6eAab
V2 CTF Exchange0xe111180000d2663c0091e4f400237545b87b996b
V2 NegRisk Exchange A0xe2222d279d744050d28e00520010520000310f59
V2 NegRisk Exchange B0xe2222d002000ba0053cef3375333610f64600036
ConditionalTokens (unchanged)0x4D97DCd97eC945f40cF65F87097ACe5EA0476045
All V2 contracts listed above are deployed and live on Polygon mainnet. On April 30 2026, Polymarket updated the collateral adapter contracts. The new adapters are effective May 1 2026 at 15:00 UTC — the relayer stops accepting transactions through the old adapters after that cutoff.

How V2 Fits Together

The key architectural insight: V2 only changes the exchange layer (how orders are matched and settled). The token layer (ConditionalTokens) is completely unchanged and cannot be replaced.

Why Token IDs Don’t Change

All prediction market positions live inside the ConditionalTokens contract. This is a Gnosis protocol contract that has been on Polygon since Polymarket launched. Every token ID is derived from a condition ID and outcome index inside this contract. V2 does not replace ConditionalTokens. It can’t. Doing so would invalidate every open position on the platform. Instead, V2 introduces an adapter layer between the new exchange contracts and the existing ConditionalTokens contract:
V1 (current):
  USDC.e → V1 Exchange → ConditionalTokens (mints/burns tokens directly)

V2 (new):
  PolyUSD → V2 Exchange → CollateralAdapter → ConditionalTokens
                                 |
                           Unwraps PolyUSD → USDC.e
                           Deposits USDC.e into ConditionalTokens
                           Same tokens minted as V1
The CollateralAdapter translates between PolyUSD (V2’s collateral) and USDC.e (what ConditionalTokens expects). ConditionalTokens never sees PolyUSD. From its perspective, nothing changed. This means:
  • Same token IDs across V1 and V2
  • Same condition IDs
  • Same market metadata (Gamma API, enrichment)
  • Positions opened on V1 are fully compatible with V2 and vice versa

New V2 Contracts

Five new contracts support the V2 architecture. These are all live on Polygon mainnet:
ContractRoleStatus
PolyUSDWrapped USDC.e (1:1, 6 decimals)Deployed
Collateral OnrampWraps USDC.e → PolyUSDDeployed
Collateral OfframpUnwraps PolyUSD → USDC.eDeployed
CTFCollateralAdapterBridges PolyUSD ↔ USDC.e for standard market settlementsDeployed
NegRiskCTFCollateralAdapterBridges PolyUSD ↔ USDC.e for multi-outcome market settlementsDeployed
V2 CTF ExchangeNew order matching for standard marketsDeployed
V2 NegRisk Exchange ANew order matching for multi-outcome marketsDeployed
V2 NegRisk Exchange BAdditional multi-outcome capacityDeployed
All V2 contracts are deployed on Polygon mainnet. V2 trading volume is currently minimal as Polymarket has not yet migrated users to V2.

What’s New in V2

PolyUSD Collateral — V2 uses PolyUSD instead of USDC.e as the exchange collateral. PolyUSD is a 1:1 wrapper around USDC.e with 6 decimals. See the PolyUSD Guide for details on wrapping and unwrapping. Updated Order Struct — The EIP-712 signed hash drops two fields (nonce, feeRateBps) and adds three (timestamp, metadata, builder). The HTTP POST body still carries expiration (defaulted to "0" for no expiration). The polynode SDK handles all of this automatically. The dropped feeRateBps field was Polymarket’s protocol fee slot inside the V1 order. V2 doesn’t have a per-order protocol fee slot — Polymarket’s fee model moves to a CLOB-level config plus the builder bytes32 rev share. The polynode Fee Escrow is unaffected by this change: it was never connected to the feeRateBps field. Our fee is pulled via a separate signed authorization (FeeAuth under our own EIP-712 domain) and only moves collateral. See the Fee Escrow Guide for the V2-native FeeEscrow contract address and the three-fee breakdown. EIP-712 Domain — The signing domain version changed from "1" to "2". The SDK handles this based on your exchange version config. No More Settlement Routers — V1 used FeeModule router contracts between operators and the exchange. V2 eliminates routers. Operators call the exchange contracts directly. exchange_version Field — V2 trades include "exchange_version": "v2" on each trade object in settlement and trade events. V1 trades omit this field. Use it to distinguish which exchange system processed a settlement.

What Didn’t Change

  • ConditionalTokens contract — same address, same position system, same token IDs
  • Market data — same condition IDs, same Gamma metadata, same enrichment pipeline
  • Authentication — same API key derivation, same HMAC headers
  • WebSocket subscriptions — same event types, same format
  • Position splits/merges/conversions — same ConditionalTokens events
  • Oracle system — unchanged UMA adapters and resolution flow
  • Orderbook streaming — same WebSocket protocol and message format

Impact on polynode Users

Settlement Stream

No changes required. V2 settlements produce the same event types you already receive:
  • settlement — early detection (3-5 seconds pre-confirmation)
  • status_update — block confirmation with latency measurement
  • trade — confirmed trade from OrderFilled event
  • deposit — now includes PolyUSD wrapping/unwrapping alongside USDC.e
polynode detects V2 transactions automatically alongside V1. Both versions are supported simultaneously.

Trading SDK

The polynode SDK supports V2 with a single config change:
use polynode::trading::{PolyNodeTrader, TraderConfig, OrderParams, OrderSide, ExchangeVersion};

let mut config = TraderConfig {
    polynode_key: "pn_live_...".into(),
    exchange_version: ExchangeVersion::V2,  // ← only change needed
    ..Default::default()
};

let mut trader = PolyNodeTrader::new(config)?;
let signer = PrivateKeySigner::from_hex("0x...")?;
let status = trader.ensure_ready(Box::new(signer), None).await?;

// Order placement is identical to V1
let result = trader.order(OrderParams {
    token_id: "10293...".into(),
    side: OrderSide::Buy,
    price: 0.05,
    size: 10.0,
    ..Default::default()
}).await?;
The SDK handles all V2 differences internally: order struct, EIP-712 signing, CLOB endpoint routing, and PolyUSD collateral. You don’t need to change how you construct orders or manage positions.

Builder Attribution in V2

V2 moves builder attribution from HTTP headers into the signed order struct itself as a bytes32 field. To attribute trades to your own builder profile, mint a builder code in Polymarket’s settings and pass it to the SDK on each order. Step 1: Mint your V2 builder code. Visit polymarket.com/settings?tab=builder with your connected wallet, create a builder profile, and copy the generated bytes32 code. Step 2: Pass it on each order. All three polynode SDKs accept a builder field on the order params. If you don’t pass one, orders sign with 0x0000...0000 (no attribution).
let result = trader.order(OrderParams {
    token_id: "10293...".into(),
    side: OrderSide::Buy,
    price: 0.05,
    size: 10.0,
    builder: Some("0x0000000000000000000000000000000000000000000000000000000000000001".into()),
    ..Default::default()
}).await?;
Your existing V1 builder_credentials (HMAC key/secret/passphrase) are unrelated to the V2 builder bytes32. The HMAC credentials are still used to query builder trade history at /builder/trades. The V2 bytes32 is what attributes newly-placed orders on-chain.

Pre-Migration V1 Orders

Open V1 orders resting at cutover get cleared from V1 orderbooks. Polymarket exposes GET /data/pre-migration-orders on the V2 CLOB for fetching your pre-cutover V1 order history. Cancel V1 resting orders before April 28 if you need the capital free for V2 trading.

Common V2 Errors

If something goes wrong during migration, these are the ones you’ll hit. order_version_mismatch Your SDK sent a V1-formatted order but the V2 CLOB expected V2. Flip exchange_version: V2 in your config. not enough balance / allowance Most common causes, in order:
  1. Your wallet has USDC.e but not PolyUSD. V2 uses PolyUSD as the collateral token. Call trader.wrap_to_polyusd(amount_raw) (Python/Rust) or trader.wrapToPolyUsd(amount_raw) (TS) to convert. Amounts are raw 6-decimal integers.
  2. Approvals not yet visible to the CLOB. The V2 CLOB caches per-API-key balance + allowance state. ensureReady / ensure_ready refreshes this cache after setting approvals; if you changed approvals outside that flow, call trader.refreshBalanceAllowance() / trader.refresh_balance_allowance() manually.
  3. Order notional + fee exceeds balance. V2 markets charge a fee (~2–5% of notional depending on base_fee). For a BUY, balance >= makerAmount + platformFee must hold or the CLOB rejects.
error parsing fee rate bps () to int64 Your SDK is too old and isn’t sending the V2 payload correctly. Upgrade to the V2-ready version listed in the checklist above. Rust-specific: not enough balance / allowance even though you have PolyUSD If you call link_wallet(signer, None) with an EOA wallet, the SDK silently uses the config’s default signature type (POLY_GNOSIS_SAFE) and derives a Safe address as the maker. That Safe has zero balance, so the CLOB rejects the order. For EOA wallets, pass explicit opts:
use polynode::trading::{LinkOpts, SignatureType};

trader.link_wallet(Box::new(signer), Some(LinkOpts {
    signature_type: Some(SignatureType::Eoa),
})).await?;

Timeline

  • Now: V2 test environment live at clob-v2.polymarket.com. V2 contracts live on Polygon mainnet processing real trades. polynode settlement stream tags V2 trades with exchange_version: "v2".
  • Before April 28: Flip your SDK config to exchange_version: V2 (or keep V1 until cutover). Mint a builder code if you want attribution. Cancel V1 resting orders you don’t want cleared.
  • April 28, ~11am UTC: Polymarket cutover. V2 becomes the primary exchange. V1 orderbooks cleared.
  • After cutover: Any integration still on V1 will fail with order_version_mismatch. Flip your config.
polynode supports both V1 and V2 simultaneously throughout the transition — no downtime, no disruption.