Polymarket Bot-tutorial · Hoofdstuk 9 van 32
Lees Polymarket on-chain data direct: USDC/pUSD balances, CTF contract-reads voor outcome supply, UMA Optimistic Oracle proposed/disputed events en Polygon transaction logs - met code.
Wat dit hoofdstuk behandelt
Polymarkets API's zijn handig maar eventually consistent. De chain is gezaghebbend. Dit hoofdstuk wandelt door de on-chain reads die een productie-bot gebruikt om eigen boekhouding te verifiëren: pUSD balances, outcome-token inventory, UMA dispute events en CTF contract state. Het patroon waar de meeste productie-bots op convergeren is API-first voor snelheid plus periodieke on-chain reconciliation voor correctheid.
Dit is hoofdstuk 9 van onze 32-delige serie over het bouwen van een Polymarket trading bot. We behandelen het onderwerp in detail in de secties hieronder. De body content voor elke sectie wordt geschreven en hoofdstuk-per-hoofdstuk uitgerold; FAQ-antwoorden en referenties zijn al compleet en weerspiegelen production-ervaring van het draaien van onze eigen trader.
- Wat on-chain leeft (vs in CLOB)
- pUSD contract-adres en ABI
- Conditional Tokens Framework (CTF)
- UMA Optimistic Oracle: proposed en disputed events
- Polygon event logs lezen (web3.py / ethers)
- Wanneer on-chain te lezen vs de API vertrouwen
- Code: detecteer een UMA dispute via event-subscription
Wat on-chain leeft (vs in CLOB)
Twee state machines, twee waarheden.
On-chain (Polygon): pUSD balances, outcome-token inventory (ERC-1155 supply per token), allowance approvals, UMA Optimistic Oracle proposals en disputes, deposit- en withdrawal-events. Uiteindelijk correct; latency is één Polygon block (~2 seconden).
CLOB (Polymarket API): order book, recente trades, pending limit orders, match acknowledgments. Real-time maar eventually consistent - een match wordt erkend voordat de ERC-1155 settled, wat het phantom-fill probleem produceert behandeld in hoofdstuk 12.
De twee moeten altijd convergeren. Wanneer ze divergeren, is de chain gezaghebbend. Een bot die alleen de CLOB vertrouwt drift; een bot die alleen de chain vertrouwt trade traag. Productiecode gebruikt beide: CLOB voor speed-critical beslissingen, chain voor periodieke reconciliation.
pUSD contract-adres en ABI
pUSD is Polymarkets stablecoin wrapper in gebruik sinds de 2026 V2-migratie. Het contract op 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB op Polygon mainnet is een standaard ERC-20.
Drie reads die tellen voor een bot:
balanceOf(proxy)- je besteedbare pUSD. Vergelijk tegen de view van de CLOB van je balance op elke restart.allowance(proxy, exchange_contract)- of de CTF/NegRisk exchange-contracten je pUSD kunnen besteden. Vereist voor order matching.Transferevent-subscription - detecteert deposits en withdrawals zonder polling.
USDC (0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) blijft het off-ramp pair. De meeste bots hebben alleen pUSD-reads nodig; USDC telt alleen tijdens deposit/withdrawal-cycli.
Conditional Tokens Framework (CTF)
Outcome shares zijn ERC-1155 tokens minted door Gnosis's Conditional Tokens Framework (CTF). Het CTF-contract op Polygon op 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 trackt per-position-id supply.
Drie reads:
balanceOf(proxy, position_id)- hoeveel outcome tokens je daadwerkelijk houdt voor dat market+outcome.getOutcomeSlotCount(condition_id)- aantal uitkomsten (2 voor binary, N voor multi-outcome).payoutNumerators,payoutDenominator- gezet wanneer UMA de markt resolved. Deze lezen vertelt je welke zijde won voordat de CLOB UI updatet.
De position_id is een hash van (condition_id, outcome_index). Bereken hem client-side via de CTF's getPositionId helper of repliceer de keccak-math in je stack.
UMA Optimistic Oracle: proposed en disputed events
UMA's Optimistic Oracle (OO) handelt alle Polymarket dispute resolution af. Twee events waar je bot op zou willen abonneren.
ProposePrice(requester, identifier, timestamp, ancillaryData, proposer, proposedPrice)- gefired wanneer een Polymarket-bot een outcome voorstelt.DisputePrice(requester, identifier, timestamp, ancillaryData, disputer)- gefired wanneer iemand de voorgestelde outcome uitdaagt.
OO contract-adres: 0xeE3Afe347D5C74317041E2618C49534dAf887c24. Filter op Polymarkets requester-adres.
Waarom abonneren: een dispute betekent dat de resolution nu betwist is en een 24-72h UMA-vote vereist. Tijdens dat venster kan de markt gepauzeerd zijn voor trading. Een bot die posities houdt op een disputed markt moet het direct weten.
Polygon event logs lezen (web3.py / ethers)
Twee referentie-implementaties.
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 });
});
Voor continue monitoring gebruik WebSocket transport (wss://...) in plaats van HTTP-polling - minder requests en snellere delivery. De meeste betaalde RPC providers includeren WebSocket op entry-tiers.
Wanneer on-chain te lezen vs de API vertrouwen
Praktische regels uit productie.
- Vertrouw de API voor: real-time order book, recente trades, je eigen pending orders, market-metadata (slug, vraag, end date), event/market discovery.
- Vertrouw de chain voor: je eigen pUSD-balance, je eigen outcome-token inventory, deposit- en withdrawal-verificatie, resolution outcomes, dispute state.
- Cross-check beide voor: alles financieel dat de bot vastlegde als een fill - match het "matched" event van de API tegen de CTF-transfer van de chain om settlement te bevestigen.
De 5-seconden-wacht regel na een buy (hoofdstuk 12) is de on-chain realiteit die de API-tijd binnendringt. Een GTC sell ingediend direct na een market buy zal balance: 0 zien vanaf de chain-check ook al matched de CLOB momenten geleden.
Code: detecteer een UMA dispute via event-subscription
Referentie: kijk naar Polymarket-gerelateerde UMA disputes in 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)
Het ancillaryData veld is hex-encoded JSON-achtige tekst die de vraag van de markt bevat. Het decoderen geeft je de slug-equivalente identifier om cross-te-referencen tegen je open posities.


