OddMaki
Operations

Creating Markets

How to create binary markets and market groups on your venue — parameters, best practices, and on-chain mechanics.

Markets are created within a venue. You need your venue ID and USDC for the creation fee.

Creating a Binary Market

A binary market is a single YES/NO question. It goes live immediately on creation.

const tx = await client.market.createMarket({
  venueId: 1n,
  question: {
    title: 'Will BTC reach $100k by end of 2026?',
    description: 'Resolves YES if Bitcoin reaches $100,000 USD on any major exchange before January 1, 2027.',
  },
  outcomes: ['Yes', 'No'],
  tickSize: parseEther('0.01'),       // $0.01 price increments
  collateralToken: USDC_ADDRESS,
  additionalReward: 0n,              // no extra UMA reward
  liveness: 0n,                      // defaults to 2 hours
});

What happens on-chain:

  1. Validates parameters and access control
  2. Allocates a market ID (auto-incrementing from 1)
  3. Enriches the question data with market metadata
  4. Generates a deterministic questionId and conditionId
  5. Prepares the CTF condition and computes YES/NO position IDs
  6. Collects the market creation fee (50/50 protocol/venue split)
  7. Emits MarketCreated

Creating a Market Group

Market groups contain N mutually exclusive outcomes. The lifecycle is: create → add outcomes → activate.

// 1. Create the group
await client.market.createMarketGroup({
  venueId: 1n,
  question: 'Who will win the championship?',
  description: 'Resolves YES for the winning team.',
  collateralToken: USDC_ADDRESS,
  tickSize: parseEther('0.01'),
  additionalReward: 0n,
  liveness: 0n,
});

// 2. Add outcomes (each becomes a binary YES/NO market)
await client.market.addMarketToGroup({
  marketGroupId: 1n,
  marketName: 'Team A',
  marketQuestion: 'Will Team A win?',
});

await client.market.addMarketToGroup({
  marketGroupId: 1n,
  marketName: 'Team B',
  marketQuestion: 'Will Team B win?',
});

// 3. Optionally add placeholder slots for late additions
await client.market.addPlaceholderMarkets({
  marketGroupId: 1n,
  count: 3n,
});

// 4. Activate (locks totalMarkets, enables trading)
await client.market.activateMarketGroup({ marketGroupId: 1n });

Placeholders can be activated later with real names and questions:

await client.market.activatePlaceholder({
  marketGroupId: 1n,
  marketId: 5n,
  marketName: 'Team C',
  marketQuestion: 'Will Team C win?',
});

Writing Good Resolution Criteria

The description field is what UMA asserters and disputors use to determine the outcome. Be specific:

  • State the exact condition for YES vs NO
  • Include data sources (e.g., "any major exchange", "official results")
  • Include deadlines with timezone if applicable
  • Avoid ambiguous language

Market Parameters

ParameterDescriptionConstraints
venueIdWhich venue to create inMust exist and be active
question.titleThe market questionNon-empty
question.descriptionResolution criteriaUsed by UMA for disputes
outcomesOutcome labelsMust be ['Yes', 'No'] for binary
tickSizePrice incrementMust be > 0. Common: parseEther('0.01')
collateralTokenTrading collateralMust be UMA-whitelisted ERC-20
additionalRewardExtra UMA asserter rewardAdded to venue default. 0 is common
livenessUMA challenge periodMinimum 7200s (2 hours), values below are floored
Market creation feeCharged to creatorMinimum 5 USDC, set per venue, 50/50 split

On-Chain Mechanics

The creation fee is pulled from your wallet via transferFrom — approve USDC to the Diamond before creating.

For market groups:

  • Only the group creator can add markets and activate
  • Maximum 50 markets per group (including placeholders)
  • Minimum 2 markets required to activate
  • totalMarkets is locked at activation — used for NegRisk conversion math
  • Placeholders reserve position IDs at creation; only the question text changes on activation

Creating a Price Market (Pyth)

Price markets use the priceMarket module and resolve automatically from a Pyth feed at closeTime:

const tx = await client.priceMarket.createPyth({
  venueId: 1n,
  pythFeedId: '0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43', // BTC/USD
  closeTime: BigInt(Math.floor(Date.now() / 1000) + 86400), // +24h
  tickSize: parseEther('0.01'),
  collateralToken: USDC_ADDRESS,
  question: {
    title: 'Will BTC close above its open price in 24h?',
    description: 'Resolves from Pyth BTC/USD at the close timestamp.',
  },
  // strikePrice: 0n,          // omit for Up/Down; set for Strike mode
  // resolutionWindow: 3600n,  // optional window allowed after closeTime
  // tags: ['crypto', 'btc'],
});

After closeTime + resolutionWindow, anyone calls client.priceMarket.resolvePyth(marketId) — no UMA bond or assertion required. See Price Markets for the full details.

Tags and Metadata

Markets support optional tags (up to 5 per market) and a metadata URI (typically IPFS) for images, extended descriptions, or structured fields consumed by the frontend. These are event-only — they are not stored on-chain, but they're indexed by the subgraph:

await client.market.updateMarketTags({ marketId: 1n, tags: ['sports', 'nba'] });
await client.market.updateMarketMetadata({ marketId: 1n, metadataURI: 'ipfs://…' });

// Equivalent methods exist for market groups:
await client.market.updateMarketGroupTags({ marketGroupId: 1n, tags: ['elections'] });
await client.market.updateMarketGroupMetadata({ marketGroupId: 1n, metadataURI: 'ipfs://…' });

Only the market creator (or venue operator) can update these.

What's Next