Pre-launch. The protocol is not publicly live yet. This documentation describes the contracts as implemented; deployed addresses are published at launch.

Dual Pools

On V4-family chains every graduated token gets two pools: a primary pool against the chain's native asset, and a secondary pool against $LP24. The secondary is funded at graduation from the same envelope and exists to tie every launched token to the protocol token.

Not on Gnosis

Uniswap V3 has no hook mechanism, so Gnosis runs a single native↔token pool managed by GraduationManager. Everything on this page applies to Base, BSC, Polygon and Robinhood.

The two pools

PrimarySecondary
Pairnative ↔ tokentoken ↔ $LP24
OwnerPrimaryHookSecondaryHook
FeeDynamic — 120 bps (1.2%) / 80 bps (0.8%) / 40 bps (0.4%)Flat 60 bps (0.6%)
Fee currencyNativeThe token
Fee destinationSplit 30 / 10 / 60Burned to 0x…dEaD
Fee directionBothBoth
CreatedAt graduation, automaticallyBy a later permissionless call
Liquidity90% of envelope10% of envelope

How the secondary is funded

Funding and pool creation are deliberately separated. Graduation must not depend on a second pool's price being sane at that instant, so the graduation transaction only delivers and earmarks the funds.

At graduation, inside createPositionAndRegister:
  ├─→ IERC20(token).transfer(SECONDARY_HOOK, SECONDARY_TOKEN_RAW)
  ├─→ SECONDARY_HOOK.notifyReserved(token) {value: SECONDARY_NATIVE_RAW}
  │     └─→ nativeEarmark[token] += msg.value
  └─→ log SecondaryReserved(token, deployer)

Later, by anyone:
  SecondaryHook.createSecondary(token)
  ├─→ assert PrimaryHook.tokenInfo(token).deployer != 0
  ├─→ assert no secondary exists yet
  ├─→ assert balanceOf(self) >= SECONDARY_TOKEN_RAW
  ├─→ assert nativeEarmark[token] >= SECONDARY_NATIVE_RAW  (and consume it)
  ├─→ swap earmarked native → $LP24 on the primary $LP24 pool
  ├─→ initialize token/$LP24 pool at the resulting ratio
  └─→ mint a full-range position owned by this contract

Both transfers happen in the graduation transaction, which gives the invariant the secondary relies on: if the SecondaryHook holds a token, it holds that token's native share too. The native push is asserted rather than fire-and-forget — a failure there would mean the secondary could never be funded for that token.

Why the earmark is per-token

An earlier design inferred the native share from a token balance: "40M of this token is here, therefore 0.4 native is here too." That holds only if token balances can arrive solely from the PrimaryHook, which nothing enforces — anyone can send ERC-20s to a contract. nativeEarmark[token] is credited only by notifyReserved(), and only the PrimaryHook may call it, so an attacker who sends a graduated token's 40M to the SecondaryHook cannot create a pool using some other token's native.

Acquiring the $LP24 side

The secondary pool needs $LP24, not native, so createSecondary swaps the earmarked native for $LP24 on the primary $LP24 pool first. This is an ordinary swap — the SecondaryHook is just another swapper and pays the PrimaryHook's normal tiered fee. There is no fee-exemption path between the two contracts, which is what keeps them decoupled: the PrimaryHook needs no knowledge of the SecondaryHook beyond the address it pushes funds to.

Slippage on that swap is bounded at 200 bps (2%), and the expected output is derived from the $LP24 pool's reserves rather than its spot price:

native_reserve ≈ L * 2^96 / sqrtP
plft_reserve   ≈ L * sqrtP / 2^96
expected_out   ≈ plft_reserve * dx / (native_reserve + dx)

A reserve-derived expectation is manipulation-resistant. A sandwich that merely shifts spot price leaves real reserves untouched, so the expected figure barely moves; to push realized output below the floor an attacker would have to actually remove liquidity — expensive and visible. A revert here is harmless: the custodied funds and the earmark stay put and the call can be retried.

Secondary pool fees

The secondary charges a flat 60 bps (0.6%) in the launched token, in both directions, and burns it immediately. There are no tiers, no reinvestment, and no deployer or protocol share.

  • Token is the inputbeforeSwap takes the fee from the specified amount.
  • Token is the outputafterSwap takes the fee from the output delta.

In both cases take() sends the fee straight to the dead address. take() debits the caller — this hook — regardless of recipient, which clears the delta the callback returns, so a single call both burns the tokens and settles the pool's accounting.

Why a secondary pool at all

Every graduated token has a permanent, protocol-owned market against $LP24. Trading it burns supply of the launched token, and routing between two venues for the same asset creates arbitrage that flows through the $LP24 pools. Because $LP24 lives at the same address on all five chains and those chains settle in four different native assets, price divergence across chains is arbitrageable — and that arbitrage passes through pools whose fees feed the protocol's buy-and-burn.