Status: Robinhood production design; read with PROJECT_SPEC.md §5.
Oracle safety is load-bearing because WoolFi uses fair value to choose the fee applied to every swap. The production target is Robinhood Chain (4663); no production adapters are deployed yet.
- Stock/USDG spot: stock/USD divided by USDG/USD.
- Stock/WETH crypto beta: stock/USD divided by WETH/USD.
- Stock/stock relative value: the two stock/USD prices divided in pool token order.
- WETH/USDG always-open spot: WETH/USD divided by USDG/USD.
All adapter outputs use 1e18 normalization. Pool token address ordering determines whether the ratio is used directly or inverted.
IPriceOracle.getPrice() returns a WAD price and reverts on invalid or stale data.
IMarketHoursOracle.isMarketOpen() identifies equity-hours pools. An address-zero market-hours
oracle denotes an always-open pool.
IPriceOracle.getPriceData() returns the same WAD price plus the source updatedAt.
IPriceOracle.requireRuntimeGuards() enforces sequencer, pause, and last-print validity without
treating heartbeat age as a failure. IMarketHoursOracle.currentSessionStart() supplies the
session boundary used by post-open stabilization.
This is the production-shaped stock-leg adapter already present in code. Before returning a price, it verifies:
- the Robinhood Stock Token reports
oraclePaused() == false; - the Chainlink round has a positive answer, a valid round relationship, and a valid timestamp;
- feed age does not exceed twice the configured heartbeat;
- when a sequencer uptime feed is configured (constructor sequencer address non-zero and grace period non-zero): the sequencer uptime answer indicates up, sequencer timestamps are nonzero and not in the future, and the post-recovery grace period has elapsed.
Any failure reverts. sequencerEnabled is an immutable public view derived from the constructor
inputs. A half-configured pair (feed set with grace period zero, or vice versa) reverts at deploy
time with IncompleteSequencerConfig or ZeroGracePeriod. On Robinhood Chain (4663) both are
zero at launch (see §5.1 and "Sequencer safety" below).
One independently verified adapter is required for every stock leg; an adapter address in a dry run is not a deployment.
The generic adapter normalizes a Chainlink feed, rejects nonpositive values, and reverts when the feed exceeds its configured freshness bound. It remains suitable for non-stock legs only after the Robinhood feed proxy, decimals, and heartbeat are verified.
The legacy generic dual-source wrapper can return the primary when sources agree, fail over when
only one source succeeds, and revert on excessive deviation or two failures. Timestamp skew is
enforced by the hook across the two pool legs, not inside this adapter. Both legs still expose
getPriceData() and requireRuntimeGuards().
NyseHoursOracle and MultisigMarketHours remain available implementation artifacts. The selected
production source and calendar configuration must be reviewed for every stock-linked pool.
WETH/USDG uses no market-hours gate.
Open-market swaps and liquidity additions hard-revert when:
- either required leg is stale, invalid, incomplete, or future-dated;
- a Robinhood stock-token oracle is paused;
- a required adapter or feed is missing;
- the sequencer is down, has invalid status data, or is inside the recovery grace period - when a sequencer uptime feed is configured on the adapter.
Closed-market and stabilization paths still hard-revert on pause, invalid last-print, and (when
configured) sequencer failures via requireRuntimeGuards(). They do not treat heartbeat
staleness as a failure.
When maxOracleSkew is set, excessive two-leg timestamp skew degrades swaps to the flat base fee
and hard-reverts new liquidity. Stale, paused, invalid, and sequencer-unsafe feeds never degrade
into that mode; they revert.
Sizing the skew limit. Every Robinhood Chain feed is deviation-triggered (0.5%) with an 86400s
heartbeat. Two legs that are both healthy can show update times hours apart, because a quiet leg
simply has not moved 0.5% (live check on 2026-10-07: SPY vs QQQ 10,553s apart). That gap is not a
price error. A skew limit tighter than the slower leg's heartbeat therefore leaves a spread pool in
degraded mode (flat fee, deposits blocked, no break detection) most of the time. The batch config
sets maxOracleSkew = 86400 on all four spread pools, script/robinhood_batch.py rejects any
nonzero limit below the slower leg's heartbeat, and test/fork/WoolFiGaps.fork.t.sol checks the
configured value against live feed timestamps.
Robinhood Chain (4663) publishes no L2 Sequencer Uptime Feed, and Chainlink has stated
it is not expanding that product to new networks. The stock adapter is deployed with
sequencerUptimeFeed == address(0) and gracePeriod == 0 on 4663, which disables the sequencer
guard at construction. oraclePaused() and price-freshness enforcement remain in force. See
PROJECT_SPEC.md §5.1 for the disclosed operator-trust assumption; the same disposition applies
to ChainlinkOracleAdapter (WETH/USDG), which never called a sequencer feed in the first place.
If a compatible sequencer uptime feed is later published on 4663, governance may redeploy the affected adapters with the guard enabled. Doing so requires a new adapter instance per pool leg and a governor pool-oracle rebinding step.
Source: https://reference-data-directory.vercel.app/feeds-robinhood-mainnet.json (the JSON
Chainlink docs load their address tables from). All Robinhood Chain feeds share decimals = 8,
heartbeat = 86400s, deviation = 50 bps, and are SVR-enabled. Stock feeds report a Total
Return Value (underlying × uiMultiplier()); corporate actions are surfaced through the token
contract's oraclePaused(). Every address below must be re-verified on-chain immediately before
adapter deployment.
| Symbol | Chainlink proxy on 4663 | Notes |
|---|---|---|
| MSTR | 0x396118bdFB181e6240E74D243F266B061c0edc3D |
Robinhood MSTR/USD, TRV, us_equities_24/5 |
| COIN | 0xA3a468A452940B7D6b69991207B508c609a98Ef2 |
Robinhood COIN/USD, TRV, us_equities_24/5 |
| CRCL | 0x6652eDf64bA3731C4F2D3ce821A0Fb1f1f6b482a |
Robinhood CRCL/USD, TRV, us_equities_24/5 |
| NVDA | 0x379EC4f7C378F34a1B47E4F3cbeBCbAC3E8E9F15 |
Robinhood NVDA/USD, TRV, us_equities_24/5 |
| SPY | 0x319724394D3A0e3669269846abE664Cd621f9f6A |
Robinhood SPY/USD, TRV, us_equities_24/5 |
| QQQ | 0x80901d846d5D7B030F26B480776EE3b29374C2ae |
Robinhood QQQ/USD, TRV, us_equities_24/5 |
| PLTR | 0x820ABedFF239034956B7A9d2F0a331f9F075eB4c |
Robinhood PLTR/USD, TRV, us_equities_24/5 |
| AAPL | 0x6B22A786bAa607d76728168703a39Ea9C99f2cD0 |
Robinhood AAPL/USD, TRV, us_equities_24/5 |
| TSLA | 0x4A1166a659A55625345e9515b32adECea5547C38 |
Robinhood TSLA/USD, TRV, us_equities_24/5 |
| MSFT | 0x45C3C877C15E6BA2EBB19eA114Ea508d14C1Af2E |
Robinhood MSFT/USD, TRV, us_equities_24/5 |
| SLV | 0x209b73908e92Ae021826eD79609845451Ecba2ce |
Robinhood SLV/USD, TRV, us_equities_24/5 |
| GLD | 0x470A51258068043bd43dC0a56245625C9fE86eB0 |
Named GLD / USD, not Robinhood GLD / USD. Reads the GLD ETF price (about $381 at verification), not spot gold. May not report Total Return Value semantics; see note below. |
| WETH | 0x78F3556b67E17Df817D51Ef5a990cDaF09E8d3A9 |
Registered as ETH/USD; WETH ↔ ETH is 1:1 by contract. Document the alias in the launch record. ChainlinkOracleAdapter, not stock-guarded. |
| USDG | 0x61B7e5650328764B076A108EFF5fa7282a1B9aD2 |
USDG/USD, crypto category, always-open. ChainlinkOracleAdapter. |
| Sequencer uptime | not published on 4663 | Adapter deploys with the sequencer guard disabled; see above. |
GLD and SLV were briefly removed from the launch catalog while Chainlink had no GLD feed on
4663. They were restored (spec §3) once Chainlink published GLD / USD.
The GLD feed is named GLD / USD rather than Robinhood GLD / USD like every other stock leg.
That naming suggests it is a general ETF price feed and may not apply the Robinhood Total Return
Value adjustment (underlying × uiMultiplier()). Before launch, compare the feed answer with the
GLD token's uiMultiplier()-adjusted reference and the GLD/USDG Uniswap v3 price on 4663; if they
diverge, GLD/USDG and GLD/SLV will carry a persistent drift and must not go live until resolved.
The GLD leg still uses RobinhoodStockOracleAdapter, so oraclePaused() on the GLD token gates
corporate actions as for every other stock leg.
Cross-check result (2026-10-07, block 82785643, test_fork_gldFeedMatchesMarket): Chainlink
GLD / USD read $376.278; the deepest GLD/USDG Uniswap v3 pool on 4663
(0x7A6A053eCCf1446A2633E05aA6D40D09381997ec, 0.3% tier, about $390k USDG reserve) priced GLD at
$377.032. Difference: 19 bps, inside the 3% tolerance. The GLD token's uiMultiplier() is exactly
1e18, so today there is no Total Return Value adjustment for the feed to miss. Verdict: the
feed is usable for GLD/USDG and GLD/SLV. Re-run the check if GLD's uiMultiplier() ever moves
off 1e18 (a distribution or corporate action), because this feed would not follow it.
The hook caches the fair price that triggered the break (cachedFairPriceWad) and admits only
swaps that reduce drift against that cached value. The target does not silently follow a moving
oracle while the pool is contained. A configurable post-open stabilization interval, measured from
currentSessionStart(), keeps asymmetric operation off until the session has settled. Always-open
pools ignore that interval.
For each of the exact 18 pools, record and independently approve:
- token0/token1 and decimals;
- adapter addresses and underlying feed proxies;
- feed decimals, heartbeat, and maximum dual-leg timestamp skew;
- stock-token
oraclePaused()behavior where applicable; - sequencer feed and recovery grace period;
- market-hours source/calendar, or always-open designation;
- fair-price orientation and an expected-value test vector;
- fork-test block and result;
- multisig approval reference.
All feeds and adapters must be ready before any pool is marked live. No placeholder or invented address may enter the production manifest.