# Streamia (Streaming Premia)

## Fees tracking in Uniswap

### Fees tracking per swap

Each Uniswap pool uses global accumulators called `feeGrowthGlobal0X128`

and `feeGrowthGlobal1X128`

to track the fees owed for each position in terms of `token0`

and `token1`

.

Every time a swap happens inside a Uniswap pool that does not change the price beyond a single tick, the accumulator is incremented by the amount of fees generated per unit of total liquidity present inside that tick.
If the swap crosses a tick, the `UniswapV3Pool`

stores the amount of fees collected up to that point for that specific tick range.

### Fees tracking per mint

When a new position is minted in `UniswapV3Pool`

, the users needs to specify a range defined by `tickLower`

and `tickUpper`

.
At minting time, the `UniswapV3Pool`

smart contract stores two values called `feeGrowthInside0LastX128`

and `feeGrowthInside1LastX128`

, which are given by the values of the global fee accumulators.

When a position is burnt, the amount of fees to be collected is computed from the `feeGrowthInside0LastX128`

and `feeGrowthInside1LastX128`

values stored when the position was minted and the current fees at the ticks given by `feeGrowthGlobal0X128`

and `feeGrowthGlobal1X128`

`def computeUniswapFees(tickLower, tickUpper, liquidity):`

# Read the last recorded feeGrowth quantities

(feeGrowthInside0LastX128, feeGrowthInside1LastX128) = pool.positions(keccak256(abi.encodePacked(msg.sender, tickLower, tickUpper)))

# Read the current feeGrowth quantities

(feeGrowthGlobal0X128, feeGrowthGlobal0X128) = (pool.feeGrowthGlobal0X128, )

# Compute the tokens owed for the amount of liquidity for that position

tokensOwed0 = ((feeGrowthGlobal0X128 - feeGrowthInside0LastX128) * liquidity) / FixedPoint128.Q128

tokensOwed1 = ((feeGrowthGlobal1X128 - feeGrowthInside1LastX128) * liquidity) / FixedPoint128.Q128

## Liquidity tracking in Panoptic

### Liquidity in "Chunks"

Each option position in Panoptic is created by moving liquidity in or out of the Uniswap smart contract. Each "chunk" of liquidity will have a few properties associated with it:

- token type
- lower tick
- upper tick
- liquidity

One extra parameter that is only present in Panoptic is the `tokenType`

, which refers to the type of token that was moved (either `token0`

or `token1`

), assuming the position was out-the-money.

Since multiple users can create the same position but each using a different size, each option position in Panoptic is "semi-fungible".
The set of (`tokenType`

, `tickLower`

, `tickUpper`

) defines a specific location in the UniswapV3Pool, and the `liquidity`

amount determines how many tokens have been moved.

### Short and Net liquidity

In Panoptic, we track two types of liquidity for each chunk:

`netLiquidity`

: Amount of liquidity currently sitting in Uniswap`removeLiquidity`

: Amount of liquidity that has been removed from Uniswap through the minting of long options

Together, these two quantities can be combined to compute the amount of `totalLiquidity`

that has been deposited to that chunk as:

`totalLiquidity = netLiquidity + removeLiquidity`

`_netLiquidity = {}`

_removeLiquidity = {}

def getAccountLiquidity(univ3pool, owner, tokenType, tickLower, tickUpper):

positionKey = keccak256(abi.encodePacked(univ3pool, owner, tokenType, tickLower, tickUpper))

netLiquidity = _netLiquidity[positionKey]

removeLiquidity = _removeLiquidity[positionKey]

## Fees tracking in Panoptic

### Net, Gross, and Owed fees (no spread)

Any liquidity that has been deposited in the AMM using the SFPM will collect fees over time, we call this the `gross`

premia. If that liquidity has been removed, we also need to keep track of the amount of fees that *would have been collected*, and we call this the `owed`

premia. The `gross`

and `owed`

premia are tracked per unit of liquidity by the `s_accountPremiumGross`

and `s_accountPremiumOwed`

accumulators.

Here is how we can use the accumulators to compute the `Gross`

, `Net`

, and `Owed`

fees collected by any position.

Let's say user A deposited `T`

at a specific tick range into Uniswap and user B later removed `S`

from that same tick. Because the netLiquidity left inside that tick range is only `(T-S)`

, the AMM will collect fees equal to:

`net_feesCollectedX128 = feeGrowthX128 * (T-S) = feeGrowthX128 * N`

