Skip to main content
This guide walks you through building a self-contained binary prediction market on Gnosis ConditionalTokens (CTF). A single cron orchestrator runs every 5 minutes, fetches the current BTC/USD price, resolves the previous cycle’s market, and prepares a new one for the next 5-minute bucket. It demonstrates multi-task orchestration, callTask fan-out, Compose-managed wallets as on-chain oracles, and idempotent chain writes.

How it works

Each 5-minute cycle:
  1. market_data fetches the current BTC/USD price from CoinGecko.
  2. resolve_market reports payouts for any market whose endTime has passed. Outcome is [1, 0] (UP) if the current price is at or above the market’s openPrice, else [0, 1] (DOWN).
  3. launch_market prepares a new condition on the CTF for the current 5-minute bucket, using the same fetched price as the new market’s openPrice.
One price fetch per cycle serves both purposes — the closing tick of the expiring market sits at the same moment as the opening tick of the new one.

Prerequisites

Project structure

Step 1: Set up the project

Clone the example repository:

Step 2: Understand the orchestrator

orchestrator.ts is the only task with a cron trigger. It fans out to the three worker tasks via context.callTask:

Key Compose features used

  • context.callTask — orchestrator delegates work to specialized child tasks with their own retry configs.
  • context.collection — persistent document storage indexed by endTime and resolved for fast lookup of overdue markets.
  • evm.wallet — a Compose-managed EOA named prediction-market-oracle serves as the CTF oracle.
  • context.fetch — CoinGecko HTTP request with built-in retries (see market-data.ts).
  • writeContract — raw ABI-signature calls for prepareCondition and reportPayouts on the CTF.

Step 3: Understand the oracle pattern

The Gnosis CTF requires an oracle address when a condition is prepared. Only that address can later call reportPayouts for the condition. In this example, the oracle is a Compose-managed EOA — the same wallet that signs both transactions:
Because each deploy of this example produces a fresh oracle address, the conditions it creates on the shared CTF are namespaced cleanly — they never collide with any other user of the same CTF.

Step 4: Understand questionId derivation

Every CTF condition is keyed by a questionId the oracle chooses. This example derives one deterministically from the market parameters so retries always compute the same value:
The SALT constant ("GOLDSKY_COMPOSE_DEMO") scopes every questionId to this example. Change it if you fork the app for your own purposes.

Step 5: Configure the Compose app

compose.yaml declares one cron plus four callable tasks:
Only tasks with a triggers block can be invoked from outside the app. launch_market, resolve_market, and market_data are called exclusively via context.callTask from the orchestrator.

Step 6: Deploy to Goldsky

Compose sponsors gas by default, so the oracle wallet needs no funding. The first cron tick fires on the next 5-minute boundary. Watch the cycles:
Look for cycle complete: log lines and on-chain ConditionPreparation / ConditionResolution events on BaseScan, filtered by your oracle EOA (topic[2]). To print the oracle EOA address:

Customization

Change the asset

Replace BTC_USD and the CoinGecko URL with any asset CoinGecko supports:
Update the response parsing in market-data.ts to read response.ethereum.usd.

Change the market duration

Update the cron expression to match the new cadence:

Change the chain

Swap baseSepolia for any other EVM testnet. You’ll also need a deployed ConditionalTokens contract on that chain — see Gnosis’s ConditionalTokens repository to deploy your own if none exists there.

Resources