In Uniswap v2, current pool k is the product of the stored token reserves, while kLast is a checkpoint written after a liquidity mint or burn when the protocol fee is enabled. Swaps can increase current k without updating kLast. A later mint or burn can issue LP tokens to feeTo from positive square-root growth, provided fees are enabled and kLast is nonzero. The checkpoint is not a running fee balance or a token price.
A pair can therefore report a larger reserve product than its saved checkpoint without anything being broken. The useful task is to reconstruct which event last moved each number, then apply the fee formula before accounting for a new deposit or withdrawal.
Read the checkpoint in the right implementation
This article follows the original Uniswap v2-core v1.0.1 implementation, pinned to commit 4dd5906. Its pair contract stores reserves and kLast separately. A fork can change the fee constants or update sequence, so matching a function name is insufficient.
Keep three quantities distinct:
| Quantity | How to obtain it | What it describes |
|---|---|---|
| Current reserve product | reserve0 × reserve1 from one state | Product of the pair's stored reserves |
kLast | Read the pair's stored value | Protocol-fee checkpoint, or zero |
| Actual balance product | Multiply the two token contracts' balances at the pair | Product of actual balances, which may differ from the reserve product |
After adjusting for token units, the reserve ratio describes a token exchange rate. The reserve product supplies the growth comparison. Neither is a dollar valuation. The constant-product versus StableSwap comparison explains the trading curve. Here the question is how v2 measures growth between liquidity events.
Uniswap's current protocol-fee documentation, checked on 28 October 2026, describes protocol fees as active on v2 in its rollout. For a particular pair and historical block, read its factory's feeTo. The disabled-fee cases below explain the behavior when that address is zero.
A swap and a deposit move different records
A swap updates reserves but does not run the protocol LP-token mint. A successful liquidity mint or burn calculates that mint against the reserves from before the liquidity event, then records the product after the event when fees are enabled. This order keeps a new proportional deposit out of the measured growth interval. The v2 whitepaper's protocol-fee section explains why collection waits for deposits and withdrawals.
| Event in the original pair | Stored reserves | Protocol-fee checkpoint |
|---|---|---|
| Successful swap | Updated to ending balances | Unchanged |
| Successful liquidity mint or burn, fees on | Updated after the liquidity change | Replaced with ending reserve product |
| Successful liquidity mint or burn, fees off | Updated after the liquidity change | Cleared if previously nonzero |
sync() | Updated to current balances | Unchanged |
Factory changes feeTo | No immediate pair update | No immediate pair update |
The contract's reserve and fee update paths establish those transitions. A Sync event alone does not mean a new fee checkpoint was written: swaps also update reserves through the internal routine that emits it.
This is specific to v2. A v3 tick is a price coordinate, covered in Uniswap tick versus price. V3 position fees require the separate accounting described in fee growth versus fees owed. Neither field can substitute for v2's checkpoint.
Why the formula uses square roots and new supply
Let S be total LP-token supply before the protocol mint. Let a be the square root of current reserve product and b the square root of kLast.
When fees are enabled, kLast is nonzero and a > b, the original formula calculates:
m = S × (a − b) / (5a + b)
Here m is newly minted LP-token supply assigned to feeTo. The whitepaper derives the denominator by requiring the protocol's share after minting to equal one sixth of the growth share of current liquidity. In continuous arithmetic, that relationship is:
m / (S + m) = (a − b) / (6a)
The denominator includes the new tokens because minting dilutes existing shares. Taking one sixth of the raw change in k, or dividing a square-root change by six and calling it LP tokens, does not reproduce this relationship. Uniswap v2 whitepaper, section 2.4.
For exact contract amounts, use integer arithmetic. The pinned Math library returns an integer square root; the fee division also truncates. Small positive growth can produce zero new LP units. A successful fee-enabled liquidity event still writes its new checkpoint, so a dashboard should not assume every fractional estimate will eventually become a mint.
Reconcile one complete liquidity event
Consider a hypothetical established pair. All quantities below are raw integer contract units, including LP supply; token decimals change their displayed presentation. There is no dollar valuation or return forecast.
At the saved checkpoint, reserves are 1,300,000 units of each token and total LP supply is 1,300,000. Supply includes the original 1,000 locked raw LP units. Fees remain enabled throughout this example.
Assume ordinary swaps subsequently leave 1,430,000 units of each token. These are stipulated state inputs, not a reconstruction from reported volume. No liquidity changes, direct token transfers or rebases occur between checkpoints.
| Input | Saved checkpoint | Before the next deposit |
|---|---|---|
| Reserve of each token | 1,300,000 | 1,430,000 |
| Reserve product | 1,690,000,000,000 | 2,044,900,000,000 |
| Square root of reserve product | 1,300,000 | 1,430,000 |
| LP supply before protocol mint | 1,300,000 | 1,300,000 |
Stored kLast | 1,690,000,000,000 | 1,690,000,000,000 |
Current k has risen 21%, while its square root has risen 10%. A percentage change in k would therefore overstate the square-root growth used by the fee formula.
The numerator is 1,300,000 × 130,000, or 169,000,000,000. The denominator is 5 × 1,430,000 + 1,300,000, or 8,450,000. Division produces exactly 20,000 new raw LP units, with no remainder.
Immediately after this protocol mint, supply is 1,320,000, while token reserves remain unchanged. The protocol owns 20,000/1,320,000, or approximately 1.51515% of the pool before the new provider's deposit is accounted for.
Check that result independently. The growth share of current liquidity is 130,000/1,430,000, approximately 9.09091%. One sixth is 1.51515%, matching the protocol's diluted ownership. This is not one sixth of the entire pool.
Now a provider adds 143,000 units of each token, equal to 10% of existing reserves. The liquidity calculation uses the supply after the protocol mint:
143,000 × 1,320,000 / 1,430,000 = 132,000
The new provider receives 132,000 raw LP units. Ending reserves are 1,573,000 of each token, and total supply is 1,452,000. The provider owns exactly one eleventh of the enlarged pool, approximately 9.09091%.
Finally, kLast becomes 1,573,000 × 1,573,000, or 2,474,329,000,000. That jump includes the deposit, but the deposit did not enter the fee calculation. The checkpoint now starts the next interval from the enlarged pool.
Disabled fees require an observed reset
The factory's pinned setter implementation changes feeTo without calling every pair. Consequently, a nonzero kLast can remain stored after the factory fee destination becomes zero.
Follow the pair's next successful mint or burn:
- It reads the zero destination, skips the protocol mint and clears a nonzero checkpoint.
- If fees are later enabled while the checkpoint is zero, the next successful liquidity event establishes a new post-event baseline without charging growth against zero.
- Subsequent fee-enabled liquidity events can compare against that baseline.
Those branches appear in the pair's fee calculation routine. Zero kLast therefore does not mean zero reserves, no trading activity or a failed pool.
There is a further consequence of the call order: if the factory switches off and back on before a pair executes a liquidity event, that pair never runs the clearing branch while fees are off. Its old checkpoint can survive, and the next enabled calculation may compare growth across that interval. Do not infer a clean excluded interval from configuration-change timestamps alone; replay the intervening pair calls.
Growth is not automatically fee income
The ordinary-swap example deliberately excludes balance changes outside trading. Uniswap's v2 troubleshooting documentation describes rebasing tokens that change balances independently and positive rebases that create surplus outside reserve accounting.
If surplus is incorporated through sync(), reserves can rise without a swap, while the checkpoint remains unchanged. The next fee calculation can therefore encounter growth that was not generated by ordinary trading. Label a reconstructed number “protocol LP mint implied by these inputs” until the event history supports a stronger attribution.
Nor does larger k establish investor profit. Token prices, inventory changes, dilution and transaction costs affect what a holder owns and what it is worth. The pool fee APR versus net return reconciliation handles that economic comparison. Adding a separate v2 fee estimate to a reserve-based redemption value can count the same embedded fees twice.
Save the evidence beside the calculation
For a reproducible checkpoint audit, retain the block number and hash. When reconstructing an event, also record its transaction position: end-of-block reads can hide earlier calls in the same block. Save the following worksheet beside the result:
| Record | Check before calculating |
|---|---|
| Chain, pair, factory and implementation | Confirm the actual contract and fee constants |
| Token identities and raw reserves | Use both reserve values from the same state |
kLast, LP supply and feeTo | Keep checkpoint, ownership denominator and fee state together |
| Actual token balances | Investigate any difference from stored reserves |
| Intervening swaps, liquidity events and configuration changes | Identify which call last wrote or cleared the checkpoint |
| Integer roots, numerator, denominator and minted units | Preserve truncation and avoid rounded display inputs |
| Event being analyzed | Separate pre-event fee inputs from post-event reserves |
When checking a withdrawal, remember the same ordering: the protocol mint can enlarge supply before the withdrawal's proportional token amounts are calculated. Using the old supply can overstate output.
Before trusting a fee display, reconstruct one successful mint or burn from its starting reserves through the protocol mint to its ending checkpoint. That single event should explain the new LP supply, the liquidity change and exactly why kLast moved.
