How Rowan grants
Rowan gives tokenized stocks on Robinhood Chain to other people on a schedule. It is a web page that builds transactions for your wallet to sign. It deploys no contract, holds nothing and charges no fee. Every grant is a stream in Sablier Lockup v4.0, which was already on the chain: your shares move into Sablier's contract, the person you chose receives the stream's NFT, and Sablier releases the shares to whoever holds that NFT as the schedule allows.
Every number on the page comes from one module, js/grant.js, and the tests run that same file against the live chain. What is tested is what is signed.
The transactions
- Buy (only with “Buy with dollars”) — one
SwapRouter02.multicall:selfPermit(USDG, …)with an EIP-2612 permit you sign (USDG's domain is checked against its ownDOMAIN_SEPARATORfirst), thenexactOutputSingle(USDG → stock)for exactly the shares the grants need, into your own wallet, spending at most the quote plus 0.5%. The fee tier is the cheapest for that amount, and the price is held to Robinhood's feed: more than 3% worse than a fresh feed is refused. - Approve —
stock.approve(BatchLockup, total)for exactly the total being granted, only if the existing allowance is short. Never unlimited. - Grant — one call to Sablier's
BatchLockup.createWithTimestamps{LL|LT|LPG}(Lockup, stock, [one entry per person]). BatchLockup takes the total from you once and creates one Lockup stream per person, with you as its sender (the only address that can cancel it) and them as its recipient. The stream ids are read back from BatchLockup's ownCreateLockupBatchevent.
Every transaction is simulated from your own address before your wallet is asked to sign it, and the schedule is rebuilt from the chain's clock at the moment of the last step, so a grant that says “starts now” starts in the block that creates it.
The four kinds
Each kind is exactly one of Sablier's stream models. The page draws the curve with its own copy of Sablier's LockupMath, which the tests hold against streamedAmountOf on the live contract.
- Vest — Lockup Linear (LL). A straight line from start to end, by the second (granularity 1). A cliff holds everything until the cliff date, then releases, at that instant, exactly what the line from start to end has reached (
total × (cliff − start) ÷ (end − start), rounded down); the rest streams from the cliff to the end. - Monthly — Lockup Tranched (LT). n equal instalments on the same day of each month (clamped to the month's last day), the first a month after the start. The division remainder rides on the last instalment, so they add up to the total exactly.
- On a date — Lockup Tranched with one instalment: everything at once, on the day chosen (at 12:00 UTC).
- At a price — Lockup Price-Gated (LPG). See below.
A start date of today starts at once; a later date starts at 12:00 UTC on that day. Up to 12 people per grant, and up to 240 monthly instalments.
Price grants
A price-gated stream stores a price feed and a target (8 decimals). Rowan points it at Robinhood's own price feed for that stock — 26 of the 42 stocks have one on chain — and refuses a target less than 1% above what the feed reads now (Sablier itself refuses one at or below it).
Read this part carefully: in Sablier Lockup v4.0 the unlock is not remembered. streamedAmountOf is the whole deposit while the feed reads at or above the target, and zero again if it falls back below — unless it has been withdrawn. So when the target is reached, take it. Anyone can call withdrawMax(id, holder), and the shares always go to the holder, which is why the grant page offers everyone a “Deliver to them” button. On the fallback date it unlocks for good, whatever the price. While it is unlocked it cannot be cancelled.
Sablier reads the feed without a staleness check, so a grant can be made and taken while the market is shut; the feed simply holds its last price.
After the grant
- Withdraw.
withdrawMax(id, holder)sends everything unlocked to the NFT's current holder. Anyone may send it.withdrawMultipledoes several at once. Sablier's comptroller may charge a withdrawal fee in ETH; it is zero today, and the page readscalculateMinFeeWeiand sends exactly that. - Cancel. Only the giver, only if the grant was made cancellable, only before everything has unlocked. What has not unlocked returns to the giver in the same transaction; what has unlocked stays in the stream for the holder to withdraw.
- Make permanent.
renounce(id): the giver gives up the right to cancel, for good. - Pass it on. If the grant was made transferable, the NFT can be sent to another wallet; the unlocked and future shares follow it.
My grants finds every grant by asking Lockup itself for the sender and the current owner of every stream id — Sablier's creation events do not index the recipient, and a grant can change hands.
Shares and units
Robinhood's stock tokens are ERC20ScaledUI: the raw balance (18 decimals) never changes for a split or a dividend; a multiplier (uiMultiplier, 1e18 = 1.0) turns raw units into shares. A grant stores raw units, so it follows the stock through a split. Rowan converts shares to raw rounding up, so a grant of “10 shares” holds at least ten.
How it is tested
The property suite (tools/test.mjs) runs the page's own js/grant.js against live Robinhood Chain state with eth_simulateV1: the real Sablier Lockup and BatchLockup, the real Uniswap pools, the real Robinhood feeds. Test wallets are funded by state override and hold real keys, so every permit is a real signature. The clock is moved forward inside the simulation to watch grants unlock, and a mock feed is installed at a real feed's address to move a price. Nothing is broadcast. Expected values are computed in the test from balances and events — never by asking the module under test.
The last run: 12/12 properties and 466 checks passed against live state at block 75,991,703 (29 Sep 2026).
| Property | What the last run showed | Checks |
|---|---|---|
| vest | two grants of 2.0000 and 1.2345 NVDA: exact deposits, cliff at +12 months to the second, the line to the unit, delivered by a stranger | 39 |
| curve | 90 checks at 15 instants — including the second before and the second of each boundary — across six grants (two cliffs, a future start, twelve instalments, a date, a price): the page's copy of LockupMath agrees with Sablier to the unit | 97 |
| monthly | 7 instalments from a 31 January start: February clamps, each lands on its day, the remainder rides on the last, three unlock after three months | 20 |
| date | locked to the second, then all at once; a permanent grant is refused cancellation by Sablier and by the page | 7 |
| price | a mock feed at Robinhood's real NVDA address moved $200 → $260 → $240 → $250: nothing, all, nothing, all; delivered; a second grant unlocked on its fallback date | 13 |
| buy | $991.01 bought 3.0000 AAPL in the 0.05% pool with a real permit, and all of it was granted | 10 |
Show all 12 properties
| approve | approve(BatchLockup, exact total), consumed to zero by the grant | 4 |
| cancel | cancelled after 91 days: 75.07% back to the giver, the unlocked rest withdrawn by the holder; renounce is final | 9 |
| many | 12 grants in 9,681,602 gas; ownership, a hand-over and a grant back all found by asking Lockup directly | 10 |
| all | three grants emptied in one withdrawMultiple — SPY by the line and by instalment, USDG by instalment — the date grant untouched | 6 |
| units | balanceOfUI agrees on 12 stocks; 210 conversions round up to the smallest raw amount holding the shares | 235 |
| refuse | 16 refusals, all before a signature: contracts and tokens as recipients, too many people, impossible schedules, a mis-domained permit | 16 |
Then a sabotage sweep plants 32 bugs, one at a time, in a copy of js/grant.js — a cliff that releases too much, instalments that do not add up, a target read in the wrong units, a withdrawal sent to the wrong wallet, an unlimited approval — and requires the property named for each one to fail. 32/32 were caught.
The browser run: 8/8 journeys (37 checks) clicked through the real pages in Chrome with a test wallet, against a private copy of the live chain (29 Sep 2026).
Addresses
Every contract Rowan talks to was already on Robinhood Chain (chain id 4663), written and audited by its own authors. Before every deploy, tools/verify-sablier.mjs compares the code at each Sablier address with the runtime bytecode in Sablier's own published package (@sablier/lockup@4.0.0, libraries linked, immutables masked): 4 of 4 were identical byte for byte on 29 Sep 2026, when Lockup held 504 streams.
Stocks
Every Robinhood stock token Rowan can grant, deepest pool first. Each has a USDG pool on Uniswap v3 (so it can be bought with dollars); those with a feed can also be granted at a price. From a scan at block 75,808,765, 29 Sep 2026. Dollars (USDG) can be granted too.
| Stock | Price | Pool fee |
|---|---|---|
| NVDA | $229.88 | 0.05% |
| SPCX | $148.86 | 0.05% |
| USO | $145.53 | 0.30% |
| CRCL | $84.28 | 0.30% |
| MU | $1068.49 | 0.30% |
| QQQ | $736.40 | 0.05% |
| GOOGL | $339.09 | 0.05% |
| COST | $920.11 | 0.30% |
Show all 42 stocks
| MSFT | $509.24 | 0.30% |
| AMC | $3.04 | 0.30% |
| MSTR | $153.99 | 1.00% |
| HIMS | $28.78 | 0.30% |
| AMZN | $246.38 | 0.30% |
| GLD | $380.82 | 0.05% |
| DJT | $9.20 | 1.00% |
| TSLA | $353.66 | 0.30% |
| SPY | $763.22 | 0.05% |
| LLY | $1177.53 | 0.05% |
| SGOV | $100.62 | 0.05% |
| DELL | $544.66 | 1.00% |
| AVGO | $357.88 | 0.30% |
| AMD | $613.67 | 0.30% |
| AAPL | $331.19 | 0.05% |
| INTC | $116.31 | 0.30% |
| META | $719.81 | 0.30% |
| RDDT | $143.98 | 1.00% |
| TSM | $453.28 | 1.00% |
| NET | $352.05 | 0.30% |
| QUBT | $8.60 | 0.30% |
| MRNA | $199.12 | 0.30% |
| PLTR | $185.89 | 0.30% |
| GME | $23.37 | 0.05% |
| SLV | $55.04 | 0.30% |
| NFLX | $70.60 | 0.30% |
| SNDK | $1720.13 | 1.00% |
| ASML | $1819.74 | 1.00% |
| TTWO | $202.43 | 0.30% |
| RBLX | $41.75 | 0.30% |
| JNJ | $266.60 | 0.30% |
| USAR | $14.08 | 0.30% |
| RIVN | $15.07 | 0.30% |
| NU | $12.41 | 0.30% |
Risks
- A grant that is not cancellable cannot be taken back, by anyone. A grant to the wrong address can only be undone by cancelling it (if you kept that right). The page draws an avatar from each address to make a typo easier to spot, and refuses tokens and contracts as recipients.
- A price grant unlocks only while the price is at or above the target; if nobody takes it and the price falls back, it waits again. See Price grants.
- Robinhood can pause a stock token or block an address. While a token is paused, nothing can be withdrawn from a grant of it.
- Rowan is not audited. It deploys nothing, and Sablier's contracts are audited, but the page that builds your transactions is new code. Read how it is tested.