ZXO.Farm · Multichain V3 farm Technical whitepaper · September 2026

ZXO Protocol

A trust-minimized concentrated-liquidity farm. Users mint a Uniswap V3–style position into the protocol, earn a capped stream of the pool’s own token, collect trading fees from that NFT, and always take the NFT back after a one-day lock. The owner cannot seize principal and cannot raise emission caps after a pool is created.

ProductMultichain V3 liquidity farm
SiteZxo.Farm
Solidity0.8.36 · shanghai · 200 runs
StatusPre-mainnet source
LicenseBusiness Source 1.1 · change 2029-09-23
AuditSource review 26 Sep 2026

1. Project overview

ZXO exists so a person can provide concentrated liquidity on a Uniswap V3–compatible pool and earn two things at once: the pool’s trading fees (the ordinary V3 LP return) and a protocol emission paid in a freshly deployed ZxoToken that is itself one side of that pool.

The farm does not take custody of loose tokens as a vault balance the owner can rewrite. On stake, ZXO mints a fee-tier 100 position NFT to itself, books the position, and later returns that exact NFT. Rewards are minted at claim time against immutable per-pool ceilings. There is no pause switch on unstake.

Farm

Stake a V3 range around parity. Earn a capped APR in the pool’s own token.

Harvest

Claim emissions and/or collect Uniswap trading fees while the pool tick is inside the user range.

Exit

After 24 hours the NFT goes back to you or to any address you name. Pending rewards are optional to forfeit.

2. Problem statement

Most on-chain “farms” fail one of four tests:

ZXO is built against those four. Principal is an NFT the owner cannot transfer. Caps are packed at createPool and have no raise path. Unstake ignores the health gate. Emissions are weighted toward in-range, two-sided liquidity via a composition bonus, and claims themselves require the pool tick to sit inside the user range (spot tick, not TWAP — accepted risk M-02).

3. Solution summary

Each pool is a pair of a new ZxoToken and an external pair token (USDC-class 6 decimals, BTC-class 8, or 18). The protocol seeds a large single-sided core LP just off 1:1 as a sell wall, books a narrower core-stake position that soaks part of the emission weight and never claims, then opens user staking on a ±300-tick band around parity at the 0.01% fee tier.

Reward rate is retargeted at most once per hour from TVL and a target APR, then clipped by two immutable caps: a bps cap (maxNormalAPR, doubled during the first year) and a 30-day token cap (max30dRate). Users who pair more of the external token than the ZXO token, above a 50% paired-ratio floor, receive extra reward weight (not extra minted principal) up to 20%, or 25% if they hold the chain’s holder token above threshold.

4. Architecture

Five contracts and one math library, compiled once per chain against a ChainLib of constants.

Zxo

Farm. Ownable2Step + ReentrancyGuard. Holds user NFTs and both core NFTs. Only minter of each pool token. Chain-id locked at construction.

ZxoToken

OZ ERC-20 + burnable. Owner is permanently the farm. coreLP, coreStake, pairing are immutable. No supply cap in the token; caps live in Zxo.

ZxoTreasury

Fee sink and default referral destination. Owner sweeps to destination. Destination change is 7-day delayed and can be locked forever. Ownable is single-step.

ZxoV3Deployer

Owner seeds single-sided positions on arbitrary V3 position managers and sends the NFT to 0x…dEaD. finalizeToken burns leftovers and closes seeding. setZxoContract is one-shot.

ZxoVerify

Constructor probe: factory owner, fee-tier 100 tick spacing, NPM.factory pointer, holder supply, treasury marker = 1, optional deployer marker = 2.

ZxoLib + ChainLib

Ticks, packing, bonus, caps, dust. Per-chain constants: chain id, holder token, treasury, deployer, factory, position manager.

System diagram

User wallet tokens + holder token Zxo farm stake · harvest · unstake owns user + core NFTs V3 Position Manager mint · collect · NFT ZxoToken mint only by Zxo V3 Factory / Pool slot0 health gate ZxoTreasury core fees + default ref ZxoV3Deployer seed other DEXes → burn ZxoVerify constructor sanity

