Analyze Uniswap in Python using UniswapPy

Ian Moore, PhD
7 min readOct 30, 2023

--

February 2024: for all major protocols checkout our DeFiPy python package

April 2024: Uniswap V3 available ≥ v.1.3.0

1. Introduction

The purpose of this article is to get you quickly started with the basics of the UniswapPy python package. This package is a python re-factor of the original Uniswap V2 and V3 pairing codes and can be utilized for the purpose of analyzing and modelling its behaviour for designing new DeFi protocols or analyzing the behaviour of existing ones. Special features include:

2. Basic Usage

The basic tools to get started with the UniswapPy python package are as follows:

  • ERC20 : Token that implements the ERC20 standard within the Ethereum virtual machine (EVM)
  • Factory : Create liquidity pools for given token pairs
  • Exchange: Refactor of the Uniswap V2 pairing code. Governs how Uniswap calls the liquidity pools; each exchange is associated with a single ERC20 token pairing. Maintains reserve asset holdings (ie, xₖ, yₖ), total liquidity pool token supply, and collected fees
  • AddLiquidity: Add liquidity to the pool given amount of one of the assets in the pair, the required quantity of the opposing token is done for you
  • RemoveLiquidity: Remove liquidity from the pool given amount of one of the assets in the pair, the required quantity of the opposing token is to perform the removal done for you
  • SwapDeposit: Process to swap approx. half of single token X for token Y (and vice verse) and deposit proceeds plus remaining other approximated half (exact proportion is mathematically calculated)
  • WithdrawSwap: Process to withdraw liquidity from LP and swap opposing token which is added to specifed token to receive a single amount of specified token
  • LPQuote: Useful tool for retrieving pricing and various settlement quotes from the liquidity pool

Setup a Liquidity Pool

To setup a liquidity pool, you must first create the tokens in the pair using the ERC20 object. Next, create a liquidity pool (LP) factory using Factory object. Once this is setup, an unlimited amount of LPs can be created; the procedure for such is as follows:

from uniswappy.erc import ERC20
from uniswappy.cpt.factory import UniswapFactory
from uniswappy.utils.data import UniswapExchangeData

dai = ERC20("DAI", "0x111")
eth = ERC20("ETH", "0x09")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP", address="0x011")

factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
lp.add_liquidity(user_nm, eth_amount, dai_amount, eth_amount, dai_amount)
lp.summary()

#OUTPUT:
Exchange ETH-DAI (LP)
Reserves: ETH = 1000, DAI = 1000000
Liquidity: 31622.776601683792

Swap(): swap one token for another

Swap one token for another using the constant product trading (CPT) mechanism (ie, xy = k)

from uniswappy.process.swap import Swap

dai = ERC20("DAI", "0x111")
eth = ERC20("ETH", "0x09")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP", address="0x011")

factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
lp.add_liquidity(user_nm, eth_amount, dai_amount, eth_amount, dai_amount)
lp.summary()

out = Swap().apply(lp, dai, user_nm, 1000)
lp.summary()

#OUTPUT:
Exchange ETH-DAI (LP)
Reserves: ETH = 1000, DAI = 1000000
Liquidity: 31622.776601683792

out = Swap().apply(lp, dai, user_nm, 1000)
lp.summary()

#OUTPUT:
Exchange ETH-DAI (LP)
Reserves: ETH = 999.00399301896, DAI = 1001000
Liquidity: 31622.776601683792

AddLiquidity(): add liquidity based on one token amount

To add liquidity to a Uniswap pool, equal amounts of each token, in accordance to the constant product trading (CPT) mechanism (ie, xy = k) is required. However, this function only requires you have one of the amounts, while the other portion is calculated for you; the procedure for such is as follows:

from uniswappy.process.liquidity import AddLiquidity

dai = ERC20("DAI", "0x111")
eth = ERC20("ETH", "0x09")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP", address="0x011")

factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
lp.add_liquidity(user_nm, eth_amount, dai_amount, eth_amount, dai_amount)
lp.summary()

