GraphQL
GraphQL is a query language for your API, and a server-side runtime for executing queries using a type system you define for your data.
you can learn all about graphql here
Hot Tip
As GraphQL is a type system this means building queries can be a bit tricky, if you are not familiar with GraphQL. The beauty of this is using the http://localhost:3001/playground supplied for you allows you to build up all your queries but also understand every single filter and ordering you can do.
Querying the data
The GraphQL will expose a playground for you which you can get to on http://localhost:3001/playground this uses apollo server sandbox which is a great tool for testing and building up your queries - https://studio.apollographql.com/sandbox/explorer.
Note in these examples we will put the raw parameters in the graphql query but you can pass parameters in using the $
syntax allowing
code to define the parameters.
query AllTransfers {
allTransfers(first: 20) {
nodes {
blockHash
blockNumber
contractAddress
from
network
nodeId
to
txHash
value
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
Query naming conventions
lets say we had 2 events Approval
and Transfer
from the ERC20 standard, the ABI would look like the below:
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "owner",
"type": "address"
},
{
"indexed": true,
"name": "spender",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "from",
"type": "address"
},
{
"indexed": true,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
}
with rindexer graphql you could generate the following queries to get the transfer data you need:
query AllTransfers {
allTransfers {
nodes {
blockHash
blockNumber
contractAddress
from
network
nodeId
to
txHash
value
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
The format of the query names are:
- list items =
all{event_name}s
=All
+Transfer
+s
=AllTransfers
- single item =
{event_name}
(lowercase) =transfer
For single item queries you can use the nodeId
to query single items which is always returned as a field
in the list results alongside the singular item query.
Conflicting event naming
If you have 2 events which have exactly the same name as another contract this is a conflict of naming for graphql so rindexer will render
it as {contract_name}{event_name}
in pascal case, for example Transfer
would turn into {contract_name}Transfer
So its is super clear lets say i had a yaml like this:
name: RocketPoolETHIndexer
description: My first rindexer project
repository: https://github.com/joshstevens19/rindexer
project_type: no-code
networks:
- name: ethereum
chain_id: 1
rpc: https://mainnet.gateway.tenderly.co
storage:
postgres:
enabled: true
contracts:
- name: RocketPoolETH
details:
- network: ethereum
address: 0xae78736cd615f374d3085123a210448e74fc6393
start_block: '18600000'
end_block: '18718056'
abi: ./abis/RocketTokenRETH.abi.json
include_events:
- Transfer
- name: RocketPoolETHFork
details:
- network: ethereum
address: 0xba78736cb615f374d3035123a210448e74fc6392
start_block: '18600000'
end_block: '18718056'
abi: ./abis/RocketTokenRETH.abi.json
include_events:
- Transfer
My query names for allTransfers
would be:
AllRocketPoolETHTransfers
AllRocketPoolETHForkTransfers
Ordering
You can order the results by any field you wish, you can also order by multiple fields the first item in the array will be the applied ordering first then the next will be applied after and so on.
This example will get the first 20 transfers ordered by the block number ascending.
query AllTransfers {
allTransfers(first: 20, orderBy: [BLOCK_NUMBER_ASC]) {
nodes {
blockHash
blockNumber
contractAddress
from
network
nodeId
to
txHash
value
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
Filtering
You can do condition filters as well as advanced filters on all the events indexed.
Condition
You can filter in every event property you want using the condition
input fields.
The example below im filtering on all transfer based on the block number, which has to be a string as its a BigFloat.
query AllTransfers {
allTransfers(first: 20, condition: {
blockNumber: "18600181"
}) {
nodes {
blockHash
blockNumber
contractAddress
from
network
nodeId
to
txHash
value
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
You can mix the filtering in every direction with any field so you can filter blockNumber
with from
and to
with value
or even network
with contractAddress
and txHash
, anything you wish.
query AllTransfers {
allTransfers(first: 20, condition: {
blockNumber: "18600181",
value: "2000000000000000000"
from: "0x0338ce5020c447f7e668dc2ef778025ce398266b"
}) {
nodes {
blockHash
blockNumber
contractAddress
from
network
nodeId
to
txHash
value
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
Filter
For more advanced filtering you can use the filter
input field. For example if we wanted to get all transfer events
over 1 rEth (wei would be 1000000000000000000) and after block number 18600181 we can use the following query.
query AllTransfers {
allTransfers(first: 20, condition: {
value: "1000000000000000000",
}, filter: {
blockNumber: {
greaterThan: "18600181"
}
}) {
nodes {
blockHash
blockNumber
contractAddress
from
network
nodeId
to
txHash
value
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
Result limits
You can define how many you which to return using the first
and last
properties, you can not return more
then 1000 in a single query but you can use offset to get the item you wish to get. We advise to always
set a limit on the amount of items you wish to return.
- first will return the first inserted x items
- last will return the last inserted x items
- offset will return the first/last x items after the offset
query AllTransfers {
allTransfers(first: 20) {
nodes {
blockHash
blockNumber
contractAddress
from
network
nodeId
to
txHash
value
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
Page info
The page info will give you the following information:
- endCursor: The cursor to continue from
- hasNextPage: If there is a next page
- hasPreviousPage: If there is a previous page
- startCursor: The cursor to start from
Cursor based pagination
Cursor-based pagination is a common approach to pagination that avoids some of the pitfalls of "classic" page-based pagination. The idea is to encode the current state of the query into a "cursor" that can be passed back to the server to get the next page of results.
You can page through the data using before
and after
cursors, you can get the cursors from the pageInfo
object.
before
will get the items before the cursor - this is how you go back in the data so say page 2 to page 1after
will get the items after the cursor - this is how you go forward in the data so say page 1 to page 2
query AllTransfers {
allTransfers(
first: 1,
orderBy: [BLOCK_NUMBER_ASC],
after: "WyJibG9ja19udW1iZXJfYXNjIixbMTg2MDAxODEsMV1d"
) {
nodes {
blockHash
blockNumber
contractAddress
from
network
nodeId
to
txHash
value
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
Relationships
When you define relationships between events rindexer will
automatically create relationships between the events in the database and expose them on the GraphQL
interface, this
means you can query the relationships within a single query avoiding having to have multiple queries to get the data you need.
Lets walk through an example imagine we were playing around with the lens
data and we want to get the profile metadata back
when we get quotes created. we can create a relationship between the QuoteCreated
quoteParams.profileId
and the ProfileMetadataSet
profileId
events, note you should read about relationships config first.
Your rindexer.yaml
would look like:
name: LensIndexer
description: My first rindexer project
repository: https://github.com/joshstevens19/rindexer
project_type: no-code
networks:
- name: polygon
chain_id: 137
rpc: https://polygon.gateway.tenderly.co
storage:
postgres:
enabled: true
relationships:
- contract_name: LensHub
event_name: QuoteCreated
event_input_name: "quoteParams.profileId"
linked_to:
- contract_name: LensHub
event_name: ProfileMetadataSet
event_input_name: profileId
contracts:
- name: LensHub
details:
- network: polygon
address: 0xDb46d1Dc155634FbC732f92E853b10B288AD5a1d
start_block: 59034400
end_block: 59034400
abi: ./abis/lens-hub-events-abi.json
include_events:
- QuoteCreated
- ProfileMetadataSet
So in this example the allQuoteCreateds
and quoteCreated
queries will allow you to get the ProfileMetadataSet
event
in the same query. This is a basic example but you can see how you can query the relationships within the same query.
query AllQuoteCreateds {
allQuoteCreateds {
nodes {
nodeId
quoteParamsContentUri
quoteParamsPointedProfileId
quoteParamsPointedPubId
by: profileMetadataSetByQuoteParamsProfileId {
profileId
metadata
transactionExecutor
timestamp
txHash
blockNumber
blockHash
network
}
timestamp
txHash
}
}
}