semivault.xyz · Robinhood Chain (4663) · Technical paper

SEMI: a token floored on tokenized Micron stock

Version 1.05 October 2026Chain state at block 80,705,802 (10:12 UTC)

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.

beforeSwapseed LPconsumeMin()donateFloor(MU)mint ΔSmintnotify(ΔS)3% of each swaprouter feeTraderwalletSemiSwapFee V2 · V4router fee, taken in MUUniswap v4 pool · SEMI / MUlpFee 0 · tickSpacing 60FloorWallown MU bandsnot in the oracleSemiLauncherseed positionno exitSemiHook5% skim · harvest() · donateFloor()reserves() · epochMinOperations0x6388…dDceSemiRatchetpoke() per 6 hmark = fbrHwmSemiBondMU → locked LPSEMI vests 24 hSemiLensread-onlySemiStakerpending → activeSemiMinteronly mint gateSemiTokenERC-20 · SEMI
Figure 1. Contract graph. Solid edges are calls made by the contracts; dashed edges are value flows to the operations wallet and the FloorWall's own positions in the same pool.
ContractRoleLinesMutable configuration
SemiTokenERC-20, 18 decimals; one gated mint path95minter set once → SemiMinter
SemiMinterThe only address the token accepts mint from; forwards for two callers54ratchet, bond, each set once
SemiLauncherOpened the pool and owns the seed position176none; launched = true
SemiHookv4 hook: 5% fee, launch window, harvest, floor donations, backing oracle390launcher, ratchet, burner, each set once
SemiRatchetBacking-gated emissions, one poke per 6 h153none
SemiStakerReceives emissions; pending/active stake accounting148ratchet set once
SemiBondMU in → locked LP; discounted SEMI out, vested 24 h169none
FloorWallSingle-sided MU limit bids in the same pool383none
SemiSwapFeeV2 · V4Site routers with a sell-tier fee taken in MU300 · 281none
SemiLensOne-call proof-of-reserves view65none

The live state used throughout this paper:

Total supply81,267,162SEMI
MU in locked LP310.30liquidity-derived
Backing per SEMI3.818µMU (10⁻⁶ MU)
Ratchet mark10.789µMU, set 3 Sep
Spot price5.470µMU per SEMI
MU reference$1,075.80USDG/MU v4 pool

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.

