Skip to content

[BUG] Cannot place sigtype=3 (POLY_1271 / Magic Wallet) orders — Privy TSS architecture mismatch #57

@vicjayjay

Description

@vicjayjay

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)

  1. 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
  2. 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
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions