Providing liquidity

Last modified:

When a liquidity provider adds liquidity to a CenturionDEX v2 pool, they must deposit amounts of tokens XX and YY in the same proportion as the pool's current reserves. In return, the protocol mints liquidity provider tokens, or LP tokens, which represent the provider's proportional share of the pool. The liquidity provider is also entitled to a corresponding share of the trading fees generated by the pool. This ownership fraction is not fixed, since it changes whenever new liquidity is added or existing liquidity is removed.

Liquidity Provision Basics

Suppose a CenturionDEX v2 pool holds reserves xx and yy of tokens XX and YY, respectively. If a liquidity provider wishes to add liquidity to the pool, they must deposit amounts Δx\Delta x of token XX and Δy\Delta y of token YY in the same proportion as the existing reserves. Therefore, the deposit amounts must satisfy

ΔyΔx=yx.\frac{\Delta y}{\Delta x}=\frac{y}{x}.

Equivalently,

Δxx=Δyy.\frac{\Delta x}{x}=\frac{\Delta y}{y}.

After the deposit, the new pool reserves become

x=x+Δx,y=y+Δy.x' = x + \Delta x,\qquad y' = y + \Delta y.

The spot price after the deposit is therefore

p=yx=y+Δyx+Δx.p'=\frac{y'}{x'}=\frac{y+\Delta y}{x+\Delta x}.

Since the deposited amounts are proportional to the current reserves, we have

y+Δyx+Δx=yx.\frac{y+\Delta y}{x+\Delta x}=\frac{y}{x}.

Thus, when liquidity is added in the correct ratio, the pool price remains unchanged.

In return for contributing liquidity, the provider receives LP tokens. These tokens represent the provider's proportional claim on the pool and on the trading fees generated by it. The total supply of LP tokens is not fixed, since it changes whenever liquidity is added or removed. When a new provider contributes assets to the pool, the AMM mints an amount of LP tokens consistent with the share of liquidity contributed, so that the ownership fractions of both existing and new liquidity providers remain accurate.

Minting LP Tokens

Assume that the pool has already been initialized and currently holds reserves xx and yy of tokens XX and YY, respectively. Let SS denote the current total supply of LP tokens. If a liquidity provider adds amounts Δx\Delta x of token XX and Δy\Delta y of token YY, then in order to preserve the pool proportions, these amounts must satisfy

ΔyΔx=yx,\frac{\Delta y}{\Delta x}=\frac{y}{x},

or equivalently,

Δxx=Δyy.\frac{\Delta x}{x}=\frac{\Delta y}{y}.

Define

q=Δxx=Δyy.q=\frac{\Delta x}{x}=\frac{\Delta y}{y}.

Then the provider receives

ΔS=qS\Delta S=qS

new LP tokens, and the total LP token supply is updated to

S=S+ΔS=(1+q)S.S' = S+\Delta S=(1+q)S.

In other words, the provider receives LP tokens in the same proportion as the share of reserves added to the pool.

In practice, the provider does not need to compute the exact proportional deposit in advance. Instead, suppose the provider is willing to supply up to cxc_x units of token XX and up to cyc_y units of token YY. The protocol then determines the largest admissible deposit that preserves the reserve ratio and returns any excess amount of one of the two tokens. This is particularly useful because the pool state may change between the moment the user prepares the transaction and the moment it is executed.

We now describe the three possible cases.

Case 1: Exact proportion

If

cycx=yx,\frac{c_y}{c_x}=\frac{y}{x},

then the offered amounts are already in the correct ratio. Hence, the protocol accepts the full deposit,

Δx=cx,Δy=cy,\Delta x=c_x,\qquad \Delta y=c_y,

and mints

ΔS=cxxS=cyyS\Delta S=\frac{c_x}{x}S=\frac{c_y}{y}S

LP tokens.

Case 2: Token YY is in excess

If

cycx>yx,\frac{c_y}{c_x}>\frac{y}{x},

then token YY is over-supplied relative to the pool ratio. In this case, the protocol uses all of token XX and only the amount of token YY needed to preserve proportions. Thus,

Δx=cx,Δy=yxcx.\Delta x=c_x,\qquad \Delta y=\frac{y}{x}c_x.

The amount of token YY returned to the provider is therefore

ry=cyyxcx>0.r_y=c_y-\frac{y}{x}c_x>0.

The LP tokens minted are

ΔS=cxxS=ΔyyS<cyyS.\Delta S=\frac{c_x}{x}S=\frac{\Delta y}{y}S<\frac{c_y}{y}S.

Case 3: Token XX is in excess

If

cycx<yx,\frac{c_y}{c_x}<\frac{y}{x},

then token XX is over-supplied relative to the pool ratio. In this case, the protocol uses all of token YY and only the amount of token XX needed to preserve proportions. Thus,

Δy=cy,Δx=xycy.\Delta y=c_y,\qquad \Delta x=\frac{x}{y}c_y.

The amount of token XX returned to the provider is

rx=cxxycy>0.r_x=c_x-\frac{x}{y}c_y>0.

The LP tokens minted are

ΔS=cyyS=ΔxxS<cxxS.\Delta S=\frac{c_y}{y}S=\frac{\Delta x}{x}S<\frac{c_x}{x}S.

Summary

In all cases, the number of LP tokens minted is determined by the limiting side of the deposit. Therefore,

ΔS=min{cxxS,cyyS}.\Delta S=\min\left\{\frac{c_x}{x}S,\frac{c_y}{y}S\right\}.

Equivalently, if Δx\Delta x and Δy\Delta y denote the actual amounts accepted by the protocol after excess tokens are returned, then

ΔS=ΔxxS=ΔyyS.\Delta S=\frac{\Delta x}{x}S=\frac{\Delta y}{y}S.

This is the canonical liquidity-minting formula for an existing constant-product pool.

If one wishes to match this formula with the standard Centurion v2-style implementation, the correspondence is

totalSupply=S,amount0=Δx,amount1=Δy,_reserve0=x,_reserve1=y,liquidity=ΔS.\begin{aligned} \texttt{totalSupply} &= S, \\ \texttt{amount0} &= \Delta x, \\ \texttt{amount1} &= \Delta y, \\ \texttt{\_reserve0} &= x, \\ \texttt{\_reserve1} &= y, \\ \texttt{liquidity} &= \Delta S. \end{aligned}

If the discussion instead refers to the user's offered amounts before optimization, then those amounts are denoted here by cxc_x and cyc_y.

The optimization above (Cases 1–3, including returning any excess token) is performed by the Router (addLiquidity / _addLiquidity). The pair-level mint function below assumes its caller has already deposited the optimized amounts Δx\Delta x and Δy\Delta y; it then mints min(ΔxS/x,  ΔyS/y)\min(\Delta x \cdot S / x,\; \Delta y \cdot S / y) LP tokens using integer division (rounding down). When the deposit is exactly proportional, the two expressions inside the min are equal and reduce to ΔS=(Δx/x)S\Delta S = (\Delta x / x)\cdot S.

// this low-level function should be called from a contract which performs important safety checks
function mint(address to) external lock returns (uint liquidity) {
    (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
    uint balance0 = IERC20(token0).balanceOf(address(this));
    uint balance1 = IERC20(token1).balanceOf(address(this));
    uint amount0 = balance0.sub(_reserve0);
    uint amount1 = balance1.sub(_reserve1);
 
    bool feeOn = _mintFee(_reserve0, _reserve1);
    uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
    if (_totalSupply == 0) {
        liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
       _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
    } else {
        liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
    }
    require(liquidity > 0, 'CenturionV2: INSUFFICIENT_LIQUIDITY_MINTED');
    _mint(to, liquidity);
 
    _update(balance0, balance1, _reserve0, _reserve1);
    if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
    emit Mint(msg.sender, amount0, amount1);
}