#OUTPUT:
Exchange ETH-DAI (LP)
Reserves: ETH = 1000, DAI = 1000000
Liquidity: 31622.776601683792

AddLiquidity().apply(lp, eth, user_nm, 10)
lp.summary()

#OUTPUT:
Exchange ETH-DAI (LP)
Reserves: ETH = 1010, DAI = 1010000.0
Liquidity: 31939.00436770063

RemoveLiquidity(): remove liquidity based on one token amount

To remove liquidity from a Uniswap pool, equal amounts of each token, in accordance to the constant product trading (CPT) mechanism (ie, xy = k) is required. However, this function only requires you have one of the amounts, while the other portion is calculated for you; the procedure for such is as follows:

from uniswappy.process.liquidity import RemoveLiquidity

dai = ERC20("DAI", "0x111")
eth = ERC20("ETH", "0x09")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP", address="0x011")

factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
lp.add_liquidity(user_nm, eth_amount, dai_amount, eth_amount, dai_amount)
lp.summary()

#OUTPUT:
Exchange ETH-DAI (LP)
Reserves: ETH = 1000, DAI = 1000000
Liquidity: 31622.776601683792

RemoveLiquidity().apply(lp, eth, user_nm, 1)
lp.summary()

#OUTPUT:
Exchange ETH-DAI (LP)
Reserves: ETH = 999.0, DAI = 999000.0
Liquidity: 31591.153825082107

SwapDeposit(): deposit liquidity given only one token

A SwapDeposit is where a certain amount of a specific token is deposited into the LP under one function call; includes two steps:

  • (Step 1) Perform approx. 50% swap for opposing token
  • (Step 2) Using amount from step 1, perform 1:1 deposit

A portion of the incoming funds are swapped to achieve equal portions of both assets. These portions are then deposited into the LP. To ensure all the funds are deposited, we must determine the portion amount that must first get swapped, which will be something close to 50%. We will cover the math behind how the exact ratio is determined in a later article.

from uniswappy.process.deposit import SwapDeposit

dai = ERC20("DAI", "0x111")
eth = ERC20("ETH", "0x09")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP", address="0x011")

factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
lp.add_liquidity(user_nm, eth_amount, dai_amount, eth_amount, dai_amount)
lp.summary()

#OUTPUT:
Exchange ETH-DAI (LP)
Reserves: ETH = 1000, DAI = 1000000
Liquidity: 31622.776601683792

amount_out = SwapDeposit().apply(lp, eth, user_nm, 1)
lp.summary()

#OUTPUT:
Exchange ETH-DAI (LP)
Reserves: ETH = 1001.0000000000001, DAI = 1000000.0
Liquidity: 31638.56029234534

WithdrawSwap(): withdraw liquidity denominated in only one token

A WithdrawSwap is where a certain amount of a specific token is withdraw from LP under one operation; includes two steps:

  • (Step 1) Split token amount into two parts and perform ~ 50/50 withdraw on one of the parts to receive tknA and tknB
  • (Step 2) Swap remaining ~50% of tknB (from step 1) to receive only tknA

To ensure all the funds are withdrawn, we must determine the portion that requested token amount is split into, which is something close to 50%. We will cover the math behind how the exact ratio is determined in a later article.

from uniswappy.process.swap import WithdrawSwap

dai = ERC20("DAI", "0x111")
eth = ERC20("ETH", "0x09")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP", address="0x011")

factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
lp.add_liquidity(user_nm, eth_amount, dai_amount, eth_amount, dai_amount)
lp.summary()

#OUTPUT:
Exchange ETH-DAI (LP)
Reserves: ETH = 1000, DAI = 1000000
Liquidity: 31622.776601683792

expected_amount_out = WithdrawSwap().apply(lp, eth, user_nm, 1)
lp.summary()

#OUTPUT:
Exchange ETH-DAI (LP)
Reserves: ETH = 999.0000000000001, DAI = 1000000.0
Liquidity: 31606.937511796754

