What determines your fee income
Shield uses Uniswap v3’s model for sharing fees among active liquidity. Your earnings depend on the fees charged during each part of a swap and your share of the liquidity active at that point. Unlike Uniswap v3’s fixed pool fee, a Shield pool’s current trading fee can change while your position stays in the same pool. An authorized fee reduction can also lower the fee on an individual swap. The protocol takes its share from the fee actually charged, and the rest goes to active liquidity. If the fee is reduced to zero, neither the protocol nor liquidity providers earn fees from that swap. Check the pool’s current trading fee and protocol share when estimating earnings. Multiplying volume by the displayed fee alone can overstate what liquidity providers receive. Earned fees stay collectible until you withdraw them; they do not automatically increase your liquidity.Read what your position can collect
Prerequisites: Your authenticated mainnetclient for Typescript or dex for Python from Setup. Replace POSITION_TOKEN_ID with the position ID returned by mint or listed by shield-swap positions.
Run the steps in your language in order. These calls read your position; they do not submit a transaction.
- Typescript
- Python
position.state unavailable. That does not mean the position has no funds. Use the zero-liquidity fallback to read its settled balances.
How fees accrue on chain
The contract records your entitlement through cumulative fee growth. Only the liquidity active during a swap step shares that step’s LP fee. The accounting path is:Fee-bearing token
Fees accrue in the input token:zero_for_one = trueadvances token0 fee growth.zero_for_one = falseadvances token1 fee growth.
Trading fee and protocol share
The swap uses the current pool fee after any authorized fee reduction, expressed in fee pips, or parts per million. A step first calculates its integer total fee under the swap math. The configured protocol fraction then splits it:fee_protocol may be zero or 4 through 10. The floor remainder belongs to lp_fee.
Protocol fees accumulate in separate slot balances. They do not appear in LP fee growth and cannot be claimed by positions.
Global growth
Each swap step divides its LP fee among the liquidity active in that step. The contract records the result as cumulative Q128.128 fee growth for the input token. The 256-bit accumulator wraps by design. A decrease in its raw value does not mean fees were removed. See Q128.128 fee growth for storage, arithmetic, and runnable calculations.Liquidity epochs
A liquidity epoch is a period during which one active-liquidity set earns fee growth. Epoch boundaries include:- Minting active liquidity
- Increasing or decreasing active liquidity
- Crossing an initialized tick
- Reinitializing a removed boundary
Tick outside growth
Each initialized tick stores outside growth for token0 and token1. At a crossing, each checkpoint becomes the token’s global growth minus its previous outside growth, with 256-bit wrapping subtraction. Both tokens flip, even though only the input token generated a fee in the current step. Crossing reverses which side of the tick is considered outside, so both reference frames must change. The current step’s fee is added before the flip. Its LP fee therefore belongs to the pre-crossing active-liquidity set. A crossing also completes when the tick price equals the user’s price limit. The slot finishes with post-crossing liquidity even if the swap stops at that exact price.Growth inside a range
The contract subtracts growth below and above your range from global growth. The current tick determines which side of each boundary counts as outside. At the lower tick, the range is active. At the upper tick, it is not. The snapshot calculation applies those boundary conditions for each token.Position checkpoint
A publicPosition stores its liquidity, an inside-growth checkpoint for each token, and settled tokens_owed0/1 balances. Newly accrued fees come from the growth since those checkpoints, multiplied by position liquidity and rounded down to whole native base units.
Pending fees are not continuously written to tokens_owed. They become settled integer amounts during increase, rebalance, decrease, or collect.
Settlement events
This ordering prevents new liquidity from sharing old growth and prevents removed liquidity from losing growth already earned.
Burn performs no fee settlement. It removes a position only after liquidity and both owed balances are zero.
Calculate the worked example
Run the Typescript block in an.mts file with Node.js 24 or later, or the Python block in a .py file with Python 3.11 or later. No SDK, account, or network connection is required.
The inputs are illustrative: one swap step charges 160 native token0 base units, the protocol takes 4/16, and your position supplies 15 of the 60 active liquidity units throughout that step. It already has 7 base units owed.
- Typescript
- Python
Rounding and dust
Global growth floors once per fee-bearing step. The aggregate shortfall is less than one native token base unit because active liquidity is au128 and the fee-growth scale is 2^128.
Position settlement floors again. Its shortfall is less than one native base unit for that position and token at that settlement.
Advancing the checkpoint discards that position fraction. It is not stored in a residual field and is not reassigned to another LP.
The contract has no generic sweep for the resulting surplus. collect_protocol can withdraw only recorded protocol fees.
Institutional reconciliation should use:
Public observability
Global growth, tick outside growth, position checkpoints, position liquidity, and settled owed amounts are public mapping state. Collection requests are also public. ThePositionNFT record authorizes the holder without storing the owner’s address in Position. The position’s range and fee economics remain public.
An observer with a token ID and a consistent state snapshot can estimate pending fees. The estimate does not identify the private immutable withdrawal address.
Off-chain calculation rules
Read the slot, both boundary ticks, and the position from one deployment at one state height. Keep all arithmetic in integers and apply token decimals only for display. The Q128.128 examples decode both limbs, apply wrapping subtraction and range boundaries, and check the resulting balances againstu128 limits.