JSON RPC, methods, calls, requests - what does it all mean?! When you start building a dapp on the Ethereum blockchain, you’re introduced to a host of new concepts, request methods and naming conventions to employ - it can be overwhelming. The Infura team are experts in web3 infrastructure. We build open source tools and materials to help more developers interact with Ethereum and IPFS. In this tutorial, we leverage the collective experience of our team to bring you an in-depth guide to reading and writing requests to the Ethereum blockchain, using Infura.

Key Terms and Concepts

Before jumping into the meat of this tutorial, let’s clarify some key terminology. Below is a list of important terms and their corresponding definitions that you need to get familiar with.

Request

A developer interacts with Infura by sending a request through JSON RPC either over HTTP or WebSocket; as a single request or as a batched array. Requests follow this format:

{"id":1234, "method": "eth_foo", "params":[...] }

Because you should be posting this to an endpoint that has your v3 key in it, we can always associate these requests with a key or a project.

Method

The method is essentially what function we are asking an Ethereum node to execute. This will either request data from the node, execute an EVM function and return a response, or transmit data to the Ethereum network (send a transaction). Currently there are 65 methods which are supported by the popular Ethereum clients (Geth, Parity, Besu, Nethermind):

The most commonly used methods are highlighted in bold and comprise the vast majority of Infura’s traffic:

These methods can be grouped into the following categories:

i) Block Data Related

  • eth_getBlockByHash: retrieves a block's details by its hash (a string representing the 32-byte hash of a block);
  • eth_getBlockByNumber: retrieves a block's details by its number (an integer block number, or the string "latest", "earliest" or "pending");
  • eth_blockNumber: retrieves the current block number.

These methods retrieve static data about a particular block in the blockchain. This data is contained in the blockchain headers and could include information like block number or transactions mined, for example.

ii) Per User (address) Data

  • eth_getTransactionCount: retrieves the nonce of an address (i.e: how many transactions have already been mined, per user, for that address). This is needed because when you go to send a new transaction, there’s a field called ‘the nonce’ which is intended to prevent you from accidentally rebroadcasting transactions and sending them out in an order that you didn’t intend.

This method retrieves the simple state data for addresses. It doesn’t matter whether that address is a user or a smart contract. Any address can have an ETH balance.

iii) Reading Existing Smart Contract Data

The concept of smart contracts, and interacting with them, is what makes Ethereum different from other blockchains. A smart contract is two things:

  1. It’s a bit of code, usually written in Solidity, that compiles down into byte code - which is understood by the Ethereum Virtual Machine (EVM);
  2. It’s a means of storage. Think of it as halfway between ‘disc storage’ and ‘ram.’

Below are the most common methods for interacting with smart contracts:

  • eth_call: retrieves a constant EVM method on a Smart Contract and is the primary way to retrieve already mined data from the blockchain about a particular smart contract;
  • eth_getLogs: retrieves events published by smart contract transactions. Smart contracts can emit logs that are really user defined, so when you’re creating this smart contract you can decide the format of these log events. These dapps can then see when your smart contract has emitted these events, and respond accordingly;
  • eth_getStorageAt: retrieves raw state storage for a smart contract;
  • eth_getCode: retrieves the compiled EVM code for a smart contract. There is no copy of a smart contract’s source code on the blockchain itself. So, for example, when you search an address on Etherscan and the verified code of that smart contract is returned, that all happens off chain.

Using eth_call

ERC20 is one standard for how developers can create a token. Almost all wallet software understands this standard, so if you want to create a token and make it easier for people to interact with it, ERC20 is a good place to start.
Eth_call is the most commonly used method for interacting with smart contracts, particularly ERC20 contracts. A typical eth_call request looks like this:

At first glance, it’s not clear what any of this information represents. Let’s break it down:

The "id":1 field is an identifier string which is set by the requester, and helps make sure the client and server are communicating in the same context.

The "jsonrpc":"2.0" field specifies the version of the protocol and must be exactly “2.0”, which the protocol expects.

The from field is the data requester, which could be any wallet address or smart contract. This field is optional.

The to field is always a smart contract. There are two ways to find out what that smart contract is:

  1. You can make a request on the blockchain using eth _getCode, but that would give you the compiled code. Unless you have tools to reverse-engineer the EVM byteCode, you’re not going to immediately know what that means.
  2. You can type the smart contract address into Etherscan.
    Searching Etherscan is the most common way to find out what type of smart contract you’re dealing with. Let’s take the smart contract address 0x6B175474E89094C44Da98b954EedeAC495271d0F and type it in to see what happens:

Etherscan tells us that this smart contract is for the ‘Dai Stablecoin (DAI)’. At this point, we also have the opportunity to inspect the original Solidity code for this contract and confirm that it’s verified:

Great! Now we know that this address represents an ERC20 contract.

Deep-Dive into ERC20

Created in 2015, ERC20 is a relatively simple token standard and the most common contract type on Ethereum. Within this standard, an ERC20 token has to implement the following functions:

  • function totalSupply() public constant returns (uint);
  • function balanceOf(address tokenOwner) public constant returns (uint balance);
  • function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
  • function transfer(address to, uint tokens) public returns (bool success);
  • function approve(address spender, uint tokens) public returns (bool success);
  • function transferFrom(address from, address to, token uint) public returns (bool success).