Genesis (all seeded to the pool)10.00M · 12.3%Ratchet emissions12.14M · 14.9%Bond mints59.12M · 72.8%
Figure 2. Where today's supply came from. Bond mints are 72.8% of all SEMI ever created; Ratchet emissions are 14.9%.
In the pool (protocol LP)56.73M · 69.8%Staked (active + pending)9.85M · 12.1%At 0x…dEaD0.42M · 0.5%All other holders14.26M · 17.5%
Figure 3. Where it sits now. "In the pool" counts only the two protocol-owned positions (they are 100% of the pool's in-range liquidity). Staked is active plus pending stake; the staker additionally holds 0.34M of unclaimed emissions.
0M10M30M50M70M90M15 Aug22 Aug29 Aug5 Sept12 Sept19 Sept26 Sept3 OctSEMI81.27Mtotal supplygenesis + Ratchet emissionsgenesis 10M
Figure 4. Total supply since launch, rebuilt from 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:

  1. pulled the entire 10,000,000 SEMI (POOL_SUPPLY) and 2 MU from the deployer;
  2. initialized the pool SEMI/MU, lpFee 0, tickSpacing 60, hooks = SemiHook at the seed ratio, sqrtPriceX96 = √(amount1 / amount0) · 2⁹⁶;
  3. 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 + 1 so v4's round-up on adds can never ask for more than it holds;
  4. called SemiRatchet.poke(), which set the first mark at the seed backing, 2 MU / 10M SEMI = 0.2 µMU per SEMI;
  5. 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 LShare
SemiLauncher (seed)4.472 × 10²¹3.4%
SemiHook (harvest compounding and donateFloor)1.282 × 10²³96.6%
Pool in-range liquidity1.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.

feeBps(t) = 500 + 4500 · ((60 − t) / 60)² for 0 ≤ t < 60 feeBps(t) = 500 for t ≥ 60
0%5%16.25%33.8%50%0 s12 s30 s45 s60 s75 shook feeseconds since afterInitialize50%33.8%16.25%5% from here on
Figure 5. Hook fee during the launch minute.

Harvest

harvest() is permissionless. Inside one unlock it sells the hook's whole SEMI balance for MU, then splits the hook's MU:

Hook balancesSEMI fees + MU feesSell all SEMI → MUprice bound ½× per swapmuBaleverything now in MU40% · _compound()half sold to SEMIFull-range LPowner = hook, salt 040% · TREASURYMU transfer20% · burnerMU transfer (if set)_observe()post-harvest reserve
Figure 6. Harvest. The internal swaps bypass the fee (sender == address(this)) and the oracle.
SliceConstantOf feesOf each swapDestination
CompoundCOMPOUND_BPS = 400040%2%Hook's locked full-range position
Burn sliceBURN_BPS = 200020%1%burner = 0x6388…dDce
Treasuryremainder40%2%TREASURY = 0x6388…dDce
External swapamountSpecified = 100SemiHook skim5.00 (500 bps)Locked LP, owned by the hook2.00 · COMPOUND_BPS 4000TREASURY 0x6388…dDce2.00 · remainderburner pointer = 0x6388…dDce1.00 · BURN_BPS 2000
Figure 7. Destination of a 5% skim on a swap of 100 units.
The burn slice is not burned. The source header describes the 20% slice as a GIWA buy-and-burn. On chain, the one-shot 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.

020040060080010001200140015 Aug22 Aug29 Aug5 Sept12 Sept19 Sept26 Sept3 Octcumulative MU into locked LP1,265.7 MU135.1 MUdonateFloor (bonds, router sweeps)harvest compounding (40% of hook fees)
Figure 8. MU added to the locked position, from 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:

L = L_launcher + L_hook amount0 = L · 2⁹⁶ · (√P_B − √P) / (√P_B · √P) amount1 = L · (√P − √P_A) / 2⁹⁶

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.

poke npoke n+1poke n+2MU reserve (schematic)one-block spike: not in the minimumconsumeMin() returns thismuReserve() at each external swapepochMin
Figure 9. Schematic. A reserve spike that reverts within the window does not raise the minimum; backing counts only if it held at every observation in the window.

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:

ConstantSemiSwapFeeV2SemiSwapFeeV4
BUY_FEE_BPS (MU input, before the swap)5050
SELL_FEE_BPS (MU output, after the swap)1001,000
WHALE_FEE_BPS (whole trade, at or above threshold)5003,000
WHALE_THRESHOLD / WINDOW100,000 SEMI / 24 h100,000 SEMI / 24 h
FLOOR_SHARE_BPS5,0000
Used byv1 sitev2 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.

Direct pool swap, either side5% · hook onlyBuy, V2 or V4 router5.475%Sell, V2 router, window < 100k SEMI5.95%Sell, V2 router, window ≥ 100k SEMI9.75%Sell, V4 router, window < 100k SEMI14.5%Sell, V4 router, window ≥ 100k SEMI33.5%
Figure 10. Total cost of one trade by route, hook fee included. Buy: 1 − 0.995 × 0.95. Sells: 1 − 0.95 × (1 − router rate).
Router totalsV2V4
To the floor (lifetime)2.6173 MU0
To treasury (lifetime)2.6173 MU0.2463 MU
Pending, unswept0.0351 MU0.1640 MU

7SemiRatchet: emissions

Backing ratio and the mark, both in MU-wei per whole SEMI (the site shows them in µMU):

fbr = R_min · 10¹⁸ / S R_min = hook.consumeMin(), S = totalSupply mint iff fbr > mark and staker.totalStaked() > 0 ΔS = min( ¾ · S · (fbr − mark) / mark , 2% · S ) mark := R_min · 10¹⁸ / (S + ΔS)

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.

yesyesyesyesyesnononononopoke()anyonenow ≥ lastEpoch + 6 h ?hook.muReserve() > 0 ?rMin = hook.consumeMin()fbr = rMin · 1e18 / S · lastEpoch = nowinitialized ?fbr > mark ?staker.totalStaked() > 0 ?mint ΔS to SemiStakermark = rMin · 1e18 / (S + ΔS) · notify(ΔS)revert"epoch cooldown"returnno state changemark = fbrMarkInitializednotify(0)NoNewHighmark = fbr, no mintBankedNoStakersFirst poke runs insideSemiLauncher.launch().ΔS = min( ¾ · S · (fbr / mark − 1), 2% · S )
Figure 11. Every path through 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.

0%1%2%4%6%0%1%2%2.67%4%6%8%g = fbr / mark − 1 (observed gain over the mark)cap binds: 2% of supplyΔS, % of supply mintedmark increase, %
Figure 12. Mint size and mark increase as a function of the observed gain. The 2% cap binds from g = 2.67%.

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.

02468101215 Aug22 Aug29 Aug5 Sept12 Sept19 Sept26 Sept3 OctµMU per SEMIlast mint, 3 Sep: 10.7893.82mark (fbrHwm)window-minimum backing read by poke()
Figure 13. Mark and observed window-minimum backing at each poke. The mark is a step function that only rises; the observed backing has stayed between 3.5 and 4.0 µMU since mid-September.
Distance to the next mint. A mint needs a window whose minimum MU reserve exceeds 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):

