Protocol whitepaper · Version 1.1

Zxo.

Uniswap V3 liquidity farming anchored by permanently locked, single-sided core liquidity.

Updated
27 September 2026
Website
Zxo.Farm
Contracts
Solidity 0.8.36 · BUSL-1.1
Security review
Revision 2.5 · 8.5 / 10
DEX model
Uniswap V3 and forks
Status
Pre-mainnet
Contents
  1. Abstract
  2. Project overview
  3. Problem statement
  4. Solution summary
  5. Architecture
  6. Core functionality
  7. Staking and reward math
  8. Technical design
  9. User and owner functions
  10. Tokenomics
  11. Governance and owner powers
  12. Security
  13. Gas efficiency
  14. Interfaces and dependencies
  15. Deployment
  16. Legal and compliance
  17. Roadmap
  18. Appendix
Abstract

Zxo in one page

Zxo is a liquidity farming protocol for Uniswap V3 and compatible DEXes. Each Zxo pool launches its own reward token, paired with an established asset such as a stablecoin or a major token, and designed to trade close to 1:1 with it. Users provide liquidity to that pair as a V3 position on a fixed, shared price range, stake the position NFT in the Zxo contract, and earn the pool's reward token while the price, and its 15-minute average, stay within the range.

Every pool is anchored by a core LP: a large, single-sided position of the reward token placed just above parity. As buyers push the price up, the core LP sells reward tokens and accumulates the pair asset. The contract has no function that can withdraw or reduce this position, so the core LP and everything it accumulates is locked permanently.

Rewards are minted on claim at a rate that the contract re-targets to a configured APR on staked value, capped by hard limits set at pool creation. A bonus rewards deposits that bring more of the pair asset than the reward token. Staked positions can always be withdrawn after a one-day lock, with no price condition and no owner approval.

For liquidity providers

Stake a V3 position, earn trading fees plus minted rewards, withdraw at any time after 24 hours.

For the ecosystem

Locked core liquidity that no one can pull, and a reward token priced against a real asset from its first block.

For verifiers

No upgrades, no pause, no admin withdrawal path. Every rule in this paper is enforced by immutable code.

01

Project overview

Zxo lets people farm, harvest, collect and earn on concentrated liquidity. One deployment of the Zxo contract on a chain can host up to 255 pools. Each pool consists of:

  • a reward token (a ZxoToken) created by the pool, with the Zxo contract as its only minter;
  • a pair token with 6, 8 or 18 decimals, such as USDC, WBTC or an 18-decimal asset;
  • a V3 pool for the pair at the 0.01% fee tier;
  • a permanently locked core LP and core-stake position owned by the Zxo contract;
  • an emission controller that targets an APR on staked value within hard caps.

The protocol consists of five deployed contracts per chain: Zxo (the farm), ZxoTreasury (fee receiver), ZxoV3Deployer (bootstraps prices on other DEXes), ZxoOracle (reads a 15-minute average price from V3 pools), and ZxoVerify (a deployment sanity check). Each pool also deploys its own ZxoToken.

02

Problem statement

Liquidity that can leave overnight

New tokens usually launch with liquidity the team controls. Holders have to trust that it will stay, and many launches end with that liquidity withdrawn.

Farms that pay for idle capital

Classic MasterChef farms pay rewards on LP tokens whether or not that liquidity is useful. With concentrated liquidity, a position outside the active price earns no fees and does nothing for traders.

Unbounded emissions

Fixed emission schedules pay the same number of tokens whether the farm holds $10,000 or $10 million, which leads to runaway APRs early and dilution later.

Admin keys over user funds

Many farms can pause withdrawals, change reward rules or migrate funds. Users carry that key risk for as long as they stay.

03

Solution summary

ProblemZxo designWhere it is enforced
Removable launch liquidityThe core LP is minted to the Zxo contract, which has no function to transfer, burn or reduce it.Zxo.initializePool
Rewards for idle liquidityAll user positions share one range around parity. Claims are only allowed while the price is inside that range._getStakeTick, _requireFairRatio
Price pushed for one transactionThe health gate also requires the 15-minute time-weighted average price to be inside the range, which a same-block swap cannot move.ZxoOracle.inRange, _poolRatio
Deploying with wrong addressesThe Zxo constructor checks every configured address on-chain: code present, DEX wiring, fee tier, HOLDER token, and the treasury, deployer and oracle contracts.Zxo constructor, ZxoVerify
Unbounded emissionsThe reward rate re-targets to an APR on staked value, capped by a max APR and a max 30-day amount fixed at pool creation._performAutoAdjust
Admin control over fundsNo upgrades, pause, migration or rescue functions. Unstake has no owner or price gate.Entire codebase
Hidden launch allocationsTokens used to seed other DEXes must be finalized (remainder burned, seeding closed) before staking can open.ZxoV3Deployer.finalizeToken
04

