Gravitational plate of three masses and a slashed discABC0Static engraved plate. Three-dimensional view is unavailable or reduced motion is requested.

← back to fieldarticle

articleAug 4, 2022

UniswapV2 subgraph event queries

How The Graph indexes contract events: subgraph manifests, GraphQL schemas, AssemblyScript handlers, and a local graph-node deploy for a Swapper-style Uniswap flow.

Uniswap
Uniswap

The Graph and subgraphs

The Graph is a decentralized protocol for indexing and querying blockchain data, starting with Ethereum. It helps when you need more than raw contract reads — Uniswap-style AMMs and NFT projects like Bored Ape Yacht Club are the usual examples.

Why not only call the contract?

Basic reads are easy: owner of a given Ape, token URI, total supply. Richer questions are not. Filtering tokens owned by an address by trait, joining transfers with IPFS metadata, or aggregating history means replaying Transfer events and stitching IPFS hashes yourself. Browser dapps make that slow; reorgs and irregular blocks make it worse.

Subgraphs expose indexed APIs over GraphQL so those queries stay cheap.

How a subgraph works

A subgraph is described by a manifest: which contracts to watch, which events matter, and how event data maps into stored entities. The Graph CLI publishes that manifest (often via IPFS) so indexers know what to index.

subgraph
subgraph

Once a subgraph manifest is deployed, data roughly flows like this:

  1. A dapp writes to Ethereum with a contract transaction.
  2. The contract emits one or more events while handling that transaction.
  3. Graph nodes watch new blocks for data their subgraphs care about.
  4. Matching events run the subgraph's mapping handlers.
  5. Mappings are WASM modules that create or update entities the Graph node stores.
  6. The dapp queries those entities over the node's GraphQL endpoint.
  7. The node turns GraphQL into store queries against its indexed database.
  8. The UI shows the result; users may send new Ethereum transactions from there.

Graph Network

The Graph Network is a decentralized indexing protocol. Apps query subgraphs with GraphQL. Roles on the network include indexers (serve data), curators, delegators, and consumers.

Participants stake GRT (an ERC-20 work token on Ethereum) to back query integrity and allocate resources. Indexers, curators, and delegators earn in proportion to work and stake.


Quick Start

  • Subgraph Studio - Ethereum mainnet indexing
  • Hosting Service
    • Ethereum mainnet ↔ External Network (ex.. Binance, Matic..)

Subgraph Studio

Graph CLI install

npm, install -g @graphprotocol/graph-cli

subgraph init

graph init --studio <SUBGRAPH_SLUG>

Subgraph layout

  • Manifest (subgraph.yaml) — data sources to index
  • Schema (schema.graphql) — GraphQL entities you want to query
  • AssemblyScript (mapping.ts) — maps events into those entities

graph init
graph init

Contract Event data query matching operation

  • After subgraph initialization, modify the generated schema.graphql file to develop GraphQl queries for the events of the contract that will collect events.
# Swapper Contract Receive
type Receive @entity {
  id: ID!
  address: Bytes!
  balance: BigInt!
  
}
 
# Swapper Event
type StakeManagermentType @entity {
  id: ID!
  sender: Bytes!
  recipient: Bytes!
  srcToken: Bytes!
  destToken: Bytes!
  expectedAmount: BigInt!
  receivedAmount: BigInt!
  percent: BigInt!
}
 
# history area
enum LogType {
  SWAP
}
 
# Logging 
type Log @entity {
  id: ID!
  logType: LogType!
  createAt: BigInt!
  tx: String!
}
 
# history event
type History @entity {
  id: ID!
  logs: [Log!]!
}
  • The following is the schema structure written to query the events that occur in the Swapper.sol contract, and the event structure of the contract developed and deployed earlier is as follows.
event Received(address, uint256);
 
event Swap(
    address indexed sender,
    address indexed recipient,
    address srcToken,
    address destToken,
    uint256 expectedAmount,
    uint256 receivedAmount,
    uint256 percent
);
  • Because it exists on-chain, in order to generate the corresponding event, the event must be triggered by calling a method that performs the swap function.
  • The Received event is triggered when an Ether exchange occurs when calling this contract externally, and it was added to collect who makes the exchange.
  • The Swap event generates a corresponding event when the swap is finally completed through the multiSwapExactInput function. It was added to collect data such as who used this function and which token pair was used as a swap.
  • If you return to the schema.graphql file again, you can check that the mapping has been performed according to the corresponding event type using the GraphQL syntax. In case of Log Type, it is added for transaction logging.

GraphQL schema handling work in progress

  • If the schema design is complete, you will be able to verify that each handling function is implemented by referencing the information of each function against the built contract abi and parsing the values configured from each event keyword. You can check the signature mapped to eventHandlers in the subgraph.yaml file below.
- event: Received(address,uint256)
handler: handleReceived
- event: Swap(indexed address,indexed address,address,address,uint256,uint256,uint256)
handler: handleSwap
  • Since this file is the role of the manifest, which is the area that Graph refers to for the first time, you need to define which event handler to use before proceeding with the work.
  • If it is created, you can check the created file in the src directory with the contract name specified when it was first set. This file is defined as follows. It informs the vector where the event handling is located, and development should proceed after checking it.
file: ./src/mapping.ts
  • The functions specified as prefix handle play the role of handling the previously designed GraphQL schema. It parses various events that occur in a transaction into an object form and provides a reference.
  • The important point is to execute the graph codegen command to transform the redesigned schema into a code that can be understood in the graph binary. Then generated/schema is generated and each keyword must be used as an entity.
