Subgraphs: How to query Uniswap V2 subgraph


In this tutorial, we will explore how to query Uniswap V2, an already-deployed elastic subgraph on Chainstack. Elastic subgraphs are a powerful solution for indexing blockchain data. You can access major DeFi protocols such as Uniswap, Curve, and PancakeSwap through elastic subgraphs. No heavy deployment or programming needed.

👍

Query DeFi subgraphs on Chainstack

Start for free and get your app to production levels immediately. No credit card required. You can sign up with your GitHub, X, Google, or Microsoft account.

Introduction

Chainstack subgraphs simplify the extraction and processing of data from archive nodes, allowing for easy filtering and querying using GraphQL. Without subgraphs, Web3 developers and users would need to aggregate, process, and filter blockchain data themselves, which involves parsing vast amounts of raw transaction data.

We will discuss an elastic subgraph for Uniswap V2. For more information on setting up dedicated custom subgraphs, please refer to our detailed tutorial here.

Uniswap V2 subgraph

The Uniswap V2 subgraph is an already-deployed elastic subgraph that monitors and indexes data from Uniswap V2 smart contracts. You can explore the subgraph schema using the Explorer tool on the GraphiQL page of the subgraph, accessible via the GraphQL UI URL. Additionally, the GraphiQL page offers a convenient tool for testing different queries directly in the browser.

📘

Automated liquidity protocol

💡 Uniswap V2 is an automated liquidity protocol powered by a constant product formula and implemented in a system of non-upgradeable smart contracts on the Ethereum blockchain.

Each Uniswap smart contract, or pair, manages a liquidity pool made up of reserves of two ERC-20 tokens.

Anyone can become a liquidity provider (LP) for a pool by depositing an equivalent value of each underlying token in return for pool tokens. These tokens track pro-rata LP shares of the total reserves, and can be redeemed for the underlying assets at any time.

According to the schema, we can interact with the following entities, among others:

  • Liquidity positions;
  • Pairs;
  • Swaps;
  • Tokens;
  • Transactions.

Additionally, there are several historical data entities that might be useful:

  • Liquidity positions snapshots;
  • Hourly and daily pair data;
  • Daily token data.

Each entity contains different sets of parameters, fields and nested entities.

Querying subgraph

There are multiple ways to query the subgraph. You can use GraphiQL, as mentioned earlier, or the command-line tool curl, along with many other HTTP packages or GraphQL clients implemented in various programming languages. For the examples below, we will use the requests package, a popular and user-friendly HTTP client library for Python.

We will write a Python function to query the subgraph. GraphQL queries are stored as separate files. You can find all the scripts and queries in the GitHub repository.

📘

Lowercased addresses

Note that addresses used in the subgraph are lowercased. Using checksummed addresses or addresses with uppercase letters may result in empty or incorrect outputs.

Prerequisites

  1. Log in to your Chainstack account and add an elastic Uniswap V2 subgraph in the Ethereum Mainnet.

  2. Open your terminal (Command Prompt for Windows or Terminal for macOS/Linux) and run the following command to clone the GitHub repository to your current directory.

    git clone https://github.com/chainstacklabs/uniswap-v2-subgraph-queries .
    
  3. Set up your Python virtual environment.

    python3 -m venv venv
    source venv/bin/activate
    
    python -m venv venv
    venv\Scripts\activate
    
  4. With the virtual environment activated, run the following command to install the required dependencies.

    pip install -r requirements.txt
    
  5. Create the environment variable file (.env) and paste your Chainstack endpoint URL there.

    SUBGRAPH_URL=<uniswap_v2_subgraph_url>
    

Python main.py

Let’s first create a main function that will run all the queries in a loop. We'll start by loading the configuration file (.env).

import os
import json
import time
import requests

from dotenv import load_dotenv


# Load environment variables
load_dotenv()

# Constants
GRAPHQL_ENDPOINT = os.getenv("SUBGRAPH_URL")
QUERIES_DIR = 'Queries'
OUTPUTS_DIR = 'Outputs'

In our project, we store queries as separate .graphql files in the Queries folder. We will read each query file, execute it, and store its result in the Outputs folder.

