Skip to main content

Overview

Kuest uses the UMA Protocol for decentralized market resolution. This system allows anyone to propose outcomes, with a dispute period for challenges, ensuring fair and accurate resolutions.

Resolution Process

1

Market Closes

When a market reaches its end date, it stops accepting new trades. The market enters the resolution phase.
// Market becomes inactive when end_at date passes
const isActive = market.is_active && new Date(market.end_at) > new Date()
2

Outcome Proposal

Anyone can propose a resolution outcome by:
  1. Visiting the UMA proposal interface
  2. Posting a bond (typically in USDC)
  3. Submitting the proposed outcome (Yes/No/Invalid)
The proposer URL is automatically generated:
// From src/lib/uma.ts
const query = new URLSearchParams()
query.set('project', 'Your Platform Name')
query.set('transactionHash', requestTxHash)
query.set('eventIndex', String(logIndex))

const proposeUrl = `https://oracle.uma.xyz/propose?${query.toString()}`
The proposer who correctly resolves the market receives the USDC reward configured during market creation (typically 5 USDC per market).
3

Liveness Period

After a proposal is submitted, there’s a liveness period (default: 1 hour) during which the proposal can be disputed.Resolution Status Values:
  • posed: Initial state, no proposal yet
  • proposed: First proposal submitted
  • reproposed: New proposal after a dispute
  • challenged: Proposal is being disputed
  • disputed: Formal dispute initiated
  • resolved: Final outcome confirmed
// From resolution sync route
const RESOLUTION_LIVENESS_DEFAULT_SECONDS = 60 * 60 // 1 hour

function computeResolutionDeadline(
  status: string,
  flagged: boolean,
  lastUpdateTimestamp: number,
  livenessSeconds: number | null,
  negRisk: boolean
): string | null {
  if (status === 'resolved') {
    return null // Already finalized
  }

  if (flagged) {
    // Extended safety period for flagged proposals
    const safetyPeriod = negRisk ? 3600 : 3600
    return new Date((lastUpdateTimestamp + safetyPeriod) * 1000).toISOString()
  }

  if (status === 'posed' || status === 'proposed' || status === 'reproposed') {
    const effectiveLiveness = livenessSeconds ?? RESOLUTION_LIVENESS_DEFAULT_SECONDS
    return new Date((lastUpdateTimestamp + effectiveLiveness) * 1000).toISOString()
  }

  return null
}
4

Dispute Window

During the liveness period, anyone can dispute an incorrect proposal by:
  1. Posting a dispute bond (larger than the proposal bond)
  2. Providing evidence for the correct outcome
  3. Initiating the dispute process
The market status updates to challenged or disputed.
// Resolution timeline builder checks dispute status
const isDisputed = Boolean(
  condition?.resolution_was_disputed || 
  status === 'challenged' || 
  status === 'disputed'
)
5

Resolution Settlement

If no disputes occur during the liveness period, the proposal is automatically accepted.If disputed:
  1. UMA token holders vote on the correct outcome
  2. Voting period lasts 48-96 hours
  3. Winning side receives the losing side’s bond
  4. Correct outcome is finalized on-chain
The resolution price is normalized:
// Resolution price values
const RESOLUTION_PRICE_YES = 1000000000000000000n // 1e18 = 100% YES
const RESOLUTION_PRICE_INVALID = 500000000000000000n // 0.5e18 = 50/50 invalid
const RESOLUTION_UNPROPOSED_PRICE_SENTINEL = 69n // Not yet proposed

function normalizeResolutionPrice(rawValue: string | null): number | null {
  if (!rawValue) return null
  
  const value = BigInt(rawValue)
  if (value === RESOLUTION_UNPROPOSED_PRICE_SENTINEL) return null
  if (value === 0n) return 0 // NO
  if (value === RESOLUTION_PRICE_YES) return 1 // YES
  if (value === RESOLUTION_PRICE_INVALID) return 0.5 // INVALID
  
  return null // Intermediate value
}
6

Payout Distribution

Once resolved, the system calculates payouts for each outcome:
// From resolution sync route
async function updateOutcomePayouts(conditionId: string, price: number) {
  const payoutYes = price >= 1 ? 1 : price <= 0 ? 0 : price
  const payoutNo = price <= 0 ? 1 : price >= 1 ? 0 : price

  const updates = [
    { index: 0, payout: payoutYes },
    { index: 1, payout: payoutNo }
  ]

  for (const update of updates) {
    const isWinningOutcome = update.payout > 0
    await db
      .update(outcomesTable)
      .set({
        is_winning_outcome: isWinningOutcome,
        payout_value: String(update.payout)
      })
      .where(and(
        eq(outcomesTable.condition_id, conditionId),
        eq(outcomesTable.outcome_index, update.index)
      ))
  }
}
Users who hold winning outcome shares can redeem them for USDC.

