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
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: https://myopicmanoeuvre.cyou. May take a while to load, but it's there. The source is in this GitHub repo.
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
// create-react-app defaults
The mainnet fork needs to be configured in
JSON_RPC_URL: If you've created an app on Alchemy (network must be
Mainnet), this is going to be
FORKING_BLOCK_NUMBER: Any recent block number will do.
LOCAL_CHAIN_ID: The ID that Hardhat will use for the local node.
accountssection is only needed when using a pre-existing account from MetaMask (exported from
Account details). The other option is to import one of the accounts that are listed when starting the local node into MetaMask.
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/v2-periphery packages. The Hardhat plugin for TypeChain reads settings from a section named
Here, we point it to where the Uniswap contract artifacts are located. The app will only use
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.