def main():
    # Ensure the output directory exists
    if not os.path.exists(OUTPUTS_DIR):
        os.makedirs(OUTPUTS_DIR)

    # List all GraphQL query files
    query_files = [f for f in os.listdir(QUERIES_DIR) if f.endswith('.graphql')]
    query_files.sort() # ascending order

    # Process each query file
    for query_file in query_files:
        query_path = os.path.join(QUERIES_DIR, query_file)
        output_path = os.path.join(OUTPUTS_DIR, query_file.replace('.graphql', '.json'))

        try:
            # Read query from a file
            with open(query_path, 'r') as file:
                query = file.read()

            # Execute query
            start_time = time.time()
            query_result = requests.post(GRAPHQL_ENDPOINT, json={'query': query}, timeout=40)
            elapsed_time = time.time() - start_time

            # Save query result
            with open(output_path, 'w') as file:
                json.dump(query_result.json(), file, indent=4)

            print(f"\nSaved results of {query_file} to {output_path}")
            print(f'Elapsed time: {elapsed_time:.2f} seconds')

        except Exception as e:
            print(f"\nFailed to execute {query_file}: {e}")


if __name__ == "__main__":
    main()

Meta queries

Get subgraph details. We can check the latest block synced to the subgraph by querying _meta. Additionally, there is the deployment field, which provides us with the unique identifier for the subgraph deployment. Append it to https://ipfsgw.com/ipfs/ to check the subgraph manifest content. hasIndexingErrors indicates where there have been any indexing errors.

{
  _meta {
    block {
      hash
      number
      parentHash
      timestamp
    }
    deployment
    hasIndexingErrors
  }
}

Queries for traders

Swaps

Get latest swaps. Here, we query the swaps entity with a few parameters: first to limit the response size, orderBy and orderDirection to sort swaps by time in descending order.

{
  swaps(first: 5, orderBy: timestamp, orderDirection: desc) {
    id
    transaction {
      id
      timestamp
    }
    pair {
      token0 {
        symbol
      }
      token1 {
        symbol
      }
    }
    amount0In
    amount1In
    amount0Out
    amount1Out
    amountUSD
  }
}

Get latest swaps for pair of tokens. To obtain a pair address, you can call the getPair function of the UniswapV2 Factory Contract (no gas is needed). You can also query the subgraph using specific conditions, such as traded pairs with USDC (this will be covered later in the tutorial). Use where to filter the results.

{
  swaps(
    first: 5
    orderBy: timestamp
    orderDirection: desc
    where: {
      # WBTC/WETH pair
      pair_: { id: "0xbb2b8038a1640196fbe3e38816f3e67cba72d940" }
    }
  ) {
    id
    transaction {
      id
      timestamp
    }
    pair {
      token0 {
        symbol
      }
      token1 {
        symbol
      }
    }
    amount0In
    amount1In
    amount0Out
    amount1Out
    amountUSD
  }
}

Get the latest swaps with threshold. In this case, we need to use the amountUSD field. In the Explorer, we can see there is a special field for this called amountUSD_gte. This field compares values to see if they are greater than or equal to a specified input.

{
  swaps(
    first: 5
    orderBy: timestamp
    orderDirection: desc
    where: { amountUSD_gte: 100000 }
  ) {
    id
    transaction {
      id
      timestamp
    }
    pair {
      token0 {
        symbol
      }
      token1 {
        symbol
      }
    }
    amount0In
    amount1In
    amount0Out
    amount1Out
    amountUSD
  }
}

Token prices

Get token price. To do this, we need to query the token entity with the argument id (token address). Since prices are in ETH, we should also include ethPrice from the bundles entity.

{
  # WBTC token address
  token(id: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599") {
    id
    symbol
    name
    derivedETH
  }
  
  bundles {
    ethPrice
  }
}

Get historical token prices. Here, we can use tokenDayDatas , which stores historical data sets for all tokens. Prices are in USD.