where `N = netLiquidity = T-S`

.

Had that liquidity never been removed, we want the `gross`

premia to be given by:

`gross_feesCollectedX128 = feeGrowthX128 * T`

So we must keep track of fees for the shorted (ie. removed) liquidity `S`

so that the long premia exactly compensates for the fees that would have been collected from the initial liquidity.

`owed_feesCollectedX128 = feeGrowthX128 * S`

### Net, Gross, and Owed fees (with spread)

In addition to tracking the actual fees owed, we also want to include a small spread to be paid by the user that remove the liquidity. Specifically, we want:

`gross_feesCollectedX128 = net_feesCollectedX128 + owed_feesCollectedX128`

where

`owed_feesCollectedX128 = feeGrowthX128 * S * (1 + spread)`

A very opinionated definition for the spread is:

`spread = ν*(liquidity removed from that strike)/(netLiquidity remaining at that strike) = ν*S/N `

so that we get

`owed_feesCollectedX128 = feeGrowthX128 * S * (1 + ν*S/N)`

(Eqn 1)

For an arbitraty parameter ν bounded by 0 <= ν <= 1.

This way, the `gross_feesCollectedX128`

will be given by:

`gross_feesCollectedX128 = feesGrowthX128 * N + feesGrowthX128*ν*S*(1 + S/N)`

which, upon simplification, yields

`gross_feesCollectedX128 = feesGrowthX128 * T * (1 + ν*S^2/(N*T))`

(Eqn 2)

## Fee Accumulators

The two equations above represent the amount of fees paid by an option buyer (Eqn 1) and the amount of fees collected by all the liquidity that was deposited at that tick (Eqn 2). In the next two sections, we will describe how we constructed the accumulator that is used by the Panoptic Pool to keep track of these amounts per units of liquidity.

### Expressions for the owed premium accumulator

The `s_accountPremiumOwed`

accumulator tracks the `feeGrowthX128 * S * (1 + spread)`

term per unit of shorted liquidity `S`

every time the position touched:

`s_accountPremiumOwed += feeGrowthX128 * S * (1 + ν*S/N) / S`

+= feeGrowthX128 * (T - S*(1-ν))/N

+= feeGrowthX128 * T/N * (1 - S*(1-ν)/T)

So, the amount of owed premia for a position of size `S`

minted at time `t1`

and burnt at
time `t2`

is:

`owedPremia(t1, t2) = (s_accountPremiumOwed_t2-s_accountPremiumOwed_t1) * S`

= ∆feesGrowthX128 * S*T/N * (1 - S*(1-ν)/T)

= ∆feesGrowthX128 * S * (T - S + ν*S)/N

= ∆feesGrowthX128 * S * (N + ν*S)/N

= ∆feesGrowthX128 * S * (1 + ν*S/N) (same as Eqn 1)

This way, the amount of premia owed for a position will match Eqn 1 exactly.

### Expressions for the gross premium accumulator

Similarly, the amount of gross fees for the total liquidity is tracked in a similar manner by the `s_accountPremiumGross`

accumulator. However, since we require that Eqn 2 holds up-- ie. the gross fees collected should be equal to the net fees collected plus the ower fees plus the small spread, the expression for the `s_accountPremiumGross`

accumulator has be be given by the expression below, which includes a `S^2/T^2`

term (you'll see why in a minute):

`s_accountPremiumGross += feeGrowthX128 * T/N * (1 - S/T + ν*S^2/T^2)`

This expression can be used to calculate the fees collected by a position between times t1 and t2 according to:

`grossPremia(t1, t2) = ∆(s_accountPremiumGross) * T`

= ∆feeGrowthX128 * T^2/N * (1 - S/T + ν*S^2/T^2)

= ∆feeGrowthX128 * T * (T - S + ν*S^2/T) / N

= ∆feeGrowthX128 * T * (N + ν*S^2/T) / N

= ∆feeGrowthX128 * T * (1 + ν*S^2/(N*T)) (same as Eqn 2)

where the last expression matches Eqn 2 exactly.

Therefore, the `s_accountPremiumOwed`

and `s_accountPremiumGross`

accumulators allow smart contracts that need to handle long+short liquidity to guarantee that liquidity deposited always receives the correct
premia, whether that liquidity has been removed from the AMM or not.

Note that the expression for the spread is extremely opinionated, and may not fit the specific risk management profile of every smart contract.