Skip to main content

Multi-Party Liquidity Aggregation & Fee Fairness Proof

This document provides the complete mathematical proof, accounting model, numerical walkthroughs, and on-chain test verification demonstrating that Biatec CLAMM (BIATEC-CLAMM-01-06-08) collects and distributes fees accurately when multiple users provide and withdraw liquidity in the same pool at different times.

Specifically, this guide proves that:

  1. No Fee Loss: 100% of swap fees and distributed staking rewards are captured on-chain and partitioned between liquidity providers (LiquidityUsersFromFees) and the protocol (LiquidityBiatecFromFees) with zero unallocated leakage.
  2. No Fee Scooping by Late Joiners: A liquidity provider who joins a pool after fees have accrued cannot extract any portion of the fees earned prior to their deposit.
  3. No Dilution of Incumbent LPs: Existing liquidity providers retain 100% of their principal and 100% of their previously accrued fees when new liquidity providers join the pool, and their accrued fees automatically compound as active liquidity for subsequent trades.
  4. Order-Independent Withdrawals: When multiple liquidity providers exit a pool, the payout to each participant is independent of who withdraws first (up to ≤2\le 2 atomic asset units of integer division flooring, which always rounds in favor of the remaining pool).

1. On-Chain Accounting Variables & Conservation Invariant​

Every BiatecClammPool contract maintains four core liquidity quantities in 9-decimal base scale (SCALE=109\text{SCALE} = 10^9):

SymbolContract State / FormulaDescription
LLLiquidity (L)Total concentrated liquidity derived from tracked reserves (x,y)=(assetABalanceBaseScale,assetBBalanceBaseScale)(x, y) = (\text{assetABalanceBaseScale}, \text{assetBBalanceBaseScale}).
DDdistributedLpCirculating LP tokens held outside the pool account, scaled to 9 decimals: D=(TOTAL_SUPPLY−poolLpBalance)×1000D = (\text{TOTAL\_SUPPLY} - \text{poolLpBalance}) \times 1000.
LuL_uLiquidityUsersFromFees (Lu)Accumulated fee/reward liquidity owned pro rata by circulating LP token holders.
LbL_bLiquidityBiatecFromFees (Lb)Accumulated protocol fee liquidity owned by the Biatec fee executor (addressExecutiveFee).

Fundamental Conservation Invariant​

At every point in the pool lifecycle, total curve liquidity LL satisfies:

L=D+Lu+Lb+UL = D + L_u + L_b + U

where:

  • D+LuD + L_u is the total liquidity claim of all LP token holders (redeemable via removeLiquidity),
  • LbL_b is the total liquidity claim of the protocol fee executor (redeemable via removeLiquidityAdmin),
  • UU is the sub-micro-LP integer flooring remainder (0≤U<1000 base units=10−6 LP0 \le U < 1000\text{ base units} = 10^{-6}\text{ LP} per deposit), which can be credited to LuL_u (or LbL_b if D=0D = 0) at any time via reconcileLiquidity.

2. How Fees Are Collected on Every Swap and Reward Distribution​

2.1 Swap Fee Accrual (swap)​

