SEMI: a token floored on tokenized Micron stock
SEMI is an ERC-20 on Robinhood Chain that trades in a single Uniswap v4 pool against MU, Robinhood's tokenized Micron Technology stock. The whole genesis supply and an MU seed were placed in that pool as full-range liquidity that no contract can remove. A hook takes 5% of every swap; part of it is added back to the locked position, so the MU under the curve grows with volume. Supply is elastic but gated: new SEMI is minted only by a Ratchet, only when MU backing per token has made a new high over a full six-hour window, and only up to 2% of supply per window; and by a Bond that pays for every token it mints with MU added to the locked position at a rate at or above that high.
This paper describes every contract, constant and value flow of the protocol and of the three systems built on it (a token launchpad, a stock-index game called The Fab, and the SemiScape game), reconciled against the deployed contracts and their event history. Where code comments, site copy and chain state disagree, chain state is reported.
1System overview
The core protocol is ten contracts, two of which are alternative site routers, plus a read-only lens. None has an owner. Every privileged setter was a one-shot pointer that has already been called; the September 2026 review confirmed each one now reverts.
| Contract | Role | Lines | Mutable configuration |
|---|---|---|---|
| SemiToken | ERC-20, 18 decimals; one gated mint path | 95 | minter set once → SemiMinter |
| SemiMinter | The only address the token accepts mint from; forwards for two callers | 54 | ratchet, bond, each set once |
| SemiLauncher | Opened the pool and owns the seed position | 176 | none; launched = true |
| SemiHook | v4 hook: 5% fee, launch window, harvest, floor donations, backing oracle | 390 | launcher, ratchet, burner, each set once |
| SemiRatchet | Backing-gated emissions, one poke per 6 h | 153 | none |
| SemiStaker | Receives emissions; pending/active stake accounting | 148 | ratchet set once |
| SemiBond | MU in → locked LP; discounted SEMI out, vested 24 h | 169 | none |
| FloorWall | Single-sided MU limit bids in the same pool | 383 | none |
| SemiSwapFeeV2 · V4 | Site routers with a sell-tier fee taken in MU | 300 · 281 | none |
| SemiLens | One-call proof-of-reserves view | 65 | none |
The live state used throughout this paper:
2The token
SemiToken is a minimal ERC-20 (name semivault.xyz, symbol SEMI). It has no transfer hooks, no tax, no pause, no blacklist and no owner. The constructor minted 10,000,000 SEMI to the deployer. The only other mint path is:
function mint(address to, uint256 amount) external {
require(msg.sender == minter, "only ratchet");
totalSupply += amount; balanceOf[to] += amount;
}
minter was set once to SemiMinter, which accepts calls only from SemiRatchet and SemiBond. There is no burn function: tokens sent to 0x…dEaD leave circulation but stay in totalSupply, and backing is always computed against totalSupply. transferFrom treats an allowance of type(uint256).max as unlimited. A transfer to address(0) is accepted and also stays in supply.
Minted and Bonded events. Bonding drove almost all growth, concentrated between 1 and 5 September.3Launch and locked liquidity
SemiLauncher.launch(muSeed) ran once, on 13 August 2026 at 23:07:47 UTC, and did five things in one transaction:
- pulled the entire 10,000,000 SEMI (
POOL_SUPPLY) and 2 MU from the deployer; - initialized the pool
SEMI/MU, lpFee 0, tickSpacing 60, hooks = SemiHookat the seed ratio,sqrtPriceX96 = √(amount1 / amount0) · 2⁹⁶; - added everything as one full-range position (ticks −887,220 to 887,220, salt 0) owned by the launcher, after shaving
liq / 1,000,000 + 1so v4's round-up on adds can never ask for more than it holds; - called
SemiRatchet.poke(), which set the first mark at the seed backing, 2 MU / 10M SEMI = 0.2 µMU per SEMI; - emitted
FloorPoured.
The launcher has no function that can call modifyLiquidity with a negative delta, and its receive() reverts. The hook's own compounded position has the same property. The only way MU leaves the pool is a trader selling SEMI into it.
| Position (full range, salt 0) | Liquidity L | Share |
|---|---|---|
| SemiLauncher (seed) | 4.472 × 10²¹ | 3.4% |
SemiHook (harvest compounding and donateFloor) | 1.282 × 10²³ | 96.6% |
| Pool in-range liquidity | 1.327 × 10²³ | 100% |
Pool id 0xe35635d9…f04e121. Current tick −121,169; price 5.470 µMU per SEMI.
4SemiHook: the fee
The hook's address carries permission flags 0x3088: beforeInitialize, afterInitialize, beforeSwap and beforeSwapReturnsDelta. beforeInitialize reverts unless the initializer is the launcher, so no other pool can be opened on this hook. afterInitialize stores startTime.
The skim
On every swap not made by the hook itself, beforeSwap computes the fee on the absolute specified amount and takes it in the specified currency:
Currency feeCur = (params.amountSpecified < 0) == params.zeroForOne ? key.currency0 : key.currency1; uint256 fee = (absAmt * feeBps) / 10_000; poolManager.take(feeCur, address(this), fee); return (beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);
Returning the fee as the specified delta makes the swapper owe it, so the hook's take nets to zero in the PoolManager. For exact-input swaps (all site routers) the fee is on the input: SEMI on sells, MU on buys. For exact-output swaps it is on the output currency. The hook accrues fees in both tokens.
The launch window
For 60 s after startTime the rate decays quadratically from 50% to 5%, front-loaded, and while it is above 5% no single swap may specify more than 10% of the protocol reserve of the specified currency ("launch window: slice it smaller"). The window ended on 13 August; currentFeeBps() has returned 500 since.
Harvest
harvest() is permissionless. Inside one unlock it sells the hook's whole SEMI balance for MU, then splits the hook's MU:
sender == address(this)) and the oracle.| Slice | Constant | Of fees | Of each swap | Destination |
|---|---|---|---|---|
| Compound | COMPOUND_BPS = 4000 | 40% | 2% | Hook's locked full-range position |
| Burn slice | BURN_BPS = 2000 | 20% | 1% | burner = 0x6388…dDce |
| Treasury | remainder | 40% | 2% | TREASURY = 0x6388…dDce |
burner pointer was set to the operations wallet, the same address as TREASURY. Each swap therefore sends 2% to the locked floor and 3% to operations. The v1 site states this; the token's immutable contractURI ("5% swap fee compounds Micron into the floor") does not._compound(muAmt) sells half of the MU for SEMI and adds both halves as full-range liquidity at salt 0, owned by the hook. Each internal swap is bounded to a √price move of 0.7071 or 1.4142, which is a price move of ½× or 2×; an oversized accrual partially fills and the remainder waits for the next harvest. _addFullRange applies the same epsilon shave as the launcher.
donateFloor
donateFloor(muAmt) is permissionless. It pulls MU from the caller and runs only the compounding step on 100% of it: no treasury cut. SemiBond, the V2 router's sweep and the launchpad's SemiSink all deliver MU through this function.
CellsRefreshed events (1,540 events). Donations total 1,265.65 MU, of which bonds are 1,256.16 MU; harvest compounding totals 135.07 MU. Over the same harvests 138.14 MU went to TREASURY, and the burn slice went to the same wallet.The MU added to the floor (1,400.7 MU) exceeds the MU in the pool today (310.3 MU). The difference left the pool through sells: adding MU raises the price and the backing, but the position is a constant-product curve, and each SEMI sold into it takes MU out at the current price.
5SemiHook: the backing oracle
Reserves from protocol liquidity only
reserves() reads the liquidity of exactly two positions with getPositionInfo (launcher and hook, full range, salt 0) and converts L to token amounts at the current price:
MU sent to the hook or pool directly, third-party LP and FloorWall bands are all invisible to it. A third party cannot park liquidity to force a mint and withdraw it afterwards.
The minimum-over-window filter
On every external swap, before the swap executes, and after every harvest or donation, the hook records epochMin = min(epochMin, muReserve()). consumeMin(), callable only by the Ratchet, returns that minimum and resets it to type(uint256).max; it does not seed the new window with the current spot reserve.
Because the observation happens before the swap, a window's last swap is reflected in the next window. A buy-then-poke in one transaction cannot raise the mark: the first observation of every window is the pre-swap reserve, so the attacker's starting point is always inside the minimum.
6Router fee layer
Uniswap's UniversalRouter on Robinhood Chain is a fork whose v4 swap struct carries an extra field, so the site routes through its own contracts. The original SemiSwap takes no fee. The two live site routers add a fee taken in MU on top of the hook:
| Constant | SemiSwapFeeV2 | SemiSwapFeeV4 |
|---|---|---|
BUY_FEE_BPS (MU input, before the swap) | 50 | 50 |
SELL_FEE_BPS (MU output, after the swap) | 100 | 1,000 |
WHALE_FEE_BPS (whole trade, at or above threshold) | 500 | 3,000 |
WHALE_THRESHOLD / WINDOW | 100,000 SEMI / 24 h | 100,000 SEMI / 24 h |
FLOOR_SHARE_BPS | 5,000 | 0 |
| Used by | v1 site | v2 site |
The sell tier is decided before the swap from a per-msg.sender rolling window: sold resets when 24 h have passed since startedAt, and a sale is charged the whale rate on its whole amount if sold + amount ≥ 100,000. A wallet can stay one SEMI under the threshold once per window. Splitting across wallets or trading directly against the pool avoids the router fee entirely. Fees accumulate in feesPending; sweep() is permissionless. V2 sends half to SemiHook.donateFloor and half to the treasury; V4 sends everything to the treasury.
| Router totals | V2 | V4 |
|---|---|---|
| To the floor (lifetime) | 2.6173 MU | 0 |
| To treasury (lifetime) | 2.6173 MU | 0.2463 MU |
| Pending, unswept | 0.0351 MU | 0.1640 MU |
7SemiRatchet: emissions
Backing ratio and the mark, both in MU-wei per whole SEMI (the site shows them in µMU):
poke() is permissionless, has no caller reward, and reverts if less than EPOCH = 6 hours has passed since the last one. With an empty pool it returns without touching state, so a poke before launch could neither brick the launch nor set a zero mark.
poke(). Every path that reads the oracle also calls staker.notify, which advances the staker's epoch.Why the mark cannot overshoot
With gain g = fbr / mark − 1 and α = ¾ below the cap, the new mark is mark · (1 + g) / (1 + ¾g). That is strictly above the old mark for any g > 0, and strictly below the observed backing. Since the observation is a minimum over the window, the backing at the time of the mint is at least the observation, so the backing per token after the mint is at least the new mark. When there are no stakers, the full gain moves the mark and nothing is minted, so the first staker cannot collect a backlog.
History
The Ratchet has been poked 147 times: 1 initialization, 27 mints, 119 epochs with no new high. It has minted 12,143,846 SEMI in total, largest single mint 1,159,613 SEMI. The last mint was on 3 September 2026 at 12:50 UTC and set the current mark of 10.789 µMU.
mark × S = 10.789 × 10⁻⁶ × 81,267,162 = 876.8 MU. The locked position holds 310.3 MU, a factor of 2.83. Until then each poke emits NoNewHigh and only advances the staker's epoch.8SemiStaker
A MasterChef-style accumulator without rebasing. The Ratchet mints ΔS to the staker and calls notify(ΔS):
New stake is pending and earns nothing until the next notify has passed: _promote moves it to active when epochCount > pendingEpoch. Staking one block before a poke and leaving after it earns nothing. unstake draws only from active stake and requires now ≥ unlockAt, where every stake() resets unlockAt = now + 6 h for the whole position. Principal and rewards are accounted separately, so claim() can never pay out principal.
9SemiBond
bond(muIn, minSemiOut) pulls MU, routes 100% of it through SemiHook.donateFloor and mints SEMI through SemiMinter:
fbrCap is the largest amount whose marginal backing muIn / semiOut is still at or above the mark, so a bond cannot lower backing per token below the mark. Reserves are the protocol-owned ones, so the quote cannot be inflated by donations or third-party LP. The minted SEMI vests linearly over VEST = 24 h. A new bond pays out whatever has vested, then puts the unvested remainder and the new amount on a fresh 24-hour schedule; a wallet that bonds more often than daily never fully vests.
History: 900 bonds between 14 August and 8 September, 1,256.16 MU in and 59,123,315 SEMI out, an average marginal backing of 21.2 µMU per SEMI, about twice today's mark. 87% of the MU arrived between 1 and 5 September.
10FloorWall
FloorWall lets anyone park MU as a single-sided v4 range position a little above the kill line, the price at which the protocol position's MU equals mark × S:
A band is [√P_kill · (1 + offset/2), · (1 + band/2)] in square-root space, aligned to ticks and clamped one tick spacing below spot, so the position is pure MU. Orders use salt = order id and are owned by FloorWall, so reserves() ignores them: the wall adds depth without changing the oracle.
| Constant | Value | Effect |
|---|---|---|
MAX_BAND_BPS / DEFAULT_BAND | 2000 / 500 | Band width limit and default |
MAX_GRID | 8 | openGrid splits one deposit across up to 8 bands |
FILL_CLOSE_BPS / FILL_PERSIST_BLOCKS | 9000 / 30 | harvestFilled needs ≥ 90% converted for 30 blocks: the first call arms, a later call closes |
RETICK_MIN_MULT / repriceCooldown | 2 / 300 blocks | Defender orders follow the line only after a 2-spacing move and a cooldown |
Owners can close() at any time; reprice and harvestFilled are permissionless but always pay the owner.
open, openGrid and reprice revert with BadBand. That is the current state: zero open orders, zero MU parked. The contract is immutable; accepting bids in this regime would need a new deployment.11Read paths
SemiLens.snapshot() returns, in one eth_call, the supply, the liquidity-derived MU reserve, the mark, the live backing ratio, lifetime Ratchet emissions and the MU token address, so anyone can re-derive the numbers in this paper. SemiRatchet.currentFbr() gives live (not minimum) backing. SemiBond.quote(muIn) returns the post-gate amount and whether the gate binds.
A wrapper, wSEMI (an exchange-rate token over a pooled stake) and a pod factory on top of it, are written and tested but not deployed.
12Launchpad (SemiVault Foundry)
The launchpad lets anyone create a token on a bonding curve that graduates into its own Uniswap v4 pool under a shared hook. The site uses SemiPad 0x68f2…8e with hook 0x4a0f…a0cC. It holds 5 launches, none graduated; its quote table has 166 assets including WETH, SEMI, MU and Robinhood stock tokens.
Launch
launch(Params) costs LAUNCH_FEE = 0.0005 ETH, paid to the operations wallet. It deploys a 1,000,000,000-token ERC-20 through the token factory and registers the pool with the hook. The creator chooses a quote asset from the frozen table and a fee between MIN_FEE_BPS = 50 and MAX_FEE_BPS = 1000. They can also enable a reflection share (≤ 500 bps, fee + reflection ≤ 1000), a burn of up to 1,000 bps on transfers or on sells only, and a token-level elastic mint of up to 200 bps per epoch of at least 1 h, gated like SEMI's Ratchet on the token's own backing high.
Curve
Each quote asset has its own phantom and threshold. Reserves are tracked internally, so a donation cannot move price or trigger graduation. The buy that empties the sellable allocation is clamped, refunded the excess, and triggers graduation in the same transaction.
Graduation
When the sellable allocation is gone, the pool is initialized at the curve's terminal price (phantom + threshold) / reserved rather than below it. The tokens that would have been sold below that price are sent to 0x…dEaD. All quote and the remaining tokens become one full-range position owned by the pad, which has no function that removes liquidity.
Fees and the SEMI link
The same split applies on the curve and in the pool, and the shares are constants: 60% to the creator (pull payment, minus any reflection share), 20% to the SemiSink, 20% to operations. In the pool the hook runs a 120 s launch curve (95% flat for 5 s, then 90%→50% to 35 s, 50%→25% to 60 s, 25%→the creator's rate to 120 s) and a sandwich-limited harvest (no harvest in a block where the pool already swapped, ±~5% price impact).
SemiSink routes its slice into SEMI. A SEMI quote goes straight to 0x…dEaD. Any other quote is swapped along a frozen route to MU, and then, with BURN_BPS = 5000, half buys SEMI and sends it to 0x…dEaD and half goes to SemiHook.donateFloor. Lifetime: 649.27 SEMI sent to dead and 0.00414 MU added to the floor.
13The Fab
The Fab is a seasonal elimination game over ten stock prices (NVDA, MU, AMD, SKHY, INTC, TSLA, AAPL, META, MSFT, SNDK), staked in USDG. It creates no token. The current version is FabV2 0xFb12…6712 with FabSwapV2 0xA66b…48E0.
Rules
openSeason()(anyone) snapshots all ten prices.enter(w, amount)stakes USDG (minimum 1) on a live tile while more than 5 tiles are alive.migratemoves stake for 0.5% (MIGRATE_BPS = 50) paid to the floor sink.- Every 6 h,
settleEpoch()(anyone) busts the funded live tile with the lowest return since the season-open snapshot:((price − snap) · 10⁴) / snap, ties to the lowest index. The season ends when one funded tile is left; a full board is 9 epochs. - Winners get their stake back plus a pro-rata share of the prize pool. A busted stake earns a refund of 40% plus 2.5% per epoch the player survived, capped at 60% (reached after 8 epochs). The refund is paid in SEMI, bought with the USDG at claim time through USDG → MU → SEMI.
"Floor" in The Fab is a plain USDG transfer to the operations wallet, 0x6388…dDce. FabV2 never calls donateFloor. Its link to SEMI is the market buy of SEMI at claim time.
Prices come from SemiFabOracle: an owner-set poster publishes Uniswap v4 spot prices against USDG every 300 s, limited to a 15% jump per post and one post per 60 s per asset. Lifetime: 208.68 USDG entered, 20.83 USDG to the floor sink, 1,069 SEMI delivered to players. The poster and keeper last ran on 17 September; season 4 is open with no stake.
14SemiScape
SemiScape is a browser action game in which each player character is a Seraph NFT (777 supply, ERC-721 with ERC-6551 token-bound accounts). Game simulation runs on the server; SEMI balances are settled on chain in signed batches.
| Contract | Function |
|---|---|
SeraphNFT 0xB14F…9704 | #1 to the developer; #2–#112 free Merkle claim for 7 days; #113–#777 at 0.01 ETH. Art traits are seeded in the mint transaction and rendered fully on chain. Transfers are blocked while a play grant is live. |
MintBurner 0x296f…DC16 | Swaps 100% of public-mint ETH along ETH → MU → SEMI and sends the SEMI to 0x…dEaD in the mint transaction. Lifetime: 0.65 ETH, 278,493 SEMI burned. |
CharacterUnlock 0x411c…8b7e | Extra classes cost 1,070 SEMI (bounded 100–20,000; repricing at most every 42 days), transferred to 0x…dEaD, or paid in ETH and swapped. |
SeraphPlayVault 0xe84b…6b54 | Per-Seraph SEMI play balance with principal tracked separately from winnings. |
Bounds on the settler key: maxBatchDebit 1,000,000 SEMI, maxDebitPerHour 2,000,000, credits of 100,000 or more held 24 h and disputable, and withdrawal limits:
- winnings above 100,000 SEMI per withdrawal wait
WITHDRAW_DELAY = 1 h; - at most 500,000 SEMI of winnings leave per hour, vault-wide;
- principal withdrawals and
revokecannot be paused.
Server policy, not enforced by the contracts: game fees and in-game purchases are 70% burned to 0x…dEaD and 30% to the fee sink; staked duels charge 5% per side.
Administration is a 48-hour TimelockController whose only proposer and executor is the developer wallet. SemiScape does not feed the SemiHook floor; its link to SEMI is the market buys and the burns.
15Trust model and risks
| Component | Who can change what |
|---|---|
| Core SEMI (§2–§11) | No one. All one-shot setters consumed; no owner, pause, upgrade or withdraw on any contract. |
| MU, the floor asset | Robinhood. MU is a beacon proxy whose implementation has pause(), role-gated mint() and can be replaced. While paused, sells, bonds and harvests revert. paused() is false. |
| Operations wallet 0x6388…dDce | An EIP-7702-delegated externally owned account. It receives 3% of every swap, router fees, the launchpad's 20% and launch fees, and The Fab's floor share. Nothing it receives is protocol backing. |
| Launchpad | No owner. The quote table was frozen by the deployer; SemiSink routes are one-shot per asset. |
| The Fab | Owner and oracle poster are one key (0x8720…1F19). It sets all prices, within the jump limit, and so decides which tile busts. It can redirect the floor sink and the swapper and disable staleness checks, but it cannot take USDG owed to players. |
| SemiScape | The server settler key, bounded as in §14; a 48 h timelock for parameters; the guardian can pause and dispute; CharacterUnlock's owner acts without a timelock. |
Known limitations
- The mark is not a redemption price. Backing per token is the MU in the pool divided by supply. Sells remove MU from the pool, so backing can fall below the mark and stay there; today it is 35% of the mark.
- The stock behind MU prices about 6.5 hours a day while the pool trades 24/7; on weekends the floor marks to the last trade of MU on chain.
- Router fee tiers key on
msg.senderand are avoided by splitting across wallets or trading against the pool directly. - Any
stake()relocks the whole position for 6 h; anybond()restarts the whole unvested balance on a new 24 h schedule. - FloorWall cannot accept orders while backing is under the mark (§10).
- The Fab has no emergency exit: if a funded tile's feed is stale,
settleEpochreverts and stakes in a running season cannot be withdrawn until the poster resumes or the owner disables the staleness check. The site'sclaim(0)has no slippage bound on the SEMI buy. - Contract reviews were internal (SEMI, 6 September 2026; SemiScape). No external audit.
16Address registry
| Contract | Address (Robinhood Chain, 4663) |
|---|---|
| SemiToken (SEMI) | 0x5f038759f6de38fd3a85c0440daff1240238bac8 |
| SemiMinter | 0x033da8e5dfeda56bfc3c8ac6a68c36c77d697bec |
| SemiHook | 0x7b89c56Da91425F35D07290eCFEcF4E58dc13088 |
| SemiLauncher | 0xbae7af495b74d2ee0f1824dfd544933d83106deb |
| SemiRatchet | 0xf0883c397a18bfd469af4f66a05406e9940190f7 |
| SemiStaker | 0x1a79b304872d3ed8b50c22721bc5561694b0927f |
| SemiBond | 0xaba28a7e980494be146de968992dfd66f5e47736 |
| FloorWall | 0x12162b9d077824fa3a1c020ce673545596d47fb1 |
| SemiSwapFeeV2 | 0x48acbaab9fb51977c0f93a153ec2e9367830bdec |
| SemiSwapFeeV4 | 0xe88187801deaa71b2e2c448d9f841881b080a42e |
| SemiLens | 0xa58368d5a0b437426bb0d23ded53f7e43b6ecb97 |
| MU (Robinhood tokenized Micron) | 0xfF080c8ce2E5feadaCa0Da81314Ae59D232d4afD |
| Uniswap v4 PoolManager | 0x8366a39CC670B4001A1121B8F6A443A643e40951 |
| Operations wallet | 0x63888d25934504CfcFb83aCc3f9af90B3e30dDce |
| SemiPad (site) | 0x68f25debfa63b70c1db9b1602db0d2c4d5b1f98e |
| SemiPadHook (site pad) | 0x4a0fffC1198f8cBDAA08B881885318F6A726a0cC |
| SemiSink | 0x89a7b3f5e6254e77ef7ef84f79f7ff2f13187969 |
| FabV2 | 0xFb1208ac98744f4cf595c48A7004136A5fA96712 |
| FabSwapV2 | 0xA66b97b64DD7033b3393D86642A2503019D348E0 |
| SemiFabOracle | 0x6e556f241404ce556537788ba40ef2fbc45de4a9 |
| SeraphNFT | 0xB14FD1f6135F2d20c48f5c414b9D20a321069704 |
| SeraphPlayVault | 0xe84b9435932b0Fa9D54C1d384551383D3Ded6b54 |
| MintBurner | 0x296fe9C6528c45B41c94E05937f5C4B0Fe3CDC16 |
| CharacterUnlock | 0x411cBCeb0a0b175763c30F46eE086e179a398b7e |
| SemiScape TimelockController | 0x05961E25C0B3461ebCCCcF4D961272263ddb6450 |