PoolCreated
, that carries the details of the newly created pool.
Here, you will learn how to create a subgraph for indexing the data from this event and keeping track of all the pools in Uniswap (version 3) and also the tokens involved in those pools.
So let’s build a subgraph.
Detail | Provided value |
---|---|
Protocol | Ethereum |
Product for which to initialize | subgraph-studio |
Subgraph slug | UniswapV3Graph** |
Directory to create the subgraph in | UniswapV3Graph** |
Ethereum network | mainnet |
Contract address | Address of the UniswapV3Factory contract |
Contract name | Name of the UniswapV3Factory contract |
Index contract events as entities | Yes |
/abis
directory. graph-cli will also install all the required npm packages for the project.
schema.graphql
file, which contains the data schema. The schema file helps define the data that is to be stored by the subgraph and how to query it using GraphQL.
Now, when you open the schema file in your code editor, you can see that it already contains some schema definitions. They define various entity types and their relationships.
An entity is essentially a data object, and we can use the schema.graphql
file to define multiple entities. Each entity in the schema file will be annotated using the @entity
directive. Within each entity, we can add certain fields that act like the entity’s properties.
Each entity field will have a name
to identify it and a type
that defines the kind of data that should be stored against that field. The subgraphs fetch the blockchain data and store it in the form of entities that are defined in the schema file.
Developers can make certain fields in the entity mandatory by marking them with an exclamation mark (!
). Entities are mutable by default, meaning we might modify existing entities while mapping the data to an entity. To prevent this, we can make entities immutable by using the following annotation: @entity(immutable: true)
.
In the auto-generated schema file, entities are modeled after the events emitted by our factory contract (OwnerChanged
, PoolCreated
, FeeAmountEnabled
). Some of the fields in those entities represent the data that is part of the respective events, and other fields are there to hold generic block information.
All the entities, however, have an ID field, which is sort of like the unique identifier or the primary key. Hence, the id
field is mandatory among entities, and the ID should always be of the type Bytes
or String
.
So, the auto-generated schema file (and the project as a whole) is defined to index the data from these events. But as we already discussed, we need to keep track of the pools and the tokens in them. To do that, we can remove all the existing entities from the schema file and create two new entities:
Pool
and a Token
entities. As you can see, within the Pool
entity, we have mentioned Token
as a data type for certain fields (token0
, token1
). This is how we define the relationship between the entities. The fields represent the tokens involved in the pool, and by making them type Token
, we have established a relationship from one entity to another.
Both entities have an id
field, which is given as type ID
. This essentially means that the id
field expects a string value (type ID
== type String
).
Token
dataToken
entity, and within that entity, we have defined fields like name
and symbol
, indented to carry the token name and symbol. Now, if you inspect the data emitted using the PoolCreated
event, it doesn’t have all those details. In the event we are provided with the token addresses, it seems like we need to use those addresses to get the name
and symbol
of the tokens.
Now, this is where The Graph becomes a thing of beauty.
PoolCreated
event is the address of these smart contracts. Since they are ERC-20 tokens, their smart contract includes certain ready-only (view) functions that return the token name and symbol.
So, all you need to do is to get the ABI of the contract, and The Graph will actually allow you to bind the corresponding contract of the ABI to its respective address and call any and all public read-only (view) functions from that contract.
This means that if we could get ABI of the token contracts, we can bind it with the token address that is passed in the event and call the required functions for fetching the token name and symbol. So, how do we get the individual token contract ABIs?
One way to do this is to add the ABI of the general ERC-20 contract implementation to our project. This will allow us to access all the default, public read-only functions that are part of every ERC-20 token. Yes, that includes the getter
functions for the token name and symbol.
/abis
directory, ERCToken.json
(you can choose any name), and save the ABI inside the file. Now your project structure should look like this:
subgraph.yaml
) is the entry point to our subgraph. It specifies all the parameters of our subgraph, like its schema, data sources, and mappings. Overall, it contains all the information required for indexing and querying our subgraph. As with other files, graph-cli generates a manifest file for our subgraph when we initialize the project, but as always, we have to make a few tweaks of our own:
startBlock
property in the dataSources
section.startBlock
startBlock
property is rather interesting as it specifies the block from which we want to start our indexing. While specifying the property, remember that the lower the block number, the higher the time it will take to complete the indexing.subgraph.yaml
(manifest) file as a reference and generate:
/generated
directory as AssemblyScript files (.ts):
/src
directory: uniswap-v-3-factory.ts
.
The structure of the mapping code is quite straightforward; remember the eventHandlers
that we defined in our manifest:
handlePoolCreated
). The event handler is defined as a function within the mapping file, and the function will have the same name as the handler (handlePoolCreated
).
Each handler function accepts a single parameter called the event
. The type
of event
is set to the event that it is handling.
Now, before we modify our mapping file, let us create some additional code for getting the token details.
/src
directory (tokenUtils.ts
) and add the code for:
tokenUtils.ts
, inside the /src
directory and add the following code:
codegen
command, in order to access the token details.
Apart from the generated files, we also use the graph-ts library to import the Address
type. The graph-ts library is specifically designed to help write subgraph mappings, and it does so by providing APIs to access data on the chain, cryptographic functions, smart contracts, etc.
Alright, now we have a way to fetch the token details, so let’s use it to map data to our entities.
PoolCreated
event, open the uniswap-v-3-factory.ts
file and replace the existing code with the following:
tokenUtils.ts
file, we import all the required classes and code from the generated files. This lets us access all the required information. Also, we are using the functions from the tokenUtils.ts
file to fetch the token details.
You can see that while calling those functions, we are passing the address of the tokens as parameters. The token address itself is given as an event parameter event.params.token0.toHexString()
.
Also, while creating new entities (Token
, Pool
), you can see that the address of the token and the pool are taken from the event parameters. It is then converted to a string with toHexString()
and passed onto the class afterward in order to be stored as the ID of that particular entity. In the case of Token
, the same method is used to load the tokens.
codegen
right before you build your code./build
directory.
And with that, your subgraph is ready for deployment.