accRewardPerShare += ΔS · 10¹⁸ / totalActive (if ΔS > 0 and totalActive > 0) epochCount += 1

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.

poke npoke n+1poke n+2stake(x)pending, earns 0active, shares ΔS from poke n+2unlockAt = stake time + 6 h (whole position)ΔS(n+1) excludes x
Figure 14. A stake placed between two pokes earns from the poke after next.
Active stake8,012,343SEMI
Pending stake1,839,845SEMI
Epochs147equal to pokes
accRewardPerShare3.356SEMI per active SEMI, lifetime

9SemiBond

bond(muIn, minSemiOut) pulls MU, routes 100% of it through SemiHook.donateFloor and mints SEMI through SemiMinter:

marketOut = muIn · semiReserve / muReserve · 1.20 DISCOUNT_BPS = 2000 fbrCap = muIn · 10¹⁸ / mark semiOut = min(marketOut, fbrCap) reverts if mark == 0

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.

FBR gate binds-60%-40%-20%0%+20%0.40.510.811.21.51.8spot price ÷ markfull +20% from 1.2 × markblock 80,705,802: −49%bond result vs buying at spot
Figure 15. SEMI received from a bond relative to buying the same MU worth at spot, before fees, as a function of spot ÷ mark. At block 80,705,802 spot is 0.507 × mark, so the gate binds and a bond returns 49% less than the market.

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:

√P_kill = √P_A + (mark · S / 10¹⁸) · 2⁹⁶ / L_protocol

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.

ConstantValueEffect
MAX_BAND_BPS / DEFAULT_BAND2000 / 500Band width limit and default
MAX_GRID8openGrid splits one deposit across up to 8 bands
FILL_CLOSE_BPS / FILL_PERSIST_BLOCKS9000 / 30harvestFilled needs ≥ 90% converted for 30 blocks: the first call arms, a later call closes
RETICK_MIN_MULT / repriceCooldown2 / 300 blocksDefender 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.

Closed while backing is under the mark. When backing is below the mark, the kill line is above spot and every band would straddle it, so 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

reserves = (phantom + trackedQuote, trackedTokens) out(a) = a · R_out / (R_in + a) constant product reserved = SUPPLY · phantom / (phantom + threshold) never sold on the curve snipe(t) = 9900 bps >> ⌊14 t / 3⌋ for t < 3 s, creator exempt

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. migrate moves 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.
Reserved for the player refund in SEMI (40–60% is paid)60%Prize pool in USDG, to the winning tile30%Floor sink 0x6388…dDce10%
Figure 16. Split of a busted tile's pod. The 0–20% gap between the reserved 60% and the player's actual refund rate is forfeited to the floor sink at settlement.