When a trader swaps input amount Δin\Delta_{\text{in}} (in base scale):

  1. Fee Deduction Before Curve Pricing: The contract computes the effective fee multiplier from the pool's fee parameter and the trader's BiatecIdentityProvider tier:

    feesMultiplier=s−⌊fee⋅user.feeMultiplieruser.base⌋,s=109\text{feesMultiplier} = s - \left\lfloor \frac{\text{fee} \cdot \text{user.feeMultiplier}}{\text{user.base}} \right\rfloor, \quad s = 10^9

    Only the net input Δin,afterFee=⌊Δin⋅feesMultiplier/s⌋\Delta_{\text{in,afterFee}} = \lfloor \Delta_{\text{in}} \cdot \text{feesMultiplier} / s \rfloor is used to compute the counter-asset output Δout\Delta_{\text{out}} along the constant-liquidity curve LoldL_{\text{old}}.

  2. Output Rounding Protection: When converting Δout\Delta_{\text{out}} from 9-decimal base scale to the output asset's native decimals, any non-zero fractional remainder causes the output to be stepped down by 1 native asset unit (realSwapBDecimals = realSwapBDecimals - 1), ensuring integer conversion never bleeds pool reserves.

  3. Reserve Update & Liquidity Recomputation: The full input Δin\Delta_{\text{in}} (including the fee portion Δin−Δin,afterFee\Delta_{\text{in}} - \Delta_{\text{in,afterFee}}) is added to the incoming reserve, while only the transferred output Δout,real\Delta_{\text{out,real}} is subtracted from the outgoing reserve. The contract then recomputes pool liquidity:

    Lnew=setCurrentLiquidityNonDecreasing(Lold)≥LoldL_{\text{new}} = \text{setCurrentLiquidityNonDecreasing}(L_{\text{old}}) \ge L_{\text{old}}
  4. Exhaustive Fee Partition (ΔLu+ΔLb=diff\Delta L_u + \Delta L_b = \text{diff}): The liquidity gain diff=Lnew−Lold\text{diff} = L_{\text{new}} - L_{\text{old}} is split between LP holders and Biatec:

    ΔLu=⌊diff⋅(s−fbiatec)s⌋,ΔLb=diff−ΔLu\Delta L_u = \left\lfloor \frac{\text{diff} \cdot (s - f_{\text{biatec}})}{s} \right\rfloor, \quad \Delta L_b = \text{diff} - \Delta L_u

    Because ΔLb\Delta L_b is defined as diff−ΔLu\text{diff} - \Delta L_u, the sum ΔLu+ΔLb=diff\Delta L_u + \Delta L_b = \text{diff} holds identically with zero rounding loss.

2.2 Staking & Excess Asset Distribution (distributeExcessAssets)​

When staking rewards or external yield are credited via distributeExcessAssets, the contract verifies physical backing (ensurePoolBalancesWithinHoldings), computes diff=Lnew−Lold\text{diff} = L_{\text{new}} - L_{\text{old}}, and applies the identical exhaustive split ΔLu+ΔLb=diff\Delta L_u + \Delta L_b = \text{diff}.


3. Mathematical Proof: Aggregating Liquidity of Multiple Parties at Different Times​

Consider two independent liquidity providers — Party A (Incumbent) and Party B (Newcomer) — who deposit into the same pool at different times t0<t2t_0 < t_2, with swaps occurring both before (t1t_1) and after (t3t_3) Party B joins.

Step 1 (t0t_0): Party A Supplies Initial Liquidity​

Party A deposits assets adding ΔLA\Delta L_A of liquidity into an empty pool (D0=0,Lu,0=0,Lb,0=0D_0 = 0, L_{u,0} = 0, L_{b,0} = 0).

  • Party A receives dA=⌊ΔLA/1000⌋d_A = \lfloor \Delta L_A / 1000 \rfloor micro-LP tokens, corresponding to DA=1000 dA=ΔLA−rAD_A = 1000 \, d_A = \Delta L_A - r_A base units (0≤rA<10000 \le r_A < 1000).

  • Pool state after t0t_0:

    D1=DA,Lu,1=0,Lb,1=0,L1=DA+rAD_1 = D_A, \quad L_{u,1} = 0, \quad L_{b,1} = 0, \quad L_1 = D_A + r_A

Step 2 (t1t_1): Swaps Accrue Fees F1F_1 While Only Party A Is in the Pool​