{
  tokenDayDatas(
    first: 30
    orderBy: date
    orderDirection: desc
    # WBTC token address
    where: { token_: { id: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599" } }
  ) {
    date
    token {
      symbol
    }
    priceUSD
  }
}

Trading volume

Get hourly trading volume of pair. There is both daily and hourly data available. For our example, we will check the hourly data for the USDC/WETH pair (id) since July 1, 2024 (hourStartUnix_gte).

{
  pairHourDatas(
    # WBTC/WETH pair
    where: {
      pair_: { id: "0xbb2b8038a1640196fbe3e38816f3e67cba72d940" }
      hourStartUnix_gte: 1719788422
    }
    orderBy: hourStartUnix
    orderDirection: desc
  ) {
    hourStartUnix
    hourlyVolumeToken0
    hourlyVolumeToken1
    hourlyVolumeUSD
    hourlyTxns
  }
}

Get daily trading volume of a pair. The same approach for querying daily data. Here, we retreive data for the last 7 days.

{
  pairDayDatas(
    # WBTC/WETH pair
    where: { pairAddress: "0xbb2b8038a1640196fbe3e38816f3e67cba72d940" }
    orderBy: date
    orderDirection: desc
    first: 7
  ) {
    date
    dailyVolumeUSD
    dailyTxns
    dailyVolumeToken0
    dailyVolumeToken1
  }
}

Queries for liquidity providers

Pairs

Get largest pairs. The largest pairs have the largest reserves converted to USD. Sort by reserveUSD in descending order.

{
  pairs(orderBy: reserveUSD, orderDirection: desc, first: 10) {
    id
    createdAtTimestamp
    createdAtBlockNumber
    token0 {
      symbol
      id
    }
    token1 {
      symbol
      id
    }
    reserveUSD
    reserveETH
  }
}

Get recently created pools. The most recent pools have the highest block number. Sort by createdAtBlockNumber in descending order.

{
  pairs(orderBy: createdAtBlockNumber, orderDirection: desc, first: 10) {
    id
    createdAtTimestamp
    createdAtBlockNumber
    token0 {
      symbol
      id
    }
    token1 {
      symbol
      id
    }
    reserveUSD
    reserveETH
  }
}

Liquidity positions and DEX metrics

Get largest liquidity positions in pair. We can filter by the id of the pair to get data for a specific pair. To find the largest positions, sort by liquidityTokenBalance in descending order.

{
  liquidityPositions(
    orderBy: liquidityTokenBalance
    orderDirection: desc
    first: 10
    # WBTC/WETH pair address
    where: { pair_: { id: "0xbb2b8038a1640196fbe3e38816f3e67cba72d940" } }
  ) {
    liquidityTokenBalance
    id
    pair {
      token0 {
        symbol
      }
      token1 {
        symbol
      }
      id
    }
    user {
      id
    }
  }
}

Get historical data on liquidity positions in pair. Snapshots store historical data for liquidity positions. Sort by block in descending order, filtering for a specific pair.

{
  liquidityPositionSnapshots(
    orderBy: timestamp
    orderDirection: desc
    where: {
      # WBTC/WETH pair address
      pair_: { id: "0xbb2b8038a1640196fbe3e38816f3e67cba72d940" }
      liquidityTokenBalance_gt: "0"
    }
  ) {
    block
    liquidityTokenBalance
    timestamp
    id
    user {
      id
    }
    liquidityTokenTotalSupply
  }
}

Get Uniswap performance. Uniswap data is accumulated and condensed into daily statistics in the uniswapDayDatas entity.

{
  uniswapDayDatas(first: 10, orderBy: date, orderDirection: desc) {
    date
    dailyVolumeETH
    dailyVolumeUSD
    totalLiquidityETH
    totalLiquidityUSD
  }
}

Conclusion

In this tutorial, we explored various entities such as swaps, tokens, and liquidity positions, and how to query them using GraphQL and Python. Additionally, we provided examples of querying historical data and filtering results based on specific conditions. By following this tutorial, you can effectively interact with and analyze data from Uniswap V2 using Chainstack subgraphs.

About author

Anton Sauchyk

🥑 Developer Advocate @ Chainstack
💻 Helping people understand Web3 and blockchain development
Anton Sauchyk | GitHub Anton Sauchyk | LinkedIn