Summary
py_clob_client_v2 exposes SignatureTypeV2.POLY_1271 = 3 and a complete order-builder path for it
(ExchangeOrderBuilderV2.build_signed_order with version=2), but in practice cannot place
orders for any account that uses sigtype=3 because of a fundamental signing-architecture mismatch
with Polymarket's Privy-managed Magic Wallets.
This affects ALL new accounts created via "Connect Wallet" or "Email Login" since the Pectra/EIP-7702
rollout — these all default to signatureType=3. End result: programmatic trading via this SDK is
not possible for new users.
Environment
py_clob_client_v2 1.0.0
- Python 3.11
- Polygon mainnet (chainId=137)
- Magic Wallet account (funder), underlying signer EOA
- Private key extracted via Polymarket UI's "Export Private Key" feature
Reproducer
from py_clob_client_v2.client import ClobClient
from py_clob_client_v2.clob_types import OrderArgsV2, PartialCreateOrderOptions, OrderType, ApiCreds, BalanceAllowanceParams
c = ClobClient(
host="https://clob.polymarket.com", chain_id=137,
key=MAGIC_WALLET_PK, # extracted via UI's "Export Private Key"
signature_type=3, # POLY_1271
funder=MAGIC_WALLET_ADDRESS, # the 0x... wallet shown in UI
creds=derived_creds,
)
# get_balance_allowance works correctly — returns the deposited cash:
bal = c.get_balance_allowance(BalanceAllowanceParams(asset_type='COLLATERAL', signature_type=3))
# => $10.00 visible
# But order placement always fails:
args = OrderArgsV2(token_id=ANY_5M_CRYPTO_TOKEN, price=0.50, size=5.0, side='BUY')
signed = c.create_order(args, PartialCreateOrderOptions(neg_risk=False))
res = c.post_order(signed, OrderType.FOK)
# => PolyApiException 400: {"error":"the order signer address has to be the address of the API KEY"}
After patching ExchangeOrderBuilderV2 to set signer = funder (matching what the UI sends) AND to
construct the multi-part signature wrapper exactly matching the format reverse-engineered from a
captured UI cURL — ecdsa(65) || domainSeparator(32) || structHash(32) || typeString(186) || lengthSuffix(2)
— orders are still rejected with 400 {"error":"invalid signature"}.
Root cause (verified empirically)
The signature exposed via the UI's "Export Private Key" is only the USER'S SHARE of a Privy
threshold signature scheme (TSS). The actual ECDSA signature that recovers correctly against the
Magic Wallet's owner is produced by Privy's server combining the user's share with a server-held
share.
Verification: ECDSA-recover the captured signature from a working UI order (status 200) over
its EIP-712 hash. The recovered address is a Privy-controlled identity, NOT the user's exported
PK address. Specifically:
Captured UI sig over canonical EIP-712 hash:
recovers to: 0xaac44c2EB50D1ad03edAa3A727F4F5AE8000ad9f (Privy server identity)
User's exported PK derives to:
address: 0xDfEe1ef0... (the address you'd expect to be the wallet's owner)
This proves Privy uses TSS — the exposed PK is just one share, and signatures must be combined
server-side.
Captured evidence (UI signature decomposition)
317-byte multi-part signature from working UI order (POST /order returned 200):
bytes 0-65 = ECDSA r,s,v (recovers to Privy server identity, NOT user's PK)
bytes 65-97 = domain separator (matches keccak of Polymarket V2 Exchange domain)
bytes 97-129 = struct hash (matches keccak of order type+fields)
bytes 129-315 = EIP-712 type string ASCII ("Order(uint256 salt,address maker,address signer,...)")
bytes 315-317 = uint16 BE length suffix (0x00ba = 186)
Requested fix (any of these)
- Add Privy SDK integration to py_clob_client_v2 — expose a path for
client.set_privy_session(privy_app_id, user_session_token) that delegates the ECDSA signing
step to Privy's server, OR
- Provide a documented workaround for sigtype=3 accounts — e.g., ability to fall back to
sigtype=2 (POLY_GNOSIS_SAFE) or sigtype=0 for the same wallet, OR
- Document the limitation explicitly in the README so developers don't waste time discovering
this independently.
Impact
- All new Polymarket users (post-Pectra) cannot use py_clob_client_v2 for programmatic trading
- Existing users with sigtype=0/1/2 accounts are unaffected
- Without a fix, third-party trading bots and tools cannot onboard new users
- Workarounds (UI automation via headless browser) add latency that breaks low-latency strategies
like late-window FOK trades
Notes
get_balance_allowance correctly returns the deposited pUSD for sigtype=3 wallets — only order
placement is affected
- Tested with both newly-derived and existing API keys (multiple
nonce values via create_api_key)
- Tested with both
signer=EOA (default SDK) and signer=funder (patched) — both rejected
- Tested with FOK at various prices (mid-book, top-of-book, far-from-book) — all rejected with
same "invalid signature" error
Summary
py_clob_client_v2exposesSignatureTypeV2.POLY_1271 = 3and a complete order-builder path for it(
ExchangeOrderBuilderV2.build_signed_orderwithversion=2), but in practice cannot placeorders for any account that uses sigtype=3 because of a fundamental signing-architecture mismatch
with Polymarket's Privy-managed Magic Wallets.
This affects ALL new accounts created via "Connect Wallet" or "Email Login" since the Pectra/EIP-7702
rollout — these all default to
signatureType=3. End result: programmatic trading via this SDK isnot possible for new users.
Environment
py_clob_client_v21.0.0Reproducer
After patching
ExchangeOrderBuilderV2to setsigner = funder(matching what the UI sends) AND toconstruct the multi-part signature wrapper exactly matching the format reverse-engineered from a
captured UI cURL —
ecdsa(65) || domainSeparator(32) || structHash(32) || typeString(186) || lengthSuffix(2)— orders are still rejected with
400 {"error":"invalid signature"}.Root cause (verified empirically)
The signature exposed via the UI's "Export Private Key" is only the USER'S SHARE of a Privy
threshold signature scheme (TSS). The actual ECDSA signature that recovers correctly against the
Magic Wallet's owner is produced by Privy's server combining the user's share with a server-held
share.
Verification: ECDSA-recover the captured signature from a working UI order (status 200) over
its EIP-712 hash. The recovered address is a Privy-controlled identity, NOT the user's exported
PK address. Specifically:
This proves Privy uses TSS — the exposed PK is just one share, and signatures must be combined
server-side.
Captured evidence (UI signature decomposition)
Requested fix (any of these)
client.set_privy_session(privy_app_id, user_session_token)that delegates the ECDSA signingstep to Privy's server, OR
sigtype=2 (POLY_GNOSIS_SAFE) or sigtype=0 for the same wallet, OR
this independently.
Impact
like late-window FOK trades
Notes
get_balance_allowancecorrectly returns the deposited pUSD for sigtype=3 wallets — only orderplacement is affected
noncevalues viacreate_api_key)signer=EOA(default SDK) andsigner=funder(patched) — both rejectedsame "invalid signature" error