Technical documentation

How Zero works

The contracts, the math, and the guarantees, written for developers, auditors and anyone who refuses to take our word for it.

75% of all protocol revenue is used to market-buy $ZERO and burn it.

This is not a policy; it is a hard-coded constant in the Factory (PLATFORM_BURN_BPS = 7500). No function exists that can change it, pause it, or redirect it. Every platform fee claim buys $ZERO from the real pool and sends it to 0xdEaD, forever.

Architecture

In plain terms: one contract creates your token and its trading pool, burns the liquidity so nobody can pull it, and splits every trading fee automatically.

Zero is six contracts on Ethereum mainnet, built directly on Uniswap v4. There is no bonding curve and no graduation step; every token trades in a real v4 pool from its first block. The Factory/Hook pair exists twice: once for ETH-quoted launches and once for launches quoted in a tokenized stock (see Pairs & RWA).

Launching

Creator
deployCoin()
Factoryone transaction
creates
TokenERC-20
Lockertrustless vesting
v4 Pool + LPLP burned to 0xdEaD

Every swap

Trader
Universal Router
PoolManagerUniswap v4
before / afterSwap
ZeroHookcharges the fee
take(fee)
Factorysolvency-checked
90% creator · 10% platformon-chain split
LP can never be withdrawn Fee fixed at launchdepositFees(token, amount) → balance ≥ totalAccrued
ContractRoleKey detail
FactoryCreates tokens, seeds pools, splits fees, pays claimsOwns the solvency invariant; LP is minted straight to 0xdEaD
ZeroHookOur Uniswap v4 hook, charging the creator-chosen swap feePermission bits encoded in its address (0xCC), CREATE2 salt-mined
LockerTrustless token locks: fixed, linear, cliff+linearNo admin path to locked funds, extend-only
ZeroRWAFactorySame launch flow, quoted in an allowlisted ERC-20 (tokenized stocks)Per-currency accounting; the token must sort above the quote
ZeroRWAHookThe v4 hook for stock-quoted poolsCharges the fee in the quote token
TokenMinimal ERC-20Fixed supply minted to the Factory at deploy; creator is immutable

Pairs & RWA

In plain terms: your token can trade against ETH, or against a tokenized stock like $TSLA or $NVDA — and then your fees are paid in that stock.

Every launch picks a quote asset: either native ETH or one of the Ondo tokenized equities on mainnet. The two cases run on separate contracts — Factory + ZeroHook for ETH, ZeroRWAFactory + ZeroRWAHook for ERC-20 quotes — so the ETH path stays exactly as audited while stock pairs get the rules they need. Everything else is identical: burned LP, the same fee split, the same locker, the same solvency invariant.

ETH pairTokenized stock pair
ContractsFactory + ZeroHookZeroRWAFactory + ZeroRWAHook
You pay withETHThe stock token (e.g. TSLAon)
Creator fees paid inETHThe same stock token
Protocol revenue75% buys and burns $ZEROTransferred to the platform; conversion happens off-chain
Quote listOwner-allowlisted, one liquidity ladder per stock

Why fees arrive in the stock

A pool only ever holds its two assets. When someone buys your $TSLA-paired token, they pay TSLAon, so the swap fee is charged in TSLAon and that is what accrues to you. The dashboard therefore lists earnings per currency, andcollectFees(quote) pays each one out separately.

The ordering rule

A Uniswap v4 pool sorts its two currencies by address. For stock pairs Zero requires the new token to sort above the quote, so the token is always currency1 and the fee path has a single branch — the same one the ETH hook uses. The launch UI mines a CREATE2 salt until the predicted address satisfies that (milliseconds); a salt that fails reverts with TokenBelowQuote.

Supported quotes

$TSLA, $AAPL, $NFLX, $SPCX, $AMZN, $META, $NOK, $QQQ, $GOOGL, $TSM, $SPY, $NVDA — all 18-decimal Ondo tokens with no transfer restrictions. New quotes are added by the owner; existing pools are never affected.

