How to decode an eth_call response
Learn how to decode the overwhelmingly large hex strings returned by the eth_call method.
How to decode an eth_call response
Learn how to decode the overwhelmingly large hex strings returned by the eth_call
method.
Introduction
The eth_call
method is commonly used to call the read-only functions of smart contracts, like reading the owner off an NFT smart contract. The values returned by these functions are in the hex format. To convert these values to a human-readable format, we need to decode them. In this guide, we will show you how to decode the responses of the eth_call
method so that you can easily read them.
Prerequisites
To continue with this guide you need to about the eth_call method and how it works.
Making an eth_call
request
For the sake of this tutorial, we will be using the eth_call
method on an ERC20 token contract (USDT) to get the balance of a particular address and also get the symbol of the token. We will be calling the eth_call method through Alchemy SDK.
It would be way easier to get the the token balance of an address and the symbol of the token using Alchemy’s getTokenBalances
and getTokenMetadata
APIs as you won’t have to deal with decoding the response in that case.
Step 1: Install Node and npm
In case you haven’t already, install node and npm on your local machine.
Make sure that node is at least v14 or higher by typing the following in your terminal:
Step 2: Create an Alchemy app
In case you haven’t already, sign up for a free Alchemy account.
Alchemy’s account dashboard where developers can create a new app on the Ethereum blockchain.
Next, navigate to the Alchemy Dashboard and create a new app. Make sure you set the chain to Ethereum and the network to Mainnet. Once the app is created, click on your app’s View Key button on the dashboard. Take note of the HTTP URL.
The URL will be in this form: https://eth-mainnet.g.alchemy.com/v2/xxxxxxxxx
You will need this later.
Step 3: Create a node project
Let’s now create an empty repository and install all node dependencies.
This will create a repository named eth-call-decoder
that holds all your files and dependencies. Next, open this repo in your favorite code editor. We will be writing all our code in the main.js
file.
In order to get the balance of a token for a wallet address and its symbol, we need to leverage the eth_call method. This will allow us to query USDT contract’s balanceOf
method and the symbol
state variable.
Add the following code to the main.js
file. Here’s an overview of what the code is doing (also explained in comments):
- Imports the required objects from Alchemy SDK.
- Sets the config object (setting API key and the network).
- Makes an
alchemy
variable using the config object for making the calls. - Defines a main function that gets executed when the script runs.
- In the main function it defines the wallet address whose balance we want to query.
- Defines the contract address for the ERC20 token.
- Defines the number of decimals of the ERC20 token.
- Defines the abi for the ERC20 token that will be used for making an
eth_call
request. - Creates function call data for getting the symbol and balance using the abi and Utils object of Alchemy SDK.
- Makes an
eth_call
request using Alchemy SDK for getting the symbol value in the hex format. - Makes an
eth_call
request using Alchemy SDK for getting the balance value in the hex format. - Logs the hex values of both symbol and balance.
Run the script using the following command:
If all goes well, you should see an output that looks like this:
As you can see that these are the hex strings and not readable by us humans. So, let’s learn how to decode these hex strings so that we can read them easily!
Decoding the response
There are two ways to decode an eth_call
response depending upon the return type of the call. Two key data types get passed over the response: quantities (integers, numbers) and unformatted byte arrays (account addresses, hashes, bytecode arrays) . Both are passed with a hex encoding but with different requirements for decoding.
Decoding Quantities
In order to format quantities(integers, numbers) into human-readable form, you simply convert the hexadecimal result received from the API response into a decimal.
Furthermore, if it’s the value of an ERC20 token, for example, ERC20 balance of an address. You need to divide the decimal form by the number of decimal places that that particular token has. USDT, for instance, has 6. You can find out how many decimal places any currency has by reading the decimals
value from the contract’s page on Etherscan.
Solidity does not support decimal point numbers. So, If you want an ERC20 token to have the ability to be subdivided with a precision of 2 decimal places, you need to represent 1 token as 100 (set its decimal places to 2). Similarly if I you want an ERC20 token to have the ability to subdivided with a precision of 18 decimal places, you need to represent 1 token as 1000000000000000000 (set its decimal places to 18).
So, the formula for number of tokens becomes:
tokenSupplyWithDecimalZeros = actualTokenSupply * (10 ** decimals)
That is why we need to divide the decimal value by the number of decimal places, to get the actual token value.
Let’s decode the balance hexadecimal string received in our case to demonstrate this. Here’s an overview of what the code is doing (also explained in comments):
- Imports the required objects from Alchemy SDK.
- Sets the config object (setting API key and the network).
- Makes an
alchemy
variable using the config object for making the calls. - Defines a main function that gets executed when the script runs.
- In the main function it defines the wallet address whose balance we want to query.
- Defines the contract address for the ERC20 token.
- Defines the number of decimals of the ERC20 token.
- Defines the abi for the ERC20 token that will be used for making an
eth_call
request. - Creates function call data for getting the symbol and balance using the abi and Utils object of Alchemy SDK.
- Makes an
eth_call
request using Alchemy SDK for getting the symbol value in the hex format. - Makes an
eth_call
request using Alchemy SDK for getting the balance value in the hex format. - Converts the balance in the hex format to decimal format and logs it.
- Logs the symbol in the hex format.
Run the script using the following command:
You should see an output like this:
As you can see that we have decoded the value of the balance. So the wallet address in our case holds 61.9 Million USDT.
But the value of the symbol will be a string and not a number, so we have to use the other approach to decode its value. Let’s take a look at how to do that.
Decoding Unformatted Data
In order to convert unformatted data(byte arrays, account addresses, hashes, bytecode arrays) into human-readable format, you need to convert its hex
value to utf8
value.
You can convert a hex value to utf8 value using the toUtf8String
method of ethers
or if for some reason you don’t want to use ethers
you can do it with pure javascript, you can use the decodeURIComponent
function in combination with some regex to accomplish this. We’ll look at both ways of doing this.
Decoding using ethers
:
Here’s an overview of what the code is doing (also explained in comments):
- Imports the required objects from Alchemy SDK.
- Sets the config object (setting API key and the network).
- Makes an
alchemy
variable using the config object for making the calls. - Defines a main function that gets executed when the script runs.
- In the main function it defines the wallet address whose balance we want to query.
- Defines the contract address for the ERC20 token.
- Defines the number of decimals of the ERC20 token.
- Defines the abi for the ERC20 token that will be used for making an
eth_call
request. - Creates function call data for getting the symbol and balance using the abi and Utils object of Alchemy SDK.
- Makes an
eth_call
request using Alchemy SDK for getting the symbol value in the hex format. - Makes an
eth_call
request using Alchemy SDK for getting the balance value in the hex format. - Converts the balance in the hex format to decimal format and logs it.
- Converts the symbol value in the hex format to utf8 format, which is readable by humans, and logs it.
Run the script using the following command:
You should see an output like this:
You can see that the symbol of the ERC20 token is USDT. Now we have the decoded value of both the symbol and the balance.
Let’s also take a look at how to decode unformatted data (byte arrays, account addresses, hashes, bytecode arrays) using pure javascript:
Decoding using pure javascript
If for some reason you don’t want to use ethers
for decoding the symbol hex value, here’s the pure javascript way of doing it.
Here’s an overview of what the code is doing (also explained in comments):
- Imports the required objects from Alchemy SDK.
- Sets the config object (setting API key and the network).
- Makes an
alchemy
variable using the config object for making the calls. - Defines a main function that gets executed when the script runs.
- In the main function it defines the wallet address whose balance we want to query.
- Defines the contract address for the ERC20 token.
- Defines the number of decimals of the ERC20 token.
- Defines the abi for the ERC20 token that will be used for making an
eth_call
request. - Creates function call data for getting the symbol and balance using the abi and Utils object of Alchemy SDK.
- Makes an
eth_call
request using Alchemy SDK for getting the symbol value in the hex format. - Makes an
eth_call
request using Alchemy SDK for getting the balance value in the hex format. - Converts the balance in the hex format to decimal format and logs it.
- Converts the symbol value in the hex format to utf8 format, which is readable by humans, and logs it.
Run the script using the following command:
And you will see the same output as before:
Congratulations! You now know how to decode an eth_call
response to understand it!