Providing liquidity
Last modified:
When a liquidity provider adds liquidity to a CenturionDEX v2 pool, they must deposit amounts of tokens and 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 and of tokens and , respectively. If a liquidity provider wishes to add liquidity to the pool, they must deposit amounts of token and of token in the same proportion as the existing reserves. Therefore, the deposit amounts must satisfy
Equivalently,
After the deposit, the new pool reserves become
The spot price after the deposit is therefore
Since the deposited amounts are proportional to the current reserves, we have
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 and of tokens and , respectively. Let denote the current total supply of LP tokens. If a liquidity provider adds amounts of token and of token , then in order to preserve the pool proportions, these amounts must satisfy
or equivalently,
Define
Then the provider receives
new LP tokens, and the total LP token supply is updated to
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 units of token and up to units of token . 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
then the offered amounts are already in the correct ratio. Hence, the protocol accepts the full deposit,
and mints
LP tokens.
Case 2: Token is in excess
If
then token is over-supplied relative to the pool ratio. In this case, the protocol uses all of token and only the amount of token needed to preserve proportions. Thus,
The amount of token returned to the provider is therefore
The LP tokens minted are
Case 3: Token is in excess
If
then token is over-supplied relative to the pool ratio. In this case, the protocol uses all of token and only the amount of token needed to preserve proportions. Thus,
The amount of token returned to the provider is
The LP tokens minted are
Summary
In all cases, the number of LP tokens minted is determined by the limiting side of the deposit. Therefore,
Equivalently, if and denote the actual amounts accepted by the protocol after excess tokens are returned, then
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
If the discussion instead refers to the user's offered amounts before optimization, then those amounts are denoted here by and .
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 and ; it then mints LP tokens using integer division (rounding down). When the deposit is exactly proportional, the two expressions inside the min are equal and reduce to .
// 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);
}