Writing Advanced GraphQL Queries with the Unmarshal Parser
GraphQL simply put is an open-source data query and manipulation language. It’s fancy SQL built for use with APIs submitting customisable JSON requests for data. GraphQL’s simplicity and customizability make it highly malleable and easy to use as a catch-all solution. Unmarshal provides users with the ability to get access to all of their parser data using our GraphQL service.
Use Cases
- You need to make configurable calls from an application
- You want access to data to perform some computations on
- You want quick paginated access to data stored in the parser
- The Templates you can get from Unmarshal’s Metabase service aren’t sufficient for your application
Using the GraphQL Playground
Log in to your console account and open the GraphQL section
You will be greeted with an in-house query editor. This editor includes documentation and most other relevant data you need to understand your GraphQL
Let’s take a closer look at the built-in playground
- Holds documentation
- List of previously tried queries
- A refresh button to refresh your schema
- Opens up a list of available shortcuts
- Settings to change your theme
- The Editor
- The run button to test your queries
- Prettify and format your queries
- Merges multiple segments into one query
- Copy the query
- Add another editor tab
- The result of your query
Writing Queries
For this guide we consider a simple ERC20 contract. You can take a look at the schema and GraphiQL playground here: https://dep-jekbi9rdfyi7tuqw-graphql.prod.unmarshal.com/marshether_oklzj/graphiql
A simple query for getting Transfer
’s would look like this
query TransferData {
allTransferEvents {
nodes {
eventFrom
eventTo
eventValue
decimalAdjustedEventValue
tokenPriceEventValue
txHash
chainId
contractAddress
}
}
}
We tell our Server that this is a query and we want allTransferEvents
. The nodes
tells us what data we expect back.
Pagination
Of note is the length of the result array consisting of only 25 items at most. We will need to paginate to get the rest of the data. We can do this in the following two ways.
Cursor Based
Exposing pageInfo
fetches us a cursors to traverse through our data
query TransferData {
allTransferEvents {
nodes {
eventFrom
eventTo
eventValue
decimalAdjustedEventValue
tokenPriceEventValue
txHash
chainId
contractAddress
}
pageInfo {
endCursor
hasNextPage
startCursor
hasPreviousPage
}
}
}
We get a result JSON that may look a little like this:
{
"data": {
"allTransferEvents": {
"nodes": [
{
"eventFrom": "0x0000000000000000000000000000000000000000",
"eventTo": "0xeba6d5b21dd4fffbd31a8719dd73eaced887b413",
"eventValue": "100000000000000000000000000",
"decimalAdjustedEventValue": "100000000",
"tokenPriceEventValue": "0",
"txHash": "0x37af853893d21a22710032994f6bf85c220a3426997fc2e885536309948a7070",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37"
},
.
.
.
],
"pageInfo": {
"endCursor": "WyJwcmltYXJ5X2tleV9hc2MiLFsyNV1d",
"hasNextPage": true,
"startCursor": "WyJwcmltYXJ5X2tleV9hc2MiLFsxXV0=",
"hasPreviousPage": false
}
}
}
}
Of note here is the pageInfo
object. We know from a cursory look at it there’s more to this data and we have an endCursor
to start the next query from. Since the initial query starts from the top there’s no previous page to go back to.
Next, we need to move ahead a page. endCursor
indicates where the next batch of data is going to start from. We can add our after
filter and pass the endCursor
to it. (Similarly, if we were paginating in the opposite direction we’d pass the startCursor
to the before
filter)
query TransferData {
allTransferEvents(after: "WyJwcmltYXJ5X2tleV9hc2MiLFsyNV1d") {
nodes {
eventFrom
eventTo
eventValue
decimalAdjustedEventValue
tokenPriceEventValue
txHash
chainId
contractAddress
}
pageInfo {
endCursor
hasNextPage
startCursor
hasPreviousPage
}
}
}
Offset based
You may also require an offset based pagination or maybe even a combination. Let’s say we want to fetch elements 20 to 30, we can write the following query:
query TransferData {
allTransferEvents(offset: 19, first: 11) {
nodes {
eventFrom
eventTo
eventValue
decimalAdjustedEventValue
tokenPriceEventValue
txHash
chainId
contractAddress
blockTime
id
}
}
}
We get the following result
{
"data": {
"allTransferEvents": {
"nodes": [
{
"eventFrom": "0x0000000000007f150bd6f54c40a34d7c3d5e9f56",
"eventTo": "0x9c5de3ad97b95a0da09fd0de84c347db450cd75c",
"eventValue": "3109172740659266715648",
"decimalAdjustedEventValue": "3109.1727406592668",
"tokenPriceEventValue": "7.243365097147954",
"txHash": "0x3f58d8cf015d685a917d9f0149d73012fcc536b653e054a4070d60ef7946c046",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37",
"blockTime": "2021-03-30T15:58:20+00:00",
"id": "20"
},
{
"eventFrom": "0x9c5de3ad97b95a0da09fd0de84c347db450cd75c",
"eventTo": "0x8588a6c17af8984553168a9a1172ac65e17d4786",
"eventValue": "760117762236807093052",
"decimalAdjustedEventValue": "760.1177622368072",
"tokenPriceEventValue": "7.490623316382352",
"txHash": "0xe520661f1007dbd825a9d34dccf6ec7c28e5b7f72de9a23c8e962e67bb5ee9be",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37",
"blockTime": "2021-03-30T15:56:06+00:00",
"id": "21"
},
{
"eventFrom": "0x9c5de3ad97b95a0da09fd0de84c347db450cd75c",
"eventTo": "0x8be54c4599c4f393026c9f41cf6bcf9fd66b10f7",
"eventValue": "51137087557317211323",
"decimalAdjustedEventValue": "51.137087557317216",
"tokenPriceEventValue": "7.362847571576029",
"txHash": "0xcf2f81f81d38e6dfcf30d0958e3faf936259442106eddbbf1f0de546e67e1d9b",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37",
"blockTime": "2021-03-30T15:55:56+00:00",
"id": "22"
},
.
.
.
.
{
"eventFrom": "0x9c5de3ad97b95a0da09fd0de84c347db450cd75c",
"eventTo": "0x34dccfae06202d1167797870679fc1fb9ef7a523",
"eventValue": "453363841465952335411",
"decimalAdjustedEventValue": "453.3638414659523",
"tokenPriceEventValue": "7.704793717011617",
"txHash": "0xfbeaf03973d4e69b71a266abca106726e8b36549cc67806e19827890b886798c",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37",
"blockTime": "2021-03-30T15:55:19+00:00",
"id": "29"
},
{
"eventFrom": "0x9c5de3ad97b95a0da09fd0de84c347db450cd75c",
"eventTo": "0xe9cc4e6bdd4b722930a09af9bb99d6726cf45b31",
"eventValue": "96575600000000000000",
"decimalAdjustedEventValue": "96.5756",
"tokenPriceEventValue": "7.704793717011617",
"txHash": "0x1adeb1878252ac4798e4be26a4e5fae99152a6b689818c5ae5966b793aad7965",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37",
"blockTime": "2021-03-30T15:55:19+00:00",
"id": "30"
}
]
}
}
}
offset
tell our server how many elements to skip and first
tells it how many to fetch
Sorting
Pagination without an obvious sort is rarely ever useful. In this case, let’s try and sort by the blockTime
. Let’s say we want the latest transactions first then orderBy
is the filter to use. It accepts an enum that’s self explanatory in function and orders our data.
query TransferData {
allTransferEvents(orderBy: BLOCK_TIME_DESC) {
nodes {
eventFrom
eventTo
eventValue
decimalAdjustedEventValue
tokenPriceEventValue
txHash
chainId
contractAddress
blockTime
}
}
}
Taking a look at the available documentation, you can sort on every column offered in ascending either descending order in addition to other sorts present
Plugins
Pagination will likely not be the only filter you’d apply to your data. You may need a host of other filters and conditions to effectively parse your data.
At the moment we support two primary plugins. (There’s planned support for aggregation based querying in a future update).
Condition
This plugin allows you to make equality-based filters when querying data. Say you want only a particular transaction hash, your query would look as follows:
query TransferData {
allTransferEvents(
condition: {txHash: "0xca87ce37910bd443d01f335be03247bf8f184203c2c42860976f03e1c2e7c6ab"}
) {
nodes {
eventFrom
eventTo
eventValue
decimalAdjustedEventValue
tokenPriceEventValue
txHash
chainId
contractAddress
}
}
}
That would give the following result
{
"data": {
"allTransferEvents": {
"nodes": [
{
"eventFrom": "0xeba6d5b21dd4fffbd31a8719dd73eaced887b413",
"eventTo": "0xf13f96f7f95517ae54a0b3f9494e58607edfca1f",
"eventValue": "600000000000000000000000",
"decimalAdjustedEventValue": "600000",
"tokenPriceEventValue": "0",
"txHash": "0xca87ce37910bd443d01f335be03247bf8f184203c2c42860976f03e1c2e7c6ab",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37"
}
]
}
}
}
You can make a host of other comparisons similarly and use it accompanied by the other filter discussed previously.
Filter
Filter can be considered a superset of Condition. It allows you to make any logical comparison. Maybe you only care about transactions which have more than 8000
tokens transferred.
query TransferData {
allTransferEvents(
filter: {decimalAdjustedEventValue: {greaterThanOrEqualTo: "8000"}}
) {
nodes {
eventFrom
eventTo
eventValue
decimalAdjustedEventValue
tokenPriceEventValue
txHash
chainId
contractAddress
}
}
}
Filter options can be combined with each other to make more specific queries. In addition to more than 8000
tokens, I want to make sure the value of the token at the time of transfer was higher than 1.60$
query TransferData {
allTransferEvents(
filter: {
decimalAdjustedEventValue: { greaterThanOrEqualTo: "8000" }
and: { tokenPriceEventValue: { greaterThan: "1.6" } }
}
) {
nodes {
eventFrom
eventTo
eventValue
decimalAdjustedEventValue
tokenPriceEventValue
txHash
chainId
contractAddress
}
}
}
Advanced Querying
There will likely be scenarios where you need data from multiple tables within the same context. You can request data from multiple different tables or even different kinds of data from the same table and submit just a single query.
Let’s say you wanted all Approval
and Transfer
events involving a single address:
query TransferAndApprovalData {
allTransferEvents(
condition: { eventFrom: "0x90b32e5408b9ea6336472c8367dfd349a0c28c28" }
) {
nodes {
eventFrom
eventTo
eventValue
decimalAdjustedEventValue
tokenPriceEventValue
txHash
chainId
contractAddress
}
}
allApprovalEvents(
condition: { eventOwner: "0x90b32e5408b9ea6336472c8367dfd349a0c28c28" }
) {
nodes {
eventOwner
eventSpender
eventValue
txHash
chainId
contractAddress
}
}
}
The data returned would look like this
{
"data": {
"allTransferEvents": {
"nodes": [
{
"eventFrom": "0x90b32e5408b9ea6336472c8367dfd349a0c28c28",
"eventTo": "0x9c5de3ad97b95a0da09fd0de84c347db450cd75c",
"eventValue": "8000032599896102303526",
"decimalAdjustedEventValue": "8000.032599896102",
"tokenPriceEventValue": "5.131407150879911",
"txHash": "0x7f8b91414e74afa25c4bcbcec295123d52a64a3f3344792fc8818ca6eedad3e7",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37"
},
{
"eventFrom": "0x90b32e5408b9ea6336472c8367dfd349a0c28c28",
"eventTo": "0x9c5de3ad97b95a0da09fd0de84c347db450cd75c",
"eventValue": "6000000000000000000000",
"decimalAdjustedEventValue": "6000",
"tokenPriceEventValue": "5.241203173168192",
"txHash": "0xd5e526527f5a84f35abacc434dceb4e8895363e733c4fb10df3e92b9651b9bdd",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37"
},
{
"eventFrom": "0x90b32e5408b9ea6336472c8367dfd349a0c28c28",
"eventTo": "0x9c5de3ad97b95a0da09fd0de84c347db450cd75c",
"eventValue": "6632550023971604865425",
"decimalAdjustedEventValue": "6632.550023971605",
"tokenPriceEventValue": "5.418116584137487",
"txHash": "0x39f7facc6a958e8bc1c3accfe59ee28bba2a99d50696c8d0222b7599a0330a4f",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37"
}
]
},
"allApprovalEvents": {
"nodes": [
{
"eventOwner": "0x90b32e5408b9ea6336472c8367dfd349a0c28c28",
"eventSpender": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
"eventValue": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
"txHash": "0xde43a70c0698a74beea13daa78580139d620c2e38a24b81c76bc8974d15fc6b4",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37"
},
{
"eventOwner": "0x90b32e5408b9ea6336472c8367dfd349a0c28c28",
"eventSpender": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
"eventValue": "115792089237316195423570985008687907853269984665640564031457551408017027336409",
"txHash": "0x7f8b91414e74afa25c4bcbcec295123d52a64a3f3344792fc8818ca6eedad3e7",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37"
},
{
"eventOwner": "0x90b32e5408b9ea6336472c8367dfd349a0c28c28",
"eventSpender": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
"eventValue": "115792089237316195423570985008687907853269984665640564025457551408017027336409",
"txHash": "0xd5e526527f5a84f35abacc434dceb4e8895363e733c4fb10df3e92b9651b9bdd",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37"
},
{
"eventOwner": "0x90b32e5408b9ea6336472c8367dfd349a0c28c28",
"eventSpender": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
"eventValue": "115792089237316195423570985008687907853269984665640564018825001384045422470984",
"txHash": "0x39f7facc6a958e8bc1c3accfe59ee28bba2a99d50696c8d0222b7599a0330a4f",
"chainId": "1",
"contractAddress": "0x5a666c7d92e5fa7edcb6390e4efd6d0cdd69cf37"
}
]
}
}
}
The results of both queries in one response.
Query Batching
You could also batch the queries together in your request and submit it as a batched query request.
Your POST
payload will look like this:
[
{
"query": "query TransferData {\n allTransferEvents(\n condition: { eventFrom: \"0x90b32e5408b9ea6336472c8367dfd349a0c28c28\" }\n ) {\n nodes {\n eventFrom\n eventTo\n eventValue\n decimalAdjustedEventValue\n tokenPriceEventValue\n txHash\n chainId\n contractAddress\n }\n }\n}",
"operationName": "TransferData"
},
{
"query": "query ApprovalEvents{allApprovalEvents(\n condition: {eventOwner: \"0x90b32e5408b9ea6336472c8367dfd349a0c28c28\"}\n ) {\n nodes {\n eventOwner\n eventSpender\n eventValue\n txHash\n chainId\n contractAddress\n }\n }\n}",
"operationName": "ApprovalEvents"
}
]
Example cURL
curl --location --request POST 'https://dep-jekbi9rdfyi7tuqw-graphql.prod.unmarshal.com/marshether_oklzj/graphql' \
--header 'Content-Type: application/json' \
--data-raw '[
{
"query": "query TransferData {\n allTransferEvents(\n condition: { eventFrom: \"0x90b32e5408b9ea6336472c8367dfd349a0c28c28\" }\n ) {\n nodes {\n eventFrom\n eventTo\n eventValue\n decimalAdjustedEventValue\n tokenPriceEventValue\n txHash\n chainId\n contractAddress\n }\n }\n}",
"operationName": "TransferData"
},
{
"query": "query ApprovalEvents{allApprovalEvents(\n condition: {eventOwner: \"0x90b32e5408b9ea6336472c8367dfd349a0c28c28\"}\n ) {\n nodes {\n eventOwner\n eventSpender\n eventValue\n txHash\n chainId\n contractAddress\n }\n }\n}",
"operationName": "ApprovalEvents"
}
]'
Edges and Fragments
When writing deep and large enough queries you might have situation where the same node
is queried multiple times. In these cases, edges
and their accompanying Fragments
may be important.
query TransferData {
userInFrom: allTransferEvents(
condition: {eventFrom: "0x90b32e5408b9ea6336472c8367dfd349a0c28c28"}
) {
edges {
...TransferEventsEdgeFragment
}
}
userInTo: allTransferEvents(
condition: {eventTo: "0x90b32e5408b9ea6336472c8367dfd349a0c28c28"}
) {
edges {
...TransferEventsEdgeFragment
}
}
}
fragment TransferEventsEdgeFragment on TransferEventsEdge {
node {
eventFrom
eventTo
eventValue
txHash
decimalAdjustedEventValue
tokenPriceEventValue
}
}
Unmarshal uses Postgraphile
under the hood so feel free to take a look at their documentation to make the most of your data.
About Unmarshal
Unmarshal is a Multi-chain Web 3.0 data network aiming to deliver granular, reliable & real-time data to dApps, DeFi protocols, NFTs, Metaverse and GameFi solutions. Unmarshal provides the easiest way to query Blockchain data from Ethereum, Polygon, BNB Chain, Avalanche, Fantom, Celo, Solana, Klaytn, Kadena, Arbitrum, and XDC Network. Unmarshal network consists of data indexers and transforming tools to power Web 3.0 applications on any chain while providing a latent view of transformed data.