How to Create Your Own Decentralized Exchange (DEX) in Solidity & HardHat
Tags: general, blockchaintechnology, decentralizedexchang, environment
Published: 2023-04-06 23:31:57

---
title: How to Create Your Own Decentralized Exchange (DEX) in Solidity & HardHat
published: true
date: 2023-04-06 23:17:43 UTC
tags: General,blockchaintechnology,decentralizedexchang,environment
canonical_url: https://cryptoloom.xyz/how-to-create-your-own-decentralized-exchange-dex-in-solidity-hardhat/
---
Decentralized Exchanges (DEXes) play a crucial part in the world of decentralized finance (DeFi), offering users complete control over their assets while simultaneously providing a platform for the trustless transaction of cryptocurrencies. Traditional centralized exchanges (CEXes) require users to deposit assets on the platform and relinquish their private keys, creating a centralized point of failure.

Are you interested in creating your own decentralized exchange on the Ethereum blockchain? Look no further! This article will guide you through the process, providing code examples in Solidity and setting up a development environment using HardHat.

## Decentralized Exchange Functionality

A basic decentralized exchange should allow users to:

1. Deposit tokens or ether.
2. Trade tokens or ether in a trustless manner.
3. Withdraw tokens or ether.

To achieve this functionality, we’ll build our DEX using Ethereum’s most popular smart contract programming language, Solidity. To make our development process streamlined and efficient, we’ll use the HardHat development environment.

## Setting Up the Development Environment

First, let’s set up our development environment using HardHat. Follow these simple steps:

