Architecture
One immutable contract per basket, prices kept strictly off-chain.
Vimen's core is intentionally small: one non-upgradeable BasketToken
contract per basket (~160 nSLOC of custom logic on OpenZeppelin v5), plus a
stateless zap router beside it and a display-only frontend above it.
┌─────────────────────────────┐
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 │
└─────────────────────────────┘Design invariants
- 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. - Redeem is ungated. No state (pause, cap, guardian action) can make
redeemrevert for a solvent holder. The only external failure mode is a constituent token itself reverting transfers (issuer freeze). - Zero oracle dependence on-chain. Prices exist only (a) off-chain at deploy to pick units, (b) in the frontend for display with staleness rules. A manipulated price can't mint underbacked tokens because prices never enter the mint path.
- Raw amounts only. The ERC-8056
uiMultiplieris never read on-chain, so corporate actions cannot desync the vault (why).
What is deliberately absent
- No proxy, no upgradeability, no
selfdestruct, nodelegatecall. The only external calls are ERC-20 transfers of the constituents. - No rebalancing. Rebalancing needs trading authority over vault funds or an oracle in the core; Vimen refuses both. Weights drift like an unrebalanced ETF.
- 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. Vimen'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.