import { BigInt } from "@graphprotocol/graph-ts"
import { evtHistoryPush } from "./utils"
import {
  Received as EvtReceived,
  Swap as EvtSwap 
} from "../generated/Swapper/Swapper"
import { Receive, Swap, Log} from "../generated/schema"
 
// - event: Received(address,uint256)
// handler: handleReceived
// - event: Swap(indexed address,indexed address,address,address,uint256,uint256,uint256)
// handler: handleSwap
 
export function handleReceived(event: EvtReceived): void {
  let entity = new Receive(
    `${event.transaction.hash.toHex()}-${event.logIndex.toString()}`
  )
  entity.address = event.params.param0
  entity.balance = event.params.param1
  entity.save()
 
  logReceived(entity.id, event)
}
 
export function logReceived(IDs: string, event: EvtReceived): void {
  let log = new Log(`${event.transaction.hash.toHex()}-${event.logIndex.toString()}`)
 
  log.logType = "RECEIVE"
  log.createAt = event.block.timestamp
  log.tx = event.transaction.hash.toHex()
 
  log.save()
  evtHistoryPush(IDs, log)
}
 
export function handleSwap(event: EvtSwap): void {
  let entity = new Swap(
    `${event.transaction.hash.toHex()}-${event.logIndex.toString()}`
  )
  entity.sender = event.params.sender
  entity.recipient = event.params.recipient
  entity.srcToken = event.params.srcToken
  entity.destToken = event.params.destToken
  entity.expectedAmount = event.params.expectedAmount
  entity.receivedAmount = event.params.receivedAmount
  entity.percent = event.params.percent
  entity.save()
 
  logSwap(entity.id, event)
}
export function logSwap(IDs: string, event: EvtSwap): void {
  let log = new Log(`${event.transaction.hash.toHex()}-${event.logIndex.toString()}`)
 
  log.logType = "SWAP"
  log.createAt = event.block.timestamp
  log.tx = event.transaction.hash.toHex()
 
  log.save()
  evtHistoryPush(IDs, log)
}
  • After allocating each value to the instance implemented as entity, finally complete save.
  • Here, log functions attached as prefix are added as a separate logging role to extract time and hash information generated in a separate transaction.

Graph build task

  • If you have completed up to this point, you should proceed with the final build to prepare for event connection.
  • Proceed with the build through the graph build command.

codegen.png
codegen.png

Graph-Node Setup

  • After deploying Subgraph, you need to configure the following to run the node for each data management locally.

https://github.com/graphprotocol/graph-node

Graph-Node

The Graph is a protocol for building decentralized applications (dApps) quickly on Ethereum and IPFS using GraphQL.

Graph Node is an open source Rust implementation that event sources the Ethereum blockchain to deterministically update a data store that can be queried via the GraphQL endpoint.

For detailed instructions and more context, check out the Getting Started Guide.

  • You can set up the relevant settings by referring to the following, but there are parts that need to be set up for each project.
version: '3'
services:
  graph-node:
    image: graphprotocol/graph-node
    ports:
      - '8000:8000'
      - '8001:8001'
      - '8020:8020'
      - '8030:8030'
      - '8040:8040'
    depends_on:
      - postgres
    extra_hosts:
      - 172.19.0.1:host-gateway
    environment:
      postgres_host: postgres
      postgres_user: graph-node
      postgres_pass: let-me-in
      postgres_db: graph-node
      ipfs: 'https://ipfs.io'
      ethereum: 'rinkeby:https://rinkeby.infura.io/v3/8c6f778de9a94b6e9ebc0481745ad286'
      GRAPH_LOG: info
      GRAPH_ALLOW_NON_DETERMINISTIC_FULLTEXT_SEARCH: 'true'
      GRAPH_ALLOW_NON_DETERMINISTIC_IPFS: 'true'
  postgres:
    image: postgres
    ports:
      - '5432:5432'
    command:
      [
        "postgres",
        "-cshared_preload_libraries=pg_stat_statements"
      ]
    environment:
      POSTGRES_USER: graph-node
      POSTGRES_PASSWORD: let-me-in
      POSTGRES_DB: graph-node
      PGDATA: "/data/postgres"
    volumes:
      - ./data/postgres:/var/lib/postgresql/data

Subgraph Studio Deploy

  • After completing the build, proceed with deployment. This is the currently defined package.json, and I use the deploy-local command because I plan to run my own graph-node locally to work.
{
  "name": "Uniswap",
  "license": "UNLICENSED",
  "scripts": {
    "codegen": "graph codegen",
    "build": "graph build",
    "deploy": "graph deploy --node https://api.studio.thegraph.com/deploy/ Uniswap",
    "create-local": "graph create --node http://localhost:8020/ Uniswap",
    "remove-local": "graph remove --node http://localhost:8020/ Uniswap",
    "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 Uniswap",
    "test": "graph test"
  },
  "dependencies": {
    "@graphprotocol/graph-cli": "0.33.0",
    "@graphprotocol/graph-ts": "0.27.0"
  },
  "devDependencies": { "matchstick-as": "0.5.0" }
}
  • When initializing is performed, the following command sequence shows the next call sequence. If you modify the code, you do not need to call graph create and proceed in order.
> graph codegen
> graph build
> graph create --node http://localhost:8020/ Uniswap
> graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 Uniswap

Uniswap Contract Code

related

  1. Jul 31, 2025/articleDeposit Forwarder System
  2. Feb 2, 2023/articleUniswap V2 math and contract review
  3. Jan 30, 2023/articleAMM and PMM mechanisms: math beyond the limit order book

graphfeed