The rules, the contracts, and exactly what is private and what is public.
| Contract | Address |
|---|---|
| hasher | 0xa7744B83495bEde25EdC03575915e05d60f30E87 |
| verifier | 0x75CB7C620d8652c9091fcEAA6CddB4abef7F6F52 |
| oracle | 0x67795378c74fcc2c3dEa75D08D622c3aEA173e51 |
| factory | 0xDB7136C0C4FCEa61462A951BC1859Ef017E5e2d8 |
| notePool_0_1 | 0x5c2330398d579adc4f20D21Bf8a3303DC57b6CfD |
| notePool_1 | 0x469B888604A3d2f2Ff0bB053E7428A7F81463AF1 |
| notePool_10 | 0x56a42Ec8cD8C9588AaFDe6F84a04c174143617d9 |
| marketImpl | 0x8c1B57e184fCA6A98118B86de201924674b7b017 |
Everyone sees the odds move. Nobody sees who bet or how much each person bet.
See the table at the top of this page.
(This list is filled at runtime from the deploy record in MongoDB; every address links to the Elysium explorer.)
1. A user deposits a fixed amount (0.1, 1 or 10 HYPE) into a NotePool from any wallet and keeps a secret note (nullifier, secret). The pool only stores commitment = Poseidon(nullifier, secret) as a leaf of a depth-20 Merkle tree.
2. To bet, the user's browser builds a Groth16 proof that it knows a note in the tree (public inputs: root, nullifierHash = Poseidon(nullifier), extDataHash). extDataHash binds the proof to the target market, the exact bet calldata, the relayer and the relayer fee, so nobody can redirect it.
3. The proof goes to the relayer (POST /api/relay/bet), which checks it with eth_call and sends it from the app's keeper wallet. The pool marks the nullifier spent, pays the relayer fee, and calls Market.bet(outcome, positionOwner) with the rest of the note. positionOwner is a fresh per-bet key made in the browser; it never holds gas.
4. After resolution, the position owner signs an EIP-712 Claim (or Refund) to any fresh recipient address. The relayer submits it (POST /api/relay/claim) and is paid the signed relayer fee from the payout.
5. Unused notes can be withdrawn to any address with a proof (POST /api/relay/withdraw).
| Rule | Value | Where |
|---|---|---|
| Note denominations | 0.1, 1, 10 HYPE (one pool each) | NotePool.denomination |
| Merkle tree depth / root history | 20 levels / last 30 roots accepted | NotePool.levels, ROOT_HISTORY_SIZE |
| Relayer fee | one fixed fee per denomination (0.1 → 0.001, 1 → 0.002, 10 → 0.005 HYPE), published in /api/pools; the relayer refuses any other fee, including 0. On-chain it is bound in the proof and must be < denomination | src/relayer.js, NotePool.spend |
| Stake per note | denomination − relayer fee | NotePool.spend → Market.bet |
| Who can bet | only a NotePool created by the factory (pool factory() is immutable), until deadline − 10 min (bets close before sampling starts) | Market.bet |
| Market kinds | ABOVE (0 = YES mcap ≥ target, 1 = NO), HYPE_BRACKET (outcome i = number of edges ≤ price), RACE (coin with highest median mcap in HYPE, no USD oracle; ties → lowest index) | Market |
| Max outcomes | 8 | MAX_OUTCOMES |
| Duration | 1 hour to 30 days from creation | MarketFactory.MIN_DURATION/MAX_DURATION |
| Template markets (anyone) | Ascend coins (WHYPE pair) or Montra coins (native HYPE pair), target $1k–$10B, pool liquidity ≥ 1e23 | createFromTemplate |
| Keeper markets | daily: 2x, 5x and $1M market-cap targets on the top active coins (up to 2 Montra and 2 Ascend; HYPE, WHYPE or KNTQ pairs; KNTQ priced through the HYPE/KNTQ pool 0xc96bfd21…), plus daily HYPE/USD brackets. The keeper only spends gas sampling and resolving its own markets, or other markets with stakes on ≥ 2 outcomes | src/keeper.js |
| Coin market cap | StateView.getSlot0(poolId) sqrtPriceX96 → coin price in pair (→ HYPE through the quote pool for KNTQ pairs) × totalSupply(); × HYPE/USD for ABOVE | Market.currentValues |
| HYPE/USD | posted by the keeper from Hyperliquid allMids (trusted keeper post), at most one post per 60 s and at most ±10% vs the previous post (larger real moves are followed in steps); raw response stored and served on the market endpoint | HypeOracle.post |
| Oracle freshness | a sample needs a post younger than 5 minutes | ORACLE_MAX_AGE |
| Sampling | two steps, from deadline − 10 min to deadline + 10 min: poke() records current values, then sample() at least 5 s and 1 block later (within 60 s) stores the average of the poked and current values. Two tracks: calls from the keeper go to the keeper track, everyone else to the public track. Per track: one pending poke, ≥ 60 s between samples, max 32 samples | Market.poke/sample, POKE_MIN_DELAY, POKE_TTL, SAMPLE_INTERVAL, MAX_SAMPLES |
| Resolution | anyone, after deadline + 10 min; uses the median (lower middle) of the keeper track if it has ≥ 3 samples, else of the public track if that has ≥ 3 | Market.resolve |
| Payout | stake + stake × (losing pools × 98%) / winning pool (rounded down) | Market.claimable |
| Fee | losing pools − floor(losing pools × 9800 / 10000) (≈ 2%), sent to the treasury at resolution; if the push fails, anyone can call collectFee() | FEE_BPS = 200 |
| One side empty / winning pool empty | everyone refunded in full, no fee | Market.resolve |
| Timeout | if not resolved by deadline + 3 days and no track has ≥ 3 samples, anyone calls enableRefund(); full refunds, no fee (with enough samples, anyone can still call resolve()) | REFUND_DELAY |
| Claims | EIP-712 signature by the position owner (domain "QuietMarket", v1), submitted by anyone; replay rejected. Payout to any address, or claimToNotes: whole notes deposited into a note pool plus the remainder to an address | Market.claim/claimRefund/claimToNotes |
| Rounding dust | goes to the treasury after every winner has claimed; after resolution + 365 days only balance − outstanding() can be swept | Market.sweepDust |
| Admin powers | no owner, no pause, no upgrade, and no key can move funds out of pools or markets. The keeper can create markets and post HYPE/USD within the bounds above, which decides HYPE_BRACKET markets and the USD conversion of ABOVE markets (see the keeper trust section below) | all contracts |
Relayed bets and claims are broadcast after a random 0–20 s delay.
Zero house capital: every payout comes from other players' stakes. Total payouts can never exceed the stakes minus the fee.
Public on-chain
/api/pools). Wait for more deposits before betting. The relayer adds a random 0–20 s delay before broadcasting, so the on-chain time doesn't match the request time, but that is no substitute for waiting.claimToNotes).Visible to the relayer operator and its host
Proof freshness (root history)
UnknownRoot.Fixed relayer fees
/api/pools, and the relayer refuses any other fee, including 0. Every bet of a given note size therefore has the same stake.Trust in the keeper (HYPE/USD)
Coin price samples
StateView.getSlot0) at each sample.Fees accrue to the app-owned treasury wallet. When TOKEN_CA is set (and only then), an hourly keeper job spends the treasury's HYPE above a 0.01 HYPE gas reserve to buy TOKEN_CA on-chain. The treasury keeps the bought tokens. Nothing runs if less than 0.005 HYPE has accrued.
StateView.getSlot0 on the possible pools, in this order: Montra (native HYPE, hook 0x6b1216A40B65c83e9c9e18a562C446697B8cf8EC), then Ascend WHYPE, then Ascend KNTQ (routed through the HYPE/KNTQ pool with fee 10000 and tick spacing 200). The first live pool is used.0x1B341FDD55d56F8E08409A2C25DDFad93Eb03C1a (v4 pools on the shared PoolManager). The minimum output is the V4Quoter quote minus 3%, and the tx is checked with eth_call first./api/token: count, HYPE spent, tokens bought, last tx).