PRESAGE+ Create market
Protocol documentation

How Presage works

Presage is a set of smart contracts for parimutuel prediction markets and 1v1 duels on tokenized stocks, live on Robinhood Chain. Every market settles itself from a 30-minute on-chain price average — no oracle feed, no judges, no disputes. This is the technical reference.

Chain Robinhood (EVM 4663)Unit USDG · 6 decimalsOracle Uniswap v3 TWAPFee 2%
Overview

A market that settles itself

Traditional prediction markets need an oracle or a human to declare the winner. Presage removes that trust assumption: each stock already trades against USDG in a Uniswap v3 pool on Robinhood Chain, so the market reads the stock's own on-chain price to decide the outcome. Nobody adjudicates — the tape does.

There are two ways to play, sharing one settlement engine:

  • Prediction markets — a shared parimutuel pool where everyone bets YES or NO on where a stock goes.
  • Showdown duels — a private 1v1 wager: your champion stock against a rival's, highest return wins.

Everything below is enforced by the contracts — not the front end. The app is just a convenient window onto state anyone can read or write directly.

Architecture

Five contracts, one vault

Funds live in exactly one place. HoodBet is the parimutuel core: it escrows every stake, tracks who bet which side, pays the fee, and settles claims. It never decides outcomes itself — it asks a resolver. TwapResolver supplies the automated price templates; AdminResolver is a manual fallback for edge cases. StockDuel runs the 1v1 Showdown game with its own escrow, and ProfileRegistry holds optional on-chain names.

Trader (EOA)signs USDG betsTwapResolverup/down · over/under · duelStockDuel1v1 Showdown escrowAdminResolvermanual fallbackHoodBet — parimutuel coreholds every pool + stake · pays fee to owner · settles claimsUniswap v3 stock/USDG pools30-min TWAP = the oracleOwner / treasuryreceives 2% fee

Traders touch the resolvers; only HoodBet holds funds. Prices come from the same pools you can swap on.

Mechanics

Parimutuel pools

There is no house taking the other side and no fixed odds. When you call bet(id, side, amount), your USDG is added to that side's pool. The implied probability of YES is simply its share of the total: poolYES / (poolYES + poolNO). As money arrives, the odds move.

At resolution the losing pool is handed to the winning side, pro-rata to each winner's stake, after a 2% fee. Winners also get their own stake back. If a side is empty, there is nothing to contest and everyone is refunded in full.

# winner payout for a stake s on the winning side w
prize = poolLOSING × (1 − 0.02)
payout = s + prize × ( s / poolWINNING )
POOLS AT CLOSEYES $600NO $400YES winsLOSING POT $400 SPLITS2%fee$8$392 → YES bettorspro-rata to stake, plus their own stake backA $150 YES bet (25% of the YES pool) collects:$150 + $392 × (150 / 600) = $248Odds are just the pool ratio — no house book, no counterparty.

Parimutuel: the losing side funds the winners. Empty side ⇒ everyone is refunded in full.

Mechanics

Three market templates

Anyone can create a market permissionlessly from one of three shapes. All comparisons happen in Uniswap tick space, where one tick is 0.01% of price — so a strike is just a tick, and returns are already logarithmic.

Up / down

The simplest call. At creation the resolver records the stock's current TWAP tick as the reference. YES (up) wins if the settlement TWAP is strictly above that reference.

Over / under

You name a price; the app converts it to a strike tick. YES wins if the stock finishes strictly above the strike. Same math as up/down, but the reference is your chosen level instead of the launch price.

Stock duel

Two stocks, one window. The resolver snapshots both TWAP ticks at creation and compares their tick deltas (log-returns) at settlement. Side A wins on a strictly larger move; a tie resolves to B, as stated in the market rules.

Mechanics

30-minute TWAP resolution

Every price the protocol trusts is a time-weighted average price over the last 30 minutes, read straight from the Uniswap v3 pool's observation history via observe([1800, 0]). The pool stores a running tick cumulative; the average tick over the window is the difference of two samples divided by the elapsed seconds (rounded down, matching Uniswap).

pricetime30-min windowTWAPt − 1800stavgTick = (tickCumulative[t] − tickCumulative[t−1800]) / 1800

Resolution reads a time-weighted average, never the last trade — a one-block spike can't move it.

Because it is an average, not a spot read, an attacker can't swing the outcome by pushing the price for a single block right before resolution — they'd have to hold the price off-market for the whole window, which costs far more than any pot. Tick space is then converted to a human price with price = 1.0001^tick, corrected for whether the stock is token0 or token1 in the pool.