Architecture

ChainLib. Each chain has a small library of constants: chain id, position manager, factory, HOLDER token and HOLDER threshold, and the treasury, deployer and oracle addresses. One library is compiled into the contracts for the target chain. Every constructor reverts if deployed on a different chain id, and the deploy script refuses to send a build to a chain that doesn't match.

Checked at deployment. The Zxo constructor reverts, with a readable reason, unless every configured address is a deployed contract, the position manager uses the configured factory, the 0.01% fee tier exists with tick spacing 1, the HOLDER token has 18 decimals and a threshold of at least one token, the treasury, deployer and oracle identify themselves correctly, and the deployer is not already linked to another Zxo. These checks live in the constructor, so they cost nothing at runtime.

No proxies. Contracts are deployed directly and cannot be upgraded. The rules described here are the rules for the life of each deployment.

05

Core functionality

Start Check (reverts on failure) Contract step External DEX call Result

5.1 Pool launch

The owner launches each pool in six steps. The contracts enforce the order of the last two: the core stake cannot be minted until the reward token is finalized on the deployer, and staking cannot open until the core stake exists. Step 3 gives the new pool room to store 15 minutes of price history, which the health gate needs.

STEP 1createPoolDeploys the ZxoToken, validates the pair's decimals, mints the core amounts to Zxo and the seeding amount to the deployer.
STEP 2Core LPCreates the V3 pool at the launch price, checks the price, mints the single-sided core LP to Zxo.
STEP 3Price historyRaise the pool's observation slots once (set-observations.py). Anyone can do it; the size can only grow.
STEP 4Seed DEXesSmall single-sided positions on other V3 DEXes. NFTs go to 0x…dEaD.
STEP 5FinalizeBurns the deployer's remaining tokens. Seeding for this token is closed forever.
STEP 6Core stakeRequires finalization. Mints the core-stake position, which opens staking.

5.2 Stake

5.3 Harvest

Action 0 claims rewards, 1 collects fees, 2 does both.

5.4 Unstake

No price check and no owner approval. Harvest first: pending rewards are forfeited on unstake.

5.5 Emission re-targeting

06

Staking and reward math

Every formula below is taken from the contract code. Amounts are integers in the token's smallest unit, divisions round down, and PRECISION = 10¹⁸. Examples use round numbers for readability.

6.1 Price, ticks and ranges

price(token1 per token0) = 1.0001 ^ tick sqrtPriceX96 = √price × 2⁹⁶
A tick is a 0.01% price step. 300 ticks ≈ 3.05%.For an 18/18 pair with Zxo as token0, tick 0 is exactly 1 pair token per Zxo (parity). For 6- and 8-decimal pair tokens, parity sits at tick ±276,324 and ±230,270 because of the decimal difference.
PositionRange (18/18, Zxo = token0)Zxo price bandPurpose
User stake(−300, +300)0.9704 – 1.0305Every user position uses this exact range
Core LP[11, 300]1.0011 – 1.0305Single-sided sell wall above parity
Core stake[31, 300]1.0031 – 1.0305Opens staking, keeps totals non-zero
Launch pricetick 101.0010One tick below the core LP

When Zxo is token1, all ranges mirror around parity (for example the core LP becomes [−300, −11]). The full table for every pair type is in the appendix.

6.2 Health gate

twapTick = ⌊ (tickCumulative(now) − tickCumulative(now − 900 s)) / 900 ⌋ (rounded toward −∞) healthy = lowerTick < currentTick < upperTick and lowerTick < twapTick < upperTick
currentTick is read live from the pool's slot0. twapTick is the 15-minute time-weighted average, read by ZxoOracle through the pool's observe. Both bounds are strict.A swap inside one transaction can move the spot tick but not the 15-minute average, so pushing the price into range just to harvest does not work. If the pool does not yet have 15 minutes of history, no average is available and the pool counts as unhealthy.Stake and harvest require a healthy pool. Unstake does not. Rewards accrue whether or not the pool is healthy, and become claimable again when the price returns to the range.

6.3 Deposit normalization