Launch lifecycle: one transaction

In plain terms: you sign once. When the transaction confirms, your token exists, trading is live, and the liquidity is already burned.

deployCoin(name, symbol, metadata, salt, configId, feeBps, deployer, lock) does everything atomically. If any step fails, the whole launch reverts; there is no half-launched state.

#StepWhat happens
1Token deployNew ERC-20 via CREATE2: the address is predictable from the salt, so frontends show it before the transaction (getTokenBytecode)
2Registry writeOne TokenInfo record + packed FeeState (recipient, renounced): a single storage slot read on every swap
3Fee registrationThe hook stores tokenFeeBps once; the tier must be on the owner-managed allowlist (1–10% at launch). It can never change; the only transition is renouncing to a flat 0.1%
4Pool initv4 pool with currency0 = ETH, fee = 0, tickSpacing = 200, hooked to our ZeroHook, which is the only fee
5LiquidityThe entire supply deposits as single-sided liquidity (see Liquidity math)
6LP burnThe LP NFT is minted directly to 0xdEaD. The Factory has no function that removes liquidity; the burn is verifiable on-chain
7First buy (optional)If ETH is attached, it buys through the Universal Router in the same transaction, optionally locked in the Locker with a cliff/vesting schedule

Measured cost on a mainnet fork: deployCoin 1.36M gas including the pool, the LP mint and bookkeeping.

Fee mechanics

In plain terms: every buy and sell pays the fee the creator chose. There is no cheap side-door: all four ways of swapping cost the same rate.

The hook implements beforeSwap and afterSwap with return-delta permissions. Its deployment address literally encodes 0xCC in the low bits, which Uniswap v4 validates. The fee is always charged on the ETH side, for all four swap shapes, and the exact-out formulas are grossed up so no shape is cheaper than another:

Swap shapeCharged inFormula
Buy, exact-inbeforeSwapfee = ethIn × bps / 10000
Buy, exact-outafterSwapfee = ethIn × bps / (10000 − bps)
Sell, exact-inafterSwapfee = ethOut × bps / 10000
Sell, exact-outbeforeSwapfee = ethOut × bps / (10000 − bps)

Collection is a single ETH transfer: the hook calls poolManager.take(currency0, factory, fee). The ETH lands on the Factory directly, and the hook then reports the amount with depositFees(token, amount). The hook never holds ETH and has no receive().

Pools created around the Factory can attach this hook but are unusable: _feeBps reverts with TokenNotRegisteredfor any token the Factory didn't register.

The solvency invariant

In plain terms: the Factory only believes money it can actually see. Nobody can invent fee balances: the math makes lying revert.

Because the hook only reports amounts, the Factory refuses to believe it blindly. It keeps one aggregate liability counter and checks it against the real balance on every deposit:

function depositFees(address token, uint256 amount) external {
    if (msg.sender != tokenHook[token]) revert OnlyTokenHook();
    totalAccrued += amount;
    if (address(this).balance < totalAccrued) revert InsufficientETHReceived();
    // ... 90/10 split
}

take() runs before depositFees() in the same transaction, so the ETH is already on the Factory when the check runs. Over-reporting by even 1 wei reverts the whole swap, verified in fork tests. Under-reporting only donates to the platform. Claims (collectFees, claimProtocolFees) decrement totalAccrued, so balance ≥ totalAccrued holds at all times: the Factory can always pay every outstanding claim.

Fee split & creator rights

In plain terms: creators earn 90% of every fee, can redirect it, or give it up forever. The platform's 10% mostly ends up as burned $ZERO.

