hack.labs() Open-Source Release: Python Uniswap Utilities to Monitor Positions and Liquidity

By Ian Masters, Senior Quant Trader at Hack VC

hack.labs()

hack.labs() is Hack VC’s in-house tech platform that supports our funds by operating, monitoring and testing the on-chain aspects of our funds’ portfolios. The team focuses on on-chain infrastructure and off-chain integrations, and the development of related tooling necessary to perform those activities.

As most web3 developers know too well, it can be extremely difficult to find simple tools for on-chain monitoring. As such, I have decided to open-source some of our most-used tools that we have developed in-house with the community, starting with two Python Uniswap tools, and with more to come. 

Uniswap-Breakouts

Introduction

The first utility that we are sharing is a Uniswap integration library.

The tool is available here on our Github: https://github.com/Hack-VC-Labs/uniswap-breakouts.

The Uniswap team has typically chosen to prioritize gas efficiency over interface simplicity. Consequently, we have found that integrating with Uniswap can be challenging for those unfamiliar with its inner workings. Uniswap has relatively good documentation, but we have still needed to perform significant trial-and-error programming as we develop our own integrations.

Here, we have split out a couple of our most-used integrations into a stand-alone library that we hope will provide some clarity.

Underlying Position Breakouts

One of the most common questions I see from Uniswap users is how to break out an LP position into its underlying balances. Tools like Zapper, Debank and APY Vision can do this for you, but they’ll only let you see the current state of your position. Our tool gives you the breakdown of your underlying position in human and machine-readable formats at arbitrary block heights.

Disclaimer: Full nodes only maintain state data for the most recent 128 blocks. You will need to run the tool against an archival node to access older block heights.

You might want to see how your position looked at a point in time in the past to:

  • Provide auditors with quarterly or EOY positions
  • Calculate and study realized impermanent loss
  • Derive PnL across any time window

You might want your other applications to be able to read your breakout to:

  • Calculate portfolio-wide exposures
  • Value your position using the underlying token prices

Uniswap V2 Positions

Getting underlying positions for Uniswap V2 pools is relatively straightforward. The basic calculation looks like this:

  1. Find your LP token balance - balanceOf() on the pool contract
  2. Find the total LP token supply - totalSupply() of the pool contract
  3. Use these results to find your % share of the pool liquidity
  4. Find the total underlying balances of the pool - balances() on the pool contract
  5. Apply your LP share to the total underlying balances to get your claim on the underlying reserves

You can find the code for this calculation in the V2 Module

Uniswap V3 Positions

Calculating underlying balances for a Uniswap V3 position is more involved; Uniswap has published a two-part blog series just on liquidity math. I found Liquidity Math in Uniswap v3 by Atis Elsts particularly helpful here. Check out those resources for a deep dive into our calculation here.

Despite the myriad of questions online about this topic, I have yet to come across an all-in-one Python implementation of V3 liquidity math. The code in our V3 Module will pull the necessary values directly from the blockchain, calculate the underlying balances and generate a JSON report for any position token ID you provide:

{
 "v3_positions": [
    {
      "chain": "ethereum",
      "pool_address": "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640",
      "nft_address": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
      "nft_id": 525319,
      "block_no": 17485966
    }
 ]
}

(Note that I used a random position from the WETH<>USDC 5bp Pool as an example.)

Running the utility from the command line will generate a JSON position report:

> python -m uniswap_breakouts -c chain_config.json -p position_config.json -o outfile.json
{
 "V3 Positions": [
    {
      "position_spec": {
        "chain": "ethereum",
        "pool_address": "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640",
        "nft_address": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
        "nft_id": 525319,
        "block_no": 17485966
      },
      "position_breakdown": {
        "chain": "ethereum",
        "block": 17485966,
        "token_id": 525319,
        "current_ratio": "0.0006076500303414135171761512701",
        "lower_tick": "0.0005213835018309208171508175096",
        "upper_tick": "0.0007080187383649397251128772395",
        "token0": {
          "index": 0,
          "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
          "symbol": "USDC",
          "decimals": 6
        },
        "num_token0_underlying": "9629.052285048853265825519564",
        "token1": {
          "index": 1,
          "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
          "symbol": "WETH",
          "decimals": 18
        },
        "num_token1_underlying": "5.028677685533798722836940252"
      }
    }
  ]
}

Roshun Patel, a Partner at Hack VC, contributed a Google sheet that allows anyone to track the current value (and more) of a V3 liquidity position given the deposit details. To do this, copy the sheet and replace the highlighted example fields with the details from your own deposit for a breakdown of your current position.

