Creating markets
Three ways to create a market — templated through Connect, guided through the Studio CLI, or directly in code with compass and market-creator. What each one needs, what it costs, and which to reach for.
There are three ways to create a market at Prophecy. They differ in who writes the question, how hard it is checked, and what you have to install. Most people want the first or the second; the third is for building a product on top.
| What you supply | What you install | Also need | Reach for it when | |
|---|---|---|---|---|
| 1 · Templated | A subject and a few knobs | connect-sdk (+ connect-react for the confirm sheet) |
Venue API key with the trade scope, and a signed-in session on that same venue |
Your venue creates markets from its own UI |
| 2 · Guided | A topic, then a choice | @prophecy-dev/studio — the prophecy CLI |
A venue and its key | Almost always. The highest quality bar of the three |
| 3 · In code | The whole loop | market-creator + viem, with compass baked in |
A model, and a funded wallet — you pay the oracle fee yourself | You are writing a program, not a market |
Route 3 is the only one that needs a model or a funded wallet. On Routes 1 and 2 someone else covers the oracle fee and the gas, so a creator with zero SOMI can use either — they differ in where the market appears and what it can be about, not in what you have to fund.
1 · Templated — through Connect
A venue calls one method with a templated definition — a subject and a few knobs, never resolution data — and gets back everything the user needs to sign.
const { calls, preview, prepared } = await client.markets.prepare({
kind: 'crypto.updown', asset: 'bitcoin', window: '1h',
})
// render `preview` read-only → user confirms → send each group sponsored:
for (const group of calls) await sponsorAndSend(auth, executor, 'marketCreation', group)
// …or hand `send: () => calls` to useConfirm() — <ProphecyConfirm/> renders the
// preview rows, the quota copy and the per-group sending for you.
The signer is the market’s on-chain creator — creator fees and market management stay with the
person who made it, not with an operator wallet.
Read this before you scope a venue: the templates are the whole list. MarketDefinition is a
closed discriminated union of seven kinds — two crypto, one stocks, four football — with no generic,
free-text or “other” kind. If your venue is about books, elections or weather, prepare cannot
mint a single one of its markets; use Route 2 or Route 3 instead. That ceiling is a deliberate
consequence of the first product rule below, not an oversight — but it is worth meeting before you
build the venue that has to clear it.
Live today: crypto.updown (asset, window: '20m'|'1h'|'4h'|'1d') and crypto.threshold
(asset, targetUsd, closesAt unix seconds). stocks.close and the four football kinds are
already in the wire contract and answer template-unavailable until their templates land — don’t
build UI for them yet.
The two product rules the schema enforces:
- A creator never supplies resolution data. The definition union has no field for a source, an
agreement rule, or a resolution time — Connect derives all of it per template from the same
matrices the resolver runs. Your form is pickers;
preview.sourceHosts,tradingCloseandresolvesAtare shown read-only before signing. - Check
prepareExpired(prepared)immediately before sending. The calldata embeds its own trading start (prepare + 300s) and dies past it; re-preparing after expiry is free — the same definition reuses its already-paid fact.
Refusals are a closed enum on err.body.error (PrepareError in wire) — switch on it:
unauthorized · session-required · bad-definition · insufficient-lead ·
duplicate-question (with err.body.existing, the live market to link to) · prepare-in-flight
(retry in seconds) · wallet-limit · venue-quota · template-unavailable ·
operator-unfunded · creation-unconfigured · internal. You need an apiKey on the client
and a signed-in session (the wallet is the creator identity), and the session’s venue must
match the key’s.
A working end-to-end reference — prepare → confirm sheet → sponsored send — is the “Create a
real market (BTC 1h)” button on the demo venue’s {c} Confirm page (testnet; it mints a real
market).
2 · Guided — through the Studio CLI
Two commands. You name a topic; the engine sources candidates and grades them before you see any of them. You pick from the slate, and each market you pick is re-checked independently at mint time and refused if it fails. Nothing reaches the chain without passing twice, which is why this is the highest quality bar of the three — and why it is the route for any question a template cannot express.
npm install -g @prophecy-dev/studio
# 1. see what your venue could carry — verified candidates, each with an id
prophecy markets "premier league"
prophecy markets "tfl disruption" --angle "line suspensions" --count 20
# 2. mint the ones you want, by id
prophecy markets mint --topic "premier league" <id> <id> <id>
The ids come from step 1 and only from step 1. --topic is how they are found again, so it must
match the topic you sourced under; an id the slate did not produce is refused.
Limits: up to 8 per call, 20 per venue per day. Defaults to testnet — pass --network mainnet
when you mean it.
Worth knowing: --angle sharpens the framing, --count sets how many candidates to generate
(default 12, max 40), and --venue picks the venue when the directory or the key does not imply one.
3 · In code — compass and market-creator
The same engine as a library. Reach for it when you are building something on top rather than creating markets by hand — a bot, a batch pipeline, a partner integration.
npm install @prophecy-dev/market-creator
npm install viem # peer dep, only if you publish on chain
import { openai } from '@ai-sdk/openai'
import { createMarket } from '@prophecy-dev/market-creator'
import { createProphecyV3Publisher } from '@prophecy-dev/market-creator/onchain'
const { market, accepted } = await createMarket('Will BTC close above $80k on 2026-06-30?', {
model: openai('gpt-4o'),
})
const publish = createProphecyV3Publisher({ collateralToken, account, rpcUrl, contracts, chainId })
if (accepted && market) await publish(market)
Note accepted. The engine is allowed to reject an idea, and a rejected market is the product
working — an unresolvable question caught before it reaches a book is far cheaper than one caught
after someone has traded it. reason names the gate that refused it.
This route pays its own way. On Route 1 Connect covers the oracle fee and sponsors the gas; on Route 2 the bots’ operator wallet covers the fee and the seed. Publishing yourself means a funded wallet and an operator’s trust level.
For batches, runPipeline writes every step’s output to a directory so you can see how markets
get shaped and gated. Run it with publish.skip: true and stubbed networkCalls to iterate fully
offline.
For deterministic shapes, don’t hand-roll the sources. Crypto price markets have a verified
per-exchange kline-at-close matrix — cryptoKlineSourcesAtClose(asset.klineSymbols, closeMs) returns
sources with strict-majority agreement already set, which is what keeps them resolving at close to a
0% void rate. Football has the same treatment through buildFootballSpec().
What creation costs
A market is two on-chain things with two different payers:
| Thing | What it is | Cost | Who pays |
|---|---|---|---|
| The fact | The oracle question the market resolves against (sources, agreement rule, resolution time) | ~1.5 SOMI, native | Connect’s operator wallet, during prepare — never the creator, never the venue |
| The market | createMarketAndSeed binding a market to that fact |
Gas only — sponsored under the venue’s marketCreation grant |
Nobody, effectively (the grant: 100 creations at venue birth, 5 per wallet per day) |
| Seed liquidity | The market’s opening book (150 PST minimum) | The creator’s own collateral — deliberately not sponsored | The creator — it is their stake in their own market, and they receive the LP shares |
That table is Route 1. A creator with zero SOMI creates end-to-end: the fact fee is Connect’s, the gas is sponsored, and the only thing they part with is the seed they get LP shares for.
Route 2 is paid for differently, and it is worth knowing which. prophecy markets mint does not
go through prepare at all — it posts to the Studio console, which puts each candidate through
social-bots’ evaluate and create. The fact fee and the seed come out of the bots’ operator
wallet, because a Studio venue has no wallet, no on-chain id and no key that may create. So the
person running the command parts with nothing, seed included — and that is exactly why the caps
exist (8 per call, 20 per venue per day, counted per venue). It spends Prophecy’s money, so
ownership of the venue is checked before the request body is even read.
Route 3 pays for both halves itself. Your wallet, your fee, your seed.
Two consequences of that split worth designing around:
- Facts are the expensive half, and they are reused. One fact supports up to 64 markets, and a retried or re-opened prepare for the same definition reuses its already-scheduled fact instead of paying ~1.5 SOMI again (idempotent by design — a lost response finds its money on retry). Recurring templates ride the same rule: only a genuinely new round schedules a new fact.
- The fee is why
prepareis gated the way it is. It spends real funds server-side, so the route fails closed: attributed venue key with thetradescope, live session, the venue’s sponsor quota (dry-run), a per-wallet daily prepare cap, and exact-duplicate refusal — all before anything is paid.
Who decides what a good market is
Quality stays upstream, and all three routes run the same brain: Connect derives its templates from it, the CLI checks against it, and the library exposes it directly.
compass the quality brain — what a resolvable market looks like, and the taxonomy
↓
market-creator the engine — an idea + a model → a rated, resolvable market, published on-chain
↓
(the chain) Envio indexes it
↓
connect projects + serves it
Every creator at Prophecy runs the same engine: the social-bots event-sync bot, the
prophecy.social AI-creator flow, and partner agents. One engine, one quality brain — which is what
keeps a partner-created market and a bot-created one describing the same world.
Compass — the quality brain
@prophecy-dev/compass binds market class → resolution structure, sources, spec patterns, caveats,
timing. It is what determines whether a created market can resolve cleanly, which is the
failure mode that actually hurts: a market nobody can settle is worse than a market nobody trades.
Three properties are worth knowing because they explain its shape:
- Advisory, not a gate. It guides creation; it never resolves a market or blocks one.
- Read-only at runtime. The graph changes only through a merged PR and a deploy — never mutated live. Bots and human partners go through the same door: an evidence-backed PR, CI-validated, merged by a curator.
- No LLM dependency. Every read is deterministic rules plus curated data. Consumers inject the guidance into their own prompt, so Compass stays reproducible while the model around it changes.
It also improves from outcomes. A bot analyses its own creation→resolution history and opens a PR
with evidence — “demote source Z, void rate climbing”. Source quality dominates resolution
success, so a Source is a first-class, individually-scored node rather than a string in a prompt.
What this means for a venue
The taxonomy is upstream, and it is small on purpose. Compass currently defines 9 categories
(crypto, sport, politics, technology, science, entertainment, …) and 20 topics.
Connect’s API imports them directly — CATEGORIES, canonicalTags, canonicalizeCategory,
getTopic — so there is exactly one definition in the system and no mapping table between the two
projects to drift.
GRAPH_VERSION is how a taxonomy change reaches you. Connect stamps its category projections
with the Compass graph version it classified under (0.15.0 at time of writing). When Compass ships
a new graph, rows carrying the old stamp are re-derived rather than silently kept — so a
re-categorisation upstream propagates instead of leaving stale labels behind a cache.
Category is a column, not a tag. Worth stating because it has caused real bugs: market tags are
namespaced (category:crypto), while a follow’s subject ref is stored bare (crypto). Matching a
subject ref directly against a tag looks correct on topic — the one axis where they coincide — and
silently misses the other four. The per-axis contract is written up in TAXONOMY.md in the Connect
repo.
Where to go next
- Route 1, the wire contract →
@prophecy-dev/connect-wire(MarketDefinition,MarketPreview,PrepareError), and the confirm sheet in SDK and React. - Route 2, the full CLI surface →
prophecy --help, and the Studio docs. - Route 3, the engine → the
market-creatorREADME and itsexamples/directory, which runs in order from quickstart to on-chain publishing. - Contributing domain knowledge — better sources for a sport or a region → open a PR against
compass. That is the intended path for partners, not a special case. - Reading and filtering what already exists → SDK and React.
market-creator and compass are published to npm under the restricted @prophecy-dev scope and
install with the same NPM_TOKEN as the Connect packages. Both are pre-release: shared source, MIT,
APIs may change.