Your historical P&L is wrong.
Here’s the query that proves it.
Robinhood’s stock tokens keep raw ERC-20 balances static and express corporate actions through a multiplier. Any historical valuation that skips that term is arithmetically incorrect the moment an instrument splits or pays a dividend — and every provider we surveyed skips it.
- 3,960,000CASH_DIVIDEND $0.04/sh→ ×1.0042
- 4,310,000FORWARD_SPLIT 4:1→ ×4.0168
One term, skipped by everyone
This is not a matter of interpretation. It is a multiplication with three terms, and the middle one is time-dependent.
What was this position worth at block 4,200,000? The balance never moved — Robinhood’s stock tokens keep raw ERC-20 amounts static and express corporate actions through uiMultiplier(). Apply today’s multiplier to a past balance and the answer is wrong by construction.
balanceOf(NVDA, 4,200,000)
× uiMultiplier() ← today's, not the block's
× price_at(block)$9,210.00
Uses the current multiplier of 4.000000. The balance is right; the scaling factor is from the wrong point in time.
balanceOf(NVDA, 4,200,000)
× uiMultiplier_at(4,200,000)
× price_at(block)$2,302.50
Uses the multiplier that was actually in force at that block: 1.000000.
The balance is supposed to stay still
This is not a bug in Robinhood's design. It is a deliberate choice that generic EVM tooling was never built to handle.
Stock tokens are ERC-20 with 18 decimals, extended by ERC-8056, the Scaled UI Amount Extension. A dividend or split adjusts the shares-per-token ratio while leaving raw balances untouched until redemption.
That means a holder’s balanceOf() is unchanged across a 4-for-1 split. Nothing looks wrong. The scaling lives entirely in uiMultiplier(), and it is time-dependent.
Because dividends are reinvested through the multiplier rather than paid out, a stock token tracks the total return of its underlying — price change plus reinvested dividends. A provider reporting price return alone is misreporting performance even when their balance is right.
// Raw balance — static across corporate actionsfunction balanceOf(address) returns (uint256); // The scaling factor, 18 decimals. 1e18 == 1.0function uiMultiplier() returns (uint256); // Underlying-share equivalent, for displayfunction balanceOfUI(address) returns (uint256); // Pending change, readable before it takes effectfunction newUIMultiplier() returns (uint256);function effectiveAt() returns (uint256); // Fires on dividend or split, carrying old, new, timestampevent UIMultiplierUpdated( uint256 oldMultiplier, uint256 newMultiplier, uint256 effectiveAt);How we compute, and where we could be wrong
A correctness claim that hides its assumptions is a marketing claim. These are ours.
Robinhood REST prices are raw underlying
Multiply by currentMultiplier before comparing to anything onchain. Stated in Robinhood's own docs, and the most likely source of silent error.
Chainlink prices are already multiplier-adjusted
Never apply uiMultiplier() to a Chainlink price. Double-application is the mirror image of R1 and produces a number wrong by the square of the multiplier.
Historical valuation uses the multiplier at that block
Never the current one. This is the bug in every incumbent's historical balance API.
Stock tokens track total return, not price return
Dividends are reinvested through the multiplier. Reporting price return alone understates performance.
These are four of ten. The full set — including how we handle stale oracles, reorgs and session boundaries — is published at /docs/equities/correctness-rules, and is useful whether or not you buy from us. That is deliberate.
9 invariants, asserted against production
The suite is open source and runnable by anyone — against us, and against any competitor implementing the same endpoints. Results are published continuously.
npx @robinhoodrpc/invariants \ --endpoint https://api.robinhoodrpc.io/v1 \ --sample 500- 01balance_ui == balance_raw × multiplier_at(block), for every sampled account and block
- 02Historical queries never apply the current multiplier when as_of is in the past
- 03Session boundaries match the published US equities calendar, including half-days
- 04A corporate action's details variant always matches its declared type
- 05Every SEQUENCED record reaches INCLUDED, DROPPED or REORGED — none dangle
- 06premium_bps recomputes exactly from the component fields in the same record
- 07tape_sequence is strictly monotonic with no gaps
- 08Candles flagged crosses_session_boundary genuinely span a boundary
- 09Reprocessing any range at a given code_version is byte-identical
Check our numbers before you trust them.
The invariant suite is public, the snapshots are signed, and every response carries the multiplier it applied. Start with a free key.