Polymarket Bot Tutorial · 第32章中9章目
Polymarketのオンチェーンデータを直接読む:USDC/pUSD残高、結果供給のためのCTFコントラクト読み取り、UMA Optimistic Oracleのproposed/disputedイベント、Polygonのトランザクションログ - コード付き。
この章で扱う内容
PolymarketのAPIは便利ですが、最終的整合性です。チェーンが正です。この章では、本番Botが自分の記帳を検証するために使うオンチェーン読み取りを解説します:pUSD残高、結果トークンの在庫、UMAの紛争イベント、CTFコントラクト状態です。本番Botの多くがたどり着くパターンは、高速化のためのAPI優先と、正確性のための定期的なオンチェーン再照合です。
- オンチェーンにあるもの(CLOBと比較して)
- pUSDコントラクトアドレスとABI
- Conditional Tokens Framework(CTF)
- UMA Optimistic Oracle: proposed と disputed イベント
- Polygonイベントログの読み取り(web3.py / ethers)
- いつオンチェーンを読むか、いつAPIを信頼するか
- コード:イベント購読でUMAの紛争を検出する
オンチェーンにあるもの(CLOBと比較して)
2つのステートマシン、2つの真実。
オンチェーン(Polygon): pUSD残高、結果トークンの在庫(トークンごとのERC-1155供給量)、allowanceの承認、UMA Optimistic Oracleの提案と紛争、入出金イベント。最終的には正しくなるが、遅延はPolygon 1ブロック分(約2秒)です。
CLOB(Polymarket API): 注文板、最近の約定、保留中の指値注文、マッチの確認応答。リアルタイムですが最終的整合性です。ERC-1155が確定する前にマッチが確認されるため、12章で扱う phantom-fill 問題が発生します。
この2つは常に収束すべきです。ずれるときは、チェーンが正です。CLOBだけを信じるBotはずれていき、チェーンだけを信じるBotは遅くなります。本番コードでは両方を使います。速度が重要な判断にはCLOBを、正確性のための定期再照合にはチェーンを使います。
pUSDコントラクトアドレスとABI
pUSDは、2025年のV2移行以降に使われているPolymarketのステーブルコインラッパーです。Polygonメインネット上の 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB にあるコントラクトは標準的なERC-20です。
Botにとって重要な3つの読み取り:
balanceOf(proxy)-実際に使えるpUSD残高。起動のたびにCLOBの残高表示と比較します。allowance(proxy, exchange_contract)-CTF/NegRiskのexchangeコントラクトがpUSDを使えるかどうか。注文マッチに必要です。Transferイベント購読-ポーリングなしで入出金を検出します。
USDC(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174)は引き続きオフランプのペアです。多くのBotではpUSDの読み取りだけで十分で、USDCが必要になるのは入出金サイクルのときだけです。
Conditional Tokens Framework(CTF)
結果シェアは、GnosisのConditional Tokens Framework(CTF)が発行するERC-1155トークンです。Polygon上の 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 にあるCTFコントラクトは、position-idごとの供給量を追跡します。
3つの読み取り:
balanceOf(proxy, position_id)-その市場+結果について、実際に保有している結果トークン数。getOutcomeSlotCount(condition_id)-結果の数(バイナリなら2、多結果ならN)。payoutNumerators,payoutDenominator-UMAが市場を解決したときに設定されます。これを読むと、CLOBのUIが更新される前にどちらが勝ったか分かります。
position_idは、(condition_id, outcome_index) のハッシュです。CTFの getPositionId ヘルパーを使ってクライアント側で計算するか、スタック内でkeccak計算を再実装してください。
UMA Optimistic Oracle: proposed と disputed イベント
UMAのOptimistic Oracle(OO)が、Polymarketのすべての紛争解決を処理します。Botが購読したい可能性のあるイベントは2つです。
ProposePrice(requester, identifier, timestamp, ancillaryData, proposer, proposedPrice)-Polymarket Botが結果を提案したときに発火します。DisputePrice(requester, identifier, timestamp, ancillaryData, disputer)-誰かが提案された結果に異議を唱えたときに発火します。
OOコントラクトアドレス: 0xeE3Afe347D5C74317041E2618C49534dAf887c24。Polymarketのrequesterアドレスでフィルタしてください。
購読する理由:紛争が起きると、その解決は争点化され、24〜72時間のUMA投票が必要になります。その間、市場は取引停止になることがあります。紛争中の市場でポジションを持つBotは、すぐにそれを知るべきです。
Polygonイベントログの読み取り(web3.py / ethers)
2つの参考実装です。
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ポーリングではなくWebSocketトランスポート(wss://...)を使ってください。リクエスト数が減り、配信も速くなります。多くの有料RPCプロバイダーは、エントリーレベルでもWebSocketを含んでいます。
いつオンチェーンを読むか、いつAPIを信頼するか
本番運用から得た実践ルールです。
- APIを信頼する: リアルタイムの注文板、最近の約定、保留中の自分の注文、マーケットメタデータ(slug、question、終了日)、イベント/マーケットの探索。
- チェーンを信頼する: 自分のpUSD残高、自分の結果トークン在庫、入出金の検証、解決結果、紛争状態。
- 両方を突き合わせる: Botが約定として記録した金額に関わるすべて。APIの「matched」イベントとチェーン上のCTF転送を照合して、確定を確認します。
購入後に5秒待つルール(12章)は、API時間にオンチェーンの現実が割り込む例です。市場買いの直後にGTC売りを出すと、CLOBでは数瞬前にマッチしていても、チェーン確認では balance: 0 になります。
コード:イベント購読でUMAの紛争を検出する
参考例: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 フィールドは、マーケットのquestionを含むhexエンコードされたJSON風テキストです。これをデコードすると、保有中ポジションと照合するためのslug相当の識別子を復元できます。












