Polymarket Bot Tutorial · Глава 9 из 32
Читайте on-chain data Polymarket напрямую: балансы USDC/pUSD, CTF contract reads для supply исходов, события UMA Optimistic Oracle proposed/disputed и Polygon transaction logs - с code.
Что охватывает эта глава
APIs Polymarket удобны, но eventually consistent. Истина - в chain. В этой главе рассматриваются on-chain reads, которые production bot использует для проверки собственной бухгалтерии: pUSD balances, inventory outcome-токенов, события споров UMA и state CTF contract. Паттерн, к которому приходят большинство production bots: API-first для скорости плюс periodic on-chain reconciliation для корректности.
- Что живет on-chain (vs in CLOB)
- Адрес и ABI контракта pUSD
- Conditional Tokens Framework (CTF)
- UMA Optimistic Oracle: события proposed и disputed
- Чтение Polygon event logs (web3.py / ethers)
- Когда читать on-chain, а когда доверять API
- Code: определить dispute UMA через event subscription
Что живет on-chain (vs in CLOB)
Две state machines, две истины.
On-chain (Polygon): pUSD balances, inventory outcome-токенов (ERC-1155 supply на каждый token), allowance approvals, предложения и disputes UMA Optimistic Oracle, события deposit и withdrawal. Eventually correct; latency - один Polygon block (~2 секунды).
CLOB (Polymarket API): order book, recent trades, pending limit orders, match acknowledgments. Real-time, но eventually consistent - match подтверждается до того, как ERC-1155 settled, что и создает проблему phantom-fill, описанную в главе 12.
Обе стороны должны всегда converge. Когда они расходятся, chain - authoritative. Bot, который доверяет только CLOB, будет drift; bot, который доверяет только chain, будет торговать медленно. Production code использует оба источника: CLOB для speed-critical решений, chain - для periodic reconciliation.
Адрес и ABI контракта pUSD
pUSD - это wrapper stablecoin Polymarket, используемый с migration V2 в 2025 году. Контракт по адресу 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB в Polygon mainnet - стандартный ERC-20.
Три reads, важные для bot:
balanceOf(proxy)- ваш spendable pUSD. Сравнивайте с view CLOB на ваш balance при каждом restart.allowance(proxy, exchange_contract)- могут ли CTF/NegRisk exchange contracts тратить ваш pUSD. Требуется для order matching.- Подписка на event
Transfer- detects deposits и withdrawals без polling.
USDC (0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) по-прежнему остается off-ramp pair. Большинству bot нужны только pUSD reads; USDC важен только во время циклов deposit/withdrawal.
Conditional Tokens Framework (CTF)
Outcome shares - это ERC-1155 tokens, minted Gnosis Conditional Tokens Framework (CTF). Контракт CTF в Polygon по адресу 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 tracks supply по каждому position-id.
Три reads:
balanceOf(proxy, position_id)- сколько outcome tokens вы реально держите для этого market+outcome.getOutcomeSlotCount(condition_id)- число outcomes (2 для binary, N для multi-outcome).payoutNumerators,payoutDenominator- задаются, когда UMA resolves market. Чтение этого показывает, какая сторона победила, еще до того, как CLOB UI обновится.
position_id - это hash от (condition_id, outcome_index). Вычисляйте его на клиенте через helper getPositionId в CTF или воспроизведите keccak math в своем stack.
UMA Optimistic Oracle: события proposed и disputed
UMA Optimistic Oracle (OO) обрабатывает все dispute resolution в Polymarket. Два события, на которые ваш bot может захотеть подписаться.
ProposePrice(requester, identifier, timestamp, ancillaryData, proposer, proposedPrice)- срабатывает, когда bot Polymarket предлагает outcome.DisputePrice(requester, identifier, timestamp, ancillaryData, disputer)- срабатывает, когда кто-то оспаривает предложенный outcome.
Адрес OO contract: 0xeE3Afe347D5C74317041E2618C49534dAf887c24. Фильтруйте по requester address Polymarket.
Почему стоит подписаться: dispute означает, что resolution теперь contested и потребует 24-72h UMA vote. В течение этого окна market может быть paused для trading. Bot, удерживающий позиции в disputed market, должен узнать об этом немедленно.
Чтение Polygon event logs (web3.py / ethers)
Две reference implementation.
Python (web3.py):
from web3 import Web3
w3 = Web3(Web3.HTTPProvider(POLYGON_RPC))
ctf = w3.eth.contract(address=CTF_ADDR, abi=CTF_ABI)
filter = ctf.events.PayoutRedemption.create_filter(fromBlock="latest")
for event in filter.get_new_entries():
print(event["args"])
Node (ethers v6):
import { ethers } from "ethers";
const p = new ethers.JsonRpcProvider(POLYGON_RPC);
const ctf = new ethers.Contract(CTF_ADDR, CTF_ABI, p);
ctf.on("PayoutRedemption", (redeemer, collateral, parentId, conditionId) => {
console.log("redemption", { redeemer, conditionId });
});
Для continuous monitoring используйте WebSocket transport (wss://...) вместо HTTP polling - меньше requests и быстрее delivery. У большинства paid RPC providers WebSocket входит в entry tiers.
Когда читать on-chain, а когда доверять API
Практические правила из production.
- Доверяйте API для: real-time order book, recent trades, ваших pending orders, market metadata (slug, question, end date), event/market discovery.
- Доверяйте chain для: вашего собственного pUSD balance, вашего собственного inventory outcome-токенов, проверки deposit и withdrawal, outcomes resolution, dispute state.
- Сверяйте оба источника для: любой финансовой операции, которую bot записал как fill - сопоставляйте API-событие "matched" с CTF transfer в chain, чтобы подтвердить settlement.
Правило 5-second-wait после buy (глава 12) - это on-chain reality, врывающаяся в API time. GTC sell, отправленный сразу после market buy, увидит balance: 0 в chain check, хотя CLOB matched его мгновения назад.
Code: определение dispute UMA через event subscription
Reference: отслеживайте связанные с Polymarket disputes UMA в real time.
from web3 import Web3
w3 = Web3(Web3.WebsocketProvider(POLYGON_WSS))
oo = w3.eth.contract(address=UMA_OO_ADDR, abi=UMA_ABI)
POLY_REQUESTER = "0x..." # Polymarket's UMA requester address
def on_dispute(event):
args = event["args"]
if args["requester"].lower() != POLY_REQUESTER.lower(): return
print(f"DISPUTE on Polymarket market: ancillary={args['ancillaryData'][:40]}...")
# decode ancillaryData to recover the market question
event_filter = oo.events.DisputePrice.create_filter(fromBlock="latest")
while True:
for ev in event_filter.get_new_entries():
on_dispute(ev)
time.sleep(2)
Поле ancillaryData - это hex-encoded JSON-ish text, содержащий question рынка. Его decode дает identifier, эквивалентный slug, чтобы сверить его с вашими open positions.












