Smart Contracts
Polygolem interacts with Polymarket’s on-chain infrastructure on Polygon (chain ID 137). This page documents every contract address, the factory permission model, and CREATE2 address derivation.
Contract Registry
Core Trading Contracts
| Contract | Address | Role |
|---|---|---|
| CTF Exchange V2 | 0xE111180000d2663C0091e4f400237545B87B996B | Order matching and settlement |
| Neg Risk CTF Exchange | 0xe2222d279d744050d28e00520010520000310F59 | Neg-risk market matching |
| Neg Risk Adapter | 0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296 | Neg-risk adapter |
Collateral
| Contract | Address | Role |
|---|---|---|
| pUSD (proxy) | 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB | ERC-20 collateral token |
| pUSD (impl) | 0x6bBCef9f7ef3B6C592c99e0f206a0DE94Ad0925f | Collateral implementation |
| CTF | 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 | Conditional Tokens (ERC-1155) |
| CollateralOnramp | 0x93070a847efEf7F70739046A929D47a521F5B8ee | Wrap/onramp collateral |
| CollateralOfframp | 0x2957922Eb93258b93368531d39fAcCA3B4dC5854 | Offramp collateral |
| PermissionedRamp | 0xebC2459Ec962869ca4c0bd1E06368272732BCb08 | Permissioned ramp |
| CtfCollateralAdapter | 0xAdA100Db00Ca00073811820692005400218FcE1f | V2 split/merge/redeem adapter for standard CTF markets |
| NegRiskCtfCollateralAdapter | 0xadA2005600Dec949baf300f4C6120000bDB6eAab | V2 split/merge/redeem adapter for negative-risk markets |
V2 Collateral Layer
pUSD is Polymarket’s V2 wrapped collateral token on Polygon. The proxy
address is 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB; local V2 source
names it Polymarket USD, symbol pUSD, with 6 decimals.
Deposit wallets should not call ConditionalTokens.redeemPositions directly
for V2 pUSD-native flows. The supported deposit-wallet path is an EIP-712
WALLET batch submitted by the relayer through the deposit-wallet factory, with
the wallet call targeting a collateral adapter. Standard binary-market split,
merge, and redeem calls route through CtfCollateralAdapter; negative-risk
calls route through NegRiskCtfCollateralAdapter.
The adapter intentionally keeps the legacy redeem ABI shape:
redeemPositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] indexSets)It uses conditionId, reads the deposit wallet’s current YES/NO CTF balances,
pulls those ERC-1155 positions into the adapter, redeems through the
underlying CTF path, wraps the USDC.e proceeds back into pUSD, and sends pUSD
to the deposit wallet. The caller-supplied collateral address, parent
collection, and indexSets are accepted for compatibility but ignored by the
adapter.
Trading approvals and adapter approvals are separate:
| Approval Set | Spenders | Purpose |
|---|---|---|
| Trading approvals | CTFExchangeV2, NegRiskExchangeV2, NegRiskAdapterV2 | CLOB order matching and settlement |
| Adapter approvals | CtfCollateralAdapter, NegRiskCtfCollateralAdapter | V2 split, merge, and redeem |
The existing six-call approval batch covers trading. V2 redeem readiness
requires an additional four-call adapter batch: pUSD approve and CTF
setApprovalForAll for both collateral adapters.
SAFE/PROXY relayer examples are separate wallet-type flows. They do not create
a deposit-wallet shortcut around the V2 adapter path, and raw
ConditionalTokens.redeemPositions must not be used as a deposit-wallet
fallback. If the relayer rejects adapter calls, first verify the local adapter
addresses against Polymarket’s current contracts reference; stale constants are
a known failure mode.
Wallet Factories
| Contract | Address | Role |
|---|---|---|
| DepositWalletFactory | 0x00000000000Fb5C9ADea0298D729A0CB3823Cc07 | CREATE2 deploys deposit wallets (POLY_1271) |
| Proxy Factory | 0xaB45c5A4B0c941a2F231C04C3f49182e1A254052 | Older POLY_PROXY wallets (type 1) |
| Gnosis Safe Factory | 0xaacFeEa03eb1561C4e67d661e40682Bd20E3541b | Gnosis Safe wallets (type 2) |
Which Wallet Wins
New API users (post-May 2026): Deposit wallet (type 3 / POLY_1271) only. EOA orders blocked by CLOB V2.
Grandfathered users: Proxy (type 1) or Safe (type 2) still work.
Deployment Status Source of Truth
There are two deployment signals, and they answer different questions:
| Signal | Source | Meaning |
|---|---|---|
| Relayer deployed flag | GET /deployed?address=<owner> | Relayer/indexer view used by Polymarket’s wallet lifecycle API |
| Contract bytecode | Polygon eth_getCode(<depositWallet>) | Chain truth: whether the deposit wallet has deployed code |
For trading safety, on-chain bytecode wins. The CTF Exchange V2 POLY_1271
path checks maker.code.length > 0 before validating the ERC-1271 signature.
If bytecode exists at the derived deposit wallet address, the wallet is deployed
for order-signing purposes even when /deployed returns a false negative.
2026-05-09 live evidence:
owner: 0x33e4aD5A1367fbf7004c637F628A5b78c44Fa76CdepositWallet: 0x21999a074344610057c9b2B362332388a44502D4relayer: deployed=falsepolygon code: 0x363d3d373d3d363d7f360894a13ba1a3210667c828492db...status: deployed for POLY_1271Polygolem exposes the chain check through pkg/contracts:
status, err := contracts.DepositWalletDeployed(ctx, depositWallet, "")if err != nil { return err}if status.Deployed { // Skip WALLET-CREATE and continue approval/funding/readiness.}The CLI reports the split explicitly:
{ "deployed": true, "deploymentStatusSource": "polygon_code", "relayerDeployed": false, "onchainCodeDeployed": true}DepositWalletFactory
Address: 0x00000000000Fb5C9ADea0298D729A0CB3823Cc07
The factory deploys per-user deposit wallets via CREATE2. Each wallet is an ERC-1967 proxy that implements ERC-1271 signature validation.
Factory Properties
| Property | Value |
|---|---|
| Compiler | v0.8.34, 1M optimizer runs |
| CREATE2 salt | keccak256(abi.encode(factory, bytes32(owner))) |
| Pattern | ERC-1967 upgradeable proxy |
Public ABI
deploy(address[] _owners, bytes32[] _ids) — ROLE-GATED (relayer only)proxy(Batch[] _batches, bytes[] _signatures) — ROLE-GATED (relayer only) + signature-validatedpredictWalletAddress(address _impl, bytes32 _id) — UNGATED (view function)implementation() — UNGATED (view)authorizedImplementation(address) — admin-gatedaddAdmin / removeAdmin / addOperator / removeOperatorgrantRoles / revokeRoles / hasAllRoles / hasAnyRolePermission Model
| Function | Gated By | Who Can Call |
|---|---|---|
deploy() | Operator role | Polymarket relayer EOA only |
proxy() | Operator role plus wallet-owner signature validation | Polymarket relayer EOA only |
predictWalletAddress() | Ungated | Anyone |
implementation() | Ungated | Anyone |
Key insight: both deploy() and proxy() are role-gated to the relayer/operator surface. The wallet owner still authorizes WALLET batches with EOA signatures, but a direct owner EOA cannot bypass the relayer by calling the factory proxy() function.
Polygon RPC proof from 2026-05-09:
cast sig 'OnlyOperator()'# 0x27e1f1e5
cast call --rpc-url https://polygon-bor-rpc.publicnode.com \ --from 0x000000000000000000000000000000000000dEaD \ 0x00000000000Fb5C9ADea0298D729A0CB3823Cc07 \ 'proxy((address,uint256,uint256,(address,uint256,bytes)[])[],bytes[])' \ '[]' '[]'# execution reverted, data: "0x27e1f1e5"ABI Errors
OnlyAdmin, OnlyOperator, OnlyRolesUnauthorized, UnauthorizedCallContextPresence of OnlyAdmin and OnlyOperator confirms role-gated functions. Polygonscan verified source for the production factory shows proxy(Batch[], bytes[]) external onlyOperator. The operator is Polymarket’s relayer EOA.
CREATE2 Address Derivation
The deposit wallet address is deterministically computed from the EOA address:
walletId = bytes32(owner) // owner address left-padded to 32 bytesargs = abi.encode(factory, walletId)salt = keccak256(args)bytecodeHash = SoladyLibClone.initCodeHashERC1967(implementation, args)depositWallet = CREATE2(factory, salt, bytecodeHash)
where: factory_address = 0x00000000000Fb5C9ADea0298D729A0CB3823Cc07 implementation = 0x58CA52ebe0DadfdF531Cde7062e76746de4Db1eBThe factory address is embedded in the derivation. You cannot deploy from a different address and get the same result. This is why the relayer must handle deployment — only the factory at 0x000...b5C9 can produce wallets at CLOB-recognized addresses.
Polygolem Implementation
func deriveCreate2(factory string, salt []byte, initCodeHash string) string { hash := sha3.NewLegacyKeccak256() hash.Write([]byte{0xff}) hash.Write(hexToBytes(strip0x(factory))) hash.Write(salt) hash.Write(hexToBytes(strip0x(initCodeHash))) return "0x" + toHex(hash.Sum(nil)[12:])}This is pure local computation — no on-chain call needed. The relayer is only required for deployment.
Deposit Wallet Interface
The deployed wallet is an ERC-1967 proxy implementing ERC-1271:
interface IDepositWallet { // Any protocol can validate EOA signatures through this wallet function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue); // 0x1626ba7e
// ERC-1155 token receiver function onERC1155Received(address, address, uint256, uint256, bytes calldata) external returns (bytes4);
// The EOA that owns this wallet function owner() external view returns (address);
// Verified implementation gates this with onlyFactory. function execute(Batch calldata batch, bytes calldata signature) external;}The wallet has no private key. The EOA signs messages; the wallet validates via isValidSignature(). This is the ERC-1271 pattern — the CLOB Exchange calls isValidSignature() on the deposit wallet to verify POLY_1271 typed orders.
The verified implementation at 0x58CA52ebe0DadfdF531Cde7062e76746de4Db1eB
uses modifier onlyFactory() { require(msg.sender == factory(), OnlyFactory()); _; }
on execute(...), so direct EOA calls to the wallet are not a recovery path.
Relayer v2
The relayer at relayer-v2.polymarket.com provides gas-sponsored transaction submission.
| Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/submit | POST | Builder HMAC or Relayer API Key | WALLET-CREATE, WALLET batch |
/nonce | GET | Builder HMAC or Relayer API Key | Current WALLET nonce |
/transaction | GET | Builder HMAC or Relayer API Key | Poll transaction status |
/deployed | GET | Builder HMAC or Relayer API Key | Relayer/indexer deployment view; use Polygon bytecode as fallback/source of truth |
/relay-payload | GET | None | Get relayer address + nonce |
/relayer/api/keys | GET | Gamma auth or API key | List relayer API keys |
Transaction Types
| Type | Purpose | Gas |
|---|---|---|
WALLET-CREATE | Deploy deposit wallet | Sponsored |
WALLET batch | Execute calls from wallet (approvals, transfers) | Sponsored |
SAFE | Gnosis Safe operations | Sponsored |
PROXY | POLY_PROXY operations | Sponsored |
Gas Sponsorship
| Operation | Who Pays | Cost to User |
|---|---|---|
| Wallet deploy (WALLET-CREATE) | Polymarket relayer | FREE |
| Trading approve batch | Polymarket relayer | FREE |
| Adapter approve batch | Polymarket relayer, when accepted by its allowlist | FREE |
| Order placement | No user transaction; posted to CLOB | No user gas |
| Trade settlement | CLOB executor / exchange settlement path | No user-submitted gas transaction |
| CTF split/merge/redeem through V2 collateral adapters | Polymarket relayer, when accepted by its allowlist | FREE |
| Fund wallet (pUSD transfer) | User | ~$0.01 POL |
The only transaction you pay gas for is the initial pUSD transfer from EOA to deposit wallet.
Alternate Deployment Paths (All Dead Ends)
| Path | Verdict |
|---|---|
Direct EOA call to deploy() | ❌ Reverts — role-gated |
Direct EOA call to factory proxy() | ❌ Reverts with OnlyOperator() unless caller has operator role |
Direct EOA call to wallet execute() | ❌ Wallet execution is factory-mediated |
Raw ConditionalTokens.redeemPositions fallback | ❌ Not a deposit-wallet fallback; SAFE/PROXY examples do not apply |
Old ProxyFactory proxy() | ❌ Creates type 1 wallets, not type 3 |
| Self-deploy on implementation | ❌ No such function |
| Separate permissionless factory | ❌ Only one DepositWalletFactory |
| Meta-transaction relay (OpenGSN) | ❌ No third-party relay holds operator role |
See Also
- Deposit Wallet Lifecycle — full onboarding walkthrough
- Secrets Management — builder credential handling
- Architecture — package boundaries
- CLOB API Reference — order signing and trade endpoints