1. Install Node.js if you haven’t done so already. You can download it [here](https://nodejs.org/).
2. Create a new directory for your project and navigate to it in your terminal or command prompt.
3. In the project directory, run the command `npm init` to initialize a new Node.js project.
4. Install HardHat by executing `npm install --save-dev hardhat`.
5. Run `npx hardhat` to generate a sample HardHat config file, and choose to create a sample project when prompted.
6. Delete the sample contracts (`Greeter.sol` and `Token.sol`) as well as the test file (`sample-test.js`).

Now, you have a blank HardHat project set up, and you’re ready to start writing your own decentralized exchange from scratch.

## Creating a Token for Our Exchange

First, we’ll create an ERC20 token for our demonstration. An ERC20 token is a widely accepted token standard on the Ethereum blockchain, allowing for seamless interoperability between different decentralized applications.

In your project directory, create a new directory called `contracts`. Within the `contracts` directory, create a new file called `MyToken.sol`. The code for our simple ERC20 token is as follows:

```solidity
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
        _mint(msg.sender, initialSupply);
    }
}

```

Here, we make use of the `ERC20` contract provided by the [OpenZeppelin](https://openzeppelin.com/) library. It takes care of the nitty-gritty of ERC20 token implementation, so we can simply provide an initial supply and our token’s name and symbol.

## Building the Decentralized Exchange Contract

Next, create a new file called `DEX.sol` in the `contracts` directory. This is where we’ll develop our decentralized exchange’s smart contract.

### Initialization

Let’s start by defining the basic structure of our contract and initializing it:

```solidity
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract DEX {
    using SafeERC20 for ERC20; // Use OpenZeppelin's SafeERC20 to deal with tokens securely
    address public owner; // The owner of the DEX

    constructor() {
        owner = msg.sender;
    }
}

```

Here, we import both the `ERC20` and `SafeERC20` contracts from the OpenZeppelin library. We also set the DEX owner to the address that deploys the contract.

### Adding Token Support

Now, let’s build the functionality for adding new tokens to our DEX:

```solidity
struct Token {
    ERC20 tokenContract;
    uint256 totalLiquidity;
}

mapping(address => Token) public tokens; // Address of token contract to Token struct

function addToken(address tokenAddress) public {
    require(msg.sender == owner, "Only owner can add tokens.");
    ERC20 token = ERC20(tokenAddress);
    tokens[tokenAddress] = Token({tokenContract: token, totalLiquidity: 0});
}

```

We define a new struct `Token`, which holds an instance of the token’s contract and the token’s total liquidity on our DEX. We create a mapping from the token’s contract address to the `Token` struct. Then, we add a function `addToken` that allows the owner of the DEX to add new tokens by providing the token’s contract address.

Notice the `require` statement to ensure that only the owner can add tokens: it checks if the function caller is the owner, and if not, the transaction is reverted with a custom error message.

### Deposit and Withdraw Functions

Next, we’ll implement the `deposit` and `withdraw` functions. Users should be able to deposit and withdraw tokens as well as ether. For token deposits and withdrawals, we’ll make use of the `transfer` and `transferFrom` functions provided by the `ERC20` contract.

Create the following functions in the `DEX.sol` file:

```solidity
mapping(address => mapping(address => uint256)) public tokenBalances; // User address -> token address -> token balance

function depositToken(address tokenAddress, uint256 amount) public {
    require(tokenAddress != address(0), "Cannot deposit zero address token."); // Safety check
    require(tokens[tokenAddress].tokenContract != ERC20(address(0)), "Token not supported by DEX.");
    tokens[tokenAddress].tokenContract.safeTransferFrom(msg.sender, address(this), amount);
    tokenBalances[msg.sender][tokenAddress] += amount;
}

function deposit() payable public {
    require(msg.value > 0, "Cannot deposit zero value."); // Safety check
    tokenBalances[msg.sender][address(0)] += msg.value; // ETH is stored as zero address
}

mapping(address => mapping(address => uint256)) public etherBalances; // User address -> token address -> ether balance

function withdrawToken(address tokenAddress, uint256 amount) public {
    require(tokenAddress != address(0), "Cannot withdraw zero address token.");
    require(tokens[tokenAddress].tokenContract != ERC20(address(0)), "Token not supported by DEX.");
    tokenBalances[msg.sender][tokenAddress] -= amount;
    tokens[tokenAddress].tokenContract.safeTransfer(msg.sender, amount);
}

function withdraw(uint256 amount) public {
    require(etherBalances[msg.sender][address(0)] >= amount, "Insufficient ether balance.");
    etherBalances[msg.sender][address(0)] -= amount;
    payable(msg.sender).transfer(amount);
}

```

With these four functions, users can deposit and withdraw both tokens and ether, and we update their balances accordingly.

### Trading

Now for the most important part: the trading functionality!

First, create a struct for an order:

```solidity
struct Order {
    address trader;
    address token;
    uint256 tokensTotal;
    uint256 tokensLeft;
    uint256 etherAmount;
    uint256 filled;
}

```

Then, define an array to store open orders and a mapping to store order history. We also implement an event to emit when a new order is created:

```solidity
Order[] public openOrders;
mapping(address => Order[]) public orderHistories;

event OrderPlaced(uint256 orderId, address indexed trader, address indexed token, uint256 tokensTotal, uint256 etherAmount);

```

We can now create the function for placing a new order:

```solidity
function placeOrder(address token, uint256 tokensTotal, uint256 etherAmount) public {
    require(tokens[token].tokenContract != ERC20(address(0)), "Token not supported by DEX.");
    require(tokenBalances[msg.sender][token] >= tokensTotal, "Insufficient token balance.");

    Order memory newOrder = Order({
        trader: msg.sender,
        token: token,
        tokensTotal: tokensTotal,
        tokensLeft: tokensTotal,
        etherAmount: etherAmount,
        filled: 0
    });

    openOrders.push(newOrder);
    tokenBalances[msg.sender][token] -= tokensTotal;

    emit OrderPlaced(openOrders.length-1, msg.sender, token, tokensTotal, etherAmount);
}

```

Finally, let’s implement the `fillOrder` function, allowing users to complete open orders:

```solidity
event OrderFilled(uint256 orderId, address indexed trader, uint256 tokensFilled, uint256 etherTransferred);

function fillOrder(uint256 orderId) public {
    Order storage order = openOrders[orderId];
    uint256 tokensToFill = order.tokensLeft;
    uint256 etherToFill = (tokensToFill * order.etherAmount) / order.tokensTotal;

    require(etherBalances[msg.sender][address(0)] >= etherToFill, "Insufficient ether balance.");

    etherBalances[order.trader][address(0)] += etherToFill;
    etherBalances[msg.sender][address(0)] -= etherToFill;
    tokenBalances[msg.sender][order.token] += tokensToFill;
    order.tokensLeft = 0;
    order.filled += tokensToFill;

    orderHistories[msg.sender].push(order);
    if (order.trader != msg.sender) orderHistories[order.trader].push(order);
    delete openOrders[orderId];

    emit OrderFilled(orderId, order.trader, tokensToFill, etherToFill);
}

```

With the `fillOrder` function, users can complete orders, exchanging their ether for tokens. The order is then removed from the open orders list and added to each party’s order history.

## Testing and Deployment

With our DEX contract completed, you can now deploy it to a local Ethereum blockchain using HardHat, test your code, or even deploy it to a public Ethereum test network.

Well done! You’ve now created your own decentralized exchange on the Ethereum blockchain. From here, you can extend the functionality of your DEX, add more trading pairs, or improve the user interface by implementing a frontend application.

### References

1. [Ethereum.org: Solidity](https://ethereum.org/greeter)
2. [OpenZeppelin: Smart Contract Library](https://openzeppelin.com/)
3. [HardHat: Ethereum Development Environment](https://hardhat.org/)

The post [How to Create Your Own Decentralized Exchange (DEX) in Solidity & HardHat](https://cryptoloom.xyz/how-to-create-your-own-decentralized-exchange-dex-in-solidity-hardhat/) appeared first on [CryptoLoom](https://cryptoloom.xyz).