Create Lens Subgraph on The Graph Protocol
how to create an indexed subgraph for decentralized querying
๐ Links
- github: andriishupta/thegraph-hello-world
- subgraph: andriishupta/hello-world
- TheGraph Documentation
- Lens Documentation
๐ฐ Published on
โจ
Initially, I got familiar with TheGraph by contributing to DeveloperDAO to a project that did the same - indexing Lens Protocol. P.S. This tutorial is NOT copy-paste of existing code, and I created a repository from scratch to understand the basics of TheGraph and how to start. I needed to develop my subgraph to test how events would be indexed.
๐ค Decentralized querying
The Graph is an indexing protocol for querying networks like Ethereum and IPFS. Anyone can build and publish open APIs, called subgraphs, making data easily accessible.
Many different tools and APIs help us query blockchain data using centralized API like we used to with web2. My favourites are alchemy and infura.
But, if we want to go decentralized, we must use decentralized tools.
All data is stored and processed on open networks with verifiable integrity. TheGraph makes querying this data fast, reliable, and secure.
Lens
Lens Protocol is a composable and decentralized social graph, ready for you to build on so you can focus on creating a great experience, not scaling your users.
Own your content. Own your social graph. Own your data.
With Lens API, we could do everything we want with protocol, but it still includes the web2 principle and has a centralized database for different things built on top of Smart Contract data.
If we want to get actual blockchain data, we need to use a protocol like TheGraph.
Disclaimer: assumption about the centralized part of Lens API is based on the features that Smart Contract has not, for example: "likes" functionality. I like Lens API very much and would count on it 99.9% of the time as a personal preference, balancing centralized vs. decentralized tooling.
๐ How to create a subgraph
In this tutorial, I will give an example of creating a subgraph using Hosted Service - it will be closed in months. Still, there won't be a difference in coding approaches, just in how it is internally deployed. With the Hosted Service, it was easier to use for testing purposes.
graph init
- install graph-cli
- run
graph init --product hosted-service
- follow CLI steps where you need to add protocol, name, and contract address
You will get a generated project. The main entry point is subgraph.yaml. This is the finished version of my subgraph.
๐ source code
You can read more on what is subgraph manifest
source block
Indicates from what address to index. Many contracts use Proxy Upgrade Pattern, which helps to fix crucial bugs or update implementation. That is why I have added LensHub ABI(Application Binary Interface) as source ABI and then changed the address to proxy
startBlock
- I chose some random block for testing purposes, so it won't start indexing from the start - it takes more time. Usually, this value is omitted or equals the Contract creation's block.
To get ABI, you can:
- Copy from Etherscan/Polygonscan whole Contract ABI
- Go to remix.ethereum.org, copy the github project, and compile the contract
I went with a 50/50 approach, such as Lens has Events.sol
library that is not compiled as part of the main contract.
graph codegen
After proper setup, we can run code generation - we will get all types of code to work with.
โจ The Graph has excellent documentation, so follow it and find all answers there. โจ
Mindset shift
As a developer who worked with databases, I started to think linearly: entity created -> entity updated. But events could be indexed from startBlock
and in non-linear order in time, so even if we index the event like handleProfileImageURISet
, we need to check if the entity existed previously(more in the code example).
To find more tips, TheGraph documentation has a section "defining entities" - it entirely describes what you should think about when creating your schema.
Before defining entities, it is important to take a step back and think about how your data is structured and linked.
Code
As we know - schema and how our data would be created is essential. Let's look at how I have added entity definition:
๐ source code
Profile
is the @entity
, including default fields that I took from ProfileCreated
event and the posts
field, which is an entity relationship
In this case, it is a "One-To-Many" relationship with the usage of the Reverse Lookup approach that TheGraph recommends us using.
For one-to-many relationships, the relationship should always be stored on the 'one' side, and the 'many' side should always be derived. ... will result in dramatically better performance for both indexing and querying the subgraph...
Post
is also an @entity
. Both have an ID
, which should be unique for the subgraph.
In lens-hub.ts
are located functions that correspond to events we want to index.
handle*Created
As we see, even for a new Post, we check if Profile already exists or not. I discovered the error I got during subgraph deployment and indexing that was saying something like "profile cannot be null" when I just wanted to load Profile to Post.
TheGraph also supports the "merge" approach - this means that if we create a new instance of Profile and it already exists - it is okay cause the subgraph would try to merge fields. I wanted to be the more precise cause in the examples, and I saw that in every place where we wish to create something - we first check if it could be already created.
Example: I have created a Post, and the subgraph knows that Post should have a Profile. Profile event was way before Post, so we don't have a Profile, and we need to create it; even if you could think - "how a Post could be created without a Profile" - it couldn't, but the event about the Post we could get first to index.
๐ source code
โ Testing
To check out how it works, you could visit my subgraph: andriishupta/hello-world. It has a pre-defined "Test" query that will give you info for both Profiles and Posts. You could modify it to get more or less information in the query window.
๐
Thanks for reading!