Swaps during [t0,t1][t_0, t_1] generate user fee liquidity F1>0F_1 > 0 and protocol fee liquidity B1≥0B_1 \ge 0.

  • Pool state at t1t_1:

    D1=DA,Lu,1=F1,Lb,1=B1,L1′=DA+F1+B1+rAD_1 = D_A, \quad L_{u,1} = F_1, \quad L_{b,1} = B_1, \quad L_1' = D_A + F_1 + B_1 + r_A
  • Because Party A holds 100% of circulating LP tokens (D1=DAD_1 = D_A), Party A's redeemable liquidity entitlement at t1t_1 is:

    ClaimA(t1)=DA+⌊Lu,1⋅DADA⌋=DA+F1\text{Claim}_A(t_1) = D_A + \left\lfloor \frac{L_{u,1} \cdot D_A}{D_A} \right\rfloor = D_A + F_1
  • Each base unit of circulating LP token is now backed by DA+F1DA>1\frac{D_A + F_1}{D_A} > 1 units of user liquidity.

Step 3 (t2t_2): Party B Deposits ΔLB\Delta L_B Into the Pool​

At t2t_2, Party B deposits assets that increase pool liquidity from L1′L_1' to L2=L1′+ΔLBL_2 = L_1' + \Delta L_B.

In processAddLiquidity, the contract computes the fee-adjusted LP mint amount XbaseX_{\text{base}}:

Xbase=⌊ΔLB⋅DADA+F1⌋X_{\text{base}} = \left\lfloor \frac{\Delta L_B \cdot D_A}{D_A + F_1} \right\rfloor

Party B is minted dB=⌊Xbase/1000⌋d_B = \lfloor X_{\text{base}} / 1000 \rfloor micro-LP tokens (DB=1000 dB=Xbase−rBD_B = 1000 \, d_B = X_{\text{base}} - r_B, where 0≤rB<10000 \le r_B < 1000), and the unminted liquidity ΔLB−Xbase\Delta L_B - X_{\text{base}} is credited to LiquidityUsersFromFees:

Lu,2=F1+(ΔLB−Xbase)L_{u,2} = F_1 + (\Delta L_B - X_{\text{base}})

Total circulating LP is now D2=DA+DBD_2 = D_A + D_B, and total user-owned liquidity is:

D2+Lu,2=(DA+Xbase−rB)+(F1+ΔLB−Xbase)=DA+F1+ΔLB−rBD_2 + L_{u,2} = (D_A + X_{\text{base}} - r_B) + (F_1 + \Delta L_B - X_{\text{base}}) = D_A + F_1 + \Delta L_B - r_B

Theorem 1 (Party B Cannot Scoop Pre-Existing Fees)​

If Party B immediately calls removeLiquidity with all DBD_B LP tokens at t2+t_2^+, Party B's liquidity payout is:

ClaimB(t2+)=DB+⌊Lu,2⋅DBDA+DB⌋=⌊(DA+DB+Lu,2)⋅DBDA+DB⌋\text{Claim}_B(t_2^+) = D_B + \left\lfloor \frac{L_{u,2} \cdot D_B}{D_A + D_B} \right\rfloor = \left\lfloor \frac{(D_A + D_B + L_{u,2}) \cdot D_B}{D_A + D_B} \right\rfloor

Since DB≤Xbase≤ΔLB⋅DADA+F1D_B \le X_{\text{base}} \le \frac{\Delta L_B \cdot D_A}{D_A + F_1}, we have:

DBDA+DB≤XbaseDA+Xbase≤ΔLBDA+F1+ΔLB\frac{D_B}{D_A + D_B} \le \frac{X_{\text{base}}}{D_A + X_{\text{base}}} \le \frac{\Delta L_B}{D_A + F_1 + \Delta L_B}

Multiplying by DA+DB+Lu,2=DA+F1+ΔLB−rB≤DA+F1+ΔLBD_A + D_B + L_{u,2} = D_A + F_1 + \Delta L_B - r_B \le D_A + F_1 + \Delta L_B yields:

ClaimB(t2+)≤ΔLB\text{Claim}_B(t_2^+) \le \Delta L_B

Conclusion: Party B can never withdraw more liquidity than ΔLB\Delta L_B (the exact liquidity Party B deposited). Party B receives zero units of the pre-existing fees F1F_1.

