Tutorial de Bot de Polymarket · Capítulo 9 de 32
Lee datos on-chain de Polymarket directamente: balances de USDC/pUSD, lecturas del contrato CTF para el suministro de resultados, eventos propuestos y disputados del UMA Optimistic Oracle, y logs de transacciones de Polygon, con código.
Qué cubre este capítulo
Las APIs de Polymarket son convenientes, pero eventualmente consistentes. La cadena es la fuente de verdad. Este capítulo recorre las lecturas on-chain que usa un bot en producción para verificar su propia contabilidad: balances de pUSD, inventario de outcome tokens, eventos de disputa de UMA y estado del contrato CTF. El patrón al que convergen la mayoría de los bots de producción es API-first para velocidad, más reconciliación on-chain periódica para asegurar precisión.
- Qué vive on-chain (vs en CLOB)
- Dirección del contrato pUSD y ABI
- Conditional Tokens Framework (CTF)
- UMA Optimistic Oracle: eventos proposed y disputed
- Lectura de logs de eventos en Polygon (web3.py / ethers)
- Cuándo leer on-chain vs confiar en la API
- Código: detectar una disputa de UMA mediante suscripción a eventos
Qué vive on-chain (vs en CLOB)
Dos máquinas de estado, dos verdades.
On-chain (Polygon): balances de pUSD, inventario de outcome tokens (suministro ERC-1155 por token), approvals de allowance, propuestas y disputas del UMA Optimistic Oracle, eventos de depósito y retiro. Eventualmente correcto; la latencia es de un bloque de Polygon, aproximadamente 2 segundos.
CLOB (Polymarket API): order book, trades recientes, órdenes limitadas pendientes, acuses de ejecución. En tiempo real, pero eventualmente consistente: una ejecución se confirma antes de que el ERC-1155 se liquide, lo que produce el problema de phantom-fill que se cubre en el capítulo 12.
Ambos siempre deben converger. Cuando divergen, la cadena es la autoridad. Un bot que confía solo en el CLOB se desalineará; un bot que confía solo en la cadena operará lento. El código de producción usa ambos: CLOB para decisiones críticas de velocidad y la cadena para reconciliación periódica.
Dirección del contrato pUSD y ABI
pUSD es el wrapper de stablecoin de Polymarket usado desde la migración V2 de 2025. El contrato en 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB en Polygon mainnet es un ERC-20 estándar.
Tres lecturas que le importan a un bot:
balanceOf(proxy)- tu pUSD gastable. Compáralo con la vista del CLOB sobre tu balance en cada reinicio.allowance(proxy, exchange_contract)- si los contratos de exchange CTF/NegRisk pueden gastar tu pUSD. Requerido para el match de órdenes.- Suscripción al evento
Transfer- detecta depósitos y retiros sin polling.
USDC (0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) sigue siendo el par de salida. La mayoría de los bots solo necesitan lecturas de pUSD; USDC importa solo durante los ciclos de depósito/retiro.
Conditional Tokens Framework (CTF)
Las participaciones de resultado son tokens ERC-1155 acuñados por el Conditional Tokens Framework (CTF) de Gnosis. El contrato CTF en Polygon en 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 rastrea el suministro por position-id.
Tres lecturas:
balanceOf(proxy, position_id)- cuántos outcome tokens realmente tienes para ese mercado+resultado.getOutcomeSlotCount(condition_id)- número de resultados (2 para binario, N para multioutcome).payoutNumerators,payoutDenominator- se establecen cuando UMA resuelve el mercado. Leerlos te dice qué lado ganó antes de que la UI del CLOB se actualice.
El position_id es un hash de (condition_id, outcome_index). Calcúlalo del lado del cliente con el helper getPositionId del CTF o replica la lógica de keccak en tu stack.
UMA Optimistic Oracle: eventos proposed y disputed
El Optimistic Oracle (OO) de UMA maneja toda la resolución de disputas de Polymarket. Dos eventos a los que tu bot puede querer suscribirse.
ProposePrice(requester, identifier, timestamp, ancillaryData, proposer, proposedPrice)- se dispara cuando un bot de Polymarket propone un resultado.DisputePrice(requester, identifier, timestamp, ancillaryData, disputer)- se dispara cuando alguien impugna el resultado propuesto.
Dirección del contrato OO: 0xeE3Afe347D5C74317041E2618C49534dAf887c24. Filtra por la dirección requester de Polymarket.
Por qué suscribirse: una disputa significa que la resolución ahora está en controversia y requerirá una votación de UMA de 24 a 72 horas. Durante esa ventana, el mercado puede quedar pausado para trading. Un bot que mantiene posiciones en un mercado disputado debe saberlo de inmediato.
Lectura de logs de eventos en Polygon (web3.py / ethers)
Dos implementaciones de referencia.
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 monitoreo continuo usa transporte WebSocket (wss://...) en lugar de polling por HTTP: menos requests y entrega más rápida. La mayoría de los proveedores RPC de pago incluyen WebSocket en planes de entrada.
Cuándo leer on-chain vs confiar en la API
Reglas prácticas de producción.
- Confía en la API para: order book en tiempo real, trades recientes, tus órdenes pendientes, metadatos del mercado (slug, pregunta, fecha de cierre), descubrimiento de eventos/mercados.
- Confía en la cadena para: tu propio balance de pUSD, tu inventario de outcome tokens, verificación de depósitos y retiros, resultados de resolución, estado de disputa.
- Contrasta ambos para: cualquier dato financiero que el bot registró como fill: compara el evento "matched" de la API con el transfer de CTF en la cadena para confirmar la liquidación.
La regla de esperar 5 segundos después de una compra (capítulo 12) es la realidad on-chain irrumpiendo en el tiempo de la API. Una venta GTC enviada inmediatamente después de una compra de mercado verá balance: 0 en la verificación de cadena, aunque el CLOB haya hecho match momentos antes.
Código: detectar una disputa de UMA mediante suscripción a eventos
Referencia: vigila en tiempo real las disputas de UMA relacionadas con Polymarket.
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)
El campo ancillaryData es texto tipo JSON codificado en hex que contiene la pregunta del mercado. Decodificarlo te da el identificador equivalente al slug para cruzarlo con tus posiciones abiertas.












