
Polymarket Trade
- 1 installs
- 1 repo stars
- Updated July 29, 2026
- starchild-ai-agent/community-skills
Place real bets on Polymarket prediction markets - buy/sell outcome tokens on the CLOB using an EOA-mode Polygon wallet with EIP-712 signing.
About
A skill that places real bets on Polymarket prediction markets using the agent's Polygon wallet in EOA mode, buying and selling outcome tokens via the CLOB with EIP-712 signing. A developer uses it to let an agent trade prediction markets with USDC on Polygon.
- Trades Polymarket CLOB in EOA mode with EIP-712 signing on Polygon
- One-time USDC approvals then gasless CLOB orders
Polymarket Trade by the numbers
- 1 all-time installs (skills.sh)
- Ranked #426 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
npx skills add https://github.com/starchild-ai-agent/community-skills --skill polymarket-tradeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 1 |
| Last updated | July 29, 2026 |
| Repository | starchild-ai-agent/community-skills ↗ |
What it does
Place real bets on Polymarket prediction markets - buy/sell outcome tokens on the CLOB using an EOA-mode Polygon wallet with EIP-712 signing.
Files
Polymarket Trading Skill
Trade on Polymarket CLOB using the agent's Polygon wallet in EOA mode (signature_type=0).
Architecture
| Mode | sig_type | How it works | Gas? |
|---|---|---|---|
| EOA (we use this) | 0 | Agent wallet = signer AND maker. No proxy. | Yes — POL for one-time approvals |
| Safe/Proxy | 2 | Gnosis Safe proxy holds funds, EOA signs. | No (relayer pays) |
Why EOA? Simpler — no proxy deployment, no relayer. Only gas cost is ~$0.01 POL for one-time approvals. CLOB orders are gasless after that.
First-Time Onboarding
If POLY_API_KEY is NOT in .env, follow these steps:
1. Get Wallet Address
Use wallet_info to get the agent's Polygon address → this becomes POLY_WALLET.
2. Create API Credentials
Sign an EIP-712 ClobAuth message and POST to CLOB to get API key/secret/passphrase.
Domain: { name: "ClobAuthDomain", version: "1", chainId: 137, verifyingContract: "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E" } Types: ClobAuth: [address, timestamp(string), nonce(uint256), message(string)] Message: { address: WALLET, timestamp: NOW, nonce: "0", message: "This message attests that I control the given wallet" }
1. Sign with wallet_sign_typed_data 2. POST https://clob.polymarket.com/auth/api-key (VPN required) with L1 auth headers 3. Save to .env: POLY_API_KEY, POLY_SECRET, POLY_PASSPHRASE, POLY_WALLET
3. Fund the Wallet
Send to the agent wallet address on Polygon:
- USDC (native Polygon USDC
0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) — betting bankroll - POL (~1 POL / ~$0.20) — gas for approval txs
4. Approve USDC (One-Time, needs POL)
Approve both CTF Exchange contracts to spend USDC (max allowance):
from eth_abi import encode
from eth_utils import function_signature_to_4byte_selector
sel = function_signature_to_4byte_selector("approve(address,uint256)")
USDC = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
# Approval 1: CTF Exchange
data1 = sel + encode(['address','uint256'], ['0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E', 2**256-1])
# wallet_transfer(to=USDC, amount="0", chain_id=137, data="0x"+data1.hex())
# Approval 2: CTF Exchange Neg-Risk
data2 = sel + encode(['address','uint256'], ['0xC5d563A36AE78145C45a50134d48A1215220f80a', 2**256-1])
# wallet_transfer(to=USDC, amount="0", chain_id=137, data="0x"+data2.hex())5. Verify
python3 skills/polymarket-trade/scripts/poly_research.py balanceShould show USDC balance and allowance > 0. Setup complete.
Wallet Policy
Agent needs: eth_signTypedData_v4 (order signing) + eth_sendTransaction on chain 137 (approvals).
---
VPN Requirement
All CLOB API requests are geo-blocked. The script auto-routes through sc-vpn.internal:8080 (default: Germany). Gamma API is NOT blocked.
Set region in .env: POLY_VPN_REGION=de
Contracts (Polygon)
| Contract | Address |
|---|---|
| CTF Exchange | 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E |
| CTF Exchange (neg-risk) | 0xC5d563A36AE78145C45a50134d48A1215220f80a |
| USDC | 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 |
⚠️ MANDATORY: User Confirmation Before Betting
NEVER place a bet without explicit user confirmation. Always: 1. Research & analyze → present findings 2. Suggest bet with R/R → user reviews 3. User requests changes → adjust 4. User explicitly confirms → ONLY THEN execute
Workflow: Link → Bet
1. Market Lookup
python3 skills/polymarket-trade/scripts/poly_research.py lookup "<polymarket_url>"2. Research
- Read resolution criteria, end date, sources
web_searchfor news, expert opinions, data- Assess market price vs estimated fair probability
- Present: overview, prices, key facts, probability estimate, edge
3. Orderbook & R/R
python3 skills/polymarket-trade/scripts/poly_research.py orderbook <token_id>
python3 skills/polymarket-trade/scripts/poly_research.py rr <token_id> <YES|NO> <size_usd>4. Present Suggested Bet (WAIT for confirmation)
📊 Suggested Bet:
Market: "Will X happen?"
Side: YES @ $0.35 (market: 35%, est: 55%)
Size: $20 → 57.14 tokens
Win: +$37.14 | Lose: -$20.00 | R/R: 1:1.865. Execute (ONLY after confirmation)
# a. Check balance
python3 skills/polymarket-trade/scripts/poly_research.py balance
# b. Prepare order (outputs domain/types/message/meta JSON)
python3 skills/polymarket-trade/scripts/poly_research.py prepare <token_id> BUY <price> <size> [neg_risk] [tick_size]
# c. Sign with wallet_sign_typed_data (primaryType: "Order")
# d. Post signed order
python3 -c "
import sys; sys.path.insert(0, '/data/workspace/skills/polymarket-trade/scripts')
from poly_research import post_signed_order
s, r = post_signed_order('<token_id>', '<signature>', <meta_dict>)
print(s, r)
"
# e. Verify
python3 skills/polymarket-trade/scripts/poly_research.py ordersOther Commands
python3 skills/polymarket-trade/scripts/poly_research.py search "query" # Find markets
python3 skills/polymarket-trade/scripts/poly_research.py positions # Current positions
python3 skills/polymarket-trade/scripts/poly_research.py trades # Recent trades
python3 skills/polymarket-trade/scripts/poly_research.py cancel <order_id> # Cancel order
python3 skills/polymarket-trade/scripts/poly_research.py cancel_all # Cancel allNotes
- Prices = probabilities: $0.55 = 55% implied, costs $0.55/token, pays $1 if correct
- tick_size: 0.01 or 0.001 (from market data — always check)
- neg_risk markets use CTF_EXCHANGE_NEG
- Orders are GTC by default, 0 bps maker fee
- Min order ~5 tokens (varies by market)
"""
Polymarket L1 Auth — derive API credentials from agent wallet via EIP-712.
Stores creds in workspace/.polymarket_creds.json
"""
import requests
import json
import time
import hmac
import hashlib
import base64
import os
import sys
CLOB_HOST = "https://clob.polymarket.com"
CHAIN_ID = 137
AGENT_ADDRESS = "0x3e880B128146d65368422B0bf4aB3757A010108E"
CREDS_FILE = "/data/workspace/.polymarket_creds.json"
def get_server_timestamp():
resp = requests.get(f"{CLOB_HOST}/time", timeout=10)
return int(resp.json()["time"])
def build_eip712_message(address, timestamp, nonce=0):
return {
"domain": {
"name": "ClobAuthDomain",
"version": "1",
"chainId": CHAIN_ID
},
"types": {
"ClobAuth": [
{"name": "address", "type": "address"},
{"name": "timestamp", "type": "string"},
{"name": "nonce", "type": "uint256"},
{"name": "message", "type": "string"}
]
},
"primaryType": "ClobAuth",
"message": {
"address": address,
"timestamp": str(timestamp),
"nonce": nonce,
"message": "This message attests that I control the given wallet"
}
}
def derive_creds(signature, address, timestamp, nonce=0):
headers = {
"POLY_ADDRESS": address,
"POLY_SIGNATURE": signature,
"POLY_TIMESTAMP": str(timestamp),
"POLY_NONCE": str(nonce),
"Content-Type": "application/json"
}
resp = requests.get(f"{CLOB_HOST}/auth/derive-api-key", headers=headers, timeout=15)
if resp.status_code == 200:
return resp.json()
# Try create instead
resp2 = requests.post(f"{CLOB_HOST}/auth/api-key", headers=headers, timeout=15)
resp2.raise_for_status()
return resp2.json()
if __name__ == "__main__":
# Step 1: Get server timestamp
ts = get_server_timestamp()
print(f"Server timestamp: {ts}")
# Step 2: Build EIP-712 payload
nonce = int(sys.argv[1]) if len(sys.argv) > 1 else 0
eip712 = build_eip712_message(AGENT_ADDRESS, ts, nonce)
print("\nEIP-712 payload to sign:")
print(json.dumps(eip712, indent=2))
print("\nPaste the signature from wallet_sign_typed_data below:")
sig = input("Signature: ").strip()
# Step 3: Derive credentials
print("\nDeriving API credentials...")
creds = derive_creds(sig, AGENT_ADDRESS, ts, nonce)
print(f"\nCredentials received:")
print(json.dumps(creds, indent=2))
# Step 4: Save to file
creds["address"] = AGENT_ADDRESS
creds["nonce"] = nonce
with open(CREDS_FILE, "w") as f:
json.dump(creds, f, indent=2)
print(f"\nSaved to {CREDS_FILE}")
"""
Polymarket CLOB client helper.
Handles L2 HMAC auth + common API calls.
"""
import time, hmac, hashlib, base64, json, requests
import os
BASE = "https://clob.polymarket.com"
GAMMA = "https://gamma-api.polymarket.com"
# Credentials from env
API_KEY = os.environ.get("POLY_API_KEY", "")
SECRET = os.environ.get("POLY_SECRET", "")
PASSPHRASE = os.environ.get("POLY_PASSPHRASE", "")
WALLET = os.environ.get("POLY_WALLET", "")
def build_hmac_sig(secret, timestamp, method, path, body=None):
base64_secret = base64.urlsafe_b64decode(secret)
message = str(timestamp) + str(method) + str(path)
if body:
message += str(body).replace("'", '"')
h = hmac.new(base64_secret, bytes(message, "utf-8"), hashlib.sha256)
return base64.urlsafe_b64encode(h.digest()).decode("utf-8")
def l2_headers(method, path, body=None):
ts = int(time.time())
sig = build_hmac_sig(SECRET, ts, method.upper(), path, body)
return {
"POLY_ADDRESS": WALLET,
"POLY_SIGNATURE": sig,
"POLY_TIMESTAMP": str(ts),
"POLY_API_KEY": API_KEY,
"POLY_PASSPHRASE": PASSPHRASE,
"Content-Type": "application/json",
}
def get_balance():
h = l2_headers("GET", "/balance-allowance")
r = requests.get(f"{BASE}/balance-allowance", headers=h, params={"asset_type": "COLLATERAL"})
r.raise_for_status()
return r.json()
def get_open_orders():
h = l2_headers("GET", "/data/orders")
r = requests.get(f"{BASE}/data/orders", headers=h)
r.raise_for_status()
return r.json()
def get_trades(limit=20):
h = l2_headers("GET", "/data/trades")
r = requests.get(f"{BASE}/data/trades", headers=h, params={"limit": limit})
r.raise_for_status()
return r.json()
def cancel_order(order_id):
body = json.dumps({"orderID": order_id})
h = l2_headers("DELETE", "/order", body)
r = requests.delete(f"{BASE}/order", headers=h, data=body)
r.raise_for_status()
return r.json()
def cancel_all():
h = l2_headers("DELETE", "/cancel-all")
r = requests.delete(f"{BASE}/cancel-all", headers=h)
r.raise_for_status()
return r.json()
def get_orderbook(token_id):
r = requests.get(f"{BASE}/book", params={"token_id": token_id})
r.raise_for_status()
return r.json()
def get_market(condition_id):
r = requests.get(f"{BASE}/markets/{condition_id}")
r.raise_for_status()
return r.json()
def search_markets(query, limit=10):
"""Search via Gamma API"""
r = requests.get(f"{GAMMA}/markets", params={
"q": query, "limit": limit, "active": True, "closed": False
})
r.raise_for_status()
return r.json()
def get_sampling_markets(limit=20):
r = requests.get(f"{BASE}/sampling-simplified-markets", params={"limit": limit})
r.raise_for_status()
return r.json()
if __name__ == "__main__":
import sys
cmd = sys.argv[1] if len(sys.argv) > 1 else "balance"
if cmd == "balance":
data = get_balance()
print(f"USDC Balance: {float(data['balance'])/1e6:.2f} USDC" if int(data['balance']) > 1e4 else f"USDC Balance on Polymarket: ${float(data['balance'])/1e6:.6f}")
print(f"Raw: {data}")
elif cmd == "orders":
data = get_open_orders()
print(f"Open orders: {data['count']}")
for o in data.get('data', []):
print(f" {o['id']}: {o['side']} {o['size_matched']}/{o['original_size']} @ {o['price']} [{o['asset_id'][:20]}...]")
elif cmd == "trades":
data = get_trades()
print(f"Recent trades: {len(data.get('data', []))}")
for t in data.get('data', [])[:5]:
print(f" {t}")
elif cmd == "markets":
q = sys.argv[2] if len(sys.argv) > 2 else ""
if q:
data = search_markets(q)
for m in data[:5]:
print(f" [{m.get('conditionId','?')[:20]}...] {m.get('question','?')}")
for t in m.get('tokens', [{'outcome':'Yes','price':0},{'outcome':'No','price':0}]):
print(f" {t.get('outcome')}: {t.get('price')}")
else:
data = get_sampling_markets()
for m in data.get('data', [])[:5]:
print(f" [{m['condition_id'][:20]}...] tokens: {len(m.get('tokens',[]))}")
"""
Polymarket order helpers. Build/post orders via EIP-712 + CLOB API.
"""
import json, time, requests, os, random
from py_clob_client.signing.hmac import build_hmac_signature
BASE = "https://clob.polymarket.com"
GAMMA = "https://gamma-api.polymarket.com"
CTF_EXCHANGE = "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E"
CTF_EXCHANGE_NEG = "0xC5d563A36AE78145C45a50134d48A1215220f80a"
CHAIN_ID = 137
EOA = 0
def _load_env():
env = {}
try:
with open(os.path.expanduser("/data/workspace/.env")) as f:
for line in f:
line = line.strip()
if line and "=" in line and not line.startswith("#"):
k, v = line.split("=", 1)
env[k.strip()] = v.strip()
except Exception:
pass
return env
_env = _load_env()
API_KEY = os.environ.get("POLY_API_KEY", _env.get("POLY_API_KEY", ""))
SECRET = os.environ.get("POLY_SECRET", _env.get("POLY_SECRET", ""))
PASSPHRASE = os.environ.get("POLY_PASSPHRASE", _env.get("POLY_PASSPHRASE", ""))
WALLET = os.environ.get("POLY_WALLET", _env.get("POLY_WALLET", ""))
def l2h(method, path, body=None):
ts = int(time.time())
sig = build_hmac_signature(SECRET, ts, method.upper(), path, body)
return {"POLY_ADDRESS": WALLET, "POLY_SIGNATURE": sig,
"POLY_TIMESTAMP": str(ts), "POLY_API_KEY": API_KEY,
"POLY_PASSPHRASE": PASSPHRASE, "Content-Type": "application/json"}
def search_markets(query, limit=5):
r = requests.get(f"{GAMMA}/markets", params={"q": query, "limit": limit, "active": "true", "closed": "false"})
r.raise_for_status(); return r.json()
def get_price(token_id, side="BUY"):
r = requests.get(f"{BASE}/price", params={"token_id": token_id, "side": side})
return float(r.json().get("price", 0)) if r.status_code == 200 else None
def get_orderbook(token_id):
r = requests.get(f"{BASE}/book", params={"token_id": token_id})
r.raise_for_status(); return r.json()
def get_market(condition_id):
r = requests.get(f"{BASE}/markets/{condition_id}")
r.raise_for_status(); return r.json()
def get_balance():
r = requests.get(f"{BASE}/balance-allowance", headers=l2h("GET", "/balance-allowance"), params={"asset_type": "COLLATERAL"})
r.raise_for_status(); return r.json()
def get_open_orders():
r = requests.get(f"{BASE}/data/orders", headers=l2h("GET", "/data/orders"))
r.raise_for_status(); return r.json()
def cancel_order(order_id):
body = json.dumps({"orderID": order_id})
r = requests.delete(f"{BASE}/order", headers=l2h("DELETE", "/order", body), data=body)
return r.status_code, r.json()
def cancel_all():
r = requests.delete(f"{BASE}/cancel-all", headers=l2h("DELETE", "/cancel-all"))
return r.status_code, r.json()
def build_order_payload(token_id, side, price, size, neg_risk=False):
"""
Returns (domain, types, message, meta) for wallet_sign_typed_data.
meta has salt/amounts needed to post after signing.
"""
exchange = CTF_EXCHANGE_NEG if neg_risk else CTF_EXCHANGE
price = round(price, 4); size = round(size, 2)
if side == "BUY":
maker_amount = int(price * size * 1_000_000)
taker_amount = int(size * 1_000_000)
order_side = 0
else:
maker_amount = int(size * 1_000_000)
taker_amount = int(price * size * 1_000_000)
order_side = 1
salt = random.randint(1, 2**64)
domain = {"name": "Polymarket CTF Exchange", "version": "1", "chainId": CHAIN_ID, "verifyingContract": exchange}
types = {"Order": [
{"name": "salt", "type": "uint256"},
{"name": "maker", "type": "address"},
{"name": "signer", "type": "address"},
{"name": "taker", "type": "address"},
{"name": "tokenId", "type": "uint256"},
{"name": "makerAmount", "type": "uint256"},
{"name": "takerAmount", "type": "uint256"},
{"name": "expiration", "type": "uint256"},
{"name": "nonce", "type": "uint256"},
{"name": "feeRateBps", "type": "uint256"},
{"name": "side", "type": "uint8"},
{"name": "signatureType", "type": "uint8"},
]}
message = {
"salt": str(salt), "maker": WALLET, "signer": WALLET,
"taker": "0x0000000000000000000000000000000000000000",
"tokenId": str(token_id), "makerAmount": str(maker_amount),
"takerAmount": str(taker_amount), "expiration": "0",
"nonce": "0", "feeRateBps": "0", "side": order_side, "signatureType": EOA,
}
meta = {"salt": salt, "maker_amount": maker_amount, "taker_amount": taker_amount,
"order_side": order_side, "exchange": exchange}
return domain, types, message, meta
def post_signed_order(token_id, signature, meta):
"""Post a signed order. meta comes from build_order_payload."""
order_body = {"order": {
"salt": meta["salt"], "maker": WALLET, "signer": WALLET,
"taker": "0x0000000000000000000000000000000000000000",
"tokenId": str(token_id), "makerAmount": str(meta["maker_amount"]),
"takerAmount": str(meta["taker_amount"]), "expiration": "0",
"nonce": "0", "feeRateBps": "0", "side": meta["order_side"],
"signatureType": EOA, "signature": signature,
}, "owner": WALLET, "orderType": "GTC"}
body_str = json.dumps(order_body)
headers = l2h("POST", "/order", body_str)
r = requests.post(f"{BASE}/order", headers=headers, data=body_str)
return r.status_code, r.json()
if __name__ == "__main__":
import sys
cmd = sys.argv[1] if len(sys.argv) > 1 else "help"
if cmd == "search":
q = " ".join(sys.argv[2:])
for m in search_markets(q)[:5]:
print(f"\n{m.get('question', '?')}")
print(f" condition_id: {m.get('conditionId', '?')}")
print(f" volume: ${float(m.get('volume', 0)):,.0f}")
for t in m.get('tokens', []):
print(f" {t.get('outcome')}: {t.get('price', '?')}")
elif cmd == "balance":
d = get_balance()
print(f"Balance: ${float(d['balance'])/1e6:.4f} USDC")
elif cmd == "orders":
d = get_open_orders()
print(f"Open orders: {d['count']}")
for o in d.get('data', []):
print(f" {o['id'][:12]}... {o['side']} @ {o['price']} size={o['original_size']}")
elif cmd == "prepare":
token_id, side, price, size = sys.argv[2], sys.argv[3].upper(), float(sys.argv[4]), float(sys.argv[5])
neg_risk = len(sys.argv) > 6 and "neg" in sys.argv[6]
domain, types, message, meta = build_order_payload(token_id, side, price, size, neg_risk)
print(json.dumps({"domain": domain, "types": types, "primaryType": "Order", "message": message, "meta": meta}, indent=2))
else:
print("Commands: search <query> | balance | orders | prepare <token_id> <BUY|SELL> <price> <size>")
"""
Polymarket Research & Trading Script.
Market lookup, orderbook analysis, R/R calculation, order execution.
All CLOB requests route through VPN to bypass geo-blocking.
Usage:
python3 poly_research.py lookup <url_or_slug>
python3 poly_research.py search <query>
python3 poly_research.py orderbook <token_id>
python3 poly_research.py rr <token_id> <YES|NO> <size_usd>
python3 poly_research.py prepare <token_id> <BUY|SELL> <price> <size> [neg_risk] [tick_size]
python3 poly_research.py post <token_id> <signature> <meta_json>
python3 poly_research.py balance
python3 poly_research.py orders
python3 poly_research.py positions
python3 poly_research.py trades
python3 poly_research.py cancel <order_id>
python3 poly_research.py cancel_all
"""
import json, time, sys, os, re, random, hashlib, hmac, base64
import requests as _requests
# VPN proxy for CLOB (geo-blocked)
VPN_REGION = os.environ.get("POLY_VPN_REGION", "ch")
VPN_PROXY = {
"https": f"http://{VPN_REGION}:x@sc-vpn.internal:8080",
"http": f"http://{VPN_REGION}:x@sc-vpn.internal:8080",
}
BASE = "https://clob.polymarket.com"
GAMMA = "https://gamma-api.polymarket.com"
CTF_EXCHANGE = "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E"
CTF_EXCHANGE_NEG = "0xC5d563A36AE78145C45a50134d48A1215220f80a"
CHAIN_ID = 137
EOA = 0 # signature_type 0: raw EOA (direct trading, no proxy wallet needed)
GNOSIS_SAFE = 2 # signature_type 2: proxy/Safe wallet (standard for Polymarket.com web users)
SIG_TYPE = EOA # We use EOA mode — wallet is both signer and maker
def _load_env():
env = {}
try:
with open("/data/workspace/.env") as f:
for line in f:
line = line.strip()
if line and "=" in line and not line.startswith("#"):
k, v = line.split("=", 1)
env[k.strip()] = v.strip()
except Exception:
pass
return env
_env = _load_env()
API_KEY = os.environ.get("POLY_API_KEY", _env.get("POLY_API_KEY", ""))
SECRET = os.environ.get("POLY_SECRET", _env.get("POLY_SECRET", ""))
PASSPHRASE = os.environ.get("POLY_PASSPHRASE", _env.get("POLY_PASSPHRASE", ""))
WALLET = os.environ.get("POLY_WALLET", _env.get("POLY_WALLET", "")) # EOA wallet — is both signer AND maker in EOA mode
PROXY_WALLET = os.environ.get("POLY_PROXY_WALLET", _env.get("POLY_PROXY_WALLET", "")) # Only used in Safe mode (unused in EOA mode)
MAKER_ADDR = WALLET # EOA mode: maker = signer = wallet
# HTTP helpers with VPN
def clob_get(url, **kw):
kw.setdefault("proxies", VPN_PROXY); kw.setdefault("timeout", 30)
return _requests.get(url, **kw)
def clob_post(url, **kw):
kw.setdefault("proxies", VPN_PROXY); kw.setdefault("timeout", 30)
return _requests.post(url, **kw)
def clob_delete(url, **kw):
kw.setdefault("proxies", VPN_PROXY); kw.setdefault("timeout", 30)
return _requests.delete(url, **kw)
def gamma_get(url, **kw):
kw.setdefault("timeout", 30)
return _requests.get(url, **kw)
# HMAC L2 auth
def _hmac_sig(secret, timestamp, method, path, body=None):
secret_bytes = base64.urlsafe_b64decode(secret)
message = str(timestamp) + method.upper() + path
if body:
message += body
sig = hmac.new(secret_bytes, message.encode(), hashlib.sha256)
return base64.urlsafe_b64encode(sig.digest()).decode()
def l2h(method, path, body=None):
ts = int(time.time())
sig = _hmac_sig(SECRET, ts, method.upper(), path, body)
return {"POLY_ADDRESS": WALLET, "POLY_SIGNATURE": sig,
"POLY_TIMESTAMP": str(ts), "POLY_API_KEY": API_KEY,
"POLY_PASSPHRASE": PASSPHRASE, "Content-Type": "application/json"}
# URL parsing
def parse_polymarket_url(url_or_slug):
url_or_slug = url_or_slug.strip()
m = re.match(r'https?://(?:www\.)?polymarket\.com/event/([^/?#]+)(?:/([^/?#]+))?', url_or_slug)
if m:
return m.group(1), m.group(2)
parts = url_or_slug.strip("/").split("/")
return (parts[0], parts[1] if len(parts) > 1 else None)
# Gamma API
def lookup_event(slug):
r = gamma_get(f"{GAMMA}/events", params={"slug": slug, "limit": 1})
r.raise_for_status(); data = r.json()
return data[0] if data else None
def lookup_market_by_slug(slug):
r = gamma_get(f"{GAMMA}/markets", params={"slug": slug, "limit": 1})
r.raise_for_status(); data = r.json()
return data[0] if data else None
def search_markets(query, limit=5):
r = gamma_get(f"{GAMMA}/markets", params={"q": query, "limit": limit, "active": "true", "closed": "false"})
r.raise_for_status(); return r.json()
# CLOB API
def get_price(token_id, side="BUY"):
r = clob_get(f"{BASE}/price", params={"token_id": token_id, "side": side})
return float(r.json().get("price", 0)) if r.status_code == 200 else None
def get_midpoint(token_id):
r = clob_get(f"{BASE}/midpoint", params={"token_id": token_id})
return float(r.json().get("mid", 0)) if r.status_code == 200 else None
def get_orderbook(token_id):
r = clob_get(f"{BASE}/book", params={"token_id": token_id})
r.raise_for_status(); return r.json()
def get_balance():
r = clob_get(f"{BASE}/balance-allowance", headers=l2h("GET", "/balance-allowance"),
params={"asset_type": "COLLATERAL", "signature_type": SIG_TYPE})
r.raise_for_status(); return r.json()
def get_open_orders():
r = clob_get(f"{BASE}/data/orders", headers=l2h("GET", "/data/orders"))
r.raise_for_status(); return r.json()
def get_trades(limit=20):
r = clob_get(f"{BASE}/data/trades", headers=l2h("GET", "/data/trades"), params={"limit": limit})
r.raise_for_status(); return r.json()
def get_positions():
r = clob_get(f"{BASE}/data/positions", headers=l2h("GET", "/data/positions"))
r.raise_for_status(); return r.json()
def cancel_order(order_id):
body = json.dumps({"orderID": order_id})
r = clob_delete(f"{BASE}/order", headers=l2h("DELETE", "/order", body), data=body)
return r.status_code, r.json()
def cancel_all():
r = clob_delete(f"{BASE}/cancel-all", headers=l2h("DELETE", "/cancel-all"))
return r.status_code, r.json()
# Orderbook analysis
def analyze_orderbook(token_id):
book = get_orderbook(token_id)
mid = get_midpoint(token_id)
bids = sorted(book.get("bids", []), key=lambda x: float(x["price"]), reverse=True)
asks = sorted(book.get("asks", []), key=lambda x: float(x["price"]))
best_bid = float(bids[0]["price"]) if bids else 0
best_ask = float(asks[0]["price"]) if asks else 1
spread = best_ask - best_bid
spread_pct = (spread / mid * 100) if mid else 0
def depth_within(levels, mp, band, side):
total = 0
for lvl in levels:
p, s = float(lvl["price"]), float(lvl["size"])
if side == "bid" and p >= mp - band: total += p * s
elif side == "ask" and p <= mp + band: total += p * s
return total
mp = mid or (best_bid + best_ask) / 2
return {
"token_id": token_id, "best_bid": best_bid, "best_ask": best_ask,
"midpoint": mid, "spread": round(spread, 4), "spread_pct": round(spread_pct, 2),
"bid_levels": len(bids), "ask_levels": len(asks),
"bid_depth_2c": round(depth_within(bids, mp, 0.02, "bid"), 2),
"ask_depth_2c": round(depth_within(asks, mp, 0.02, "ask"), 2),
"bid_depth_5c": round(depth_within(bids, mp, 0.05, "bid"), 2),
"ask_depth_5c": round(depth_within(asks, mp, 0.05, "ask"), 2),
"top_bids": [{"price": b["price"], "size": b["size"]} for b in bids[:5]],
"top_asks": [{"price": a["price"], "size": a["size"]} for a in asks[:5]],
}
# R/R analysis
def rr_analysis(token_id, side, size_usd):
ob = analyze_orderbook(token_id)
if side.upper() == "YES":
entry_price = ob["best_ask"]
else:
entry_price = 1 - ob["best_bid"]
if entry_price <= 0 or entry_price >= 1:
return {"error": f"Invalid entry price: {entry_price}"}
tokens = size_usd / entry_price
profit_if_win = tokens - size_usd
rr_ratio = profit_if_win / size_usd if size_usd > 0 else 0
return {
"side": side.upper(), "entry_price": round(entry_price, 4),
"implied_probability": f"{entry_price*100:.1f}%",
"size_usd": round(size_usd, 2), "tokens": round(tokens, 2),
"profit_if_win": round(profit_if_win, 2), "loss_if_lose": round(size_usd, 2),
"risk_reward_ratio": f"1:{round(rr_ratio, 2)}",
"breakeven_probability": f"{entry_price*100:.1f}%",
"orderbook": {"spread": ob["spread"], "spread_pct": ob["spread_pct"],
"bid_depth_5c": ob["bid_depth_5c"], "ask_depth_5c": ob["ask_depth_5c"]},
}
# Order building
def build_order_payload(token_id, side, price, size, neg_risk=False, tick_size="0.01"):
exchange = CTF_EXCHANGE_NEG if neg_risk else CTF_EXCHANGE
tick = float(tick_size)
price = round(round(price / tick) * tick, 4)
size = round(size, 2)
if side.upper() == "BUY":
taker_amount = int(round(size, 2) * 1_000_000)
raw_maker = round(round(size, 2) * round(price, 2), 4)
maker_amount = round(raw_maker * 1_000_000)
order_side = 0
else:
maker_amount = int(size * 1_000_000)
taker_amount = int(price * size * 1_000_000)
order_side = 1
salt = round(time.time() * random.random())
domain = {"name": "Polymarket CTF Exchange", "version": "1", "chainId": CHAIN_ID, "verifyingContract": exchange}
types = {"Order": [
{"name": "salt", "type": "uint256"}, {"name": "maker", "type": "address"},
{"name": "signer", "type": "address"}, {"name": "taker", "type": "address"},
{"name": "tokenId", "type": "uint256"}, {"name": "makerAmount", "type": "uint256"},
{"name": "takerAmount", "type": "uint256"}, {"name": "expiration", "type": "uint256"},
{"name": "nonce", "type": "uint256"}, {"name": "feeRateBps", "type": "uint256"},
{"name": "side", "type": "uint8"}, {"name": "signatureType", "type": "uint8"},
]}
message = {
"salt": str(salt), "maker": MAKER_ADDR, "signer": WALLET,
"taker": "0x0000000000000000000000000000000000000000",
"tokenId": str(token_id), "makerAmount": str(maker_amount),
"takerAmount": str(taker_amount), "expiration": "0",
"nonce": "0", "feeRateBps": "0", "side": order_side, "signatureType": SIG_TYPE,
}
meta = {"salt": salt, "maker_amount": maker_amount, "taker_amount": taker_amount, "maker": MAKER_ADDR, "signer": WALLET,
"order_side": order_side, "exchange": exchange, "price": price, "size": size,
"side_str": side.upper(), "neg_risk": neg_risk}
return domain, types, message, meta
# Post signed order
def post_signed_order(token_id, signature, meta):
side_str = "BUY" if meta["order_side"] == 0 else "SELL"
order_body = {"order": {
"salt": meta["salt"], "maker": MAKER_ADDR, "signer": WALLET,
"taker": "0x0000000000000000000000000000000000000000",
"tokenId": str(token_id), "makerAmount": str(meta["maker_amount"]),
"takerAmount": str(meta["taker_amount"]), "expiration": "0",
"nonce": "0", "feeRateBps": "0", "side": side_str,
"signatureType": SIG_TYPE, "signature": signature,
}, "owner": API_KEY, "orderType": "GTC"}
body_str = json.dumps(order_body, separators=(",", ":"))
headers = l2h("POST", "/order", body_str)
r = clob_post(f"{BASE}/order", headers=headers, data=body_str)
return r.status_code, r.json()
# Full market lookup from URL
def full_lookup(url_or_slug):
event_slug, market_slug = parse_polymarket_url(url_or_slug)
event = lookup_event(event_slug)
if not event:
market = lookup_market_by_slug(event_slug)
if market:
return {"type": "single_market", "market": _enrich_market(market)}
return {"error": f"Not found: {event_slug}"}
result = {
"type": "event", "title": event.get("title"), "slug": event.get("slug"),
"description": event.get("description", "")[:500],
"end_date": event.get("endDate"), "volume": event.get("volume"),
"neg_risk": event.get("negRisk", False),
"neg_risk_market_id": event.get("negRiskMarketID"),
"markets": [],
}
for m in event.get("markets", []):
enriched = _enrich_market(m)
result["markets"].append(enriched)
if market_slug and m.get("slug") == market_slug:
result["focused_market"] = enriched
return result
def _enrich_market(m):
outcomes = json.loads(m.get("outcomes", "[]")) if isinstance(m.get("outcomes"), str) else m.get("outcomes", [])
prices = json.loads(m.get("outcomePrices", "[]")) if isinstance(m.get("outcomePrices"), str) else m.get("outcomePrices", [])
token_ids = json.loads(m.get("clobTokenIds", "[]")) if isinstance(m.get("clobTokenIds"), str) else m.get("clobTokenIds", [])
md = {
"question": m.get("question"), "slug": m.get("slug"),
"condition_id": m.get("conditionId"),
"description": (m.get("description") or "")[:300],
"end_date": m.get("endDate"),
"volume": float(m.get("volume", 0) or 0),
"active": m.get("active"), "closed": m.get("closed"),
"accepting_orders": m.get("acceptingOrders"),
"tick_size": m.get("orderPriceMinTickSize", 0.01),
"min_order_size": m.get("orderMinSize", 5),
"outcomes": [],
}
for i, outcome in enumerate(outcomes):
entry = {"outcome": outcome,
"gamma_price": float(prices[i]) if i < len(prices) else None,
"token_id": token_ids[i] if i < len(token_ids) else None}
if entry["token_id"]:
try:
entry["buy_price"] = get_price(entry["token_id"], "BUY")
entry["sell_price"] = get_price(entry["token_id"], "SELL")
entry["midpoint"] = get_midpoint(entry["token_id"])
except Exception:
pass
md["outcomes"].append(entry)
return md
# CLI
if __name__ == "__main__":
cmd = sys.argv[1] if len(sys.argv) > 1 else "help"
if cmd == "lookup":
url = sys.argv[2] if len(sys.argv) > 2 else ""
if not url: print("Usage: poly_research.py lookup <url_or_slug>"); sys.exit(1)
print(json.dumps(full_lookup(url), indent=2, default=str))
elif cmd == "search":
q = " ".join(sys.argv[2:])
if not q: print("Usage: poly_research.py search <query>"); sys.exit(1)
for m in search_markets(q)[:5]:
print(f"\n{m.get('question', '?')}")
print(f" slug: {m.get('slug', '?')} vol: ${float(m.get('volume',0)):,.0f}")
prices = json.loads(m.get("outcomePrices", "[]"))
outcomes = json.loads(m.get("outcomes", "[]"))
for i, o in enumerate(outcomes):
print(f" {o}: {prices[i] if i < len(prices) else '?'}")
elif cmd == "orderbook":
tid = sys.argv[2] if len(sys.argv) > 2 else ""
if not tid: print("Usage: poly_research.py orderbook <token_id>"); sys.exit(1)
print(json.dumps(analyze_orderbook(tid), indent=2))
elif cmd == "rr":
if len(sys.argv) < 5: print("Usage: poly_research.py rr <token_id> <YES|NO> <size_usd>"); sys.exit(1)
print(json.dumps(rr_analysis(sys.argv[2], sys.argv[3], float(sys.argv[4])), indent=2))
elif cmd == "prepare":
if len(sys.argv) < 6: print("Usage: poly_research.py prepare <token_id> <BUY|SELL> <price> <size> [neg_risk] [tick]"); sys.exit(1)
neg = len(sys.argv) > 6 and sys.argv[6].lower() in ("true", "yes", "1", "neg", "neg_risk")
tick = sys.argv[7] if len(sys.argv) > 7 else "0.01"
d, t, m, meta = build_order_payload(sys.argv[2], sys.argv[3].upper(), float(sys.argv[4]), float(sys.argv[5]), neg, tick)
print(json.dumps({"domain": d, "types": t, "primaryType": "Order", "message": m, "meta": meta}, indent=2))
elif cmd == "balance":
d = get_balance()
print(f"Balance: ${float(d.get('balance',0))/1e6:.4f} Allowance: ${float(d.get('allowance',0))/1e6:.4f}")
elif cmd == "orders":
d = get_open_orders()
print(f"Open: {d.get('count',0)}")
for o in d.get("data", []): print(f" {o['id'][:16]}... {o['side']} @ {o['price']} sz={o['original_size']}")
elif cmd == "positions":
print(json.dumps(get_positions(), indent=2))
elif cmd == "trades":
d = get_trades(); trades = d.get("data", []) if isinstance(d, dict) else d
for t in trades[:10]: print(json.dumps(t))
elif cmd == "cancel":
if len(sys.argv) < 3: print("Need order_id"); sys.exit(1)
s, r = cancel_order(sys.argv[2]); print(f"{s}: {json.dumps(r)}")
elif cmd == "cancel_all":
s, r = cancel_all(); print(f"{s}: {json.dumps(r)}")
elif cmd == "post":
if len(sys.argv) < 5: print("Usage: poly_research.py post <token_id> <signature> <meta_json>"); sys.exit(1)
meta = json.loads(sys.argv[4])
s, r = post_signed_order(sys.argv[2], sys.argv[3], meta)
print(f"{s}: {json.dumps(r, indent=2)}")
else:
print(__doc__)
"""
Polymarket Trading Script — full flow:
- L1 EIP-712 auth → get API creds
- L2 HMAC signing for trading requests
- Place/cancel orders, check positions
Usage:
python3 polymarket.py auth # derive/create API creds
python3 polymarket.py approve # approve USDC for CTF exchange
python3 polymarket.py order <token_id> <side> <price> <size> # place order
python3 polymarket.py orders # list open orders
python3 polymarket.py cancel <order_id> # cancel order
python3 polymarket.py balance # check USDC allowance/balance
"""
import requests
import json
import time
import hmac
import hashlib
import base64
import os
import sys
import argparse
CLOB_HOST = "https://clob.polymarket.com"
CHAIN_ID = 137
AGENT_ADDRESS = "0x3e880B128146d65368422B0bf4aB3757A010108E"
CREDS_FILE = "/data/workspace/.polymarket_creds.json"
# Polygon contract addresses
USDC_ADDRESS = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
CTF_EXCHANGE = "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E"
def load_creds():
if not os.path.exists(CREDS_FILE):
print("❌ No credentials found. Run: python3 polymarket.py auth")
sys.exit(1)
with open(CREDS_FILE) as f:
return json.load(f)
def get_server_timestamp():
resp = requests.get(f"{CLOB_HOST}/time", timeout=10)
return int(resp.json()["time"])
def l2_sign(method, request_path, body, timestamp, secret):
"""Generate HMAC-SHA256 L2 signature"""
body_str = json.dumps(body, separators=(',', ':')) if body else ""
message = f"{timestamp}{method}{request_path}{body_str}"
secret_bytes = base64.b64decode(secret)
sig = hmac.new(secret_bytes, message.encode(), hashlib.sha256).digest()
return base64.b64encode(sig).decode()
def l2_headers(method, path, body, creds):
ts = str(get_server_timestamp())
sig = l2_sign(method, path, body, ts, creds["secret"])
return {
"POLY_ADDRESS": AGENT_ADDRESS,
"POLY_SIGNATURE": sig,
"POLY_TIMESTAMP": ts,
"POLY_API_KEY": creds["apiKey"],
"POLY_PASSPHRASE": creds["passphrase"],
"Content-Type": "application/json"
}
def get_open_orders():
creds = load_creds()
path = "/orders"
headers = l2_headers("GET", path, None, creds)
resp = requests.get(f"{CLOB_HOST}{path}", headers=headers, timeout=15)
resp.raise_for_status()
return resp.json()
def get_balance_allowance():
creds = load_creds()
path = "/balance-allowance"
params = {"asset_type": "COLLATERAL", "signature_type": 0}
headers = l2_headers("GET", path, None, creds)
resp = requests.get(f"{CLOB_HOST}{path}", headers=headers, params=params, timeout=15)
resp.raise_for_status()
return resp.json()
def cancel_order(order_id):
creds = load_creds()
path = "/order"
body = {"orderID": order_id}
headers = l2_headers("DELETE", path, body, creds)
resp = requests.delete(f"{CLOB_HOST}{path}", headers=headers, json=body, timeout=15)
resp.raise_for_status()
return resp.json()
def get_market_info(token_id):
"""Get tick size and neg_risk for a token"""
resp = requests.get(f"{CLOB_HOST}/markets", params={"token_id": token_id}, timeout=10)
if resp.status_code == 200 and resp.json():
market = resp.json()[0] if isinstance(resp.json(), list) else resp.json()
return {
"tick_size": market.get("minimum_tick_size", "0.01"),
"neg_risk": market.get("neg_risk", False)
}
return {"tick_size": "0.01", "neg_risk": False}
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("action", choices=["auth", "approve", "order", "orders", "cancel", "balance", "market"])
parser.add_argument("args", nargs="*")
args = parser.parse_args()
if args.action == "orders":
orders = get_open_orders()
if not orders:
print("No open orders.")
else:
for o in orders:
print(f"ID: {o.get('id')} | {o.get('side')} {o.get('size_matched','?')}/{o.get('original_size','?')} @ {o.get('price')} | Status: {o.get('status')}")
elif args.action == "balance":
bal = get_balance_allowance()
print(json.dumps(bal, indent=2))
elif args.action == "cancel":
if not args.args:
print("Usage: polymarket.py cancel <order_id>")
sys.exit(1)
result = cancel_order(args.args[0])
print(json.dumps(result, indent=2))
elif args.action == "market":
if not args.args:
print("Usage: polymarket.py market <token_id>")
sys.exit(1)
info = get_market_info(args.args[0])
print(json.dumps(info, indent=2))
elif args.action == "auth":
print("Auth flow requires EIP-712 signing via tool call.")
print("Use the agent's auth workflow instead.")
elif args.action == "approve":
print("USDC approval requires on-chain transaction via agent wallet tools.")
print("Use: wallet_transfer with CTF Exchange approval calldata.")
elif args.action == "order":
if len(args.args) < 4:
print("Usage: polymarket.py order <token_id> <BUY|SELL> <price> <size>")
sys.exit(1)
token_id, side, price, size = args.args[0], args.args[1].upper(), float(args.args[2]), float(args.args[3])
print(f"Order params: {side} {size} USDC of token {token_id} @ {price}")
print("Order placement requires signed order via py_clob_client — use agent workflow.")