Skip to main content
Your position earns a share of the fees charged on swaps while its liquidity is active.

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 mainnet client 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.

Step 1: Find the position

Step 2: Read the balances

All amounts are integer native token base units. The SDK’s uncollectedFees0/1 already include the owed balances.

Step 3: Format the amounts

Continue with balances from step 2. Read each token’s decimals from its metadata.
For a token with 6 decimals, 7 base units already owed, and 30 newly accrued, the output is:
The Typescript formatter may omit trailing zeros. Your output uses the pool’s token symbols and balances. Owed balances can include settled fees, withdrawn principal, and unused deposits. The difference between total collectible and already owed is only the fees accrued since the last checkpoint. Do not report the full collectible balance as fee income. Lifetime fee income requires transaction history. Treat these live reads as estimates: the SDK can read mappings at different heights while swaps continue. For reconciliation, use a consistent state snapshot. Refresh before collecting. In the Python SDK, missing boundary ticks after a full decrease can leave 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 = true advances token0 fee growth.
  • zero_for_one = false advances token1 fee growth.
The output token’s global accumulator does not advance merely because the pool paid output in that token. For multi-hop swaps, the rule applies separately to every hop. A route can therefore generate fees in different token programs at different pools during one transaction.

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
The implementation stores no pool-level residual from fee-growth division. Each step records the floor at 128-bit fractional precision using the liquidity active in that step. The sub-Q128 remainder is discarded, so it cannot cross into a later epoch and be credited to a different active-liquidity set. This rounding can leave surplus in the contract.

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 public Position 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.
Both print:
This position earns one quarter of the LP fee in this step. For a live position, use growth inside its range since its last checkpoint, as shown in Calculate from a state snapshot.

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 a u128 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:
Read Rounding, overflow, and invariants.

Public observability

Global growth, tick outside growth, position checkpoints, position liquidity, and settled owed amounts are public mapping state. Collection requests are also public. The PositionNFT 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 against u128 limits.