At certain times, an ERC20 token also has to emit the following events:

  • event Transfer(address indexed from, address indexed to, uint tokens);
  • event Approval(address indexed tokenOwner, addressed indexed spender, uint tokens).

You can view the standard on GitHub for more detail about how these functions work and when to emit these events. For now, let’s try to request the balance for some of the DAI tokens.

We know that our address is 0xDfBaf3E4c7496DAd574a1B842bC85B402BDC298D.

We also know that the DAI contract address is 0x6B175474E89094C44Da98b954EedeAC495271d0F.

To query our balance using eth_call, we use the balanceOf function of the token contract. The JSON-RPC format expects eth_call to have a specific data field format that requires normalizing the contract function balanceOf to a short Function Selector. To do this, we take out the names and the type of inputs it takes, and take that as a string. We then run it through Ethereum’s sha3 keccak hash:

web3_sha3(“balanceOf(address)”)[0..4]-> 0x70a08231

The first four bytes of this hash comprise it’s 4-byte signature. We then take this 4-byte signature, pad it with zeros, add our address as an input and send the eth_call:

This is a hexadecimal number, padded to a 32-byte length with zeroes. If we want to, we can convert this output to a decimal number by using a hex-to-decimal converter. This would return the result 3.890227426645114e+24, which is scientific notation for 3.890227426645114 * 10^24. This is the contract’s balance of Dai in wei, a standard unit of 10^18.

So finally, divide 3.890227426645114 * 10^24 by 10^18, and ta-da! Now we know the balance of the wallet address 0xDfBaf3E4c7496DAd574a1B842bC85B402BDC298D in this contract is 3,890,227.426645114 Dai.

Sending Transactions

Eth_calls are commonly used to read data from a smart contract. But what if you want to write data to a smart contract?

Let’s say we wanted to call the transfer function. Again, we would need to normalize the function first. We take the string transfer(address to, uint tokens) and hash it to get the 4-byte signature. Again, we take that 4-byte signature, pad it with zeros, insert the address and the amount of tokens, and package this information into a data string. We then sign the transaction and send it using eth_sendRawTransaction.

At this point, nodes start broadcasting our transaction out to the rest of the network. Eventually a miner will get a copy of it, go through all their proof-of-work stuff, get lucky and find a hash that matches the proof-of-work and broadcast a block that contains our transaction. Every node that receives this block reads the transactions within it, re-executes them, and holds our ERC20 token transaction in the EVM to update.  

Gas is sent along with any other values, and is spent running the EVM code. The miner gets the block reward, plus all the gas spent in transactions. If you’d like to learn more about how Ethereum mining works, these EthHub docs are a great start.

Mined Transactions

When a transaction is mined, two important things happen: the on-chain Ethereum state is changed via contract interactions, and Event Logs are published for public viewing. Once the Event Logs are published, we can execute JSON-RPC requests for eth_getLogs to investigate what changed, relative to the events that we care about, and react to them.

For example: an event ticketing service that wants to issue off-chain tickets based on crypto payments, could use eth_getLogs to find payments to their address, and react to these events by processing some logic in their backend servers to issue tickets to users.

Using eth_getLogs

In the above sample request, we use the parameters "from block" and "to block" to specify the hexadecimal block number that we are interested in retrieving logs from. Note that if this information is omitted, the method will return information from "block 0" to "block latest" by default (the entire chain history) and that’s not ideal. Infura has a cap on requests of 10,000 events per query. Best practice is to request a single block, as we’ve done in this example, and do that for each mined block.

What this request is saying to the blockchain is “hey, I want event logs relating to address 0x6B175474E89094C44Da98b954EedeAC495271d0F emitted in block 0x91F37C (hexadecimal for block 9565052) that matches topics 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000ee25e1ba53c225d250861c8e5a9a3e0fe19c790e and  0x000000000000000000000000dfbaf3e4c7496dad574a1b842bc85b402bdc298d.

The response returned for this request is an array of events. In the example we’re using, only one event for one address matched the specified topics.

Topics

Topics are events emitted by smart contracts. If we take a look at the original DAI ERC20 Solidity contract again 0x6B175474E89094C44Da98b954EedeAC495271d0F, we can see two event signatures that could be associated with it:

  • 94    event Transfer(address indexed from, address indexed to, uint tokens);
  • 95    event Approval(address indexed tokenOwner, address indexed spender, uint tokens).

How do we find out which topic (event) it actually was? You guessed it: we have to create the Function Selector of the Event and take the hash of it. Let’s try the event on line 94:

web3.sha3('Approval(address,address,uint256)')

The resulting hash is 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. This doesn’t match the hash provided in the initial request response. Now let’s try the event on line 95 of the contract:

web3.sha3('Transfer(address,address,uint256)')

Bingo! The resulting hash is 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, which matches the hash provided in the initial request response. Now we know that 0xddf25 is the Transfer event.

More Event logs

You might be wondering what the data provided in the request response means:

In this context, data refers to all the ‘non-indexed stuff’ that’s captured in the events. In our example, for the Transfer topic, data represents the number of tokens that were transferred. That is, that 0x41f900d25d6693623a6 or 19471.6949921 Dai tokens were transferred from ee25e1ba53c225d250861c8e5a9a3e0fe19c790e to dfbaf3e4c7496dad574a1b842bc85b402bdc298d.

This completes our investigation 🎉


View our developer documentation to explore different ways to interact with Infura to read and write Ethereum data. New to Infura? Sign up and get started for free!