class: center, middle, inverse, title-slide .title[ # Uniswap Analytics Bounty #13
] .author[ ### Omni Analytics Group ] --- <style type="text/css"> pre { max-height: 400px; overflow-y: scroll; } pre[class] { max-height: 100px; } </style> ## Introduction This brief README/tutorial will walk you through our submission for the Uniswap Community Analytics Bounty #13. Let's get started! First, we load two packages - The meta `tidyverse` package for data manipulation, and our own `uniswappeR` package. We also load a few other packages for purposes of visualization and data manipulation. ```r ## Load Libraries library(tidyverse) library(uniswappeR) library(lubridate) library(cowplot) library(scales) ``` --- ## Objective For this bounty we chose to analyze the liquidity provision patterns of Uniswap market makers during the first two weeks after an airdrop. The 9 airdropped tokens of interest are Uniswap ($UNI), Badger DAO ($BADGER), Tornado Cash ($TORN), ParaSwap ($PSP), MurALL ($PAINT), Mirror Protocol ($MIR), Ethereum Push Notification Service ($PUSH), and Hop Protocol ($HOP). This analysis will use statistical graphics to compare and contrast trends in liquidity provider behavior across the first two weeks of some of the eco-systems' most popular airdrops. --- ## Data Using the `uniswappeR` package, we called the get_liq_data function to extract data for each of the 9 tokens across both the v2 and v3 Uniswap pools. Looping through and saving the results, we compiled the liq_burns.RDS dataset. This data is a list of time series dataframes, which each contain 7 columns, and in total comprise 117,524 rows. A preview of the data is shown below: ![](data_screencap.png) --- ## Analysis From here, we tied together the data around each token to create area plots that track the amount of liquidity added and removed over time. To follow up, we produced cumulative area plots that show the total amount of liquidity of each. The areas are colored by whether, at the particular time points, the liquidity for each of the nine tokens was positive. Added liquidity is shown in green, while removed liquidity is shown in red. The overall blue area comprises the total liquidity balance as a function of time. --- ## Liquidity Plot ![](AirdropLiq.jpeg) --- ## Results (Part One) We see that the trend in liquidity added/removed over time is highly dependent on the token, with the maximum liquidity provided over the first two weeks topping out at over $50 million for $UNI while it stayed at a lowly, stagnant $30,000 for $PAINT. From a macro perspective, there is a substantial amount of diversity in the shape of the cumulative amount of liquidity provided. Uniswap experience a logarithmic rise to its plateau as it reached its $50M saturation mark in only 5 days. Gitcoin, the Hop Protocol, and Murall, to a lesser extent, all had the same trend within their cumulative liquidity amounts. The EPNS protocol had a growth pattern more similar to an "S curve" where, after a slow start marred with a lack of liquidity, providers swooped in later to inject $3M over the course of a week, then the deposits tapered off. Paraswap unfortunately saw a nearly 40% decline in liquidity after it spiked in the first few days. --- ## Results (Part Two) Of all the protocols, Tornado Cash has exhibited the most variability in its liquidity. It had a slower than usual start, a massive jump, a dip, then a resurgence only to suffer further declines until it reach it caught its footing by finishing on an upward trend. When observing the initial liquidity provisions for each platform it is fairly standard that most liquidity is provided at the initial on-set of the pool's creation. In this two week span and for the airdrops analyzed here, there was some additional liquidity added during the middle and later parts of the period, but the vast majority for each was added at the beginning. The removal of liquidity is a bit more variable overtime. From each graph, we see don't see too many patterns categorizing when money leaves a pool. It should be noted that both $GTC and $PSP saw stark increases in the amount of liquidity leaving in the first week. In Gitcoin's case, the outflows didn't stifle overall liquidity since inflows actually outpaced them as they increased the overall balance. For Paraswap, unfortunately, this was not so. --- ## Code Walkthrough Let's step through how we produced this plot. First, we construct a list of the tokens and associated addresses: ```r drops <- list( UNI = "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", BADGER = "0x3472A5A71965499acd81997a54BBA8D852C6E53d", GTC = "0xde30da39c46104798bb5aa3fe8b9e0e1f348163f", TORN = "0x77777FeDdddFfC19Ff86DB637967013e6C6A116C", PSP = "0xcAfE001067cDEF266AfB7Eb5A286dCFD277f3dE5", PAINT = "0x4C6eC08CF3fc987c6C4BEB03184D335A2dFc4042", MIR = "0x09a3EcAFa817268f77BE1283176B946C4ff2E608", PUSH = "0xf418588522d5dd018b425E472991E52EBBeEEEEE", HOP = "0xc5102fe9359fd9a28f877a67e36b0f050d81a3cc" ) ``` --- ## Code Walkthrough (Part Two) We need one utility function for getting the liquidity for all pairs deployed with this token. The function is reproduced below: ```r ## Define function to get liquidity across all pairs deployed with this token get_liq_data <- function(x,token) { v2_pairs <- token_pair_map_v2(x) v3_pairs <- token_pair_map_v3(x) ## V2 v2_liq <- list() for(idx in 1:nrow(v2_pairs)) { m_data <- pair_mint_txs_v2(pair_address = v2_pairs$id[idx]) if(nrow(m_data)==0) next token2 <- ifelse(m_data$pair$token0$symbol[1]==token,m_data$pair$token1$symbol[1],m_data$pair$token0$symbol[1]) token2add <- ifelse(m_data$pair$token0$symbol[1]==token,m_data$pair$token1$id[1],m_data$pair$token0$id[1]) m_data <- m_data[,c("timestamp","amountUSD")] m_data$Version <- 2 m_data$Type <- "Mint" m_data$Token <- token m_data$Token2 <- token2 m_data$Token2Add <- token2add b_data <- pair_burn_txs_v2(pair_address = v2_pairs$id[idx]) if(nrow(b_data)==0) next b_data <- b_data[,c("timestamp","amountUSD")] b_data$Version <- 2 b_data$Type <- "Burn" b_data$Token <- token b_data$Token2 <- token2 b_data$Token2Add <- token2add v2_liq[[idx]] <- rbind(m_data,b_data) message(paste0(token,":V2:Pair",idx,"_",nrow(v2_pairs))) } v2_liq <- do.call(rbind,v2_liq) ## V3 v3_liq <- list() for(idx in 1:nrow(v3_pairs)) { m_data <- pair_mint_txs_v3(pair_address = v3_pairs$id[idx]) if(nrow(m_data)==0) next token2 <- ifelse(m_data$pool$token0$symbol[1]==token,m_data$pool$token1$symbol[1],m_data$pool$token0$symbol[1]) token2add <- ifelse(m_data$pool$token0$symbol[1]==token,m_data$pool$token1$id[1],m_data$pool$token0$id[1]) m_data <- m_data[,c("timestamp","amountUSD")] m_data$Version <- 3 m_data$Type <- "Mint" m_data$Token <- token m_data$Token2 <- token2 m_data$Token2Add <- token2add b_data <- pair_burn_txs_v3(pair_address = v3_pairs$id[idx]) if(nrow(b_data)==0) next b_data <- b_data[,c("timestamp","amountUSD")] b_data$Version <- 3 b_data$Type <- "Burn" b_data$Token <- token b_data$Token2 <- token2 b_data$Token2Add <- token2add v3_liq[[idx]] <- rbind(m_data,b_data) message(paste0(token,":V3:Pair:",idx,"_",nrow(v3_pairs))) } v3_liq <- do.call(rbind,v3_liq) ## Return All return(rbind(v2_liq,v3_liq)) } ``` --- ## Code Walkthrough (Part Three) Next is the fun part! We apply our function for getting the liquidity of the pairs to all the relevant pairs that we've listed: ```r liq_burns <- list() for(idx in 1:length(drops)) { liq_burns[[idx]] <- get_liq_data(drops[[idx]],names(drops)[idx]) } saveRDS(liq_burns, "liq_burns.RDS") ``` --- ## Code Walkthrough (Part Four) Now, with our data saved, we produce one plot per panel, by doing some basic data transformations, and using `ggplot2` to illustrate the distributions for each pair. ```r liq_burns <- readRDS("liq_burns.RDS") plots <- list() for(idx in 1:length(liq_burns)) { t_data <- liq_burns[[idx]] t_data$timestamp <- as_datetime(as.numeric(t_data$timestamp)) t_data$start <- min(t_data$timestamp) t_data$duration_d <- as.numeric(difftime(t_data$timestamp,t_data$start,units="days")) t_data <- t_data[t_data$duration_d<=14,] t_data <- t_data[order(t_data$duration_d),] t_data$duration_d <- ifelse(t_data$duration_d==0,.1,ceiling(t_data$duration_d*10)/10) t_data$amountUSD <- ifelse(t_data$Type=="Mint",as.numeric(t_data$amountUSD),-1*as.numeric(t_data$amountUSD)) t_data2 = t_data %>% group_by(Type,duration_d) %>% summarise(amountUSDT = sum(amountUSD,na.rm=TRUE)) t_data2 <- t_data2[order(t_data2$duration_d,t_data2$Type,decreasing=c(FALSE,TRUE),method="radix"),] t_data2$amountUSDC <- cumsum(t_data2$amountUSDT) t_data2 <- rbind(t_data2, data.frame( Type = "Balance", duration_d = unique(t_data2$duration_d), amountUSDT = t_data2$amountUSDC[sapply(unique(t_data2$duration_d),function(x,y) max(which(y$duration_d==x)),y=t_data2)] ) ) t_data2$Type <- ifelse(t_data2$Type=="Mint","Liquidity Added",ifelse(t_data2$Type=="Burn","Liquidity Removed","Liquidity Balance")) # t_data2 <- t_data2[t_data2$Type!="Liquidity Balance",-4] plots[[idx]] <- ggplot(t_data2, aes(fill=Type, y=amountUSDT, x=duration_d)) + geom_area(alpha=0.4 , size=.2, colour="black",position = "identity")+ theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))+ scale_x_continuous(breaks = seq(0, 14, by = 1),limits=c(0,14),labels=format(as.Date(t_data$start[1])+days(0:14),"%b-%d-%y"))+ scale_y_continuous(labels = dollar_format(),breaks = pretty(t_data2$amountUSDT,10),lim = range(pretty(t_data2$amountUSDT,10)) )+ scale_fill_manual(values = c("#619CFF","#00BFC4","#F8766D"),breaks=c("Liquidity Balance","Liquidity Added","Liquidity Removed"))+ labs(title = toupper(t_data$Token[1]))+ xlab("")+ ylab("Amount in USD") message(idx) } ``` --- ## Code Walkthrough (Part Five) With each panel stored in a list, we can now make a grid of plots very easily: ```r ## With dates as x axis for first 14 days. plot <- plot_grid( plotlist=lapply(plots,function(x) x+theme(legend.position="none")), nrow=3,ncol=3, align="hv",axis="lr", greedy=TRUE ) legend <- get_legend(plots[[1]] + theme(legend.box.margin = margin(0, 0, 0, 12))) title <- ggdraw() + draw_label("First 14 Days : Liquidity Added/Removed across Uniswap V2 and V3 In all Pairs With Token Present", fontface='bold',size = 20) plot_final <- plot_grid(title, plot, ncol=1, rel_heights=c(0.05, 1)) plot_final <- plot_grid(plot_final, legend, rel_widths = c(3, .3)) ggsave("~/AirdropLiq.jpeg",plot=plot_final,width = 18,height=12) ``` --- ## ... and that gives us our final result! ![](AirdropLiq.jpeg) --- ## Conclusion Ultimately, our foray into this data yielded some interesting findings about how the liquidity distributions have varied across the nine tokens. These customized set of visualizations are useful tools that highlight the variation in liquidity provider behavior. Through the use of these graphs it becomes immediately clear that there is much more we could do. For one, this analysis could be expanded beyond the tokens from this, and potentially an API could be deployed to allow a similar dataset and chart to be generated for the token of one's choosing. In addition, the time frame need not be restricted to this two week period, and could be expanded far beyond. Lastly, the liquidity could also be compared to each token's overall market capitalization, to give even more insight into the token's performance and how it has behaved on the Uniswap platform.