5. Networks and DEX hosts

One binary per chain. update-chain-lib.py selects the library, writes treasury / deployer / holder addresses, and rewrites the import. The constructor reverts if block.chainid does not match. PulseChain testnet and mainnet are Ethereum forks and retain the original Uniswap V3 factory and position-manager addresses; Pulse mainnet farming is configured against Liberty V3.

LibraryChain idPrimary V3 host
EthereumLib1Uniswap V3
OptimismLib10Uniswap V3
BNBChainLib56Pancake V3
UnichainLib130Uniswap V3
PolygonLib137Uniswap V3
PulseChainLib369Liberty V3
PulseChainTestLib943Uniswap V3 (fork copy)
RobinhoodLib4663Uniswap-style
BaseLib8453Uniswap V3
ArbitrumLib42161Uniswap V3
SepoliaTestLib9991Uniswap on virtual mainnet

Fee tier is always 100 (0.01%). Pair tokens may have 6, 8, or 18 decimals. ZXO token is always 18.

6. Pool lifecycle

1. createPool token + caps 2. seed DEXes startV3pool 3. finalize burn leftovers 4. core LP sell wall @ launch 5. core stake users may stake
  1. createPool deploys the token. If withoutPool is false, it registers the pool, packs caps, mints coreLP + coreStake to Zxo and v3StartAmnt to the deployer. If withoutPool is true, it only deploys a token — used to grind CREATE addresses until the token hex sorts below the pair so DEXScreener-class UIs render buys as buys. The live farm is a later call with withoutPool = false and forceLower = true.
  2. Owner seeds satellite DEX pools via startV3pool. NFTs go to the burn address on a widened tick range.
  3. finalizeToken sets isFinalized and burns any remainder. User staking cannot open until this is true.
  4. initializePool(..., isCore=true) creates the canonical pool at the launch sqrt price if needed, requires slot0 to equal that price, and mints the single-sided core LP. Token id is packed into poolConfig.
  5. initializePool(..., isCore=false) mints the narrower core-stake NFT, books it as owned by Zxo, increments stakes from 0 to 1. From that moment stake() is open.

Create and core-init are separate transactions (contract-size limit). A third party can initialize the factory pool at a wrong price and grief that pair; the team accepts that. A dead pair is abandoned and a new token/pool is opened.

7. User functions and flows

Everything a non-owner can call that changes state: stake, harvest, unstake, updatePool. Views: queryData, getUserData, getTargetAprsByIds, plus the public mappings.

7.1 Stake

stake(uint8 poolId, uint256 amount0Desired, uint256 amount1Desired,
      uint256 slippageBps, address referrer)

7.2 Harvest

harvest(uint64 tokenId, uint8 poolId, Action_ action)

Action_ on this path is only Claim (0), CollectFees (1), or ClaimAndCollect (2). Health gate applies. You must own the position and it must still have liquidity.

Rewards keep accruing while the pool is out of range. The gate only blocks when you can take them. Unstake does not require the gate.

7.3 Unstake

unstake(uint64 tokenId, uint8 poolId, bool acceptForfeit, address to)

7.4 Public poke

updatePool(poolId, updateAll) accrues and, if the one-hour cooldown has passed, retargets rewardRate. Anyone may call it.

User journey

Approve pair + ZXO stake() harvest when in range wait 24h unstake NFT

8. Owner capabilities

Owner is trusted for bootstrap and for policy inside the packed caps. Owner is not trusted with user principal. After every planned pool is live, ownership of Zxo may be renounced; users keep stake / harvest / unstake.

