Initial Liquidity
This page covers the necessary concepts for the reader to understand the initial liquidity flow in Uniswap V2.
Concepts
Section titled “Concepts”The theory behind a Uniswap pool, also known as a pair, is rooted in the concept of an Automated Market Maker (AMM).
Automated Market Makers
Section titled “Automated Market Makers”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
Constant Product Market Maker
Section titled “Constant Product Market Maker”The specific type of AMM used by Uniswap v2 is the Constant Product Market Maker (CPMM), which is governed by the formula
- and represent the reserve balances of the two tokens in the pool
- is the invariant, stays constant
- Trading curve: The protocol requires that remains constant during a trade (ignoring fees)
- The spot price is determined by the ratio of the tokens in the pool
- When a trader buys/withdraws token , they must deposit a proportional amount of token to ensure the product remains equal to .
- 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 (LPs)
Section titled “Liquidity Providers (LPs)”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.
Initial Reserves
Section titled “Initial Reserves”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:48if (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:122if (_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)).sub(MINIMUM_LIQUIDITY);
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 ) change due to trades.
- Ownership should not change unless new liquidity is added or removed.
- Definition: is constant along the curve ()
- Let’s find some functions that respect the above:
- : stays constant by definition
- : stays constant (property of the parabola )
- : does NOT stay constant (arithmetic mean)
- : does NOT stay constant
| Function | Ratio 1 (10 ETH, 10 USDC) k=100 | Ratio 2 (100 ETH, 1 USDC) k=100 | P1 Constant? |
|---|---|---|---|
| x·y | 100 | 100 | ✅ |
| √(x·y) | 10 | 10 | ✅ |
| x+y | 20 | 101 | ❌ |
| (x+y)/2 | 10 | 50.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
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
- : scales quadratically with the value of the pool
- : scales linearly with the value of the pool
- We try both functions from 1
| Function | k=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·y | 100 | 100 | 400 | 400 | ✅ | ❌ (4x) |
| √(x·y) | 10 | 10 | 20 | 20 | ✅ | ✅ (2x) |
Why burn one thousand LP tokens
Section titled “Why burn one thousand LP tokens”Summary: it makes an inflation attack on initial liquidity very costly
You can read more about it: Burn Initial Shares
Price Oracle
Section titled “Price Oracle”Summary: it makes oracle price resistant to manipulation
You can read about it: Price Oracle
Ecosystem
Section titled “Ecosystem”| pair@26/02/2026 | x | y | k | uniswap | etherscan |
|---|---|---|---|---|---|
| USDC/WETH | 9.2M * | 4.4K * | 4.076 | link | link |
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.
Function Trace
Section titled “Function Trace”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:89async 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:200private 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 );}