normalized = amount × 10^(18 − decimals) (decimals ∈ {6, 8, 18}) totalAmount = normalized0 + normalized1
All TVL figures are 18-decimal amounts summed 1:1, which matches the pair's parity design. totalAmount is recorded as the position's contribution to pool TVL.
ExampleDeposit 5,000 USDC (6 decimals) and 4,990 Zxo: 5,000 × 10¹² + 4,990 × 10¹⁸ in base units, recorded as 9,990 tokens of TVL.

6.4 Position liquidity

With √a, √b the range bounds and √p the current price, for √a < √p < √b: amount0 = L × (√b − √p) / (√p × √b) amount1 = L × (√p − √a) L = min( amount0 × √p√b / (√b − √p) , amount1 / (√p − √a) )
The position manager computes L from your deposit and returns any unused amount. Because every user position uses the same range, L is proportional to the position's value at any price, so weighting rewards by L is fair between users.The mix of tokens you deposit is set by the current price, not by you. Above parity (Zxo token0) a deposit is mostly pair token; below parity it is mostly Zxo.

6.5 Stake bonus

pairedRatio = paired / (paired + local) (normalized amounts) bonusBps = min( 2000 , (pairedRatio − 0.5) × 59,538 ) only if paired > local bonusL = L × bonusBps / 10,000 HOLDER : bonusL = bonusL × 1.25 if HOLDER balance ≥ HOLDER_THRESHOLD weight = L + bonusL
local is the reward token in your deposit and paired is the pair token. The bonus rewards deposits that bring more of the pair asset than of the reward token, which happens when the price is above parity.The bonus reaches its 20% cap (25% for HOLDER holders) at a 53.36% paired ratio, about 20 ticks above parity. At the launch tick the organic bonus is about 10–12%.The bonus is virtual reward weight. It adds nothing to your principal and is removed from the totals when you unstake.HOLDER_THRESHOLD and the HOLDER token are set per chain in its ChainLib and fixed at compile time. The threshold is at least one token (for example 369,000 on Arbitrum).
ExampleThe price is 10 ticks above parity, so a deposit is 51.68% pair token. Excess = 0.0168, so bonusBps = 0.0168 × 59,538 = 1,000 (10%). A position with L = 1,000,000 gets weight 1,100,000, or 1,125,000 with the HOLDER bonus.
Bonus calculator
1,012bonus (bps)
10.1%bonus rate
1,101,200reward weight

6.6 Reward accounting

On every pool update (Δt seconds since the last): accRewardPerShare += Δt × rewardRate × 10¹⁸ / totalStaked For a position: pending = weight × accRewardPerShare / 10¹⁸ − rewardDebt rewardDebt = weight × accRewardPerShare / 10¹⁸ (reset on stake and claim)
totalStaked is the sum of all weights in the pool, including the core stake. Your share of emissions at any moment is weight / totalStaked.Rewards are minted when claimed. Nothing is pre-funded, and rewards that are never claimed are never minted.

6.7 Emission rate

SECONDS_PER_YEAR = 31,536,000 targetRate = TVL × targetAprBps / (SECONDS_PER_YEAR × 10,000) maxRate = TVL × maxAprBps / (SECONDS_PER_YEAR × 10,000) monthlyCap = max30dRate / 2,592,000 (if set) rewardRate = min(targetRate, maxRate, monthlyCap) maxAprBps = maxNormalAPR × 2 during the first 365 days of the pool = maxNormalAPR afterwards (maxNormalAPR ≤ 32,000 = 320%) displayed APR (bps) = rewardRate × SECONDS_PER_YEAR × 10,000 / TVL
The rate re-targets on the next stake, harvest, unstake or updatePool call after a 1-hour cooldown from the last change. The default target is 10% (1,000 bps).
ExampleTVL 2,000,000 tokens, target 10%: rewardRate = 2,000,000 × 1,000 / (31,536,000 × 10,000) = 0.006342 tokens per second, or 200,000 tokens per year.

6.8 Referral split

referral = pending × refRateBps / 10,000 (3% – 30%, default 3%) you = pending − referral
Your referrer is saved on your first stake and never changes. If you pass no referrer, the treasury is your referrer. If your referrer is your own address, you receive 100% and no split happens. The split comes out of your rewards; the total minted is the same either way.

6.9 Unstake threshold

allowed if pending ≤ 10¹² base units (0.000001 tokens) or acceptForfeit = true
Pending rewards are not paid on unstake. Harvest first, then unstake. The threshold prevents accidental forfeits; acceptForfeit lets you exit immediately, for example while the pool is out of range and harvest is blocked.
Reward estimator (illustrative)
200,000pool emissions / year
0.55%your share of weight
1,066your tokens / year after referral
10.7%your effective APR