"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.

ContractFunction
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…DC16Swaps 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…8b7eExtra 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…6b54Per-Seraph SEMI play balance with principal tracked separately from winnings.
depositAndGrantsigned batchfeeburnpot deltaPlayer wallet / TBAdeposit · withdrawSeraphPlayVaultbalance · principal · grantGame serverSETTLER_ROLE signaturesettleBatch(b, sig)debits = credits + pots+ fee + burnfeeSinksweepFees()0x…dEaDb.burnPotslive : reserve = 8 : 1
Figure 17. Play-vault settlement. The settler key can only debit tokens with a live grant, within the grant cap, and every batch must balance.

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 revoke cannot 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

ComponentWho 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 assetRobinhood. 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…dDceAn 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.
LaunchpadNo owner. The quote table was frozen by the deployer; SemiSink routes are one-shot per asset.
The FabOwner 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.
SemiScapeThe 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.sender and are avoided by splitting across wallets or trading against the pool directly.
  • Any stake() relocks the whole position for 6 h; any bond() 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, settleEpoch reverts and stakes in a running season cannot be withdrawn until the poster resumes or the owner disables the staleness check. The site's claim(0) has no slippage bound on the SEMI buy.
  • Contract reviews were internal (SEMI, 6 September 2026; SemiScape). No external audit.

16Address registry

ContractAddress (Robinhood Chain, 4663)
SemiToken (SEMI)0x5f038759f6de38fd3a85c0440daff1240238bac8
SemiMinter0x033da8e5dfeda56bfc3c8ac6a68c36c77d697bec
SemiHook0x7b89c56Da91425F35D07290eCFEcF4E58dc13088
SemiLauncher0xbae7af495b74d2ee0f1824dfd544933d83106deb
SemiRatchet0xf0883c397a18bfd469af4f66a05406e9940190f7
SemiStaker0x1a79b304872d3ed8b50c22721bc5561694b0927f
SemiBond0xaba28a7e980494be146de968992dfd66f5e47736
FloorWall0x12162b9d077824fa3a1c020ce673545596d47fb1
SemiSwapFeeV20x48acbaab9fb51977c0f93a153ec2e9367830bdec
SemiSwapFeeV40xe88187801deaa71b2e2c448d9f841881b080a42e
SemiLens0xa58368d5a0b437426bb0d23ded53f7e43b6ecb97
MU (Robinhood tokenized Micron)0xfF080c8ce2E5feadaCa0Da81314Ae59D232d4afD
Uniswap v4 PoolManager0x8366a39CC670B4001A1121B8F6A443A643e40951
Operations wallet0x63888d25934504CfcFb83aCc3f9af90B3e30dDce
SemiPad (site)0x68f25debfa63b70c1db9b1602db0d2c4d5b1f98e
SemiPadHook (site pad)0x4a0fffC1198f8cBDAA08B881885318F6A726a0cC
SemiSink0x89a7b3f5e6254e77ef7ef84f79f7ff2f13187969
FabV20xFb1208ac98744f4cf595c48A7004136A5fA96712
FabSwapV20xA66b97b64DD7033b3393D86642A2503019D348E0
SemiFabOracle0x6e556f241404ce556537788ba40ef2fbc45de4a9
SeraphNFT0xB14FD1f6135F2d20c48f5c414b9D20a321069704
SeraphPlayVault0xe84b9435932b0Fa9D54C1d384551383D3Ded6b54
MintBurner0x296fe9C6528c45B41c94E05937f5C4B0Fe3CDC16
CharacterUnlock0x411cBCeb0a0b175763c30F46eE086e179a398b7e
SemiScape TimelockController0x05961E25C0B3461ebCCCcF4D961272263ddb6450