You might be wondering how to kickstart building your decentralized exchange (DEX) project, or perhaps you're curious about the inner workings of the UniswapV2 contract or DEXs in general. Well, you're in the right spot. Welcome! In this tutorial, I'll guide you through the process of cloning UniswapV2 and bringing your project to life. I'll also provide insights into how the UniswapV2 contract operates. So, without any delay, let's jump right in!

Estimated time to follow along: 45 mins - 1 hour

But before we delve in, here's a list of things you need to have in place before you begin following this tutorial.

Prerequisites

  • Basic understanding of Ethereum and smart contracts.

  • Familiarity with Solidity.

  • Familiarity with some crypto terminologies.

  • Setting up the development environment: Remix and Metamask.

Overview of Decentralized Exchanges (DEX)

A Decentralized Exchange (DEX) is a platform where users can trade different cryptocurrencies directly with one another without the need for an intermediary or centralized authority. In contrast to traditional cryptocurrency exchanges, where transactions are managed by a central entity, DEXs operate using smart contracts on a blockchain. These contracts allow users to swap tokens or assets without having to trust a middleman.

Introduction to UniswapV2

UniswapV2, being the second version of the Uniswap protocol, is one of the most popular DEXs in the Ethereum ecosystem, which was built on the principle of the Automated Market Maker (AMM). Uniswap protocol allows for trustless token swaps without the need for order books or intermediaries.

Before delving deeper into the mechanics of UniswapV2, let's pause for a moment and backtrack to the earlier version of Uniswap, highlighting the difference with version two.

UniswapV1UniswapV2
UniswapV1 was written in Vyper.UniswapV2 is written in Solidity.
There is no direct token exchange in V1. Every liquidity pool in UniswapV1 is between an ERC-20 token and ETH, which means that if you wanted to swap LINK ↔ USDC, you need to do LINK↔ ETH first and then USDC↔ ETH, making it a two-step trading process.Introduced direct ERC-20 to ERC-20 pairs, allowing users to trade between two ERC-20 tokens without requiring ETH as an intermediary, which means you can swap your LINK token to USDC directly.
Had no built-in price oracle functionality.V2 introduced a price oracle which enables other contracts in the Ethereum ecosystem to make estimates of how one token's price compares to another.
No flash swap functionality.Introduced flash swaps, allowing users to instantly borrow tokens from a liquidity pool as long as they return the borrowed amount (plus a small fee) within the same transaction. This opens up various opportunities like arbitrage without upfront capital.
Had no protocol fee mechanism.Implemented a protocol fee switch which, when turned on, would take 0.05% of the 0.30% fee from liquidity providers for the Uniswap treasury.

These are the primary differences, but as with any significant protocol upgrade, there are many other changes and adjustments in the background. The objective of these upgrades in UniswapV2 was to make the protocol more flexible, secure, and efficient. UniswapV2 has made important progress in decentralized finance (DeFi) by adding these new features, as shown in the diagram above, that make it more useful and solidify its position as a top decentralized exchange (DEX) in the Ethereum ecosystem.

Why Was UniswapV2 Needed?

Version two of Uniswap was introduced out of the need to address several limitations in the decentralized trading space. The earlier version, UniswapV1, while groundbreaking, had its challenges highlighted in the table above. Users couldn't swap one type of token directly for another without going through Ethereum first, which was like always needing to change your money to euros before buying pounds. UniswapV1 also had a vulnerability which UniswapV2 aimed to mitigate, you can check out the exploit in detail here. Plus, the system's methods for determining token prices needed refining, and there were opportunities to introduce more advanced trading features. So, in a nutshell, UniswapV2 stepped in to make trading simpler, more efficient, and even more decentralized, setting a new standard in crypto trading.

Why Not Clone UniswapV3?

You may have heard that UniswapV3 is active, and you might be wondering if it's a better choice to clone UniswapV3 instead of V2. Many AMMs, like Sushiswap, are a fork of UniswapV2. UniswapV2 is simpler compared to V3, making it easier to manage. Once you understand how UniswapV2 works, you'll have a good grasp of most AMMs, which will make it relatively straightforward to adapt to UniswapV3 as well.

Understanding UniswapV2 Architecture: Core vs Periphery

Before we consider cloning UniswapV2, It's important that you understand how UniswapV2 is structured first. UniswapV2's architecture consists of two parts: the Core and the Periphery.

The core part of the protocol handles its essential functions, like making and overseeing liquidity pools and enabling token exchanges. This core consists of the following three different contracts:

  • Pair Contract

  • Factory Contract

  • ERC20 Contract