Mechanics

Market lifecycle

A market is created with a closeTime (betting deadline) and a resolveTime. The contract enforces resolveTime ≥ closeTime + 1 hour — the lock gap. Once resolveTime passes, anyone can call resolve(); winners are motivated to do it promptly, and the app auto-resolves too. Then each winner calls claim() to withdraw.

Createdtemplate + deadlinesOpenbet either sideLocked1h gap · no betsResolvedanyone calls resolve()Claimwinners withdrawcloseTimeresolveTimeCanceled → full refund

Betting always closes at least an hour before the TWAP is sampled, so no one bets on a half-settled outcome.

Mechanics

Why it can't be gamed

  • TWAP, not spot — the 30-minute window makes last-second price pushes worthless.
  • Lock gap — betting ends an hour before the price is sampled, so you can never bet on a half-known result.
  • Cardinality floor — a pool must carry at least 100 observations of history to be tradable; thin pools, whose averages are cheap to move, are rejected outright.
  • Real-pool check — a market only lists if the pool is genuinely paired with USDG and holds live liquidity, filtering out dead or out-of-range pools with bogus prices.
  • No privileged oracle — resolution is a pure function of public pool state; there is no feed to bribe or halt.

The owner key can only cancel a market (triggering full refunds) and collect the 2% fee. It cannot pick a winner or touch a winner's stake.

Games

Showdown — 1v1 duels

A Showdown is a direct wager. You call create(pool, stake, duration) with your champion stock and the amount you're risking. A rival answers with accept(id, poolB), matching your stake with their own stock. At that instant both TWAPs are snapshotted and the clock starts; duration runs from 1 hour to 30 days.

When the window closes, the contract compares each champion's log-return from its snapshot. The larger move wins the whole pot minus a 2% fee; a genuine tie refunds both sides. Until someone accepts, the creator can cancel and reclaim their stake.

Creatorchampion stock A+ stake + durationChallengerrival stock Bmatches the stakeopen challengeaccept() snapshots both TWAPsrefA, refB · clock startsAfter duration: bigger log-return winswinner takes pot − 2% · draw refunds both

Both champions are measured from the same starting instant, so the duel is a pure relative-return race.

Games

Daily rounds

Alongside user-created markets, a scheduled job publishes fresh up/down rounds each session on deep-pool names and settles them at U.S. market close. They're ordinary HoodBet markets — same pools, same TWAP, same claim flow — just created on a cadence so there is always something live to trade.

Protocol

Fees & payout

The fee is a flat 2% (FEE_BPS = 200), and it only ever applies when there is a real contest with a winner and a loser:

  • Markets (HoodBet) — 2% of the losing pool. Winners split the remaining 98% pro-rata and keep their own stake in full.
  • Duels (StockDuel) — 2% of the whole pot; the winner takes the other 98%. A draw refunds both stakes with no fee.
  • Refund cases — canceled markets, empty-side markets, and tied duels return every stake untouched.
Protocol

On-chain identity

Presage needs no accounts. Your wallet address is your identity: a small rolling hash of the address deterministically derives both a readable pseudonym and an avatar seed, so the same address always shows the same name and face — on any device, with no database behind it.

0x5891…42F7rolling hashh = h·31 + bytepseudonymavatar seed

Same address ⇒ same face and name, on every device, with zero server state.

Two optional layers sit on top: a locally stored display name and photo you control in your browser, and an on-chain name claimed through ProfileRegistry for anyone who wants a portable, verifiable handle.

Protocol

Reference

Deployed contracts — Robinhood Chain

ContractRoleAddress
HoodBetParimutuel escrow + accounting0xfcfbb9365ffb00d153ba73162c178916855f8b3b
TwapResolverAutomated price templates0x9249c9EeDdd5188eaC6E86c804a1255249F1CEc3
StockDuel1v1 Showdown duels0x646c1bf51d20ce0d794fbaebdb9bc17a110c9d9f
AdminResolverManual resolution fallback0x6fc7dbbd27039d77f4f22d10e67938e4bddb4d31
ProfileRegistryOptional on-chain names0x47c9908766ef11f69caf120134f6e9c6ab23201b

Protocol constants

ConstantValueMeaning
TWAP_WINDOW30 minutesAveraging window for every price read
LOCK_GAP1 hourMinimum gap between betting close and resolution
FEE_BPS200 (2%)House fee — losing pot (markets) / whole pot (duels)
MIN_CARDINALITY100Minimum pool observation history to be tradable
MIN_DURATION / MAX_DURATION1 hour / 30 daysShowdown duel length bounds