Assumes weight is proportional to value and ignores the core stake's share, fee income and price changes. The contract's actual figures depend on every position in the pool.

07

Technical design

7.1 Storage

VariableTypeContents
poolInfoPoolInfo[]Per pool: accRewardPerShare, totalStaked, totalTokens, token0, token1, rewardToken, rewardRate (uint96), lastRewardTime, lastAutoUpdate (uint40), targetAprBps, refRateBps (uint16), stakes (uint32)
poolConfiguint8 → uint256Packed: maxNormalAPR (bits 240–255), decimals (232–239), maxBonusEnds (192–231), max30dRate (64–191), corePositionId (0–63)
stakedPositions_uint8 → uint64 → StakedPositionrewardDebt, liquidity (weight), tokens (TVL share), owner, stakeTime, poolId
userStakedRefs_address → PositionRef[]Up to 21 (poolId, tokenId) pairs per address
userReferrer_address → addressReferrer, set once on first stake
referralsPerUser_address → uint32Count of addresses that chose this referrer
totalGlobalStakesuint32Active positions across all pools

7.2 Events

Zxo emits a single event, through one shared internal function, to keep the contract under the 24,576-byte size limit. Indexers filter on action. A harvest with action 2 (ClaimAndCollect) logs a Claim and a CollectFees event.

event ZxoEvent(address indexed user, uint256 data0, uint256 data1, uint256 data2, Action_ action);
ActionCodeuserdata0data1data2
Claim0stakerpoolIdtokenIdrewards claimed (incl. referral share)
CollectFees1stakerpoolIdtokenIdamount0 + amount1
Bonus3stakerpoolIdtokenIdbonus weight added
Unstake4stakerpoolIdtokenIdpending rewards forfeited
AutoAdjust5callerpoolIdnew rewardRatetargetAprBps
Stake6stakerpoolIdtokenIdweight (liquidity + bonus)
Referrer7referrer000
Deployed8deployertimestampchain id0
PoolCreated9reward tokenpoolIdmax30dRatemaxNormalAPR
CoreFeesCollected10treasurypoolIdamount0amount1
ConfigUpdated11ownerpoolIddataTypevalue stored (target APR after the cap)
PoolInitialized12reward tokenpoolIdposition tokenIdliquidity

7.3 Access control, errors and upgradeability

Access control

Zxo and ZxoV3Deployer use OpenZeppelin Ownable2Step. ZxoTreasury uses Ownable. ZxoToken ownership is fixed to Zxo and cannot be transferred or renounced.

Reentrancy

Every state-changing external function is nonReentrant. State is written before external transfers, and unstake makes its NFT transfer the final step.

Errors and upgrades

Runtime failures use a bare revert() to save bytecode; the dapp checks conditions before sending. Deployment checks use readable messages. No proxy, no upgrade path.

08

User and owner functions

8.1 Functions anyone can call

FunctionWhat it doesRequirements
stake(poolId, amount0Desired, amount1Desired, slippageBps, referrer)Mints a V3 position on the pool's stake range from your tokens and stakes it.Approve Zxo for both tokens. Pool open and healthy. Fewer than 21 positions. Slippage ≤ 5,000 bps.
harvest(tokenId, poolId, action)0 claims rewards, 1 collects trading fees, 2 does both.You own the position. Pool healthy.
unstake(tokenId, poolId, acceptForfeit, to)Returns the position NFT to to (address(0) = you).You own the position. 24 hours since staking. Pending ≤ 0.000001 or forfeit accepted. to can receive NFTs.
updatePool(poolId, updateAll)Accrues rewards and re-targets the rate for one or all pools.None
pool.increaseObservationCardinalityNext(n)A V3 pool function, not Zxo. Enlarges the pool's price history so the 15-minute average is always available. set-observations.py picks a size for the chain.None. The size can only grow, so it cannot be used to break a pool.

8.2 Read functions