Retrieve token settlement amount given opposing token amount.

LPQuote(): retrieve various quotes from the LP

LPQuote is a useful tool for retrieving pricing and various settlement quotes from the liquidity pool; the setup is as follows:

from uniswappy.cpt.quote import LPQuote

dai = ERC20("DAI", "0x111")
eth = ERC20("ETH", "0x09")
exchg_data = UniswapExchangeData(tkn0 = eth, tkn1 = dai, symbol="LP", address="0x011")

factory = UniswapFactory("ETH pool factory", "0x2")
lp = factory.deploy(exchg_data)
lp.add_liquidity(user_nm, eth_amount, dai_amount, eth_amount, dai_amount)
lp.summary()

#OUTPUT:
Exchange ETH-DAI (LP)
Reserves: ETH = 1000, DAI = 1000000
Liquidity: 31622.776601683792

Retrieve LP prices

p_eth = LPQuote().get_price(lp, eth)
p_dai = LPQuote().get_price(lp, dai)
print(f'The price of {eth.token_name} in {dai.token_name} is {p_eth}')
print(f'The price of {dai.token_name} in {eth.token_name} is {p_dai}')

#OUTPUT:
The price of ETH in DAI is 1000.0
The price of DAI in ETH is 0.001

Retrieve token settlement amount given opposing token amount.

amt_dai = LPQuote().get_amount(lp, eth, 1)
amt_eth = LPQuote().get_amount(lp, dai, 1)
print(f'1 {eth.token_name} token is worth {amt_dai} {dai.token_name}')
print(f'1 {dai.token_name} token is worth {amt_eth} {eth.token_name}')

#OUTPUT:
1 ETH token is worth 1000.0 DAI
1 DAI token is worth 0.001 ETH

Retrieve rebased token settlement amount given amount of LP token. We will cover the math behind how this is determined in a later article.

amt_eth = LPQuote(False).get_amount_from_lp(lp, eth, 1)
amt_dai = LPQuote().get_amount_from_lp(lp, eth, 1)
print(f'1 LP token is worth {amt_eth} {eth.token_name}')
print(f'1 LP token is worth {amt_dai} {dai.token_name}')

#OUTPUT:
1 LP token is worth 0.06314969086446823 ETH
1 LP token is worth 63.149690864468226 DAI

Retrieve LP token settlement amount given amount of asset token. We will cover the math behind how this is determined in a later article.

amt_eth_lp = LPQuote(False).get_lp_from_amount(lp, eth, 1)
amt_dai_lp = LPQuote(False).get_lp_from_amount(lp, dai, 1)
print(f'1 {eth.token_name} token is worth {amt_eth_lp} LP tokens')
print(f'1 {dai.token_name} token is worth {amt_dai_lp} LP tokens')

#OUTPUT:
1 ETH token is worth 15.839089887038602 LP tokens
1 DAI token is worth 0.01583514496167512 LP tokens

3. Summary

The work presented in this article is a larger body of work for doing things like: (a) simulate the behaviour of a simple liquidity pool [1]; (b) studying the effects of Impermanent Loss in an LP [2]; and (c) analyzing the risk profile and profitability in more advanced DeFi systems. Here, we provide the basic setup for those who are interested in getting into researching LPs. Please be sure to look out for future medium posts on this!

See GH repos for Jupyter notebook of behind this presentation

4. References

[1] I.C. Moore, Simulating a Liquidity Pool for Decentralized Finance, Syscoin Github repos, Accessed on: Mar 2023. [Online]. Available: https://github.com/icmoore/uniswappy/blob/main/notebooks/medium_articles/simple_simulation.ipynb

[2] I.C. Moore and J. Sidhu, An Analytical and Empirical Survey of Impermanent Loss, Syscoin Github repos, Accessed on: Mar 2023. [Online]. Available: https://github.com/syscoin/latex_docs/blob/main/impermanent_loss/article.pdf

--

--