ContractFunctionCanCannot
ZxocreatePoolDeploy a token, optionally open a pool, mint bootstrap, set immutable capsRaise those caps later
ZxoinitializePoolOne-shot core LP, then one-shot core stakeRe-init, init after users have staked
ZxoupdatePoolConfig type 0Set target APR, clipped to cap / 2× cap in year oneExceed packed maxNormalAPR
ZxoupdatePoolConfig type 1Set referral rate in [3%, 30%]Go outside that band
ZxoupdatePoolConfig type 2Strictly lower rewardRate, stamp cooldownRaise the rate by this path
ZxocollectFeesFromCoreLPCollect the packed core-LP NFT fees to treasuryCollect user NFTs; collect core-stake fees
Zxo——Pause, rescue dust/NFTs, seize positions, mint except via createPool bootstrap and public claims
DeployersetZxoContractOnceChange it later
DeployerstartV3poolSeed any V3 manager until finalizeSeed after isFinalized
DeployerfinalizeToken / burnTokensClose seeding, burn ERC20Burnable balancesUn-finalize
TreasurysweepToken / sweepAllSend balances to current destinationSend to an arbitrary address in the same call
Treasurydestination changeSchedule (7 days), execute, lock foreverChange after lock; instant change

9. Formulas

Amounts of the non-18-decimal pair token are scaled to 18 decimals before any ratio or TVL math: ×$10^{12}$ for 6 decimals, ×$10^{10}$ for 8.

9.1 Accrual (MasterChef)

Each pool stores an accumulator and a per-position debt. Time is wall-clock seconds. $R$ is rewardRate in token wei per second. $S$ is totalStaked (V3 liquidity plus bonus weight). Precision $P = 10^{18}$.

$$ \mathrm{acc}_{t} = \mathrm{acc}_{t_0} + \frac{(t - t_0)\, R\, P}{S} \qquad (S > 0,\; R > 0) $$ $$ \mathrm{pending} = \frac{L \cdot \mathrm{acc}}{P} - D $$

where $L$ is the position’s booked liquidity (base V3 liquidity plus any stake bonus) and $D$ is rewardDebt, set to $L \cdot \mathrm{acc} / P$ at stake and after every claim. If $S = 0$ the accumulator is frozen and lastRewardTime is stamped to now.

9.2 Auto-adjust of $R$

At most once per hour, using TVL $V =$ totalTokens (normalized notionals, not liquidity units) and target $A$ in bps:

$$ R_{\mathrm{target}} = \frac{V \cdot A}{31\,536\,000 \cdot 10\,000} $$

clipped by two packed ceilings that cannot be raised:

$$ A_{\max} = \begin{cases} 2 \cdot \mathrm{maxNormalAPR} & t \le t_{\mathrm{create}} + 365\,\mathrm{d} \\ \mathrm{maxNormalAPR} & \text{otherwise} \end{cases} $$ $$ R \le \min\!\left( \frac{V \cdot A_{\max}}{31\,536\,000 \cdot 10\,000},\; \frac{\mathrm{max30dRate}}{2\,592\,000} \right) $$

maxNormalAPR itself cannot exceed 32 000 bps (320%). Default target at create is 1 000 bps (10%). Type-2 owner update may only lower $R$, not raise it; auto-adjust can raise $R$ back up to the clip.

Observed APR versus TVL, for dashboards:

$$ \mathrm{APR_{bps}} = \frac{R \cdot 31\,536\,000 \cdot 10\,000}{V} \qquad (V \ne 0) $$

Users earn below headline $A$ while the core stake holds a share of $S$ and never claims (accepted M-03). Auto-adjust does not close that gap; it shrinks as user TVL grows.

9.3 Stake bonus (reward weight, not extra tokens deposited)

Let $a$ be the normalized ZXO (local) side actually minted into the NFT and $b$ the normalized pair side. $T = a + b$. If either side is zero, or $b \le a$, bonus is 0. Otherwise:

$$ \rho = \frac{b}{T} $$

Bonus starts only when the paired ratio clears 50%:

$$ \mathrm{bps} = \min\!\left(2000,\; \mathrm{floor}\!\left(\frac{(\rho - 0.5)\cdot 59\,538}{10^{18}} \cdot 10^{18}\right)\right) $$

In code: bonusBps = (pairedRatio - 5e17) * 59538 / 1e18, then cap at 2 000. That constant reaches the 20% cap at a paired ratio of about 53.36% — roughly 20 ticks above parity. Then:

