Uniswap-v3 icon indicating copy to clipboard operation
Uniswap-v3 copied to clipboard

🦄🦄 🦄Single & multi hop swap, add and remove liquidity, flash swap ⚡

Contributors Forks Stargazers Issues MIT License LinkedIn


Table of Contents
  1. About The Project
    • Built With
  2. Getting Started
    • Prerequisites
    • Installation
  3. Usage
  4. Constant Product AMM
  5. Test Single and Multi hop swap
  6. Uniswap V2 Single Hop Swap
  • State variables
  • Function swapExactInputSingleHop
  • Function swapExactOutputSingleHop
  • Uniswap V3 Multi Hop Swap
    • State variables
    • Function swapExactInputMultiHop
    • Function swapExactOutputMultiHop
  • Uniswap V3 Curve of real reserves
  • Test Mint new position
  • Uniswap V3 Liquidity of a single position
  • Test Collect fees
  • Test Increase liquidity
  • Test Decrease liquidity
  • Uniswap V3 Liquidity Delta
  • Uniswap V3 How many tokens to add
  • Uniswap V2 Add and Remove Liquidity
    • State variables
    • Constructor
    • Function addLiquidity
    • Function removeLiquidiquiity
  • Uniswap V3 Price of ETH from sqrtPriceX96
  • Uniswap V3 Tick and sqrtPriceX96
  • Uniswap V3 Price Change from a Swap
  • Uniswap V3 Liquidity Price Graph
  • Uniswap V3 Liquidity Price Graph Example
  • Uniswap V3 Flash Swap
    • State variables
    • Constructor
    • Function flashSwap
  • Uniswap V3 Capital Efficiency
  • Uniswap V3 Just In Time Liquidity
  • Forking mainnet
  • Note
  • Roadmap
  • Contributing
  • License
  • Contact
  • Acknowledgments
  • About The Project

    This project shows how to interact with the main functions of Uniswap V3 and derive the equations used in the protocol

    (back to top)

    Built With

    • Hardhat
    • Ethers

    (back to top)

    Getting Started

    To get a local copy up and running follow these simple example steps.

    Prerequisites

    • npm

      npm init
      
    • hardhat

      npm install --save-dev hardhat
      

      run:

      npx hardhat
      

      verify:

      npx hardhat verify --network goerli "contract address" "pair address"
      

    Installation

    1. Clone the repo

      git clone https://github.com/Aboudoc/Uniswap-v3.git
      
    2. Install NPM packages

      npm install
      
    3. Dependencies

       npm add @uniswap/v3-periphery @uniswap/v3-core
      

      For openzeppelin contract, we'll need to install first solidity 0.7

      npm i @openzeppelin/[email protected]
      

      To fix compiler errors, we'll need to change the compilation settings

    (back to top)

    Usage

    If you need testnet funds, use the Alchemy testnet faucet.

    This project shows how to swap, add and remove liquidity

    Constant Product AMM

    Uniswap V3 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

    Test Single and Multi hop swap

    npx hardhat test test/unlock-account.test.js
    
    npx hardhat test test/swapV3.test.js
    
    Test

    (back to top)

    Uniswap V3 Single Hop Swap

    This contract introduces 2 functions to perform single hop swaps on Uniswap V3

    exactInputSingle - Sell all of input token. exactOutputSingle - Buy specific amount of output token.

    We are interacting with the interface for Uniswap V3 router ISwapRouter

    State variables

    1. Address of tokens (2 or 3) and the address of the router
    2. Set interfaces for tokens and router

    Function swapExactInputSingleHop

    1. Transfer amountIn from msg.sender
    2. Approve amountIn to router
    3. Set the params by preparing struct ISwapRouter.ExactInputSingleParams
    4. Call exactInputSingle on ISwapRouter (router interface)

    Function swapExactOutputSingleHop

    1. Transfer amountInMaxfrom msg.sender
    2. Approve amountInMax to router
    3. Set the params by preparing struct ISwapRouter.ExactOutputSingleParams
    4. Call exactOutputSingle on ISwapRouter and store amount of WETH spent by Uniswap in amountIn (uint)
    5. Refund WETH not spent back to msg.sender
    6. Reset approvals of WETH for router to 0

    Uniswap V3 Multi Hop Swap

    Swap WETH for USDC and then USDC for DAI.

    exactInput - Sell all of input token. exactOutput - Buy specific amount of output token.

    We are interacting with the interface for Uniswap V3 router ISwapRouter

    State variables

    1. Address of tokens and the address of the router
    2. Set interfaces for tokens and router

    Function swapExactInputMultiHop

    This function will swap WETH for maximum amount of DAI.

    1. Transfer amountIn from msg.sender
    2. Approve amountIn to router
    3. Setup the swapping path
    4. Prepare struct ISwapRouter.ExactInputParams
    5. Execute the trade by calling router.exactInput with the parameters prepared above

    Function swapExactOutputMultiHop

    This function will swap minimum amount of WETH for a specific amount of DAI.

    1. Transfer amountInMaxfrom msg.sender
    2. Approve amountInMax to router
    3. Setup the swapping path
    4. Call swapTokensForExactTokens on IUniswapV2Router and store the actual amount of token in swapped for token out in amountSwap (uint)
    5. Refund efund WETH not spent back to msg.sender
    6. Reset approvals of WETH for router to 0

    (back to top)

    Uniswap V3 Curve of real reserves

    In Uniswap V3 the curve of the real reserve is giving by the formula below (orange square)

    Let's derive this equation starting from the constant product equation XY = K

    Maths

    Let's now derive the equation, the curve for the real reserve

    Maths

    Now that we can rewrite x and y in terms of the liquidity L and the current price P, let's now derive the equation for the real reserves (solve for Xv and Yv)

    Maths

    Let's solve for Xv

    Maths

    Let's solve for Yv

    Maths

    The final step to derive the curve for the real reserve is to combine all of the equations that we have derived so far

    Maths

    Later on, we will use this equation to derive the liquidity delta: changing liquidity when we add some amount of token x and token y

    (back to top)

    Test Mint new position

    npx hardhat test test/unlock-account.test.js
    
    npx hardhat test test/liquidityV3.test.js
    
    Test

    (back to top)

    Test Collect fees

    npx hardhat test test/liquidityV3.test.js
    
    Test

    No fees in this case

    (back to top)

    Uniswap V3 Liquidity of a single position 

    We previously derived the curve for the real reserves.

    Let's use this equation to derive the equation for the liquidity(this equation has 3 parts):

    Test

    Let's start with the first case

    Test

    Let's derive the equation for liquidity when the current price >= Pb

    Test

    Let's apply the same technique to find Ly

    Test

    Find Lx and Ly

    Test

    Finally let's put the two equations that we derived earlier

    Test

    (back to top)

    Test Increase liquidity

    npx hardhat test test/liquidityV3.test.js
    
    Test

    (back to top)

    5000 DAI and 2,77 WETH added

    Amounts specified in liquidityV3.test.js:

    const daiAmount = 5000n * 10n ** 18n;
    const wethAmount = 10n * 10n ** 18n;
    

    Learn more about Add liquidity - How many dx, dy to add? in this repo about Constant Product AMM

    Test Decrease liquidity

    npx hardhat test test/liquidityV3.test.js
    

    When we call the function decreaseLiquidity, it doesn't transfer the tokens back to contract

    To actually withdraw the tokens from Uniswap V3, after calling the function decreaseLiquidity(), we'll have to call the function collect()

    Test

    Note that after calling decreaseLiquidity, DAI balance and WETH balance remain the same

    (back to top)

    Uniswap V3 Liquidity Delta

    Test

    let's derive these two equations

    First, preliminary math:

    Test

    Let's define liquidity delta

    Test

    Four steps to calculate liquidity delta:

    Test
    Test

    (back to top)

    Uniswap V3 How many tokens to add

    Let's see a real example of how to calculate the amount of tokens needed when we add liquidity to Uniswap V3

    How much USDC will we need to add with the following parameters?

    Test

    How do we compute delta Y? We'll use the previous equation (Uniswap V3 Liquidity Delta)

    Test

    Comparing with user interface

    You can access the uniswap app to compare the amount of USDC that we will need to put in when the price of ETH is $1754

    Test

    If we compare with the UI, the result is very close

    Test

    (back to top)

    Uniswap V3 Add and Remove Liquidity

    Manage liquidity in Uniswap V3

    Mint new position Increase liquidity Decrease liquidity Collect fees and withdraw tokens

    State variables

    Contract inherits from IERC721Receiver

    1. Address of tokens. Set MIN_TICK, MAX_TICK, TICK_SPACING
    2. Set interfaces for tokens and manager with the INonfungiblePositionManager interface

    Function onERC721Received

    This function is called when safeTransferFrom is called on INonFungiblePositionManager.

    Function onERC721Received

    1. Transfer wethAmountDesired and daiAmountDesired from msg.sender
    2. Approve amountwethAmountDesired and daiAmountDesired to router
    3. Call addLiqiuidity() on router and store wethAmount, daiAmount and liquidity returned from the function call
    4. Refund to msg.sender, excess WETH and DAI that were not added to liquidity

    Function mint

    This function removes liquidity from the Uniswap WETH - DAI pool.

    1. Transfer DAI and WETH from msg.sender into this contract. amount0ToAdd is DAI amount,amount1ToAdd is WETH
    2. Approve manager to spend DAI and WETH from this contract
    3. Set tickLower and tickUpper, price range to add liquidity. Both ticks must be a multiple of TICK_SPACING.
    4. Prepare parameter to add new liquidity and mint new position
    5. Add liquidity by calling manager.mint with the parameters prepared above
    6. manager.mint returns 4 outputs. Refund tokens not added to liquidity back to msg.sender. We pulled in amount0ToAdd and amount1ToAdd. Actual amount added to Uniswap V3 are amount0 and amount1.
    7. Reset approvals of DAI and WETH for manager to 0
    8. Emit Mint with tokenId.

    (back to top)

    Uniswap V3 Price of ETH from sqrtPriceX96

    Test

    Let's find out where the 10**12 comes from

    First, let's find P through an example

    Test

    But we are interested in the price of ETH in terms of USDC

    Test

    But how we calculate the price of ETH starting from sqrtPriceX96?

    This is the variable related to the price of tokens, stored in Uniswap V3 contract

    Test

    Find sqrtPriceX96 variable on Uniswap USDC / ETH pool contract

    Test

    Let's consider sqrtPriceX96 = 2200755647817846498385002322429664. The same can be done with the actual sqrtPriceX95

    Test

    (back to top)

    Uniswap V3 Tick and sqrtPriceX96

    let's see how to convert from tick to sqrtPriceX96 and from sqrtPriceX96 to tick

    First, let's start by reviewing some of the variables

    Test

    We're gonna need these two equations to calculate from tick to sqrtPriceX96 and from sqrtPriceX96 back to tick

    Test

    For the first example, we'll say tht we know what the tick is and we'll try to compute the sqrtPriceX96

    Test

    Let's use python to compute sqrtPriceX96 and tick

    Use python jupyter notebook

    import math
    
    Q96 = 2 ** 96
    
    def tick_to_sqrt_price_x_96(tick):
        return int(1.0001 ** (tick / 2) * Q96)
    
    def sqrt_price_x_96_to_tick(sqrt_price_x_96):
        base = math.sqrt(1.0001)
        p = sqrt_price_x_96 / Q96
        return math.floor(math.log(p, base))
    
    tick = 204632
    
    tick_to_sqrt_price_x_96(tick)
    
    sqrt_price_x_96 = 1892484952596364096357191768742857
    sqrt_price_x_96_to_tick(sqrt_price_x_96)
    
    Test

    (back to top)

    Uniswap V3 Price Change from a Swap

    Test

    First, let's explain what we mean by "we are assuming there is enough liquidity to swap on the curve"

    Test
    • Liquidity is only supported between the price range Pa and Pb, and the price after the swap falls outside of the price range range
    • In the second case, the price P1 after the swap remains in the range Pa and Pb

    Let's go over the math, let's derive the equations

    Test

    => These equations can be applied to Y0, Y1, X0 and X1 since all of these points are on the curve XY = L2

    This is a important fact to remember as we will derive the equations for delta x and delta y

    Test

    We'll do the same trick to find delta y

    Test

    Finally, let's derive the equations for sqrt(P1) and sqrt(P2)

    Test

    (back to top)

    Uniswap V3 Liquidity Price Graph

    Let's see how to read liquidity price graph

    The current liquidity on Uniswap v3 is L and we are trading on the curve XY = L2

    There is a single position supporting this liquidity from the price range [Pa - Pb]

    From Pa, Pb and the current price P, let's convert to ticks (equations inside the square) and let's map it horizontally.

    The vertical axis represents liquidity

    => To the left of the current tick t, all the tokens will be in token 1 (Y)

    => To the right of the current tick t, all the tokens will be in token 0 (X)

    Test

    Let's see why this is:

    Test

    When the current price is equal to Pb, then the liquidity is fully in token Y

    When the price P is equal to Pa, then the liquidity is fully in token X

    => Combinng these two observations, we know that liquidity is in Y for the left of the current tick t, and liquidity is X for the right of t

    (back to top)

    Uniswap V3 Liquidity Price Graph Example

    In the following Liquidity Price Graph of the ETH / USDC pool, in pink you can see the current price

    To the left of the current price all of the tokens are in USDC

    And to the right of the current price, all of the liquidity are in ETH

    Test

    Let's see why

    Notice that in the graph below:

    • X is on the left and Y is on the right
    • ticks are negative
    Test

    Instead of P increasing to the right, we want to convert this into a graph where 1 / P increases to the right

    Inside the first purple rectangle below, we start by mapping the ticks to this new graph (we flip the inequality before mapping into the graph), then we convert 1 / P to ticks inside the second purple rectangle

    Test

    Here is the link to the ETH / USDC pool on Uniswap v3

    (back to top)

    Test Flash Swap

    npx hardhat test test/unlock-account.test.js
    
    npx hardhat test test/flashSwapV3.test.js
    
    npx hardhat test test/flashSwapArbV3.test.js
    

    Uniswap V3 Flash Swap

    Borrow tokens from Uniswap V3 pool and then repay with fee in a single transaction. This is called flash loan.

    We'll call flash on Uniswap V3 pool to borrow WETH.

    State Variables

    1. Address of tokens and the address of the factory
    2. Set WETH interface and declare pool (IUniswapV3Pool)
    3. Set POOL_FEE to 3000 because we'll call DAI / WETH pool with 0.3% fee
    4. Declare struct FlashData with wethAmount and caller

    Constructor

    1. The variable pool is immutable, so initialize it inside the constructor.
    2. Address of the pool can be obtained by calling PoolAddress.computeAddress.
    3. PoolKey can be obtained by calling PoolAddress.getPoolKey

    Function flash

    This function will initialize the flash loan on Uniswap V3 pool by calling pool.flash

    Below is an overview of what will happen after pool.flash is called.

    • The pool sends tokens to the borrower.
    • The pool calls uniswapV3FlashCallback on the borrower.
    • Inside the uniswapV3FlashCallback our customm code is executed. At the end of the code, we must pay back the borrowed amount plus fees.
    1. Prepare data to be passed to pool.flash. Any bytes can be passed, but we will encode our custom struct FlashData.
    2. Call pool.flash
    interface IUniswapV3Pool {
        function flash(
            address recipient,
            uint amount0,
            uint amount1,
            bytes calldata data
        ) external;
    }
    
    

    Function uniswapV3FlashCallback

    This is the function called by pool, after we call pool.flash.

    Here we have the requested borrow amount of WETH. Our custom code logic goes here. In this case, we will simply repay the borrowed amount plus fee.

    1. Require that msg.sender is the pool.
    2. Decode the data into FlashData
    3. Caller stored in FlashData will pay for the fee on borrow. Transfer WETH from decoded.caller into this contract for the amount fee1.
    4. Repay WETH back to the pool. Transfer borrowed amount + fee1.

    (back to top)

    Uniswap V3 Capital Efficiency

    If we were to add the same amount of tokens to Uniswap v2 and Uniswap v3, then how much more can we increase the liquidity in Uniswap v3 if we were to narrow the price range?

    Let's start with these equations from Liquidity Delta section to derive the capital efficiency equation

    Test

    As an example, we calculated the capital efficiency if we were to provide liquidity +/- 2% of the current price P

    In other words, if we were to provide the same amount of token to Uniswap v2 and Uniswap v3, the amount of tokens are deltaX and deltaY

    However, in Uniswap v3, let's say that we provide the liquidity for the price range +/- 2% of the current price, then the liquidity in Uniswap v3 is about 100 times greater than in Uniswap v2.

    Test

    We calculated the ratio of liquidity delta for Uniswap v2 and for Uniswap v3

    Next, let's replace the current price P with another number

    • We're going to say that the current price P is the geometric mean of Pa and Pb
    • We take the equation highlighted previously in green
    • We replace the sqrtP highlighted below by the geometric mean of Pa and Pb (sqrt(PaPb))
    Test

    (back to top)

    Uniswap V3 Just In Time Liquidity

    Alice is a liquidity provider and Bob swaps tokens.

    From this trade, Alice earns some fees :)

    This is how normally how a liquidity provider will earn some fees

    Test

    Let's see how just-in-time liquidity will change how the liquidity providers earn fees

    Test

    In this case, Justin sees Bob's transaction in mempool and front-runs Bob

    Test

    In the example above we assume that Justin has a lot of liquidity in a narrow price range including the current (shown in red)

    Close to the current price P0, Aloce has provided liquidity as shown in the green area

    Whereas in the same price range, Justin provided liquidity as shown in the red area, a lot more than Alice

    This means that when a trade executes in this price range, Justin will earn the majority of the fees (after Bob's trade is processed)

    Note that the price change from P0 to P*1 is less than the change from P0 to P1

    This is beause there is a lot of liquidity arount the current price range in the second case => the price moves very little, this is called just-in-time liquidity

    (back to top)

    Forking mainnet

    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.

    npx hardhat test test/swapV3.test.js
    

    (back to top)

    Note

    This contract assumes that token0 and token1 both have same decimals

    Consider Uniswap trading fee = 0.3%

    Further reading

    You can find Uniswap pools referenced below. Select a pool with the highest TVL

    Uniswap V3 Pool Infos

    You can find official Uniswap documentation below:

    Single Hop Swap

    Multi Hop Swap

    Sources

    (back to top)

    Roadmap

    • [-] Uniswap V3 TWAP
    • [-] Further reading
    • [-] Unit test
    • [-] Flash Swap
    • [ ] Flash Swap test
    • [ ] Uniswap V3 Capital Efficiency (maths)
    • [ ] Uniswap V3 Just In Time Liquidity (maths)
    • [ ] Deploy on mainnet

    See the open issues for a full list of proposed features (and known issues).

    (back to top)

    Contributing

    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!

    1. Fork the Project
    2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
    3. Commit your Changes (git commit -m 'Add some AmazingFeature')
    4. Push to the Branch (git push origin feature/AmazingFeature)
    5. Open a Pull Request

    (back to top)

    License

    Distributed under the MIT License. See LICENSE.txt for more information.

    (back to top)

    Contact

    Reda Aboutika - @twitter - [email protected]

    Project Link: https://github.com/Aboudoc/Uniswap-v3.git

    (back to top)

    Acknowledgments

    (back to top)