UMA Integration

Oracle Contracts

Kuest integrates with UMA’s Optimistic Oracle contracts:
// Contract addresses from src/lib/contracts.ts
const UMA_CTF_ADAPTER_ADDRESS = '0x20088f6aa9D8D5947c9f002167355Cb332134bf8'
const UMA_NEG_RISK_ADAPTER_ADDRESS = '0x724259Fe88100FE18C134324C4853975FBDa4d76'

// Polymarket compatibility
const UMA_CTF_ADAPTER_POLYMARKET_ADDRESS = '0x65070BE91477460D8A7AeEb94ef92fe056C2f2A7'
const UMA_NEG_RISK_ADAPTER_POLYMARKET_ADDRESS = '0x2F5e3684cb1F318ec51b00Edba38d79Ac2c0aA9d'

Resolution Data Structure

interface SubgraphResolution {
  id: string                    // Question ID / request ID
  status: string                // posed, proposed, disputed, resolved
  flagged: boolean              // Safety flag for admin review
  paused: boolean               // Temporarily halted
  wasDisputed: boolean          // Had at least one dispute
  approved: boolean | null      // Admin approval status
  lastUpdateTimestamp: string   // Unix timestamp
  price: string | null          // Resolution outcome (1e18 = YES)
  liveness: string | null       // Dispute period in seconds
}
The system automatically generates proposal and settlement URLs:
// Propose a new outcome
const proposeUrl = buildUmaProposeUrl({
  uma_request_tx_hash: '0x123...',
  uma_request_log_index: 42
}, 'Kuest')
// => https://oracle.uma.xyz/propose?project=Kuest&transactionHash=0x123...&eventIndex=42

// View settled outcome
const settledUrl = buildUmaSettledUrl({
  uma_request_tx_hash: '0x123...',
  uma_request_log_index: 42
}, 'Kuest')
// => https://oracle.uma.xyz/settled?project=Kuest&transactionHash=0x123...&eventIndex=42

Resolution Timeline

The frontend displays a visual timeline showing the resolution progress:
// Timeline phases
export type ResolutionTimelineItemType =
  | 'outcomeProposed'   // Initial proposal submitted
  | 'noDispute'         // No disputes raised
  | 'disputed'          // Dispute in progress
  | 'disputeWindow'     // Awaiting dispute period end
  | 'finalReview'       // Admin or system review
  | 'finalOutcome'      // Resolution complete

export interface ResolutionTimelineItem {
  id: string
  type: ResolutionTimelineItemType
  icon: 'check' | 'gavel' | 'open'
  state: 'done' | 'active' | 'pending'
  outcome: 'yes' | 'no' | 'invalid' | null
  timestampMs: number | null
  deadlineMs: number | null
  remainingSeconds: number | null
}
Example timeline for a resolved market:
[
  {
    id: 'outcome-proposed',
    type: 'outcomeProposed',
    icon: 'open',
    state: 'done',
    outcome: 'yes',
    timestampMs: 1704067200000,
    deadlineMs: null,
    remainingSeconds: null
  },
  {
    id: 'no-dispute',
    type: 'noDispute',
    icon: 'check',
    state: 'done',
    outcome: 'yes',
    timestampMs: 1704070800000,
    deadlineMs: null,
    remainingSeconds: null
  },
  {
    id: 'final-outcome',
    type: 'finalOutcome',
    icon: 'gavel',
    state: 'done',
    outcome: 'yes',
    timestampMs: 1704070800000,
    deadlineMs: null,
    remainingSeconds: null
  }
]

Dispute Handling

When to Dispute

Dispute a proposal if:
  • The proposed outcome is factually incorrect
  • Resolution rules were not followed
  • The outcome source was misinterpreted
  • The proposal violates market integrity

Dispute Process

1

Identify Incorrect Proposal

Monitor newly proposed markets and verify outcomes against resolution rules.
2

Prepare Evidence

Gather proof of the correct outcome:
  • Screenshots of authoritative sources
  • Archived web pages
  • Official announcements
  • Data from trusted APIs
3

Post Dispute Bond

Visit the UMA dispute interface and post the required bond (typically 2x the proposal bond).
4

Submit Dispute

Explain why the proposal is incorrect and provide your evidence.
5

Await Vote

UMA token holders review the dispute and vote on the correct outcome.
6

Resolution

If your dispute is correct, you receive:
  • Your original bond back
  • The incorrect proposer’s bond
  • Resolution rewards (if applicable)

Dispute Tracking

The system tracks dispute history:
// Database fields
interface Condition {
  resolved: boolean
  resolution_status: string
  resolution_flagged: boolean
  resolution_paused: boolean
  resolution_was_disputed: boolean  // Ever had a dispute
  resolution_approved: boolean | null
  resolution_deadline_at: Date | null
  resolution_price: string | null
  resolution_liveness_seconds: number | null
}

