Uniswap V3 LP Position Tracking: A Position Is an NFT, Not a Balance (2026)
Uniswap V3 LP Position Tracking: A Position Is an NFT, Not a Balance (2026)
Reviewed by Wag3s Editorial Team — verified against the Uniswap V3 concentrated-liquidity model (NFT positions, price ranges, separately-accrued fees) · Last reviewed May 2026
Uniswap V3 LP Position Tracking: A Position Is an NFT, Not a Balance
What sets Uniswap V3 apart from earlier AMMs is that a liquidity position is no longer a fungible LP token but a unique concentrated-liquidity NFT bound to a price range, earning fees only while in range, with a two-asset composition that shifts as price moves and fees that accrue in a separate, uncollected pocket. A balance-based tracker cannot represent any of that. This guide is the V3 model and how to track it, one spoke of the wider multi-wallet aggregation problem applied to concentrated liquidity.
What a V3 position actually is
- A V3 LP position is a non-fungible NFT, not a fungible LP token, so no single ERC-20 balance represents it.
- It earns fees only while the price is inside its range; out of range it stops earning and skews to one asset.
- The two-asset split is not fixed and rebalances with price within the range.
- Fees accrue separately and must be collected; they are not auto-compounded into principal.
- Tracking follows the position-NFT lifecycle (mint, modify, collect, burn), not a balance.
- Tax is jurisdiction-specific: track the mechanics, confirm tax separately. The decomposition discipline mirrors DeFi position reconciliation, and the fee-accrual lesson echoes DeFi lending position tracking.
A position is an NFT with a range
In Uniswap V2 an LP position was a fungible LP token, readable like a balance. In V3 a position is a non-fungible NFT: a distinct token with its own price range and its own accrued, uncollected fees. No single ERC-20 balance represents it. Tracking must therefore follow the position-NFT identity through its lifecycle of mint, modify, collect and burn, not read a fungible balance. This is the object-identity discipline applied to liquidity.
The NonfungiblePositionManager contract
Uniswap V3 positions are managed by the NonfungiblePositionManager contract (0xC36442b4a4522E871399CD717aBDD847Ab11FE88 on Ethereum mainnet). Each position is an ERC-721 token with a tokenId. The positions(tokenId) view function returns:
token0,token1— the pair assetsfee— the fee tier (500, 3000, or 10000 basis points)tickLower,tickUpper— the price range bounds (in tick units)liquidity— the current liquidity amountfeeGrowthInside0LastX128,feeGrowthInside1LastX128— fee growth checkpointstokensOwed0,tokensOwed1— uncollected fees
A complete position snapshot requires reading all of these fields, not just the liquidity.
Range decides everything
A V3 position only earns fees while the current price is within its range. The consequences for tracking:
- In range, the position earns fees and the underlying is a price-dependent mix of the pair.
- Out of range, it stops earning and the composition skews toward one asset.
A tracker that ignores in-range and out-of-range state misreads both the fee accrual and the asset mix. Range is not a detail; it is the position's economic state.
Computing in-range/out-of-range status
The current tick is available from the Pool contract's slot0() function as currentTick. Compare:
- If
tickLower ≤ currentTick < tickUpper→ position is in range. - If
currentTick < tickLower→ position holds only token0 (price too low). - If
currentTick ≥ tickUpper→ position holds only token1 (price too high).
The two-asset split moves
The position's underlying is not a fixed pair of amounts. As price moves within the range the position rebalances between the two assets; out of range it can be entirely one asset. So "the position holds X of A and Y of B" is only valid at a point in time and must be recomputed as price moves, the impermanent-loss and rebalancing effect made explicit by the range.
Fees are separate
Accrued fees in V3 are tracked separately and must be explicitly collected; they are not auto-compounded into the position's principal. A tracker must:
- capture uncollected accrued fees as their own component;
- treat collect as its own event;
- avoid assuming fees sit inside the position's principal amounts.
Folding fees into principal, or missing them until collect, is the recurring V3 error, the same "separate the reward stream" rule as yield farming tracking.
Step-by-step: how to track a Uniswap V3 position
- Enumerate owned position NFTs. Call
balanceOf(owner)on the NonfungiblePositionManager, thentokenOfOwnerByIndex(owner, i)for each index to get all token IDs. Alternatively, subscribe to theTransferevent filtered for the owner address as recipient. - Read position details for each tokenId. Call
positions(tokenId)to retrieve tick bounds, liquidity, fee tier, andtokensOwed. - Compute the current asset composition. Using the Pool's
slot0().sqrtPriceX96, the tick bounds, and the liquidity amount, apply Uniswap V3's liquidity math to computeamount0andamount1at the current price. Libraries like@uniswap/v3-sdkimplementgetAmount0ForLiquidityandgetAmount1ForLiquidity. - Compute uncollected fees. Fees are not stored as a running total — they are computed from fee growth variables. Query the Pool's
feeGrowthGlobal0X128andfeeGrowthGlobal1X128, the tick-levelfeeGrowthOutsidevalues, and compare to the position'sfeeGrowthInside0LastX128. The Uniswap V3 SDK providescomputePoolAddressand fee-growth helpers. - Track lifecycle events. Subscribe to
IncreaseLiquidity,DecreaseLiquidity, andCollectevents on the NonfungiblePositionManager filtered by the tokenId. - Mark the position as closed on burn. When
DecreaseLiquidityremoves all liquidity andCollectis called with zero remaining, the position is effectively closed. The token may still exist as an NFT with zero liquidity. - Apply the jurisdiction cost-basis method to mint/burn/collect events; flag fee characterisation for adviser confirmation.
Common errors and how to fix them
Error 1 — Reading V3 liquidity as a V2 LP-token balance. Uniswap V2 LP tokens represent proportional shares of a pool. V3 liquidity units are a different concept — they encode the position's tick range and are not comparable across positions. A tracker that treats V3 liquidity as an ERC-20 balance to value at a pool share price will produce a wrong number. Fix: compute the token composition from the liquidity + tick math, not from a pool share formula.
Error 2 — Missing uncollected fees in the position value. The tokensOwed0 and tokensOwed1 fields in positions() reflect fees that have been checkpointed but not collected. Additional fees accrued since the last checkpoint require the fee growth calculation described above. A tracker reading only tokensOwed under-counts uncollected fees. Fix: implement the full fee-growth computation or use an indexer that does it.
Error 3 — Booking a DecreaseLiquidity call as the full disposal. DecreaseLiquidity reduces the position's liquidity and checkpoints the owed tokens, but does not transfer tokens to the wallet. The actual transfer happens only on Collect. A tracker that records the disposal at DecreaseLiquidity time will show the tokens as received before they actually arrive. Fix: record the liquidity reduction at DecreaseLiquidity and the proceeds receipt at Collect.
Error 4 — Assuming one fee tier per token pair. Uniswap V3 has separate pools for the same pair at different fee tiers (0.05%, 0.3%, 1%). A position in USDC/ETH at 0.05% is a different NFT than one in USDC/ETH at 0.3%. Aggregating both as "USDC/ETH LP" without distinguishing fee tier will conflate different positions. Fix: track fee tier as a component of the position identity.
Tax is jurisdiction-specific
Whether mint, modify, collect or burn is a taxable event, and how fee income is treated, is framework- and jurisdiction-specific and must not be assumed (see cost-basis methods). The NFT, range and fee mechanics are the tracking layer; the tax characterisation is a separate, adviser-confirmed question.
Practical guidance
- Model the position as an NFT, not a fungible LP balance, and follow its lifecycle.
- Track in-range and out-of-range state, since it drives both fees and composition.
- Recompute the two-asset split as price moves; it is not static.
- Capture uncollected fees separately and treat collect as its own event.
- Confirm tax treatment of mint, modify, collect and burn per jurisdiction.
- Reconcile the position-NFT to the protocol with an audit trail.
Choosing a tool for Uniswap V3 positions
Koinly and Zerion both claim to model V3 NFT positions, but the concentrated-liquidity design is where coverage often breaks down. Before you trust a V3 number, confirm the tool:
- enumerates the position NFTs on the
NonfungiblePositionManagerand readspositions(tokenId), rather than treating V3 liquidity as a V2-style pool share; - derives the in-range or out-of-range status from the pool's
slot0()tick and recomputes the two-asset split at the current price; - computes uncollected fees from the fee-growth variables (not just the checkpointed
tokensOwed) and recordsCollectas the proceeds event, separate fromDecreaseLiquidity; - distinguishes the same pair at different fee tiers (0.05%, 0.3%, 1%) as different positions.
Treating a V3 position like a fungible V2 LP token is the structural error, and it shows up as a wrong valuation and missing fee income.
How Wag3s handles it
Wag3s Folio models each Uniswap V3 position as an NFT through its mint, modify, collect and burn lifecycle, reflects in-range and out-of-range state and the price-dependent asset split, captures uncollected fees as a separate component, and surfaces the result for the jurisdiction-specific tax characterisation. See the Folio product page.
Further reading
- DeFi Position Reconciliation
- Liquidity Pool Accounting
- Pendle PT/YT Tracking
- Yield Farming Tracking
- Crypto Cost Basis Methods 2026
- DeFi Lending Position Tracking
Sources
- Uniswap — NonfungiblePositionManager reference: V3 positions are wrapped as non-fungible tokens with a price range, modified via
increaseLiquidity/decreaseLiquidity. - Uniswap — Collecting Fees: accrued fees are tracked separately and collected by an explicit
collectcall rather than auto-compounded into principal.
Lido stETH vs wstETH Tracking: The Rebase Is the Whole Problem (2026)
stETH is a rebasing token — your balance grows daily as staking rewards accrue. wstETH is non-rebasing — the balance is fixed and value rides an increasing exchange rate. Why the rebase is a stream of balance changes a portfolio must characterise, and why wstETH moves the same value into a price.
Pendle PT/YT Tracking: One Asset Split Into Two, With a Clock (2026)
Pendle splits a yield-bearing asset into a Principal Token and a Yield Token. PT redeems 1:1 for the underlying at maturity and trades at a discount before; YT carries the future yield and decays to zero at maturity. Why PT+YT is a time-bound decomposition a portfolio must not value at par early.
Every chain, integration, and competitor mentioned in this article gets its own page — coverage detail, comparison signals, and the audit trail your finance team needs.
- Chain
Base
Coinbase L2 with USDC-native treasury flows.
View page - Integration
Uniswap
V2 / V3 / V4 swap and LP decoding.
View page - Chain
Ethereum
ERC-20, DeFi, gas, restaking — the largest ecosystem.
View page - Chain
Solana
SPL tokens, native stake, Jupiter, Metaplex NFTs.
View page - Integration
NetSuite integration
Mid-market and enterprise crypto subledger.
View page - Integration
QuickBooks integration
SMB GL with daily JE sync.
View page