FunctionReturns
queryData(tokenId, poolId, 0, 0)Time of the last rate change
queryData(tokenId, poolId, 1, 0)Pending rewards for a position
queryData(0, poolId, 2, index)Pool config: 0 maxNormalAPR, 1 decimals, 2 bonus-year end, 3 max30dRate, 4 core LP id, 5 packed value. Other indexes revert.
queryData(0, poolId, 3, 0)Current APR in bps
queryData(0, poolId, 4, 0)(healthy, sqrtPriceX96, tick); healthy needs the spot tick and the 15-minute average in range. (false, 0, 0) when no price is available
ZxoOracle.twapTick(pool) / inRange(pool, lower, upper)The 15-minute average tick (ok = false when history is missing), and whether it sits strictly inside a range
queryData(tokenId, poolId, 5, 0)(canUnstakeWithoutForfeit, pending, threshold)
getUserData(user, dataType)0: positions and ids · 1: ids and pool ids · 2: all
getTargetAprsByIds(poolIds)Target APR per pool
poolInfo(i), poolConfig(i), stakedPositions_(pid, id), userReferrer_(a), totalGlobalStakes()Raw storage getters

8.3 Owner functions

ContractFunctionEffect and limits
ZxocreatePool(...)New token and pool. maxNormalAPR ≤ 320%. Pair decimals must match (6, 8 or 18). Max 255 pools.
ZxoinitializePool(poolId, slippageBps, isCore)Once each: core LP (price must equal the launch price), then core stake (token must be finalized).
ZxoupdatePoolConfig(poolId, 0, v)Target APR, clamped to the current max.
ZxoupdatePoolConfig(poolId, 1, v)Referral rate between 3% and 30%.
ZxoupdatePoolConfig(poolId, 2, v)Lower the reward rate only. Lasts until the next re-target. Any other type reverts.
ZxocollectFeesFromCoreLP(poolId)Sends core LP trading fees to the treasury. Liquidity is not touched.
ZxoV3DeployerstartV3pool, finalizeToken, burnTokens, setZxoContractSeed external DEXes until finalized. ZXO_CONTRACT can be set once.
ZxoTreasurysweepToken, sweepAll, scheduleDestination, executeDestinationChange, lockDestinationForeverSweeps to the destination. Destination changes wait 7 days and can be locked permanently.
09

Tokenomics

Each pool has its own reward token, a standard ERC-20 with burn support. The name, symbol and core allocations are set by the owner when the pool is created. Only the Zxo contract can mint, and it only mints in three situations.

supply = coreLP + coreStake minted to Zxo at creation, locked in positions + v3StartAmnt − burnedAtFinalize seeding, remainder burned + Σ claimed rewards minted on claim (user + referral shares) − holder burns
There is no fixed maximum supply. Emissions are bounded instead by the per-pool rate caps (max APR and max 30-day amount), which are fixed at creation.

Where value flows

  • Trading fees (0.01%) on user positions go to the stakers who own them.
  • Core LP fees go to the treasury.
  • Referral share (3–30% of each claim) goes to the staker's referrer, or the treasury by default.
  • Pair tokens bought by the core LP stay locked in it.
  • Zxo charges no fee on principal and no deposit or withdrawal fee.

Utility and incentives

  • The reward token trades against its pair asset near parity, backed on the upside by the core LP's sell wall.
  • Deposits above parity get up to 20% extra reward weight.
  • Holders of at least the chain's HOLDER threshold get 25% more bonus. The HOLDER token and threshold are set per chain; the token is only available on public DEXes.
  • Referrers earn a share of the rewards of everyone who names them.
Parity is a design target, not a pegThe core LP only sells above parity. There is no redemption mechanism and no protocol buy support below parity. The price can fall below the stake range, and while it does, claims are paused.

Planned core LP sizes range from about $5 million to $15 million per pool, with larger pools possible. At those sizes, moving the price from the launch tick into the core-stake range requires absorbing about 7% of the core LP (roughly $350,000 for a $5 million core).

10

Governance and owner powers

Zxo has no token voting. Each contract has a single owner address. The team intends to move ownership of Zxo and ZxoV3Deployer to a multisig before mainnet; both use two-step transfers, so a new owner must accept before control moves.

The owner can

  • Create pools and their tokens, up to 255
  • Initialize each pool's core LP and core stake once
  • Set the target APR within the pool's cap, including 0
  • Set the referral rate between 3% and 30%
  • Temporarily lower a pool's reward rate
  • Send core LP trading fees to the treasury
  • Seed external DEXes for a new token until it is finalized
  • Transfer or renounce ownership

The owner cannot

  • Touch staked NFTs, deposits or unclaimed rewards
  • Block, delay or pause unstaking
  • Withdraw or reduce core LP or core-stake liquidity
  • Mint reward tokens outside pool creation and emissions
  • Burn anyone's tokens
  • Raise a pool's max APR or 30-day cap after creation
  • Change ranges, fee tier, DEX or reward token
  • Upgrade the contracts or rescue any asset Zxo holds

