sign_api_key_auth in src/polymarket/_internal/l1_auth.py:55-77 signs the ClobAuth EIP-712 payload using signer.address (the EOA) for every wallet type, and _resolve_api_credentials in src/polymarket/clients/async_secure.py:2360-2363 calls it without passing wallet_type or any deposit-wallet context. The resulting API key is registered against the EOA, which is incompatible with how the SDK later signs orders for WalletType.DEPOSIT_WALLET (SignatureType.POLY_1271 = 3).
Current flow
# _internal/l1_auth.py:55-77
def sign_api_key_auth(
signer: LocalAccount,
*,
chain_id: int,
timestamp: int,
nonce: int = 0,
) -> ApiKeyAuthSignature:
typed_data = build_api_key_auth_typed_data(
address=signer.address, # ← EOA, regardless of wallet_type
chain_id=chain_id,
timestamp=timestamp,
nonce=nonce,
)
try:
signed = signer.sign_typed_data(full_message=typed_data) # ← raw ECDSA, no 1271 wrap
except Exception as error:
raise SigningError(...) from error
return ApiKeyAuthSignature(
address=signer.address, # ← POLY_ADDRESS header = EOA
nonce=nonce,
signature="0x" + signed.signature.hex(),
timestamp=timestamp,
)
# clients/async_secure.py:2360-2363
signature = sign_api_key_auth(
signer, chain_id=environment.chain_id, timestamp=int(time.time()), nonce=nonce
)
return await _auth_actions.create_or_derive_api_key(clob, signature)
The caller does not consult wallet_type (despite _ctx.wallet_type being available elsewhere, e.g. async_secure.py:1928, 1953, 1988, 2014).
Asymmetry with the order path
The order path already implements the POLY_1271 wrap correctly:
src/polymarket/_internal/actions/orders/orders.py:20 — signer = wallet if signature_type == _POLY_1271_SIGNATURE_TYPE else draft.signer
src/polymarket/_internal/actions/orders/typed_data.py:65-90 — build_order_typed_data wraps the order in TypedDataSign with the DepositWallet domain (_DEPOSIT_WALLET_DOMAIN_NAME = "DepositWallet", verifyingContract: order.signer, salt: BYTES32_ZERO) for _POLY_1271_SIGNATURE_TYPE.
src/polymarket/_internal/actions/orders/typed_data.py:93-108 — build_order_signature builds the ERC-7739 trailer (appDomainSep + contentsHash + CONTENTS_TYPE hex + length_BE2).
So orders authored by a deposit wallet sign via ERC-7739 wrapping with the wallet as signer/maker; authentication still signs with the EOA. The auth path was simply not updated when the order path picked up POLY_1271 support.
Server-side consequence (matches clob-client-v2 cluster)
The API key is registered with POLY_ADDRESS = <EOA>. Later orders carry maker = ctx.wallet (deposit wallet) and the signer field set to the wallet (per orders.py:20). The CLOB's acceptance check ties the order's signer to the api-key holder, which now mismatch — same shape reported repeatedly on the V2 transitional client:
Same root cause, different code organization.
What's needed to fix
Two pieces, mirroring build_order_typed_data / build_order_signature:
ApiKeyAuthSignature.address and the address field in the ClobAuth typed-data message must be the deposit wallet when wallet_type == "DEPOSIT_WALLET", not signer.address. Pass wallet / wallet_type from _resolve_api_credentials into sign_api_key_auth.
- The signature must be ERC-7739
TypedDataSign-wrapped so the CLOB server's isValidSignature call on the deposit wallet contract validates. The wrap pattern already exists in typed_data.py:65-108 and can be ported to a _build_api_key_auth_typed_data deposit-wallet variant + _build_api_key_auth_signature trailer builder.
The blocker is two server-side constants the SDK can't infer alone:
- The app domain separator the CLOB server uses when calling
isValidSignature on the deposit wallet for the L1-auth flow (analogous to _app_domain_separator(order) for orders, which derives from the CTF Exchange V2 domain).
- The
CONTENTS_TYPE string for ClobAuth (analogous to _ORDER_TYPE_STRING).
Once those two are confirmed by Polymarket, the py-sdk implementation is mechanical — the existing typed_data.py infrastructure can be parametrized over the contents-type.
Impact
Every user with a V2 deposit wallet hits this on first authentication. The ApiKeyCreds returned looks valid, but the first order submission fails server-side with the "signer address has to be the address of the API KEY" error.
Cross-SDK
Same bug in ts-sdk: Polymarket/ts-sdk#73 (beginAuthentication in packages/client/src/clients.ts:418-432 signs ClobAuth with signerAddress regardless of identity.walletType, with no wrap).
Happy to send a PR once the two server-side constants are confirmed.
sign_api_key_authinsrc/polymarket/_internal/l1_auth.py:55-77signs theClobAuthEIP-712 payload usingsigner.address(the EOA) for every wallet type, and_resolve_api_credentialsinsrc/polymarket/clients/async_secure.py:2360-2363calls it without passingwallet_typeor any deposit-wallet context. The resulting API key is registered against the EOA, which is incompatible with how the SDK later signs orders forWalletType.DEPOSIT_WALLET(SignatureType.POLY_1271 = 3).Current flow
The caller does not consult
wallet_type(despite_ctx.wallet_typebeing available elsewhere, e.g.async_secure.py:1928, 1953, 1988, 2014).Asymmetry with the order path
The order path already implements the POLY_1271 wrap correctly:
src/polymarket/_internal/actions/orders/orders.py:20—signer = wallet if signature_type == _POLY_1271_SIGNATURE_TYPE else draft.signersrc/polymarket/_internal/actions/orders/typed_data.py:65-90—build_order_typed_datawraps the order inTypedDataSignwith theDepositWalletdomain (_DEPOSIT_WALLET_DOMAIN_NAME = "DepositWallet",verifyingContract: order.signer,salt: BYTES32_ZERO) for_POLY_1271_SIGNATURE_TYPE.src/polymarket/_internal/actions/orders/typed_data.py:93-108—build_order_signaturebuilds the ERC-7739 trailer (appDomainSep + contentsHash + CONTENTS_TYPE hex + length_BE2).So orders authored by a deposit wallet sign via ERC-7739 wrapping with the wallet as
signer/maker; authentication still signs with the EOA. The auth path was simply not updated when the order path picked up POLY_1271 support.Server-side consequence (matches clob-client-v2 cluster)
The API key is registered with
POLY_ADDRESS = <EOA>. Later orders carrymaker = ctx.wallet(deposit wallet) and thesignerfield set to the wallet (perorders.py:20). The CLOB's acceptance check ties the order'ssignerto the api-key holder, which now mismatch — same shape reported repeatedly on the V2 transitional client:createApiKey()doesn't EIP-1271-wrap L1 auth)Same root cause, different code organization.
What's needed to fix
Two pieces, mirroring
build_order_typed_data/build_order_signature:ApiKeyAuthSignature.addressand theaddressfield in the ClobAuth typed-data message must be the deposit wallet whenwallet_type == "DEPOSIT_WALLET", notsigner.address. Passwallet/wallet_typefrom_resolve_api_credentialsintosign_api_key_auth.TypedDataSign-wrapped so the CLOB server'sisValidSignaturecall on the deposit wallet contract validates. The wrap pattern already exists intyped_data.py:65-108and can be ported to a_build_api_key_auth_typed_datadeposit-wallet variant +_build_api_key_auth_signaturetrailer builder.The blocker is two server-side constants the SDK can't infer alone:
isValidSignatureon the deposit wallet for the L1-auth flow (analogous to_app_domain_separator(order)for orders, which derives from the CTF Exchange V2 domain).CONTENTS_TYPEstring forClobAuth(analogous to_ORDER_TYPE_STRING).Once those two are confirmed by Polymarket, the py-sdk implementation is mechanical — the existing
typed_data.pyinfrastructure can be parametrized over the contents-type.Impact
Every user with a V2 deposit wallet hits this on first authentication. The
ApiKeyCredsreturned looks valid, but the first order submission fails server-side with the "signer address has to be the address of the API KEY" error.Cross-SDK
Same bug in ts-sdk: Polymarket/ts-sdk#73 (
beginAuthenticationinpackages/client/src/clients.ts:418-432signsClobAuthwithsignerAddressregardless ofidentity.walletType, with no wrap).Happy to send a PR once the two server-side constants are confirmed.