Uniswap v2 router code walkthrough
Updated: Nov 13
The Router contracts provide a user-facing smart contract for
safely minting and burning LP tokens (adding and removing liquidity)
safely swapping pair tokens
They add the ability to swap Ether by integrating with the wrapped Ether (WETH) ERC20 contract.
They add the slippage related safety checks omitted from the core contract.
They add support for fee on transfer tokens.
Router02 is everything Router01 does with support added for fee on transfer tokens
When we first open up the contracts folder in the periphery repository, we see three contracts
Router02 is Router01 with additional functions for fee on transfer tokens. When we look at the interface of Router02, we can see it inherits from Router01 (red box) (which means it implements all of its functions), and has the following additional functions, which are all for doing operations with support for fee on transfer tokens (yellow highlight).
swapExactTokensForTokens and swapTokensForExactTokens
Let’s start with the Router functions for swapping tokens. There are two functions that accomplish this (highlighted in green).
The difference in these function names is as follows:
In swapExactTokensForTokens the "first token is exact" means that the amount of the input token you are swapping is a fixed quantity.
In swapTokensForExactTokens, the "second token is exact" indicates that the amount of the output token you want to receive is a fixed quantity.
If a user is only swapping two tokens, then they will supply to these functions an address calldata path array (highlighted in blue) [address(tokenIn), address(tokenOut)]. If they are hopping across pools, they will specify [address(tokenIn), address(intermediateToken), …, address(tokenOut)].
In the case of swapExactTokensForTokens, the user specifies exactly how much of the first token they are going to deposit and the minimum amount of the output token they will accept.
For example, suppose we want to trade 25 token0 for 50 token1. If this is the exact price at the current state, this leaves no tolerance for the price changing before our transaction is confirmed, leading to a revert. So we instead specify the minimum out to be 49.5 token1, implicitly leaving a 1% tolerance.
In this case we specify we want exactly 50 token1, but we are willing to trade up to 25.5 token0 to obtain in.
Which swap function to use?
Most users using an EOA would probably opt to use the exact input function, because they need to have an approval step, and the trade would fail if they needed to input more than they approved. By having an exact input, they can approve the exact amount. Smart contracts integrating with Uniswap however may have more complex requirements, so the router gives them the option for both.
How swap works
When the input is exact (swapExactTokensForTokens), the function predicts the expected output across a single swap or a chain of swaps. If the resulting output is below the user specified amount, the function reverts. Vice versa for exact output: it calculates the required input and reverts if it is above the user specified threshold.
Then both functions will transfer the user’s tokens to the pair (remember, Uniswap V2 Pair requires the tokens to be sent into the contract before the pair contract function swap is called). Finally, they both call the internal _swap function discussed next.
The _swap() function
Under the hood, all publicly facing functions with the name swap in the name call the _swap internal function shown below.
Recall that the function signature for the core swap function specifies the amountOut for both tokens and the amountIn is implied by the amount that was transferred in before the function was called.
Remember the safety checks for adding liquidity? Specifically, we want to make sure we deposit the two tokens at exactly the same ratio as what the pair currently has, otherwise the amount of LP tokens we mint is the worse of the two ratios between what we provide and what the pair balances are. However, the ratio could change between when the liquidity provider attempts to add liquidity and when the transaction is confirmed.
To guard against this, a liquidity provider must provide (as a parameter), the minimum balance they are seeking to deposit for token0 and token1 (UniswapV2 calls those amountAMin and amountBMin). Then they transfer in an amount higher than those minimums (UnsiwapV2 calls those amountADesired and amountBDesired). If the pair ratio has shifted in such a way that the minimums are no longer respected, then the transaction reverts.
_addLiquidity will take amountADesired and calculate the correct amount of tokenB that will respect the ratio. If this is amount is higher than amountBDesired (the amount of B the liquidity provider sent), then it will start with amountBDesired and calculate the optimal amount of B. The logic is show below. Note that adding liquidity may create a new pair contract if it doesn’t already exist.
For example, suppose the current pair balance is 100 token0 and 300 token1. We want to add 20 and 60 token0 and token1 respectively, but the pair ratio might change. So we instead approve the router for 21 token0 and 63 token1 while saying the minimum we want to deposit is 20 and 60. If the ratio shifts such that the optimal amount of token0 to deposit is 19.9, then the transaction reverts.
Recall that we said quote should not be used as an oracle, and that is still true. However for the purposes of adding liquidity we are not interested in the average of previous prices but the current price (pool ratio) now because the liquidity provider must respect it.
addLiquidity(), and addLiquidityEth()
These functions should be self-explanatory. They first calculate the optimal ratio using _addLiquidity from above then transfer the assets to the pair, then call mint on the pair. The only difference is the addLiquidityEth function will wrap the Ether into ETH first.
Remove liquidity calls burn but uses parameters amountAMin and amountBMin (red highlights) as safety checks to ensure that the liquidity provider gets back the amount of tokens they are expecting. If the ratio of tokens changes dramatically before the the liquidity tokens are burned, then the user burning the tokens won’t get back the amount of token A or B that they are expecting.
The function removeLiquidityEth calls removeLiquidity (green highlight) but sets the router as the recipient of the tokens. The regular ERC20 token is then transferred to the liquidity provider, and the WETH is unwrapped to ETH, then sent back to the liquidity provider.
removeLiquidityWithPermit() and removeLiquidityETHWithPermit()
On line 109 in the file above with the gray comment send liquidity to pair, this step assumes the pair contract has approval to transfer LP tokens from the liquidity provider to burn them. This means burning the LP tokens requires approving the pair first. This step can be skipped with permit(), since the LP tokens of Uniswap V2 is an ERC20 Permit Token. The function removeLiquidityWithPermit() receives a signature to approve and burn in one transaction. If one of the tokens is WETH, the liquidity provider would use removeLiquidityETHWithPermit().
Router02: supporting fee on transfer tokens
To handle fee on transfer tokens, the router cannot directly do it’s calculations on arguments like amountIn (for swap) or liquidity (for removing liquidity). Adding liquidity is not affected by fee on transfer tokens because the user is only credited for what they actually transfer to the pair.
Wrappers around the UniswapV2Library
The rest of the functions in the Router library are wrappers around the UniswapV2Library functions as shown below.
The Router contracts provide a user-facing mechanism for swapping tokens with slippage protection, possibly across multiple pools, and add support for trading ETH and fee-on-transfer tokens (in Router02). Depositing liquidity does not need to account for fee-on-transfer tokens because Uniswap only credits for what was actually transferred into the pool.
The depositing liquidity functions ensure the user only deposits at the exact ratio of the pool. Removing liquidity can be as simple as transferring LP tokens to the router then burning them, or include unwrapping WETH and withdrawing fee on transfer tokens.
Additionally, support for gas free approvals via ERC20 Permit are included.