If ownership is renounced, pools keep running with their last settings. No new pools can be created and core LP fees stay uncollected in the positions.

Treasury. The treasury owner can sweep tokens to a destination address. Changing that destination takes 7 days, and the destination can be locked permanently.

11

Security

Security review
8.5/10

Revision 2.5, AI-assisted, up from 6.6 before remediation.

Findings

20 resolved 8 accepted 0 open

Both High and three of four Medium findings fixed. The core-stake weighting is accepted as a design trade-off.

Next

Fork and invariant tests, a testnet launch rehearsal, and an independent human re-audit before mainnet.

11.1 User guarantees

GuaranteeWhy it holds
You can always withdraw after 24 hoursUnstake has no price check and no owner gate. Accounting totals are guarded so a drift can never block exits.
Only you control your positionEvery action checks the stored owner. Zxo never approves anyone to move its NFTs.
Your NFT is only released by unstakeUnstake deletes the record before the transfer; no other function moves position NFTs.
Core liquidity cannot be pulledNo function transfers, burns or reduces core positions.
A failed withdrawal changes nothingIf the NFT transfer fails, the whole unstake reverts and your position stays staked.

11.2 Threat model

ThreatProtectionStatus
Reentrancy through tokens, NFTs or recipient contractsnonReentrant on every mutating entry point; checks-effects-interactions; external call last in unstakeProtected
Owner extracting launch tokens through a fake DEXFinalization burns the remainder and closes seeding; staking cannot open before it. Users verify isFinalized and each seeding DEX.Mitigated
Pool pre-initialized at a wrong priceCore LP init reverts unless the pool price equals the launch price.Protected
DEX fork with a different slot0 layoutPrice read decodes only the two fields every fork shares; failures report unhealthy.Protected
Flash swap to harvest while the price is out of rangeThe health gate also requires the 15-minute average tick in range, which one transaction cannot moveProtected
Wrong or swapped addresses in a chain libraryThe Zxo constructor and ZxoVerify check every address on-chain; a bad build cannot deployProtected
Non-standard tokens such as USDTSafeERC20 transfers and forceApprove. Fee-on-transfer and rebasing pair tokens are not supported.Protected
Contract stakers that cannot receive NFTsto parameter on unstakeProtected
Flash swap to raise one stake's bonusThe bonus uses the deposit's token mix at the spot price. It is capped at 20% (25% with HOLDER), emissions stay capped and principal is not at riskAccepted
Price history not enlarged on a new poolRun set-observations.py once per pool. Until then stake and harvest pause; unstake and funds are unaffectedOperational
Core stake takes an unclaimed share of emissionsUsers earn somewhat below the target APR; gap shrinks as TVL growsAccepted
Owner key compromiseOwner cannot reach user funds; multisig planned; two-step transfersOperational

11.3 Accepted risks and design choices

  • Spot-based bonus. The health gate uses both the spot price and a 15-minute average. The stake bonus still uses the deposit's token mix at the current price.
  • Core-stake dilution. The contract-owned core stake counts toward TVL and reward weight but never claims; its trading fees cannot be collected.
  • Bonus curve. The bonus reaches its cap about 20 ticks above parity, by design.
  • Deadlines. Mint deadlines are set at execution time; price protection comes from the slippage minimums.
  • No rescue path. Dust below the refund threshold and NFTs sent directly to Zxo cannot be recovered. This keeps the owner away from every asset Zxo holds.
  • Referrers. Any address can be a referrer. Naming the Zxo contract loses that user's referral share; naming yourself skips the split.
  • Holder bonus. The HOLDER token can be bought, used for a stake and sold again. This is intended.
  • Forfeiture. Unstaking forfeits pending rewards. Rewards are paid only through harvest, while the pool is healthy.

11.4 Compiler

Contracts are compiled with solc 0.8.36 on the legacy pipeline (via-IR off), optimizer 200 runs, EVM version Shanghai. The three known bugs listed for 0.8.36 require via-IR, recursion, custom errors in require, or deleting an element of a memory bytes array. Zxo uses none of these.

12

Gas efficiency

