<Swap/> with Uniswap

Adam Fuller
8 min readJan 4, 2021

--

Uniswap is a DeFi household name. If you have participated in any of the antics of the last 6 months, you have likely benefited from the unicorn protocol’s automated token liquidity 🦄.

Uniswap meets scaffold-eth

But what is actually going on under the hood when you make a trade on Uniswap? We built a <Swap/> widget in scaffold-eth to get the low-down.

🚨 This is experimental code, use at your own risk! 🚨

Read on to…

  • 🍴 Fork mainnet (!)
  • 💱 Grok swaps on Uniswap
  • 📝 Learn about token-lists

But if you want to get right to hacking on it yourself, check out the uniswapper branch on scaffold-eth!

What is Uniswap? 🦄

“Uniswap is an automated liquidity protocol powered by a constant product formula and implemented in a system of non-upgradeable smart contracts on the Ethereum blockchain” — Uniswap Documentation

Uniswap consists of many smart contracts, each representing a pair of erc20 tokens. A Pair contract contains an equivalent value of each token, initially added by Liquidity Providers 🤝

Users are then able to withdraw one of the paired tokens in exchange for a value of the other such that the product of the token two balances remains the same (that’s the constant product bit), plus a small fee paid to the Liquidity Providers (currently 0.3%).

The constellation of Pair contracts can be accessed in one place via a Router contract, which acts as a gateway to the Uniswap protocol. This is the smart contract that our widget will be primarily interacting with.

Side note: the fact that anyone can create the UI of their choice to provide access to all of Uniswap illustrates the unstoppable & open nature of protocols built on Ethereum. End shill.

For our widget, we are interested in exchanging one token for another, so we then need to specify three things:

All you could possibly need
  • The token we have: tokenIn
  • The token we want: tokenOut
  • A fixed amount, specified either in tokenIn or tokenOut You can specify either the amount of tokens you have (EXACT_INPUT), or the amount of tokens you want (EXACT_OUTPUT). The other side will then be determined at point of transaction.

We then want to be able to estimate the other amount, present it to the user (summarised as a Trade), and allow them to submit swap on-chain if they are happy with the price.

Let’s get to it!

Forking main-net side-quest 🍴

Our first challenge was less to do with Uniswap, more to do with the fact that to develop new apps on top of DeFi, you need a realistic recreation of deployed protocols in your local setup.

Fortunately Hardhat (f.k.a. Buidler) has an excellent mainnet forking option which allows you to snapshot the latest state of mainnet, using that as the foundation for your locally running network.

This can now be done with a single command from within scaffold-eth:

yarn fork

It is worth noting that long-lasting forked local nodes need an archive source node, rather than the Infura node provided out-of-the-box with scaffold-eth, which will start to throw errors after an hour or so. Alchemy API comes highly recommended.

The Uniswap SDK 🦄

Now we’ve got a local mainnet fork running, we are ready to integrate! Uniswap is kind enough to provide a javascript SDK for developers. The SDK primarily provides two things for our widget:

  • Entities: the classes which make up the Uniswap protocol (Tokens, Pairs, Trades etc.), with associated helper functions (for doing calculations & transformations).
  • An easy way to Fetch the current on-chain state of Uniswap Pairs (i.e. the reserves of each Token).

There are some important things that the SDK doesn’t do…

  • It doesn’t know about specific tokens. The developer has to know about the erc20 tokens of interest, which is where Token Lists come in 📝
  • It doesn’t know about ETH. While Ether is a first-class citizen when interacting with the Router, within the SDK you have to treat ETH as Wrapped ETH (WETH) 🎁
  • It doesn’t prepare or send transactions. The developer has to take the information about potential Trades from the SDK and prepare the appropriate transactions to actually exchange tokens with Uniswap ⚡

Token List side-quest ⚔️

While anyone can deploy an erc20 token contract on Ethereum, and anyone can see those contracts, that freedom and open-ness makes it hard for people to filter high quality legitimate tokens from scams and fakes. Announced in August 2020 by Uniswap, Token Lists are an effort to create a common standard for different organisations maintaining lists of tokens-of-interest:

The idea is simple — any project on Ethereum that maintains a list of ERC20 tokens hosts this list publicly following a standard JSON schema. Projects attach their reputation to lists by hosting them on a domain they control.

<Swap/> uses the Uniswap default token list as its input tokenListURI, but the parameter can take any token list JSON URI following the Token List standard.

My favourite Token List Easter Egg 🥚 is the token logo URLs! They are part of the defined schema, making it super easy to pull logo images into your frontend ❤️

Estimating trades with the SDK

Before we get a Uniswap Trade, we need to instantiate the required Tokens (which could be done using information from a token list!), and fetch the corresponding Pair data from our local chain:

import { ChainId, Token, WETH, Fetcher } from '@uniswap/sdk'const DAI = new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18)const pair = await Fetcher.fetchPairData(DAI, WETH[DAI.chainId], provider)

