DEV Community

Cover image for Polymarket V2 Migration: What I Actually Hit Migrating a Live Bot Today
manja316
manja316

Posted on

Polymarket V2 Migration: What I Actually Hit Migrating a Live Bot Today

If you run a trading bot against Polymarket's CLOB, today's V2 cutover broke it. I migrated my live crash-recovery bot (308 closed trades, 80.2% WR) in 4 hours during the cutover window. This post is exactly what I observed — not what the announcement said, but what actually happened on the wire.

There's a companion repo with the working code, line-by-line diffs, and a smoke test you can run in 30 seconds: github.com/LuciferForge/polymarket-v2-migration

Reading this without checking the cookbook is fine. Running a live bot through the cutover without the cookbook is how you lose hours.


The headline you missed

Polymarket announced a 1-hour V2 cutover. The actual disruption was ~6 hours of mixed states, not 1. If your bot was running during the window and you were retrying every 30 seconds, you logged a few hundred 503s for nothing. Here's what those 6 hours looked like, hour by hour:

Time (UTC) State What worked What didn't
11:00 Cutover begins Reads (orderbook, balance, positions) New orders → HTTP 503 cancel-only
11:00–13:00 Pure cancel-only Cancels of pre-existing V1 orders Anything that posts a new order
13:00–14:30 V2 contracts active, V1 SDK confused Migrated wallets via UI V1 SDK paths still 503-ing
14:30–16:00 Mixed state V2 SDK + migrated wallets V1 SDK; un-migrated V2 wallets (allowance errors)
16:00 → Stable V2 Everything Anyone still on V1 imports

The bot saw HTTP 503: Trading is currently cancel-only for the entire window. Nothing about that error tells you it'll last 6 hours. Polling the are-orders-scoring flag wouldn't have helped either — the right behavior is "pause, walk away, resume when V2 is up".