$$ B = \frac{L_{\mathrm{v3}} \cdot \mathrm{bps}}{10\,000} $$

If the caller’s holder-token balance ≥ $3\,690\,000\,000 \times 10^{18}$:

$$ B \leftarrow B + \frac{B}{4} $$

so the max weight bonus is 25%. $B$ is added to booked liquidity. TVL (tokens) stays $T$. The bonus changes how emissions are split, not how many tokens the user deposited.

Worked example. $L_{\mathrm{v3}} = 1\,000\,000$, $\rho = 0.5336$, holder below threshold. Excess above 0.5 is $0.0336$. $0.0336 \times 59538 \approx 2000$ bps. $B = 200\,000$. Booked liquidity = $1\,200\,000$. TVL unchanged. With holder, $B = 250\,000$.

9.4 Referral split on claim

$$ \mathrm{ref} = \left\lfloor \mathrm{pending} \cdot r / 10\,000 \right\rfloor \qquad \mathrm{user} = \mathrm{pending} - \mathrm{ref} $$

$r \in [300, 3000]$. If referrer is unset-as-self or equals the claimer, $\mathrm{ref} = 0$ and the user receives all of pending. Treasury earns only when the stored referrer is the treasury (the default for address(0)).

9.5 Slippage on mint

$$ \mathrm{amountMin} = \mathrm{amountDesired} \cdot (10\,000 - s) / 10\,000 $$

Users: $s \le 5000$. Owner init/seed: $s$ is a frontend parameter; $s = 10000$ is a zero minimum; $s > 10000$ reverts.

9.6 Health gate

Pool is healthy iff the current tick is strictly inside the user stake band:

$$ \tau_{\mathrm{low}} < \tau_{\mathrm{now}} < \tau_{\mathrm{high}} $$

18-dec band is $[-300, 300]$. 6-dec and 8-dec bands are the documented parity tick ± 300, flipped when ZXO is token0. Spot tick, not TWAP.

10. Tokenomics

There is no single protocol token. Each pool has its own ZxoToken, minted by that pool’s farm and burned by holders via ERC-20 burn. Utility is the farm reward and the inventory side of the V3 pair.

Bootstrap

coreLP and coreStake are chosen at create, required non-zero, minted once to Zxo. v3StartAmnt is minted to the deployer for satellite seeds and burned at finalize.

Emissions

Only minted on harvest claim. Bounded by $A_{\max}$ and max30dRate. Unclaimed pending on unstake is never minted.

Fees

User NFT trading fees belong to the user. Core-LP fees go to treasury. Core-stake fees are locked in the NFT (accepted).

Holder boost

A known 18-decimal token per chain. Balance ≥ 3.69B units adds 25% on top of the composition bonus. Decimals are not checked on-chain.

Address mining (withoutPool + forceLower) is how a pool gets ZXO as token0, so buy flow charts as buys on DEX screens.

11. Technical design

State

LocationWhat it holds
poolInfo[]Accumulator, TVL, liquidity weight, token addresses, reward token, $R$, timestamps, target APR, referral bps, stake count
poolConfig[poolId]Packed word: maxNormalAPR (16) | decimals (8) | maxBonusEnds (40) | max30dRate (128) | corePositionId (64)
stakedPositions_[pid][tid]debt, liquidity, tokens, owner, stakeTime, poolId
userStakedRefs_Up to 21 (poolId, tokenId) pairs per user
userReferrer_ / referralsPerUser_First-write referrer and counts

Events

One packed event: ZxoEvent(user, data0, data1, data2, Action_) with actions Claim, CollectFees, ClaimAndCollect, Bonus, Unstake, AutoAdjust, Stake, Referrer.

Access and reentrancy

Owner functions are onlyOwner. Every state-changing external on Zxo and the deployer is nonReentrant. Unstake performs the ERC-721 transfer last. ZxoToken disables transferOwnership and renounceOwnership with custom errors. Zxo itself can still renounce Ownable2Step — that ossifies admin, not users.

Upgradeability