Theorem 2 (Party A Retains 100% of Principal and Pre-Existing Fees)​

Immediately after Party B joins at t2+t_2^+, Party A's liquidity claim is:

ClaimA(t2+)=DA+⌊Lu,2⋅DADA+DB⌋=(DA+F1+ΔLB−rB)−ClaimB(t2+)≥DA+F1\text{Claim}_A(t_2^+) = D_A + \left\lfloor \frac{L_{u,2} \cdot D_A}{D_A + D_B} \right\rfloor = (D_A + F_1 + \Delta L_B - r_B) - \text{Claim}_B(t_2^+) \ge D_A + F_1

Conclusion: Party A's entitlement ClaimA(t2+)\text{Claim}_A(t_2^+) is never reduced by Party B's deposit (and increases by up to rB<1000r_B < 1000 base units of sub-micro-LP rounding in favor of the pool).

Step 4 (t3t_3): Subsequent Swaps Accrue Fees F2F_2 While Both Parties Are in the Pool​

During [t2,t3][t_2, t_3], both Party A and Party B have capital actively providing liquidity in the pool:

  • Party A's active user liquidity at t2t_2 is Lactive,A=DA+F1L_{\text{active},A} = D_A + F_1 (Party A's initial principal plus Party A's compounded Epoch 2 fees F1F_1).
  • Party B's active user liquidity at t2t_2 is Lactive,B=ΔLBL_{\text{active},B} = \Delta L_B.

Because DB≈ΔLB⋅DADA+F1D_B \approx \frac{\Delta L_B \cdot D_A}{D_A + F_1}, the ratio of circulating LP tokens held by Party A and Party B satisfies:

DADA+DB=DA+F1DA+F1+ΔLB=Lactive,ALactive,A+Lactive,B\frac{D_A}{D_A + D_B} = \frac{D_A + F_1}{D_A + F_1 + \Delta L_B} = \frac{L_{\text{active},A}}{L_{\text{active},A} + L_{\text{active},B}} DBDA+DB=ΔLBDA+F1+ΔLB=Lactive,BLactive,A+Lactive,B\frac{D_B}{D_A + D_B} = \frac{\Delta L_B}{D_A + F_1 + \Delta L_B} = \frac{L_{\text{active},B}}{L_{\text{active},A} + L_{\text{active},B}}

When new swaps at t3t_3 add F2F_2 to LiquidityUsersFromFees (Lu,3=Lu,2+F2L_{u,3} = L_{u,2} + F_2):

  • Party A's total claim becomes:

    ClaimA(t3)=(DA+F1)+DA+F1DA+F1+ΔLBF2\text{Claim}_A(t_3) = (D_A + F_1) + \frac{D_A + F_1}{D_A + F_1 + \Delta L_B} F_2
  • Party B's total claim becomes:

    ClaimB(t3)=ΔLB+ΔLBDA+F1+ΔLBF2\text{Claim}_B(t_3) = \Delta L_B + \frac{\Delta L_B}{D_A + F_1 + \Delta L_B} F_2

Conclusion: New fees F2F_2 are distributed in exact proportion to each party's active liquidity in the pool during [t2,t3][t_2, t_3], automatically compounding Party A's earlier fee earnings F1F_1.

Step 5 (t4,t5t_4, t_5): Order-Independent Withdrawals (removeLiquidity)​

When Party i∈{A,B}i \in \{A, B\} withdraws their LP tokens DiD_i first at t4t_4:

  1. lpWithOthers =DA+DB= D_A + D_B.
  2. myPortionOfFeesCollected =⌊Lu,3⋅DiDA+DB⌋= \lfloor \frac{L_{u,3} \cdot D_i}{D_A + D_B} \rfloor.
  3. lpDeltaWithFees =Di+myPortionOfFeesCollected=Claimi(t3)= D_i + \text{myPortionOfFeesCollected} = \text{Claim}_i(t_3).
  4. LiquidityUsersFromFees is reduced to Lu,4=Lu,3−myPortionOfFeesCollectedL_{u,4} = L_{u,3} - \text{myPortionOfFeesCollected}.
  5. Pool reserves (x,y)(x, y) are paid out pro rata by Claimi(t3)L\frac{\text{Claim}_i(t_3)}{L}, leaving L−Claimi(t3)L\frac{L - \text{Claim}_i(t_3)}{L} of (x,y)(x, y) in the pool.

