Oracle health
developerThe eight health states, staleness rules, and the uiMultiplier trap.
Address identity
Pricing starts where the asset registry starts: the token address is the identity. A feed is only ever attached to a verified canonical address — never looked up by ticker, because tickers can collide and the docs ticker can even differ from the on-chain symbol (see CUSO/USO).
The eight health states
Every price evaluation resolves to exactly one state. Order matters: pause and sequencer problems mask price data entirely, because a "fresh" price during an outage is still untrustworthy.
| State | Meaning | Priced? |
|---|---|---|
| OK | Fresh, positive price from a configured feed; sequencer healthy. | yes |
| STALE | The feed's last update is older than the staleness limit (default 90,000s). | no |
| INVALID_PRICE | The feed answered zero/negative, or the round is incomplete (updatedAt ≤ 0). | no |
| MISSING_FEED | No Chainlink feed proxy exists for this asset — today that means BE (Bloom Energy) only. | no |
| SEQUENCER_DOWN | The L2 sequencer uptime feed reports down; feed values may be frozen. | no |
| GRACE_PERIOD | The sequencer just recovered; prices stay untrusted for a grace window (default 3600s). | no |
| ORACLE_PAUSED | The oracle reports paused — often a corporate action. Price data is masked entirely. | no |
| UNKNOWN | A feed is configured but no reading was supplied to the evaluator. | no |
Staleness: the primary guard
Every Robinhood Chain USD feed publishes on an 86,400-second (24-hour) heartbeat with a 0.5% deviation threshold, so the oracle policy's staleness limit is 90,000 seconds — heartbeat plus a one-hour buffer. A reading is stale when now − updatedAt exceeds that limit (manifests can tighten it via risk.oracleStalenessSeconds). Stock feeds discover prices 24/5, but the heartbeat keeps writing on weekends (verified on a Sunday: observed feed ages stayed at or under 16.7 hours, carrying Friday-close-derived values) — so one threshold covers all days, with no special weekend allowance.
Staleness is the primary guard by policy: the official docs describe the oraclePaused() flag as advisory and not enforced on-chain, so a paused oracle may still return a value. HISS checks the pause flag anyway (strictly more conservative), but never relies on it alone.
Pinned feeds (26 of 27)
Feed proxies were pinned on 2026-07-06 from Chainlink's reference data directory for robinhood-mainnet — the source of truth the official docs point to — and live-verified via eth_call on chain 4663 (spot-checked proxies answered with matching description(), decimals() = 8, positive answers, and fresh timestamps). All pinned feeds: 8 decimals, 86,400s heartbeat, 0.5% deviation, 24/5 market hours. This table renders live from PINNED_FEEDS in @hiss/core:
| Ticker | Feed proxy (chain 4663) | Decimals | Heartbeat |
|---|---|---|---|
| WETH | 0x78F3556b67E17Df817D51Ef5a990cDaF09E8d3A9 | 8 | 86,400s |
| USDG | 0x61B7e5650328764B076A108EFF5fa7282a1B9aD2 | 8 | 86,400s |
| AAPL | 0x6B22A786bAa607d76728168703a39Ea9C99f2cD0 | 8 | 86,400s |
| AMD | 0x943A29E7ae51A4798823ca9eEd2ed533B2A22C72 | 8 | 86,400s |
| AMZN | 0xD5a1508ceD74c084eBf3cBe853e2C968fB2a651C | 8 | 86,400s |
| BABA | 0x62Cc8F9b5f56a33c9C8A60c8B92779f523c4E984 | 8 | 86,400s |
| COIN | 0xA3a468A452940B7D6b69991207B508c609a98Ef2 | 8 | 86,400s |
| CRCL | 0x6652eDf64bA3731C4F2D3ce821A0Fb1f1f6b482a | 8 | 86,400s |
| CRWV | 0xe1b3aABCAFAd1c94708dc1367dcfF8Aa4407487C | 8 | 86,400s |
| GOOGL | 0xF6f373a037c30F0e5010d854385cA89185AE638b | 8 | 86,400s |
| INTC | 0x3f390C5C24628Ac7C489515402235FeAD71D1913 | 8 | 86,400s |
| META | 0x7C38C00C30BEe9378381E7B6135d7283356D71b1 | 8 | 86,400s |
| MSFT | 0x45C3C877C15E6BA2EBB19eA114Ea508d14C1Af2E | 8 | 86,400s |
| MU | 0x425EEFdCf05ed6526C3cE61Af99429A228a6d596 | 8 | 86,400s |
| NVDA | 0x379EC4f7C378F34a1B47E4F3cbeBCbAC3E8E9F15 | 8 | 86,400s |
| ORCL | 0x0e6a64a2B58A6693a531E6c555f3A5d042eEA844 | 8 | 86,400s |
| PLTR | 0x820ABedFF239034956B7A9d2F0a331f9F075eB4c | 8 | 86,400s |
| SNDK | 0xfb133Fa4B7b385802B693a293606682Df47109A3 | 8 | 86,400s |
| SPCX | 0xB265810950ba6c5C0Ff821c9963014a56fD8Bffb | 8 | 86,400s |
| TSLA | 0x4A1166a659A55625345e9515b32adECea5547C38 | 8 | 86,400s |
| USAR | 0xA994d3684e8400A6c8078226925779FdeE682DD9 | 8 | 86,400s |
| QQQ | 0x80901d846d5D7B030F26B480776EE3b29374C2ae | 8 | 86,400s |
| SGOV | 0xa0DF4ee0fFf975306345875E3548Fcc519577A11 | 8 | 86,400s |
| SLV | 0x209b73908e92Ae021826eD79609845451Ecba2ce | 8 | 86,400s |
| SPY | 0x319724394D3A0e3669269846abE664Cd621f9f6A | 8 | 86,400s |
| CUSO | 0x75a9c76Ef439e2C7c2E5a34Ab105EcFe3766431c | 8 | 86,400s |
Total-return drift
Stock Token feeds carry total-return semantics: dividends are reinvested into the token via the multiplier, so as dividends accrue the feed price drifts above the headline share price. Per the official docs, this is expected — the token tracks price changes plus reinvested dividends, not just the share price. Never treat that drift as an error, and never present the feed price as "the share price" (divide by the multiplier for that display).
uiMultiplier: never double-apply
Robinhood Chain Stock Tokens expose a uiMultiplier() that reflects corporate actions (splits, reinvested dividends). The critical rule: the Chainlink feed price is the token price and already includes the multiplier. The multiplier exists only to display share-equivalent units. Applying it to the feed price double-counts corporate actions.
// USD value of a raw token amount — the ONLY valuation formula:
value = rawTokenAmount * feedPrice;
// Share-equivalent units, for DISPLAY only:
shareEquivalent = rawTokenAmount * uiMultiplier;
// WRONG — double-counts corporate actions:
value = rawTokenAmount * uiMultiplier * feedPrice; // never do thisCorporate actions can change uiMultiplier() at any time, so never cache share-equivalent math.
Why live pricing is still off
Pinned feeds are necessary but not sufficient. The live-mode gate also requires the Chainlink L2 sequencer uptime feed for chain 4663, which is not yet published (no entry in the reference data directory, and Robinhood is absent from Chainlink's L2 sequencer feed list). Until it appears, liveFeedsPinned() returns false and assertLivePricingAllowed() throws on every live-pricing code path — live pricing is structurally impossible to enable by flag alone, and all pricing comes from the deterministic mock oracle.