ChoiceSaves
Bare revert() without messagesBytecode and deployment gas
One shared event with an action code, emitted through one internal functionBytecode; keeps Zxo under the 24,576-byte limit
Deployment checks in the constructorReadable errors with no runtime bytecode cost
The oracle is read only when the spot check passesAn external call on unhealthy pools
Packed structs (uint96 rate, uint40 times, uint16 settings) and one packed config word per poolStorage reads and writes
Rewards minted on claimNo reward funding transfers or balance bookkeeping
Chain addresses as compile-time constantsNo storage reads for external addresses
Dust below thresholds kept instead of refundedA token transfer on most stakes
Rate re-targeting at most once per hourRepeated storage writes
Bounded loops (21 positions per address, 255 pools)Predictable worst-case gas
uint8 pool ids, unchecked counters where overflow is impossibleArithmetic overhead
13

Interfaces and dependencies

DependencyUsed for
OpenZeppelin Contracts 5.xERC20, ERC20Burnable, Ownable, Ownable2Step, ReentrancyGuard, SafeERC20, IERC721, IERC721Receiver, IERC20Metadata
Uniswap V3 NonfungiblePositionManager (or fork)createAndInitializePoolIfNecessary, mint, collect, NFT custody
Uniswap V3 Factory (or fork)getPool for the pair at fee 100
V3 poolslot0 read by low-level call (price and tick only); observe read by ZxoOracle for the 15-minute average; increaseObservationCardinalityNext at launch
HOLDER tokenbalanceOf for the holder bonus
StandardsERC-20, ERC-721 and the ERC-721 receiver hook

Project interfaces: IZxo, IZxoToken, IZxoV3Deployer, IZxoOracle, Iv3PosManager, IUniswapV3. Libraries: ZxoLib (ticks, bonus, packing, validation) and one ChainLib per chain.

14

Deployment

14.1 Networks

NetworkChain idDEXPosition manager
PulseChain369LibertySwap0x19b1347900840e7a299F339868881750918FAD91
Ethereum1Uniswap V30xC36442b4a4522E871399CD717aBDD847Ab11FE88
Arbitrum42161Uniswap V30xC36442b4a4522E871399CD717aBDD847Ab11FE88
Optimism10Uniswap V30xC36442b4a4522E871399CD717aBDD847Ab11FE88
Polygon137Uniswap V30xC36442b4a4522E871399CD717aBDD847Ab11FE88
Base8453Uniswap V30x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1
BNB Chain56PancakeSwap V30x46A15B0b27311cedF172AB29E4f4766fbE7F4364
Unichain130Uniswap V30x943e6e07a7E8E791dAFC44083e54041D743C46E9
Robinhood Chain4663Uniswap V30x73991a25C818Bf1f1128dEAaB1492D45638DE0D3
PulseChain Testnet943Testconfigured for testing

Zxo contract addresses will be published at Zxo.Farm after each deployment is verified. Only use addresses from that source.

14.2 Deployment order

StepAction
1Deploy ZxoTreasury; set ZXO_TREASURY in the chain library
2Deploy ZxoOracle; set ZXO_ORACLE in the chain library
3Deploy ZxoVerify(false, true) to check the factory, position manager, HOLDER token and threshold, treasury and oracle on-chain
4Deploy ZxoV3Deployer; set ZXO_V3_DEPLOYER in the chain library
5Deploy ZxoVerify(true, true) to confirm the deployer as well
6Deploy Zxo (its constructor repeats the checks); call ZxoV3Deployer.setZxoContract(Zxo) once
7Transfer ownership to the multisig
Per poolAfter the core LP is initialized, run set-observations.py once so the 15-minute average is available

14.3 Verification

The build compiles each contract once with solc --standard-json. The deployed bytecode and the explorer's verification input come from that single compile, and the input embeds every source file under its import name, so any explorer can reproduce the bytecode without local files. After deploying, the deploy script compares the on-chain code with the build (immutable values masked) and records the deployment.

Anyone can check a deployment by compiling the published standard-json-input.json with solc 0.8.36 and comparing the runtime bytecode with the chain. This has been confirmed for Zxo: the input compiles with no local files and reproduces the build bytecode.

16

Roadmap

Milestones are planned and may change. Dates will be announced at Zxo.Farm.

Phase 1 · Protocol and review Complete

  • Core contracts, chain libraries and deployment verifier
  • Security review and remediation to Revision 2.5, with no open findings
  • 15-minute average price gate (ZxoOracle) and on-chain deployment checks
  • Reproducible build, verification and deployment tooling

Phase 2 · Testing In progress

  • Contracts deployed on PulseChain Testnet; full launch rehearsal
  • Fork tests on every target DEX and invariant tests
  • Static analysis (Slither or Aderyn) and an independent human re-audit of the changes
  • Dapp trust panel: finalization, seeding DEXes, burned NFTs

