Skip to main content

Market Fees

We would like to capture a percentage of the swap fees as protocol fees. The best way to do it is by minting LPs to the protocol, proportional to the "increase in the market's liquidity due to swap fees".

Comparing with Uniswap

First, let's see how Uniswap does it. In Uniswap:

  • xy=kx \cdot y = k, where xx and yy are amounts of the two tokens
  • Let's defined k\sqrt{k} as the liquidity of the pool, or basically the geometric mean of the two token balances.
  • When first bootstrapped at t0t0, the initial LPs minted is exactly the liquidity in the pool lpTotal=x0y0=k=pool’s liquidity\text{lpTotal} = \sqrt{x_0 \cdot y_0} = \sqrt{k} = \text{pool's liquidity}
  • Let's say there is no liquidity addition or removal from t0t0 to t1t1:
    • At t0:x0y0=k0t0 : x_0 \cdot y_0 = k_0
      • There are multiple swaps happening from t0t0 to t1t1 . Due to the 0.3% fees for each swap, kk actually increases (due to the 0.3% of the tokens being retained in the pool for each swap)
    • At t1:x1y1=k1t1 : x_1 \cdot y_1 = k_1
      • Now, the liquidity in the pool increases by an amount: k1k0\sqrt{k_1} - \sqrt{k_0}
      • For the sake of understanding, lets use the example that k1=1.21k0k_1 = 1.21 * k_0
      • Basically, there is a 10% increase in liquidity
      • As such, the lpTotallpTotal LPs will enjoy this 10% increase in liquidity (claim to a higher geometric mean of the two token balances by 10%)
    • At t1 , before another event (add/remove liquidity) that changes k that is not due to the 0.30% fees from swapping:
      • Uniswap will mint a certain amount of s LPs to the protocol, right before doing the k-changing event, such that due to this effect, the protocol will receive 1/6 of the increase in liquidity k1k0\sqrt{k_1} - \sqrt{k_0} . Now to calculate ss, we can refer to the Uniswap whitepaper here at page 5.
      • This will lead to the protocol enjoys 1/6 of the value brought about by the swap fees from t0t0 to t1t1
  • Basically, the increase in liquidity due to swap fees are converted to LP tokens for the protocols. As such, all these mintLpFeesForProtocol() functions are done right before any other actions that change k due to a non-swapping event.
    • Details:
      • Uniswap saves the kLastkLast right after the last non-swapping event
      • Uniswap makes sure that when mintLpFeesForProtocol() is called, the liquidity of the pool is only changed due to swap fees
      • Basically, Uniswap calls mintLpFeesForProtocol() right before any non-swapping event that changes kk.

Pendle's Method

How Pendle will do it:

  • Our AMM formula is as such:
    • xαy(1α)=kx^\alpha \cdot y^{(1 - \alpha)} = k
    • Initially, α=0.5\alpha = 0.5 and we mint exactly kk LPs to the bootstrapper
  • As such, the "liquidity" definition in our case is exactly kk, or the weighted geometric mean of the token balances.
  • When there are no curve shifts, (and alpha is fixed), kk will also increase due to 0.35% of the token swapped being retained in the market.
    • As such, we can adopt a similar approach, to mint a percentage of the increases in kk to the protocol
    • We will also need to save the last k right after any non-swapping event, and call a function mintProtocolFees() to mint the protocol LPs right before kk is changed due to a non-swapping event
    • mintProtocolFees():
      currentK=xαy(1α)toMint=(currentKlastK)((1feesPortion1)currentK+lastK)totalLPmint(protocol,toMint)currentK = x^\alpha \cdot y^{(1-\alpha)} \\ toMint = \cfrac{(currentK - lastK)}{( (\cfrac{1}{feesPortion} - 1) * currentK + lastK )} * totalLP \\ mint(protocol, toMint)
    • Basically, we need to make sure that:
      note

      INVARIANT 1: From the moment lastK is saved, until the moment mintProtocolFees is called, the only way k is changed is due to swapping.

  • In our protocol, other than adding liquidity and removing liquidity, another non-swapping event that would change k is the curve shift, where:
    • At time tt , when a curve shift happens, the balances of XYT is xtx_t and balance of base token is yty_t
      • Before curve shifting: xtαyt(1α)=kbeforex_t^\alpha \cdot y_t^{(1-\alpha)} = k_{before}
      • After curve shifting: xt(ααδ)yt(1α+αδ)=kafterx_t^{(\alpha - \alpha_\delta)} \cdot y_t^{(1 - \alpha + \alpha_\delta)} = k_{after}
    • kafterk_after is clearly different from kbeforek_before, so:
      • before doing a curve shift, we will need to call mintProtocolFees()
      • after doing a curve shift, we need to save the lastKlastK
  • Similarly for adding liquidity and removing liquidity, we will also need to:
    • Call mintProtocolFees() before the action
    • Save lastKlastK after the action
    • INVARIANT 1 makes sure that we don't wrongly count non-swapping changes in kk to the protocol fees
    • There is also this INVARIANT 2 to make sure we capture all changes in kk due to swapping:
      note

      INVARIANT 2: For every swaps, the increases in kk due to the swap must be captured due to an eventual execution of mintProtocolFees()