What actually changed (it's not a version bump)

The V2 migration is a new SDK package, not a pip install --upgrade. Polymarket shipped V2 as a parallel package so bots could be migrated incrementally rather than via big-bang upgrade.

pip install py-clob-client-v2   # V2 SDK
# Don't uninstall py-clob-client yet — keep it for rollback
Enter fullscreen mode Exit fullscreen mode

Three changes in your code. That's it:

- from py_clob_client.client import ClobClient
- from py_clob_client.clob_types import (
+ from py_clob_client_v2.client import ClobClient
+ from py_clob_client_v2.clob_types import (
      ApiCreds, BalanceAllowanceParams, AssetType, MarketOrderArgs,
  )
- from py_clob_client.constants import POLYGON
+ from py_clob_client_v2.constants import POLYGON
  ...
- return client.post_order(signed_order, orderType="FOK")
+ return client.post_order(signed_order, order_type="FOK")
Enter fullscreen mode Exit fullscreen mode

Two import rewrites and one kwarg rename (orderTypeorder_type, snake_case in V2). If you forget the kwarg rename, you get a loud TypeError: post_order() got an unexpected keyword argument 'orderType'. Loud errors are mercy.


Contract addresses (changed) and the only way to migrate your wallet

V2 ships with new contract addresses. The SDK has them baked in, but your wallet's allowances are not auto-migrated.

Contract V1 V2
CTF Exchange 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E 0xE111180000d2663C0091e4f400237545B87B996B
NegRisk Exchange 0xC5d563A36AE78145C45a50134d48A1215220f80a 0xe2222d279d744050d28e00520010520000310F59
Collateral USDC.e pUSD (1:1 backed by USDC.e)
CTF (token framework) unchanged unchanged

Important: the only way to trigger your wallet's V2 migration is to manually trade ≥5 shares in the Polymarket UI. I tried each of the SDK methods first because the docs hinted they might work. They don't:

  • update_balance_allowance() from the SDK — only refreshes the SDK's view of existing allowances.
  • ❌ Manually approving the V2 contracts on Polygonscan — Polymarket also requires the pUSD wrapper signature, which only happens in the UI flow.
  • ❌ Logging into polymarket.com without trading — login alone does not trigger the migration prompt.
  • ❌ Trading <5 shares — Polymarket gates the migration prompt behind a minimum.

The only thing that works: go to polymarket.com, click Trade on any market, set size to ≥5 shares, sign all 3 wallet signatures (USDC.e → pUSD wrap, V2 CTF allowance, V2 NegRisk allowance). Costs about 0.1 MATIC in gas. Takes 60 seconds.

After that, your wallet has V2 allowances and the V2 SDK works. Until then, you'll get HTTP 400: insufficient allowance for V2 CTF Exchange.

If you operate multiple proxy wallets, each one has to do its own UI flow. There is no batched migration.


What I actually did, in order

Time (UTC) Action Result
11:05 Detected first 503. Paused bot via MAX_ENTRY_PRICE = 0.0 config flag. Bot stopped firing buys; existing positions kept being monitored.
11:30 Read V2 announcement + SDK release notes. Confirmed import + kwarg changes.
12:00 pip install py-clob-client-v2. Did NOT uninstall V1. Both SDKs available; rollback path preserved.
12:15 `sed -i '' 's\ from py_clob_client.\
12:20 {% raw %}`sed -i '' 's\ orderType="FOK"\
12:30 Smoke test: {% raw %}python -c "from py_clob_client_v2.client import ClobClient; print('ok')" Pass.
12:45 Opened polymarket.com, traded 5 shares on a small market, signed migration approvals. ~$0.01 in MATIC gas. Wallet now holds pUSD; V2 allowances appeared in get_balance_allowance response.
13:00 Manually posted a 1-share order via my updated bot to verify V2 path. Order filled on-chain.
14:00 Resumed bot at half-size (POSITION_SIZE_USD = 5.0 instead of 10). First 3 orders went through cleanly.
17:00 Confirmed 6h of clean operation. Returned to normal POSITION_SIZE.

Total wall time: ~4 hours of focus, plus passive waiting for V2 to stabilize.


The 12 errors I hit (with fixes)

The cookbook's troubleshooting guide has all 12 with exact fixes. The most common ones:

  1. ModuleNotFoundError: No module named 'py_clob_client_v2'pip install py-clob-client-v2 into the right venv. If you use launchd or systemd, double-check the interpreter path.

  2. TypeError: post_order() got an unexpected keyword argument 'orderType' → grep for orderType= and replace with order_type=.

  3. HTTP 503: Trading is currently cancel-only → Polymarket-side maintenance. Pause your bot, wait. Don't retry-storm.

  4. HTTP 400: insufficient allowance for V2 CTF Exchange → wallet not migrated. Go to polymarket.com and trade 5 shares.

  5. Decimal precision mismatch on amount → V2 is stricter. Wrap your amount in round(amount_usd, 2).

  6. Order accepted but never settles on-chain → your wallet has no pUSD balance. The migration trade wraps USDC.e to pUSD; if you skipped that step, accepts will fail at settlement.

The full list with code is in docs/troubleshooting.md of the cookbook repo.


The slippage problem V2 doesn't fix

If your bot used a price-discount ladder (e.g., 5%, 15%, 25% below ref price) for TIMEOUT exits on thin markets, V2 doesn't fix that. V2 fixes nonce-related failed-fill bugs in the matching layer — it doesn't change order-book depth.

I ran a separate audit on my bot's lifetime P&L vs. on-chain fills using a tool I open-sourced today: pnl-truthteller. Result:

Lifetime theoretical P&L:  +$33.49
Lifetime actual P&L:       -$89.01
Total slippage cost:       -$122.50  (-365.8% of theoretical)
Enter fullscreen mode Exit fullscreen mode

That's 302 closed trades, 4-5 months of operation, and the difference between "I'm slightly profitable" and "I'm clearly not, and I should change the exit ladder." If you've never run that reconciliation against your bot's records, you don't know what you're really making.

pip install pnl-truthteller
pnl-truthteller --wallet 0xYourProxyAddress
Enter fullscreen mode Exit fullscreen mode

Read-only, wallet address only, no API key. Outputs a Markdown report with by-exit-reason breakdown, worst-10 trades, and dust shares stranded on-chain.


The deeper lesson: V2 wasn't really about V2

Every DeFi protocol upgrade exposes which bots are well-coupled to the protocol's own SDK abstractions and which ones have hardcoded addresses, hardcoded version assumptions, or naive retry loops.

What V2 actually tested:

  • Whether your bot detects cancel-only mode cleanly (most don't)
  • Whether you have a kill switch that pauses the bot on first sign of a system-wide error (most don't)
  • Whether your slippage cost is being measured against on-chain reality or against your own DB optimism (most aren't)

If any of those answers were "no", today is the day to fix them. I open-sourced three things that handle exactly these failure modes:

  • polymarket-v2-migration — the cookbook for this specific cutover. 12 errors with fixes, smoke test, hour-by-hour timeline.
  • pnl-truthteller — fill-level P&L audit. Find your hidden slippage. (pip install pnl-truthteller)
  • quant-rollout — staged-deployment toolkit with kill switch and veto window. Stops you from shipping bad params into production. (pip install quant-rollout)

All MIT, all zero-dep cores, all PyPI-published. If they save you a few hours over the next week, star them.


Resources

If your bot is still 503-ing right now, the cookbook tells you whether it's the SDK or your wallet allowances. If it's silent, run pnl-truthteller. The chain is the source of truth.


LuciferForge is a solo operator running a public-audited Polymarket trading bot. Also runs protodex.io (5,800+ MCP servers indexed) and the free Polymarket data API.

Top comments (0)