Polymarket Bot Tutorial · Capítulo 9 de 32
Leia dados on-chain da Polymarket diretamente: saldos de USDC/pUSD, leituras do contrato CTF para supply de outcome, eventos proposed/disputed do UMA Optimistic Oracle e logs de transação da Polygon - com código.
O que este capítulo aborda
As APIs da Polymarket são convenientes, mas eventualmente consistentes. A chain é a autoridade. Este capítulo mostra as leituras on-chain que um bot de produção usa para verificar sua própria contabilidade: saldos de pUSD, inventário de outcome-token, eventos de disputa da UMA e estado do contrato CTF. O padrão para o qual a maioria dos bots de produção converge é API-first para velocidade + reconciliação on-chain periódica para correção.
- O que vive on-chain (vs no CLOB)
- Endereço do contrato pUSD e ABI
- Conditional Tokens Framework (CTF)
- UMA Optimistic Oracle: eventos proposed e disputed
- Leitura de logs de eventos da Polygon (web3.py / ethers)
- Quando ler on-chain vs confiar na API
- Código: detectar uma disputa da UMA via subscription de eventos
O que vive on-chain (vs no CLOB)
Duas state machines, duas verdades.
On-chain (Polygon): saldos de pUSD, inventário de outcome-token (supply ERC-1155 por token), approvals de allowance, propostas e disputas do UMA Optimistic Oracle, eventos de depósito e saque. Eventualmente correto; a latência é de um bloco da Polygon (~2 segundos).
CLOB (Polymarket API): order book, trades recentes, limit orders pendentes, acknowledgments de match. Em tempo real, mas eventualmente consistente - um match é confirmado antes de o ERC-1155 ser liquidado, o que produz o problema de phantom-fill coberto no capítulo 12.
Os dois devem sempre convergir. Quando divergem, a chain é a autoridade. Um bot que confia só no CLOB vai ficar desalinhado; um bot que confia só na chain vai operar devagar. O código de produção usa ambos: CLOB para decisões críticas de velocidade e chain para reconciliação periódica.
Endereço do contrato pUSD e ABI
pUSD é o wrapper de stablecoin da Polymarket usado desde a migração V2 de 2025. O contrato em 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB na Polygon mainnet é um ERC-20 padrão.
Três leituras que importam para um bot:
balanceOf(proxy)- seu pUSD disponível para gastar. Compare com a visão do CLOB sobre seu saldo em cada restart.allowance(proxy, exchange_contract)- se os contratos de exchange do CTF/NegRisk podem gastar seu pUSD. Necessário para matching de ordens.- subscription do evento
Transfer- detecta depósitos e saques sem polling.
USDC (0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) continua sendo o par de off-ramp. A maioria dos bots só precisa de leituras de pUSD; USDC importa apenas durante ciclos de depósito/saque.
Conditional Tokens Framework (CTF)
As shares de outcome são tokens ERC-1155 mintados pelo Conditional Tokens Framework (CTF) da Gnosis. O contrato CTF na Polygon em 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 rastreia o supply por position-id.
Três leituras:
balanceOf(proxy, position_id)- quantos outcome tokens você realmente possui para aquele market+outcome.getOutcomeSlotCount(condition_id)- número de outcomes (2 para binário, N para multi-outcome).payoutNumerators,payoutDenominator- definidos quando a UMA resolve o mercado. Ler isso mostra qual lado venceu antes de a UI do CLOB atualizar.
O position_id é um hash de (condition_id, outcome_index). Calcule-o no lado do cliente via o helper getPositionId do CTF ou replique a lógica de keccak no seu stack.
UMA Optimistic Oracle: eventos proposed e disputed
O Optimistic Oracle (OO) da UMA lida com toda a resolução de disputas da Polymarket. Dois eventos nos quais seu bot pode querer fazer subscription.
ProposePrice(requester, identifier, timestamp, ancillaryData, proposer, proposedPrice)- disparado quando um bot da Polymarket propõe um outcome.DisputePrice(requester, identifier, timestamp, ancillaryData, disputer)- disparado quando alguém contesta o outcome proposto.
Endereço do contrato OO: 0xeE3Afe347D5C74317041E2618C49534dAf887c24. Filtre pelo endereço requester da Polymarket.
Por que fazer subscription: uma disputa significa que a resolução agora está contestada e vai exigir um voto da UMA em 24-72h. Durante essa janela, o mercado pode ficar pausado para trading. Um bot que mantém posições em um mercado disputado deve saber disso imediatamente.
Leitura de logs de eventos da Polygon (web3.py / ethers)
Duas implementações de referência.
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 });
});
Para monitoramento contínuo, use transporte WebSocket (wss://...) em vez de polling HTTP - menos requests e entrega mais rápida. A maioria dos provedores pagos de RPC inclui WebSocket nos planos de entrada.
Quando ler on-chain vs confiar na API
Regras práticas de produção.
- Confie na API para: order book em tempo real, trades recentes, suas próprias ordens pendentes, metadata do mercado (slug, question, data de encerramento), descoberta de eventos/mercados.
- Confie na chain para: seu próprio saldo de pUSD, seu próprio inventário de outcome-token, verificação de depósito e saque, outcomes de resolução, estado de disputa.
- Compare ambos para: qualquer coisa financeira que o bot registrou como fill - faça o match do evento "matched" da API com a transferência CTF da chain para confirmar a liquidação.
A regra dos 5 segundos de espera após uma compra (capítulo 12) é a realidade on-chain invadindo o tempo da API. Um sell GTC enviado imediatamente após uma compra no mercado verá balance: 0 na checagem on-chain, mesmo que o CLOB tenha feito o match segundos antes.
Código: detectar uma disputa da UMA via subscription de eventos
Referência: acompanhe disputas da UMA relacionadas à Polymarket em tempo real.
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)
O campo ancillaryData é texto em estilo JSON, codificado em hex, que contém a pergunta do mercado. Decodificá-lo fornece o identificador equivalente ao slug para cruzar com suas posições abertas.












