Robinwood StocksDocs
Protocol

Architecture

One immutable contract per basket; prices touch the chain only where they must, and never the exits.

Robinwood Stocks's core is intentionally small: one non-upgradeable basket contract per basket (built on OpenZeppelin v5), plus a stateless zap router beside it and a display-only frontend above it. Two product lines share that core:

  • Frozen baskets (BasketToken): the recipe never changes and no oracle exists on-chain at all.
  • Agentic baskets (BasketToken2): the same vault plus exactly one power — an agent may rebalance inside immutable ceilings — which requires pricing, added in the narrowest shape that works and only on the rebalance path.
                     ┌─────────────────────────────┐
   Chainlink feeds ──┤  scripts/computeUnits.ts    │   deploy-time only
   (off-chain read)  │  equal weights → raw units  │
                     └──────────────┬──────────────┘
                                    │ baskets/*.json

                     ┌─────────────────────────────┐
        user ──────► │  BasketToken (per basket)   │ ◄────── guardian (Safe)
   mint / redeem     │  immutable, no oracles      │   pause-mint / cap /
   (in-kind, ERC-20) │  holds constituents 1:1     │   fee recipient ONLY
                     └──────────────┬──────────────┘
                                    │ reads (units, supply, events)

                     ┌─────────────────────────────┐
   Chainlink feeds ──┤  frontend (Next.js, edge)   │   display-time only
   (client read)     │  NAV, drift, badges, geoblk │
                     └─────────────────────────────┘

The agentic line adds one path to that picture — and only one:

                     ┌─────────────────────────────┐
   AssetRegistry ────┤  BasketToken2 (per basket)  │ ◄────── agent key
   (frozen feeds:    │  same vault + rebalance()   │   rebalance ONLY,
   Chainlink / TWAP) │  policy ceilings immutable  │   inside the policy
                     └──────┬───────────────┬──────┘
                            │ trades via    │ surplus USDG
                            ▼               ▼
                     MakerRegistry →   BasketDistributor
                     whitelisted       push payouts to
                     settlement venues holders on a fixed cycle

Mint and redeem on a BasketToken2 are byte-for-byte the same in-kind, price-free operations as on the frozen line. The oracle layer (details) exists solely to check the agent's trades against the policy.

Design invariants

  1. Full backing. For every constituent i: balanceOf(vault) ≥ ceil(totalSupply × units[i] / 1e18) after any call sequence. Enforced by ceil-on-mint, balance-delta checks and floor-on-redeem; proven by the invariant test suite. Details: Full backing & rounding.
  2. Redeem is ungated. No state (pause, cap, guardian action) can make redeem revert for a solvent holder. The only external failure mode is a constituent token itself reverting transfers (issuer freeze).
  3. No oracle can ever touch an exit. On frozen baskets, prices exist only (a) off-chain at deploy to pick units, (b) in the frontend for display with staleness rules. On agentic baskets, prices additionally gate the agent's rebalance — frozen feeds and TWAPs, fail-safe by construction — but never mint or redeem. A manipulated price can't mint underbacked tokens on any basket, because prices never enter the mint path.
  4. Raw amounts only. The ERC-8056 uiMultiplier is never read on-chain, so corporate actions cannot desync the vault (why).

What is deliberately absent

  • No proxy, no upgradeability, no selfdestruct, no delegatecall. The only external calls are ERC-20 transfers of the constituents.
  • No rebalancing on the frozen line. Rebalancing needs trading authority over vault funds and an oracle in the core; the frozen BasketToken refuses both, and its weights drift like an unrebalanced ETF. The agentic line grants exactly that authority — to one key, bounded by immutable ceilings, with backing re-proven after every trade. The two lines are separate contracts; choosing a frozen basket still buys you the zero-oracle, zero-trading-authority core.
  • No admin over funds. The guardian's complete power is: pause minting, move the supply cap under an immutable ceiling, change the fee recipient. See Guardian & governance.
  • No rescue paths. If a basket were misconfigured, the remedy is pausing its mints and deploying a corrected one, never mutating the old, whose redemption keeps working forever.

Units: computed once, immutable forever

scripts/computeUnits.ts reads live Chainlink prices during US market hours (it refuses to run on stale feeds), converts equal USD weights into raw constituent wei per 1e18 basket wei targeting ≈$100 per basket token, and writes the deploy config. From that moment on, the units are constructor arguments to an immutable contract; nothing on-chain can ever change the recipe.

The chain underneath

Robinhood Chain is an Arbitrum Orbit/Nitro L2: standard EVM, ~100ms blocks with preconfirmations, a single Robinhood-operated sequencer, settlement on Ethereum. Finality is soft on sequencer confirmation and hard on Ethereum. Stock Tokens are ERC-20s with 18 decimals, freely transferable by contracts; "indices & baskets" is an explicitly invited use case in the official docs.

Fake tokens with identical tickers exist on the chain. Robinwood Stocks's tooling re-verifies every address against the official docs page and on-chain symbol()/decimals() before any deploy. Do the same in your own integrations, and take addresses only from Reference → Addresses.

On this page