On the other hand, the periphery is for interacting with the core. It comprises the Router Contract. We will explain the roles of these contracts in the next section.

UniswapV2's Core

Now, let's delve deeper into the core contracts of UniswapV2 and examine them closely. You can find the contracts here:

UniswapV2Factory.sol

The primary role of the factory contract is to create and manage the individual token pair contracts (liquidity pools) on Uniswap. Whenever a new token pair is to be created on Uniswap, this contract gets to work. It ensures that for each unique pair, there's only one corresponding pool. It keeps track of all the created pairs. So, when a user or a dApp wants to interact with a specific pair, the Factory provides the corresponding address of that pair's contract.

UniswapV2Pair.sol

Liquidity Pools: Liquidity pools are the big banks of tokens where users can trade. Liquidity providers put their tokens into these pools. They make decentralized trading work by ensuring there are always tokens available to trade with. Instead of waiting for a buyer or seller, like in traditional order-book-based exchanges, you're basically trading with the pool directly.

Token Reserves: Each pair contract maintains reserves of the two tokens, for example WETH/USDC pair. The contract keeps these reserves in balance according to the trades happening on the platform.

Adding/Removing Liquidity: Users can provide liquidity to a pair by depositing both tokens into the contract; for example, a user who wants to provide liquidity for the WETH/USDC pair will deposit both WETH and USDC tokens into the contract. In return, they receive liquidity tokens, which represent their share in the pool called the LP token. When they want to withdraw their provided liquidity, they can redeem these tokens.

Handling Token Swaps: When a user wants to swap one token for another, this contract processes the request. The reserves are adjusted, and based on a formula (the x*y=k invariant), it determines the amount of tokens the user receives. If you want to understand how much amount of tokens the user receives based on the constant product formula used by AMMs, checkout this video.

UniswapV2ERC20.sol

The UniswapV2ERC20 contract represents a certificate (LP Token) given to users when they provide tokens to UniswapV2's trading pairs. It's like when you give your coat at a restaurant's coat check, and you get a token or receipt in return. You can use that token later to claim your coat back.

Together, these core contracts form the foundation of UniswapV2, each with its specific role to make sure users have a smooth decentralized trading experience. This setup not only makes trading easy but also encourages users to be active in the ecosystem, all while keeping the platform secure and efficient.

UniswapV2's Periphery

The periphery contracts in UniswapV2 function as the interface layer for developers. While you can interact with the core contracts directly, it’s recommended to go through the periphery also known as Router, primarily to avoid potential pitfalls.

Periphery Contracts

UniswapV2Router01.sol: Consider this contract an older version of the interface. It has some problems, which is why we don't recommend using it anymore. But keep in mind, periphery contracts, like this one, don't hold any assets, so it's relatively easy to phase them out and switch to UniswapV2Router02 instead.

UniswapV2Router02.sol: This is the contract you should use for most Uniswap actions. It's the newer and more reliable interface that we suggest for integrating with dApps or making external calls. It simplifies your experience by hiding many of the complexities of direct core interaction.

UniswapV2Migrator.sol: This contract is used to migrate liquidity from UniswapV1 to UniswapV2.

Tutorial: Cloning the UniswapV2 Smart Contracts

info
💡 This tutorial will focus on the backend of a UniswapV2 clone, but in future guides, we will cover the frontend development and make use of Covalent’s XY=K endpoints, which allow for the retrieval of data from various protocols that are similar to UniswapV2. If you’re curious, check out thishttps://www.covalenthq.com/blog/xyk-announcement/ detailed announcement about the XY=K endpoints.

Now, let's dive into the step-by-step process of cloning UniswapV2. If you want to build your own version of UniswapV2, whether it's for customization or learning purposes, you can use this guide.

1

Accessing the Contracts

Visit the GitHub repository provided here and either use the template from there or download the zip file to your computer.

1.1

All the contracts in this repository have been duplicated from UniswapV2 and are conveniently placed in a single folder, simplifying the cloning process instead of managing various repositories. Within the contract folder, you'll find four different files:

  • Core.sol

  • Multicall.sol

  • Router

  • WETH

1.2

As shown in the image above, the Core.sol file consists of the following:

