How to Build a Decentralized Exchange (DEX)
In this guide, you'll learn how to create your own simple decentralized exchange (DEX) using thirdweb.
We'll build a DEX contract that allows users to swap between a native token (like MATIC on Polygon) and an ERC20 token of your choice.
Then we'll create a simple web application that interacts with the DEX contract.
Here's an overview of what we'll cover:
- Creating the DEX smart contract
- Building the DEX web application
Let's get started!
Video Tutorial
Prerequisites
Before diving in, make sure you have the following:
- A thirdweb account
- Your thirdweb API key (get it from your account settings)
- Node.js installed on your machine
- Basic knowledge of React, Next.js, TypeScript
Step 1: Create the DEX Smart Contract
We'll start by creating the DEX smart contract using thirdweb's CLI tools.
Open your terminal and run:
npx thirdweb create contract
Name your project 'dex-contract' and choose Hardhat and TypeScript.
Once created, change into the new directory:
cd dex-contract
Open the project in your code editor. In the contracts/
folder, rename Contract.sol
to DEX.sol
.
Update the contract code in DEX.sol
(you can find it in the Contracts GitHub repo):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@thirdweb-dev/contracts/base/ERC20Base.sol";
contract DEX is ERC20Base {
address public token;
constructor (address _token, address _defaultAdmin, string memory _name, string memory _symbol)
ERC20Base(_defaultAdmin, _name, _symbol)
{
token = _token;
}
function getTokensInContract() public view returns (uint256) {
return ERC20Base(token).balanceOf(address(this));
}
function addLiquidity(uint256 _amount) public payable returns (uint256) {
uint256 _liquidity;
uint256 balanceInEth = address(this).balance;
uint256 tokenReserve = getTokensInContract();
ERC20Base _token = ERC20Base(token);
if (tokenReserve == 0) {
_token.transferFrom(msg.sender, address(this), _amount);
_liquidity = balanceInEth;
_mint(msg.sender, _amount);
}
else {
uint256 reservedEth = balanceInEth - msg.value;
require(
_amount >= (msg.value * tokenReserve) / reservedEth,
"Amount of tokens sent is less than the minimum tokens required"
);
_token.transferFrom(msg.sender, address(this), _amount);
unchecked {
_liquidity = (totalSupply() * msg.value) / reservedEth;
}
_mint(msg.sender, _liquidity);
}
return _liquidity;
}
function removeLiquidity(uint256 _amount) public returns (uint256, uint256) {
require(
_amount > 0, "Amount should be greater than zero"
);
uint256 _reservedEth = address(this).balance;
uint256 _totalSupply = totalSupply();
uint256 _ethAmount = (_reservedEth * _amount) / totalSupply();
uint256 _tokenAmount = (getTokensInContract() * _amount) / _totalSupply;
_burn(msg.sender, _amount);
payable(msg.sender).transfer(_ethAmount);
ERC20Base(token).transfer(msg.sender ,_tokenAmount);
return (_ethAmount, _tokenAmount);
}
function getAmountOfTokens(
uint256 inputAmount,
uint256 inputReserve,
uint256 outputReserve
)
public pure returns (uint256)
{
require(inputReserve > 0 && outputReserve > 0, "Invalid Reserves");
// We are charging a fee of `1%`
// uint256 inputAmountWithFee = inputAmount * 99;
uint256 inputAmountWithFee = inputAmount;
uint256 numerator = inputAmountWithFee * outputReserve;
uint256 denominator = (inputReserve * 100) + inputAmountWithFee;
unchecked {
return numerator / denominator;
}
}
function swapEthTotoken() public payable {
uint256 _reservedTokens = getTokensInContract();
uint256 _tokensBought = getAmountOfTokens(
msg.value,
address(this).balance,
_reservedTokens
);
ERC20Base(token).transfer(msg.sender, _tokensBought);
}
function swapTokenToEth(uint256 _tokensSold) public {
uint256 _reservedTokens = getTokensInContract();
uint256 ethBought = getAmountOfTokens(
_tokensSold,
_reservedTokens,
address(this).balance
);
ERC20Base(token).transferFrom(
msg.sender,
address(this),
_tokensSold
);
payable(msg.sender).transfer(ethBought);
}
}
Once your contract code is complete, deploy it using:
npx thirdweb deploy
Select the network you want to deploy to (make sure it matches where your ERC20 token is deployed).
After deployment, copy the contract address. We'll need this when building the app.
Step 2: Build the DEX Application
Now let's build a simple web app that interacts with our DEX contract. We'll use Next.js and the thirdweb SDK.
From your terminal, run:
npx thirdweb create app
Name the project 'dex-app' and choose Next.js and TypeScript.
Navigate to the newly created directory:
cd dex-app
Open the project in your code editor and install the dependencies:
yarn
Configure the thirdweb provider
Open pages/_app.tsx
and configure the thirdweb provider with your API key and network:
import { ThirdwebProvider } from '@thirdweb-dev/react';
function MyApp({ Component, pageProps }) {
const API_KEY = process.env.NEXT_PUBLIC_API_KEY || '';
const activeChain = 'mumbai';
return (
<ThirdwebProvider supportedChains={[activeChain]} apiKey={API_KEY}>
<Component {...pageProps} />
</ThirdwebProvider>
);
}
export default MyApp;
Make sure to add your thirdweb API key to the .env
file.
Build the swap components
Create a new component called SwapInput
to handle the input fields:
export default function SwapInput() {
// Component code here
}
This component will:
- Allow the user to enter an amount to swap
- Display token balances
- Handle setting max amount
Next, update pages/index.tsx
with the main swap functionality:
import { useContract } from '@thirdweb-dev/react';
import SwapInput from 'components/SwapInput';
const TOKEN_ADDRESS = '0x123...'; // ERC20 token address
const DEX_ADDRESS = '0x456...'; // DEX contract address
export default function Home() {
const tokenContract = useContract(TOKEN_ADDRESS);
const dexContract = useContract(DEX_ADDRESS);
const [tokenBalance, setTokenBalance] = useState('0');
const [nativeBalance, setNativeBalance] = useState('0');
const [tokenSymbol, setTokenSymbol] = useState('');
const [amountOut, setAmountOut] = useState(0);
const [isLoading, setIsLoading] = useState(false);
// useEffect hooks to fetch balances & allowance
// ...
async function executeSwap() {
try {
setIsLoading(true);
// Approve DEX to spend token
await tokenContract.call('approve', [DEX_ADDRESS, tokenAmount]);
const tx = currentType === 'native'
? await dexContract.call('swapEthToToken', {
value: toWei(nativeAmount),
})
: await dexContract.call('swapTokenToEth', [
toWei(tokenAmount),
]);
await tx.wait();
alert(`Swap successful!`);
} catch (err) {
console.error(err);
alert('An error occurred');
} finally {
setIsLoading(false);
}
}
return (
<div>
<SwapInput
symbol='MATIC'
type='native'
balance={nativeBalance}
amount={nativeAmount}
onAmountChange={setNativeAmount}
/>
<button onClick={() => setCurrentType(t => t === 'native' ? 'token' : 'native')}>
Switch
</button>
<SwapInput
symbol={tokenSymbol}
type='token'
balance={tokenBalance}
amount={tokenAmount}
onAmountChange={setTokenAmount}
/>
{amountOut > 0 && (
<p>You will receive ~{formatUnits(amountOut)} tokens</p>
)}
{address ? (
<button onClick={executeSwap} disabled={isLoading}>
{isLoading ? 'Swapping...' : 'Swap'}
</button>
) : (
<p>Connect wallet to swap</p>
)}
</div>
)
}
This component handles:
- Fetching token balances and symbol
- Calculating amount of tokens out
- Approving the DEX to spend tokens
- Executing the swap
Test it out
Start the development server:
yarn dev
Open http://localhost:3000 in your browser to test out the DEX!
Connect your wallet, enter an amount, and click Swap to exchange between the native token and your ERC20 token.
Step 3: Deploy Your DEX App
Once you have tested your DEX locally and everything is working as expected, you can deploy it to share with others.
Deploy the Smart Contract
First, deploy your DEX smart contract to a live network using the thirdweb CLI. Make sure you have your wallet set up with the necessary funds for the network you are deploying to.
From the dex-contract
directory, run:
npx thirdweb deploy
Select the network you want to deploy to and confirm the transaction in your wallet. Once deployed, you will see the live contract address printed out. Copy this as you will need it to configure your web app.
Configure the Web App
In your web app, open the .env
file and add the following:
NEXT_PUBLIC_DEX_ADDRESS=<your-dex-contract-address>
NEXT_PUBLIC_TOKEN_ADDRESS=<your-token-contract-address>
Replace <your-dex-contract-address>
with the address of your deployed DEX contract, and <your-token-contract-address>
with the address of your ERC20 token contract.
Deploy the Web App
To deploy your Next.js app, you can use a service like Vercel.
Install the Vercel CLI:
npm i -g vercel
Then from your dex-app
directory, run:
vercel
Follow the prompts to log in and deploy your app. Once deployed, you will get a live URL where people can access your DEX.
Wrapping Up
Congratulations, you just built your own decentralized exchange using thirdweb! Share the URL with others so they can start swapping tokens.
In this guide, we covered:
- Creating a DEX smart contract to handle swapping between tokens
- Building a web app to interact with the DEX contract
- Calculating token amounts, fetching balances, and executing swaps
What's next?
There are many ways you can expand on this DEX implementation, such as:
- Add a liquidity pool to earn fees on swaps
- Implement limit orders
- Support multiple token pairs
- Integrate charting and price data
- Allow users to create new token pairs
Use this guide as a starting point and continue building out your ideal decentralized exchange. Check out the thirdweb documentation to learn more about the available tools and features you can integrate.
Thanks for following this guide and good luck with your DEX! The complete code for this tutorial is available on GitHub — leave a star if you found it helpful.
If you have any questions or need help along the way, join the thirdweb Discord to connect with other builders and get support from the team.
Thanks for following this guide and good luck with your DEX!