How I made a Uniswap interface from scratch

Ben Haslam
clearmatics
Published in
10 min readSep 23, 2021

--

Intro

With the goal of allowing users to easily make swaps between different coins and deploy or remove liquidity on the Autonity network with a UI, rather than using scripts, I decided to make an interface for Uniswap contracts deployed on the network.

The official Uniswap interface proved difficult to fork for the private network, due to its very expansive codebase and problems connecting to a wallet. As I wanted to have a smaller codebase that I understood top to bottom, I decided to write my own simpler application, with the help of my awesome intern Matt.

We used ReactJS for the project, with the EthersJS module to connect to the blockchain via metamask in the browser, and Material-UI for the frontend. As it was a static site, we used github pages to host the application.

This first blog describes the code for the swap part of the application. I’ll first go through the functions needed to connect and make calls and transactions with the Ethereum blockchain backend, and secondly explain the React frontend, which makes extensive use of React hooks and Material-UI. This is written with the expectation that the reader already has a good understanding of ReactJS, as well as a knowledge of how Uniswap V2 automated market makers (AMMs) work (see this article). I won’t explain generic React components such as the NavBar, or the redirect page for when an Ethereum wallet can’t be found, as this blog will be long enough without that!

Checkout the interface here, and the raw code at the repo here.

Please note, this blog describes the state of the interface as of this commit. It has since been updated to have support for multiple networks, and displays different default coins depending on the network, as well as a few other small changes.

Ethereum Functions

Connecting to the blockchain

These functions are needed to connect to the blockchain via a wallet in the browser, to fetch information from the blockchain via calls, and to use contracts on the network to make swaps via transactions.

My original plan was to use the javascript module Web3 to connect to the blockchain with metamask in the browser, but metamask dropped support for it in January 2021, so I instead used EthersJS. EthersJS is a much smaller module than Web3, which means your application will load faster.

In the file ethereumFunctions.js, the first function is getProvider, which connects to the Ethereum provider (metamask or another wallet) in the browser, then the function getSigner, which is used to sign transactions. Using the EthersJS Contract class, are functions that return contract objects for the Router, Weth and Factory contracts that I had deployed onto the blockchain previously. For these the Contract class took the address, ABI of the deployed smart contract and the EthersJS signer as parameters.

Also defined is the function getAccount, which prompts the user to select accounts to use from the connected wallet.

ethereumFunctions.js

These functions were all imported into the CoinSwapper/CoinSwapper.js file, and used to set their corresponding state variables using React.useState hooks, from line 70:

CoinSwapper/CoinSwapper.js

ERC20 token functions

The next two functions in the ethereumFunctions.js file make calls to the network to fetch information on the token addresses chosen or provided by the user.

doesTokenExist does a check to make sure the address provided corresponds to a token deployed on the blockchain:

ethereumFunctions.js

getBalanceAndSymbol checks if a provided address is the address of the Weth contract on the blockchain, in which case it returns the AUT (the native coin of the Autonity blockchain, equivalent to ETH) balance of the user. Otherwise it returns the ERC20 token balance of the user, along with the symbol of the token:

This function checks if the address is the Weth address by comparing it to COINS.AUTONITY.address, an array of the default tokens exported from constants/coins.js.

The swap function

The swapTokens function in ethereumFunctions.js makes a transaction to the Router contract on the blockchain to perform one of three different swaps (AUT to token, token to AUT, token to token), depending on the token addresses provided to it, address1 and address2.

  • If address1 is the address of the Weth contract, it calls the Router function swapExactETHForTokens
  • If address2 is the address of the Weth contract, it calls the Router function swapExactTokensForETH
  • If neither address1 or address2 is the address of the Weth contract, the swapTokens function calls the Router function swapExactTokensForTokens

The function getAmountOut is used to get a preview of a swap. It calls the Router function getAmountsOut with the amount of the first token and an array of the addresses of the tokens to be swapped as parameters. It returns the amount out of the second token.

The reserves function

Finally, the GetReserves function in ethereumFunctions.js returns the liquidity pool reserves for a given pair of tokens, as well as the liquidity token balance for the user. Internally this function calls another function fetchReserves, which fetches the reserves by making a call to the pair contract then making sure the reserves are returned in the right order.

React Frontend

The frontend of the application makes extensive use of Material-UI components such as Grid, Container, Paper, Typography, as well as various buttons and more. Rather than explaining the workings of every component in the application, I will aim to give a high level overview, which will make the most sense if you are reading through the code at the same time. If you are not familiar with Material-UI I recommend reading up on some of their great documentation here.

The file CoinSwapper.js exports a function CoinSwapper, which returns the React component used to select tokens and make swaps. This function itself makes use of some other custom React components, which I will explain first before going through the internal functions and hooks that make the CoinSwapper component work.

Coin Dialog component

The CoinDialog component renders the menu of coins that opens upon clicking one of the ‘SELECT’ buttons. This extends the React Dialog component, which renders a window that opens in front of the rest of the app, used to ask for a decision. It is defined in CoinSwapper/CoinDialog.js

Inside the CoinDialog component, there is first a modified version of Material-UI's DialogTitle component, with the addition of a close button which on click calls the exit function that closes CoinDialog.

Next there is a React TextField component, which enables the user to paste the address of a token to be used. On change, this sets the state variable address to the user's input.

