- Subgraph schemas define how data is stored and queried, including immutable vs. mutable entities and relationships
@entityandidfields are required, with types likeBytes,BigInt,String,Boolean,Int, andBigDecimal- Relationships can be one-to-one, one-to-many, or many-to-many, sometimes using
@derivedFrom - A clear understanding of schema design (e.g., SHIB token indexing example) ensures efficient and accurate data retrieval
Introduction
Chainstack just announced its newest addition to the platform—Chainstack Subgraphs. It allows developers to index and consume blockchain data with peace of mind. To tailor a subgraph to your specific needs, it is essential to understand how its data is stored. A typical schema looks like this:schema.graphql file and is auto-generated when you run the graph init command. It is essential for subgraph deployment. You can create multiple schema files with different names, just make sure to add them in subgraph.yaml.
This article provides a general overview of subgraph schemas. Most of the examples are simplified versions of Messari’s subgraphs. For more in-depth information on how to use a subgraph, you can also check out these great articles in this category.
Entity
All data entries in a subgraph should be an entity and marked with@entity in the schema. Without explicitly declaring, it causes a compilation error. Entities can be related to other entities. For example:
TransferEvent entity has a Token entity in its field. It is a one-to-many relationship. We will go into different types of relationships later.
Mutable vs immutable
In general, there are two types of entities: immutable and mutable.TransferEvent entity is defined as immutable. Immutable entities are much faster to write and query so always define an entity as immutable if possible.
By default, all entities are mutable meaning that they can be changed during execution. Immutable entities, as the name suggested, can’t be modified once it is created. It can only be modified if only the changes happen in the same block in which it is created.
When you define your schema, you should have a clear idea of how your subgraph stores data. For example, the Owner entity and Token entity are always updated when a transfer happens; therefore they should not be immutable entities. TransferEvent is the transaction details, and is considered finalized once it is confirmed; it is very unlikely to be modified therefore should be an immutable entity.
Types
The Graph supports 6 data types:Bytes— hexadecimal string. Commonly used for hashes and addresses.String— values ofstringtypeBoolean— values ofbooleantypeInt— integer values in GraphQL are limited to 32 bytes. This type can be used to storeint8,uint8,int16,uint16,int24,uint24, andint32data.BigInt— this type can store integers that are beyond 32 bytes, such asuint32,int64,uint64untilint256anduint256.BigDecimal— handling decimals on Ethereum can be tricky. The Graph supports high-precision decimals represented as a significand and exponent. The exponent range is from -6,143 to +6,144. Rounded to 34 significant digits.
enum types. For example:
Optional and required fields
The fields in an entity can either be optional or required. A required field is indicated with “!”. In this example,id and name are mandatory for Token entity. If any of these fields are missing during indexing, an error occurs.
id field is mandatory for all entities. It must be unique and it serves as the primary key. The id field must be either byte or string type.
If your subgraph is indexing transactions, you can use the transaction hash as id. For a token, this id can be either the token’s smart contract address or a unique string. Below are some ideas for unique ID values:
event.params.id.toHex()event.transaction.from.toHex()event.transaction.hash.toHex() + "-" + event.logIndex.toString()
Relationships
An entity may relate to one or more other entities.One-to-one
Below is an example pair of one-to-one entities referencing each other:One-to-many
Below is a one-to-many relation example:Many-to-many
Many-to-many relations are usually defined as an array in both entities. Taking ERC-20 token as an example, a token may have many owners, so theToken entity should keep an owner list. An owner can potentially hold more than one token so the Owner entity also keeps a list of tokens. The schema:
Reverse lookups
In the above example, a new keyword@derivedFrom is used, it is called reverse lookup.
When a field is defined as @derivedFrom, it is never actually created during indexing. Instead, it is referencing another field in the other entity. In the above case, when an owner’s tokens field is queried, GraphQL looks into the owners field of the Token entity to find all the tokens with a particular owner in its owners field and returns that as a result.
Indexing SHIB token
Here is an example of subgraph indexing for Shiba Inu coin, which includes transfer events and owners’ account balances. This example is built with graph CLI version 0.45.1. Firstly, rungraph init with contract address: 0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE.
Change the subgraph.yaml to the following:
schema.graphql in the following manner:
Account entity keeps all the balance of SHIB token holder’s account balances. The Transfer entity keeps a record of all transactions that are related to SHIB token. Here we use transaction hash as its ID.
Next, to generate schema.ts in the generated directory run:
src directory to define our event mapping.
npm install and graph build to compile the code and deploy it to Chainstack Subgraphs with the deployment command which can be found in your Chainstack console.
When deployment completes, open the GraphQL UI URL in your browser to see the result.
