On Ethereum and other smart contract networks like BNB Smart Chain, interest in decentralized finance (DeFi) has skyrocketed.
Tokenized Bitcoin (BTC) is expanding on Ethereum, the popularity of "yield farming" to distribute tokens is rising, and the volume of "flash loans" is skyrocketing.
The number of people using automated market maker protocols like Uniswap is growing while the quantities traded on these platforms remain competitive.
But how do these transactions take place? How well do AMMs stack up against order book exchanges? We’ll explore that.
What is an AMM?
An AMM is a type of DEX (Decentralized Exchange) protocol that facilitates the elimination of order books in liquidity determination. Instead it uses an arithmetic algorithm to determine prices. To understand the role of AMMs let's see the following example:
Customer A has ETH and wants to buy DAI, in a centralized exchange there must be a customer B willing to sell DAI, and a system that matches buy orders with sell orders (using the exchange as an intermediary).
But what if customer B does not exist at that time? It will be almost impossible for A to complete this transaction. That's where markets require AMMs. They allow immediate liquidity to the necessary exchanges and set asset prices automatically by a predefined equation.
For the purpose of this guide we will work with the equation X + Y = K known as "Constant Sum AMM" where X is the amount of one token and Y is the amount of the other. In this formula, K is a fixed constant.
There are other AMMs that make use of the equation X * Y = K, for example, Uniswap.
How to Create A Simple AMM
Let's see in code how they work and how to create a simple AMM:
AMMs work with exchange currency pairs e.g. USDC/DAI. Each DEX can be composed of several AMMs with different pairs. Let's create the initial structure of our contract:
1) First create the token instances that will be our exchange currency pairs (USDC/DAI for this example):
2) Then create variables to keep track of reserves
3) Create variables to keep track of our share tokens (more about this below).
4) Create the constructor where we define the contract address of the exchange currency pair that our AMM will use.
5) Then write functions to make mint and burn from our shares tokens
6) Finally, write a function to update the values of our reserves.
For an AMM to work there must be a person who injects liquidity to both pairs, in this case, who deposits USDC and DAI in the contract.
This actor is known as Liquidity Provider, they are in charge of injecting liquidity to a protocol in order to obtain future returns (The returns and strategies of the liquidity providers are not discussed in this article) Let's create the function "addLiquidity":
- First, we define our function, set as parameters the amount of token0 and token1 deposited by the liquidity provider and return the amount of shares that the AMM will assign to it.
Now we will create the body of our function...
- Transfer funds from the liquidity provider to the AMM contract. Then input variables to keep track of our contract balances for each token.
- Keep track of the actual amount deposited by the liquidity provider.
- Shares are tokens that the AMM issues and allocates to liquidity providers to represent a participation in our liquidity pool (big pile of funds) and future returns. In the contract if some shares have already been created, we calculate the shares to mint based on the total supply.
- Mint the shares and assign them to the liquidity provider, we must also update our reserves with the new balance sheet values.
The main function of an AMM is to provide liquidity to exchanges. Unlike centralized exchanges, these exchanges are peer-to-peer (P2P), in this case there are no intermediaries and the exchanges are peer-to-contract.
Each time a swap is performed the protocol will take a certain percentage of the output token (0.3% for this example). Let's see our function to make a swap:
- We define our function. We set as parameter the contract address of the token we want to exchange and its amount and return the amount of the output tokens.
We create the body of our function...
- Validate that the tokens to be swapped are those supported by the AMM.
- Identify and keep track of which is the input token and which will be the output token. Identify the reserves we have for each one.
- Transfer the amount of the token to be swapped from the client to the AMM contract. We input a variable to keep track of the amount of tokens entered.
- The AMM retains a 0.3% commission on the outgoing token.
- We calculate the new values of our reserves:
- Update our reserves and make the swap effective, sending the outbound tokens to the customer.
It is important for liquidity providers to be able to withdraw the funds they originally deposited + some returns (in this article we will not take those returns into account). Let's code our "removeLiquidity" function:
- Let's define our function. We set as parameter the amount of token shares and return the amount that was withdrawn from each token.
- We cCalculate the corresponding values to be withdrawn for each token.
- Burn the amount of shares tokens corresponding to the amount to be withdrawn and update the balance of our reserves.
- Perform the withdrawal by transferring the tokens from our AMM contract to the liquidity provider.
You are done! You have created your first AMM with basic functionalities.
AMM development is only just starting out. The AMMs that we are familiar with and use today, such as Uniswap, Curve, and PancakeSwap, are lovely in appearance but have few functions.
Future AMM designs are anticipated to be far more creative. Every DeFi user should benefit from cheaper costs, less friction, and improved liquidity as a result of this.
AMMs are fundamental elements for DeFi protocols, they can be as simple as the one we have seen in this article or much more complex as the ones used by protocols such as curve or balancer.
For more details about the code you can visit here.