Protocol documentation

Docs

What the protocol does, what the contracts guarantee, and what is actually deployed. Written to be checkable against the code, not to sell anything.

Status. The factory and its vaults are live on Robinhood Chain mainnet, and the app lists them off the registry plus one vault deployed outside it. One vault is retired and takes no deposits. Only the routed USDG vault currently earns; the rest have no yield source wired up yet, so their share price has not moved. The contracts pass a full test suite but have not been through a third-party audit. Private routing is designed and not yet shipped; vaults came first.

YieldShares

A vault holds one asset. You deposit that asset and the vault mints you a share token. The share token is an ordinary ERC-20: it has a symbol, it shows in your wallet, and it transfers without the protocol's permission.

The share price is:

pricePerShare = (totalAssets + 1) / (totalSupply + 1)

The + 1 on each side is a virtual asset and a virtual share. It exists so that the very first deposit cannot be used to manipulate the price for the second one. Without it, an attacker deposits 1 wei, donates a large amount to the vault, and the next depositor's shares round down to zero.

Yield

Income is pushed into the vault by its harvester. The vault takes the protocol's cut, adds the rest to totalAssets, and mints no new shares. The supply is unchanged and the assets grew, so every share is now worth more.

What is deployed today: one vault, the routed USDG one, forwards deposits into Steakhouse USDG on Morpho Blue and earns there. Every other vault has no harvester set, so nothing is pushed in and its share price stays at exactly 1. A vault with no yield source is a share token and nothing more, which is why the app shows the share price rather than an advertised rate.

There is nothing to claim. If you transfer the share, the accrued yield transfers with it, because it lives in the redemption price rather than in a per-user reward balance.

Redemption

Burn shares, receive the underlying at the current price. No lockup, no queue, no epoch. Rounding on withdrawals and mints always goes in the vault's favour, by at most a few wei, so a sequence of deposits and redemptions cannot drain other holders.

What the contract guarantees

PropertyHow it is enforced
A donation cannot move the share pricetotalAssets is a storage variable, not balanceOf(this). Raw transfers into the vault are invisible to pricing.
The first depositor cannot round out the secondVirtual asset and virtual share in every conversion.
The protocol fee has a ceilingMAX_FEE_BPS is a constant checked in the constructor and in setFee. The owner cannot exceed it.
The owner cannot take shareholders' assetsskim can only move the balance above totalAssets. The backing is out of reach.
Only the harvester can add yieldharvest checks msg.sender == harvester and pulls the tokens from the caller.
Fee-on-transfer assets are rejectedDeposits verify the received amount matches the requested amount, and revert otherwise.

What it does not guarantee

Private execution

The intended design: you sign an intent describing the outcome you want rather than a swap call. Solvers compete to fill it and one of them submits the transaction. Your order is never a pending transaction anyone can read and sandwich, and because the solver pays gas, the fee can be settled in the traded asset.

Vaults shipped first. Routing is designed and not yet live, and the app does not offer it.

Contracts

ContractSOON
Token$SELVO SOON

Parameters

ParameterValue
Protocol cut — factory default for a new vault
Protocol cut — routed USDG vault, the default deposit targetread from the vault
Hard cap on that cut
Pool fee tiers the deposit route tries
Vaults in the registryread from the factory
Chain
Withdrawal delaynone

The first two rows differ on purpose: the cut is stored per vault, so the factory default is not automatically what a given vault charges. The figures above are read from the contracts when this page loads; if a read fails they fall back to the values in config.js, which is what the app is configured with.

Running it yourself

Tests:

forge test

Deploy the factory, then create a vault per asset:

forge create contracts/VaultFactory.sol:VaultFactory \
  --rpc-url $RPC --private-key $KEY \
  --constructor-args 1000 $TREASURY

cast send $FACTORY "createVault(address,string,string)" \
  $ASSET "YieldShares NVDA" "ys-NVDA" \
  --rpc-url $RPC --private-key $KEY

That command produces a factory vault, which is where the 10% default comes from. The routed USDG vault is a different contract and is not created this way.

config.js holds the addresses the front end cannot derive: the factory, the routed vault, the zap and its Uniswap v3 factory, the USDG stablecoin and the tokenized-stock tokens. The vault list itself is not among them — it is read from the registry at the factory address.

Front end

Static HTML, CSS and vanilla JavaScript. config.js is the source of truth for the brand, the chain and the addresses. There is one serverless function, /api/rpc, which proxies JSON-RPC reads to Robinhood Chain: the public node intermittently returns a malformed CORS header that browsers reject, so reads go through the proxy and fall back to the public node directly if it is unreachable. Wallets still talk to the public RPC themselves.

Nothing on these pages invents a number. Where a figure cannot be read, it is left blank or the page says the read failed.

Read it, then try it

Deposit into a vault and hold a share token that keeps earning.

Open the app