Skip to content

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

ContractAddressRole
CTF Exchange V20xE111180000d2663C0091e4f400237545B87B996BOrder matching and settlement
Neg Risk CTF Exchange0xe2222d279d744050d28e00520010520000310F59Neg-risk market matching
Neg Risk Adapter0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296Neg-risk adapter

Collateral

ContractAddressRole
pUSD (proxy)0xC011a7E12a19f7B1f670d46F03B03f3342E82DFBERC-20 collateral token
pUSD (impl)0x6bBCef9f7ef3B6C592c99e0f206a0DE94Ad0925fCollateral implementation
CTF0x4D97DCd97eC945f40cF65F87097ACe5EA0476045Conditional Tokens (ERC-1155)
CollateralOnramp0x93070a847efEf7F70739046A929D47a521F5B8eeWrap/onramp collateral
CollateralOfframp0x2957922Eb93258b93368531d39fAcCA3B4dC5854Offramp collateral
PermissionedRamp0xebC2459Ec962869ca4c0bd1E06368272732BCb08Permissioned ramp
CtfCollateralAdapter0xAdA100Db00Ca00073811820692005400218FcE1fV2 split/merge/redeem adapter for standard CTF markets
NegRiskCtfCollateralAdapter0xadA2005600Dec949baf300f4C6120000bDB6eAabV2 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 SetSpendersPurpose
Trading approvalsCTFExchangeV2, NegRiskExchangeV2, NegRiskAdapterV2CLOB order matching and settlement
Adapter approvalsCtfCollateralAdapter, NegRiskCtfCollateralAdapterV2 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

ContractAddressRole
DepositWalletFactory0x00000000000Fb5C9ADea0298D729A0CB3823Cc07CREATE2 deploys deposit wallets (POLY_1271)
Proxy Factory0xaB45c5A4B0c941a2F231C04C3f49182e1A254052Older POLY_PROXY wallets (type 1)
Gnosis Safe Factory0xaacFeEa03eb1561C4e67d661e40682Bd20E3541bGnosis 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:

SignalSourceMeaning
Relayer deployed flagGET /deployed?address=<owner>Relayer/indexer view used by Polymarket’s wallet lifecycle API
Contract bytecodePolygon 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: 0x33e4aD5A1367fbf7004c637F628A5b78c44Fa76C
depositWallet: 0x21999a074344610057c9b2B362332388a44502D4
relayer: deployed=false
polygon code: 0x363d3d373d3d363d7f360894a13ba1a3210667c828492db...
status: deployed for POLY_1271

Polygolem 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

PropertyValue
Compilerv0.8.34, 1M optimizer runs
CREATE2 saltkeccak256(abi.encode(factory, bytes32(owner)))
PatternERC-1967 upgradeable proxy

Public ABI

deploy(address[] _owners, bytes32[] _ids) — ROLE-GATED (relayer only)
proxy(Batch[] _batches, bytes[] _signatures) — ROLE-GATED (relayer only) + signature-validated
predictWalletAddress(address _impl, bytes32 _id) — UNGATED (view function)
implementation() — UNGATED (view)
authorizedImplementation(address) — admin-gated
addAdmin / removeAdmin / addOperator / removeOperator
grantRoles / revokeRoles / hasAllRoles / hasAnyRole

Permission Model

FunctionGated ByWho Can Call
deploy()Operator rolePolymarket relayer EOA only
proxy()Operator role plus wallet-owner signature validationPolymarket relayer EOA only
predictWalletAddress()UngatedAnyone
implementation()UngatedAnyone

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:

Terminal window
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, OnlyRoles
Unauthorized, UnauthorizedCallContext

Presence 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 bytes
args = abi.encode(factory, walletId)
salt = keccak256(args)
bytecodeHash = SoladyLibClone.initCodeHashERC1967(implementation, args)
depositWallet = CREATE2(factory, salt, bytecodeHash)
where:
factory_address = 0x00000000000Fb5C9ADea0298D729A0CB3823Cc07
implementation = 0x58CA52ebe0DadfdF531Cde7062e76746de4Db1eB

The 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

internal/wallet/derive.go
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.

EndpointMethodAuthPurpose
/submitPOSTBuilder HMAC or Relayer API KeyWALLET-CREATE, WALLET batch
/nonceGETBuilder HMAC or Relayer API KeyCurrent WALLET nonce
/transactionGETBuilder HMAC or Relayer API KeyPoll transaction status
/deployedGETBuilder HMAC or Relayer API KeyRelayer/indexer deployment view; use Polygon bytecode as fallback/source of truth
/relay-payloadGETNoneGet relayer address + nonce
/relayer/api/keysGETGamma auth or API keyList relayer API keys

Transaction Types

TypePurposeGas
WALLET-CREATEDeploy deposit walletSponsored
WALLET batchExecute calls from wallet (approvals, transfers)Sponsored
SAFEGnosis Safe operationsSponsored
PROXYPOLY_PROXY operationsSponsored

Gas Sponsorship

OperationWho PaysCost to User
Wallet deploy (WALLET-CREATE)Polymarket relayerFREE
Trading approve batchPolymarket relayerFREE
Adapter approve batchPolymarket relayer, when accepted by its allowlistFREE
Order placementNo user transaction; posted to CLOBNo user gas
Trade settlementCLOB executor / exchange settlement pathNo user-submitted gas transaction
CTF split/merge/redeem through V2 collateral adaptersPolymarket relayer, when accepted by its allowlistFREE
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)

PathVerdict
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