Phase 3 · Mainnet launch

  • PulseChain mainnet deployment with multisig ownership
  • First pools and public verification of all contracts

Phase 4 · Multichain

  • Ethereum, Arbitrum, Base, Optimism, Polygon, BNB Chain, Unichain and Robinhood Chain

Phase 5 · Growth

  • Additional pools per chain, up to 255 per deployment
  • Seeding new V3 DEXes for new pools as they launch
Appendix

Reference

A. Constants

ConstantValueMeaning
LP_FEE_TIER1000.01% V3 fee tier
DEFAULT_TARGET_APR1,00010% initial target
MAX_NORMAL_APR32,000320% max per pool (×2 in year one)
BONUS_APR_ENDS365 daysDoubled APR cap window
RATE_COOLDOWN1 hourMinimum time between rate changes
MIN_REF_RATE_BPS / MAX_REF_RATE_BPS300 / 3,000Referral share 3% – 30%
MAX_BONUS_BPS2,00020% bonus cap (25% with HOLDER)
BONUS_THRESHOLD0.5 × 10¹⁸Bonus starts above 50% paired
HOLDER_THRESHOLDper chain, ≥ 10¹⁸HOLDER balance for +25% bonus (ChainLib)
ZxoOracle.WINDOW900 s15-minute average for the health gate
MAX_USER_STAKES21Positions per address
MAX_SLIP5,00050% max slippage
MIN_UNSTAKE10¹²Pending above this needs forfeit
MIN_DUST_18 / _8 / _610¹² / 10 / 1,000Smallest refund sent back
Unstake lock1 dayFrom stake time
PRECISION10¹⁸Reward accumulator scale

B. Tick ranges for every pair type

PairZxo isLaunch tickCore LPCore stakeUser stake
18 / 18token010[11, 300][31, 300](−300, 300)
18 / 18token1−10[−300, −11][−300, −31](−300, 300)
18 / 6token0−276,314[−276,313, −276,024][−276,293, −276,024](−276,624, −276,024)
18 / 6token1276,314[276,024, 276,313][276,024, 276,293](276,024, 276,624)
18 / 8token0−230,260[−230,259, −229,970][−230,239, −229,970](−230,570, −229,970)
18 / 8token1230,260[229,970, 230,259][229,970, 230,239](229,970, 230,570)

C. Code references

TopicLocation
StakingZxo.stake, ZxoLib._requireStakeValidations
Bonus and TVLZxoLib._calcBonusAndTotal
RewardsZxo._updatePool, Zxo._pendingRewards, Zxo._claimRewards
EmissionsZxo._performAutoAdjust, Zxo.updatePoolConfig
HealthZxo._readSlot0, Zxo._poolRatio, ZxoOracle.twapTick, ZxoOracle.inRange, ZxoLib._getStakeTick
Deployment checksZxo constructor, ZxoVerify
LaunchZxo.createPool, Zxo.initializePool, ZxoLib._getCoreTicks, ZxoV3Deployer.startV3pool, ZxoV3Deployer.finalizeToken
Config packingZxoLib.packPoolConfig and unpack*

D. Glossary

Tick
A 0.01% price step in a V3 pool. Price = 1.0001^tick.
Concentrated liquidity
Liquidity placed only within a chosen price range, earning fees only while the price is in that range.
Position NFT
The ERC-721 token a V3 position manager issues for each liquidity position.
Parity
One reward token equals one pair token in value.
Core LP
The protocol's permanently locked, single-sided reward-token position above parity.
Core stake
A contract-owned staked position that opens staking for a pool.
Healthy
The pool's current tick and its 15-minute average tick are both strictly inside the user stake range.
TWAP
Time-weighted average price. Zxo uses the average tick over the last 15 minutes.
Observation cardinality
How many price-history slots a V3 pool keeps. Raised once per pool so the 15-minute average is always available.
Weight
A position's liquidity plus any bonus. Rewards are shared by weight.
accRewardPerShare
Running total of rewards per unit of weight, scaled by 10¹⁸.
Basis point (bps)
0.01%. 10,000 bps = 100%.
TVL
Sum of staked deposits in 18-decimal units, counted 1:1.
HOLDER token
A separate token, set per chain, whose holders at or above the chain's threshold get a larger stake bonus.
Seeding
Small single-sided positions on other V3 DEXes that set the initial price there.
Finalization
Burning the deployer's remaining launch tokens and closing seeding for that token.
Forfeit
Giving up pending rewards when unstaking without harvesting first.
ChainLib
The per-chain library of addresses compiled into the contracts.