When the remaining Party j≠ij \ne i withdraws their LP tokens DjD_j at t5t_5:

  1. lpWithOthers =Dj= D_j (since Party ii's LP tokens were returned to the pool at t4t_4).
  2. myPortionOfFeesCollected =⌊Lu,4⋅DjDj⌋=Lu,4= \lfloor \frac{L_{u,4} \cdot D_j}{D_j} \rfloor = L_{u,4}.
  3. Party jj receives Dj+Lu,4=Claimj(t3)D_j + L_{u,4} = \text{Claim}_j(t_3), and LiquidityUsersFromFees becomes 00.

Lifecycle Overview​

4. Concrete Numerical Example​

Suppose a flat-price pool (P=1.0P = 1.0, biatecFee=10%\text{biatecFee} = 10\%) undergoes the following sequence:

  1. Epoch 1 (t0t_0) — Alice deposits 100 A+100 B100\text{ A} + 100\text{ B}:

    • ΔLA=200×109\Delta L_A = 200 \times 10^9
    • Alice receives 200,000,000200,000,000 micro-LP (DA=200×109D_A = 200 \times 10^9).
    • Pool state: L=200×109,  D=200×109,  Lu=0,  Lb=0L = 200 \times 10^9, \; D = 200 \times 10^9, \; L_u = 0, \; L_b = 0.
  2. Epoch 2 (t1t_1) — Fees/rewards of 20 A+20 B20\text{ A} + 20\text{ B} (40×10940 \times 10^9 liquidity) accrue:

    • 90%90\% goes to users (ΔLu=36×109\Delta L_u = 36 \times 10^9) and 10%10\% goes to Biatec (ΔLb=4×109\Delta L_b = 4 \times 10^9).
    • Pool state: L=240×109,  D=200×109,  Lu=36×109,  Lb=4×109L = 240 \times 10^9, \; D = 200 \times 10^9, \; L_u = 36 \times 10^9, \; L_b = 4 \times 10^9.
    • Alice's liquidity entitlement is DA+Lu=236×109D_A + L_u = 236 \times 10^9 (118 A+118 B118\text{ A} + 118\text{ B}).
  3. Epoch 3 (t2t_2) — Bob deposits 60 A+60 B60\text{ A} + 60\text{ B} (ΔLB=120×109\Delta L_B = 120 \times 10^9 liquidity):

    • Contract computes Bob's discounted LP mint:

      Xbase=⌊120×109×200×109(200+36)×109⌋=101,694,915,254X_{\text{base}} = \left\lfloor \frac{120 \times 10^9 \times 200 \times 10^9}{(200 + 36) \times 10^9} \right\rfloor = 101,694,915,254
    • Bob receives dB=101,694,915d_B = 101,694,915 micro-LP (DB=101,694,915,000D_B = 101,694,915,000 base units).

    • Unminted fee-dilution offset ΔLB−Xbase=18,305,084,746\Delta L_B - X_{\text{base}} = 18,305,084,746 is added to LuL_u:

      Lu,2=36×109+18,305,084,746=54,305,084,746L_{u,2} = 36 \times 10^9 + 18,305,084,746 = 54,305,084,746
    • Total circulating LP: D2=301,694,915,000D_2 = 301,694,915,000.

    • Check immediate claims at t2+t_2^+:

      • Bob's claim: 101,694,915,000+⌊54,305,084,746×101,694,915,000301,694,915,000⌋=119,999,999,700≤120×109101,694,915,000 + \lfloor \frac{54,305,084,746 \times 101,694,915,000}{301,694,915,000} \rfloor = 119,999,999,700 \le 120 \times 10^9 (Bob cannot scoop any of Alice's 36×10936 \times 10^9 fees).
      • Alice's claim: 200×109+⌊54,305,084,746×200×109301,694,915,000⌋=236,000,000,046≥236×109200 \times 10^9 + \lfloor \frac{54,305,084,746 \times 200 \times 10^9}{301,694,915,000} \rfloor = 236,000,000,046 \ge 236 \times 10^9 (Alice keeps 100% of her deposit and 100% of Epoch 2 fees).
  4. Epoch 4 (t3t_3) — Additional fees/rewards of 10 A+10 B10\text{ A} + 10\text{ B} (20×10920 \times 10^9 liquidity) accrue:

    • 90%90\% goes to users (ΔLu=18×109\Delta L_u = 18 \times 10^9) and 10%10\% goes to Biatec (ΔLb=2×109\Delta L_b = 2 \times 10^9).
    • Lu,3=54,305,084,746+18×109=72,305,084,746L_{u,3} = 54,305,084,746 + 18 \times 10^9 = 72,305,084,746.
    • Bob's share of the new 18×10918 \times 10^9 fee is 120236+120×18×109≈6.0674×109\frac{120}{236 + 120} \times 18 \times 10^9 \approx 6.0674 \times 10^9 (≈3.0337 A+3.0337 B\approx 3.0337\text{ A} + 3.0337\text{ B}).
    • Alice's share of the new 18×10918 \times 10^9 fee is 236236+120×18×109≈11.9326×109\frac{236}{236 + 120} \times 18 \times 10^9 \approx 11.9326 \times 10^9 (≈5.9663 A+5.9663 B\approx 5.9663\text{ A} + 5.9663\text{ B}).
    • Biatec's total protocol fee is Lb=6×109L_b = 6 \times 10^9 (3 A+3 B3\text{ A} + 3\text{ B}).

