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 2025 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.











