Smart Contracts
Polygolem interacts with Polymarket’s on-chain infrastructure on Polygon (chain ID 137). This page documents every contract address polygolem relies on, the upstream resolution contracts, the factory permission model, and CREATE2 address derivation.
Source of Truth
Use this order when contract addresses disagree:
| Source | Use |
|---|---|
| Polymarket contracts reference | Upstream source of truth for deployed Polygon addresses |
pkg/contracts.PolygonMainnet() | Polygolem’s mirrored SDK registry for addresses the SDK actively uses |
Polygon eth_getCode | Runtime deployment truth for a derived deposit wallet or target contract |
Resolution contracts are documented here from Polymarket’s upstream registry,
but they are not currently fields in pkg/contracts.Registry because the
SDK does not call them directly.
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) |
| USDC.e | 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 | Legacy Polygon USDC.e collateral used by CTF adapter internals and the Enable Trading onramp approval |
| 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) |
Resolution Contracts
| Contract | Address | Role |
|---|---|---|
| UMA Adapter | 0x6A9D222616C90FcA5754cd1333cFD9b7fb6a4F74 | Resolution adapter for UMA oracle results |
| UMA Optimistic Oracle | 0xCB1822859cEF82Cd2Eb4E6276C7916e692995130 | UMA optimistic oracle contract referenced by Polymarket resolution |
Polygolem currently reads settlement readiness from Data API position fields and submits V2 adapter calls through the relayer. It does not call the UMA contracts directly.
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_address | Relayer/indexer view used by Polymarket’s wallet lifecycle API |
| Contract bytecode | Polygon eth_getCode(deposit_wallet) | 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 active SDK registry shape is:
registry := contracts.PolygonMainnet()registry.DepositWalletFactoryregistry.CTFExchangeV2registry.NegRiskExchangeV2registry.NegRiskAdapterV2registry.CtfCollateralAdapterregistry.NegRiskCtfCollateralAdapterregistry.CollateralOnrampregistry.CollateralOfframpregistry.PermissionedRampregistry.PUSDregistry.CTFregistry.USDCEThe 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 |
|---|---|---|---|
/relayer/api/auth | POST | SIWE session cookies | Mint a V2 relayer API key |
/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 |
/transactions | GET | Builder HMAC or Relayer API Key | Recent transaction list |
/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
- Relayer API Reference — relayer endpoints and auth headers
- Upstream: Polymarket Contracts