Interfacing With UniswapV2Router02: 1 ― Setting Up the Project

What is being built?

Uniswap is the project that pioneered ERC20 token swaps on Ethereum in 2018. Practically all token swaps that came after it would copy its architecture of having:

This series is a write-up of building a simplistic token swap wrapped up as a React app. Essentially, it's a rebuild of Uniswap's token swap UI with some limitations. Specifically:

In order to issue token swaps, the app calls functions on Uniswap's UniswapV2Router02 contract which is deployed at 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D on mainnet and may be used by any DApp. Uniswap does provide a JavaScript SDK, but since the purpose of building this app was to get a better idea of what's going on under the hood of the Uniswap UI, the JavaScript SDK is not used, and the app interfaces with the router directly.

While the app is capable of swapping the user's actual tokens on mainnet, a local Hardhat node that forks mainnet will be used in order to have a sandbox environment. The way this works is that Hardhat is configured to use an archive node (such as are provided by Alchemy) to query the state of mainnet up to a specified block, while all subsequent blocks live on the local node only. This allows us to talk to mainnet contracts without changing any state on mainnet, or, practically speaking, to issue token swaps using 'play money' while executing the real smart contract code as it was deployed to mainnet.

The 'finished' app was deployed to IPFS and is reachable through Cloudflare's IPFS gateway here: May take a while to load, but it's there. The source is in this GitHub repo.

Project setup


The React app is bootstrapped using create-react-app's official redux-typescript template. Apart from React, we'll obviously be using Hardhat. Since we'll be using it to run a few tests against our app's token swap API, and not only for running a local node, we want to integrate it into the project, and that's where we are first trolled by a seeming conflict of CJS and ESM module structures. It isn't a real conflict though, since the separation between what's run in the browser and in Node isn't eroded. We just need to let ts-node know that it is to assume commonjs module structure, as it would by default, if it weren't for the "module": "esnext" that create-react-app puts in tsconfig.json. This is achieved by adding a section to tsconfig.json with settings that are specifically for ts-node:

// create-react-app defaults
// ...

"ts-node": {
"compilerOptions": {
"module": "commonjs",


The mainnet fork needs to be configured in hardhat.config.ts:

  networks: {
hardhat: {
forking: {
accounts: [
balance: initialBalanceEther,
throwOnCallFailures: false,
throwOnTransactionFailures: false,

Setting throwXxx to false makes Hardhat behave as though it were on a 'real' node, where no exception is thrown on call or transaction failures.


TypeChain has become indispensable when developing DApps in TypeScript, as it generates TypeScript bindings from smart contract ABIs. In this project, it's used to obtain bindings for Uniswap's interfaces, which are located in the @uniswap/sdk and @uniswap/v2-periphery packages. The Hardhat plugin for TypeChain reads settings from a section named typechain in hardhat.config.ts:

  typechain: {
outDir: 'src/typechain',
externalArtifacts: [

Here, we point it to where the Uniswap contract artifacts are located. The app will only use IUniswapV2Router02, IUniswapV2Pair, and IERC20, so we don't need TypeChain to process all the artifacts. (With the patterns above, it will process a bunch of unused ones, but the overhead is negligible.) On each compile cycle, Hardhat will now invoke TypeChain to process those artifacts, and emit bindings in the specified output directory.