Under the hood
Contracts
Addresses, the powers table, invariants, the changes from the reference contract and how to verify each one.
Addresses
Every address the venue uses comes from one file, deployments/robinhood-mainnet.json, which the site, the keeper and the README all read. Each links to Blockscout.
HunchVPM
Holds every stake and pays every winner. Nobody can move a stake or set a price.
StockRoundResolver
Settles each market from two proven Chainlink rounds. It has no owner.
HunchMarketFactory
Lists a market in one transaction, with the opening seed paid by the lister.
USDG
Paxos Global Dollar, the only stake and payout asset.
Price feeds
NVDA / USD
Chainlink price of the NVIDIA Stock Token, 8 decimals, 0.5% deviation or 24 h heartbeat.
TSLA / USD
Chainlink price of the Tesla Stock Token, 8 decimals, 0.5% deviation or 24 h heartbeat.
AAPL / USD
Chainlink price of the Apple Stock Token, 8 decimals, 0.5% deviation or 24 h heartbeat.
COIN / USD
Chainlink price of the Coinbase Stock Token, 8 decimals, 0.5% deviation or 24 h heartbeat.
How they fit together
Safe (owner / guardian / treasury)
│ setFeed, setOpener │ setEntriesPaused (enter only)
▼ ▼
keeper (opener) ─► HunchMarketFactory ──create──► HunchVPM ◄── bettors: enter / (relayed) enterWithAuthorization
│ (seed legs │ claim / withdrawRefund
│ register spec handed back │ ◄── anyone: claimFor / withdrawRefundFor
▼ to opener) │ finalizeVintage / sweepFees
StockRoundResolver ──resolve / void────┘
│ getRoundData / latestRoundData
▼
Chainlink stock feed proxies (NVDA/USD, TSLA/USD, …) on Robinhood Chain- HunchVPM holds every stake, books every bet and pays every winner. It has no owner, no upgrade and no price input.
- StockRoundResolver settles each market from two proven Chainlink rounds. It has no owner and no admin.
- HunchMarketFactory lists a market in one transaction: it takes the opening seed from the lister, creates the market, registers its settlement spec, hands the seed positions to the lister and keeps nothing.
Solidity 0.8.28, optimizer at 200 runs, EVM version cancun, no via-IR, and no upgradeable proxies. Production contracts use no external libraries beyond minimal local interfaces.
Powers
Stated once, verbatim, the same table the README carries:
| Who | Can | Cannot |
|---|---|---|
| Safe (guardian of HunchVPM) | pause and resume new entries and new markets; name or remove the pauser | pause or block claims, refunds, resolution; move any stake; set any price |
| Pauser (one key the Safe names) | pause new entries and new markets | resume them; anything else |
| Safe (owner of factory) | allow-list a feed, its Stock Token and its staleness bounds; add/remove an opener | change a listed market's feed, times, bounds, seed, fee or caps |
| Opener (keeper hot wallet) | list a new market through the factory (the only creator HunchVPM accepts), paying the seed itself; owns the seed legs it paid for | change or close an existing market; touch anyone else's position |
| Anyone | resolve with the two proven rounds; void on proven staleness, a proven out-of-range price or a 24 h oracle pause; relay a bettor's signed entry; deliver claims and refunds to owners; sweep fees to the treasury | choose the outcome; send anyone's funds anywhere but to their owner |
| StockRoundResolver | settle its registered markets per the spec | anything else (it has no owner) |
Safe (guardian of HunchVPM)
- Can
- pause and resume new entries and new markets; name or remove the pauser
- Cannot
- pause or block claims, refunds, resolution; move any stake; set any price
Pauser (one key the Safe names)
- Can
- pause new entries and new markets
- Cannot
- resume them; anything else
Safe (owner of factory)
- Can
- allow-list a feed, its Stock Token and its staleness bounds; add/remove an opener
- Cannot
- change a listed market's feed, times, bounds, seed, fee or caps
Opener (keeper hot wallet)
- Can
- list a new market through the factory (the only creator HunchVPM accepts), paying the seed itself; owns the seed legs it paid for
- Cannot
- change or close an existing market; touch anyone else's position
Anyone
- Can
- resolve with the two proven rounds; void on proven staleness, a proven out-of-range price or a 24 h oracle pause; relay a bettor's signed entry; deliver claims and refunds to owners; sweep fees to the treasury
- Cannot
- choose the outcome; send anyone's funds anywhere but to their owner
StockRoundResolver
- Can
- settle its registered markets per the spec
- Cannot
- anything else (it has no owner)
Invariants
Properties the contracts are built to keep, checked by the test suite over random sequences of bets, settlements and payouts:
| Id | Property | In plain words |
|---|---|---|
| INV-1 | Solvency | The contract always holds at least everything it owes: open stakes, unclaimed payouts and refunds, leftovers and fees. |
| INV-2 | Conservation | A settled market pays out exactly its pool (plus a rounding leftover); a refunded one returns exactly what was accepted. |
| INV-3 | Exits | Money leaves only as a payout or refund to its owner, a leftover to its named owner, or fees to the treasury. |
| INV-4 | Pause scope | While new bets are paused, only new bets fail. |
| INV-5 | Only goes up | An open position's win payout never decreases, whatever comes after it. |
| INV-6 | Reference equivalence | With the fee and limits switched off, it behaves exactly like the reference contract. |
| INV-7 | Settlement soundness | Only the one valid pair of rounds can settle a market; any other pair is rejected. |
| INV-8 | Signed bets | A signed bet with a different market, side, amount or salt fails, and a used signature cannot be replayed. |
Changes from the reference
HunchVPM is the paper’s reference contract, VestedParimutuel.sol, changed by these ten diffs and nothing else. The literal diff against the reference is kept with the source as contracts/DIFF.md. The payout arithmetic, the batching, the settlement semantics and the storage layout are untouched.
| Change | Why | |
|---|---|---|
| D1 | A fee on winners' gains (at most 5%, 2% on this venue), taken at claim; swept to the treasury separately. | The business model, with no fee on stakes, refunds or losses. |
| D2 | claimFor and withdrawRefundFor: anyone can deliver a payout or refund, always to its owner. | The keeper pushes every payout, so nobody has to come back to claim. |
| D3 | Minimum and maximum bet per market, fixed at listing. | A guarded beta that bounds the damage of any bug. |
| D4 | The guardian can pause new bets. Nothing else is pausable. | An emergency brake that cannot trap anyone’s money. |
| D5 | enterWithAuthorization: a bet from a signed USDG transfer bound to market, side and amount. | Gasless bets; the bettor needs no ETH. |
| D6 | Read views: accrued, marketPositions, previewFee (and marketTerms). | What the site needs to show a position and a book. |
| D7 | Events: FeeAccrued, FeesSwept, EntriesPaused. | So indexers and the Proof page can follow fees and pauses. |
| D8 | Settlement finalizes the last batch of bets even in the same Ethereum block as the last bet. | On Robinhood Chain one Ethereum block spans many chain blocks; without it a market could be drained. |
| D9 | At most 200 bets per batch, and a cap on capacity. | Settling a batch always fits in a block, so no market can be locked. |
| D10 | Only the market factory can create markets; the pause also stops new markets; a pauser key can pause but never resume; a claim checks its bet was batched. | One escrow holds every market, so nobody else may open one with odd settings, and the brake is fast. |
The settlement contract
StockRoundResolver settles on the Chainlink price in effect at each bell, proven by round id, so neither the time of the call nor who makes it can change the answer. Its functions:
resolve(specId, strikeRound, finalRound): anyone, after the bell. Verifies both proofs, then settles UP, DOWN, or refunds on a flat price. Never refunds for staleness.voidStale(specId, strikeRound, finalRound): anyone, after the bell, only if the proven rounds break an age limit. A market with a good answer cannot be refunded this way.voidBadAnswer(specId, strikeRound, finalRound): anyone, after the bell, only if a proven round’s price is out of range (not a real price). A market with a good answer cannot be refunded this way.voidPaused(specId): anyone, 24 hours after the bell, if Robinhood’s corporate-action flag on the token is still set.preview(specId, strikeRound, finalRound): read-only; the status and both prices, for the keeper and for you.
Each market’s spec (settler, market id, feed, Stock Token, both bell times, both age limits) is hashed into its specId when it is registered, and cannot change. Details: round proofs.
The market factory
openUpDown can be called only by an allowed lister (the keeper’s wallet). It checks the feed is allow-listed, the market is listed before its opening bell, the window is at most 8 days, the seed is at least 1 USDG per side and any age limit asked for is tighter than the feed’s own, never looser. It lists every market with the same constants: capacity 30, fee 2% of gains, refund timeout 72 hours, and leftovers and fees to the treasury Safe. The Safe, as owner, can only allow-list feeds (with their Stock Token and age limits) and listers; it cannot change a listed market.
Verify them yourself
Each contract is verified on Blockscout, with Sourcify as the fallback:
forge verify-contract <address> src/HunchVPM.sol:HunchVPM \
--chain-id 4663 --rpc-url $RH_RPC_URL \
--verifier blockscout --verifier-url https://robinhoodchain.blockscout.com/api/The reference contract and the paper: The Vested Parimutuel (opens in a new tab), 2nd edition, conformance suite 1.2.0 with 118 vectors.