Querying EVM-based smart contracts with GraphQL

GraphQL is an extremely powerful tool on Geth. Compared to JSON-RPC, it is much more lightweight and resource-efficient for data querying. That is why more and more developers are using GraphQL nowadays.

However, few developers know that they not only can query data with GraphQL, but they can also query smart contracts just like using eth_call.

In this tutorial, you will be guided step-by-step through this process and learn how to interact with a smart contract using GraphQL.

Prerequisite

A Chainstack dedicated Ethereum node.

GraphQL is available on dedicated Ethereum nodes on Chainstack, starting from the Growth plan. Follow this guide to set up your own dedicated node.

Once the node is ready, the GraphQL endpoint can be found in your console:

Querying a smart contract

The first thing we need is a smart contract. Open Etherscan, search for 0xc2edad668740f1aa35e4d8f227fb8e17dca888cd.

This is the smart contract for SushiSwap. It will be used as an example in this tutorial.

Scroll down to the lower part of the webpage, there are several tabs for viewing and interacting with the smart contract. Navigate to Contract > Read Contract. In this tutorial, we focus on the poolInfo method.

The poolInfo method returns the liquidity pool state of SushiSwap. For example, if the input is 1, it returns the current state for the USDC-WETH pool.

The smart contract method is written in Solidity, it cannot be used in GraphQL directly. We need its raw transaction payload in the format of:

{ 
  From:”0x*****************”, 
  To:” 0x*****************”, 
  Data:” 0x*****************” 
}

Calling a smart contract is equivalent to sending a transaction on Ethereum, a from address, a to address, and a data"field must be included in the request body.

poolInfo is a read function, so its from address is just a null address:

0x0000000000000000000000000000000000000000

Its to address is the signature of this smart contract:

0xc2EdaD668740f1aA35E4D8f227fB8E17dcA888Cd

The data field specifies which function to call, and the arguments for the target function.

To target poolInfo(uint256), open an online Keccak-256 hash tool, and enter poolInfo(uint256).

From the resulting string, copy the first 8 characters, which is 1526fe27.

This is the first part of the data field, it represents the method to call.

poolInfo takes a 256 bits unsigned integer as its argument, but the request payload is in hexadecimal format. To transform 256 bits uint into hexadecimal, every 4 bits of uint is mapped to one hexadecimal character. For example:

uint256(1) = bit(0001) =
0x0000000000000000000000000000000000000000000000000000000000000001
uint256(16) = bit (1000) =
0x0000000000000000000000000000000000000000000000000000000000000010
uint256(10) = bit (0110) =
0x000000000000000000000000000000000000000000000000000000000000000A

The final result contains 64 characters with 63 0s and a 1 at the end.

Combining the hexadecimal representation of method string and argument string(s), you have:

0x1526fe270000000000000000000000000000000000000000000000000000000000000001

This is the final input for data field.

{ 
from:"0x0000000000000000000000000000000000000000", 
to:"0xc2edad668740f1aa35e4d8f227fb8e17dca888cd",
data:"0x1526fe270000000000000000000000000000000000000000000000000000000000000001"
}

For more information on Solidity data mapping, reference here.

In GraphQL

Open the GraphQL UI endpoint in the browser, the URL should be in the format of:

https://xxxxxxxx/graphql/ui

In the left pane enter:

{ 
  block
  {
    call(data:{ 
        from:"0x0000000000000000000000000000000000000000", 
        to:"0xc2edad668740f1aa35e4d8f227fb8e17dca888cd",
        data:"0x1526fe270000000000000000000000000000000000000000000000000000000000000001" }) 
    {data status gasUsed} 
  } 
}

Click the run button. The result shows immediately in the right pane.

GraphQL can be used to query one or several older blocks too. This allows users to see the liquidity pool in the past. For example:

{ 
  block(from:15017490, to:15017494)
  {
    call(data:{ 
        from:"0x0000000000000000000000000000000000000000", 
        to:"0xc2edad668740f1aa35e4d8f227fb8e17dca888cd",
        data:"0x1526fe270000000000000000000000000000000000000000000000000000000000000001" }) 
    {data status gasUsed} 
  } 
}

Simple as this:

If the message shows missing trie node, that is because the block is no longer available. You need an archive node access the chain state that is 128+ blocks in the past. See also EVM nodes: A dive into the full vs. archive mode.

In this case, all you need to do is query a new block or switch to an archive node.

Conclusion

This is the end of this tutorial. Thanks for reading.

If you have any questions, feel free to ping me on Twitter/Telegram/Discord.

Happy coding.

Cheers!

  • Connect to the Ethereum, Polygon, BNB Smart Chain, Avalanche, Fantom, Solana, Harmony, Tezos and StarkNet mainnet or testnets through the interface designed to help you get the job done.
  • Get access to the Ethereum, Polygon, BNB Smart Chain, Avalanche, Fantom, and Tezos archive nodes to query the entire history of the mainnet—starting at just $49 per month.
  • Choose where you want to deploy, and we will provide you with the dedicated managed infrastructure that can handle high-volume, high-velocity read/write access to the network.
  • To learn more about Chainstack, visit our Knowledge Center or join our Discord server and Telegram group. 

Have you already explored what you can achieve with Chainstack? Get started for free today.

Chainstack uses cookies to provide you with a secure and
personalized experience on its website. Learn more.