Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Polymarket/uma-ctf-adapter/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The UMA CTF Adapter emits events throughout the question lifecycle, from initialization to resolution. Monitoring these events is critical for:
- Tracking question state changes
- Detecting disputes and manual interventions
- Auditing resolution outcomes
- Building off-chain integrations and dashboards
Core Events
QuestionInitialized
Emitted when a new question is created and submitted to the Optimistic Oracle.
event QuestionInitialized(
bytes32 indexed questionID,
uint256 indexed requestTimestamp,
address indexed creator,
bytes ancillaryData,
address rewardToken,
uint256 reward,
uint256 proposalBond
);
Key Fields:
questionID - Unique identifier (keccak256 hash of ancillary data)
requestTimestamp - Timestamp of the OO price request
creator - Address that initialized the question
ancillaryData - The question data with creator address appended
rewardToken - ERC20 token used for rewards/bonds
reward - Amount offered to successful proposers
proposalBond - Bond required from proposers/disputers
What to Monitor:
- Track new questions as they’re created
- Validate reward and bond amounts are economically secure
- Record creator addresses for accountability
- Store ancillary data for question interpretation
Example Listener (ethers.js):
const adapter = new ethers.Contract(adapterAddress, abi, provider);
adapter.on('QuestionInitialized', (
questionID,
requestTimestamp,
creator,
ancillaryData,
rewardToken,
reward,
proposalBond,
event
) => {
console.log('New question:', {
questionID,
creator,
reward: ethers.utils.formatUnits(reward, 18),
bond: ethers.utils.formatUnits(proposalBond, 18)
});
});
QuestionResolved
Emitted when a question is resolved through the Optimistic Oracle.
event QuestionResolved(
bytes32 indexed questionID,
int256 indexed settledPrice,
uint256[] payouts
);
Key Fields:
questionID - The resolved question identifier
settledPrice - Price from OO (0, 0.5 ether, or 1 ether)
payouts - Array [YES_payout, NO_payout]
Price Interpretation:
0 → NO (payouts: [0, 1])
0.5 ether → UNKNOWN/TIE (payouts: [1, 1])
1 ether → YES (payouts: [1, 0])
What to Monitor:
- Track resolution outcomes for market settlement
- Detect unusual resolutions (e.g., ties)
- Verify payout arrays match expected values
- Trigger settlement flows in dependent systems
QuestionManuallyResolved
Emitted when an admin resolves a question manually after flagging.
event QuestionManuallyResolved(
bytes32 indexed questionID,
uint256[] payouts
);
What to Monitor:
- Critical: Manual resolutions bypass the Optimistic Oracle
- Review all manual resolutions for governance compliance
- Alert on unexpected manual interventions
- Verify safety period (1 hour) was respected
QuestionReset
Emitted when a question is reset, triggering a new OO price request.
event QuestionReset(bytes32 indexed questionID);
When This Occurs:
- OO price was disputed (
priceDisputed callback)
- OO returned the ignore price (
type(int256).min)
- Admin manually called
reset() (failsafe)
What to Monitor:
- Multiple resets may indicate contentious questions
- Track reset frequency per question (max 1 automatic reset)
- Alert if reset occurs after dispute
QuestionFlagged / QuestionUnflagged
Admin actions to flag questions for manual resolution.
event QuestionFlagged(bytes32 indexed questionID);
event QuestionUnflagged(bytes32 indexed questionID);
What to Monitor:
- Flagging pauses automatic resolution
- Sets
manualResolutionTimestamp = block.timestamp + 1 hour
- Unflagging only possible before safety period expires
- Alert governance channels on flag events
QuestionPaused / QuestionUnpaused
Admin controls to pause/resume resolution.
event QuestionPaused(bytes32 indexed questionID);
event QuestionUnpaused(bytes32 indexed questionID);
What to Monitor:
- Paused questions cannot be resolved
- Track pause duration for SLA monitoring
- Distinguish from flagging (flagging also sets pause)
Setting Up Event Listeners
Using ethers.js
import { ethers } from 'ethers';
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const adapter = new ethers.Contract(ADAPTER_ADDRESS, ABI, provider);
// Listen to all events
adapter.on('*', (event) => {
console.log('Event:', event.event, event.args);
});
// Listen to specific event
adapter.on('QuestionResolved', async (questionID, settledPrice, payouts) => {
// Fetch full question data
const question = await adapter.getQuestion(questionID);
// Trigger settlement logic
await settleMarket(questionID, payouts);
});
Using Viem
import { createPublicClient, parseAbiItem } from 'viem';
const client = createPublicClient({
chain: mainnet,
transport: http()
});
const unwatch = client.watchEvent({
address: ADAPTER_ADDRESS,
event: parseAbiItem('event QuestionInitialized(bytes32 indexed questionID, uint256 indexed requestTimestamp, address indexed creator, bytes ancillaryData, address rewardToken, uint256 reward, uint256 proposalBond)'),
onLogs: logs => {
logs.forEach(log => {
console.log('Question initialized:', log.args.questionID);
});
}
});
Querying Historical Events
// Get all resolutions in the last 1000 blocks
const filter = adapter.filters.QuestionResolved();
const events = await adapter.queryFilter(filter, -1000);
events.forEach(event => {
const { questionID, settledPrice, payouts } = event.args;
console.log(`Resolved: ${questionID}, Price: ${settledPrice}`);
});
Best Practices
1. Monitor All Lifecycle Events
Track the complete flow:
QuestionInitialized → [QuestionPaused/Flagged?] → [QuestionReset?] → QuestionResolved/QuestionManuallyResolved
2. Set Up Alerts
High Priority:
QuestionFlagged - Admin intervention required
QuestionManuallyResolved - Governance action taken
- Multiple
QuestionReset events for same questionID
Medium Priority:
QuestionPaused - Resolution blocked
- Resolution with
0.5 ether price (tie/unknown)
3. Correlate with Optimistic Oracle Events
Monitor OO events for complete visibility:
ProposePrice - Someone proposed an answer
DisputePrice - Proposal was disputed
Settle - OO finalized the price
4. Store Event Data
Persist events to a database for:
- Historical analysis
- Dispute tracking
- Performance metrics
- Audit trails
5. Handle Reorgs
Implement reorg-safe event processing:
const CONFIRMATIONS = 12;
adapter.on('QuestionResolved', async (questionID, ...args) => {
const currentBlock = await provider.getBlockNumber();
const eventBlock = await event.getBlock();
if (currentBlock - eventBlock.number < CONFIRMATIONS) {
// Wait for more confirmations
await waitForConfirmations(eventBlock.number, CONFIRMATIONS);
}
// Process event
});
Event Data Interpretation
Question State from Events
Derive question state from event history:
async function getQuestionState(questionID) {
const events = await getAllEventsForQuestion(questionID);
let state = 'UNKNOWN';
for (const event of events) {
switch (event.event) {
case 'QuestionInitialized':
state = 'INITIALIZED';
break;
case 'QuestionPaused':
state = 'PAUSED';
break;
case 'QuestionUnpaused':
state = 'ACTIVE';
break;
case 'QuestionFlagged':
state = 'FLAGGED';
break;
case 'QuestionReset':
state = 'RESET';
break;
case 'QuestionResolved':
case 'QuestionManuallyResolved':
state = 'RESOLVED';
break;
}
}
return state;
}
Resolution Outcome Analysis
function analyzeResolution(settledPrice, payouts) {
if (settledPrice === 0n) {
return { outcome: 'NO', payouts: [0, 1], description: 'Question resolved NO' };
} else if (settledPrice === ethers.utils.parseEther('0.5')) {
return { outcome: 'TIE', payouts: [1, 1], description: 'Unknown/ambiguous outcome' };
} else if (settledPrice === ethers.utils.parseEther('1')) {
return { outcome: 'YES', payouts: [1, 0], description: 'Question resolved YES' };
} else {
throw new Error(`Invalid settled price: ${settledPrice}`);
}
}
Integration Examples
Dashboard Metrics
class AdapterMonitor {
constructor(adapter) {
this.adapter = adapter;
this.metrics = {
totalQuestions: 0,
resolvedQuestions: 0,
manualResolutions: 0,
disputes: 0,
paused: 0
};
this.setupListeners();
}
setupListeners() {
this.adapter.on('QuestionInitialized', () => {
this.metrics.totalQuestions++;
});
this.adapter.on('QuestionResolved', () => {
this.metrics.resolvedQuestions++;
});
this.adapter.on('QuestionManuallyResolved', () => {
this.metrics.manualResolutions++;
});
this.adapter.on('QuestionReset', () => {
this.metrics.disputes++;
});
}
getMetrics() {
return { ...this.metrics };
}
}
See Also