IUniswapV2Factory: This is the interface for the UniswapV2 Factory contract. It lays out the necessary functions the factory must have, like createPair functionality, which creates new token trading pairs.

  • IUniswapV2Pair: This is the interface for the UniswapV2 Pair contract. It highlights the necessary functionalities a pair (token trading pair) must implement, such as managing liquidity or fetching reserves.

  • IUniswapERC20: This defines the standard functions that the Uniswap version of the ERC20 contract should have.

  • IERC20: This is the interface for the standard ERC20 token. It provides a blueprint for functions like transfer, approve, balanceOf, and so on.

  • IUniswapV2Callee: is an interface for the callback function used in UniswapV2 to handle certain advanced trading operations, enabling trade interactions.

  • UniswapV2ERC20: This is Uniswap's own implementation of the ERC20 token standard. It includes functionalities from the IUniswapERC20 interface and has specifics that make it tailored for Uniswap's liquidity provision system.

  • UniswapV2Pair: This implements the actual behavior of a token trading pair on Uniswap. It manages the reserves of each token in the pair, handles swaps, and deals with liquidity events.

  • UniswapV2Factory: This is the contract that manages the creation of new token trading pairs. It's where new pairs are created, and it maintains a record of all available pairs.

  • SafeMath library: This library offers mathematical operations with safety checks to prevent actions like overflows and underflows when dealing with arithmetic operations in Solidity.

  • Math library: Offers basic math operations, often used in conjunction with SafeMath to facilitate calculations safely.

  • UQ112x112: This is a library used for handling floating-point numbers since solidity lacks native support for floating-point numbers. So, UQ112x112 serves as the necessary library whenever floating-point numbers are required in Uniswap such as calculating token prices within liquidity pools and determining the token amounts involved in trade executions.

1.3

The Router.sol file contains the following interfaces and libraries:

  • IUniswapV2Factory

  • IUniswapV2Pair

  • IERC20

  • SafeMath library

  • IUniswapV2Router01: The interface which defines the available functions for interacting with UniswapV2. Other contracts use it to perform actions like swapping tokens, adding liquidity, and removing liquidity.

  • IUniswapV2Router02: This is an updated version of IUniswapV2Router01, offering new features like the ability to swap tokens across multiple liquidity pools.

  • IWETH: This contract interface defines the functions for interacting with WETH.

  • UniswapV2Library: This library contract contains various functions used by the UniswapV2 protocol, including calculations for prices, retrieving liquidity pool reserves, and performing swaps.

  • TransferHelper: This library contract includes functions for transferring tokens between addresses. The UniswapV2 protocol uses these functions for swaps, as well as adding and removing liquidity.

1.4

We also have two other standalone contract files:

  • Multicall.sol: This is the contract that allows you to batch multiple calls to other smart contracts in a single transaction.

  • WETH.sol: This contract represents Wrapped ETH (WETH). WETH is an ERC-20 token that is pegged to the price of ETH. It is used in Uniswap to facilitate swaps between ERC-20 tokens.

For example, you can use Multicall.sol to batch multiple calls to the IUniswapV2Router02 contract to swap tokens between multiple liquidity pools. You can also use WETH.sol to deposit ETH into the WETH contract and then swap it for other tokens.

With an understanding of the roles of each of these contracts, interfaces, and libraries in our contract folder, we are now ready to continue with our tutorial.

2

Deployment of the UniswapV2Factory Contract

Once you've copied or downloaded the zip file from the GitHub repository, transfer the contract to REMIX. In this step, there's no requirement to set up a Hardhat or Foundry project. We're choosing Remix to deploy to streamline the process for your convenience. Your Remix environment should look like this:

2.1

In this tutorial, we will demonstrate the deployment process on the Sepolia testnet network. However, feel free to use any network that suits your preferences.

Next, we will start by deploying the factory contract. To deploy the factory contract, we'll need to provide the feeSetter address as an argument in the constructor.

2.2

The feeSetter is the address responsible for adjusting the fees charged for swaps in Uniswap protocol.

Enter the address in the deployment tab, as indicated in the image below. Before clicking the Deploy button, ensure that your Remix environment is connected to the Injected Provider-Metamask.

2.3

Once you've configured the feeSetter, proceed by clicking the Deploy tab to initiate the deployment of the Factory contract. This action will trigger a confirmation request for the contract deployment transaction in Metamask, similar to the following:

2.4

After confirming, you'll notice the deployed Factory contract in the Deployed Contract section within Remix, and you also have the option to view the deployed contract on Etherscan.

3

UniswapV2Factory Contract Interaction

Once the Factory contract has been deployed, the next step is to interact with its functionalities.

One major function here is to create a new pair contract, this createPair() function takes in two token addresses. When called, this function creates a new liquidity pool for these tokens if it doesn’t already exist.