V3 Tick Liquidity Breakouts

The second utility we provide with this release is a lightweight tool for constructing Uniswap V3 liquidity curves. I have found V3’s tick liquidity interface even more opaque than the position interface. We looked under the hood at the contract code to develop an efficient way to compile the most serviceable liquidity data.

A quick Google search will yield many suggestions for how to generate the liquidity curves you see on uniswap.com:

Uniswap’s V3 endpoint on The Graph’s hosted service will get you the data you need to construct this chart, and if you inspect uniswap.com’s network activity, you will see that it uses the subgraph to supply its tick data.

We, however, prefer solutions that query directly from the blockchain. Blockchain queries are uniform, easily verifiable and more reliable than trusting a third-party hosting service. If you want to derive the liquidity curve from the blockchain, you will see a couple of common suggestions online:

Reconstruct the entire liquidity profile from LP Mint and Burn events: If you scan each block from the pool’s creation, you can see all the transactions that add or remove liquidity. You will see the upper and lower bounds of the liquidity range and the amount of liquidity provided. You can compile all these events by attributing the net liquidity to the corresponding ticks and generating a complete profile. This method will give you a nice timeline of the pool’s liquidity curve and aid in the discovery of new pools. The downside is that there can be many blocks to scan and thousands and thousands of such events. 

Request tick data on every possible tick in the pool: V3 Pools have a ticks() method that will give you the relative liquidity of the tick at the index you request. Each tick will have a liquidityNet data field that tells you the difference in liquidity between that tick and the previous tick. When we have the net liquidity for every tick, we can construct the curve by taking the cumulative sum across them. There are 2^24 possible ticks in each pool. The spacing (found at the tickSpacing() method) of the ticks will reduce the number of potential “initialized” ticks, but only by a factor of 200 at most. If you call each tick individually, you will end up DDOSing your node or burning through your query allowance in short order.

That brings us to our suggested method: Using the tick lens to construct the liquidity from the middle out. The tickLens contract provides batched results for tick ranges. We can find the active tick range and its liquidity via the slot0() function on the pool. Since the tick lens gives us the net liquidity of each tick relative to the adjacent ones, we can easily construct the liquidity around our active tick. We primarily care about ticks close to the current price, so we can massively reduce the number of queries necessary to construct the most critical ranges.

Check out the excellent V3 Development Book section on the tick bitmap for more color on how this works, and see the code and comments in our tool for how we execute this method.

The result, using only ten queries, looks like:

Where to go from here?

These utilities are primarily designed to take a snapshot of a pool or position state at a single point in time, but we can use them as the basis for real-time systems as well. If we want to build an application that tracks a pool's live liquidity profile, we can construct an initial state, then watch new blocks for LP Mint/Burn events and apply the updates to the snapshot object. Similarly, we can use the position breakouts tool to construct a snapshot, track trade events and apply price changes when our position is in range.

But these are topics for a different article. Feel free to reach out on Twitter (@iancmasters) or GitHub with any questions, concerns or collaborations!

The information herein is for general information purposes only and does not, and is not intended to, constitute investment advice and should not be used in the evaluation of any investment decision. Such information should not be relied upon for accounting, legal, tax, business, investment, or other relevant advice. You should consult your own advisers, including your own counsel, for accounting, legal, tax, business, investment, or other relevant advice, including with respect to anything discussed herein.

This post reflects the current opinions of the author(s) and is not made on behalf of Hack VC or its affiliates, including any funds managed by Hack VC, and does not necessarily reflect the opinions of Hack VC, its affiliates, including its general partner affiliates, or any other individuals associated with Hack VC. Certain information contained herein has been obtained from published sources and/or prepared by third parties and in certain cases has not been updated through the date hereof. While such sources are believed to be reliable, neither Hack VC, its affiliates, including its general partner affiliates, or any other individuals associated with Hack VC are making representations as to their accuracy or completeness, and they should not be relied on as such or be the basis for an accounting, legal, tax, business, investment, or other decision. The information herein does not purport to be complete and is subject to change and Hack VC does not have any obligation to update such information or make any notification if such information becomes inaccurate.

Past performance is not necessarily indicative of future results. Any forward-looking statements made  herein are based on certain assumptions and analyses made by the author in light of his experience and perception of historical trends, current conditions, and expected future developments, as well as other factors he believes are appropriate under the circumstances. Such statements are not guarantees of future performance and are subject to certain risks, uncertainties, and assumptions that are difficult to predict.