Table of Contents
- About The Project
- Getting Started
- Usage
- Constant Product AMM
- Impermanent Loss on Uniswap v2
This project shows how to interact with the main functions of Uniswap V2
To get a local copy up and running follow these simple example steps.
-
npm
npm install npm@latest -g
-
hardhat
npm install --save-dev hardhat
npm install @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle
run:
npx hardhat
verify:
npx hardhat verify --network goerli "contract address" "pair address"
-
Clone the repo
git clone https://github.com/Aboudoc/Uniswap-v2.git
-
Install NPM packages
npm install
-
Dependencies
npm i @uniswap/v2-core @uniswap/v2-periphery
If you need testnet funds, use the Alchemy testnet faucet.
This project shows how to swap, add and remove liquidity
Uniswap V2 is a Constant product AMM (automated market maker) <=> a decentralized exchange where 2 tokens are traded. You can find a deep overview of CPAMM in this repo
How to calculate Impermanent Loss in a constant product AMM?
If the price does not change at all, the d = 1 and there is no loss in providing liquidity to CPAMM
if the price changes either down or up, then the LP will experience some loss
Let's see how to derive this equation. Find x
and y
We solved y
and x
in terms of L
and P
. We are now ready to solve IL(d)
Le's find out what are V1
and Vhold
in terms of token y
.
Below we multiplied x1
by P1
to get the price of x1
in term of y
We are now ready to solve the equation of IL(d)
This contract introduces 2 functions to swap tokens on Uniswap V2
swapExactTokensForTokens
- Sell all of input token.
swapTokensForExactTokens
- Buy specific amount of output token.
- Address of tokens (2 or 3) and the address of the router
- Set interfaces for tokens and router
- Transfer
amountIn
frommsg.sender
- Approve
amountIn
torouter
- Set the
path
- Call
swapExactTokensForTokens
on IUniswapV2Router
- Transfer
amountInMax
frommsg.sender
- Approve
amountInMax
torouter
- Set the
path
- Call
swapTokensForExactTokens
on IUniswapV2Router and store amount of WETH spent by Uniswap in amounts (uint[]) - Refund excess WETH to
msg.sender
. Amount of WETH spent by Uniswap is stored in amounts[0]
Sell DAI and buy CRV.
However there is no DAI - CRV pool, so we will execute multi hop swaps, DAI to WETH and then WETH to CRV.
- Address of tokens and the address of the router
- Set interfaces for tokens and router
This function will swap all
of DAI for maximum amount
of CRV. It will execute multi hop swaps from DAI to WETH and then WETH to CRV.
- Transfer
amountIn
frommsg.sender
- Approve
amountIn
torouter
- Setup the swapping
path
- Send CRV to msg.sender
This function will swap minimum
DAI to obtain a specific amount
of CRV. It will execute multi hop swaps from DAI to WETH and then WETH to CRV.
- Transfer
amountInMax
frommsg.sender
- Approve
amountInMax
torouter
- Setup the swapping
path
- Call
swapTokensForExactTokens
on IUniswapV2Router and store amount of DAI spent by Uniswap in amounts (uint[]) - Refund DAI to
msg.sender
if not all of DAI was spent. Amount of DAI spent by Uniswap is stored in amounts[0]
When we add or remove tokens from a Uniswap V2 pool, the liquidity changes.
Let's see how to derive the liquidity delta
As always, we start with the definition:
Preliminary math to derive these equations (green square)
Now we have the math needed, let's now derive the equation for the liquidity delta
Deposit your tokens into an Uniswap V2 pool to earn trading fees.
This is called adding liquidity.
Remove liquidity to withdraw your tokens and claim your trading fees.
- Address of tokens and the addresses of the router and the factory. Declare pair variable
- Set interfaces for tokens, router and factory
- Setup pair (IERC20) by calling getPair() on factory
This function adds liquidity to the Uniswap WETH - DAI pool.
- Transfer
wethAmountDesired
anddaiAmountDesired
frommsg.sender
- Approve
amountwethAmountDesired
anddaiAmountDesired
torouter
- Call
addLiqiuidity()
onrouter
and storewethAmount
,daiAmount
andliquidity
returned from the function call - Refund to msg.sender, excess WETH and DAI that were not added to liquidity
This function removes liquidity from the Uniswap WETH - DAI pool.
- Transfer
liquidity
frommsg.sender
- Approve
liquidity
torouter
- Call
removeLiqiuidity()
onrouter
Tokens in the pool can be borrowed as long as they are repaid in the same transaction plus fee on borrow.
This is called flash swap.
The contract inherit from IUniswapV2Callee
- Address of tokens and the address of the factory
- Set WETH and factory interface then declare pair (IUniswapV2Pair)
- Call getPair() on factory and store the result inside pair variable (which is a IUniswapV2Pair interface)
- Prepare data of bytes to send. This can be any data, as long as it is not empty Uniswap will trigger a flash swap. For this example, we encode WETH and msg.sender.
- Call
swap()
on pair. Find belowswap()
fromIUniswapV2Pair
function swap(
uint amount0Out,
uint amount1Out,
address to,
bytes calldata data
) external;
amount0Out
: Amount of token0 to withdraw from the pool => 0
amount1Out
:Amount of token1 to withdraw from the pool => wethAmount
to
: Recipient of tokens in the pool => address(this)
data
: Data to send to uniswapV2Call => data
This function is called by the DAI/WETH pair contract after we called pair.swap.
Immediately before the pool calls this function, the amount of tokens that we requested to borrow is sent. Inside this function, we write our custom code and then repay the borrowed amount plus some fees.
- Require that
msg.sender
is pair. Only pair contract should be able to call this function. - Require
sender
is this contract. Initiator of the flash swap should be this contract. - Decode
data
. Inside flashSwap we've encoded WETH and msg.sender. - Once the data is decoded, we would write our custom code here (arbitrage). We only emitted events for this example
- Calculate total amount to repay
- Transfer fee amount of WETH from caller (about 0.3% fee, +1 to round up)
- Repay WETH to pair, amount borrowed plus fee
When we fork π΄ the mainnet, we have the current state of the blockchain running locally on our system, including all contracts deployed on it and all transactions performed on it.
- Setup hardhat.config
- Find a whale on etherscan
hardhat.config.js
networks: {
hardhat: {
forking: {
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`,
},
},
}
Note: Replace the ${}
component of the URL with your personal Alchemy API key.
.config
const DAI = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
const DAI_WHALE = process.env.DAI_WHALE;
module.exports = {
DAI,
DAI_WHALE,
};
.env
ALCHEMY_API_KEY=...
Terminal 1
npx hardhat test --network localhost
Terminal 2
ALCHEMY_API_KEY=...
npx hardhat node --fork https://eth-mainnet.g.alchemy.com/v2/$ALCHEMY_API_KEY
This contract assumes that token0 and token1 both have same decimals
Consider Uniswap trading fee = 0.3%
(...soon)
- Uniswap V3 TWAP
- Further reading
- Deploy script
- Unit test
See the open issues for a full list of proposed features (and known issues).
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
Distributed under the MIT License. See LICENSE.txt
for more information.
Reda Aboutika - @twitter - reda.aboutika@gmail.com
Project Link: https://github.com/Aboudoc/Uniswap-v2.git