Writing Advanced GraphQL Queries with the Unmarshal Parser

A Comprehensive Guide

Unmarshal
8 min readDec 9, 2022

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

The GraphQL tab can be seen in your sidebar

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

  1. Holds documentation
  2. List of previously tried queries
  3. A refresh button to refresh your schema
  4. Opens up a list of available shortcuts
  5. Settings to change your theme
  6. The Editor
  7. The run button to test your queries
  8. Prettify and format your queries
  9. Merges multiple segments into one query
  10. Copy the query
  11. Add another editor tab
  12. 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.

Website|Telegram Chat|Telegram Ann|Twitter|Medium|Discord

--

--

Unmarshal

The Most Advanced Blockchain Data Infrastructure #⃣ http://xscan.io: MultiChain Explorer http://unmarshal.io/parser: No Code Smart Contract Indexing