The Pair can tell us the amounts in reserve for each token, which is enough to calculate the price for different amounts, via the constant product — this is the only thing the SDK goes on-chain for! ⛓️

The simplest next step is to create the Route 🛣️ for the trade (i.e. we are going from tokenIn to tokenOut), then to create a new Trade using that route and our fixed amount. That Trade entity can then give us the information about the variable amount we can expect, as well as other metrics about the trade (executionPrice, nextMidPrice, priceImpact etc.)

const DAI_TO_ETH = new Route([pair], DAI)const trade = new Trade(DAI_TO_ETH, new TokenAmount(DAI, '1000000000000000'), TradeType.EXACT_INPUT)

However this direct approach is dependent on there being a deployed Pair contract for the tokens of interest, which won’t necessarily be the case if you want to go from Exotic Token 🥥 to Exotic Token 🍍.

That is where the real power of Uniswap comes in — unlike conventional centralised exchanges, where you would need to make two transactions, from 🥥 to ETH, then from ETH to 🍍, you can make one single trade with a route from 🥥 -> ETH -> 🍍. And this is also highlights the freshest bit of the SDK, which provides two functions (bestTradeExactIn and bestTradeExactOut) which, given a list of Pairs, will find the trade with the best estimated price:

Trade.bestTradeExactIn(
pairs: Pair[],
amountIn: TokenAmount,
tokenOut: Token,
{ maxNumResults = 3, maxHops = 3 }: BestTradeOptions = {}):

We want our widget to be ready for the most elaborate of trades, so we definitely want to take advantage of this functionality:

  1. Create a list of “Base” tokens, plus the tokens selected by the user 🏛️
  2. Get Pair information for all the possible combinations of the tokens in our list, where a Pair contract exists 🔭
  3. Use the relevant bestTrade method to identify the optimum Route from tokenIn to tokenOut across all those Pairs, and the estimated variable amount and corresponding price 💱

We can then present this to the User — it’s time to trade!

Swapping via the Router

The Router contract is our gateway to Uniswap. Before we are ready to submit our transaction, we need to think about two things. What are our safety parameters? And are we trading tokens or ETH?

What are our safety parameters? 🥽

Ethereum is an adversarial and unpredictable environment. As a result Uniswap has some built in controls to ensure that Swaps don’t go badly wrong. While a given trade will only specify one of an amountIn or an amountOut, the upper or lower bound for the other side is an additional parameter, as amountInMax or amountInMin.

<Swap/> allows the user to specify their slippageTolerance, default 0.5%, which takes the estimated price, and adjusts the min or max by that amount. If the trade falls outside that tolerance, the transaction will revert.

In addition a user may want to ensure that their transaction takes place within a given period of time, to ensure that high gas prices & busy blocks don’t leave them hanging. This is specified as a unix timestamp deadline. ⏰

The <Swap/> default is ten minutes after the transaction, with trade failing after that point.

Are we trading tokens or ETH?

The Uniswap Router treats ETH as a first-class citizen, but using ETH rather than a token does introduce some different requirements…

There are six potential swap varieties, depending on which amount is exact, and whether ETH is the input or the output:

  • swapExactTokensForTokens
  • swapTokensForExactTokens
  • swapExactETHForTokens
  • swapTokensForExactETH
  • swapExactTokensForETH
  • swapETHForExactTokens

If the tokenIn is ETH, we need to send the right amount of ETH as the msg.value in our transaction, rather than specifying it as a call parameter.

If the tokenIn is a token, then we need to ensure that the Router address is approved to transfer at least the required amount of erc20 tokens as part of the swap.

We therefore implemented an Approve flow for tokens as part of <Swap/>

Executing the swap 🏁

With all that cleared up, we are ready to execute the trade. The Router ABI is available at @uniswap/v2-periphery/build/IUniswapV2Router02.json, so all that remains is to instantiate an ethers.js contract with our user’s signer and call the appropriate function with the parameters as required.

The finished widget

<Swap/> is a minimum viable component, taking great inspiration from the original Uniswap app.

Logos FTW!

It is built to be educational (I certainly learned a lot building it!), portable and forkable

  • Pull it into your scaffold-eth branch to let your users easily move between erc20s
  • Update it to work with non-mainnet Uniswap deployments (testnets, Honeyswap)
  • Extend the widget to visualise the changing price for different trade sizes
  • Set up a TWAP Swap
  • Build in other liquidity providers to get even better pricing (next stop 1inch!)
  • Get racy with Flash Swaps
  • Strip it down to a simple Swap button to keep things simple
  • [Your idea here!]
Who doesn’t want SOCKS?

If I made any gross missteps, or if you just want to chat, I am down to clown at azacharyf. Happy Swapping!

Many thanks to Austin Griffith for eyes and the ideas!

--

--