Skip to content

bearvar/poly-examples

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Polymarket CTF Split/Merge/Redeem via Builder Relayer

License: MIT Polygon

Gas-free Split, Merge, and Redeem operations for Polymarket CLOB tokens using the Builder Relayer.

Repository: github.com/AleSZanello/poly-examples

Key Discovery

For CLOB tokens, you MUST use the CTF contract directly with parentCollectionId = bytes32(0)

Approach Target Contract Works for CLOB Tokens?
NegRisk Adapter 0xd91E80cF... ❌ NO - Different token IDs
CTF Direct 0x4d97dcd9... ✅ YES - Use parent=0

The NegRisk Adapter computes different internal token IDs, causing "SafeMath: subtraction overflow" errors when trying to merge/redeem CLOB tokens.

Verified Transactions

All operations tested and verified on Polygon Mainnet:

Operation TX Hash Result
Split 0x2de1ba6f... 1 USDC → 1 YES + 1 NO ✅
Merge 0x24c98b90... 1 YES + 1 NO → 1 USDC ✅
Redeem 0x0b115de5... After resolution → 1 USDC ✅

Installation

Prerequisites

  • Python 3.9+
  • Polymarket account with Builder API credentials
  • USDC in your Polymarket wallet

Setup

  1. Clone this repository:
git clone https://github.com/AleSZanello/poly-examples.git
cd poly-examples
  1. Create virtual environment:
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
  1. Install dependencies:
pip install -r requirements.txt
  1. Configure environment:
cp .env.example .env
# Edit .env with your credentials

Usage

Check Balances

python test_split_merge_redeem.py --wallet proxy --check-only

Split USDC into YES + NO tokens

# Split 1 USDC into 1 YES + 1 NO tokens
python test_split_merge_redeem.py --wallet proxy --split-only --amount 1

Merge YES + NO tokens back to USDC

# Merge 1 YES + 1 NO back to 1 USDC
python test_split_merge_redeem.py --wallet proxy --merge-only --amount 1 -c <CONDITION_ID>

Redeem after market resolution

# Redeem tokens after market resolves
python test_split_merge_redeem.py --wallet proxy --redeem -c <CONDITION_ID>

Full Cycle Test

# Run complete split -> merge cycle
python test_split_merge_redeem.py --wallet proxy --amount 1

Dry Run Mode

# Simulate without executing (validates everything works)
python test_split_merge_redeem.py --wallet proxy --dry-run --amount 1

Wallet Types

The script supports two wallet types:

Wallet Flag Description
PROXY --wallet proxy Polymarket Proxy wallet (default for website trading)
SAFE --wallet safe Gnosis Safe wallet (Builder Relayer default)

Most users should use --wallet proxy since that's where your USDC and tokens are when trading on Polymarket.

Contract Addresses (Polygon Mainnet)

Contract Address
CTF (Conditional Tokens) 0x4d97dcd97ec945f40cf65f87097ace5ea0476045
USDC 0x2791bca1f2de4661ed88a30c99a7a9449aa84174
NegRisk Adapter 0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296
CTF Exchange 0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e

How It Works

CTF Function Signatures

// Split USDC into YES + NO tokens
function splitPosition(
    address collateralToken,     // USDC
    bytes32 parentCollectionId,  // bytes32(0) for CLOB tokens!
    bytes32 conditionId,         // Market condition ID
    uint256[] partition,         // [1, 2] for YES and NO
    uint256 amount               // Amount in 6 decimals
) external;

// Merge YES + NO tokens back to USDC
function mergePositions(
    address collateralToken,     // USDC
    bytes32 parentCollectionId,  // bytes32(0) for CLOB tokens!
    bytes32 conditionId,         // Market condition ID
    uint256[] partition,         // [1, 2] for YES and NO
    uint256 amount               // Amount in 6 decimals
) external;

// Redeem tokens after resolution
function redeemPositions(
    address collateralToken,     // USDC
    bytes32 parentCollectionId,  // bytes32(0) for CLOB tokens!
    bytes32 conditionId,         // Market condition ID
    uint256[] indexSets          // [1, 2] for both outcomes
) external;

Key Implementation Detail

The critical parameter is parentCollectionId = bytes32(0). This tells the CTF contract to use the root collection, which matches the CLOB token IDs.

def build_merge_data(condition_id: str, amount: int) -> str:
    params = encode(
        ['address', 'bytes32', 'bytes32', 'uint256[]', 'uint256'],
        [
            USDC_ADDRESS,
            bytes(32),  # parentCollectionId = 0 (KEY!)
            condition_id_bytes,
            [1, 2],
            amount
        ]
    )
    return "0x" + (selector + params).hex()

API Endpoints for Market Lookup

When you need to find a market's conditionId:

Endpoint Works? Notes
GET /markets/{conditionId} Returns "id is invalid"
GET /markets?conditionId=X Returns wrong market!
GET /markets?clob_token_ids=X Best method
GET /book?token_id=X (CLOB API) Returns conditionId as market

Example: Get conditionId from token_id

import aiohttp
import json

async def get_condition_id(token_id: str) -> str:
    url = f"https://gamma-api.polymarket.com/markets?clob_token_ids={token_id}"
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            markets = await resp.json()
            return markets[0]['conditionId']

Troubleshooting

"SafeMath: subtraction overflow"

Cause: Using NegRisk Adapter instead of CTF contract directly. Solution: Ensure you're targeting 0x4d97dcd97ec945f40cf65f87097ace5ea0476045 with parent=0.

"Could not find market for condition ID"

Cause: The Gamma API doesn't support direct conditionId lookup. Solution: Use token_id lookup instead: GET /markets?clob_token_ids={token_id}

"Insufficient balance"

Cause: Not enough USDC or tokens in the wallet. Solution: Check balances with --check-only and ensure you're using the correct wallet type.

"Gas estimation failed"

Cause: Usually indicates the transaction would revert. Solution: Verify token balances, approvals, and that the market hasn't resolved yet (for merge) or has resolved (for redeem).

Security Notes

  • Never commit your .env file with real credentials
  • The private key controls your Polymarket wallets - keep it secure
  • Builder API credentials should be treated as sensitive
  • Test with small amounts first

Dependencies

py-builder-relayer-client
py-builder-signing-sdk
python-dotenv
eth-account
web3
eth-abi
aiohttp
requests

License

MIT License

Contributing

Pull requests welcome! Please test thoroughly before submitting.

Acknowledgments

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Python 100.0%