💰 Polymarket CLOB API 下单
本文档详细说明 Polymarket CLOB v2 下单流程,涵盖签名、钱包、排错和完整代码示例。
架构概览
┌─────────────┐ EIP-712 签名 ┌──────────────────┐
│ CLOBClient │ ──────────────────▶ │ Polymarket CLOB │
│ (Python) │ ◀────────────────── │ API (v2) │
└─────────────┘ JSON Response └──────────────────┘
│
│ 钱包
▼
┌──────────────────┐
│ Gnosis Safe │ ← 资金托管 (操作员 EOA 签名)
│ (0xe39C...) │
└──────────────────┘
钱包架构
| 地址 | 类型 | 用途 | CLOB 余额 |
|---|---|---|---|
0xAf63... | EOA | 操作员私钥 (签名) | 0 USDC |
0xe39C4853A6e14045F192B65EfbACECf845d74C0B | Gnosis Safe | 资金托管 | ~87 USDC |
关键: 资金在 Safe 中,下单需使用 Safe 地址作为 maker,签名类型为 POLY_GNOSIS_SAFE (2)。
CLOB v2 下单流程
1. 环境配置 (.env)
# Polymarket L2 API 凭证
POLYMARKET_L2_API_KEY=your_api_key
POLYMARKET_L2_SECRET=your_secret
POLYMARKET_L2_PASSPHRASE=your_passphrase
POLYMARKET_WALLET_ADDRESS=0xAf63... # EOA 操作员地址
# Safe 多签钱包 (资金地址)
POLYMARKET_SAFE_ADDRESS=0xe39C4853A6e14045F192B65EfbACECf845d74C0B
# EOA 私钥 (用于签名)
POLYMARKET_PRIVATE_KEY=0x...
# 代理 (国内需要)
# 系统级 Clash TUN 路由,不需额外配置
2. CLOBClient 初始化
from src.mm.clob_client import CLOBClient
# 方式 A: 使用 EOA 私钥 + Safe 地址
client = CLOBClient.from_private_key(
private_key="0x...",
funder_address="0xe39C..." # Safe 地址
)
# 方式 B: 使用 L2 API 凭证 + Safe 地址
client = CLOBClient(
api_key="...",
api_secret="...",
passphrase="...",
wallet_address="0xAf63...", # EOA
funder_address="0xe39C..." # Safe (资金方)
)
3. 下单 (create_order)
# 基本下单
result = client.create_order(
token_id="1234567890...", # 合约 token ID
price=0.63, # 价格 (0-1, 即 63¢)
size=10.0, # 数量 (最大 2 位小数)
side="BUY", # BUY 或 SELL
)
# 返回示例 (成功)
{
"id": "0xabcd1234...",
"status": "live",
"maker": "0xe39c...",
"price": "0.63",
"original_size": "10.0",
"size_matched": "0",
"side": "BUY"
}
4. EIP-712 签名结构 (v2)
EIP712_ORDER_TYPES_V2 = {
"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": "side", "type": "uint8"},
{"name": "signatureType", "type": "uint8"},
{"name": "timestamp", "type": "uint64"},
{"name": "expiration", "type": "uint64"},
{"name": "metadata", "type": "bytes32"},
{"name": "builder", "type": "address"}
]
}
EIP712_DOMAIN = {
"name": "Polymarket CTF Exchange",
"version": "2",
"chainId": 137, # Polygon
"verifyingContract": "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E"
}
5. 签名类型
| 类型 | 值 | 说明 |
|---|---|---|
EOA | 0 | 普通 EOA 签名 |
POLY_PROXY | 1 | Polymarket Proxy 合约 |
POLY_GNOSIS_SAFE | 2 | Gnosis Safe 多签 |
香港天气做市使用 类型 2 (POLY_GNOSIS_SAFE):
signer = EOA 操作员地址maker = Safe 地址 (资金方)CLOB v2 Order Wire Format
{
"salt": 123456789,
"maker": "0xe39C4853A6e14045F192B65EfbACECf845d74C0B",
"signer": "0xAf63...",
"taker": "0x0000000000000000000000000000000000000000",
"tokenId": "1234567890...",
"makerAmount": "500000",
"takerAmount": "500000",
"side": "BUY",
"signatureType": 2,
"timestamp": "1714400000",
"expiration": "0",
"metadata": "0x0000000000000000000000000000000000000000000000000000000000000000",
"builder": "0x0000000000000000000000000000000000000000",
"signature": "0x..."
}
注意事项:
salt 必须是随机 uint256timestamp 是 Unix 秒级时间戳makerAmount / takerAmount 是 USDC 精度 (6 位小数) 的整数price = makerAmount / takerAmount (BUY) 或 takerAmount / makerAmount (SELL)nonce、feeRateBps 字段 (v1 遗留)Web UI 下单流程
在 /mm/weather 页面上的下单操作,通过以下 API 调用:
POST /api/mm/weather/place-order
// Request
{
"token_id": "1234567890...",
"price": 0.63,
"size": 10.0,
"side": "BUY",
"temp": 29,
"comp_type": "exact"
}
// Response (成功)
{
"success": true,
"order": { "id": "0x...", "status": "live" },
"message": "BUY 10.0 @ 0.63 placed",
"warnings": [],
"cooldown_seconds": 30
}
下单防护规则
1. 重复检测: 同 token + 同方向 → 自动撤旧单再挂新单
2. 自成交防护:
- 新 BUY 价格必须 < 现有 SELL 价格
- 新 SELL 价格必须 > 现有 BUY 价格
- 违反 → 返回 409 并提示冲突订单ID
3. 冷却期: 同 token+方向 30s 冷却 (返回 429)
4. 测试上限: 单笔 $30,超过拒绝
5. 余额重试: 余额不足时自动缩量 (-0.1/级, 最多10级)
6. Tick 自适应: < 10¢ 用 0.1¢ tick,≥ 10¢ 用 1¢ tick
POST /api/mm/weather/quick-order
// Request — 从前端 Gamma 数据计算的报价
{
"token_id": "1234567890...",
"side": "BUY",
"size": 5.0,
"price": 0.067, // 前端预先算好的价格
"temp": 29,
"comp_type": "exact"
}
常见问题排查
HTTP 400: "not enough balance"
原因: CLOB 余额 ≠ 链上余额。CLOB 有独立的内部记账。
解决:
1. 确认 Safe 地址正确且已在 polymarket.com/portfolio 存入 USDC
2. 检查 funder_address 是否为 Safe (非 EOA)
3. 确认已从 polymarket.com 执行 deposit 操作
HTTP 401: Unauthorized
原因: HMAC 签名时间偏差或 L2 凭证错误。
解决:
1. 确保系统时间同步 (NTP)
2. 检查代理设置 (国内需 SOCKS5 代理)
3. 验证 API Key/Secret/Passphrase 正确性
签名失败: "invalid address"
原因: EOA 地址格式错误 (缺少字符或校验和不匹配)。
解决:
1. 地址必须是 40 hex 字符 (不含 0x)
2. 使用 EIP-55 校验和格式
3. 从 Polymarket 账户设置页面复制正确的地址
代理问题
CLOB API 直连: https://clob.polymarket.com
polymkt.lt.sopher.cool:4433 反向代理已失效 (针对 CLOB)CLOBClient 内置代理:
self.session.proxies.update({
'http': 'socks5h://127.0.0.1:7897',
'https': 'socks5h://127.0.0.1:7897',
})
相关代码文件
| 文件 | 行数 | 说明 |
|---|---|---|
src/mm/clob_client.py | ~37KB | CLOB API 完整客户端 |
src/web/app.py (下单) | 行 4109-4295 | Web UI 下单端点 |
src/web/app.py (快捷下单) | 行 4328+ | quick-order 端点 |
src/web/app.py (撤单) | 行 4611+ | cancel 端点 |
src/mm/weather_mm_runner.py | ~73KB | 做市主循环 (自动下单) |