5. On-Chain Test Suite Verification​

All multi-party fee and liquidity aggregation invariants are continuously verified against a live Algorand LocalNet AVM in the Jest test suite:

Test FileVerified Scenarios
__test__/pool/multi-party-fee-aggregation.test.ts1. Two LPs joining at different times across two swap epochs share fees strictly by active liquidity (DA+Lu,1):ΔLB(D_A + L_{u,1}) : \Delta L_B.
2. Withdrawal order independence (Alice first vs Bob first) and Biatec protocol fee (LbL_b) isolation.
3. Three LPs (Alice, Bob, Charlie) joining at three different times with partial (50%) and full withdrawals.
4. Staking / excess-asset reward distribution (distributeExcessAssets) across multiple LPs joining at different times.
5. Rapid 5x deposit/withdraw cycling by a second party never siphons fees or reduces pool physical holdings.
__test__/pool/liquidity.test.tsNew liquidity provider joining after two-way swaps and immediately withdrawing cannot scoop pre-existing fees (deltaA <= 0, deltaB <= 0).
__test__/pool/mainnet-replay-3720188642.test.tsFull 242-transaction MainNet history replay of Gold/GoldDAO pool 3720188642 verifying L=D+Lu+LbL = D + L_u + L_b conservation after every deposit, swap, and full redemption.
__test__/pool/reconcile-liquidity.test.tsVerification that sub-micro-LP flooring dust is swept to LuL_u when D>0D > 0 and to LbL_b when D=0D = 0, and that reconcileLiquidity is restricted to addressExecutiveFee.
__test__/pool/fees.test.tsDeterministic swap fee accrual, LP withdrawal with fees, removeLiquidityAdmin protocol fee collection, and distributeExcessAssets across price ranges.

Running the Verification Suite Locally​

# Ensure Algorand LocalNet is running
algokit localnet start

# Run the dedicated multi-party fee aggregation test suite
npx jest __test__/pool/multi-party-fee-aggregation.test.ts

# Run the complete contract build and full test suite
npm run test