Pre-auditThree days of senior audit time for $1,500, credited in full toward your Zealynx audit.3 audit days, credited toward your auditSee how →
Burn Your Whole Uniswap V3 Position and Receive Nothing

Burn Your Whole Uniswap V3 Position and Receive Nothing

9 min
Uniswap V3 · Part 9 of 9
On this page▾

TL;DR — Quick Summary

  • burn moves nothing. It converts your liquidity into tokensOwed — a debt the pool owes you — and collect is the only function that moves those tokens out.
  • Burn is where you can watch both halves of V3's rounding policy at once: mint rounded up against you, burn rounds down against you, and the dust stays in the pool.
  • The two-phase split buys three things: one exit door, principal and fees unified as the same kind of debt, and composability the core never has to anticipate.
  • Burn with amount zero is a supported primitive — a "poke" — because fee accounting is lazy: feeGrowthGlobal ticks on every swap but your balance only changes when something calls position update.
  • collect does not settle fees. It reads tokensOwed exactly as it finds it. Collect without poking first and a year of fees stays invisible — not lost, invisible.

There is a video walkthrough of this section, with the code written on screen, on the channel. The article below covers the same ground.

Introduction

Here is the thing that catches everyone coming from V2.
You have a position. You want out. You call burn, with your full position size.
Your wallet receives exactly nothing.
Not a revert. Not an error. It works perfectly, and no tokens move.
This is section 9 of Building Uniswap V3, and once you see why, the design is genuinely elegant.

What burn actually does

It runs _modifyPosition with a negative liquidity delta, so every piece of machinery from the previous section reruns in reverse.
Gross liquidity shrinks on both boundary ticks. Liquidity net adjusts. Ticks that hit zero flip out of the bitmap and get cleared. Global active liquidity drops, if the range was in range.
And crucially, position update settles every bit of fee growth since your last touch into tokensOwed.
The amounts then come back negative, because the pool owes you now rather than the other way round. Burn flips the sign and adds both to your tokensOwed.
So your funds absolutely moved. They moved from being liquidity to being a debt the pool owes you. They just did not leave the pool.

Both halves of the rounding policy, side by side

Burn is the one place you can see V3's entire rounding discipline in both directions at once.
The signed helpers round down for negative deltas. So when the pool pays out, it pays a wei less. When it charged you on mint, it charged a wei more.
Mint rounded up against you. Burn rounds down against you. The dust stays in the pool.
Every rounding boundary in this protocol leans the same way, and this is the function where you can watch it happen on both sides of the same position.

Why split it into two phases at all

Why not just transfer? The design buys three things, and each is worth naming separately.
One exit door. Principal and fees leave through collect and through nothing else. When you audit a V3 fork, the token-outflow surface for LP funds is exactly one function. Compare that to a protocol where five different functions can move user balances, each needing its own accounting proof.
Fees and principal unify. After a burn, your freed principal is bookkept identically to your trading fees. Both are just tokensOwed. collect needs no concept of what kind of value it is moving, which removes an entire category of "did we credit this to the right bucket" bug.
Composability. The periphery can burn, then collect to any recipient, in patterns the core never has to anticipate. collect takes a destination; burn always acts on your own position. The split is what lets one be delegated and the other not.
Building something that holds user funds? The Founder Security Sprint is a focused engagement for teams shipping their first protocol. And if you want to work through design questions like "why two functions instead of one" every week, that is Zealynx Insiders.

The free primitive: burn zero

Burn with an amount of zero is supported, deliberately, and called out in the function's own documentation: use it to trigger a recalculation of the fees owed to a position.
You need it because fee accounting in V3 is lazy.
feeGrowthGlobal ticks up on every single swap. But your tokensOwed only changes when something calls position update — and only mint, burn, and pokes do that.
So a position nobody has touched for a year has a year of fees that exist only as "growth since my last checkpoint". They are real, they are computable, and they have not been written down.

The part that bites people

collect does not settle fees. It reads tokensOwed exactly as it finds it.
Collect without poking first and a year of fees stays invisible. Not lost — the growth accumulators still hold it, and a later poke will surface it. But invisible at the moment you asked, which is exactly when an integrator's UI reports a balance to a user.
That gives two canonical flows:
  • Full exit: burn everything, then collect(max, max).
  • Claim fees without exiting: burn zero, then collect(max, max).

Tracing a zero burn

