OHMVAULT: a token floored on OHM
OHMVAULT is an ERC-20 on Ethereum that trades in a single Uniswap v4 pool against OHM, the Olympus reserve currency. The whole genesis supply and an OHM seed go into that pool as full-range liquidity that no contract can remove. A hook takes 5% of every swap and adds part of it back to the locked position, so the OHM under the curve grows with volume. Supply is elastic but gated: new tokens are minted by a Ratchet only while OHM backing per token is above an up-only mark, in both OHM and dollar terms, at most 2% of supply per six-hour slot; and by a Bond that pays for every token it mints with OHM added to the locked position, never below live backing.
The protocol is SEMI v1 (semivault.xyz, Robinhood Chain, August 2026) moved to Ethereum and to a new collateral. Most of the code is unchanged. This paper describes every contract and constant, and spends most of its length on the parts that changed and why, using SEMI's on-chain history as the test set. The token is 0xd520e9f57ad0076b470af4d2eb1577b60604a233: name ohmvault.xyz, symbol OHMV.
1System overview
Ten contracts. None has an owner. Every privileged setter is a one-shot pointer that the launch script calls once, after which it reverts for every address, the deployer's included. These are the ten core contracts. The add-ons in sections 15 to 19 (private payouts, auto-stake, OHMV loans, Cerberus, Kennel Pad and Cerberus loans) are separate contracts built on top of them. None of them can mint OHMV or remove the locked liquidity.
| Contract | Role | Lines | Mutable configuration |
|---|---|---|---|
| VaultToken | ERC-20, 18 decimals; one gated mint path | 89 | minter, set once |
| VaultMinter | The only address the token accepts mints from; forwards for two callers | 54 | ratchet, bond, each set once |
| VaultLauncher | Opens the pool and owns the seed position | 175 | none; launched = true |
| VaultHook | v4 hook: 5% fee, launch minute, harvest, floor donations, backing oracle, TWAP | 458 | launcher, ratchet, each set once |
| VaultRatchet | Backing-gated emissions on a fixed 6 h grid, OHM and USD marks | 205 | none |
| VaultStaker | Receives emissions; pending, active and cooling stake | 197 | ratchet, set once |
| VaultBond | OHM in, locked LP up, discounted OHMV out over 24 h | 189 | none |
| OhmUsdOracle | Chainlink OHM/ETH × ETH/USD with staleness limits | 64 | none |
| VaultLens | One-call proof-of-reserves view | 79 | none |
| VaultSwap | Site router: ETH or OHM in, OHMV out, and back; 1% on sells | 202 | none |
The test suite has 39 unit and fuzz tests against a local PoolManager with a 9-decimal mock OHM and mock feeds, and three fork tests that run the full launch, a stake's whole life and a router round trip against the real mainnet PoolManager, OHM and Chainlink feeds. Fuzzing covers the two properties everything else rests on: a bond never lowers backing per token, and a mint never leaves live backing under the mark.
2The token
VaultToken is a minimal ERC-20 with no transfer hooks, no tax, no pause, no blacklist and no owner. Name, symbol and links are constructor arguments, so the final brand ships in the same bytecode the tests ran against and can't change afterwards. The deployed token reads name() = "ohmvault.xyz", symbol() = "OHMV", and stores WEBSITE (https://ohmvault.xyz), TELEGRAM (https://t.me/ohmvault) and X_ACCOUNT (https://x.com/OHMVault) on chain, emitted once in MetadataDeclared. The constructor mints 10,000,000 tokens to the deployer. The only other mint path is mint(to, amount), callable only by the minter, which is set once to VaultMinter. VaultMinter accepts calls from exactly two contracts: VaultRatchet and VaultBond.
There is no burn function. Tokens sent to 0x…dEaD leave circulation but stay in totalSupply, and backing is always computed against totalSupply, so a burn never flatters the numbers.
3Launch and locked liquidity
The launch transaction
VaultLauncher.launch(ohmSeed) runs once, from the deployer, and does five things in one transaction:
- pulls the entire 10,000,000 tokens (
POOL_SUPPLY) and the OHM seed from the deployer; - initializes the OHMV/OHM pool with
lpFee 0,tickSpacing 60and VaultHook attached, priced exactly at the seed ratio; - adds everything as one full-range position 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; - calls
VaultRatchet.poke(), which starts the 6-hour clock and seals both marks at the seed backing; - emits
FloorPoured.
The seed was 100 OHM, about $2,000 at $20 (VaultLauncher.ohmSeeded() and the FloorPoured event record it). Launch price is then 10,000,000 / 100 = 100,000 OHMV per OHM, and the fully diluted value at launch equals the seed. There is no dev bag: the deployer holds 0 OHMV when launch returns, and the launch script checks it.
The launcher has no function that removes liquidity, and the hook's own compounded position has the same property. OHM leaves the protocol positions only when OHMV is sold into the pool, by a trader or by the hook selling its OHMV fees at harvest. Anyone may add a position of their own, but with an LP fee of 0 it earns nothing, its owner can withdraw it, and it is not counted as backing (section 5).
Deployment order
scripts/launch-ohmvault.mjs sends 19 transactions from one wallet in three stages, each behind a typed confirmation, and waits between them. TOKEN is transaction 1: the token exists and can be verified, and the whole supply sits in the deployer with no pool to trade in. DEPLOY is transactions 2–16: every other contract and the wiring, with the pool still closed. LAUNCH is transactions 17–19 and opens the pool. Each address is written to deployment-ohmvault.json atomically as it lands, together with the nonce of any deploy in flight, so a run that is killed at any point resumes exactly where it stopped (RESUME=1) and never deploys a second token. The token went out on 7 October at 09:29 UTC. The pool opened at 20:00:11 UTC in block 26,142,760, pool id 0xb20736912eacde8dd0bac06ec67ab7f150a20afc4a34632f56dede4c04ca20ee.
| # | Transaction | Note |
|---|---|---|
| 1 | VaultToken | stage TOKEN; genesis, 10M to the deployer |
| 2–4 | VaultMinter, VaultStaker, OhmUsdOracle | stage DEPLOY begins |
| 5 | VaultHook via the CREATE2 deployer 0x4e59…956C | salt mined so the lowest 14 bits of the address are the flags 0x30C8 (it ends in …f0c8) |
| 6–10 | VaultRatchet, VaultBond, VaultLauncher, VaultLens, VaultSwap | immutable wiring in constructors; the router is live before the pool opens |
| 11–16 | minter.setRatchet, minter.setBond, token.setMinter, staker.setRatchet, hook.setRatchet, hook.setLauncher | every one-shot setter used up |
| 17–18 | approve 10M OHMV and the OHM seed to the launcher | |
| 19 | launch(seed) | stage LAUNCH (17–19); pool opens; the launch minute starts |
How SEMI launched, from the chain
SEMI's launch on Robinhood Chain is the reference run. From its Transfer and Ratchet logs:
| Event | Block | Time (UTC, 13 Aug 2026) |
|---|---|---|
| Genesis: 10M SEMI minted to the deployer | 35,772,946 | 23:07:15 |
| Launch: 10M SEMI and 2 MU into the pool, first poke, mark sealed at 0.2 µMU | 35,773,265 | 23:07:47, 32 s after genesis |
| First buy, 459,615 SEMI, inside the launch minute | 35,773,270 | 5 blocks after the launch transaction |
| Next buys | 23:08:14 (+27 s), three in one block at 23:08:39 (+52 s) | |
| Steady buying begins as the fee reaches 5% | from about 23:08:47 (+60 s) | |
| First sells back into the pool | 23:08:52 and 23:09:04 | |
| First Ratchet mint | 36,216,079 | 14 Aug 11:27, the second regular poke |
Robinhood Chain makes a block roughly every tenth of a second; Ethereum makes one every 12 seconds. The launch minute is therefore five blocks long on Ethereum, and the fee each block pays is fixed in advance:
| Seconds after launch | 0 | 12 | 24 | 36 | 48 | 60+ |
|---|---|---|---|---|---|---|
| Hook fee | 80% | 77% | 59% | 41% | 23% | 5% |
A bot that lands in the launch block itself pays 80%, and while the fee is above 5% a swap reverts if its specified amount (the input on an exact-input swap, the output on an exact-output one) is more than 10% of the protocol positions' reserve of that token. The token contract went public hours before launch so anyone could read it. That gives no head start: the deployer holds the entire supply until launch, there is no pool to trade in until transaction 19, and the hook refuses to initialize a pool for anyone but the launcher.
4VaultHook: the fee
The hook's address carries permission flags 0x30C8: beforeInitialize, afterInitialize, beforeSwap, afterSwap and beforeSwapReturnsDelta. The constructor checks its own address against these flags and reverts on a mismatch. 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. For exact-input swaps the fee is on the input: OHMV on sells, OHM on buys. For exact-output swaps it is 5% of the requested output, taken in that token: the swapper gets the full amount asked for and pays for 105% of it. The hook accrues fees in both tokens until a harvest.
The launch minute
The launch curve is the one $APT launched with: 80% flat for 10 seconds, then straight down to 50% at 30 seconds and to 5% at 60. SEMI v1 used a quadratic 50%→5%; the flat 80% start makes a first-block snipe cost more than any realistic first-minute move pays.
The hook's own swaps pay 5% too
Uniswap v4 does not run a hook's callbacks for swaps the hook makes itself. In SEMI v1 that made the hook's own swaps free. Here _swap sends 95% of the intended amount into the pool and keeps 5% back as fees, so harvest compounds and the sold half of every bond pay the same skim as a trader. That fee stays in the hook and is split at the next harvest like any other.
Each of the hook's own swaps is also bounded to a price move of about 8% (√price × 0.9591 or × 1.0392). On a pool where every leg pays 5%, a sandwich around a move smaller than 8% costs the attacker more in skims than the move is worth. An oversized harvest fills partially; the OHM it could not place stays in the hook and is split 40/60 again at the next harvest. An oversized donation is refunded.
Harvest
harvest() is permissionless. Inside one unlock it sells the hook's whole OHMV balance for OHM, then splits the hook's OHM 40/60:
_compound(ohmAmt) sells 1/1.95 of the OHM for OHMV, so that after the 5% skim on the sold part the two halves match, and adds both as full-range liquidity owned by the hook. There is no burn slice: SEMI v1 sent 20% of each harvest to a GIWA buy-and-burn pointer, which was in practice the operations wallet. On Ethereum that slice is simply part of the 60%.
donateFloor
donateFloor(ohmAmt) is permissionless. It pulls OHM from the caller and runs only the compounding step, with no direct treasury cut. The half the hook sells still pays the 5% skim, about 2.56% of the donation. That OHM stays in the hook and is split 40/60 at the next harvest, so about 1.55% of every donation, and of every bond, ends up with the operations wallet. It returns what it placed and refunds what the 8% bound left unplaced. Because the sold half pays 5%, the share of a donation that reaches the pool is 1 − 0.05 / 1.95 ≈ 97.44% (donationNetBps()). VaultBond delivers all bonded OHM through this function.
5VaultHook: 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 it to token amounts at the current price. OHM sent to the hook or pool directly and any third-party liquidity are invisible to it. Nobody can park liquidity to force a mint and pull it afterwards.
Price never sits under live backing
For a full-range constant-product position, price in OHM per OHMV is R_ohm / R_tok and live backing is R_ohm / S. Their ratio is S / R_tok: total supply over the tokens held by the protocol position. Since that position holds part of the supply, the ratio is never below 1. If every holder sold everything, the price would end exactly at backing. Backing itself moves with trading, though: buys add OHM to the pool and sells take it out.
The minimum over the window
The hook records epochMin = min(epochMin, ohmReserve()) before every external swap, after every external swap, and after every harvest or donation. consumeMin(), callable only by the Ratchet, returns that minimum and resets it. SEMI v1 sampled only before swaps, so a window's last trade was never seen until the next window; sampling after each swap as well closes that.
TWAP
Each observation also updates a time-weighted price accumulator and writes at most one entry per block into a 32-slot ring. twapTokenPerOhm() returns the average OHMV-per-OHM over the last 10 minutes, falling back to the oldest entry if the ring is younger. The ring holds the last 32 blocks that touched the pool, so when the pool trades in more than 32 blocks within 10 minutes (as little as 6.4 minutes at 12-second blocks) the average covers only those blocks. The bond uses it (section 9).
6VaultRatchet: emissions
Backing ratio and marks, scaled by 10³⁶ because OHM has 9 decimals (at 10¹⁸, one token's backing would read about 10⁴ and move in 0.01% steps):
Supply is read at the poke, while R_min is the lowest reading since the previous poke. Tokens bonded during a slot therefore count in full, but the OHM they added counts only if it was there at every observation. A large bond usually lowers that slot's reading and shows up in the next slot's.
A fixed clock
The launch poke sets genesis. Slots are 6 hours long and counted from it. Genesis was 7 October 2026 at 20:00:11 UTC, so slots close at 02:00, 08:00, 14:00 and 20:00 UTC. The keeper pokes within a minute or two of each boundary. poke() is permissionless and pays no reward; it reverts until a slot boundary has passed since the last settlement, then settles once, however many slots were missed. A missed slot is not paid on its own: the catch-up poke mints at most once, at most 2% of supply, against the lowest backing seen across the whole gap, and the staker ticks once. In SEMI v1 the six hours ran from the previous poke, so whoever poked chose which observations fell into the window. Here the boundaries are fixed, so nobody can poke early or shorten a window. A late poke still matters a little. The window runs from the previous poke to this one, and supply and the dollar price are read at the moment of the poke. Observations after the boundary fall into the closing slot, where they can only lower the minimum, and no longer count for the next one.
poke(). Each path that reads the oracle also calls staker.notify, which advances the staker's epoch.Two marks: OHM and dollars
The OHM mark alone has one blind spot. If OHM crashes against the dollar, arbitrageurs sell cheap OHM into every OHM pool, including this one; OHM backing per token rises while the dollar value of the floor falls. The Ratchet therefore keeps a second mark in USD per token and needs a new reading above both. An OHM crash with arbitrage inflow raises the OHM reading but not the dollar one: no mint. An OHM pump with no trading raises the dollar reading but not the OHM one: no mint. A stale or broken feed means no mint and both marks untouched; the slot still ticks so stakers keep activating. After a mint the dollar mark becomes the new OHM mark times that poke's OHM price, if that is higher; when nothing is minted for lack of stake it becomes the dollar reading. Like the OHM mark, it never falls.
The trailing mark
In SEMI v1 the mark was set to the post-mint backing after every mint. That is harmless for small gains. For large ones it threw most of the gain away: the 2% cap paid a sliver, and the mark still jumped to the top. OHMVAULT moves the mark by the part of the gain the mint actually paid for, and lets it trail the post-mint backing by at most 20%:
When the cap doesn't bind, carried equals the post-mint backing exactly and the rule is v1's. When it binds, the unpaid part of the gain stays in front of the mark and is paid at up to 2% a slot for as long as the backing holds. The three invariants of v1 still hold: nothing mints unless backing is above the mark; the mark never goes down; after a mint, live backing is at or above the mark.
When there is no active stake, the whole gain moves both marks and nothing is minted, so the first staker can't collect a backlog. Stake placed during the slot being settled is still pending and does not count. This is unchanged from v1.
7The trailing mark on SEMI's history
SEMI's Ratchet was poked 146 times after initialization between 14 August and 6 October 2026. It minted 27 times, 12,143,846 SEMI in total, and never after 3 September. Bonds minted 59.1M in the same period: 73% of all SEMI ever created came from bonding, 15% from emissions.
The question for OHMVAULT was how to get more paying slots without changing what the mint is backed by. The test: take SEMI's real window-minimum backing at every poke and its real bond mints, keep both fixed, and replay only the mark rule. Supply in the replay grows by the extra emissions, which dilutes the backing reading the way it would have.
| Mark rule | Slots that minted | Emitted | Share of final supply |
|---|---|---|---|
| SEMI v1, on chain | 27 | 12.1M | 14.9% |
| SEMI v1 rule, replayed (check) | 23 | 12.2M | 13.0% |
| 10% trail | 34 | 20.8M | 20.3% |
| 20% trail (OHMVAULT) | 44 | 29.5M | 26.6% |
| 30% trail | 56 | 44.1M | 35.2% |
The replayed v1 rule lands within 0.2% of the real emissions, with four fewer paying slots than the chain, which is close enough to trust the comparison. At 20%, emissions are 2.4× v1's and the number of paying slots roughly doubles. 20% was chosen over 30% because the 30% run kept printing into the first day of SEMI's September drawdown.
What no mark rule fixes: from 3 September SEMI's backing fell about 65% and never came back. Any rule that keeps the promise "never mint below an up-only mark" stops paying in that situation, and the trailing mark does too. It extends the payout of every rise that holds; it does not pay through a bear market. Getting emissions there would mean letting the mark fall, which is the one thing this design will not do.
8VaultStaker
An accumulator without rebasing. The Ratchet mints ΔS to the staker and calls notify(ΔS):
Entry: one slot
New stake is pending until the next tick. That tick only activates it; the stake gets no share of that tick's mint. It first earns at the tick after, so with on-time pokes the first reward comes 6 to 12 hours after staking, depending on where in the slot you stake. Staking one block before a poke and leaving after it earns nothing.
In SEMI v1 a pending stake became active only when its owner next touched the contract. A holder who staked and walked away was invisible to the Ratchet, which then saw "no stakers" and banked the gain instead of minting it. Here the tick itself activates everything staked in the closed slot (pendingTotal[epoch]), and each account settles lazily from the accumulator value recorded at that tick (accAtEpoch). No keeper and no loop over users.
Exit: one slot
unstake(amount) draws from active stake only, and not within 6 hours of the account's last stake. It does not pay out. The amount stops earning immediately and moves into cooling with coolingEnds = now + 6 h. Each tick pays its mint in one lump to the stake that is active at that moment, and nothing is pro-rated within a slot, so stake unstaked even one block before a tick gets none of that tick's mint. withdraw() pays the whole cooled amount once that time has passed; there is no partial withdraw. Unstaking again before withdraw() adds to cooling and restarts the 6 hours on the total, even if the earlier cooldown had already ended. Withdraw first to avoid a second wait. Rewards are accounted separately: claim() works at any time, cooldown or not, and can never touch principal.
The exit wait does two jobs. It makes staking a commitment of at least two slots, so rewards go to people who stay, and it slows a run on the exit: a staker who wants out in a panic is out six hours later, not in the same block as everyone else.
Partial unstake and parallel positions
unstake(amount) takes any amount up to the caller's active stake. Only that amount leaves active and enters cooling for COOLDOWN (6h); the remainder keeps the same reward accumulator, so it earns through the unstake without interruption, and claimable is untouched. Pending stake (placed since the last tick) cannot be unstaked until it turns active. Every stake() resets unlockAt for the whole position (MIN_LOCK, 6h), so a top-up blocks unstaking for 6h.
The staker keys positions by address only. The auto-stake vault (section 16) holds its own position under its contract address, so one wallet can hold a direct stake and an auto-stake share at the same time; the two have independent locks, cooldowns and rewards.
9VaultBond
bond(ohmIn, minTokenOut) pulls OHM, routes it through donateFloor, and mints OHMV through VaultMinter, vesting linearly over 24 hours:
The gate defends live backing
In SEMI v1 the gate was ohmIn / mark. After SEMI's September drawdown the mark sat 2.8× above live backing, so every bond was capped far below the market and bonding was dead until a 137% recovery. A bond only needs to leave backing per token where it was to be non-dilutive, and ohmIn / live backing is exactly that. The marks now gate emissions only. The 1 ppm shave keeps v4's liquidity rounding from ever tipping a capped bond a fraction under par; the fuzz test checks it.
Quote, partial fills, skim
The price is the lower of the 10-minute TWAP and spot, so dumping right before a bond does not improve it. The quote and the gate are fixed before the donation runs. The hook places what its 8% bound allows and refunds the rest in the same transaction; OHMV is minted only against OHM that reached the floor. The contract has no bond size limit of its own: with the pool's full-range liquidity, one bond fills in full up to about 8.7% of the OHM in the pool, where the 8% price bound stops it. The 7% max shown on the site is the site's own limit, not the contract's. minTokenOut covers the gate and a partial fill together: if the OHMV minted after any refund comes in below it, the whole bond reverts and no OHM moves. The sold half pays the pool's 5%, about 2.56% of the bond, which stays in the hook as fees. At the next harvest that OHM splits 40/60 like any other fee, so about 1.5% of every bonded OHM goes to the operations wallet and about 1% back into the floor. quote() shows the result net of that skim.
A new bond pays out whatever has vested, then puts the unvested remainder and the new amount on a fresh 24-hour schedule. There is no per-slot bond cap.
10OHM and its price
OHM v2 (0x64aa…f1D5, 9 decimals) is the token of Olympus, on Ethereum since 2021. Olympus backs it with a treasury the protocol owns and runs Range Bound Stability: standing treasury orders that buy OHM below a lower band and sell above an upper band. Holders can also borrow against staked OHM through Cooler Loans instead of selling. The point for OHMVAULT is that OHM has a defended price; a floor made of it moves less with the market than a floor made of ETH would. Bands, backing and treasury figures change and are published by Olympus; check them there.
The OHM in OHMVAULT's pool is plain OHM, not staked gOHM, and earns no Olympus yield. It grows from fees and bonds.
OhmUsdOracle
OHM/ETH has a 24-hour heartbeat and a 2% deviation trigger; ETH/USD has a 1-hour heartbeat and 0.5%. The limits sit two hours past the OHM heartbeat and one hour past the ETH one. A reading that fails any check returns ok = false and the Ratchet skips the mint; it never reverts the poke.
11What changed from SEMI v1
| Change | Why |
|---|---|
| Pair asset is OHM (9 dp) on Ethereum instead of MU on Robinhood Chain | A defended collateral on the main chain, with deep OHM/ETH liquidity for routing in and out. |
| Harvest 40% floor / 60% operations; no burn slice | v1's 20% GIWA burn went to the operations wallet anyway, and there is no GIWA on Ethereum. |
| The hook's own swaps pay 5% | v4 skips hook callbacks for the hook's swaps, so v1's compounds and bonds traded free. Now every leg pays. |
| Hook's own swaps bounded to ~8% (v1: 2× / ½×) | No sub-8% move can be sandwiched profitably through two 5% skims. |
Reserves sampled after every swap as well as before (flags 0x30C8) | v1 never saw a window's closing reserve. |
| Fixed 6 h slots from launch | v1 let the poker choose the window. |
| USD mark next to the OHM mark | Blocks the "collateral crashes, arbitrage OHM flows in, mint" path. |
| Trailing mark (20%) | v1 paid one slot per pump. Replayed on v1's tape: 44 slots and 29.5M instead of 27 and 12.1M. |
| Backing scaled 10³⁶ | OHM's 9 decimals made 10¹⁸ coarse. |
| Passive stakers activate at the tick | v1 banked gains when holders had staked and walked away. |
| 6 h unstake cooldown | One slot to enter, one to leave. |
| Bond gate on live backing | v1's mark gate shut bonding for weeks after a drawdown. |
| Bond quote = min(TWAP, spot); partial fills with refund | A dump before bonding doesn't help; large bonds are safe under the 8% bound. |
| Launch curve 80% flat 10 s → 50% at 30 s → 5% at 60 s (v1: quadratic 50%→5%) | APT's curve: a first-block snipe pays 80%. |
| One site router, VaultSwap: 1% on sells only (v1: tiered buy/sell router fees up to 30% for whales) | Simple and stated up front; trading the pool directly avoids it. |
| Removed: FloorWall, launchpad, The Fab, SemiScape | Not part of this launch. A launchpad came back on 9 Oct as Kennel Pad (section 18). |
Everything else is v1's logic: the 5% skim and launch minute, the protocol-only reserve reading, the minimum over the window, α = ¾, the 2% cap, banking when nobody is staked, the 20% discount and 24-hour vest, 10M supply all in the pool, one-shot setters and no owner.
VaultSwap, the site router
The Uniswap app only routes a hooked v4 pool once Uniswap allowlists the hook, so the site ships its own router. VaultSwap has no owner and holds nothing between calls. buyWithEth wraps ETH, swaps it for OHM on the Uniswap v3 OHM/WETH 0.3% pool (0x8805…8598, about $5M deep), then swaps the OHM for OHMV in our pool, in one transaction with no approval. sellForEth runs the same path backwards. buyWithOhm and sellForOhm skip the ETH leg. Every call takes a minimum out, a deadline and a recipient address to, so the output can go to any address.
Buys through VaultSwap pay no router fee: an OHM buy pays only the pool's 5%, and an ETH buy or sell also pays the v3 pool's 0.3% on the ETH leg. Sells pay SELL_FEE_BPS = 100, 1% of the OHM they return, straight to the operations wallet; with the pool's 5% on the OHMV in, a sell for OHM costs about 5.95% before price impact. The v3 leg has no price limit of its own; the one minimum out covers both legs. Bots and other routers that trade the pool directly pay only the 5%.
Where it trades
Before launch every user path was run on Ethereum mainnet against the same contracts: buy and sell for ETH and for OHM through VaultSwap, bond and claim, stake, and harvest. Every invariant held: backing per token did not fall across a bond, price stayed at or above backing, and supply equalled genesis plus the bond mint. The pool was also traded through outside tools:
| Route | Result | Notes |
|---|---|---|
| ohmvault.xyz (VaultSwap) | buy, sell, bond, stake | MetaMask in the browser; one transaction from ETH |
| GMGN | buy and sell | trades the pool directly; dollar figures correct |
| BasedBot | buy | reads liquidity and the 5% / 5% fee; flags the minter (below) |
| Axiom | listed | reserves ~0.014 ETH for gas before sending at default settings |
| DexScreener | chart and trades | may show $0 liquidity, FDV and volume ("unknown liquidity") for a hooked v4 pool |
| Uniswap app | no route | hooks with beforeSwapReturnsDelta need Uniswap's manual allowlisting before the app routes them |
Trades confirmed in one or two blocks (4 to 20 seconds). Automated scanners report the token as "mintable" or "Unlimited Minting": they see a mint function and a minter address, not the two gates behind that address (sections 6 and 9). Since Serialized's 11 Oct re-audit, BasedBot shows the token as Safe (section 13).
A gas note from the live run
The hook writes a new TWAP observation the first time the pool is touched in a block (section 5). A gas estimate taken against the latest block, where that slot is already written, can therefore come in 20–40k gas short for a transaction that lands in the next block. One sell ran out of gas this way in testing. The site, the keeper and the scripts now set the limit to the estimate plus 35%. Wallets that add no margin of their own are the ones this protects.
12Read paths
One call to VaultLens.snapshot() returns: supply, OHM and OHMV in the protocol positions, both marks, live backing, spot and TWAP, Chainlink's USD price and whether it's fresh, lifetime emissions, the current and last settled slot, whether a poke is due, the current fee, and what 1 OHM would bond for right now with the gate flag. Anyone can re-derive every pool, mark, emission and bond number the site shows from that one read; staking totals come from VaultStaker.totalActive(), totalPending() and totalCooling(). VaultStaker.earned(u), stakeOf(u) (active(u) plus pending(u)), unlockAt(u) (when unstake opens, 6 hours after the last stake), cooling(u) and coolingEnds(u) cover a single account; VaultBond.claimable(u) and locked(u) cover its bond. For Kennel Pad (section 18), KennelPad.page(start, n) lists launches newest first and infoOf(token) gives a meme's hook, Kennel, creator and launch time. On a meme's hook, currentFeeBps() is the tax now, reserves() the meme and OHMV in the locked position, and creatorOwed / platformOwed the unclaimed fees. On its Kennel, earned(u), info(u), ticketIn(u), chanceOf(u) and jackpotInfo() cover one staker and the draw. For auto-stake, VaultAutoStake.positionOf(u) gives shares, their OHMV value, deposits still waiting for a price, and any exit with whether it is ready, and pricePerShare() gives the current rate. For loans, loanOf(u) on VaultLoans or CerbLoans gives collateral, debt, the borrow limit, the liquidation line and the loan's current ratio. lenderOf(u) gives a lender's shares and their value, and borrowRateBps(), supplyRateBps() and liquidatable(u) cover the rest. CerbLoans.oracle() shows its price record.
13Trust model and risks
What nobody can do
Remove the locked liquidity. Mint outside the two gates. Change the fee, the split, α, the cap, the trail, the discount, the vest, the 6-hour unstake lock or the cooldown, or the router's 1% sell fee and where it goes. Pause anything. Point any contract somewhere else. Each of these needs a function that doesn't exist or a one-shot setter that has already been used.
What the team controls
The operations wallet. Its address is fixed in the OHMV hook, the router, the Cerberus hook and Kennel Pad. It receives about 60% of all OHM the hook harvests: the trading skims (about 3% of pool volume) and the skim on the sold half of every bond and donation (about 1.55% of the OHM bonded). It also receives the 1% VaultSwap fee on sells through the site, the Cerberus launch-window premium (section 17), and on every Kennel Pad meme 0.5% of each trade plus all launch-window tax above 4% (section 18). This is the team's income and pays for development, infrastructure and promotion. Nothing in the mechanics depends on it.
Risks
- OHM. Olympus is governed by its DAO, which can mint OHM, move the bands and change the treasury. OHMVAULT has no say. If OHM fails, the floor under OHMV fails with it.
- Oracle. Chainlink can go stale or be wrong. Stale halts minting; wrong-but-fresh could let one slot mint against a bad dollar reading, still bounded by the OHM mark and the 2% cap. A price that is too high at a mint, real or wrong, also lifts the dollar mark for good: if OHM then falls in dollars, emissions stop until dollar backing per token climbs back above it.
- More emissions. The trailing mark pays more and for longer. Stakers who sell their rewards add selling pressure during the run. The run is bounded by the 20% trail, never by the size of a pump.
- Bond supply. On SEMI, bonds created 73% of supply. With the gate on live backing bonding stays open in drawdowns, so supply can grow faster than in v1. Each bond is non-dilutive to backing per token by construction; it can still dilute price.
- Liquidity and routing. The pool starts at $2,000 of OHM. The Uniswap app routes hooked pools only once Uniswap allowlists the hook. Until then buyers come through the site, GMGN, BasedBot, Axiom and other tools that trade v4 pools directly (section 11).
- Code. 39 tests, fuzzing, a fork rehearsal and an automated audit are not a full manual audit. v1's logic ran for two months on Robinhood Chain with real money. The changed parts have run on Ethereum only since 7 October 2026.
- External audit. Serialized (serializedaudit.io) ran its AI audit engine and then a priority re-audit of the token, VaultMinter, VaultRatchet, VaultBond, VaultLauncher and VaultHook against their verified source and the on-chain state. Verdict on 11 Oct 2026: Safe, "3 warnings worth knowing · nothing scam-level":
- Mint Authority (token): only the frozen Ratchet and Bond can mint, through VaultMinter; staking emissions are capped at 2% of supply per 6 hours and dilute holders who do not stake.
- Fee Controls (pool): the 80% → 5% launch fee of the first 60 seconds after launch; it does not affect the verdict.
- Trade Limits (pool): the temporary trade-size cap of the same first 60 seconds; it does not affect the verdict.
- Keeper.
poke()andharvest()need someone to send them, and so do the add-ons' upkeep calls. A keeper wallet sends them privately through Flashbots Protect. It pokes within a minute or two of each 6-hour boundary. It harvests once the hook holds about 2 OHM of fees. It syncs auto-stake after every tick and whenever an exit can be unstaked or paid. Each minute it checks every OHMV and CERB loan and liquidates any that qualify. It sends the loans' interest share on withpushFloor()andpushKennel(). It takes a CerbLoans price sample about every 7 minutes while anything is lent or borrowed. It syncs the Cerberus Kennel and runs its daily jackpot. About once an hour, up to 10 at a time, it refreshes Cerberus Kennel stakes of at least 100,000 CERB (0.01% of the supply) so their new boosts and jackpot tickets switch on; smaller stakes need a claim or arefresh(user)call. Pad Kennels get the same jobs on a slower, capped schedule (section 18). Every one of these calls is permissionless, so a dead keeper never locks anything. Emissions wait, and a gap of several slots is settled by one poke with at most one 2% mint.
14Address registry
| Name | Address |
|---|---|
| OHM v2 (Olympus) | 0x64aa3364F17a4D01c6f1751Fd97C2BD3D7e7f1D5 |
| Uniswap v4 PoolManager | 0x000000000004444c5dc75cB358380D2e3dE08A90 |
| Chainlink OHM/ETH | 0x9a72298ae3886221820B1c878d12D872087D3a23 |
| Chainlink ETH/USD | 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 |
| Uniswap v3 OHM/WETH 0.3% (router ETH leg) | 0x88051B0eea095007D3bEf21aB287Be961f3d8598 |
| CREATE2 deployer | 0x4e59b44847b379578588920cA78FbF26c0B4956C |
| Operations wallet (TREASURY) | 0x63888d25934504CfcFb83aCc3f9af90B3e30dDce |
| VaultToken (OHMV) | 0xd520e9f57ad0076b470af4d2eb1577b60604a233 |
| Deployer (holds 0 OHMV after launch) | 0x0D459F23F801C1833B393877D3835f4992D310aC |
| VaultMinter (the only minter, frozen) | 0xf5e2b42fb5baf508f27e8318c9be29e98654edb9 |
| VaultHook (flags 0x30C8) | 0x450c7eb41cB3c98690F8411DFe0fBe690B22f0c8 |
| VaultLauncher (holds the LP, no withdraw) | 0xa3074eff30e6a635c4668c69a5f9ba9caeeeb1ab |
| VaultRatchet | 0xa8307b5900ab2d446d5b17dd0f95b95de60417f0 |
| VaultStaker | 0x52e1cecde94440c41090c5bab3081ca0a00105a5 |
| VaultBond | 0x9f2e0600d43de1de6ec75991f18e0852cc37d3d7 |
| OhmUsdOracle | 0x51f1c7d2edb70aa64a6367b53d946d5db77d4ff6 |
| VaultLens | 0x91f2820d253ac19a28aab7826b4bd617233f5096 |
| VaultSwap (site router) | 0x79822e1c5a43666c31cae3f34c19ac4347079532 |
| VaultBoxFactory (private payouts, 8 Oct) | 0xe6d8f90a74e015d24079e27f773c5a2f0ce9a550 |
| VaultBox (implementation every box clones) | 0x8333aD8E9D87285b09fdA08bce12b0e3618E40dB |
| VaultBoxFactory2 (gasless boxes and box keys, 8 Oct) | 0x917d1a8e55c2c9f188482c060c9bb47e47d46b86 |
| VaultBox2 (implementation every v2 box clones) | 0xE34A74705e5edD16fCD42455cf11D9A176BCc14A |
| VaultAutoStake (auto-compounding staking) | 0xadfd79ad398b49e3ae61dec6aaf038a0370f3b7b |
| RAILGUN smart wallet (Ethereum, external) | 0xFA7093CDD9EE6932B4eb2c9e1cde7CE00B1FA4b9 |
| OHMV/OHM v4 pool id (opened 7 Oct 20:00 UTC) | 0xb20736912eacde8dd0bac06ec67ab7f150a20afc4a34632f56dede4c04ca20ee |
| VaultLoans (OHM loans against OHMV, 8 Oct) | 0x452d6beda02ca6d31cb8e508fabc83ab55661545 |
| CerbToken ($CERBERUS, 9 Oct) | 0x333DaeFb87f4cC7F73fd56fDdB2087a690DaE333 |
| CerbHook (4% tax in OHMV) | 0x61720095E793EEC048E714440a15eCF9A174F8CC |
| CerbKennel (the Kennel) | 0xba72c3ce5fac4fa2b2eeef4eed30800b7ba3e8a6 |
| CerbLauncher (holds the CERB LP, no withdraw) | 0xb9c1a0898d50a02137631f6da467d4dea24a1073 |
| CerbSwap (Cerberus router) | 0x3c69392e3cc87cf6ea3470acd539db7026bedaae |
| CERB/OHMV v4 pool id | 0x9768269d0bceb8d0907c4e458f274b9b45a2c68ace443d967fd76f782b002012 |
| KennelPad (launchpad, 9 Oct) | 0xa019aa16f2dc2f1f2c9c47cbb462663211557af0 |
| PadSwap (pad router) | 0x6417c149b654ae26491682c4887106ff6f1a2012 |
| PadTokenDeployer | 0xB19CB3fc4b09b284826eFe003bcDDef227018f0b |
| PadHookDeployer | 0x81034022f4Ce1563036fFA81321414573D0796a2 |
| CerbLoans (OHMV loans against CERB, 10 Oct) | 0xE9498D5c14F107B96CD3DF3904460edc03c02c2F |
All 26 contracts above are deployed, wired and verified on Etherscan with exact-match source: the ten core contracts (7 Oct), the VaultBox and VaultBox2 factories and implementations, VaultAutoStake and VaultLoans (8 Oct), the five Cerberus contracts and the four Kennel Pad contracts (9 Oct), and CerbLoans (10 Oct). Every one-shot setter has been used and now reverts. None of them has an admin or an upgrade path; the only owners anywhere are users of their own private boxes. scripts/verify-all.mjs rebuilds every core constructor argument from the deployment file.
15Private payouts (VaultBox, added 8 Oct 2026)
VaultBond and VaultStaker are frozen and pay msg.sender. VaultBox is an add-on layer that gives every user a personal contract which is msg.sender for them: it bonds and stakes with the owner's tokens, owns the resulting vest and stake, and pays out wherever the owner says.
- One box per owner.
VaultBoxFactory.getOrCreate()deploys an EIP-1167 clone of VaultBox at a CREATE2 address derived from the owner (predict(owner)shows it before it exists).factory.bond(ohmIn, minOut)andfactory.stake(amount)move the caller's own OHM or OHMV into the box and act through it. The factory can only ever pull frommsg.sender. - Same rules. A box bonds and stakes like a wallet: 24h vest, pending until the next tick, 6h unstake lock, 6h cooldown. Boxes never share a position.
- Pay out anywhere.
payout(to, amount)first collects whatever is ready (unlocked bond OHMV, staking rewards, stake that has finished its 6h cooldown) into the box, then sendsamountOHMV to any address, or everything in the box ifamountis 0.payoutPrivate(ShieldRequest)collects the same way, then hands exactly the request's value to the RAILGUN smart wallet'sshield(), so it lands in a 0zk private balance. Anything above that value stays in the box. Both revert if the box holds less than the amount. Active stake is never paid out: it has to be unstaked and cooled first. The shield note is built in the browser with RAILGUN's own engine code from the recipient's 0zk address; only that RAILGUN wallet can decrypt it. RAILGUN keeps its 0.25% shield fee out of that value. - Owner calls. In v1 only the owner's wallet can act on the box. Besides the payouts it has
unstake(amount), which starts the 6h cooldown,collect(), which pulls everything that is ready into the box without paying out, andrestake(amount), which stakes OHMV already in the box (compounding without a payout). - What is and isn't hidden. The box is public and linked to its owner: anyone can see who bonded or staked, and how much. A payout to a public address is fully visible, recipient included. A private payout shows on chain how much was shielded and when; what stays private is which 0zk wallet received it and where the OHMV goes after that. RAILGUN's Private Proofs of Innocence delay a fresh shield by about an hour.
- Trust. No protocol owner, no admin, no upgrades: each box answers only to its own owner. The implementation's owner slot is burned so only clones work. The owner can move any stray ERC-20 token or ETH out of their box with
sweep(erc20, to, amount)andsweepETH(to). VaultBox has 10 unit tests plus a mainnet-fork test against the live contracts and the real RAILGUN proxy. The code is new and unaudited.
Version 2: gasless boxes and private entry (8 Oct)
VaultBox v1 needs the owner's wallet for every action, so the box stays linked to that wallet and the wallet needs ETH. VaultBox2 (factory 0x917d1a8e55c2c9f188482c060c9bb47e47d46b86) removes both requirements. v1 stays on chain unchanged; the site now uses v2.
- Box keys. The owner can be any address, including a key generated in the browser that never holds ETH and has never touched a wallet.
predictFor(owner, salt)gives the box address before it exists, andcreateFor(owner, salt)lets anyone deploy it for that owner. The address is derived from both the owner and the salt, so nobody can deploy a box there for anyone else, and tokens sent to it early wait for that owner. One owner can have many boxes, one per salt;isBox(addr)says whether an address is a real box. Only the owner, an operator the owner sets, or whoever submits a valid owner signature can act on it. The factory's wallet-modebondandstakealso act on the salt-0 box (predict(owner)) of the wallet that calls them, with that wallet's own tokens. - Private entry. Tokens can be sent to the predicted box address first, for example by unshielding OHM or OHMV from RAILGUN.
bondHeld(minOut)bonds every OHM the box holds andstakeHeld()stakes every OHMV it holds, so no approval or wallet transfer is involved. If a bond is only partly filled, the unplaced OHM stays in the box until the next bond orrefundOhm(), which sends all OHM in the box to the owner. In wallet mode,factory.bond(ohmIn, minOut)andfactory.stake(amount)move the caller's tokens into its salt-0 box, then bond or stake everything the box holds, not just the amount sent. OHMV already in the box (claimed rewards, unlocked bond OHMV) is staked with it, and the 6h unstake lock restarts on the whole stake.factory.bondthen callsrefundOhm(), so a partial-fill refund goes straight back to the wallet as in v1. - Signed actions. Bonding, staking, unstaking and both payouts have an EIP-712 twin (
bondHeldBySig,stakeHeldBySig,unstakeBySig,payoutBySig,payoutPrivateBySig; domain "OHMVAULT Box", version "2", one nonce sequence per box, a deadline on every message). Anyone can submit a valid signature. The submitter is paid the tip the owner signed, in the token being moved; unstaking carries no tip. The tip is paid first, so a signed bond bonds whatever OHM the box holds when it is sent, minus the tip, withminOutas the only guard, and a signed payout of 0 sends everything left after the tip. Signatures are used in nonce order: once one goes through, any other signature with the same nonce is dead. There is no cancel call; an unsent signature lapses at its deadline.collect,compound,refundOhm,setOperator, the ownership transfer andexechave no signed version, so the owner (or the operator, for the first three) must send them and pay the gas. A box key that never holds ETH can therefore not rotate ownership or set an operator, andstakeHeldBySigonly stakes what is already in the box; it does not claim rewards first. - The relayer. ohmvault.xyz runs one at
/api/relay. It checks the signature, the nonce and a simulation before sending, and asks for the larger of 0.5% of the amount moved or the gas cost plus 15%, never more than 20%. That limit is the relayer's policy: the contract pays whatever tip the owner signed and sets no maximum, so check the tip before signing. The relayer cannot change any signed field. The worst it can do is not send, or send late, any time up to the deadline; for a bond that means a later price, bounded only byminOut. Keep deadlines short. Any other relayer, or the owner, can submit the same signature. - Owner tools. A two-step ownership transfer (
transferOwnership(to), thenacceptOwnership()from the new key;transferOwnership(0)cancels), an optional operator, andexecso the owner can recover anything sent to the box by mistake. The operator may bond, stake, unstake, collect, compound and send stray OHM back to the owner (refundOhm), but never pay out or callexec, and it cannot sign messages. It cannot take funds, but each stake it makes restarts the 6h unstake lock, so only set a key you control. A transfer does not reset the operator, so the new owner should callsetOperator. Signatures from the old owner stop working. The factory still records the box as the old wallet's salt-0 box, so after a transfer do not bond or stake from the old wallet through the factory: the tokens would go into a box it no longer owns. - Trust. No admin and no upgrades. 14 unit tests plus a mainnet-fork test against the live contracts and the real RAILGUN proxy, including a fully relayed bond and a relayed shield. New contracts: start small.
16Auto-stake and loans (added 8 Oct 2026)
Auto-stake (8 Oct)
VaultAutoStake (0xadfd79ad398b49e3ae61dec6aaf038a0370f3b7b) holds one staker position for everyone in it and restakes every epoch's reward, so the rewards earn too. It plays by the staker's own rules: a deposit is staked at once and is pending until the next tick, and its share of the vault is priced only after that tick, so a deposit made just before a paying tick takes nothing from it. Leaving sets the OHMV aside at the current share price; it is unstaked once the staker's 6-hour lock allows and paid after the 6-hour cooldown, or, if the rewards claimed while it waits cover every exit in its batch, paid from them with no unstake and no cooldown. Rewards that cover only part of the batch shrink the unstake. While an exit waits for the lock, new deposits and claimed rewards are held unstaked, because staking them would restart the lock. Held deposits earn nothing, are staked once the exit is unstaked, and are priced at the tick after that; until then cancelDeposit() returns them in full. Shares cannot be transferred, so OHMV exposure cannot be traded around the pool's 5% fee. No owner, no admin, no fee; sync() is permissionless and the keeper calls it after every tick. Pricing needs one call between ticks: any deposit, exit, claim or sync() does it. If no call lands between the tick that activates a deposit and the next tick, the deposit is priced after that next tick's reward, and its part of that reward goes to the existing shares.
Partial exits. requestExit(shares) takes any number of shares. The shares kept stay in the vault and keep compounding; the shares exited are priced at that moment (convertToAssets), stop earning, are unstaked as soon as the vault's 6h lock allows, cool for 6h, and are then paid out with claimExit(). Only one exit per address can be open at a time. More shares can be added to it while its batch is still collecting requests. Once that batch has been unstaked, a new requestExit reverts ("an earlier exit is still unstaking") until the first exit is claimable after its cooldown; from then on a new requestExit pays out the first exit before booking the new one.
Loans (8 Oct)
VaultLoans lets anyone borrow OHM against OHMV and lend OHM for the interest. Collateral is valued at its backing, never its market price: backing per OHMV is the OHM in the protocol-owned pool divided by the OHMV supply. That liquidity is full range and has no withdraw function, so selling any amount of OHMV into it returns at least 95% of the live backing per token.
- Borrow up to 85% of the collateral's backing, measured on the lower of the live pool and its ~10-minute average, so a pump can't stretch the limit.
- Using a loan. One loan per address.
open(collateral, amount)locks OHMV and borrows in one call;close()repays everything and returns all the collateral. A loan is either 0 or at least 1 OHM, so a partial repayment must leave at least 1 OHM or clear it. Anyone can repay anyone's loan withrepay(borrower, amount). Collateral can be taken out while the loan stays at or under 85% of backing, on the same lower measure. - Liquidation above 92%, measured on the 10-minute average. It is blocked while the pool's live OHM reserve is more than 3% below its level at the average price (about a 6% fall in OHMV's price), so a fresh dump cannot trigger it; a live pool above the average never blocks. A loan whose debt exceeds the collateral's live backing can be liquidated at once. A liquidation sells only enough collateral into the pool to bring the loan back to 80%, valued on the lower of live and average backing after the sale's own effect, plus 0.5% headroom (
liquidationSize(borrower)quotes it). It pays the caller 1% of the OHM the sale returns, and leaves the rest locked for the borrower. If 80% can't be reached, everything is sold: the debt is paid first, then the caller, then the borrower. - Interest follows use of the lenders' OHM in straight lines: 3% APR when idle up to 10% at 80% use, then up to 60% at 100%. Lenders earn the borrow rate × use × 90%. The other 10% is owed to the floor as it accrues, ahead of lenders, and anyone can send it into the locked floor with
pushFloor(), which callsdonateFloor. New borrowing is refused once use would pass 95%, or once all debt would pass 25% of the protocol-owned OHM in the pool (on the lower of live and average). Interest, withdrawals and pool moves can take both higher later; the limits only block new loans. No due date. - Lenders' risk. Lenders lose only if most of the pool's OHM leaves in one trade, faster than a keeper can act, with no market premium left over backing. Even then the sale returns at least 95% of the remaining live backing, and any shortfall is written off, recorded on-chain as
badDebt, and borne by all lenders in proportion to their shares. Lenders can withdraw only OHM that is not lent out: at high use a withdrawal reverts ("lent out: wait for repayments") until borrowers repay or are liquidated. Loans have no due date; the rate rising to 60% at full use is what pushes borrowers to repay. - Borrowers' risk. Backing per OHMV is divided by the whole supply, so each staking mint (at most 2% of supply per tick) lowers your collateral's backing by its share at once. OHMV locked as collateral is not staked and earns nothing. Interest compounds into the debt, so a loan left alone reaches 92% in time unless backing grows faster.
There is no owner, no admin and no fee switch; every term is a constant in the bytecode. 13 tests cover it, including a 1,500-run fuzz test in which lenders never lose and the books always balance through random borrowing, repaying, dumps and liquidations. The keeper checks every loan each minute and liquidates over Flashbots Protect. Live page: /loans.
17Cerberus ($CERBERUS) and the Kennel (added 9 Oct 2026)
Cerberus is a meme token that lives beside OHMV and feeds it. Every CERB trade pays a 4% tax in OHMV, and all of it is staked in OHMV staking by a contract that can never unstake it: the Kennel. The OHMV that stake earns is paid to people who stake CERB, 90% as a 6-hour stream and 10% as a daily jackpot. The team takes none of the 4%. Its only income from the Cerberus contracts is the anti-snipe premium in the first five minutes, described below. Selling CERB for ETH through the site also ends in an OHMV sale through VaultSwap, which pays its usual 1% of the OHM to the operations wallet (section 11).
- Token.
CerbToken, nameohmvault.xyz/cerberus, symbolCERBERUS, 1,000,000,000 supply, 18 decimals. Plain ERC20: no owner, no mint, no tax in the token, so wallet-to-wallet transfers move the full amount. Website, X, Telegram and an SVG logo URL are stored in the contract. Its address,0x333DaeFb87f4cC7F73fd56fDdB2087a690DaE333, is a vanity address deployed through the standard CREATE2 deployer (section 14). - Pool. One CERB/OHMV Uniswap v4 pool, full range, seeded with the whole CERB supply and an OHMV seed by
CerbLauncher, which has no withdraw function: the liquidity is locked by construction. Only the launcher may add liquidity, so nobody can open side positions that would trade CERB untaxed. The pool's LP fee is 0, so the 4% tax is the only fee in it and the locked liquidity earns nothing. - The tax.
CerbHooktakes 4% of every trade, always in OHMV, inside the swap: from the OHMV going in on a buy, from the OHMV coming out on a sell. Exact-in and exact-out swaps pay the same share of the OHMV that changes hands. Nothing is queued or sold later, so there is no tax pile to front-run. The 4% part is sent to the Kennel in the same swap. - Launch curve. 95% for the first 10 seconds, falling linearly to 70% at 60 seconds, 30% at 120 seconds and 4% at 300 seconds, then 4% forever. During those five minutes the Kennel still receives 4% of each trade, and the tax above 4% goes to the team's ops wallet. Unlike OHMV's launch, there is no cap on swap size during the window.
Why this curve
The shape was chosen by replaying the first five minutes of six earlier launches (OHMV on Ethereum; APT, MUCAT, SEMI, MUDOG and TROJAN on Robinhood Chain) under candidate curves, trade by trade, and counting only buyers who were still in profit at the one-hour price after paying the tax. Three things came out of that:
- Early buyers do not care about the tax. On every launch with real demand, the wallets that bought in the first 30 seconds at 70–95% tax still ended the hour in profit, often several times over. A high opening tax does not stop them; it only decides who gets the money.
- On Ethereum the volume arrives at two to ten minutes, not in the first 30 seconds. OHMV had no trade at all in its first 10 seconds and most of its first-hour fees came after the two-minute mark. A curve that is already at 4% by 120 seconds gives that volume away.
- Stretching the tail pays. Keeping the first minute as it was (95% → 70%) and sliding the end from 120 to 300 seconds collected about 41% more from OHMV's own first five minutes and about 30% more from APT's, without pricing out any buyer who was profitable at one hour. A steeper alternative (95% → 60% → 30% at 120 s) lost 29% on OHMV and was dropped.
Who the window pays: the Kennel gets exactly 4% of every trade from the first second; the rest of the window tax is the price of sniping, and it goes to the team as the one piece of Cerberus income it has. After five minutes the Cerberus contracts pay the team nothing. The one fee left is VaultSwap's 1% when CERB is sold for ETH through the site; selling for OHMV or trading the pool directly avoids it.
The Kennel
- In. Tax OHMV arrives with every trade.
sync()stakes the Kennel's idle OHMV intoVaultStakeronce at least 100 OHMV has built up (MIN_STAKE); until then it waits in the Kennel, earning nothing. The Kennel has no unstake path, so that OHMV is off the market for good. - Out. The Kennel is an ordinary staker: when the Ratchet mints, it earns its share like anyone else.
sync()claims those emissions and streams them evenly over the next 6 hours (one OHMV epoch) to CERB stakers. A new payout is added to whatever is still streaming and the 6 hours restart. While nobody stakes CERB the stream pauses and the queue waits. 10% of every payout goes to the jackpot instead (below). Anyone can callsync(), and every deposit, withdrawal, claim and refresh runs it first. The keeper also calls it. It does so within about a minute once 50 OHMV of emissions are waiting, so each tick's payout starts streaming right away, or once 2,000 OHMV of tax is waiting. In the last 10 minutes before each tick it stakes any waiting tax of 100 OHMV or more, so that tax is activated at that tick instead of waiting a whole extra slot. And it syncs at least every 6 hours while anything is waiting. - Daily jackpot. 10% of every emissions payout goes into a pot.
draw()commits to a block two ahead once 24 hours have passed since the last pot was paid or forfeited (so the time of day drifts) and there is a pot and at least one ticket holder; a second call after that block reads its hash and pays the whole pot to one ticket holder, chosen with probability equal to their share of all ticket weight (boost included). Weights live in a Fenwick tree, so the pick is O(log n). Tickets: a stake holds tickets once it is a day old and at least 0.01% of the supply (100,000 CERB), so a fly-by stake or a swarm of dust wallets cannot enter the draw; tickets count once the stake is refreshed (deposit, claim, or anyone callingrefresh). The keeper refreshes such stakes about once an hour (up to 10 per pass, so it can take longer when more are waiting) and again just before each draw, so tickets usually switch on within about an hour of a stake becoming eligible; a claim switches them on at once. The day runs on the same clock as the boost: a top-up moves it to the amount-weighted average, and any withdrawal, even a partial one, restarts it, so the stake leaves the draw for the next 24 hours. At most 8,192 stakes hold tickets at once; when every slot is taken, a new stake still earns the stream but gets no ticket until a slot frees up. Freeze: while a draw is committed and its block has not passed, deposits, withdrawals, claims and refreshes wait those two blocks, so nobody can enter or leave around the hash (emergencyWithdrawstill works and simply forfeits that draw). No re-roll: a hash not read within 256 blocks is forfeited and the pot waits for the next day; the keeper settles every draw within a minute. A block proposer could in principle bias one draw by withholding a block; at these pot sizes that costs more than it wins, and a VRF can be added if pots grow. - Weight. A CERB staker's share is CERB staked × boost. Boost by stake age: 1× under 2 days, 1.5× from 2 days, 2× from 5 days, 2.5× from 7 days. A top-up moves the age to the amount-weighted average, with old stakes counted at no more than 7 days, so a small old stake cannot lift a large new one to 2.5×. A withdraw restarts the clock on what stays. A new tier counts only once the stake is refreshed. Until then the stake earns at its old boost. A claim refreshes it at once, and so does anyone calling
refresh(user). For stakes of at least 100,000 CERB (the jackpot minimum) the keeper also refreshes about once an hour, so their new boost usually switches on within an hour. Smaller stakes switch on only when they claim or someone callsrefresh(user). On a Kennel Pad meme the keeper's refreshes are rarer (section 18). - No lock. CERB can be withdrawn at any time, except during a draw's two-block freeze (above).
emergencyWithdraw()works even then: it returns the whole stake without touching OHMV staking at all, in case that path ever reverted. Rewards credited up to the lastsync()stay claimable; what streamed since then goes to the remaining stakers. - Why the stream. OHMV staking pays in one lump every 6 hours. Paid out at once, a wallet could stake a minute before the lump and leave a minute after. Streamed over 6 hours, a one-minute staker gets one minute's worth.
- Views. A stake can be checked on the Kennel directly, without the site:
earned(user),info(user)(stake, boost in use, the boost a refresh would give, seconds to the next tier),ticketIn(user),chanceOf(user),jackpotInfo(),principal(),uncollected()anddripPerDay().
What it does for OHMV
- Float. Every CERB trade moves 4% of its OHMV value into a stake that can never be sold. The share of all OHMV locked this way is shown live on the Stats tab.
- Backing and volume. CERB bought or sold with ETH routes through OHMV's own pool, which pays OHMV's 5% fee there (2% into OHMV's locked floor).
- Router. The site trades through
CerbSwap:buyWithEth(ETH → OHM → OHMV through VaultSwap → CERB),buyWithOhmv,sellForOhmvandsellForEth(the ETH path backwards). Every route is exact-in, with one minimum out for the whole trip and a deadline. It adds no fee of its own, has no owner and holds nothing between calls. The ETH routes also pay the 0.3% Uniswap v3 OHM/WETH pool fee and OHMV's 5%, andsellForEthpays VaultSwap's 1% sell fee: about 9% in fees on an ETH buy and about 10% on an ETH sale, against 4% for OHMV ↔ CERB. - Trade-off. The Kennel's stake shares in every future OHMV mint, so other OHMV stakers' share of each mint gets smaller as the Kennel grows. Mint size does not depend on how much is staked.
- Reward rate. The site shows the Kennel's APR at OHMV's emission pace since launch: all OHMV minted to stakers ÷ days since genesis × the Kennel's share of everything staked, valued against the CERB staked. It is an APR, not an APY: rewards are paid in OHMV and do not compound into CERB. It can be zero while OHMV staking is paused.
Trust
No owner, no admin, no fee switch: the 4% rate, the curve and its split, the boost tiers, the 6-hour stream and the 10% jackpot share are constants in the bytecode. setLauncher on the hook is one-shot. The contracts are covered by 153 tests across eight suites, including regression tests for every issue found in two pre-launch review rounds (exact-out swaps under-paying the launch tax, untaxed side liquidity, boost banking through an old stake, jackpot re-rolls by withholding a draw, fly-by staking around a draw, and dust-wallet ticket slots), all fixed before deploy. The full set was rehearsed on a fork of mainnet against the live OHMV contracts, and then for real on mainnet with a throwaway deployment: ETH → OHMV → token through the live router, the 4% split, the Kennel's accumulation and its stake in live OHMV staking were all checked transaction by transaction. Live page: /cerberus. The token, hook, Kennel, launcher and router addresses are in the registry (section 14).
18Kennel Pad: a launchpad that feeds OHMV (added 9 Oct 2026)
Kennel Pad (/pad) lets anyone launch a meme paired with OHMV, with the Cerberus mechanics applied to it: a locked pool, a 4% tax in OHMV, its own Kennel and its own daily jackpot. The creator needs no OHMV; a launch costs gas only.
Launch
- One transaction.
KennelPad.launch()deploys a plain ERC20 (PadToken, 1,000,000,000 supply, 18 decimals, name (1–40 bytes), ticker (1–12 bytes) and links (image, website, X, Telegram) frozen at launch, no owner, no mint, no tax in the token), aCerbKennelfor it, and aPadHookat a CREATE2 address whose low bits carry the Uniswap v4 permission flags (the page hands the contract a list of pre-mined salts; the first still-free one is used). - One-sided liquidity. The whole supply goes into a MEME/OHMV Uniswap v4 pool as a single range from the opening price upward, so no OHMV is needed to open it. The opening market cap is a constant of the pad (
START_MCAP, 1,333,000 OHMV, about $10k when the pad went live; it is fixed in OHMV, so its dollar value moves with OHMV), rounded down to the pool's 60-tick spacing, so the real opening cap can be up to 0.6% off. The pad owns the position and has no function that removes it. There is no OHMV below the opening price: holders can only sell into the OHMV that buyers have paid in, and no trade fills below the price the pool opened at. A sell larger than the pool's OHMV fills only in part, andPadSwaphands back the meme it could not sell. After such a sell the pool's raw price (slot0,PadHook.sqrtPriceX96()) can run on through the empty range and read near zero until the next buy;PadHook.reserves()shows the real amounts. - Optional dev buy. ETH sent with the launch, OHMV approved to the pad, or both, buy the creator's bag in the same transaction at the opening price, before anyone else can trade. The meme pool does not tax it. ETH is first swapped to OHMV through VaultSwap and pays that route's 0.3% (OHM/WETH) and OHMV's 5%, like any OHMV buy.
minTokensOutguards the whole buy. There is no cap: a creator can take any share of the supply this way. The size is public in theLaunchedevent (devBuyOhmv,devTokens); check it before buying. Rounding dust from the liquidity add (about 1,000 tokens) is sent to the dead address0x…dEaD.
The tax
- 4% of every trade, in OHMV, taken inside the swap by
PadHookexactly as Cerberus does (exact-in and exact-out both pay the same share of the OHMV that changes hands). Split: 3% to the meme's Kennel, sent in the same swap; 0.5% to the creator and 0.5% to the platform, which accrue in the hook and are paid byclaimCreator()/claimPlatform(). The platform share goes to the operations wallet. Anyone can call either claim; they always pay the current creator and the operations wallet. The creator hands the stream over withsetCreator(to), in one step with no confirmation, and fees not yet claimed go to the new address. Nothing else about a launched meme can change. The pool has no LP fee of its own, so the tax is the only fee in it. - Launch curve. The same five-minute curve as Cerberus (95% → 70% at 60 s → 30% at 120 s → 4% at 300 s). The Kennel, creator and platform still get their 3 / 0.5 / 0.5 from the first second; the tax above 4% goes to the platform.
- Only the pad can open a pool on a pad hook or add liquidity to it, so there are no untaxed side positions. A pad meme traded on some other venue is outside the hook and untaxed; arbitrage back into the pad pool pays the 4%.
Each meme's Kennel
The same contract as the Cerberus Kennel (section 17), one per meme: the 3% is staked in OHMV staking forever, 90% of the emissions stream to the meme's stakers over 6 hours with the 1× to 2.5× boost, 10% build that meme's daily jackpot. Jackpot tickets need a stake at least a day old and at least 0.01% of the meme's supply (100,000 tokens). Each Kennel has 8,192 ticket slots; once all are taken, a new stake earns from the stream but holds no tickets until a slot frees and the stake is touched again. As in section 17, a withdraw restarts the stake's age and a large top-up pulls it back toward zero, so either can take a stake out of the draw for up to a day. Every pad meme adds one more Kennel that stakes OHMV forever, so together they take a growing share of each OHMV mint and direct OHMV stakers' share falls as pad volume grows; mint size does not depend on how much is staked. A keeper looks after pad Kennels on a slower, capped schedule. It syncs a Kennel once 5,000 OHMV of tax or 200 OHMV of emissions are waiting, or after 12 hours with at least 500 OHMV waiting. It runs a jackpot draw only once the pot holds 500 OHMV. It sends at most 6 transactions per Kennel in any 24 hours, counting syncs, draws and refreshes together, so it refreshes at most 4 stakes a day there, and only stakes of at least 100,000 tokens. So on a small meme, tax can sit unstaked and the stream, the pot and a new boost can wait for hours. Anyone can move them along by calling sync(), draw() or refresh(user), and a staker's own claim applies their boost at once.
Trading
PadSwap routes any pad meme in one transaction: buyWithEth (ETH → OHM → OHMV through VaultSwap → meme), buyWithOhmv, sellForOhmv and sellForEth (meme → OHMV → OHM → ETH). All are exact-input, with one minimum out for the whole route and a deadline. PadSwap adds no fee of its own, but every pool on the path takes its own. An ETH buy pays 0.3% (OHM/WETH), OHMV's 5% and the meme's 4%. An ETH sell pays the meme's 4%, OHMV's 5%, VaultSwap's 1% sell fee and 0.3%. Trading in OHMV pays only the meme's 4%. In a meme's first five minutes its tax follows the launch curve instead of 4%.
Trust
No owner, no admin, no fee switch on the pad, the hooks, the Kennels or the router. The pad's router (PadSwap) only routes tokens the pad launched. Two independent pre-launch reviews found no critical or high issues; the jackpot findings they raised (re-rolls by withholding a draw, fly-by staking around the draw, dust-wallet ticket slots) were fixed before deploy, and the fixes also went into the Cerberus Kennel. Known limits: a griefer can make one launch attempt revert only by front-running it with a full launch of their own for each salt it submitted, since only the pad can deploy a hook (the page retries with fresh ones), and the page must sanitise token links since they are creator-supplied. The pad, its router and its two deployers are in the registry (section 14); each meme's token, hook and Kennel are deployed by the pad and listed on /pad.
19Cerberus loans: borrow OHMV against CERB (added 10 Oct 2026)
The CerbLoans contract (0xE9498D5c14F107B96CD3DF3904460edc03c02c2F) lets anyone lock CERB and borrow OHMV, and lets anyone lend OHMV for the interest. It is built like the OHMV-backed loans in section 16: pooled lender shares, the same interest curve shape, loans with no due date, and liquidations that sell only part of the collateral. Unlike there, the 10% interest cut is booked as ordinary lender shares rather than a claim ahead of lenders. The difference is that CERB has no floor. Its only market is the CERB/OHMV pool (section 17): full range, locked forever, with a 4% tax on every trade. So CERB is valued at what selling it into that pool would pay, after the tax and the sale's own impact, at prices from the contract's own 30-minute record, never at a backing figure.
How CERB is valued
- Sale value, not spot price.
value(c) = (1 − tax) × rO × c / (rC + c), on the pool's virtual reserves at a given price. The tax is read live from the hook (currentFeeBps()), and the sale's own price impact is included, so a large bag is worth less per CERB than a small one, as it would be on a real sale. The pool's liquidity can never change (only the launcher can add, it did so once at launch and cannot again, and it has no exit), so this formula holds for the life of the pool. - Its own price record. There is no external oracle and the live hook is not touched. The contract samples the pool price at most once a minute (
SPACING= 60 s) into a 64-slot ring, about an hour of history. Every state-changing call exceptpushKennel()takes a sample, and so doespoke(), which anyone can call. The keeper calls it every few minutes while anything is lent or borrowed. - Two 30-minute averages. LOW counts each stretch between two samples at the lower of its two ends. MID counts each stretch at the price sampled at its start. A pump that is sold back before someone else's sample never reaches LOW: the next honest sample closes the stretch at the lower price.
- Warm record. Borrowing, removing collateral from a loan with debt, and liquidations all need a full 30 minutes of history with at least 3 samples in it (
MIN_OBS). With fewer samples an attacker who is the only one sampling would only need to fake one or two prices; with at least three, they must fake several in a row and pay the 4% tax both ways on each. Lending, withdrawing (from idle OHMV), adding collateral, repaying,close()and taking CERB back from a loan with no debt never need a warm record.oracle()shows the live, LOW and MID prices, the sample count and whether the record is warm.
Borrowing
- Borrow up to 40% of the collateral's sale value, priced at the lower of the live pool and the LOW average. A pump can't stretch the limit.
- Minimum 1,000 OHMV. Borrowing and repaying keep a loan at 0 or at least 1,000 OHMV: a partial repayment has to leave at least 1,000 OHMV owed or repay everything, so no loan is too small to be worth liquidating. Liquidations are sized to the same rule on their quote.
- No due date. Interest accrues until the loan is repaid. Anyone can repay anyone's loan.
open(collateral, amount)locks and borrows in one call;close()repays everything and returns all the CERB. - Locked CERB earns nothing. It does not earn Kennel rewards and holds no jackpot tickets. The Kennel books stakes per wallet, and staking the collateral would restart every borrower's boost at each liquidation and freeze liquidations during jackpot draws.
Liquidation
- When. A loan can be liquidated once its debt is above 60% of the collateral's sale value on the MID average, the record is warm, and the live price is no more than about 10% (1,054 ticks) below the last sample taken in an earlier block. A sample from the current block is ignored, so a dump followed by a liquidation in the same bundle cannot sell anyone's CERB at the dumped price. After a real crash the check passes again one honest sample later.
- How much.
liquidate(borrower)is open to anyone. It sells just enough CERB through the Cerberus router (CerbSwap.sellForOhmv, paying the 4% tax like any seller) to bring the loan back to 35%. The amount is sized on the lower of the live price and MID, counting the tax and the sale's own impact, plus 0.5% headroom; the minimum output is the same quote less 0.1%. The caller is paid 1% of what the sale returns. The rest of the CERB stays locked for the borrower. - When 35% can't be reached, everything is sold: the debt is repaid first, then the caller's 1%, then the borrower gets whatever is left. Any shortfall is written off as
badDebtand borne by the lenders, the Kennel included. If selling down to 35% would leave less than 1,000 OHMV owed, enough is sold to repay the whole loan instead.liquidationSize(borrower)quotes the size in advance. - The buffers. A loan opened at 40% only becomes liquidatable once the CERB has lost about a third of its sale value, and only after that drop has held long enough to pull the MID average down. Lenders only lose money after a further drop of about 40% below the 60% line, faster than liquidations can sell. The keeper checks every loan each minute and sends liquidations privately.
Lending and interest
- Lenders deposit OHMV for shares of the book: idle OHMV plus everything lent out. Shares can be redeemed at any time from idle OHMV. If it is all lent out, a lender waits for repayments or liquidations. Borrowing stops at 95% use, so borrowers alone cannot lend out the last 5%. Withdrawals have no such limit, so lenders who leave first can take use up to 100%.
- The rate follows use: 5% APR when nothing is lent, rising linearly to 15% at 80% use, then steeply to 80% at 100%. Lenders earn the borrow rate × use × 90% (
supplyRateBps()). - 10% of every unit of interest goes to the Cerberus Kennel. It is booked as lender shares, so the Kennel is an ordinary lender: it earns and loses with everyone else and has no claim ahead of them.
pushKennel(), open to anyone, cashes those shares out of idle OHMV into the Kennel, which stakes them in OHMV staking forever. That means more OHMV emissions paid to CERB stakers (90% streamed, 10% to the daily jackpot). - Pool cap. New borrowing is refused once all loans together would exceed 15% of the OHMV in the CERB pool, measured at the borrow price (
debtCap()). Interest or a price drop can take total debt above that later. This keeps a joint liquidation of every loan payable by the pool. It also makes a pump-to-borrow attack cost more in tax than it could gain. - After a near-total loss, if a share is worth under 10⁻⁹ OHMV, new deposits stop, so share counts can never overflow.
Risks
- Lenders. A crash faster than the 30-minute average that then holds can leave bad debt. It is bounded by the 15% cap and priced into the higher rates.
- Leaving early. After a crash a loan can be under water before it can be liquidated, because the guard refuses dumped prices and MID has to catch up. In that window a lender who is watching can withdraw from idle OHMV, and a later write-off lands on the shares still in. This is accepted: a delayed withdrawal would slow every honest exit.
- Accrued interest. Share values include interest that has accrued but has not been paid yet, as in any pooled lending.
- Keeper. Without regular samples the record goes cold. Borrowing, removing collateral from a loan with debt, and liquidations pause; repaying and closing still work. With the keeper down, an attacker has to pay the tax on every faked sample.
- CERB is a meme token. Its sale value can fall a long way. The 40 / 60 / 35 limits are set wider than the OHMV loans' 85 / 92 / 80 for that reason.
Trust
No owner, no admin, no fee switch and no external oracle. Every term is a constant in the bytecode: LTV_BPS 4,000, LIQ_BPS 6,000, TARGET_BPS 3,500, BONUS_BPS 100, GUARD_TICKS 1,054, KENNEL_BPS 1,000, MAX_USE_BPS 9,500, POOL_CAP_BPS 1,500, rates 500 / 1,500 / 8,000 bps with the kink at 80%, MIN_DEBT 1,000 OHMV and a 30-minute window. Tests cover it in two suites:
- 25 tests including fuzzing and attacks on the price record: pump, sample, dump; a held pump; a sandwiched liquidation.
- 13 accounting regression tests from a pre-deploy review. Every issue it raised was fixed before deploy except one: the early-exit risk under Risks, which is accepted and pinned by a test.
A mainnet-fork test also runs against the live Cerberus contracts. The code is new and unaudited. Deployed 10 Oct 2026 (block 26,159,268), verified on Etherscan. Live page: /cerb-loans.