Docs

How a moot works

Two small contracts, no owner, and everything else is Uniswap and Robinhood’s own stock tokens.

The coin

Every Moot coin is a clone (EIP-1167) of one small contract, MootCoin. It is an ordinary ERC-20 with 18 decimals and a fixed supply of 1,000,000,000, all of which starts inside the coin itself. The coin is also its own market: a constant-product curve between the coins it holds and a reserve of ETH that starts with a virtual 1 ETH. So a new coin has a price from its first block (a market cap of 1 ETH), and there is no other pool to route around its fee.

Buying sends ETH in; the fee is taken first, the rest goes to the curve, and you receive coinReserve × net ÷ (ethReserve + net) coins, rounded down. Selling is the mirror image, and the fee is taken from the ETH that comes out. The page computes both with the contract’s own integer formulas and sends a minimum 1% below its quote.

The fee

Chosen at launch, from 0.25% to 10% in steps of 0.25%, and stored in the coin. No function changes it: there is no owner and no admin, in the coin or in the factory. Every buy and every sell pays it, in ETH. None of it goes to the creator or to Moot.

The ballot

A coin’s ballot is two to eight tokenized stocks, no stock twice, each bought through one Uniswap USDG pool whose fee tier is chosen at launch (the launch page picks the deepest). The ballot and its order are fixed when the coin launches; nothing can add, remove or reorder a stock afterwards. The first stock is the first pick: it is what fees buy while nobody has voted.

The vote

Every address may vote for one stock on the ballot with vote(k) (1-based; 0 withdraws). A vote is not a snapshot: it counts every coin the address holds, now and later. The coin keeps a live tally per stock and a total, and its transfer function moves them with the coins: whatever leaves a voter leaves their stock’s tally, whatever reaches a voter joins theirs, and a burn takes the burned coins out. Coins inside the curve can never vote, and neither can holders who have not chosen. A buy may cast the buyer’s vote in the same transaction (buy(min, to, k), only for coins bought for yourself), and the launcher may vote with the first buy at launch.

The leader is the stock with the most coins voting for it, ties going to the earlier stock on the ballot; with no votes at all it is the first pick. leader() and the per-stock tallies in info() are readable by anyone, and the coin page draws them.

Each trade’s fee is swapped after that trade’s coins and vote have landed, so a buy that takes the lead buys its own stock with its own fee. Votes only steer future fees. Nothing the treasury already holds is ever sold to follow a vote, so a coin whose lead changes holds several stocks, and a burn pays a share of each.

If the leader’s swap is refused (its pool is off its average price, below), that stock rests for ten minutes: until then each fee buys the most-voted stock that is not resting. A stock nobody votes for is never bought, except the first pick while nobody votes at all. If every stock with votes is resting, the leader is tried anyway, so a fee is never stuck behind a rule. The contract exposes this choice as nextBuy().

Votes are weighted by coins, so anyone who holds the most coins can lead. Holding them means buying on the curve, paying the fee into the treasury on the way in and again on the way out, and the only prize is choosing what later fees buy.

The fair-price guard

Each fee is swapped inside the same transaction: ETH → USDG in Uniswap’s WETH/USDG 0.01% pool, then USDG → the chosen stock in its pool. Before swapping, the coin reads both pools’ time-weighted average price (30 minutes; if a very busy pool has overwritten that much history, 10 minutes, then 2) and sets the swap’s minimum to what the averages say the ETH is worth, less both pools’ fees and 2%.

A price pushed inside the current block carries no weight in an average, so an attacker who moves a pool and then triggers a treasury purchase gets nothing: the swap fails its minimum, the ETH stays in the coin as pendingEth, that stock rests, and the next trade (or anyone calling convert) buys the runner-up. The trade itself always goes through.

One refusal is deliberate: a transaction with too little gas left for the swap reverts with NeedsMoreGas instead of quietly deferring the fee. Wallets estimate the smallest gas at which a transaction does not revert; without that refusal, estimates would starve every purchase.

Burning for the treasury

Anyone holding coins can burn them with redeem and receive exactly held × coins ÷ totalSupply of every stock the treasury holds, including any that no longer lead the vote, plus the same share of any fee ETH still waiting. The share is taken over the whole supply, including coins still in the curve, so nobody can take more than their fraction, and every burn leaves each remaining coin backed by at least as much of every stock as before. A burn may name a minimum for each stock; the page sends 1% under its quote. The coin page shows whether burning or selling pays more for your amount.

The picture and links

The picture (cropped square and shrunk to under 16 KB in your browser), the description and up to three links are ABI-encoded and stored as the code of a tiny contract (SSTORE2) when the coin launches: 24 KB at most. meta() returns them byte for byte. Nothing depends on a server.

The contracts

WhatAddress
MootFactory0x6013bD66148A6E1a979411C61386f2f221c422cF
MootCoin implementation0xBa6922E2493c6b2E12bd87B22364f4DA8606E7aE
CREATE2 deployer (Arachnid’s, deterministic)0x4e59b44847b379578588920ca78fbf26c0b4956c
Uniswap SwapRouter020xCaf681a66D020601342297493863E78C959E5cb2
WETH / USDG 0.01% pool0x52e65B17fB6E5BA00Ed806f37Afcd2DaA50271Ca