ActionWhoEffect
EarnFee recipient90% of every fee accrues per-recipient across all their tokens; claimed in one call via collectFees()
RedirectDeployer (permanent right)setTokenFeeRecipient points future fees at any address. Already-accrued fees stay with the old recipient
RenounceDeployer, one-wayrenounceCreatorFees drops the fee to a flat 0.1%, all of it to the platform, forever. Disables redirecting. Traders can verify it
Community takeoverPlatform ownercommunityTakeover hands the deployer role (and future fees) to a new address; not available after renounce
Platform claimPlatform ownerOn every claimProtocolFees call, a fixed 75% of the accumulated revenue market-buys $ZERO through the real pool and burns it at 0xdEaD; 25% pays out. The split is the immutable constant PLATFORM_BURN_BPS = 7500, an unchangeable rule

Locker

In plain terms: locked tokens are provably untouchable, even by us, until the schedule says otherwise.

Anyone can lock any ERC-20 for any beneficiary. The owner role exists for future admin needs only; there is deliberately no path to locked funds and no way to shorten a lock.

KindScheduleNotes
FixedEverything unlocks after N daysBeneficiary can extend, never shorten
LinearVests continuously from day 0 to NWithdraw any vested amount at any time
CliffLinearNothing during the cliff, then linearCliff and vesting lengths are independent

Implementation details

Each lock packs into 3 storage slots (token+kind+timestamps / beneficiary+vestEnd / amount+claimed as uint128). Deposits are fee-on-transfer safe: the contract locks what actually arrived, measured by balance difference. Lock lists are not stored on-chain; indexers rebuild them from Locked/Withdrawn events, while totalLocked[token] keeps the live locked balance for badges.

Liquidity math

In plain terms: your two launch choices, supply and starting liquidity, are just a price: how much ETH the pool pretends to hold on day one.

A launch config is a pair: total supply S and virtual ETH V (the opening market cap). The entire supply deposits as single-sided currency1 liquidity, priced as if V ETH were already in the pool:

sqrtPriceX96 = √(S / V) × 2⁹⁶          // pool opens at S/V tokens per ETH
initTick     = log₁.₀₀₀₁(S / V)
tickUpper    = floor(initTick, 200)     // price sits just above the range →
tickLower    = -887200                  // the whole supply deposits one-sided,
                                        // buy-side liquidity never runs out

The tick-floor gap makes the effective opening price at most ~1.6% below nominal. All 20 precomputed configs (1M–1B supply × 0.69–10 ETH) are verified against the 200 tick spacing: divisibility, min/max bounds, and the single-sided condition.

Events & indexing

The contracts store the minimum on-chain and emit everything an indexer needs. Metadata, deploy timestamps and per-token fee totals are intentionally not in storage; they are derived from events:

EventEmitted byAnswers
ERC20TokenCreated(token, name, symbol, deployer, metadata)FactoryToken list, identity, metadata, launch time (block timestamp)
FeesDeposited(token, recipient, creatorShare, platformShare)FactoryPer-token volume of fees, creator earnings, platform take
TokenFeeSet(token, feeBps)HookThe fee tier, and which hook the token trades through (the emitter address)
CreatorFeesCollected / PlatformFeesBurnedFactoryClaim history and the ZERO burn tally
Locked / Withdrawn / LockExtendedLockerLock lists, vesting state, locked-% per token
TokenFeeRecipientUpdated / CreatorFeesRenounced / CommunityTakeoverFactoryThe full history of a token's fee stream

Security properties

PropertyMechanism
Liquidity cannot be pulledLP NFT owned by 0xdEaD; no removal function exists anywhere in the Factory
Fees cannot be inflatedThe solvency invariant: crediting unbacked ETH reverts the swap
The fee cannot changeRegistered once on the hook; the only transition is the one-way renounce to 0.1%
Locks cannot be touchedNo owner path in the Locker; fixed locks extend-only
ReentrancyOpenZeppelin ReentrancyGuardTransient (EIP-1153) on every ETH-paying path
Predictable addressesCREATE2 everywhere: token addresses are verifiable pre-launch; the hook address encodes its own permissions
Tested against mainnetThe full launch flow, fee routing, invariant and buyback run in fork tests against the real Uniswap v4 contracts