The next part is a mapping that maps each of the default tokens in Constants/coins to a custom CoinButton component (see below), that when clicked calls the exit function that closes CoinDialog returning the address of the selected tokens.

Finally, there is the Enter button, which on click calls the submit function, which checks a token exists with the address in the TextField with the Ethereum function doesTokenExist, before calling exit to close CoinDialog returning the address.

CoinSwapper/CoinDialog.js

This component, as well as all the others, makes use of the Material-UI makeStyles function for the styling, a CSS in JS solution that is easy to use with other Material-UI components and allows for theme nesting and dynamic styles.

Coin Button component

The CoinButton component, defined in CoinSwapper/CoinButton.js, extends the Material-UI ButtonBase component, which contains text of the token symbol (coinAbbr) and token name (coinName), which are passed in via props.

CoinSwapper/CoinButton.js

Coin Field component

The CoinField component, defined in CoinSwapper/CoinField, renders the input bar with a "SELECT" button used to select each token and input an amount for swapping. The component is relatively simple and consists of Material-UI components Fab (floating action button) and InputBase (a text field), both wrapped in Grid components for spacing. The relative properties for the Fab and InputBase components are passed into the CoinField component via props.

CoinSwapper/CoinField.js

Loading button

The LoadingButton component, defined in Components/LoadingButton.js, renders the ‘SWAP’ button on the bottom of the main CoinSwapper component. It extends the Material-UI Button component, so it will be disabled depending on the valid prop, and displays a spinning loading icon on click until the swap transaction is complete.

Components/LoadingButton.js

Coin Swapper Function

The Return statement

The main function of the application, CoinSwapper, in CoinSwapper/CoinSwapper.js returns a boxed in component that contains two CoinDialog components, which only open when one of two CoinField components is selected. Next there are Material-UI Typography components which display the balances and reserves of the two selected tokens.

Finally there is the LoadingButton component at the bottom, and a short instructional paragraph with a link to the AUT faucet.

CoinSwapper/CoinSwapper.js

When the LoadingButton is clicked, it calls the internal function swap (see below), which then calls the swapTokens Ethereum function with the state variables coin1.address, coin2.address and field1Value as arguments. This will first prompt the user to allow the spending of coin 1 with a metamask notification, then with another notification prompt the user to confirm the transaction. When that is done, the values in field1 and field2 will reset.

State variables

The return statement above references several state variables that keep track of the state of the application. These are:

  • dialog1Open — Keeps a record of weather the first dialog window is open
  • dialog2Open — Keeps a record of weather the first dialog window is open
  • coin1 — array of address, symbol, balance for coin1
  • coin2 — array of address, symbol, balance for coin2
  • reserves — Stores the liquidity reserves in the pool for coin1 and coin2.
  • field1Value — Stores the user’s input in field1 (the value of coin1 to be swapped)
  • field2Value — Stores the user’s input in field2 (the value of coin2 to be swapped)
  • loading — boolean to control the loading button

These are defined with React.useState hooks from line 83:

CoinSwapper/CoinSwapper.js

These state variables are used in the following functions, and also used to display information to the user in the prior mentioned return statement.

Internal functions

Also referenced in the return statement above are several internal functions, these are:

  • switchFields
  • handleChange
  • formatBalance
  • formatReserve
  • isButtonEnabled
  • onToken1Selected
  • onToken2Selected
  • swap

switchFields switches the top and bottom coins. This is called when users hit the swap button or select the opposite token in the dialog (e.g. if coin1 is TokenA and the user selects TokenB when choosing coin2):

handleChange is a common internal function used in ReactJS, which takes an HTML event, pulls the data out, and puts it into a state variable. Here it used to set the field1 value when the value in the first coinFied changes:

formatBalance / formatReserveturn the account's balance / reserves into something nice and readable:

isButtonEnabled determines whether the button should be enabled or not:

onToken1Selected / onToken2Selected are called when the dialog window for coin1 / coin2 exits, and sets the relevant state variables:

swap calls the swapTokens Ethereum function to make the swap, then resets necessary state variables:

Line 16 in the swap function uses enqueueSnackbar, a component from the node module Notistack. Notistack is a great library for making temporary notifications. Check the repo here.

useEffect hooks

Finally, in CoinSwapper/CoinSwapper.js, there are four useEffect hooks, which are used to keep the application up to date with the latest changes. The lambda (code) within each hook is run when one of the dependencies changes. The dependencies are defined in the array of variables passed to the function after the lambda expression.

The first useEffect is called when either of the state variables coin1.address or coin2.address change. This means that when the user selects a different coin to convert between, or the coins are swapped, the new reserves will be calculated:

The second hook is called when any of the state variables field1Value coin1.address or coin2.address change. It attempts to calculate and set the state variable field2Value. This means that if the user enters a new value into the conversion box or the conversion rate changes, the value in the output box will change:

The third hook creates a timeout that will run every ~10 seconds, its role is to check if the user’s balance has updated and changed. This allows them to see when a transaction completes by looking at the balance output:

The final hook will run when the component first mounts. It is used to set the account:

Conclusion

The first section of this blog covered the Ethereum functions needed to make calls and transactions to the blockchain. The next section went through the custom components, internal functions and hooks needed to make the main swap functionality work.

I have yet to mention any of the remove/deploy liquidity functionality. That is the topic of the next blog, here.

To tip the author, you can use this ethereum address: 0xe7EeDB184E63Fe049EebA79EfeAc72939cB1461D

--

--