An Introduction to AMM Swapping

for Uniswap V2 and Sushiswap-like exchanges

Jeff Gensler
7 min readApr 22, 2022

I have spent the last few days investigating AMM swapping to build a connector implementation for Hummingbot. While some libraries exist to calculate the swap, I found some missing pieces that I would like to document here. Hopefully, this will help fellow developers understand the pieces of an AMM swap.

Swapping and Market Making

x * y = kAMMs exist to provide oracle-less quotes to on-chain buyers and sellers. Buyers and sellers of tokens have a choice to transact on-chain and off-chain. Both platforms need to incentivize market participants to provide liquidity. For on-chain markets, LP providers provide liquidity and are incentivized with swap fees and reward systems. For off-chain markets, market makers provide liquidity and are incentivized with their (hopefully superior) pricing algorithms.

For an introduction to market making, I recommend the following people:

@therobotjames has a ton of useful pictures and threads on market making.

@idrawcharts has also published some material on market making.

In my opinion, it all comes down to the age-old saying: “buy low, sell high.”

Buying and Selling on-chain

Just like market makers provide quotes on both sides of the book, we need to find a way to get quotes from on-chain AMMs. Obviously, quotes are accessible from an AMMs website and these values are derived from the state on-chain. Without going to a website, we are left with the following order of priority for programmatically fetching prices:

  1. The exchange might provide an SDK library in your favorite language to make receiving quotes and placing trades easier. These libraries have constants defined for popular chains so little configuration is required for quoting and swapping on-chain.
  2. As most swap protocols are clones of the Uniswap/Sushiswap contracts, you can find a way to use the existing SDKs from (1) but swapping out some default parameters for chain and contract address. Some protocols list various contract addresses on the “Documentation” portion of their website which makes finding the various configuration parameters easy.
  3. If no SDK or Exchange Documentation website exists, you can initiate a swap transaction and take the contract address you are interacting with. You can look this address up in a block explorer and, if the block explorer allowed contract decoding or ABI uploading, you can verify the interface (ABI) meets the functions required for Uniswap or Sushiswap libraries.

Just like trust exists for off-chain exchanges, please use a similar level of skepticism when interacting with new on-chain exchanges. I don’t have any concrete advice to verifying a contract swaps exactly like it says so but a malicious exchange isn’t hard to imagine when the infrastructure is mostly anonymous.

The Uniswap v2 ABI

To gain more familiarity with what to expect from the SDK APIs, let’s take a detour and interact with the contracts themselves. Here are the two Router contracts for Uniswap and Sushiswap:

Uniwap v2 Router , Sushiswap Router

As you can see, these read contracts are identical! Now, lets supply some arguments to ask for a quote. We will need addresses for two tokens so lets use WETH and USDC:

WETH , USDC

Supplying the amountOut of 1 and the path of [USDC, WETH], we get a result of [1,1]

example 1

This is very confusing as we know that 1 WETH should be worth close to ~3000 USDC. Lets add a few more zeros and see what we can get. I’ll use the amount 1000000000000for the amountOut parameter:

example 2

Finally, we can see a more reasonable number that looks close to the expected price of ~3000! You can try adding more zeros and still get a number resembling a price of ~3000:

example 3

So what is going on here?

Decimal Places

First, we can see from the Etherscan UI that this parameter is a uint256 (“u” meaning unsigned and “int” meaning integer). Half of 1 is 0.5 but 0.5 is not an integer. Decimal places are used to interpret integers as fractions which is convenient when trying to express something like 0.02 USDC.

If you head back to the individual token pages, you’ll see each token has a decimal places:
USDC: 6
WETH: 18

This means that “one” USDC can be interpreted as a uint256 with 6 trailing decimal places: 1000000 . Similarly “one” WETH can be interpreted as a uint256 with 18 trailing decimal places: 1000000000000000000.

Looking at the documentation of the Uniswap v2 API, getAmountsIn has the following description:

Given an output asset amount and an array of token addresses, calculates all preceding minimum input token amounts by calling getReserves for each pair of token addresses in the path in turn, and using these to call getAmountIn.

similarly, getAmountIn has the following description:

Returns the minimum input asset amount required to buy the given output asset amount (accounting for fees) given reserves.

Using the example 2 from above, we are asking the following:
“what is the minimum input USDC required to buy 1000000000000 WETH?”

The contract is replying with: [2973,1000000000000] .

Moving the decimal points around, we can then interpret the results in their “real world” form: “what is the minimum input USDC require to buy 0.000001 WETH”

The contract is replying with 0.002973 USDC.

Aside: Token Lists

It is clear that our program will need some sort of data structure describing various tokens. Those tokens have addresses and decimal places that are crucial for understanding balances and placing trades. One solution for aggregating this data is a Token List. Many lists for “popular” tokens can be found searching the web. https://tokenlists.org/ is one website I have found that has numerous lists that contain token contract address and decimal information.

Visualizing Both Sides

Now that we have some background on token representation and one of the swap functions, lets dive in the path argument and its involvement with the two swap functions: getAmountsIn and getAmountsOut. I think the easiest way to visualize these functions is the following collection of pictures:

In each picture, I have placed a “one” on both sides of the trade. In some cases, this value is on the “input” side which would mean I want to know how many tokens I get out of the trade. Conversely, placing the “one” on the output side would give be the number of tokens I need to supply to buy “one” of the output.

If the pictures are arranged properly on your device, you can follow a “round trip” swap vertically. On the left, you can see 1 ETH would get ~2948 USDC. Unfortunately, we need 2966 USDC (18 USDC more than before!) to get 1 ETH back.

SDK-less Swap

Just like using the Etherscan interface to test the getAmountsIn function call, we can write Javascript to call this exact function.

The Uniswap v2 SDK

Unfortunately, it appears that the @uniswap/v2-sdk is in a bit of disarray. While the building blocks are available in the libraries (@uniswap/sdk-core and @uniswap/v2-sdk), there is currently no convenient way to fetch token balances from the pools. In earlier versions of this library, there was a fetcher class that was responsible for pulling these numbers from chain. The quote would then be generated client-side instead of the getAmountsIn function call we explored earlier.

Wrapping Up

While the examples only cover quotes, implementing the swap should include the following:

  • gas price calculation (and updating quote)
  • token approval checking
  • transaction status checking until completion

--

--