Docs v2 · testnet

Architecture

Read mainnet, decide once, serve everyone

Novak has three zones: real-world sources on Robinhood Chain mainnet (read only), an off-chain resolver network, and the contracts on Robinhood Chain testnet that store, dispute and serve the answers.

The big picture

1 · Sources

Robinhood Chain mainnet (4663) + public APIs

  • 53 Chainlink feeds (stocks, SGOV, crypto)
  • ERC-8056 stock tokens — multiplier logs
  • Robinhood asset API — trading status
  • FRED — Fed target rate

2 · Resolver network

independent Node.js daemons

  • discover events from logs
  • observe via adapters + evidence hash
  • vote in dispute committees
  • keeper: finalize / escalate / settle
  • claim fee rewards

3 · Contracts

Robinhood Chain testnet (46630)

  • EventRegistry — specs, quorum, finality
  • DisputeManager — bonded committees
  • EventComposer + EventBus — compose & read
  • TreasuryVault — fee thirds
  • DistributionMarket · Market · StockLendingGuard

Why split mainnet and testnet? Chainlink's Robinhood stock feeds and the real ERC-8056 stock tokens only exist on mainnet, so resolvers read the real data there and write their observations to testnet, where everything else runs on test money (MockUSDG). Moving the contracts to mainnet later changes an RPC URL, not the design.

The five layers

Click a layer to see its job and the functions it exposes. A layer only talks to the one directly beneath it.

Layer Dependency Stack (Bottom → Top)Click any layer to inspect interface details
01

Source

Robinhood Chain mainnet + public APIs (read-only)

Raw facts: Chainlink stock & SGOV feeds, ERC-8056 stock-token multipliers, Robinhood's asset API, the Fed's target rate.

Novak never writes here. Resolver adapters read it (resolver/adapters/): chainlink.price-at.v1, rh.corporate-action.v1, rh.trading-status.v1, macro.fomc.v1. Mainnet is not an archive node, so adapters read logs and round history rather than historical eth_call.

Interfaces & Methods:Chainlink AggregatorV3UIMultiplierUpdated logsapi.robinhood.com/rhj/assetsFRED DFEDTARU
02

Resolvers & quorum

EventRegistry.sol

Authorized resolvers submit what they observed plus an evidence hash; N-of-M identical answers propose the outcome.

Observations are rejected before openTimestamp and after observationDeadline. v2 payloads carry occurredAt. Each resolver's boolean is recorded so the TreasuryVault can pay the ones who were right. Nobody observed it → Expired.

Interfaces & Methods:createEvents()submitObservation()finalize()expire()observedOutcome()
03

Disputes & finalization

DisputeManager.sol

A proposed outcome waits out its dispute window; a bonded challenge hands it to resolver committees.

Tier 1: up to 7 resolvers, 1 hour, 66% of the committee decides. No decision → Tier 2: up to 15, 2 hours. Still none → Voided, bonds refunded. Never a token vote. Payouts are pull-based (withdraw()).

Interfaces & Methods:dispute()submitTier1Vote()escalateTier2()submitTier2Vote()voidAfterTier2Timeout()
04

Event Bus & composition

EventBus.sol & EventComposer.sol

Finalized events become standing facts that can be read and combined.

EventComposer builds AND / OR / NOT / BEFORE / WITHIN composites with canonical IDs and bounded depth; a voided operand propagates. EventBus serves primitives and composites through one read API with Pending / Available / Voided.

Interfaces & Methods:readOutcome()getAvailability()createComposite()tryResolve()
05

Applications

DistributionMarket · Market · StockLendingGuard · your contract

Consumers read only the Event Bus (via Settlement) and push fees to the TreasuryVault.

Range markets (LMSR) settle on a ladder of threshold events; yes/no markets settle on one event; the lending guard pauses liquidations. Each has a regression test proving it holds no Registry, Composer or resolver reference.

Interfaces & Methods:Settlement.getSettlements()settle()redeem() / claim()canLiquidate()

Side by side: an event oracle, not another price feed

What each oracle design gives a prediction market. Chainlink is one of Novak's data sources, not an alternative to it. For how an answer is decided and defended, see Oracle architecture.

Live pricesPrice oracleits core jobOptimistic + token votenot designed for itNovak
reads Chainlink as a source
Corporate actions & halts as factsPrice oraclenot providedOptimistic + token voteany question, in proseNovak
machine-checkable specs
Compose AND / OR / BEFORE / WITHIN on-chainPrice oracle—Optimistic + token vote—Novak
canonical composite IDs
Hard disputes decided byPrice oraclen/aOptimistic + token votetoken-holder voteNovak
bonded resolver committees
When nobody can agreePrice oraclen/aOptimistic + token votethe vote still picksNovak
VOID → refunds
Fees reward whoever was rightPrice oracle—Optimistic + token votebonds and voter rewardsNovak
TreasuryVault ⅓·⅓·⅓