Contract reference

In plain terms: every function you can call, what each input means, and who is allowed to call it.

Factory — ETH pairs

deployCoin(name, symbol, metadata, salt, configId, feeBps, deployer, lock)
payableAnyone*

The launch. Creates the ERC-20, initialises the v4 pool, adds the whole supply as liquidity, burns the LP position, and — if you attached ETH — performs your first buy, all in one transaction.

namestring
Token name, e.g. “Ethereum”.
symbolstring
Ticker, e.g. “ETH”.
metadatastring
An ipfs://CID pointing at the JSON document with image, description and socials. Emitted in the event; never stored on-chain.
saltbytes32
CREATE2 salt — decides the token address. Any value works for ETH pairs.
configIduint256
Picks a supply x virtual-ETH ladder entry (the opening market cap). Ladders are set by the owner; getLiquidityConfig reads them.
feeBpsuint256
Your swap fee in basis points (100 = 1%). Must be one of the tiers the hook allows.
deployeraddress
Who owns the fee stream and the creator rights. Usually you; can be a treasury or multisig.
locktuple
kind 0 fixed / 1 linear / 2 cliff+linear, plus cliffDays and vestDays. Applies to the tokens bought by your first buy; kind 0 + 0 days means no lock.

*Gated by publicDeployEnabled: only the owner can launch until the platform opens it. msg.value is the size of your first buy — send 0 for none.

collectFees()
Any recipient

Pays out everything you have accrued across all your tokens, in one transfer. No arguments — the contract knows who is calling.

setTokenFeeRecipient(token, newRecipient)
Current fee recipient

Redirects FUTURE fee accrual to another address. Fees already accrued stay claimable by the current recipient.

tokenaddress
The token you launched.
newRecipientaddress
Where new fees should accrue from now on. Cannot be the zero address.

Control travels WITH the stream: after you redirect it, only the new recipient can redirect it again (or renounce). The launching address keeps no special power — the one exception is communityTakeover, which the platform owner can always use.

renounceCreatorFees(token)
Current fee recipient

Permanently gives up your fee stream. The swap fee drops to a flat 0.1% that goes entirely to the platform.

tokenaddress
The token to renounce fees on.

One-way. No function can restore the fee, and already-accrued fees remain claimable.

claimProtocolFees()
Owner

Settles the platform's share: 75% market-buys $ZERO and burns it, 25% goes to the platform. The 75% is the immutable constant PLATFORM_BURN_BPS.

communityTakeover(token, newDeployer)
Owner

Hands the deployer role and the fee stream of an abandoned token to a new address.

tokenaddress
The token being handed over.
newDeployeraddress
The new owner of the creator rights.
depositFees(tokenAddress, amount)
Hook only

How the hook reports a swap fee it already sent. Credits the split and is rejected unless the Factory's ETH balance actually covers totalAccrued — see the solvency invariant.

tokenAddressaddress
Which token the fee came from (decides whose creator share it is).
amountuint256
Fee amount in wei that was transferred to the Factory in this same transaction.

ZeroRWAFactory — tokenized stock pairs

Same functions, with two differences: a quote address selects which stock the pool is priced in, and money moves as ERC-20 transfers instead of msg.value.

deployCoin(name, symbol, metadata, salt, quote, configId, feeBps, deployer, initialBuyAmount, lock)
Anyone*

The stock-paired launch. Identical flow to the ETH factory, quoted in an allowlisted ERC-20.

quoteaddress
The tokenized stock the pool trades against (e.g. TSLAon). Must be allowlisted by the owner.
saltbytes32
Must produce a token address ABOVE the quote address, otherwise the call reverts with TokenBelowQuote. The UI mines this for you.
configIduint256
Ladder entry for THIS quote — each stock has its own supply x virtual-quote ladder.
initialBuyAmountuint256
Your first buy, denominated in the quote token. Approve the factory for this amount first; 0 skips it.
locktuple
Same lock options as the ETH factory, applied to the tokens your first buy receives.

