Skip to main content

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