It is worth following one through the previous section's code, because it is neat.
A zero liquidity delta means the tick-update branch of update position is skipped entirely — no bitmap churn, no per-tick cap check.
But getFeeGrowthInside and position update still run. And position update's 'NP' guard requires the position to actually have liquidity, so you cannot poke something that does not exist.
The three-case amount logic is skipped too, behind the same gate. So burn returns zero and zero, and the only thing that happened is the fee settlement.
That is a lot of machinery arriving at exactly one side effect, on purpose.

Collect: clamp, decrement, transfer

Three steps.
The caller asks for any amount, and the pool pays the minimum of what you asked for and what you are actually owed. Which is why the idiom for "everything" is to request type(uint128).max. There is no separate collect-all function, and no failure mode for over-asking — the clamp turns an impossible request into a correct one.
Then, per token: reduce the debt, and only then transfer. Effects before interactions, textbook order.
And notice what collect deliberately does not do: it does not touch fee growth, does not update any tick, and does not settle anything. It is a pure payout of a number somebody else computed. That narrowness is what makes "one exit door" a claim you can actually verify.

What you have after this

Liquidity can leave the pool, in two phases, through exactly one door. And you have the poke — the idiom every V3 integrator needs and many discover late.
Next in this series: SwapMath, one step of a swap, where the rounding rules meet the fee formula and the branch people most often get wrong.

FAQ

1. Why does burn return no tokens in Uniswap V3?
Because burn converts liquidity into a debt rather than paying it out. It reruns the position machinery with a negative delta, settles accrued fees, and adds both the freed principal and the fees to the position's tokensOwed. The tokens stay in the pool until collect is called, which is the only function that transfers them out.
2. What is a "poke" in Uniswap V3 and why do you need one?
A poke is a burn with an amount of zero. It is supported deliberately and documented as a way to recalculate fees owed. It is needed because fee accounting is lazy: the global accumulator advances on every swap, but a position's tokensOwed only updates when mint, burn or a poke runs. Without a poke, uncollected fees exist only as unrealised growth since the last checkpoint.
3. Does collect claim your fees in Uniswap V3?
No. collect reads tokensOwed exactly as it finds it and transfers up to that amount. It performs no fee settlement at all. Calling collect on a position that has not been touched in a long time returns only what was already recorded, and the unsettled fees remain invisible until a poke or a real burn writes them down.
4. Why does Uniswap V3 split withdrawal into burn and collect?
Three reasons. It gives LP funds exactly one exit door, which shrinks the outflow surface an auditor has to reason about. It unifies principal and fees into the same tokensOwed debt, so collect needs no notion of what kind of value it moves. And it enables composability: collect takes a recipient while burn always acts on the caller's own position, so the periphery can combine them in ways the core never anticipates.
5. How do you withdraw everything from a Uniswap V3 position?
Burn your full liquidity, then call collect with type(uint128).max for both tokens. The pool clamps the request to what you are actually owed, so over-asking is the correct idiom rather than an error. To claim fees without exiting, burn zero first — that pokes the position into settling its fees — then collect the same way.

Glossary

TermDefinition
Fee GrowthA global accumulator of fees per unit of liquidity, settled by differencing two snapshots.
Rounding DirectionThe deliberate per-operation choice of rounding so truncation always favours the pool.
Liquidity NetThe signed liquidity applied to the active total when the price crosses a tick.
Active LiquidityThe liquidity currently in range and backing trades at the pool's present price.
Tick BitmapA sparse bitmap recording which ticks are initialized, so the swap loop finds the next in one read.
Callback ValidationProving the caller of a protocol callback is the genuine protocol contract before releasing funds.

Write it yourself

This article is section 9 of Building Uniswap V3, a free 18-section module on the Zealynx Academy. In this one you write burn and collect — the sign flip, the debt accumulation, the clamp and the effects-before-interactions order — and a 15-test suite checks each of them, including the zero-burn poke.
If you would rather learn it alongside other engineers, with a live build session every week and office hours where you bring your own code, that is Zealynx Insiders.

Working auditors in your corner, all year

Zealynx Insiders: weekly live sessions, 1:1 advisory, pair-auditing, and Krait runs on your code, from an audit firm that publishes its reports.

Carlos (Bloqarl)
Written by

Founder at Zealynx

Keep reading
Uniswap V2 Router Explained — addLiquidity Line by Line
DeFi analysisOct 16, 2023

Uniswap V2 Router Explained — addLiquidity Line by Line

10 min read
How to Build Compound V2 From Scratch (18 Sections, Line by Line)
DeFi analysisMay 28

How to Build Compound V2 From Scratch (18 Sections, Line by Line)

13 min read
Uniswap v4 hook attacks: 4 exploit patterns with PoCs
DeFi analysisMay 25

Uniswap v4 hook attacks: 4 exploit patterns with PoCs

25 min read