None. No proxy. New behavior is a new pool or a new deploy. Caps cannot move up.

12. Security considerations

Pre-mainnet source review dated 26 September 2026. Classification: no principal-theft finding. A follow-up review will match creation transactions and deployed bytecode after launch.

Threat model — what is in scope

Protections

Accepted risks (team + review)

IDSummary
M-01Factory pool can be pre-initialized at a wrong price. Size limit blocks atomic create+init. Griefing is paid; pair can be abandoned.
M-02Health gate and bonus read spot tick. A swap-in, act, swap-out costs two 0.01% fees. No TWAP. Principal untouched; caps still bind mint.
M-03Core stake takes emission weight and never claims. Core-stake fees are locked. Users earn below headline APR until user TVL dominates.
L-01Treasury commission only if referrer is left default. Self or third-party referral is intended.
L-03Bonus ramp 59538 → 20% at ~53.36% paired ratio.
L-04Deadline is now + 20 minutes at execution. Price protection is slippage.
L-05No rescue for dust or stray NFTs. Owner has no path to assets in Zxo.
L-07Treasury Ownable is single-step.
L-08Deployer takes any position manager. Mitigated by finalize-before-stake; users can inspect seeds first.
L-09No pause. That is the anti-freeze property.
L-10Owner slippage uncapped; core-stake does not re-read slot0. Defended by the core-LP sell wall and the owner frontend.

Fee-on-transfer, rebasing, and fee-on-collect pair tokens are unsupported. Pair tokens should be canonical 6/8/18-decimal stables or majors. Holder token decimals are assumed 18.

13. Gas efficiency

14. Interfaces and dependencies

15. Deployment

Addresses are not canonical until the post-launch matching audit. Process per chain:

  1. Run update-chain-lib.py, compile with solc 0.8.36.
  2. Deploy ZxoTreasury.
  3. Deploy ZxoVerify(false). Require isVerified().
  4. Deploy ZxoV3Deployer. Write its address into the lib.
  5. Recompile verify + Zxo. Deploy ZxoVerify(true), then Zxo.
  6. setZxoContract once.
  7. Move treasury destination, wait 7 days, optionally lock. Move ownership to the production key. Record creation hashes for the second audit.

ZxoVerify will refuse a zero factory, NPM, holder, treasury, or (when asked) deployer, a factory without fee 100, or an NPM whose factory() pointer does not match.

16. Governance

No token vote, no Governor, no proxy admin. Policy knobs after launch are target APR (clipped), referral rate (banded), and lower-only reward rate. New pools require owner. When the protocol will not open more pools, Zxo ownership is renounced. Treasury destination is a separate, lockable sink. Production expectation: multisig or controlled key on Zxo and the deployer (2-step), careful single-step key on treasury.

18. Roadmap

  1. Finish per-chain testnet farms (ChainLib + Verify + seed + finalize + core).
  2. Mainnet deploy per chain with recorded creation hashes.
  3. Post-launch audit matching source to bytecode and addresses.
  4. Open user staking pool by pool once core walls are in.
  5. Optional ownership renounce after the planned set of pools is live.

19. Appendix

Glossary

TermMeaning
Core LPProtocol-owned single-sided V3 NFT just off parity. Fees collectable to treasury.
Core stakeProtocol-owned narrower NFT booked as a stake. Never claims. Fees locked.
Health / fair ratioCurrent tick strictly inside the user band.
TVL / totalTokensNormalized token notionals used to size $R$.
totalStakedLiquidity weight used to split $R$, including bonus.
Holder tokenExternal 18-dec token that adds +25% on the composition bonus.
FinalizeIrreversible close of deployer seeding for one ZxoToken.

Tick sheet (user band and core)

Pair decimalsParity tickUser bandCore (ZXO lower)Landed tick (ZXO lower)
180[-300, 300][11, 300]10
8230270parity ± 300near parity, 20-tick tighten on core stakesee sqrt-price constants
6276324parity ± 300near parity, 20-tick tighten on core stakesee sqrt-price constants

Source map