3.1

Some other interactions you can make in the UniswapV2Factory contract:

  • To set the address that will receive the fees from swaps on the protocol, you would call the setFeeTo() function and pass in the address of the recipient.

  • To set the address that can change the feeTo address, you would call the setFeeToSetter() function and pass in the address of the new feeToSetter.

  • To get an array of all the liquidity pool addresses that have been created by the contract, you would call the allPairs() function.

  • To get the number of liquidity pool addresses that have been created by the contract, you would call the allPairsLength() function.

  • To get the address that is currently receiving the fees from swaps on the protocol, you would call the feeTo() function.

  • To get the address that can change the feeTo address, you would call the feeToSetter() function.

  • To get the address of the liquidity pool that contains the two tokens specified by the tokenA and tokenB addresses, you would call the getPair() function.

3.2

Another major interaction here is to call the INIT_CODE_PAIR_HASH function to get the Init_hash.

The INIT_CODE_PAIR_HASH constant is a hash of the bytecode of the UniswapV2Pair contract, which is used to verify the authenticity of the UniswapV2Pair contract when it is deployed. When a new UniswapV2Pair contract is deployed, the contract's bytecode is hashed and compared to the INIT_CODE_PAIR_HASH constant. If the hashes match, then the contract is verified as authentic.

3.3

Inside the UniswapV2Library contract found in the Router.sol, change the Init_hash in the pairFor() function and remove the 0x prefix. In this case, we should have this:

246ba35fa37fb6bf3b71cd68dd22514e93a5d804976bfd9895b26b4434adb34d

3.4

We should have this now after the replacement:

4

UniswapV2Router02 and Multicall Contract Deployment

The next step is to deploy the UniswapV2Router02 contract. Here, you need to pass in the address of the WETH and factory contract, which means you have to deploy the Weth9.sol contract first to get the contract address.

Note: Deploying the UniswapV2Router02 will throw a contract code exceeding 24576 error, to fix this, check optimization on your remix deployment tab and set the runs to 500. After setting it, you can then proceed to deploy the contract.

4.1

The Router Contract has been deployed successfully and is ready for interaction. You can interact with various functionalities within the contract, such as adding liquidity through the addLiquidity function, removing liquidity with the removeLiquidity function, and so on.

4.2

Other functionalities in the UniswapV2Router02 can be seen in the image below. Feel free to explore and interact with these functions as needed.

4.3

Following the deployment of the UniswapV2Router02 contract, the next contract to be deployed is the Multicall contract.

After the Multicall contract is deployed, your contract is set to be connected to the V2 interface.

This straightforward process allows you to clone the UniswapV2 contract easily. If you aim to create your unique DEX, simply replace every instance of "UniswapV2" in the contract with your protocol name, and your DEX is ready to go.

Uniswap Version Roadmap

UniswapV3 was launched in May 2021. It is a major upgrade over V2, with a number of new features and improvements. V3 allows liquidity providers to specify the price range they want to provide liquidity for, which can help to reduce slippage and improve capital efficiency. V3 also supports multiple tokens per pool, which can make it easier to find liquidity for less popular tokens.

UniswapV4 builds on the improvements of V3 with even more features and functionality. Some of the features of V4 include:

  • Hooks: Buttons that developers can tweak to add new options to their pools, enabling things like dynamic swap fees, trading limits, and integration with other protocols for extra yield.

  • Singleton Contract: A unified contract for all token pairs, boosting gas efficiency and slashing the deployment cost of new pairs. It includes a flash accounting system for reduced gas fees.

  • Native ETH: Direct pairing with ETH, bypassing the need for WETH, making trades simpler and less costly.

  • Improved Developer Experience: V4 enhances the developer toolkit with features like ERC-1155 accounting and governance controls for better fee and setting management.

The future roadmap of Uniswap is still being developed, but it is clear that the protocol is constantly evolving and improving. As the DeFi space continues to grow, Uniswap is well-positioned to remain a leading player.

Conclusion

In this tutorial, we've explored decentralized exchanges, specifically diving into the inner workings of UniswapV2. We've covered everything from its structure to its core and periphery components, giving us the knowledge to create and deploy these contracts on a testnet. While we've mainly focused on V2, DeFi keeps evolving, with UniswapV3 and V4 already here. However, understanding V2 serves as a strong foundation for future endeavours. As developers, there are exciting opportunities ahead to innovate, improve, and delve deeper into the intricacies of UniswapV3. The decentralized future awaits your contributions.

References & Further Reading