Polymarket Bot Tutorial · Chapter 9 of 32
دادههای on-chain Polymarket را مستقیماً بخوانید: موجودیهای USDC/pUSD، خواندنهای CTF contract برای عرضه outcome، رویدادهای proposed/disputed در UMA Optimistic Oracle، و Polygon transaction logs - همراه با code.
این chapter چه چیزهایی را پوشش میدهد
APIهای Polymarket راحت هستند، اما در نهایت eventually consistent میشوند. chain مرجع نهایی است. این chapter خواندنهای on-chain را مرور میکند که یک bot production برای verify کردن bookkeeping خودش از آنها استفاده میکند: pUSD balances، موجودی outcome-token، رویدادهای UMA dispute، و وضعیت CTF contract. الگویی که بیشتر botهای production به آن میرسند این است: API-first برای سرعت، بههمراه on-chain reconciliation دورهای برای correctness.
- چه چیزهایی on-chain هستند (در برابر CLOB)
- آدرس contract و ABI برای pUSD
- Conditional Tokens Framework (CTF)
- UMA Optimistic Oracle: proposed و disputed events
- خواندن Polygon event logs (web3.py / ethers)
- چه زمانی on-chain بخوانیم و چه زمانی به API اعتماد کنیم
- code: تشخیص UMA dispute با استفاده از event subscription
چه چیزهایی on-chain هستند (در برابر CLOB)
دو state machine، دو truth.
On-chain (Polygon): pUSD balances، موجودی outcome-token (عرضه ERC-1155 برای هر token)، allowance approvals، proposalها و disputeهای UMA Optimistic Oracle، و رویدادهای deposit و withdrawal. در نهایت correct میشود؛ latency آن برابر با یک Polygon block است (حدود 2 ثانیه).
CLOB (Polymarket API): order book، recent trades، pending limit orders، match acknowledgments. Real-time اما eventually consistent - یک match قبل از تسویه ERC-1155 تأیید میشود و این همان phantom-fill problem است که در chapter 12 پوشش داده میشود.
این دو باید همیشه به هم converge کنند. وقتی diverge میشوند، chain authoritative است. بoti که فقط به CLOB اعتماد کند drift میکند؛ botی که فقط به chain اعتماد کند آهسته معامله میکند. code production از هر دو استفاده میکند: CLOB برای تصمیمهای حساس به سرعت، chain برای reconciliation دورهای.
آدرس contract و ABI برای pUSD
pUSD stablecoin wrapperِ Polymarket است که از migration به V2 در سال 2025 استفاده میشود. contract در Polygon mainnet با آدرس 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB یک standard ERC-20 است.
سه read مهم برای یک bot:
balanceOf(proxy)- pUSD قابلاستفاده شما. در هر restart آن را با viewی که CLOB از موجودی شما دارد compare کنید.allowance(proxy, exchange_contract)- اینکه contractهای exchange مربوط به CTF/NegRisk میتوانند pUSD شما را خرج کنند یا نه. برای order matching لازم است.Transferevent subscription - deposit و withdrawal را بدون polling تشخیص میدهد.
USDC (0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) همچنان off-ramp pair باقی مانده است. بیشتر botها فقط به pUSD reads نیاز دارند؛ USDC فقط در چرخههای deposit/withdrawal اهمیت دارد.
Conditional Tokens Framework (CTF)
Outcome shareها توکنهای ERC-1155 هستند که توسط Conditional Tokens Framework (CTF) گnosis mint میشوند. contract CTF در Polygon با آدرس 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 عرضه را برای هر position-id track میکند.
سه read:
balanceOf(proxy, position_id)- اینکه واقعاً چند outcome token برای آن market+outcome نگه میدارید.getOutcomeSlotCount(condition_id)- تعداد outcomes (2 برای binary، N برای multi-outcome).payoutNumerators,payoutDenominator- وقتی UMA بازار را resolve میکند set میشوند. با خواندن اینها قبل از اینکه UIِ CLOB بهروزرسانی شود میفهمید کدام طرف برنده شده است.
position_id یک hash از (condition_id, outcome_index) است. آن را سمت client با helperِ getPositionId در CTF compute کنید یا math مربوط به keccak را در stack خود replicate کنید.
UMA Optimistic Oracle: proposed و disputed events
Optimistic Oracle (OO) از UMA همه dispute resolutionهای Polymarket را مدیریت میکند. دو event وجود دارد که bot شما ممکن است بخواهد subscribe کند.
ProposePrice(requester, identifier, timestamp, ancillaryData, proposer, proposedPrice)- زمانی fired میشود که یک bot در Polymarket یک outcome را propose میکند.DisputePrice(requester, identifier, timestamp, ancillaryData, disputer)- زمانی fired میشود که کسی outcome پیشنهادی را challenge میکند.
آدرس contract مربوط به OO: 0xeE3Afe347D5C74317041E2618C49534dAf887c24. آن را با requester address مربوط به Polymarket filter کنید.
چرا subscribe کنیم: یک dispute یعنی resolution حالا contested شده و به یک vote در UMA طی 24 تا 72 ساعت نیاز دارد. در این بازه ممکن است بازار برای trading pause شود. botی که روی بازاری disputed position دارد باید فوراً مطلع شود.
خواندن Polygon event logs (web3.py / ethers)
دو 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 });
});
برای monitoring پیوسته از WebSocket transport (wss://...) بهجای HTTP polling استفاده کنید - درخواستهای کمتر و delivery سریعتر. بیشتر providerهای paid RPC در پلنهای entry tier، WebSocket را هم ارائه میدهند.
چه زمانی on-chain بخوانیم و چه زمانی به API اعتماد کنیم
قواعد عملی از production.
- به API اعتماد کنید برای: real-time order book، recent trades، pending orderهای خودتان، market metadata (slug، question، end date)، discovery رویداد/market.
- به chain اعتماد کنید برای: pUSD balance خودتان، outcome-token inventory خودتان، verify کردن deposit و withdrawal، resolution outcomeها، وضعیت dispute.
- هر دو را cross-check کنید برای: هر چیز مالی که bot آن را بهعنوان fill ثبت کرده - event «matched» در API را با transfer مربوط به CTF در chain match کنید تا settlement تأیید شود.
قانون 5 ثانیه انتظار بعد از یک buy (chapter 12) همان realityِ on-chain است که وارد زمان API میشود. یک GTC sell که بلافاصله بعد از market buy ارسال شود، از chain check مقدار balance: 0 را میبیند، حتی اگر CLOB لحظاتی قبل match شده باشد.
code: تشخیص UMA dispute با استفاده از event subscription
Reference: disputeهای Polymarket مربوط به UMA را در real time monitor کنید.
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 است که question بازار را در خود دارد. decode کردن آن به شما identifier معادل slug را میدهد تا با positionهای بازتان cross-reference کنید.












