Polymarket Bot Tutorial · 32개 챕터 중 9장
Polymarket on-chain data를 직접 읽어보세요: USDC/pUSD balances, outcome supply를 위한 CTF contract reads, UMA Optimistic Oracle의 proposed/disputed events, 그리고 Polygon transaction logs - 코드와 함께.
이 장에서 다루는 내용
Polymarket의 APIs는 편리하지만 eventually consistent합니다. 체인이 authoritative source입니다. 이 장에서는 production bot이 자체 bookkeeping을 검증하기 위해 사용하는 on-chain reads를 살펴봅니다: pUSD balances, outcome-token inventory, UMA dispute events, 그리고 CTF contract state. 대부분의 production bot이 수렴하는 패턴은 속도를 위한 API-first와 정확성을 위한 주기적인 on-chain reconciliation입니다.
- on-chain에 있는 것들 (vs CLOB)
- pUSD contract address와 ABI
- Conditional Tokens Framework (CTF)
- UMA Optimistic Oracle: proposed 및 disputed events
- Polygon event logs 읽기 (web3.py / ethers)
- 언제 on-chain을 읽고 언제 API를 신뢰할지
- Code: event subscription으로 UMA dispute 감지
on-chain에 있는 것들 (vs CLOB)
두 개의 state machine, 두 개의 truth.
On-chain (Polygon): pUSD balances, outcome-token inventory (토큰별 ERC-1155 supply), allowance approvals, UMA Optimistic Oracle proposals와 disputes, deposit 및 withdrawal events. 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가 settle되기 전에 acknowledged되며, 이로 인해 12장에서 다루는 phantom-fill 문제가 발생합니다.
둘은 항상 수렴해야 합니다. 서로 다를 때는 체인이 authoritative합니다. CLOB만 신뢰하는 bot은 drift하게 되고, 체인만 신뢰하는 bot은 느리게 거래하게 됩니다. production code는 둘 다 사용합니다: 속도가 중요한 결정에는 CLOB를, 정확성을 위한 정기적 reconciliation에는 chain을 사용합니다.
pUSD contract address와 ABI
pUSD는 2025년 V2 migration 이후 사용되는 Polymarket의 stablecoin wrapper입니다. Polygon mainnet의 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB에 있는 contract는 표준 ERC-20입니다.
bot에 중요한 세 가지 read:
balanceOf(proxy)-실제로 쓸 수 있는 pUSD입니다. 모든 restart마다 CLOB가 보는 balance와 비교하세요.allowance(proxy, exchange_contract)-CTF/NegRisk exchange contracts가 pUSD를 사용할 수 있는지 여부입니다. order matching에 필요합니다.Transferevent subscription-polling 없이 deposit과 withdrawal을 감지합니다.
USDC(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174)는 여전히 off-ramp pair로 남아 있습니다. 대부분의 bot은 pUSD reads만 필요하고, USDC는 deposit/withdrawal cycle 동안에만 중요합니다.
Conditional Tokens Framework (CTF)
Outcome shares는 Gnosis의 Conditional Tokens Framework (CTF)가 mint하는 ERC-1155 tokens입니다. Polygon의 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045에 있는 CTF contract는 position-id별 supply를 추적합니다.
세 가지 read:
balanceOf(proxy, position_id)-해당 market+outcome에 대해 실제로 보유한 outcome tokens 수입니다.getOutcomeSlotCount(condition_id)-outcome 수입니다(binary는 2, multi-outcome은 N).payoutNumerators,payoutDenominator-UMA가 market을 resolve할 때 설정됩니다. 이를 읽으면 CLOB UI가 업데이트되기 전에 어떤 side가 이겼는지 알 수 있습니다.
position_id는 (condition_id, outcome_index)의 hash입니다. CTF의 getPositionId helper를 사용하거나, stack에서 keccak math를 직접 재현해 client-side로 계산하세요.
UMA Optimistic Oracle: proposed 및 disputed events
UMA의 Optimistic Oracle (OO)은 모든 Polymarket dispute resolution을 처리합니다. bot이 subscribe하고 싶을 수 있는 두 가지 event가 있습니다.
ProposePrice(requester, identifier, timestamp, ancillaryData, proposer, proposedPrice)-Polymarket bot이 outcome을 propose할 때 발생합니다.DisputePrice(requester, identifier, timestamp, ancillaryData, disputer)-누군가 proposed outcome에 이의를 제기할 때 발생합니다.
OO contract address: 0xeE3Afe347D5C74317041E2618C49534dAf887c24. Polymarket의 requester address로 필터링하세요.
왜 subscribe해야 하는가: dispute는 resolution이 이제 contested 상태이며 24-72시간의 UMA vote가 필요하다는 뜻입니다. 그 기간 동안 market은 거래가 paused될 수 있습니다. disputed market에 position을 보유한 bot은 즉시 알아야 합니다.
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 });
});
지속적인 모니터링에는 HTTP polling 대신 WebSocket transport(wss://...)를 사용하세요-요청 수가 줄고 delivery가 더 빠릅니다. 대부분의 유료 RPC provider는 entry tier에서도 WebSocket을 포함합니다.
언제 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, 본인의 outcome-token inventory, deposit 및 withdrawal 검증, resolution outcomes, dispute state.
- 둘 다 교차 확인할 것: bot이 fill로 기록한 모든 금융 정보-API의 "matched" event와 chain의 CTF transfer를 대조해 settlement를 확인하세요.
구매 후 5초 기다리기 규칙(12장)은 API 시간에 on-chain 현실이 끼어드는 현상입니다. market buy 직후 submitted 된 GTC sell은 CLOB가 방금 matched했더라도 chain check에서 balance: 0을 보게 됩니다.
Code: event subscription으로 UMA dispute 감지
Reference: Polymarket 관련 UMA dispute를 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 필드는 market question이 들어 있는 hex-encoded JSON-ish text입니다. 이를 decode하면 open positions와 cross-reference할 수 있는 slug-equivalent identifier를 얻을 수 있습니다.