Contracts and their roles

Contract

EventRegistry

Job

Event specs, observations, quorum, finalize / expire, per-resolver outcomes

Reads

—

Called by

anyone (create), resolvers (observe), keeper

Contract

DisputeManager

Job

Bonds, committee draw, Tier-1/Tier-2 votes, VOID, pull payouts

Reads

Registry

Called by

disputers, committee resolvers, keeper

Contract

EventComposer

Job

AND / OR / NOT / BEFORE / WITHIN composites, cached resolution

Reads

Registry

Called by

anyone, keeper (tryResolve)

Contract

EventBus

Job

One read API for primitives + composites: readOutcome, getAvailability

Reads

Registry, Composer

Called by

every consumer (through Settlement)

Contract

Settlement

Job

Thin adapter: (availability, bool outcome) for one or many events

Reads

EventBus

Called by

markets

Contract

TreasuryVault

Job

Fee thirds per event, insurance reserve, dispute-bond sweep, claims

Reads

Registry, Composer, DisputeManager

Called by

markets (deposit), resolvers (claim)

Contract

DistributionMarket

Job

LMSR range markets over threshold ladders

Reads

Settlement only

Called by

traders, keeper (settle)

Contract

Market

Job

USDG yes/no parimutuel markets

Reads

Settlement only

Called by

traders, keeper (settle)

Contract

StockLendingGuard

Job

canLiquidate(token): pause while a risky event is open

Reads

EventBus only

Called by

lending protocols

The one rule

Applications depend on the Event Bus — never on a resolver, the Registry or the Composer.

A market holds a Settlement reference (which holds an IEventBus), so the resolver network can add sources, change quorum rules or add resolvers without redeploying a single market. Consumers may additionally push fees into ITreasuryVault; the vault — protocol infrastructure — is the one that reads the Registry and DisputeManager to decide who gets paid.

Consumer

Market

Holds only

Settlement

Regression test

Market.t.sol::test_market_onlyHoldsSettlementReference

Consumer

DistributionMarket

Holds only

Settlement

Regression test

DistributionMarket.t.sol::test_holdsOnlySettlementReference

Consumer

StockLendingGuard

Holds only

IEventBus

Regression test

StockLendingGuard.t.sol::test_guard_onlyHoldsEventBusReference

Consumer

ExternalPredictionMarket (example)

Holds only

IEventBus

Regression test

ExternalPredictionMarket.t.sol::test_holdsOnlyEventBusReference

One range market, end to end

  1. 1

    Create the ladder

    SDK
    The creator builds N−1 threshold events (“NVDA ≥ $230 at T”, “≥ $232”, …) with buildPriceLadderSpecs and creates them in one transaction with createEvents.
  2. 2

    Open the market

    DistributionMarket
    createMarket(question, boundaryIds, tradingClosesAt, b) pulls the LMSR subsidy b·ln N in USDG. Every range starts at 1/N.
  3. 3

    Trade until close

    traders
    Buy or sell any range; prices move with the LMSR and always sum to 1. A 1% fee accrues per trade.
  4. 4

    Observe at T

    resolvers
    After T each resolver finds the Chainlink round in effect at T on mainnet and submits TRUE/FALSE per boundary with an evidence hash.
  5. 5

    Quorum → dispute window → final

    Registry
    Identical answers from the quorum propose the outcome; if nobody disputes before the window ends, the keeper finalizes it.
  6. 6

    Settle

    keeper
    Winning range = number of TRUE boundaries. Fees go to the TreasuryVault, tagged with the boundary events; leftover subsidy returns to the creator.
  7. 7

    Redeem & claim

    everyone
    Winners redeem 1 USDG per share; resolvers who reported the final answer claim their third from the vault.

Off-chain pieces

Resolver node (resolver/)

One process per resolver key. Duties run on a poll loop: discovery, resolve, committee voting, keeper (one node with RESOLVER_KEEPER=true), rewards. It serves /health and /evidence/:hash so anyone can check what a resolver saw. See Resolver network.

Frontend data routes (frontend/src/app/api/)

Route

/api/feeds

Returns

Latest round for every Chainlink Robinhood feed (list from Chainlink's directory, one mainnet multicall)

Cache

60 s

Route

/api/feeds/history

Returns

Recent rounds for one feed + realized volatility (drives charts and insights)

Cache

300 s

Route

/api/rates

Returns

Fed funds target range (FRED) + effective rate (NY Fed)

Cache

30 min

Route

/api/rh-assets

Returns

Corporate-action calendar: Robinhood's asset registry + each token's ERC-8056 multiplier state

Cache

5 min

SDK (@novakoracle/sdk)

Generated ABIs and deployment addresses (never hand-edited), a typed NovakClient, source-spec encoders, the 53-feed Chainlink catalog and ladder helpers. See the SDK reference.