This is the smart-contract-wallet companion to the rest of the V2 series: the Migration playbook, the errors troubleshooting page, the pUSD explainer, and the TS ethers→viem port.

Last verified: September 2026 against docs.polymarket.com/trading/wallets-auth, the V2 SDK source, and open GitHub issues.


The four signature types in V2

The signatureType parameter tells the CLOB how to verify your order signature. V1 had three. V2 added a fourth. Here’s the full set:

ValueNameWhat it meansWallet type
0EOAPlain ECDSA — the CLOB verifies your signature recovers to your address.Standard EOA (MetaMask, hardware wallet, raw private key).
1POLY_PROXYThe CLOB validates against a Polymarket-managed proxy contract.Magic Link / Google / email signup users.
2GNOSIS_SAFEThe CLOB validates against an existing pre-V2 Gnosis Safe.Browser-wallet users who connected before April 28, 2026.
3POLY_1271The CLOB delegates verification to the funder contract via IERC1271.isValidSignature(hash, signature).Any smart-contract wallet that implements EIP-1271 — Polymarket’s deposit wallet, Safe (new), Coinbase Agentic Wallet, custom SCWs.

The first three existed in V1. The fourth — POLY_1271 — is the architectural change. Where types 1 and 2 hardcoded specific wallet types Polymarket had integrated, type 3 is generic: the CLOB doesn’t care what kind of contract you’re using, only that the contract implements the EIP-1271 standard interface. That’s a meaningfully different stance.

Important: pre-V2 wallets keep their signature type. If you had a Safe pre-cutover with signature_type=2, that’s still the right value. Don’t “upgrade” to 3 — your Safe wasn’t deployed as a Polymarket deposit wallet, and switching breaks your auth.


What EIP-1271 actually unlocks

EIP-1271 defines a standard interface that lets contracts sign things in the same conceptual way that EOAs do. The spec is one function:

interface IERC1271 {
    function isValidSignature(bytes32 hash, bytes memory signature)
        external view returns (bytes4 magicValue);
}

When a contract wants to sign data — for example, a Safe with three owners agreeing to a trade, or a deposit wallet whose owner is delegated to a session key — there’s no private key on the contract itself. Instead, the contract exposes isValidSignature(hash, signature) and decides for itself whether the supplied signature is “valid” by whatever rules it wants (m-of-n multi-sig, session-key whitelist, time-locked permissions, social recovery, etc.). It returns a magic 4-byte value (0x1626ba7e) when valid, anything else when not.

For Polymarket, this matters because the CLOB no longer needs to know your wallet’s internals. The verification path is:

  1. Order comes in with signatureType = 3 and signer = <smart-contract wallet address>.
  2. The CLOB extracts the order hash.
  3. The CLOB calls IERC1271(signer).isValidSignature(orderHash, wrappedSignature) on-chain.
  4. If the contract returns the magic value, the order is valid. Otherwise rejected.

This is what makes the path generic. The CLOB can now accept orders from:

  • A Safe (multi-sig — three owners signing).
  • A deposit wallet (Polymarket-deployed proxy delegating to an EOA owner or session signer).
  • A Coinbase Agentic Wallet (a Coinbase-deployed SCW with isValidSignature wired to the user’s hardware key or session policy).
  • A custom agent wallet built on top of ERC-4337 bundlers, account abstraction, or session keys.

If your wallet implements EIP-1271 correctly, it can post orders to Polymarket. That’s the architectural unlock.

ERC-7739: the safety wrapper

There’s a wrinkle worth knowing about. Raw EIP-1271 has a well-known replay vulnerability: a signature that’s valid for one purpose can sometimes be replayed for another. ERC-7739 is a complementary standard that wraps EIP-1271 signatures in a nested TypedDataSign envelope bound to the verifying contract’s own domain — so a signature valid for “this Safe’s batch on this chain” can’t be replayed elsewhere.

Polymarket uses ERC-7739 wrapping for POLY_1271 CLOB signatures (the wrapped signature is longer than a normal 65-byte ECDSA signature). The V2 SDKs build this wrapper for you when signatureType = SignatureTypeV2.POLY_1271 and a funderAddress is set. If you sign at the raw API level, you need to build it yourself — see the Wallets and Authentication docs for the nested-domain spec.


