Skip to content

auth: sign_api_key_auth uses signer.address for ClobAuth, ignoring POLY_1271 deposit wallet shape #55

@Nexory

Description

@Nexory

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:20signer = wallet if signature_type == _POLY_1271_SIGNATURE_TYPE else draft.signer
  • src/polymarket/_internal/actions/orders/typed_data.py:65-90build_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-108build_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:

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions