Skip to content

Initial Liquidity

This page covers the necessary concepts for the reader to understand the initial liquidity flow in Uniswap V2.

The theory behind a Uniswap pool, also known as a pair, is rooted in the concept of an Automated Market Maker (AMM).

Automated Market Maker (AMM) is the underlying architecture behind Uniswap V2

  • Alternative to order books, economically inefficient on-chain due to the high gas costs
  • Prices are determined by a mathematical formula written into protocol code
  • Enable permissionless, middleman-free trading
  • Provide instant execution on rates and slippage

The specific type of AMM used by Uniswap v2 is the Constant Product Market Maker (CPMM), which is governed by the formula

xy=kx \cdot y = k
  • xx and yy represent the reserve balances of the two tokens in the pool
  • kk is the invariant, stays constant
  • Trading curve: The protocol requires that kk remains constant during a trade (ignoring fees)
    • The spot price is determined by the ratio of the tokens in the pool p=y/xp = y/x
    • When a trader buys/withdraws token xx, they must deposit a proportional amount of token yy to ensure the product xyx \cdot y remains equal to kk.
  • Enforces a natural inverse relationship between the two assets, ensuring that as one gets scarcer, its price rises.
  • You cannot deplete the reserve of any asset (asymptotic to zero).

Liquidity Providers are users or smart contracts that deposit an equivalent value of two ERC-20 tokens into a specific pair contract. In exchange, they get LP tokens that represent their deposit share in the pool.

  • First Liquidity Provider: The individual who performs the initial deposit into a new pool. They are responsible for seeding the reserves and setting the initial exchange rate (price) of the pair.
  • Subsequent Providers: LPs who add to an existing pool are incentivized to deposit tokens proportional to the current reserve ratio.

Depositing at an incorrect ratio creates an immediate arbitrage opportunity, which external parties will likely exploit.

When setting up the pool, the initial amounts of tokenA and tokenB can be in any proportion, since no prior balances exist for reference.

// contracts/uniswap-v2/v2-periphery/contracts/UniswapV2Router02.sol:46:48
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {

LP tokens are ERC20 tokens that are minted to the LP.

Let’s analyze the formula that calculates LP tokens to the first LP.

// contracts/uniswap-v2/v2-core/contracts/UniswapV2Pair.sol:119:122
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 {

The initial liquidity calculation contains two important elements

  • Math.sqrt(amount0.mul(amount1)) \Rightarrow xy\sqrt{x \cdot y}
  • .sub(MINIMUM_LIQUIDITY); \Rightarrow 1000- 1000

Let’s try to understand each part

Why square root of the token amount product
Section titled “Why square root of the token amount product”

Let’s try to go into the mind of the creator and reach to the same formula.

Our goal is to find a number that correctly measures the LP’s token pair ownership in the pool.


1 . Logic steps

  • Principle: The LP token number must represent the ownership the LP has on the pool assets.
    • LP tokens should not change in quantity simply because prices (aka ratio xy\frac{x}{y}) change due to trades.
    • Ownership should not change unless new liquidity is added or removed.
  • Definition: kk is constant along the curve (xy=kx \cdot y = k)
  • Let’s find some functions that respect the above:
    • xyx \cdot y : stays constant by definition
    • xy\sqrt{x \cdot y} : stays constant (property of the parabola xy=kx \cdot y = k)
    • x+y2\frac{x + y}{2} : does NOT stay constant (arithmetic mean)
    • x+yx + y : does NOT stay constant
FunctionRatio 1
(10 ETH, 10 USDC)
k=100
Ratio 2
(100 ETH, 1 USDC)
k=100
P1 Constant?
x·y100100
√(x·y)1010
x+y20101
(x+y)/21050.5

2 . Logic steps:

  • Principle: The LP token number should be able to scale proportional to the reserves of the pool.
    • eg, LP token calculation must double/half when the reserves of the pool double/halve.
    • So the function L that calculates the LP tokens should respect both
      • L(nx,ny)=nL(x,y)L(nx,ny) = n*L(x,y)
      • L(xn,yn)=L(x,y)nL(\frac{x}{n},\frac{y}{n}) = \frac{L(x,y)}{n}

3 . Solution (1,2):

  • We could try to solve 2 using math, but we are already limited by 1
    • We try both functions from 1
      • xy\sqrt{x \cdot y} : scales quadratically with the value of the pool
      • xyx \cdot y : scales linearly with the value of the pool
Functionk=100
(10 ETH,10 USDC)
k=100
(100 ETH,1 USDC)
k=400
(20 ETH,20 USDC)
k=400
(200 ETH,2 USDC)
P1 Constant?P2 2x Scale?
x·y100100400400❌ (4x)
√(x·y)10102020✅ (2x)

Summary: it makes an inflation attack on initial liquidity very costly

You can read more about it: Burn Initial Shares

Summary: it makes oracle price resistant to manipulation

You can read about it: Price Oracle

pair@26/02/2026xykuniswapetherscan
USDC/WETH9.2M * 10610^64.4K * 101810^{18}4.076 103410^{34}linklink

With the interactive chart below you can experiment with all the concepts above to deepen your understanding.

Each folder in the chart represents a concept that you can show or hide.

Loading graph...

In this example we will set the initial erc20 token deposits.

This is the code that runs to produce the function trace

// UniswapV2Workflows:initialLiquidity:64:89
async initialLiquidity({
user = USER_1.address,
amountADesired = 200n * _1e18,
amountBDesired = 400n * _1e18,
traceTx = true,
} = {}) {
await this.lensClient.fundAccount(user, _1e18);
const erc20TokenA = await this.deployErc20(USER_0.address, 1_000_000n * _1e18);
const erc20TokenB = await this.deployErc20(USER_0.address, 1_000_000n * _1e18);
const trace = await this._addLiquidity(
erc20TokenA,
erc20TokenB,
amountADesired,
amountBDesired,
0n,
0n,
user,
this.maxUint256(),
traceTx
);
return { trace, erc20TokenA, erc20TokenB };
}
// UniswapV2Workflows:_addLiquidity:173:200
private async _addLiquidity(
tokenA: UniswapV2ERC20,
tokenB: UniswapV2ERC20,
amountADesired: bigint,
amountBDesired: bigint,
amountAMin: bigint,
amountBMin: bigint,
user: Address,
deadline: bigint,
trace = true
) {
const { router2 } = this.deployment;
await this.transferErc20(tokenA, USER_0.address, user, amountADesired);
await this.transferErc20(tokenB, USER_0.address, user, amountBDesired);
await this.approve(tokenA, router2.address, amountADesired, user);
await this.approve(tokenB, router2.address, amountBDesired, user);
return await this.lensClient.contract(
router2,
'addLiquidity',
[tokenA.address, tokenB.address, amountADesired, amountBDesired, amountAMin, amountBMin, user, deadline],
user,
undefined,
trace
);
}
EVM LENS

Function Trace

WorkflowInitial Liquidity
DescriptionSee the function trace and protocol source code
NETWORK STATUS: ONLINE
BROWSER VM