Automated Resolution Sync

The platform automatically syncs resolution data from the UMA subgraph:
// Cron job endpoint: /api/sync/resolution
export const maxDuration = 300 // 5 minutes

const RESOLUTION_SUBGRAPH_URL = 
  'https://api.goldsky.com/api/public/project_cmkeqj653po3801t6ajbv1wcv/subgraphs/resolution-subgraph/1.0.0/gn'

const SYNC_TIME_LIMIT_MS = 250_000 // 4 minutes
const RESOLUTION_PAGE_SIZE = 200

Sync Process

1

Acquire Sync Lock

Prevent concurrent syncs using database locking:
const lockAcquired = await tryAcquireSyncLock()
if (!lockAcquired) {
  return { skipped: true, message: 'Sync already running' }
}
2

Fetch Resolution Updates

Query the UMA subgraph in pages:
{
  marketResolutions(
    first: 200,
    orderBy: lastUpdateTimestamp,
    orderDirection: asc
  ) {
    id
    status
    flagged
    paused
    wasDisputed
    approved
    lastUpdateTimestamp
    price
    liveness
  }
}
3

Update Database

For each resolution, update:
  • Condition resolution status
  • Market active/resolved flags
  • Outcome payout values
  • Event status (if all markets resolved)
await db.update(conditionsTable)
  .set({
    resolved: isResolved,
    resolution_status: status,
    resolution_flagged: flagged,
    resolution_price: String(resolutionPrice),
    resolution_was_disputed: wasDisputed,
    resolution_deadline_at: deadlineAt
  })
  .where(eq(conditionsTable.id, conditionId))
4

Update Cursor

Save sync progress for incremental updates:
await updateResolutionCursor({
  lastUpdateTimestamp: 1704070800,
  id: '0x123...'
})

Event Status Updates

When markets are resolved, event status automatically updates:
const nextStatus: 'draft' | 'active' | 'resolved' | 'archived' =
  !hasMarkets
    ? 'draft'
    : !hasUnresolvedMarket
      ? 'resolved'
      : hasActiveMarket
        ? 'active'
        : 'archived'

Settlement

After resolution, users can settle their positions:

Merging Shares

Users holding shares in both outcomes can merge them to recover USDC:
// Merge YES and NO shares to get back collateral
const mergeTx = {
  to: isNegRiskMarket 
    ? UMA_NEG_RISK_ADAPTER_ADDRESS 
    : CONDITIONAL_TOKENS_CONTRACT,
  data: encodeFunctionData({
    abi: ConditionalTokensABI,
    functionName: 'mergePositions',
    args: [
      collateralToken,
      parentCollectionId,
      conditionId,
      partition,
      amount
    ]
  })
}

Redeeming Winning Shares

Users with winning shares redeem them for USDC:
// Redeem winning outcome shares
const redeemTx = {
  to: isNegRiskMarket
    ? UMA_NEG_RISK_ADAPTER_ADDRESS
    : CONDITIONAL_TOKENS_CONTRACT,
  data: encodeFunctionData({
    abi: ConditionalTokensABI,
    functionName: 'redeemPositions',
    args: [
      collateralToken,
      parentCollectionId,
      conditionId,
      indexSets // Array of outcome indices to redeem
    ]
  })
}

Best Practices

Monitor Proposals

Set up alerts for new proposals on your markets to quickly verify correctness.

Document Sources

Keep records of outcome sources during market creation for easy resolution.

Incentivize Proposals

Set appropriate reward amounts to attract proposers (typically 5+ USDC).

Clear Resolution Rules

Write unambiguous rules to minimize disputes and ensure fair outcomes.

Troubleshooting

No Proposal Submitted

Issue: Market closed but no one proposed an outcome. Solution:
  • Increase the resolution reward for future markets
  • Manually propose using the UMA interface
  • Contact your community to encourage participation

Incorrect Proposal

Issue: Someone proposed the wrong outcome. Solution:
  • Gather evidence of the correct outcome
  • Submit a dispute within the liveness period
  • Engage the UMA community for voting support

Sync Delays

Issue: Resolution status not updating on your platform. Solution:
  • Check cron job execution logs
  • Verify CRON_SECRET is configured correctly
  • Manually trigger sync: GET /api/sync/resolution with authorization header
  • Check Sentry for sync errors

Settlement Fails

Issue: Users can’t redeem winning shares. Solution:
  • Verify the market is fully resolved (resolution_status = 'resolved')
  • Check that payout values are set in the database
  • Ensure users are calling the correct contract (CTF vs NegRisk adapter)
  • Verify sufficient POL gas balance

Market Creation

Create markets with proper resolution configuration

UMA Documentation

Learn more about UMA’s Optimistic Oracle

Admin Panel

Monitor market resolution status

API Reference

Resolution sync API endpoint

Build docs developers (and LLMs) love