Current section
Files
Jump to
Current section
Files
scripts/generate_parity_fixtures.py
#!/usr/bin/env python3
"""Generate deterministic Polymarket CLOB parity fixtures from the official Python v2 client.
Set PY_CLOB_CLIENT_V2_PATH when the client checkout is somewhere other than
/tmp/py-clob-client-v2.
"""
from __future__ import annotations
import dataclasses
import json
import os
import subprocess
import sys
from pathlib import Path
CLIENT_PATH = Path(os.environ.get("PY_CLOB_CLIENT_V2_PATH", "/tmp/py-clob-client-v2"))
sys.path.insert(0, str(CLIENT_PATH))
from py_clob_client_v2.config import get_contract_config
from py_clob_client_v2.constants import BYTES32_ZERO
from py_clob_client_v2.order_utils.exchange_order_builder_v2 import ExchangeOrderBuilderV2
from py_clob_client_v2.order_utils.model.order_data_v2 import order_to_json_v2, OrderDataV2
from py_clob_client_v2.order_utils.model.side import Side
from py_clob_client_v2.order_utils.model.signature_type_v2 import SignatureTypeV2
from py_clob_client_v2.order_builder.builder import OrderBuilder, ROUNDING_CONFIG
from py_clob_client_v2.order_builder.constants import BUY, SELL
from py_clob_client_v2.signer import Signer
from py_clob_client_v2.signing.hmac import build_hmac_signature
from py_clob_client_v2.signing.eip712 import (
CLOB_DOMAIN_NAME,
CLOB_VERSION,
MSG_TO_SIGN,
sign_clob_auth_message,
)
PRIVATE_KEY = "0x59c6995e998f97a5a0044976f094538f4f0eb6e095e3875038fc2b94efc60e77"
MAKER_PROXY = "0x1111111111111111111111111111111111111111"
TOKEN_ID = "1234567890123456789012345678901234567890123456789012345678901234"
API_SECRET = "c2VjcmV0LWtleS0zMi1ieXRlcy0xMjM0NTY="
def source_metadata(path: str) -> dict:
return {
"client": "Polymarket/py-clob-client-v2",
"commit": source_commit(),
"path": path,
}
def source_commit() -> str:
try:
return subprocess.check_output(
["git", "-C", str(CLIENT_PATH), "rev-parse", "HEAD"],
text=True,
).strip()
except Exception:
return "unknown"
CASES = [
{
"name": "polygon_standard_gnosis_safe_buy",
"chain_id": 137,
"neg_risk": False,
"signature_type": SignatureTypeV2.POLY_GNOSIS_SAFE,
"maker": MAKER_PROXY,
"side": Side.BUY,
"salt": "170000000000000000000000000000000000000001",
"maker_amount": "5500000",
"taker_amount": "10000000",
"timestamp": "1712345678123",
"expiration": "0",
"metadata": BYTES32_ZERO,
"builder": BYTES32_ZERO,
"order_type": "GTC",
},
{
"name": "polygon_neg_risk_gnosis_safe_sell",
"chain_id": 137,
"neg_risk": True,
"signature_type": SignatureTypeV2.POLY_GNOSIS_SAFE,
"maker": MAKER_PROXY,
"side": Side.SELL,
"salt": "170000000000000000000000000000000000000002",
"maker_amount": "7500000",
"taker_amount": "3375000",
"timestamp": "1712345678456",
"expiration": "0",
"metadata": "0x" + "12" * 32,
"builder": BYTES32_ZERO,
"order_type": "FOK",
},
{
"name": "amoy_standard_eoa_buy",
"chain_id": 80002,
"neg_risk": False,
"signature_type": SignatureTypeV2.EOA,
"maker": None,
"side": Side.BUY,
"salt": "170000000000000000000000000000000000000003",
"maker_amount": "1250000",
"taker_amount": "5000000",
"timestamp": "1712345678789",
"expiration": "0",
"metadata": BYTES32_ZERO,
"builder": "0x" + "34" * 32,
"order_type": "GTC",
},
]
BUILDER_AMOUNT_CASES = [
{"name": "limit_buy_tick_0_01", "kind": "limit", "side": BUY, "size_or_amount": 21.04, "price": 0.56, "tick_size": "0.01"},
{"name": "limit_sell_tick_0_001", "kind": "limit", "side": SELL, "size_or_amount": 21.04, "price": 0.056, "tick_size": "0.001"},
{"name": "limit_buy_precision", "kind": "limit", "side": BUY, "size_or_amount": 20.0, "price": 0.82, "tick_size": "0.01"},
{"name": "market_buy_tick_0_01", "kind": "market", "side": BUY, "size_or_amount": 21.04, "price": 0.56, "tick_size": "0.01"},
{"name": "market_sell_tick_0_0001", "kind": "market", "side": SELL, "size_or_amount": 10, "price": 0.0056, "tick_size": "0.0001"},
]
BUILDER_ORDER_CASES = [
{
"name": "limit_buy_standard_gnosis_safe",
"kind": "limit",
"side": BUY,
"size_or_amount": 21.04,
"price": 0.56,
"tick_size": "0.01",
"neg_risk": False,
"signature_type": SignatureTypeV2.POLY_GNOSIS_SAFE,
"maker": MAKER_PROXY,
"salt": "180000000000000000000000000000000000000001",
"timestamp": "1712345680001",
"expiration": "1234567",
"metadata": BYTES32_ZERO,
"builder": "0x" + "ab" * 32,
},
{
"name": "limit_sell_neg_risk_gnosis_safe",
"kind": "limit",
"side": SELL,
"size_or_amount": 21.04,
"price": 0.056,
"tick_size": "0.001",
"neg_risk": True,
"signature_type": SignatureTypeV2.POLY_GNOSIS_SAFE,
"maker": MAKER_PROXY,
"salt": "180000000000000000000000000000000000000002",
"timestamp": "1712345680002",
"expiration": "0",
"metadata": "0x" + "12" * 32,
"builder": BYTES32_ZERO,
},
{
"name": "market_buy_standard_eoa",
"kind": "market",
"side": BUY,
"size_or_amount": 21.04,
"price": 0.56,
"tick_size": "0.01",
"neg_risk": False,
"signature_type": SignatureTypeV2.EOA,
"maker": None,
"salt": "180000000000000000000000000000000000000003",
"timestamp": "1712345680003",
"expiration": "0",
"metadata": BYTES32_ZERO,
"builder": "0x" + "cd" * 32,
},
]
def order_fixture(case: dict) -> dict:
signer = Signer(PRIVATE_KEY, case["chain_id"])
config = get_contract_config(case["chain_id"])
contract = config.neg_risk_exchange_v2 if case["neg_risk"] else config.exchange_v2
maker = case["maker"] or signer.address()
builder = ExchangeOrderBuilderV2(
contract_address=contract,
chain_id=case["chain_id"],
signer=signer,
generate_salt=lambda: case["salt"],
)
data = OrderDataV2(
maker=maker,
signer=signer.address(),
tokenId=TOKEN_ID,
makerAmount=case["maker_amount"],
takerAmount=case["taker_amount"],
side=case["side"],
signatureType=case["signature_type"],
timestamp=case["timestamp"],
metadata=case["metadata"],
builder=case["builder"],
expiration=case["expiration"],
)
order = builder.build_order(data)
typed_data = builder.build_order_typed_data(order)
signature = builder.build_order_signature(typed_data)
order_hash = builder.build_order_hash(typed_data)
signed = builder.build_signed_order(data)
return {
"name": case["name"],
"source": source_metadata("py_clob_client_v2/order_utils/exchange_order_builder_v2.py"),
"private_key": PRIVATE_KEY,
"signer_address": signer.address(),
"chain_id": case["chain_id"],
"neg_risk": case["neg_risk"],
"exchange_address": contract,
"order_type": case["order_type"],
"typed_data": typed_data,
"unsigned_order": dataclasses.asdict(order),
"signature": signature,
"order_hash": order_hash,
"signed_order": dataclasses.asdict(signed),
"post_body": order_to_json_v2(
signed,
owner=maker,
order_type=case["order_type"],
),
}
def amount_fixture(case: dict) -> dict:
signer = Signer(PRIVATE_KEY, 137)
builder = OrderBuilder(signer)
round_config = ROUNDING_CONFIG[case["tick_size"]]
if case["kind"] == "limit":
side, maker_amount, taker_amount = builder.get_order_amounts(
case["side"], case["size_or_amount"], case["price"], round_config
)
else:
side, maker_amount, taker_amount = builder.get_market_order_amounts(
case["side"], case["size_or_amount"], case["price"], round_config
)
return {
"name": case["name"],
"kind": case["kind"],
"source": source_metadata("py_clob_client_v2/order_builder/builder.py"),
"input": {
"side": case["side"],
"size_or_amount": case["size_or_amount"],
"price": case["price"],
"tick_size": case["tick_size"],
},
"output": {
"side": int(side),
"maker_amount": str(maker_amount),
"taker_amount": str(taker_amount),
},
}
def builder_order_fixture(case: dict) -> dict:
signer = Signer(PRIVATE_KEY, 137)
amount = amount_fixture(case)["output"]
config = get_contract_config(137)
contract = config.neg_risk_exchange_v2 if case["neg_risk"] else config.exchange_v2
maker = case["maker"] or signer.address()
data = OrderDataV2(
maker=maker,
signer=signer.address(),
tokenId=TOKEN_ID,
makerAmount=amount["maker_amount"],
takerAmount=amount["taker_amount"],
side=Side(int(amount["side"])),
signatureType=case["signature_type"],
timestamp=case["timestamp"],
metadata=case["metadata"],
builder=case["builder"],
expiration=case["expiration"],
)
builder = ExchangeOrderBuilderV2(
contract_address=contract,
chain_id=137,
signer=signer,
generate_salt=lambda: case["salt"],
)
order = builder.build_order(data)
typed_data = builder.build_order_typed_data(order)
signed = builder.build_signed_order(data)
return {
"name": case["name"],
"kind": case["kind"],
"source": source_metadata("py_clob_client_v2/order_builder/builder.py"),
"private_key": PRIVATE_KEY,
"chain_id": 137,
"exchange_address": contract,
"input": {
"side": case["side"],
"size_or_amount": case["size_or_amount"],
"price": case["price"],
"tick_size": case["tick_size"],
"neg_risk": case["neg_risk"],
"signature_type": int(case["signature_type"]),
"maker": maker,
"salt": case["salt"],
"timestamp": case["timestamp"],
"expiration": case["expiration"],
"metadata": case["metadata"],
"builder": case["builder"],
},
"amounts": amount,
"typed_data": typed_data,
"unsigned_order": dataclasses.asdict(order),
"signature": builder.build_order_signature(typed_data),
"order_hash": builder.build_order_hash(typed_data),
"signed_order": dataclasses.asdict(signed),
}
def auth_fixture() -> dict:
signer = Signer(PRIVATE_KEY, 137)
timestamp = 1712345678
nonce = 7
return {
"name": "polygon_clob_auth_nonce_7",
"source": source_metadata("py_clob_client_v2/signing/eip712.py"),
"private_key": PRIVATE_KEY,
"address": signer.address(),
"chain_id": 137,
"timestamp": timestamp,
"nonce": nonce,
"domain": {
"name": CLOB_DOMAIN_NAME,
"version": CLOB_VERSION,
"chainId": 137,
},
"message": {
"address": signer.address(),
"timestamp": str(timestamp),
"nonce": nonce,
"message": MSG_TO_SIGN,
},
"signature": sign_clob_auth_message(signer, timestamp, nonce),
"headers": {
"POLY_ADDRESS": signer.address(),
"POLY_SIGNATURE": sign_clob_auth_message(signer, timestamp, nonce),
"POLY_TIMESTAMP": str(timestamp),
"POLY_NONCE": str(nonce),
},
}
def hmac_fixture() -> dict:
timestamp = "1712345678"
method = "POST"
request_path = "/order"
body = '{"order":{"tokenId":"123","side":"BUY"},"orderType":"GTC"}'
return {
"name": "l2_hmac_post_order",
"source": source_metadata("py_clob_client_v2/signing/hmac.py"),
"address": Signer(PRIVATE_KEY, 137).address(),
"api_key": "fixture-api-key",
"api_secret": API_SECRET,
"api_passphrase": "fixture-passphrase",
"timestamp": timestamp,
"method": method,
"request_path": request_path,
"body": body,
"signature": build_hmac_signature(API_SECRET, timestamp, method, request_path, body),
"headers": {
"POLY_ADDRESS": Signer(PRIVATE_KEY, 137).address(),
"POLY_SIGNATURE": build_hmac_signature(
API_SECRET, timestamp, method, request_path, body
),
"POLY_TIMESTAMP": timestamp,
"POLY_API_KEY": "fixture-api-key",
"POLY_PASSPHRASE": "fixture-passphrase",
},
}
def write_json(path: Path, payload: dict) -> None:
path.write_text(json.dumps(jsonable(payload), indent=2, sort_keys=True) + "\n")
def jsonable(value):
if isinstance(value, bytes):
return "0x" + value.hex()
if isinstance(value, dict):
return {key: jsonable(item) for key, item in value.items()}
if isinstance(value, list):
return [jsonable(item) for item in value]
if hasattr(value, "__int__") and value.__class__.__name__.endswith("TypeV2"):
return int(value)
if hasattr(value, "__int__") and value.__class__.__name__ == "Side":
return int(value)
return value
def main() -> None:
out_dir = Path(__file__).resolve().parents[1] / "test" / "fixtures" / "parity"
out_dir.mkdir(parents=True, exist_ok=True)
write_json(out_dir / "clob_auth.json", auth_fixture())
write_json(out_dir / "l2_hmac.json", hmac_fixture())
orders = [order_fixture(case) for case in CASES]
write_json(
out_dir / "orders.json",
{
"source": {
"client": "Polymarket/py-clob-client-v2",
"commit": source_commit(),
"path": "py_clob_client_v2/order_utils/exchange_order_builder_v2.py",
},
"fixtures": orders,
},
)
write_json(
out_dir / "order_builder.json",
{
"source": source_metadata("py_clob_client_v2/order_builder/builder.py"),
"amount_fixtures": [amount_fixture(case) for case in BUILDER_AMOUNT_CASES],
"order_fixtures": [builder_order_fixture(case) for case in BUILDER_ORDER_CASES],
},
)
if __name__ == "__main__":
main()