How to make Polymarket CLOB live orders actually execute from position_manager. After a multi-hour debugging marathon, the answer is the deposit-wallet flow with signature type Poly1271 (ERC-1271): authenticate with the EOA that the deposit-wallet contract’s owner() returns, let the SDK derive the L2 API key (do NOT supply captured creds), and set order.maker = order.signer = funder = deposit wallet. The CLOB then validates that the api-key EOA owns order.signer and accepts the order.
For Agents
Full repo writeup:
docs/EOA_TRADING_SOLUTION.mdin the position_manager repo. Reference impl: SDK exampleexamples/clob/orders/gtc_limit_buy_deposit_wallet.rs(rs-clob-client-v2). Working image:acef9d1(has configurablesignature_type). Funded deposit wallet0xAdAc17C0A3725D2CA2Df66E6DcBBAC82ebA0aEb2, owner EOA0x932E3f…(MetaMask, key held). This note is the solution; pmv2-signer-prearm-proxy-owner-gotcha-2026-06-25 is the pre-armowner()gate that made it pass.
The Problem — a chain of failures, each revealing the next
Live orders kept getting rejected by the CLOB. Each fix exposed the next error:
- 403 geoblock —
Trading restricted in your region. Fixed by region migration. - 400
maker address not allowed, please use the deposit wallet flow— hit for both the old Magic proxy and for a raw EOA maker. - 400
order signer address has to be the address of the API KEY— signer/api-key mismatch. - 401
Unauthorized / Invalid api key— when supplying captured L2 creds for the wrong/stale account.
Root Cause — Polymarket migrated to deposit wallets (Poly1271 / ERC-1271)
The Rust SDK / official CLI (rs-clob-client-v2) still derive the old CREATE2 addresses:
derive_proxy_wallet(eoa, 137)— the legacy “proxy” wallet.derive_safe_wallet(eoa, 137)— the Gnosis “safe” wallet.
Both of those are EMPTY. The user’s actual funds live in a deposit-wallet contract that the Polymarket web UI creates — which is NOT the SDK-derived proxy/safe. And a raw EOA maker is rejected because an EOA is not an onboarded Polymarket account. So every classic maker the SDK can produce is wrong, which is exactly what error #2 means.
The three rejections are all the same root cause
“maker not allowed”, “signer must be the api key”, and “invalid api key” are all symptoms of trying to trade a wallet the CLOB doesn’t recognize as yours, or authenticating as the wrong account. The fix is to point at the real funded deposit wallet and prove ownership of it via its
owner()EOA.
Key Insight — how to identify the right wallet AND the right key
A Polymarket deposit wallet is a CONTRACT (eth_getCode != 0x) that exposes an owner() method (selector 0x8da5cb5b). To trade it you must sign with the EOA that owner() returns.
| Deposit wallet | owner() returns | Key held? | Tradeable? |
|---|---|---|---|
0xAdAc17C0A3725D2CA2Df66E6DcBBAC82ebA0aEb2 (funded) | 0x932E3f… (user MetaMask EOA) | yes | YES |
0xdb0d23c2…d9efe (old Magic, $491.85) | 0xfa77… (Magic custody) | no | NO — withdraw via web UI |
So the discriminator is: does owner(deposit_wallet) equal an EOA whose private key we hold? If yes → tradeable via the flow below. If no (Magic custody) → the funds can only be moved by withdrawing through the Polymarket web UI.
The Solution — the official deposit-wallet flow
From SDK example examples/clob/orders/gtc_limit_buy_deposit_wallet.rs:
let client = ClobClient::authentication_builder(&signer) // signer = OWNER EOA key
.funder(deposit_wallet) // the funded deposit wallet
.signature_type(SignatureType::Poly1271) // ERC-1271
.authenticate() // DERIVES the L2 api key from the owner EOA
.await?;Then on the order:
order.maker = deposit_wallet(the funder)order.signer = funder(same deposit wallet)- The CLOB validates that the api-key EOA is the
owner()oforder.signer. Sinceowner(deposit_wallet) == authenticated EOA, owner == signer → accepted.
DO NOT supply captured L2 creds
The earlier 401 was caused by providing
POLYMARKET_L2_API_KEY/SECRET/PASSPHRASE. The deposit-wallet flow derives the L2 key from the owner EOA during.authenticate(). Supplying stale/wrong creds overrides the derivation and breaks auth. Derive, don’t supply.
PM Config That Works
On image acef9d1 (which exposes a configurable signature_type):
| Setting | Value |
|---|---|
DB pmv2_autotrade_config.maker_proxy | the deposit wallet 0xAdAc17C0A3725D2CA2Df66E6DcBBAC82ebA0aEb2 |
env PM_SIGNATURE_TYPE | poly1271 |
signer key POLYGON_V2_PRIVATE_KEY | the owner EOA (0x932E3f) |
POLYMARKET_L2_API_KEY / _SECRET / _PASSPHRASE | REMOVE (derive, don’t supply) |
The PM signer_arm gate (see pmv2-signer-prearm-proxy-owner-gotcha-2026-06-25) checks proxy_matches: owner(maker_proxy) == signer_eoa. Boot log on success:
signer armed eoa=0x932e3f proxy=0xadac... onchain_owner=Some(0x932e3f)
That line means the gate passed and live execution is armed.
Outcome
PM armed clean in dry, then on live the order was ACCEPTED by the CLOB (the maker wall cleared). End-to-end live execution from PM now works through the deposit-wallet / Poly1271 path.
Reference Constants (for future work)
SDK constants / methods used
derive_proxy_wallet(eoa, 137)— legacy proxy (EMPTY for this account)derive_safe_wallet(eoa, 137)— Gnosis safe (EMPTY)owner()selector —0x8da5cb5b- pUSD collateral —
0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB- Working public Polygon RPC for
balanceOf/getCode/owner()—https://polygon-bor-rpc.publicnode.com(note:polygon-rpc.comwas rate-limited)
Related
- pmv2-signer-prearm-proxy-owner-gotcha-2026-06-25 — the pre-arm
owner()gate that makes the Poly1271 config pass; first identified the deposit-wallet type - position_manager — PM service overview / execution gate
- polymarket-fetch — upstream Polymarket integration / CLOB crate
- pmv2-polymarket-clob-crate-migration — the polymarket-clob crate PM signs orders with