Polymarket’s deposit wallet: the reference implementation

The deposit wallet is Polymarket’s own EIP-1271 wallet. It’s a per-user ERC-1967 proxy, deployed by a factory contract, owned by an EOA you control. It’s purpose-built for the POLY_1271 flow, and since May 4, 2026 it is the default for every new Polymarket account — web signups included. Magic-proxy (type 1) and Safe (type 2) are now explicitly legacy paths that only apply to accounts created before the cutover.

Architecture

  • Type: per-user ERC-1967 proxy (not a Safe, not Coinbase’s Smart Wallet — a custom contract).

  • Factory: 0x00000000000Fb5C9ADea0298D729A0CB3823Cc07 on Polygon mainnet.

  • Deterministic address: derived via CREATE2 from the factory + a salt computed from the owner address. The deposit wallet address is computable before deployment:

    walletId      = bytes32(owner)                          // owner left-padded to 32 bytes
    args          = abi.encode(factory, walletId)
    salt          = keccak256(args)
    bytecodeHash  = SoladyLibClone.initCodeHashERC1967(implementation, args)
    depositWallet = CREATE2(factory, salt, bytecodeHash)
    

    Both SDKs expose helpers: deriveDepositWalletAddress() (TypeScript), get_expected_deposit_wallet() (Python).

  • Holds: pUSD (your trading collateral) and conditional tokens (your positions).

  • Signs: two different payload kinds — WALLET batches for on-chain actions, and CLOB orders for trading. Those signatures aren’t interchangeable.

Deployment

Deployment is a relayer operation — Polymarket’s Relayer pays the gas, no POL needed on the owner EOA. The call is authenticated: if you’re deploying and operating a deposit wallet for yourself, use a Relayer API key for your own wallet rather than Builder API keys — it also lifts the 100/day Unverified relay cap without a tier upgrade. Submit a WALLET-CREATE request to the relayer (no user signature required on this body), and the factory deploys your proxy at the deterministic address:

# Python — via py-builder-relayer-client
from py_builder_relayer_client.client import RelayClient

relayer = RelayClient(
    os.environ["RELAYER_URL"],
    137,
    os.environ["PRIVATE_KEY"],      # owner EOA
    builder_config,
)

deposit_wallet = relayer.get_expected_deposit_wallet()
response       = relayer.deploy_deposit_wallet()        # → WALLET-CREATE
confirmed      = response.wait()
print("Deployed at:", deposit_wallet)
// TypeScript — via @polymarket/builder-relayer-client
import { RelayClient } from "@polymarket/builder-relayer-client";

const relayer = new RelayClient(relayerUrl, 137, walletClient, builderConfig);
const depositWalletAddress = await relayer.deriveDepositWalletAddress();
const response             = await relayer.deployDepositWallet();
await response.wait();

Two signature flows

After deployment, the owner signs two distinct kinds of payloads against the deposit wallet:

1. WALLET batches — on-chain wallet actions (approvals, transfers, splits/merges of CTF positions). A normal 65-byte EIP-712 signature over the DepositWallet/Batch type, against the wallet’s own EIP-712 domain:

const domain = {
  name: "DepositWallet",
  version: "1",
  chainId,
  verifyingContract: depositWalletAddress,
};

const types = {
  Call:  [{name:"target", type:"address"}, {name:"value", type:"uint256"}, {name:"data", type:"bytes"}],
  Batch: [{name:"wallet", type:"address"}, {name:"nonce", type:"uint256"},
          {name:"deadline", type:"uint256"}, {name:"calls", type:"Call[]"}],
};

Submitted to the relayer as type: "WALLET", executed by the deposit wallet contract.

2. CLOB orders — trading. An ERC-7739-wrapped POLY_1271 signature over the V2 Order struct. The order’s maker and signer must both be the deposit wallet address. Submitted to the CLOB as signatureType = 3. The SDK builds the wrapped signature; the CLOB validates it via isValidSignature on the deposit wallet.

This split is important to internalize: the same EOA owner uses the same key to sign both, but the payloads are different shapes and they target different verifiers. Conflating them is the most common source of bugs (we’ve seen it in user reports — see the py-clob-client-v2 Errors page).

