Every historical balance API on Robinhood Chain is wrong
Robinhood 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 — and every provider we surveyed skips it. Here is the arithmetic, and the query that demonstrates it.
RobinhoodRPC Research
Engineering & market structure
There is a moment in every tokenized-equity integration where a wallet's historical value disagrees with a broker statement by a factor of exactly four, and an engineer spends an afternoon discovering that nothing is broken: the data was simply valued with the wrong multiplier. This post is the long-form version of that afternoon.
The mechanism
Robinhood stock tokens are ERC-20 with 18 decimals, extended by ERC-8056 — the Scaled UI Amount Extension. Corporate actions do not mint or burn. They adjust a shares-per-token multiplier and leave raw balances untouched until redemption.
That is a deliberate design choice, and a good one: a holder's balance does not churn every time a dividend is paid, and the token contract does not need to iterate holders. The consequence is that the meaning of a balance is time-dependent while the balance itself is not.
function balanceOf(address) returns (uint256); // raw — static across actionsfunction uiMultiplier() returns (uint256); // 18 decimals, 1e18 == 1.0function balanceOfUI(address) returns (uint256); // raw * multiplier event UIMultiplierUpdated( uint256 oldMultiplier, uint256 newMultiplier, uint256 effectiveAt);The arithmetic
Valuing a position at block N takes three terms. The middle one is the one that moves.
value_at(N) = balanceOf(addr, N) × uiMultiplier_at(N) × price_at(N)Every provider we surveyed returns balanceOf(addr, N) from its historical balance endpoint and stops there. Multiplying that by a price gives a number that is confidently, invisibly wrong for any instrument that has since split or paid a dividend.
A worked example
Take a position of 12.5 raw tokens at a block before a 4-for-1 split, with the underlying at $184.20. The multiplier at that block was 1.0; today it is 4.0.
The error does not decay. It compounds with each subsequent action, and because raw balances look stable, nothing in the response signals that anything is wrong.
It also breaks performance
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 — not the share price. A provider computing performance from price alone understates every dividend-paying instrument.
This is a smaller error than a split, but a more insidious one: it never produces an obviously absurd number, so it survives review.
Checking your own provider
This is testable in one request against anyone. Ask for a wallet's position value at a block preceding a corporate action, then check which multiplier came back.
curl -s https://api.robinhoodrpc.io/v1/accounts/0xabc.../positions \ -H "Authorization: Bearer $RHRPC_KEY" \ -G --data-urlencode "as_of_block=4200000" \ | jq '.positions[] | { token_symbol, balance_raw, balance_ui, multiplier_applied, multiplier_effective_block }'If multiplier_effective_block is close to the current head rather than to the block you asked about, the answer is wrong. Most providers do not return the field at all, which is itself the answer.
What correct looks like
- Store the multiplier series indexed by block, immutably. A historical multiplier is never rewritten; a correction supersedes rather than replaces.
- Require an as_of parameter on historical queries. Return 400 rather than quietly answering with today's multiplier.
- Return balance_raw, balance_ui, multiplier_applied and multiplier_effective_block together, always, so a consumer can check the arithmetic.
- Report total return and price return separately, with the dividend contribution broken out.
- Carry cost basis through corporate actions rather than resetting at them.
None of that is difficult. It is simply work that generic EVM infrastructure has no reason to do, on a chain where it is the only thing that makes the numbers mean anything.