Name, symbol, metadata, feeBps and deployer behave exactly as in the ETH factory. Not payable — no ETH is involved anywhere in this path.

collectFees(quote)
Any recipient

Pays out your accrued fees IN THAT QUOTE. Fees are tracked per currency, so claiming TSLAon and NVDAon are two separate calls.

quoteaddress
Which quote currency to withdraw.
claimProtocolFees(quote)
Owner

Transfers the platform's share of that currency to the owner as-is. There is deliberately NO on-chain buyback here — stock revenue has no guaranteed route to $ZERO; conversion happens off-chain.

quoteaddress
Which quote currency to settle.
setQuoteAllowed(quote, allowed)
Owner

Adds or removes a stock from the allowlist and grants the router the Permit2 allowance it needs. Existing pools are never affected.

quoteaddress
The ERC-20 to allow as a pair asset.
allowedbool
True to enable launches against it, false to stop new ones.

setTokenFeeRecipient, renounceCreatorFees, communityTakeover and depositFees have the same signatures and rules as the ETH factory — only the currency differs.

Hooks (ZeroHook / ZeroRWAHook)

The hooks are called by the PoolManager on every swap; you never call them directly. Their only writable surface is administrative.

setTokenFee(token, feeBps)
Factory only

Registers a token's fee tier at launch. Can only ever be set once per token.

tokenaddress
The freshly launched token.
feeBpsuint256
Fee in basis points; must be an allowed tier.
setRenouncedFee(token)
Factory only

Drops a token to the flat 0.1% platform fee after its creator renounces.

tokenaddress
The token being renounced.
setFeeAllowed(feeBps, allowed)
Owner

Manages which fee tiers creators may pick. Affects future launches only — a live token's fee can never be changed.

feeBpsuint256
The tier, in basis points.
allowedbool
Whether new launches may select it.

Locker

lockFixed(token, amount, lockDays, beneficiary)
Anyone

Locks tokens until a date. Nothing is claimable before it; everything is claimable after.

tokenaddress
Token to lock (approve the Locker first).
amountuint256
Raw amount, 18 decimals.
lockDaysuint256
Days until the unlock date.
beneficiaryaddress
Who may withdraw — can be someone other than you.
lockLinear(token, amount, vestDays, beneficiary)
Anyone

Vests continuously from the moment of the lock until the end date.

vestDaysuint256
Length of the vesting period in days.
token / amount / beneficiary
As above.
lockCliffLinear(token, amount, cliffDays, vestDays, beneficiary)
Anyone

Nothing vests until the cliff, then it vests linearly until the end.

cliffDaysuint256
Days before anything becomes claimable.
vestDaysuint256
Days of linear vesting AFTER the cliff.
withdraw(lockId)
Beneficiary

Claims everything vested so far. Safe to call repeatedly; it only ever sends what is newly available.

lockIduint256
Id from the Locked event / your dashboard.
extend(lockId, extraDays)
Beneficiary

Pushes a fixed lock's unlock date further out. The date can never move earlier.

lockIduint256
The lock to extend.
extraDaysuint256
Days to add to the current unlock date.
claimable(lockId)
View

Vested minus already claimed, per that lock's schedule. Free to call.

Deployments

In plain terms: these are the exact contracts your transactions touch. Verify them on Etherscan before you sign anything.

Zero contracts

Uniswap v4 infrastructure

Zero deploys none of these — they are Uniswap's canonical mainnet contracts, and your funds move through them, not through us.

Burned liquidity goes to 0x000000000000000000000000000000000000dEaD — the standard dead address, which has no known private key.

Ready to launch?

One transaction, and every guarantee above applies to your token.

Launch a token