Funding and approvals (the gotcha)

The most common new-user mistake: funding the owner EOA instead of the deposit wallet. pUSD held on the EOA is irrelevant to the CLOB — orders are made by the deposit wallet, and the CLOB checks pUSD balance at the funder (deposit wallet) address. Same for allowances — approve() calls from the EOA do not approve the V2 Exchange contracts to move pUSD from the deposit wallet. Approvals must be submitted via a WALLET batch from the deposit wallet.

After funding and approvals, sync the CLOB’s cached view with signature_type = 3:

client.update_balance_allowance(
    BalanceAllowanceParams(
        asset_type=AssetType.COLLATERAL,
        signature_type=SignatureTypeV2.POLY_1271,
    )
)

Skip this step and the CLOB will report your balance as zero even though pUSD sits in the deposit wallet on-chain.


⚠ An honest note: the SDK currently breaks the documented flow

If you’re a new API user trying to follow Polymarket’s documented path — deposit wallet, signature_type=3 — there is an open SDK bug as of May 25, 2026 that blocks order placement. Both py-clob-client-v2 #70 and clob-client-v2 #65 describe the same root cause: the L1 authentication path (create_or_derive_api_key()) signs the auth message using the EOA’s address rather than wrapping it through the deposit wallet’s EIP-1271 path. So the API key gets registered against the EOA. When you then place an order with signer = deposit_wallet, the CLOB rejects it: {"error": "the order signer address has to be the address of the API KEY"}.

The deposit wallet infrastructure itself (deployment, funding, approvals, batches) works correctly — only the API-key creation path is broken. The fix is small (~50 lines per SDK to wire the existing ERC-7739 wrapper into the L1 auth path) and has been proposed in the issue, but as of writing no PR has merged.

Workaround for new users right now: sign up via email/Magic at polymarket.com, export the wallet’s private key, and use signature_type=1 (POLY_PROXY) with the proxy address as funder. This works because Polymarket’s own onboarding registers the API key against the proxy correctly, bypassing the SDK’s broken L1 path. Note the shrinking window: Polymarket’s docs now deploy deposit wallets for all accounts created on or after May 4, 2026, so a fresh email signup no longer yields a type-1 proxy. The workaround only applies if you have (or can obtain) an account created before the cutover. Verify against a live signup before building on it.

We’re tracking the issue and will update once it’s fixed. If your bot is operating on a pre-V2 Safe or Magic account, none of this affects you — sig types 1 and 2 are working normally.