The factory’s address is keccak256(0xff ++ deployer ++ salt ++ keccak256(initCode)), with salt 0x84d91c97b92573fecb405146955bde42f7791d8e99838bf3d942bea58197d43d and init-code hash 0x2f231f66bea6866618c1d16b17c7dd45d6ec43171b3b1a3b50871d5284f6248e. So the address is the code: anyone can deploy it, and whoever does puts exactly this code there. The launch page does it for you: if the factory is not there yet, your wallet first sends that one deployment, then your launch. Source: MootFactory.sol, MootCoin.sol, and Uniswap’s TickMath.sol; solc 0.8.26, optimizer 1000 runs, via-IR, Cancun. Status right now: checking…

How it was tested

Every property below runs on a private fork of live Robinhood Chain (anvil, started fresh at the newest block for each property): the real CREATE2 deployer deploys the factory, coins launch with ballots of real stock tokens, holders vote, and every treasury purchase swaps through the real Uniswap pools, in the state they are in right now. Nothing is broadcast and nothing is mocked. Expected numbers are computed in the test from the formulas written out there, never by asking the contract.

The last run: 13/13 properties and 662 checks passed against live state (01 Oct 2026), for the factory at 0x6013bD66148A6E1a979411C61386f2f221c422cF.

#PropertyChecks
P1The factory lands at the address its code fixes, with the implementation beside it7
P2Launch refuses every bad input with the error named for it, and accepts a good one30
P3Buys and sells pay exactly the curve and the fee, and every fee reaches the treasury62
P4A round trip never makes money, and everyone can always sell back20
P5The treasury only buys near the average price; a pushed pool defers the buy until it is not15
P6The price read is Uniswap's, in both token orders and every fee tier, and the swap matches the quoter
NVDA through its 0.05% pool: 0.005 ETH bought 0.058203 NVDA, 9 bp below what the 30-minute average said (the floor allows 206 bp below)
SPCX through its 0.05% pool: 0.005 ETH bought 0.090560 SPCX, 17 bp below what the 30-minute average said (the floor allows 206 bp below)
TSLA through its 0.3% pool: 0.005 ETH bought 0.037863 TSLA, 37 bp below what the 30-minute average said (the floor allows 231 bp below)
MSTR through its 1% pool: 0.005 ETH bought 0.083460 MSTR, 106 bp below what the 30-minute average said (the floor allows 301 bp below)
17
P7Redeeming pays exactly the holder's share of EVERY stock the treasury holds and nothing more47
P8Too little gas is refused outright rather than silently deferring the fee6
P9A coin keeps its picture and links on chain, byte for byte6
P10The coin is an ordinary ERC-20, refuses stray ETH, and cannot be re-initialised8
P11Votes follow the coins through every buy, sell, transfer, vote and burn, and each fee buys the leader
random phase: 7 buys, 5 buys that voted, 5 sells, 8 transfers, 4 votes, 1 withdrawn votes, 5 burns; fees bought 10 × stock 1, 7 × stock 2, 0 × stock 3
417
P12A pushed pool rests its stock for ten minutes and the runner-up buys; a stock nobody votes for is never bought14
P13A vote names a stock on the ballot, belongs to its holder, and nobody else can cast it13

Then a sabotage sweep plants 30 bugs, one at a time, in copies of the contracts — the fair-price guard removed, the 30-minute average swapped for the spot price, coins that leave a voter but stay in the tally, a switched vote that is never taken back, a leader chosen by the fewest votes, burned coins that keep voting, a buy that swaps its fee before its vote lands, a resting leader never passed over, an unvoted stock bought, a burn that pays out only the first stock, a buy that rounds one coin the wrong way — and requires the property named for each one to fail. 30/30 were caught by the property named for them.

And in a real browser: headless Chrome drove these pages with a test wallet against a private copy of the live chain — one click on the launch page deployed the factory and launched a coin with a picture and a TSLA / NVDA / SPCX ballot at 3%, its first buy voting NVDA (so the first fee bought NVDA); then a vote switched from the ballot, a buy that voted TSLA into the lead and bought TSLA with its own fee, a plain buy, a sale whose coins left the tally, a burn that paid a share of every stock held, and the board. 8/8 journeys, 49 checks, each outcome read back from the chain by the harness itself (01 Oct 2026).

Risks

  • Unaudited. The contracts are small and tested, not audited.
  • Coins can go to zero. The treasury gives each coin a floor only as high as the stock it holds; a coin can trade far above it and fall back to it.
  • The vote is by coins, not by people. Whoever holds the most voting coins decides what future fees buy. A large holder can take the lead, and holders who never vote count for nothing.
  • Stocks fall, and Robinhood controls its stock tokens. Robinhood can pause, block or burn its tokenized stocks. A paused stock cannot be bought (fees buy the runner-up or wait) or paid out (burns revert until it resumes).
  • Thin pools make a stock wait. If the leader’s pool is too thin for a fair fill, it rests and the runner-up is bought; if every voted stock is off, fees accumulate as ETH until one is not. Burns still pay that ETH out pro rata.