
Polymarket
- 176 installs
- 21 repo stars
- Updated August 3, 2026
- starchild-ai-agent/official-skills
Use polymarket for development tasks
About
polymarket: A skill for development. This provides functionality for development workflows.
- polymarket
Polymarket by the numbers
- 176 all-time installs (skills.sh)
- +7 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #2,240 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/starchild-ai-agent/official-skills --skill polymarketAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 176 |
|---|---|
| repo stars | ★ 21 |
| Last updated | August 3, 2026 |
| Repository | starchild-ai-agent/official-skills ↗ |
What it does
Use polymarket for development tasks
Files
Polymarket Trading (CLOB V2)
Script-first. Every workflow is one bash call + at most one wallet_sign_typed_data (which can't be scripted because it's a wallet RPC). Verified live: see Changelog v5.1.0.
---
TL;DR — Trade in 3 steps
# 1. One-time setup (idempotent — safe to re-run)
python3 scripts/setup.py --all 10 # wrap 10 USDC.e -> pUSD + all approvals
# 2. Find a market
python3 scripts/search.py "trump" --limit 3 # returns token_ids
# 3. Place order
python3 scripts/prepare_order.py <token_id> BUY <price> <size>
# → sign /tmp/poly_order.json via wallet_sign_typed_data
python3 scripts/post_order.py <signature>All scripts live in skills/polymarket/scripts/ and assume working dir is the skill folder.
---
Account prerequisites (one-time, on-chain)
CLOB V2 settles in pUSD (an ERC-20 USDC wrapper), NOT raw USDC.e. Before the first order, the EOA must:
1. Hold USDC.e on Polygon (any amount ≥ what you want to trade). 2. Wrap USDC.e → pUSD via CollateralOnramp.wrap(). 3. approve(pUSD, spender, MAX) for the 3 V2 exchange spenders. 4. setApprovalForAll(CTF, spender, true) for the same 3 spenders (needed for SELL/redemption).
scripts/setup.py does all of this and is idempotent: it reads on-chain state and skips anything already done. Gas is sponsored by the Privy/Alchemy paymaster — user pays 0 MATIC.
python3 scripts/setup.py # dry-run: show current state + next step
python3 scripts/setup.py --all 10 # wrap 10 USDC.e + approve everything
python3 scripts/setup.py --wrap 50 # wrap more later
python3 scripts/setup.py --approve # re-issue approvals only---
Scripts
| Script | Purpose | Tool calls |
|---|---|---|
setup.py | One-time wrap + approvals (idempotent) | 0–8 wallet_transfer |
search.py | Find events/markets, returns token_ids + live prices | 0 |
status.py | Balance + positions + open orders + recent trades | 0 |
prepare_order.py | Fetch orderbook + build EIP-712 payload | 0 |
post_order.py | Submit signed order, verify fill | 0 |
cancel.py | Cancel one order (--id) or all (--all) | 0 |
close_positions.py | Build SELL orders for all positions | 0 |
auth.py | Check / derive CLOB API key from wallet | 0–1 sign |
Search
python3 scripts/search.py "ceasefire" --limit 3- Use short keywords (
trump,btc,ceasefire), not full literal questions — long queries often return empty. - Output JSON includes
outcomes[i].token_id(YES = index 0, NO = index 1) and current price.
Place an order — full flow
# 1. Prepare: fetches market info + orderbook, writes /tmp/poly_order.json
python3 scripts/prepare_order.py 7892825...50228 BUY 0.65 10
# 2. Sign (Python in agent runtime, ONE tool call)
# from core.skill_tools import wallet
# p = json.load(open('/tmp/poly_order.json'))
# sig = wallet.wallet_sign_typed_data(
# domain=p['domain'], types=p['types'],
# primaryType=p['primaryType'], message=p['message']
# )['signature']
# 3. Post: submits and prints order ID + fill status + tx hash
python3 scripts/post_order.py 0xSIGNATUREClose one or all positions
python3 scripts/close_positions.py # all positions
python3 scripts/close_positions.py --token_id X # one position
# → writes one /tmp/poly_close_N.json per position; sign each + post---
YES / NO & orderbook
Every binary market has two complementary tokens: YES + NO ≈ $1.00.
| Bet | Action | Use | Buy price |
|---|---|---|---|
| Event happens | BUY YES | outcomes[0].token_id | YES price |
| Event won't happen | BUY NO | outcomes[1].token_id | NO price |
Always check the book for the token you intend to buy:
- BUY → entry ≈
best_ask - SELL → exit ≈
best_bid - If you see 0.01 / 0.99, you're looking at the wrong token's book.
---
Order rules (CLOB V2)
- Minimum order value: $1 (i.e.
price × size ≥ 1.0) - Minimum size: 5 shares
- Tick: normalized automatically by
prepare_order.py(usually $0.01) - `signatureType`: always
0(EOA) for Privy wallets
V2 wire format is strict — post_order.py already handles this, but if you ever build a payload by hand:
saltmust be int (not string)- Do NOT send
taker/nonce/feeRateBps(V1 fields, removed in V2) metadataandbuilderarebytes32zeros (0x+ 64 zeros)
---
Auth refresh
CLOB credentials (POLY_API_KEY / POLY_SECRET / POLY_PASSPHRASE) are derived from a wallet signature once and persist. If status.py returns 401:
python3 scripts/auth.py --check # quick sanity check
python3 scripts/auth.py --prepare 0xWALLET # build signing payload
# → wallet_sign_typed_data(...)
python3 scripts/auth.py --save 0xSIG 0xWALLET TIMESTAMPInvariants: TIMESTAMP and 0xWALLET in --save must match the most recent --prepare. If 401 persists, rerun the full prepare→sign→save flow with a fresh timestamp.
---
Geo / VPN
CLOB API is geo-blocked from US IPs. scripts/common.py transparently routes through sc-vpn.internal:8080 and caches the fastest region. No agent action needed. Override with POLY_VPN_REGION=ar or disable with POLY_DISABLE_VPN=true.
---
Errors → fixes
| Error | Cause | Fix |
|---|---|---|
Invalid order payload | Wrong V2 wire format (string salt / extra V1 fields) | Use post_order.py (it sends the right shape) |
invalid amount for a marketable BUY order ($X), min size: $1 | Order value < $1 | Increase price × size to ≥ $1 |
not enough balance / 0 buying power | pUSD not wrapped yet, OR signature_type mismatch in cache | setup.py --all 10, then status.py |
L2_BALANCE_TOO_LOW | No pUSD in EOA | Wrap more: setup.py --wrap N |
order_version_mismatch | Old V1 signing schema | Re-run prepare_order.py (uses V2 domain version=2) |
| 401 / Invalid API key | Stale CLOB credentials | auth.py refresh flow |
| 403 geo-block | VPN unhealthy | Retry; if persists, set POLY_VPN_REGION to another region |
| Orderbook shows 0.01 / 0.99 | Looking at the wrong outcome's book | Use the token_id you actually plan to buy |
Known transient errors
- Privy / Alchemy paymaster HTTP 500 may happen during rapid back-to-back on-chain calls.
- This is typically transient infra jitter, not a permanent wallet/skill issue.
- Practical fix: wait ~10 seconds and retry the same step.
---
Architecture summary
- Wallet: Privy EOA,
signatureType=0. Agent signs EIP-712 viawallet_sign_typed_data; no private key in agent context. - Gas: sponsored (Alchemy paymaster) — every on-chain call routes through
wallet_transfer. - Collateral: pUSD (
0xC011a7E1...DFB), 6 decimals, 1:1 wrap of USDC.e. - Exchanges: CTF Exchange V2 (binary), Neg-Risk Adapter, Neg-Risk Exchange V2 — all 3 must be approved for SELL/settlement.
- CLOB:
https://clob.polymarket.com(V2 backend, live since Apr 28 2026). L1 = wallet sig, L2 = HMAC with derived API key.
---
Changelog
- v6.0.1 — Bugfixes: (1)
scripts/common.py::save_env_var()now guards missing trailing newline in.envto prevent key concatenation; (2)scripts/close_positions.pymigrated from V1 wire fields to CLOB V2 schema (timestamp/metadata/builder, domainversion=2) aligned withprepare_order.py. Added "Known transient errors" note for occasional Privy/Alchemy paymaster HTTP 500 under rapid consecutive calls (retry after ~10s). - v6.0.0 — Major: full SKILL rewrite for clarity ("3 steps to trade"), new idempotent
setup.pyfor one-time wrap + approvals, end-to-end live-verified on CLOB V2 (BUY0x43f20c67...20b653→ SELL0x19f475e4...fedde1, 5 NO @ 0.989 → @ 0.988, ~$0.005 slippage). Supersedes the V1-era flow entirely. - v5.1.0 — Added
setup.py(idempotent wrap + approvals). Full SKILL rewrite for clarity. Live-verified: BUY0x43f20c67...20b653, SELL0x19f475e4...fedde1(5 NO @ 0.989 → @ 0.988, ~$0.005 slippage). - v5.0.5 — CLOB V2 wire format fix:
saltmust be int; removetaker/nonce/feeRateBps. - v5.0.4 — Migrated to V2 EIP-712 domain (version=2), V2 contracts, V2 order fields.
- v5.0.0 — Script-first architecture (BUY: 8 calls → 3).
"""
Polymarket Skill v5.0.3 — Script-first Unified Entry
Primary interface is scripts/ via bash + wallet_sign_typed_data.
No Python tool wrappers are registered.
"""
import logging
from typing import List
logger = logging.getLogger(__name__)
def register(api) -> List[str]:
logger.info("Polymarket loaded in script-first mode (no registered Python tools)")
return []
EXTENSION_INFO = {
"name": "polymarket",
"version": "5.0.3",
"description": "Polymarket prediction markets — script-first unified entry (search/status/auth/prepare/post/cancel/close)",
"tools": [],
"env_vars": [
"POLY_API_KEY",
"POLY_SECRET",
"POLY_PASSPHRASE",
"POLY_WALLET",
],
}
Polymarket Onboarding Checklist v5.0.3
Goal
Get to a state where the agent can place and close a Polymarket trade reliably.
Preconditions
skills/polymarket/scripts/*.pyavailable (script-first)- Wallet signing available (
wallet_sign_typed_data) - Agent has POL (gas) and USDC.e (collateral) on Polygon
1) Auth
1. python3 scripts/auth.py --check 2. If stale/missing: python3 scripts/auth.py --prepare 0xWALLET 3. wallet_sign_typed_data(...) on /tmp/poly_auth.json 4. python3 scripts/auth.py --save 0xSIG 0xWALLET TIMESTAMP 5. python3 scripts/status.py --json → confirm auth works
2) Funding / Allowance
1. Check on-chain balances (POL + USDC.e) 2. If status.py allowance remains 0, verify ERC20 allowance(owner,spender) on-chain 3. Ensure non-zero allowance for CLOB spenders before order test
3) Trade
1. python3 scripts/search.py "keyword" --limit 2 → pick active market token_id 2. python3 scripts/prepare_order.py TOKEN_ID BUY PRICE SIZE 3. wallet_sign_typed_data(...) on /tmp/poly_order.json 4. python3 scripts/post_order.py 0xSIG → submit 5. python3 scripts/status.py --json + trades/positions verify
Common Failures
| Error | Fix |
|---|---|
| 401/INVALID_API_KEY | Re-run auth flow (steps 1-4) |
| INVALID_ORDER_PAYLOAD | Use polymarket_prepare_order (handles normalization) |
| L2_BALANCE_TOO_LOW | Fund wallet with USDC.e |
| 403 geo-block | VPN auto-detection handles this transparently |
Polymarket Smoke Test v5.0.3
Objective
Complete one full loop: search → buy → verify → sell → verify.
Test Size
5-10 USDC
Procedure
A — Init (script-first)
1. python3 scripts/auth.py --check 2. If needed: python3 scripts/auth.py --prepare 0xWALLET → sign → python3 scripts/auth.py --save ... 3. python3 scripts/status.py --json
B — Discover
1. python3 scripts/search.py "keyword" --limit 2 (avoid long literal full-sentence query) 2. Pick token_id from outcomes
C — Open
1. python3 scripts/prepare_order.py TOKEN_ID BUY PRICE SIZE 2. wallet_sign_typed_data(domain, types, "Order", message) 3. python3 scripts/post_order.py 0xSIG
D — Verify Open
1. python3 scripts/status.py --json 2. Check orders/positions/trades fields
E — Close
1. python3 scripts/prepare_order.py TOKEN_ID SELL PRICE SIZE 2. wallet_sign_typed_data(...) 3. python3 scripts/post_order.py 0xSIG
F — Verify Close
1. python3 scripts/status.py --json → position back to baseline
Pass Criteria
- No auth errors
- Order placed and filled
- Position opened and closed
- All tool outputs include verifiable IDs
Retry Policy
- Auth failure: re-auth once
- Payload error: rebuild with prepare_order once
- VPN failure: auto-handled, retry once
- Same error twice → stop and report
#!/usr/bin/env python3
"""
Polymarket Auth — check credentials, output EIP-712 for derive if missing.
Usage:
python3 auth.py --check # just check if creds exist & valid
python3 auth.py --prepare <wallet_address> # build ClobAuth EIP-712 for signing
python3 auth.py --save <signature> <wallet> <timestamp> # derive + save to .env
Full flow (agent):
1. bash: python3 auth.py --check
2. If missing: python3 auth.py --prepare 0xWALLET → get EIP-712 JSON
3. wallet_sign_typed_data(domain, types, primaryType, message)
4. bash: python3 auth.py --save 0xSIG 0xWALLET TIMESTAMP
"""
import sys, json, argparse, time
import os
sys.path.insert(0, __file__.rsplit("/", 1)[0])
from common import (
BASE, CHAIN_ID, cred, ensure_credentials,
clob_get, clob_post, save_env_var, die,
)
def check():
ok, msg = ensure_credentials()
if not ok:
print(f"❌ {msg}")
return False
# Verify creds work by checking balance
from common import l2_headers
r = clob_get("/balance-allowance",
headers=l2_headers("GET", "/balance-allowance"),
params={"asset_type": "COLLATERAL", "signature_type": 0},
)
if r.status_code == 200:
bal = r.json().get("balance", "0")
print(f"✅ Credentials valid. Balance: ${int(bal)/1_000_000:.2f}")
return True
else:
print(f"⚠️ Credentials exist but may be stale ({r.status_code}). Re-derive recommended.")
return False
def prepare(wallet):
ts = str(int(time.time()))
payload = {
"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": wallet,
"timestamp": ts,
"nonce": 0,
"message": "This message attests that I control the given wallet",
},
"meta": {"wallet": wallet, "timestamp": ts},
}
outfile = "/tmp/poly_auth.json"
with open(outfile, "w") as f:
json.dump(payload, f, indent=2)
print(f"AUTH READY: sign with wallet_sign_typed_data")
print(f" File: {outfile}")
print(f" Timestamp: {ts}")
print(f" Then: python3 auth.py --save <signature> {wallet} {ts}")
def save(signature, wallet, timestamp):
if not isinstance(signature, str) or not signature.strip() or signature == "undefined":
die("Invalid signature: must be a non-empty hex string from wallet_sign_typed_data")
try:
ts_int = int(timestamp)
except (TypeError, ValueError):
die("Invalid timestamp: must be integer from the latest --prepare")
if ts_int <= 0:
die("Invalid timestamp: must be > 0")
now = int(time.time())
if abs(now - ts_int) > 600:
die("Timestamp expired: rerun --prepare, re-sign, then --save with the new timestamp")
auth_file = "/tmp/poly_auth.json"
if os.path.exists(auth_file):
try:
with open(auth_file, "r") as f:
prepared = json.load(f)
prepared_wallet = ((prepared.get("meta") or {}).get("wallet") or "").lower()
prepared_ts = str(((prepared.get("meta") or {}).get("timestamp") or ""))
if prepared_wallet and wallet.lower() != prepared_wallet:
die("Wallet mismatch: --save wallet must match the latest --prepare wallet")
if prepared_ts and str(ts_int) != prepared_ts:
die("Timestamp mismatch: --save timestamp must match the latest --prepare timestamp")
except Exception as e:
die(f"Failed to validate /tmp/poly_auth.json: {e}")
headers = {
"POLY_ADDRESS": wallet,
"POLY_SIGNATURE": signature,
"POLY_TIMESTAMP": str(ts_int),
"POLY_NONCE": "0",
"Content-Type": "application/json",
}
# Try derive first
r = clob_get("/auth/derive-api-key", headers=headers)
if r.status_code != 200:
r = clob_post("/auth/api-key", headers=headers)
if r.status_code != 200:
die(
f"Auth failed ({r.status_code}): {r.text} | "
"Likely causes: signature/timestamp mismatch, wallet mismatch vs --prepare, or expired timestamp. "
"Fix: rerun --prepare <wallet>, sign that exact payload, then --save with same wallet+timestamp."
)
data = r.json()
api_key = data.get("apiKey", "")
secret = data.get("secret", "")
passphrase = data.get("passphrase", "")
if not all([api_key, secret, passphrase]):
die(f"Incomplete credentials: {json.dumps(data)}")
save_env_var("POLY_API_KEY", api_key)
save_env_var("POLY_SECRET", secret)
save_env_var("POLY_PASSPHRASE", passphrase)
save_env_var("POLY_WALLET", wallet)
print(f"✅ Credentials saved to .env")
print(f" API_KEY: {api_key[:8]}...")
print(f" WALLET: {wallet}")
def main():
parser = argparse.ArgumentParser(description="Polymarket Auth")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--check", action="store_true")
group.add_argument("--prepare", metavar="WALLET")
group.add_argument("--save", nargs=3, metavar=("SIG", "WALLET", "TIMESTAMP"))
args = parser.parse_args()
if args.check:
sys.exit(0 if check() else 1)
elif args.prepare:
prepare(args.prepare)
elif args.save:
save(*args.save)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Polymarket Cancel — cancel one or all open orders.
Usage:
python3 cancel.py --all
python3 cancel.py --id 0xabc123...
Output: Cancellation result.
"""
import sys, json, argparse
sys.path.insert(0, __file__.rsplit("/", 1)[0])
from common import BASE, cred, ensure_credentials, clob_delete, l2_headers, die
def main():
parser = argparse.ArgumentParser(description="Cancel orders")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--all", action="store_true", help="Cancel all open orders")
group.add_argument("--id", help="Cancel specific order ID")
args = parser.parse_args()
ok, msg = ensure_credentials()
if not ok:
die(msg)
if args.all:
r = clob_delete("/cancel-all", headers=l2_headers("DELETE", "/cancel-all"))
result = r.json() if r.text.strip() else {}
canceled = result.get("canceled", [])
not_canceled = result.get("not_canceled", {})
print(f"✅ Canceled: {len(canceled)} orders")
if not_canceled:
print(f"⚠️ Not canceled: {json.dumps(not_canceled)}")
else:
body = json.dumps({"orderID": args.id})
r = clob_delete("/order", headers=l2_headers("DELETE", "/order", body), data=body)
result = r.json() if r.text.strip() else {}
if r.status_code == 200:
print(f"✅ Canceled: {args.id}")
else:
print(f"❌ Failed ({r.status_code}): {json.dumps(result)}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Polymarket Close Positions — build SELL orders for all open positions.
Usage:
python3 close_positions.py # close all positions
python3 close_positions.py --token_id X # close specific position
Output: One or more EIP-712 JSON files (/tmp/poly_close_N.json) for signing.
Prints the signing instructions for the agent.
"""
import sys, json, argparse, time, random
sys.path.insert(0, __file__.rsplit("/", 1)[0])
from common import (
BASE, DATA_API, GAMMA, CHAIN_ID, CTF_EXCHANGE, CTF_EXCHANGE_NEG, EOA,
cred, ensure_credentials, clob_get, die,
)
import requests
def get_positions():
wallet = cred("POLY_WALLET")
r = requests.get(f"{DATA_API}/positions", params={"user": wallet}, timeout=30)
if r.status_code != 200:
return []
data = r.json()
return data if isinstance(data, list) else data.get("data", [])
def get_orderbook(token_id):
r = clob_get("/book", params={"token_id": token_id})
if r.status_code != 200:
return None, None
book = r.json()
bids = sorted(book.get("bids", []), key=lambda x: float(x["price"]), reverse=True)
asks = sorted(book.get("asks", []), key=lambda x: float(x["price"]))
return (float(bids[0]["price"]) if bids else None,
float(asks[0]["price"]) if asks else None)
def get_market_info(token_id):
info = {"fee_bps": 0, "neg_risk": False, "tick_size": "0.01"}
try:
r = requests.get(f"{GAMMA}/markets", params={"clob_token_ids": str(token_id)}, timeout=20)
if r.status_code == 200:
arr = r.json()
if arr:
cid = arr[0].get("conditionId")
if cid:
rc = clob_get(f"/markets/{cid}")
if rc.status_code == 200:
mk = rc.json()
info["tick_size"] = str(mk.get("minimum_tick_size", "0.01"))
info["fee_bps"] = int(mk.get("taker_base_fee", 0) or 0)
info["neg_risk"] = bool(mk.get("neg_risk", False))
except Exception:
pass
return info
def build_sell_payload(token_id, size, price, info, wallet):
exchange = CTF_EXCHANGE_NEG if info["neg_risk"] else CTF_EXCHANGE
tick = float(info["tick_size"])
price = round(round(price / tick) * tick, 4)
maker_amount = int(size * 1_000_000)
taker_amount = int(price * size * 1_000_000)
salt = round(time.time() * random.random())
# CLOB V2 signing schema (aligned with prepare_order.py)
ts_ms = int(time.time() * 1000)
zero32 = "0x" + "0" * 64
return {
"domain": {
"name": "Polymarket CTF Exchange",
"version": "2",
"chainId": CHAIN_ID,
"verifyingContract": exchange,
},
"types": {
"Order": [
{"name": "salt", "type": "uint256"},
{"name": "maker", "type": "address"},
{"name": "signer", "type": "address"},
{"name": "tokenId", "type": "uint256"},
{"name": "makerAmount", "type": "uint256"},
{"name": "takerAmount", "type": "uint256"},
{"name": "side", "type": "uint8"},
{"name": "signatureType", "type": "uint8"},
{"name": "timestamp", "type": "uint256"},
{"name": "metadata", "type": "bytes32"},
{"name": "builder", "type": "bytes32"},
]
},
"primaryType": "Order",
"message": {
"salt": str(salt),
"maker": wallet,
"signer": wallet,
"tokenId": str(token_id),
"makerAmount": str(maker_amount),
"takerAmount": str(taker_amount),
"side": 1,
"signatureType": EOA,
"timestamp": str(ts_ms),
"metadata": zero32,
"builder": zero32,
},
"meta": {
"token_id": token_id,
"salt": salt,
"maker_amount": maker_amount,
"taker_amount": taker_amount,
"order_side": 1,
"side_str": "SELL",
"price": price,
"size": size,
"fee_bps": info["fee_bps"],
"neg_risk": info["neg_risk"],
"exchange": exchange,
"timestamp": str(ts_ms),
"metadata": zero32,
"builder": zero32,
},
}
def main():
parser = argparse.ArgumentParser(description="Close Polymarket positions")
parser.add_argument("--token_id", help="Specific token to close (default: all)")
args = parser.parse_args()
ok, msg = ensure_credentials()
if not ok:
die(msg)
wallet = cred("POLY_WALLET")
positions = get_positions()
if not positions:
print("No open positions.")
return
# Filter if specific token
if args.token_id:
positions = [p for p in positions if p.get("asset") == args.token_id]
if not positions:
die(f"No position found for token {args.token_id}")
files = []
for i, pos in enumerate(positions):
token_id = pos.get("asset")
size = float(pos.get("size", 0))
if size <= 0:
continue
title = pos.get("title") or pos.get("market") or "?"
info = get_market_info(token_id)
best_bid, _ = get_orderbook(token_id)
if best_bid is None or best_bid <= 0:
print(f"⚠️ Skip {title}: no bid available")
continue
payload = build_sell_payload(token_id, size, best_bid, info, wallet)
outfile = f"/tmp/poly_close_{i}.json"
with open(outfile, "w") as f:
json.dump(payload, f, indent=2)
files.append(outfile)
print(f"CLOSE #{i}: SELL {size} @ {best_bid} — {title}")
print(f" File: {outfile}")
if files:
print(f"\n📝 {len(files)} order(s) ready for signing.")
print(f"For each file, sign with wallet_sign_typed_data, then run:")
print(f" python3 scripts/post_order.py <signature> --order <file>")
else:
print("No positions to close.")
if __name__ == "__main__":
main()
"""
Polymarket Common — shared by all scripts.
VPN auto-detect, credential management, HMAC auth, HTTP helpers.
"""
import os, sys, time, json, hmac, hashlib, base64, random
import requests
import concurrent.futures
# ── Endpoints ──
BASE = "https://clob.polymarket.com"
GAMMA = "https://gamma-api.polymarket.com"
DATA_API = "https://data-api.polymarket.com"
# ── Contracts ──
# CLOB V2 exchange contracts (Apr 2026+)
CTF_EXCHANGE = "0xE111180000d2663C0091e4f400237545B87B996B"
CTF_EXCHANGE_NEG = "0xe2222d279d744050d28e00520010520000310F59"
CHAIN_ID = 137
EOA = 0
ENV_FILE = "/data/workspace/.env"
VPN_CACHE = "/data/workspace/.polymarket_vpn_cache.json"
# ── Credential Loading ──
def load_env():
"""Read .env file into dict."""
env = {}
try:
with open(ENV_FILE) 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 FileNotFoundError:
pass
return env
def save_env_var(key, value):
"""Write/update a single key in .env file."""
lines = []
found = False
try:
with open(ENV_FILE) as f:
lines = f.readlines()
except FileNotFoundError:
pass
new_lines = []
for line in lines:
if line.strip().startswith(f"{key}="):
new_lines.append(f"{key}={value}\n")
found = True
else:
new_lines.append(line)
if not found:
# Guard: if existing .env last line has no trailing newline,
# ensure new key starts on a new line instead of being concatenated.
if new_lines and not new_lines[-1].endswith("\n"):
new_lines[-1] = new_lines[-1] + "\n"
new_lines.append(f"{key}={value}\n")
with open(ENV_FILE, "w") as f:
f.writelines(new_lines)
os.environ[key] = value
def cred(key):
"""Get credential from env or .env file."""
v = os.environ.get(key)
if v:
return v
return load_env().get(key, "")
def ensure_credentials():
"""Check credentials exist. Returns (ok, message)."""
keys = ["POLY_API_KEY", "POLY_SECRET", "POLY_PASSPHRASE", "POLY_WALLET"]
missing = [k for k in keys if not cred(k)]
if missing:
return False, f"Missing: {', '.join(missing)}. Run: polymarket_auth() first."
return True, "OK"
# ── VPN ──
VPN_REGIONS = ["ar", "br", "mx", "my", "th", "au", "za"]
def _load_vpn_cache():
try:
with open(VPN_CACHE) as f:
return json.load(f)
except Exception:
return {}
def _save_vpn_cache(region):
try:
with open(VPN_CACHE, "w") as f:
json.dump({"region": region, "ts": time.time()}, f)
except Exception:
pass
def _vpn_proxy(region):
return {
"https": f"http://{region}:x@sc-vpn.internal:8080",
"http": f"http://{region}:x@sc-vpn.internal:8080",
}
def detect_vpn():
"""Return best VPN proxy dict, or None."""
# Manual override
forced = os.environ.get("POLY_VPN_REGION", "").strip() or cred("POLY_VPN_REGION")
if forced:
return _vpn_proxy(forced)
# Disk cache (valid for 1 hour)
cache = _load_vpn_cache()
if cache.get("region") and time.time() - cache.get("ts", 0) < 3600:
return _vpn_proxy(cache["region"])
# Parallel probe
def test(r):
try:
t0 = time.time()
resp = requests.get(f"{BASE}/time", proxies=_vpn_proxy(r), timeout=5)
if resp.status_code == 200:
return (r, time.time() - t0)
except Exception:
pass
return None
with concurrent.futures.ThreadPoolExecutor(max_workers=7) as ex:
results = [x for x in ex.map(test, VPN_REGIONS) if x]
if results:
best = sorted(results, key=lambda x: x[1])[0][0]
_save_vpn_cache(best)
return _vpn_proxy(best)
return None
# ── HTTP with auto VPN ──
def clob_request(method, path, headers=None, data=None, json_data=None, params=None):
"""HTTP request to CLOB with auto VPN on 403."""
url = f"{BASE}{path}" if path.startswith("/") else path
kw = {"timeout": 30}
if headers: kw["headers"] = headers
if data: kw["data"] = data
if json_data: kw["json"] = json_data
if params: kw["params"] = params
# Try direct
try:
r = requests.request(method, url, **kw)
if r.status_code != 403:
return r
except Exception:
pass
# VPN fallback
proxy = detect_vpn()
if proxy:
kw["proxies"] = proxy
return requests.request(method, url, **kw)
# Last resort direct
return requests.request(method, url, **kw)
def clob_get(path, **kw):
return clob_request("GET", path, **kw)
def clob_post(path, **kw):
return clob_request("POST", path, **kw)
def clob_delete(path, **kw):
return clob_request("DELETE", path, **kw)
def gamma_get(path, params=None):
url = f"{GAMMA}{path}" if path.startswith("/") else path
return requests.get(url, params=params, timeout=30)
# ── HMAC Auth ──
def hmac_sig(timestamp, method, path, body=None):
secret = cred("POLY_SECRET")
key = base64.urlsafe_b64decode(secret)
msg = str(timestamp) + method.upper() + path
if body:
msg += body
sig = hmac.new(key, msg.encode(), hashlib.sha256)
return base64.urlsafe_b64encode(sig.digest()).decode()
def l2_headers(method, path, body=None):
ts = str(int(time.time()))
return {
"POLY_ADDRESS": cred("POLY_WALLET"),
"POLY_SIGNATURE": hmac_sig(ts, method, path, body),
"POLY_TIMESTAMP": ts,
"POLY_API_KEY": cred("POLY_API_KEY"),
"POLY_PASSPHRASE": cred("POLY_PASSPHRASE"),
"Content-Type": "application/json",
}
# ── Helpers ──
def fmt_usd(raw):
"""Convert raw USDC (6 decimals) string to float."""
try:
return int(raw) / 1_000_000
except Exception:
return 0.0
def die(msg):
print(f"ERROR: {msg}", file=sys.stderr)
sys.exit(1)
#!/usr/bin/env python3
"""
Polymarket Post Order — submit a signed order to CLOB and verify.
Usage:
python3 post_order.py <signature> [--order /tmp/poly_order.json]
Output: Order ID, fill status, updated position.
"""
import sys, json, argparse, re
sys.path.insert(0, __file__.rsplit("/", 1)[0])
from common import (
BASE, EOA, cred, ensure_credentials,
clob_post, l2_headers, die, fmt_usd,
)
def main():
parser = argparse.ArgumentParser(description="Post signed order")
parser.add_argument("signature", help="EIP-712 signature (0x...)")
parser.add_argument("--order", default="/tmp/poly_order.json", help="Order JSON file")
args = parser.parse_args()
ok, msg = ensure_credentials()
if not ok:
die(msg)
sig = (args.signature or "").strip()
if not re.fullmatch(r"0x[0-9a-fA-F]+", sig):
die("Invalid signature format: expected 0x-prefixed hex from wallet_sign_typed_data")
if len(sig) < 130:
die("Invalid signature length: expected full ECDSA signature from wallet_sign_typed_data")
try:
with open(args.order) as f:
payload = json.load(f)
except Exception as e:
die(f"Cannot read {args.order}: {e}")
meta = payload["meta"]
message = payload["message"]
wallet = cred("POLY_WALLET")
api_key = cred("POLY_API_KEY")
side_str = "BUY" if int(message.get("side", meta.get("order_side", 0))) == 0 else "SELL"
# CLOB V2 wire format: salt MUST be int, taker/nonce/feeRateBps removed
order_body = {
"order": {
"salt": int(message["salt"]),
"maker": wallet,
"signer": wallet,
"tokenId": str(message["tokenId"]),
"makerAmount": str(message["makerAmount"]),
"takerAmount": str(message["takerAmount"]),
"side": side_str,
"signatureType": int(message.get("signatureType", EOA)),
"timestamp": str(message["timestamp"]),
"metadata": str(message.get("metadata", "0x" + "0" * 64)),
"builder": str(message.get("builder", "0x" + "0" * 64)),
"expiration": "0",
"signature": args.signature,
},
"owner": api_key,
"orderType": "GTC",
"deferExec": False,
"postOnly": False,
}
body_str = json.dumps(order_body, separators=(",", ":"))
headers = l2_headers("POST", "/order", body_str)
r = clob_post("/order", headers=headers, data=body_str)
result = r.json() if r.text.strip() else {}
if r.status_code != 200:
print(f"FAILED ({r.status_code}): {json.dumps(result, indent=2)}")
sys.exit(1)
order_id = result.get("orderID", "?")
taking = result.get("takingAmount", "")
making = result.get("makingAmount", "")
status = result.get("status", "")
tx_hashes = result.get("transactionsHashes", [])
print(f"✅ ORDER POSTED")
print(f" ID: {order_id}")
print(f" Side: {side_str} {meta['size']} @ {meta['price']}")
if taking:
print(f" Filled: taking={taking} making={making}")
if status:
print(f" Status: {status}")
if tx_hashes:
for tx in tx_hashes:
print(f" TX: https://polygonscan.com/tx/{tx}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Polymarket Prepare Order — build EIP-712 payload for signing.
Usage:
python3 prepare_order.py <token_id> BUY 0.76 13
python3 prepare_order.py <token_id> SELL 0.76 13
Output: JSON with domain/types/message for wallet_sign_typed_data,
plus meta for post_order.py. Saved to /tmp/poly_order.json.
"""
import sys, json, argparse, time, random
sys.path.insert(0, __file__.rsplit("/", 1)[0])
from common import (
BASE, GAMMA, CHAIN_ID, CTF_EXCHANGE, CTF_EXCHANGE_NEG, EOA,
cred, ensure_credentials, clob_get, die, fmt_usd,
)
import requests
def get_market_info(token_id):
"""Get fee, neg_risk, tick_size for a token."""
info = {"fee_bps": 0, "neg_risk": False, "tick_size": "0.01", "min_size": 5}
# Gamma lookup: token → condition
try:
r = requests.get(f"{GAMMA}/markets", params={"clob_token_ids": str(token_id)}, timeout=20)
if r.status_code == 200:
arr = r.json()
if isinstance(arr, list) and arr:
cid = arr[0].get("conditionId")
if cid:
# CLOB metadata
rc = clob_get(f"/markets/{cid}")
if rc.status_code == 200:
mk = rc.json()
info["tick_size"] = str(mk.get("minimum_tick_size", "0.01"))
info["fee_bps"] = int(mk.get("taker_base_fee", 0) or mk.get("maker_base_fee", 0) or 0)
info["neg_risk"] = bool(mk.get("neg_risk", False))
info["min_size"] = float(mk.get("minimum_order_size", 5))
except Exception:
pass
return info
def get_orderbook(token_id):
"""Get best bid/ask."""
r = clob_get(f"/book", params={"token_id": token_id})
if r.status_code != 200:
return None, None
book = r.json()
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 None
best_ask = float(asks[0]["price"]) if asks else None
return best_bid, best_ask
def main():
parser = argparse.ArgumentParser(description="Build Polymarket order")
parser.add_argument("token_id", help="CLOB token ID")
parser.add_argument("side", choices=["BUY", "SELL", "buy", "sell"])
parser.add_argument("price", type=float, help="Limit price (0.01-0.99)")
parser.add_argument("size", type=float, help="Number of shares")
parser.add_argument("--out", default="/tmp/poly_order.json", help="Output file")
args = parser.parse_args()
ok, msg = ensure_credentials()
if not ok:
die(msg)
side = args.side.upper()
wallet = cred("POLY_WALLET")
# Market info
info = get_market_info(args.token_id)
tick = float(info["tick_size"])
price = round(round(args.price / tick) * tick, 4)
size = round(args.size, 2)
if size < info["min_size"]:
die(f"Size {size} below minimum {info['min_size']}")
# Orderbook check
best_bid, best_ask = get_orderbook(args.token_id)
exchange = CTF_EXCHANGE_NEG if info["neg_risk"] else CTF_EXCHANGE
if side == "BUY":
taker_amount = int(size * 1_000_000)
maker_amount = round(round(size * price, 4) * 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())
# CLOB V2 signing schema (domain version=2, V2 order fields)
ts_ms = int(time.time() * 1000)
zero32 = "0x" + "0" * 64
payload = {
"domain": {
"name": "Polymarket CTF Exchange",
"version": "2",
"chainId": CHAIN_ID,
"verifyingContract": exchange,
},
"types": {
"Order": [
{"name": "salt", "type": "uint256"},
{"name": "maker", "type": "address"},
{"name": "signer", "type": "address"},
{"name": "tokenId", "type": "uint256"},
{"name": "makerAmount", "type": "uint256"},
{"name": "takerAmount", "type": "uint256"},
{"name": "side", "type": "uint8"},
{"name": "signatureType", "type": "uint8"},
{"name": "timestamp", "type": "uint256"},
{"name": "metadata", "type": "bytes32"},
{"name": "builder", "type": "bytes32"},
]
},
"primaryType": "Order",
"message": {
"salt": str(salt),
"maker": wallet,
"signer": wallet,
"tokenId": str(args.token_id),
"makerAmount": str(maker_amount),
"takerAmount": str(taker_amount),
"side": order_side,
"signatureType": EOA,
"timestamp": str(ts_ms),
"metadata": zero32,
"builder": zero32,
},
"meta": {
"token_id": args.token_id,
"salt": salt,
"maker_amount": maker_amount,
"taker_amount": taker_amount,
"order_side": order_side,
"side_str": side,
"price": price,
"size": size,
"fee_bps": info["fee_bps"],
"neg_risk": info["neg_risk"],
"exchange": exchange,
"timestamp": str(ts_ms),
"metadata": zero32,
"builder": zero32,
},
}
with open(args.out, "w") as f:
json.dump(payload, f, indent=2)
cost = maker_amount / 1_000_000 if side == "BUY" else size
print(f"ORDER READY: {side} {size} shares @ {price} (cost ~${cost:.2f})")
if best_bid is not None:
print(f" Orderbook: bid={best_bid} ask={best_ask}")
print(f" Saved to: {args.out}")
print(f" Next: wallet_sign_typed_data(domain, types, primaryType='Order', message)")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Polymarket Search — find markets + lookup token IDs in one shot.
Usage:
python3 search.py "US Iran ceasefire"
python3 search.py "Trump" --limit 5
Output: JSON with events, markets, and token_ids ready for ordering.
"""
import sys, json, argparse
sys.path.insert(0, __file__.rsplit("/", 1)[0])
from common import gamma_get, GAMMA
def search_v2(query, limit=10):
r = gamma_get("/search-v2", params={
"q": query, "optimized": "true",
"limit_per_type": limit, "type": "events",
"search_tags": "true", "cache": "true",
})
if r.status_code >= 400:
return []
data = r.json() if r.text.strip() else {}
return data.get("events", []) if isinstance(data, dict) else []
def lookup_market(slug):
"""Lookup single market by slug to get clobTokenIds."""
r = gamma_get("/markets", params={"slug": slug, "limit": 1})
if r.status_code == 200:
data = r.json()
if isinstance(data, list) and data:
return data[0]
return None
def main():
parser = argparse.ArgumentParser(description="Search Polymarket")
parser.add_argument("query", help="Search query")
parser.add_argument("--limit", type=int, default=10)
args = parser.parse_args()
events = search_v2(args.query, args.limit)
if not events:
print(json.dumps({"error": "No results", "query": args.query}))
sys.exit(1)
results = []
for ev in events:
event_out = {
"title": ev.get("title"),
"slug": ev.get("slug"),
"markets": [],
}
for m in ev.get("markets", []):
slug = m.get("slug", "")
question = m.get("question", "")
outcomes = m.get("outcomes", [])
if isinstance(outcomes, str):
outcomes = json.loads(outcomes)
prices = m.get("outcomePrices", [])
if isinstance(prices, str):
prices = json.loads(prices)
token_ids = m.get("clobTokenIds")
if isinstance(token_ids, str):
token_ids = json.loads(token_ids)
# If no token_ids from search, lookup by slug
if not token_ids and slug:
detail = lookup_market(slug)
if detail:
raw = detail.get("clobTokenIds", [])
if isinstance(raw, str):
raw = json.loads(raw)
token_ids = raw
market_out = {
"question": question,
"slug": slug,
"active": m.get("active", True),
"closed": m.get("closed", False),
"outcomes": [],
}
for i, name in enumerate(outcomes):
entry = {"name": name}
if i < len(prices):
try: entry["price"] = float(prices[i])
except: pass
if token_ids and i < len(token_ids):
entry["token_id"] = token_ids[i]
market_out["outcomes"].append(entry)
event_out["markets"].append(market_out)
results.append(event_out)
print(json.dumps({"query": args.query, "events": results}, indent=2))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Polymarket One-Time Setup — wrap USDC.e -> pUSD, then approve V2 spenders.
Idempotent: skips steps already done on-chain. Safe to re-run.
Usage:
python3 setup.py # check only, print what's needed
python3 setup.py --wrap 10 # wrap 10 USDC.e -> pUSD (if pUSD balance < amount)
python3 setup.py --approve # approve pUSD + CTF to all 3 V2 spenders (if missing)
python3 setup.py --all 10 # wrap + approve (one shot for first-time users)
Requires: POLY_WALLET set, wallet has USDC.e on Polygon.
Gas is sponsored via the Privy/Alchemy paymaster — user pays nothing.
"""
import sys, json, time, argparse
sys.path.insert(0, __file__.rsplit("/", 1)[0])
from common import cred, ensure_credentials, die
from core.skill_tools import wallet as wallet_tool
from core.http_client import proxied_post
# --- Contracts (Polygon mainnet, CLOB V2) ---
USDCE = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
PUSD = "0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB"
ONRAMP = "0x93070a847efEf7F70739046A929D47a521F5B8ee"
CTF = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"
SPENDERS = [
"0xE111180000d2663C0091e4f400237545B87B996B", # CTF Exchange V2 (binary)
"0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296", # Neg Risk Adapter
"0xe2222d279d744050d28e00520010520000310F59", # Neg Risk Exchange V2
]
RPC = "https://polygon-bor-rpc.publicnode.com"
MAX_UINT = (1 << 256) - 1
# --- ABI helpers (raw encoding to avoid eth-abi dependency) ---
def _addr(a): return a.lower().replace("0x", "").rjust(64, "0")
def _uint(n): return hex(n)[2:].rjust(64, "0")
def _approve(spender, amt): return "0x095ea7b3" + _addr(spender) + _uint(amt)
def _wrap(asset, to, amt): return "0x62355638" + _addr(asset) + _addr(to) + _uint(amt)
def _set_approval_for_all(op, on): return "0xa22cb465" + _addr(op) + _uint(1 if on else 0)
def _rpc(method, params):
r = proxied_post(RPC, json={"jsonrpc": "2.0", "id": 1, "method": method, "params": params},
headers={"SC-CALLER-ID": "chat:polymarket-setup"}).json()
return r.get("result")
def _balance(token, owner):
data = "0x70a08231" + _addr(owner)
res = _rpc("eth_call", [{"to": token, "data": data}, "latest"])
return int(res, 16) if res else 0
def _allowance(token, owner, spender):
data = "0xdd62ed3e" + _addr(owner) + _addr(spender)
res = _rpc("eth_call", [{"to": token, "data": data}, "latest"])
return int(res, 16) if res else 0
def _is_approved_for_all(ctf, owner, op):
data = "0xe985e9c5" + _addr(owner) + _addr(op)
res = _rpc("eth_call", [{"to": ctf, "data": data}, "latest"])
return bool(int(res, 16)) if res else False
def _send(to, data, label):
r = wallet_tool.wallet_transfer(to=to, amount="0", chain_id=137, data=data)
op = r.get("data", {}).get("user_operation_hash") or r.get("data", {}).get("hash")
print(f" ✓ {label}: {op}")
return op
def main():
parser = argparse.ArgumentParser(description="Polymarket one-time on-chain setup")
parser.add_argument("--wrap", type=float, help="wrap N USDC.e -> pUSD if balance short")
parser.add_argument("--approve", action="store_true", help="approve pUSD + CTF for V2 spenders")
parser.add_argument("--all", type=float, help="wrap N + approve (first-time users)")
args = parser.parse_args()
ok, msg = ensure_credentials()
if not ok: die(msg)
eoa = cred("POLY_WALLET")
if not eoa: die("POLY_WALLET not set")
do_wrap = args.wrap is not None or args.all is not None
do_approve = args.approve or args.all is not None
wrap_amt = args.all if args.all is not None else args.wrap
check_only = not (do_wrap or do_approve)
print(f"EOA: {eoa}\n")
# ── State ──
usdce = _balance(USDCE, eoa) / 1e6
pusd = _balance(PUSD, eoa) / 1e6
pusd_allow = {s: _allowance(PUSD, eoa, s) for s in SPENDERS}
ctf_allow = {s: _is_approved_for_all(CTF, eoa, s) for s in SPENDERS}
print(f"USDC.e balance: {usdce:.4f}")
print(f"pUSD balance: {pusd:.4f}")
print(f"pUSD allowances (need MAX for each):")
for s in SPENDERS:
v = pusd_allow[s]
print(f" {s}: {'MAX' if v > 10**70 else v}")
print(f"CTF setApprovalForAll (need True for each):")
for s in SPENDERS:
print(f" {s}: {ctf_allow[s]}")
needs_wrap = (do_wrap and wrap_amt and pusd < wrap_amt)
needs_pusd_ap = [s for s in SPENDERS if pusd_allow[s] < 10**70]
needs_ctf_ap = [s for s in SPENDERS if not ctf_allow[s]]
if check_only:
print("\n=== Check only. Suggested next step: ===")
if pusd < 1 and usdce >= 1:
print(f" python3 setup.py --all {min(usdce, 100):.2f}")
elif needs_pusd_ap or needs_ctf_ap:
print(" python3 setup.py --approve")
else:
print(" ✅ Ready to trade.")
return
# ── Wrap ──
if needs_wrap:
if usdce < wrap_amt:
die(f"USDC.e balance {usdce} < requested wrap {wrap_amt}. Fund EOA on Polygon first.")
amt_wei = int(wrap_amt * 1_000_000)
print(f"\n=== Wrap {wrap_amt} USDC.e -> pUSD ===")
_send(USDCE, _approve(ONRAMP, amt_wei), f"approve USDC.e -> Onramp")
time.sleep(2)
_send(ONRAMP, _wrap(USDCE, eoa, amt_wei), f"wrap {wrap_amt} USDC.e -> pUSD")
time.sleep(2)
elif do_wrap:
print(f"\n=== Wrap skipped: pUSD balance {pusd} already >= {wrap_amt} ===")
# ── Approvals ──
if do_approve:
if not needs_pusd_ap and not needs_ctf_ap:
print(f"\n=== Approvals already in place ===")
else:
print(f"\n=== Approvals: pUSD to {len(needs_pusd_ap)} spenders + CTF to {len(needs_ctf_ap)} spenders ===")
for s in needs_pusd_ap:
_send(PUSD, _approve(s, MAX_UINT), f"approve pUSD MAX -> {s[:10]}...")
time.sleep(2)
for s in needs_ctf_ap:
_send(CTF, _set_approval_for_all(s, True), f"setApprovalForAll CTF -> {s[:10]}...")
time.sleep(2)
print("\n=== Verify post-setup ===")
print(" python3 setup.py # re-check")
print(" python3 status.py # confirm CLOB sees pUSD + allowances")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Polymarket Status — balance + positions + open orders in one shot.
Usage:
python3 status.py
python3 status.py --json
Output: Human-readable summary or raw JSON.
"""
import sys, json, argparse
sys.path.insert(0, __file__.rsplit("/", 1)[0])
from common import (
BASE, DATA_API, cred, ensure_credentials,
clob_get, l2_headers, fmt_usd, die,
)
import requests
def get_balance():
r = clob_get("/balance-allowance",
headers=l2_headers("GET", "/balance-allowance"),
params={"asset_type": "COLLATERAL", "signature_type": 0},
)
return r.json() if r.status_code == 200 else {}
def get_positions():
wallet = cred("POLY_WALLET")
r = requests.get(f"{DATA_API}/positions", params={"user": wallet}, timeout=30)
return r.json() if r.status_code == 200 else []
def get_open_orders():
r = clob_get("/data/orders", headers=l2_headers("GET", "/data/orders"))
return r.json() if r.status_code == 200 else {}
def get_trades(limit=5):
wallet = cred("POLY_WALLET")
r = requests.get(f"{DATA_API}/trades", params={"user": wallet, "limit": limit}, timeout=30)
return r.json() if r.status_code == 200 else []
def main():
parser = argparse.ArgumentParser(description="Polymarket Status")
parser.add_argument("--json", action="store_true", help="Raw JSON output")
args = parser.parse_args()
ok, msg = ensure_credentials()
if not ok:
die(msg)
balance = get_balance()
positions = get_positions()
orders = get_open_orders()
trades = get_trades(5)
if args.json:
print(json.dumps({
"balance": balance,
"positions": positions,
"orders": orders,
"recent_trades": trades,
}, indent=2))
return
# Human-readable
bal = fmt_usd(balance.get("balance", "0"))
print(f"💰 Balance: ${bal:.2f}")
# Positions
pos_list = positions if isinstance(positions, list) else positions.get("data", [])
if pos_list:
print(f"\n📊 Positions ({len(pos_list)}):")
for p in pos_list:
token = p.get("asset", "?")[:12] + "..."
size = p.get("size", 0)
avg = p.get("avgPrice", "?")
cur = p.get("curPrice", "?")
title = p.get("title") or p.get("market", "?")
side = p.get("proxyOutcome") or ("YES" if p.get("outcome", "") == "Yes" else "NO")
print(f" {side} {size} @ avg={avg} cur={cur} — {title}")
else:
print("\n📊 Positions: none")
# Open orders
order_list = orders.get("data", []) if isinstance(orders, dict) else orders
if order_list:
print(f"\n📋 Open Orders ({len(order_list)}):")
for o in order_list:
side = o.get("side", "?")
price = o.get("price", "?")
size_matched = o.get("size_matched", "0")
original = o.get("original_size", "?")
oid = o.get("id", "?")[:16] + "..."
print(f" {side} {original} @ {price} (filled={size_matched}) [{oid}]")
else:
print("\n📋 Open Orders: none")
# Recent trades
if trades:
print(f"\n🔄 Recent Trades:")
for t in trades[:5]:
side = t.get("side", "?")
size = t.get("size", "?")
price = t.get("price", "?")
title = t.get("title") or t.get("market", "?")
print(f" {side} {size} @ {price} — {title}")
if __name__ == "__main__":
main()