Update (June 2026): the same defect has carried into Polymarket’s new unified beta SDKs — py-sdk (#55, sign_api_key_auth) and ts-sdk (#73, beginAuthentication) both sign ClobAuth with the bare EOA address — so migrating to the newer SDK does not fix it on its own. The Rust SDK remains the only client that wraps the L1 auth correctly today. Full roundup with workarounds: Polymarket Beta SDK Bugs (May 2026).

Update (September 2026): still open. py-clob-client-v2 #70 and clob-client-v2 #65 remain unresolved with no merged fix; the beta py-sdk and ts-sdk carry the same defect. The Rust SDK remains the only client that wraps L1 auth correctly. If your architecture depends on POLY_1271 order flow from a non-pre-V2 wallet, plan on implementing the ERC-7739-wrapped L1 auth yourself — the fix sketch in #70 is the reference.


Beyond deposit wallets: the broader SCW ecosystem

The deposit wallet is the path Polymarket steers you to, but POLY_1271 is a generic mechanism. Any EIP-1271-compliant smart-contract wallet can — in principle — be wired in. Here’s how the other major SCWs fit.

Gnosis Safe

Pre-V2 Safes deployed through Polymarket’s onboarding flow use signature_type=2 (POLY_GNOSIS_SAFE) — that’s a dedicated path the CLOB has special-cased for years. New Safes deployed today, outside Polymarket’s flow, would use signature_type=3 (POLY_1271) — because Safe implements EIP-1271 natively. The CLOB would call Safe.isValidSignature(orderHash, packedSig) and the Safe’s threshold logic would determine validity.

In practice, this requires the same API-key-binding fix as deposit wallets (you’d want your CLOB API key registered against the Safe address, not your EOA), so for now new Safes have the same #70 limitation. Once the SDK fix lands, this becomes the canonical path for multi-sig agent operators.

Coinbase Agentic Wallets

Coinbase’s Agentic Wallets (and the broader Smart Wallet line) are EIP-1271-compliant smart-contract wallets designed for agent use cases — session keys, scoped permissions, passkey signing. They can sign CLOB orders via POLY_1271 the same way: pass the agentic wallet address as funder, set signatureType=3, and the CLOB validates via isValidSignature.

For agent operators this is actually the more interesting path than deposit wallets: agentic wallets give you fine-grained permission scoping (e.g., “this session key can spend up to $100/day on Polymarket markets resolving by date X”), which is meaningful security infrastructure for autonomous agents. See Best Agent Wallet for Prediction Markets for the comparison.

Custom / ERC-4337 agent wallets

If you’ve built a custom SCW for your agent — your own session-key model, intent-based execution, custom signature recovery — POLY_1271 is the integration target. As long as your wallet implements isValidSignature(hash, signature) correctly and you wrap the order signature in the ERC-7739 envelope, the CLOB will validate against it.

The catch is the same as Safe and agentic wallets: the API-key-registration bug currently blocks the L1 flow. Until that’s fixed, you’re stuck either using a pre-V2 wallet type or implementing the ERC-7739-wrapped L1 auth yourself.


Why this matters for agents specifically

For human users, the EOA flow was usually fine — one user, one key, one wallet. For agents, smart-contract wallets are a different category of value:

  • Session keys. An agent can be granted a session signing key with narrow scope (specific markets, dollar caps, time windows). The session key signs orders; the smart wallet’s isValidSignature enforces the scope. If the session key leaks, blast radius is bounded. EOAs cannot do this — a leaked private key gives full control.
  • Multi-sig and approval workflows. A bot operator running money for clients might require m-of-n approval for large positions. Safe + POLY_1271 makes this trivial; an EOA cannot.
  • Delegated execution. An agent provider (think a copy-trading service) can sign orders on behalf of users without ever touching user EOAs — the user’s smart wallet authorizes the agent address; the agent signs and posts; the smart wallet validates.
  • Account abstraction. Bundlers, paymasters, and intent-based execution all assume smart-contract wallets. POLY_1271 is the bridge between that world and Polymarket.

The architectural posture matters: pre-V2, Polymarket was an “EOAs and a few special proxies” platform. Post-V2, it’s “any smart wallet that implements ERC-1271.” That’s the kind of plumbing that agent infrastructure builds on top of.


Session keys in practice

Session keys are now a first-class, documented feature — docs.polymarket.com/trading/session-keys. A session key is a separate signer that a deposit wallet owner authorizes to trade for the deposit wallet: the owner key stays cold, the session key does the day-to-day signing. The constraints, per the docs: authorizations carry a fixed 180-day expiration (revoke earlier if you want a shorter window), scope can be limited to CLOB, Combos RFQs, or all venues, the feature works only with deposit wallets, and a session key can trade but cannot withdraw funds.

Authorization goes through the SecureClient. TypeScript:

const sessionKeyPrivateKey = generatePrivateKey();
const { address: sessionKeyAddress } = privateKeyToAccount(sessionKeyPrivateKey);

const secureClient = await createSecureClient({
  signer: privateKey(process.env.POLYMARKET_PRIVATE_KEY),   // owner EOA
  wallet: process.env.POLYMARKET_DEPOSIT_WALLET,
  apiKey: builderApiKey({...}),
});

await secureClient.authorizeSessionKey({ address: sessionKeyAddress });

Python:

from eth_account import Account
session_key = Account.create()

secure_client = await AsyncSecureClient.create(
    private_key=os.environ["POLYMARKET_PRIVATE_KEY"],   # owner EOA
    wallet=os.environ["POLYMARKET_DEPOSIT_WALLET"],
    api_key=BuilderApiKey(...),
)

await secure_client.authorize_session_key(address=session_key.address)

Management is symmetric: fetchSessionKeys() / fetch_session_keys() lists active keys, and revokeSessionKey() / revoke_session_key() cuts one off by address. Session keys are also isolated on the data plane — a session signer receives notifications only for its own activity and cannot read orders or trades submitted by other signers or the owner. For an agent deployment, that isolation plus the no-withdrawal rule is exactly the blast-radius bound the Layer 2 security model wants: leak the session key and the attacker can trade badly for at most 180 days, but cannot drain the wallet.


What didn’t change

To avoid over-correcting your existing setup:

  • Pre-V2 Safes — still signature_type = 2. Don’t move them to 3.
  • Pre-V2 Magic/email accounts — still signature_type = 1. Don’t move them to 3.
  • L1/L2 API auth — the ClobAuthDomain EIP-712 used for API key derivation stays at version "1". You do not need to regenerate API keys.
  • The Relayer — gasless transactions still work via the Relayer Client, using your existing HMAC builder API key. The relayer’s wallet-deployment flow now supports WALLET-CREATE for deposit wallets in addition to the legacy Safe/proxy paths.

More questions

What’s the difference between a WALLET batch signature and a CLOB order signature?

Both are signed by the owner EOA against the deposit wallet. A WALLET batch signature is a normal 65-byte EIP-712 signature over the DepositWallet/Batch type, used for on-chain wallet actions (approvals, transfers). A CLOB order signature is an ERC-7739-wrapped POLY_1271 signature (longer than 65 bytes), used for trading — the CLOB validates it via IERC1271.isValidSignature on the deposit wallet. The two payloads are not interchangeable; signing a CLOB order with the batch-signature shape will fail validation, and vice versa.

Can my deposit wallet be exported / moved?

The deposit wallet is owned by the EOA you specified at deployment. The EOA’s private key controls the wallet. The wallet itself is a deployed contract at a deterministic address; the contract isn’t portable, but the owner key (and therefore control) is. If you lose the owner key, the deposit wallet is unrecoverable.

Do I still need the Relayer for gasless transactions?

Yes. The Relayer is what makes the gasless story work — Polymarket pays the gas on WALLET-CREATE, WALLET batches, token approvals, CTF operations. Without the Relayer your owner EOA would need POL on Polygon to pay for everything. With it, the EOA only needs to sign; the Relayer submits and pays.

Why ERC-7739 instead of just EIP-1271?

EIP-1271 alone has a replay risk: a signature valid for one purpose can sometimes be replayed for another. ERC-7739 fixes this by wrapping the signed payload in a nested TypedDataSign envelope bound to the wallet’s own domain — so signatures don’t leak across contexts. It’s the modern way to use EIP-1271 safely.


Changelog

DateChange
May 25, 2026Initial publication. Verified against docs.polymarket.com/trading/deposit-wallets, docs.polymarket.com/trading/gasless, the py-clob-client-v2 source, and open issues #70 / #53.
June 1, 2026Noted the L1-auth defect carried into the new unified beta SDKs (py-sdk #55, ts-sdk #73); cross-linked the Beta SDK Bugs roundup.
Sep 2026Re-verified: #70/#65 still open; docs moved deposit-wallet reference to /trading/wallets-auth; deposit wallets now default for all accounts created on or after May 4, 2026; session keys documented at /trading/session-keys.

Official Resources

AgentBets Guides

Where This Fits in the Agent Betting Stack

This is Layer 2 (Wallet) ↔ Layer 3 (Trading) plumbing in the Agent Betting Stack. POLY_1271 is the bridge between how an agent holds money and signs (Layer 2) and how it puts orders on the wire (Layer 3). The architectural move — from “Polymarket accepts EOAs and a few special proxies” to “Polymarket accepts any ERC-1271 wallet” — is what makes things like session keys, agent-scoped permissions, and account-abstracted bot deployments concrete on Polymarket. Once the open SDK bug lands, the canonical path for new agent operators is a smart-contract wallet (deposit wallet, Safe, or an agentic wallet) signing CLOB orders through POLY_1271, with the Relayer paying gas in the background.


This guide is maintained by AgentBets.ai. Built something interesting on POLY_1271? Let us know on Twitter.

Not financial advice. Built for builders.