Polymarket Bot Tutorial · Розділ 9 із 32
Читаємо on-chain data Polymarket безпосередньо: баланси USDC/pUSD, reads контракту CTF для supply outcome, події UMA Optimistic Oracle щодо proposed/disputed, а також Polygon transaction logs - з кодом.
Що охоплює цей розділ
API Polymarket зручні, але зрештою є лише eventually consistent. Chain - це джерело істини. У цьому розділі розбираємо on-chain reads, які production bot використовує, щоб перевіряти власний облік: баланси pUSD, inventory outcome-токенів, події спорів UMA та стан контракту CTF. Підхід, до якого сходяться більшість production bot, - API-first для швидкості плюс періодична on-chain reconciliation для коректності.
- Що живе on-chain (vs у CLOB)
- Адреса контракту pUSD та ABI
- Conditional Tokens Framework (CTF)
- UMA Optimistic Oracle: події proposed і disputed
- Читання Polygon event logs (web3.py / ethers)
- Коли читати on-chain, а коли довіряти API
- Код: визначити UMA dispute через event subscription
Що живе on-chain (vs у CLOB)
Дві state machine, дві правди.
On-chain (Polygon): баланси pUSD, inventory outcome-токенів (ERC-1155 supply на кожен token), allowance approvals, події propose та dispute в UMA Optimistic Oracle, події deposit і withdrawal. З часом все стає коректним; latency становить один блок Polygon - приблизно 2 секунди.
CLOB (Polymarket API): order book, recent trades, pending limit orders, підтвердження matches. У реальному часі, але eventually consistent - match підтверджується раніше, ніж ERC-1155 settle, що й породжує phantom-fill problem, описану в розділі 12.
Узгодження між ними має зрештою відбуватися завжди. Коли вони розходяться, chain є джерелом істини. Bot, який довіряє лише CLOB, буде drift; bot, який довіряє лише chain, торгуватиме повільно. Production code використовує обидва: CLOB - для рішень, чутливих до швидкості, chain - для періодичної reconciliation.
Адреса контракту pUSD та ABI
pUSD - це stablecoin wrapper Polymarket, який використовується з міграції V2 у 2025 році. Контракт за адресою 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB у Polygon mainnet є стандартним ERC-20.
Три важливі reads для bot:
balanceOf(proxy)- ваш доступний для витрат pUSD. Порівнюйте з тим, як CLOB бачить ваш balance, при кожному restart.allowance(proxy, exchange_contract)- чи можуть контракти exchange CTF/NegRisk витрачати ваш pUSD. Потрібно для order matching.- Підписка на подію
Transfer- виявляє deposit і withdrawal без polling.
USDC (0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) і далі лишається off-ramp pair. Більшості bot потрібні лише reads pUSD; USDC важливий тільки під час циклів deposit/withdrawal.
Conditional Tokens Framework (CTF)
Outcome shares - це токени ERC-1155, які mint-ить Gnosis Conditional Tokens Framework (CTF). Контракт CTF у Polygon за адресою 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 відстежує supply для кожного position-id.
Три reads:
balanceOf(proxy, position_id)- скільки outcome token ви фактично тримаєте для цього market+outcome.getOutcomeSlotCount(condition_id)- кількість outcomes (2 для binary, N для multi-outcome).payoutNumerators,payoutDenominator- встановлюються, коли UMA розв’язує market. Читаючи їх, ви дізнаєтеся, яка сторона виграла, ще до оновлення UI в CLOB.
Position_id - це hash від (condition_id, outcome_index). Обчислюйте його на клієнті через helper getPositionId у CTF або відтворіть keccak-математику у своєму стеку.
UMA Optimistic Oracle: події proposed і disputed
Optimistic Oracle (OO) від UMA обробляє всю dispute resolution Polymarket. Є дві події, на які ваш bot може підписатися.
ProposePrice(requester, identifier, timestamp, ancillaryData, proposer, proposedPrice)- спрацьовує, коли bot Polymarket пропонує outcome.DisputePrice(requester, identifier, timestamp, ancillaryData, disputer)- спрацьовує, коли хтось оскаржує запропонований outcome.
Адреса контракту OO: 0xeE3Afe347D5C74317041E2618C49534dAf887c24. Фільтруйте за адресою requester Polymarket.
Навіщо підписуватися: dispute означає, що resolution тепер оскаржується і потребуватиме голосування UMA протягом 24–72 годин. У цей період market може бути призупинений для торгівлі. Bot, який тримає позиції в disputed market, має знати про це негайно.
Читання Polygon event logs (web3.py / ethers)
Дві референсні реалізації.
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 });
});
Для безперервного моніторингу використовуйте WebSocket transport (wss://...) замість HTTP polling - менше запитів і швидша доставка. Більшість платних RPC providers включають WebSocket у entry tiers.
Коли читати on-chain, а коли довіряти API
Практичні правила з production.
- Довіряйте API для: real-time order book, recent trades, ваших власних pending orders, market metadata (slug, question, end date), discovery events/markets.
- Довіряйте chain для: вашого власного балансу pUSD, вашого власного inventory outcome-токенів, перевірки deposit і withdrawal, результатів resolution, стану dispute.
- Перевіряйте обидва для: будь-якої фінансової дії, яку bot записав як fill - зіставляйте подію API “matched” з CTF transfer у chain, щоб підтвердити settlement.
Правило 5 секунд очікування після buy (розділ 12) - це on-chain reality, що втручається в API time. GTC sell, поданий одразу після market buy, побачить balance: 0 у chain check, навіть якщо CLOB щойно підтвердив match.
Код: визначити UMA dispute через event subscription
Референс: відстежуйте пов’язані з Polymarket спори UMA в реальному часі.
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-подібний текст, що містить запитання market. Його декодування дає вам identifier, еквівалентний slug, щоб зіставити його з вашими open positions.










