Rust: Interacting with Etherscan API

Pudding Entertainment
11 min readApr 27, 2023
Generated by Bing Image Creator

If you’re a fan of cryptocurrencies, you’ve likely heard of etherscan.io, a popular tool for navigating the world of crypto. But did you know that if you’re working with any L2 solutions based on Ethereum, you’ll eventually need to interact with Etherscan API to get all the details about a given mint event? In this article, we’ll explore how to do just that in Rust. Whether you’re a seasoned crypto pro or just starting out, this guide will help you harness the power of Etherscan and ImmutableX APIs as well as Rust for your next project.

Prerequisites

I’m using Rust version 1.69.0. This tutorial assumes prior knowledge of Rust and concentrates more on practical usage of the language.

I’ll be using the side project I’m actively working on at the moment — illuvi-analytics. The relevant parts of it will be stripped into self-sufficient code snippets, and you are welcome to check the entire project out.

If you are curious to know what this project is about please check out the Background section of my previous tutorial.

There are 4 parts in this tutorial: Ethereum and ImmutableX, Exploring etherscan.io and immutascan.io, Exploring Etherscan and ImmutableX APIs, and Coding

Ethereum and ImmutableX

If you’re well-versed in Ethereum blockchain data storage and have previous experience using the Etherscan API — feel free to skip ahead to the coding section. Otherwise, let me briefly explain how Ethereum uses a distributed ledger to store all transactions on its blockchain. Each block contains a set of transactions validated by the network’s nodes. Every transaction includes a sender address, a recipient address, an amount of Ether or other tokens being transferred and other details. When a transaction is sent, it is broadcasted to the network and validated by nodes in the network, which perform complex mathematical computations to verify the transaction’s authenticity and integrity.
Once a transaction is verified and validated, it is included in the next block in the chain. Each block is connected to the previous block, forming a chain of blocks, hence the term “blockchain”.

In order to achieve faster and cheaper transactions, while still maintaining the same level of security and decentralization as the main Ethereum network a Layer 2 scaling solution, such as ImmutableX, were developed. ImmutableX uses a technology called zk-rollups to bundle multiple transactions into a single transaction on the Ethereum main chain, reducing the gas fees and increasing the transaction throughput. When a user initiates a transaction on ImmutableX, the transaction is verified by the ImmutableX validators and then included in a zk-rollup bundle. This bundle is then submitted to the Ethereum main chain, where it is verified and added to the Ethereum blockchain. The Ethereum blockchain then records the state of the zk-rollup bundle, which contains the state changes of all the transactions in the bundle.

Lastly, there are also smart contracts that are self-executing computer programs stored on the Ethereum blockchain. When a contract is deployed on the blockchain, it is assigned a unique address that can be used to interact with it. Once deployed, the contract’s code is immutable, meaning that it cannot be changed or updated. Since contracts are executed on a decentralized network of nodes, they are trustless and transparent. Anyone can read the code of a contract and verify its behavior, and once deployed, the contract will execute exactly as programmed without the need for any central authority or intermediary.

Now that you have a short intro into the cryptocurrency world, let’s move onto the next chapter, where we will take a deeper look into etherscan.io and immutascan.io to help us better understand the concept.

Exploring etherscan.io and immutascan.io

As mentioned earlier, our goal is to retrieve all relevant information about an Illuvium land mint event that occurred on ImmutableX. Fortunately, there is a website called immutascan.io that allows us to visualize all the minted tokens. To keep things simple we will concentrate on one particular NFT with the token id 46378.

To begin our search for the mint event details, we first need to locate the corresponding transaction on Etherscan. This can be done by checking the wallet activity that minted the NFT, which can be found on this page. Specifically, we’re looking for a successful transaction that uses the Buy L2 method. We found only two such transactions, so let’s dive into them further to see which one is the right one.

When viewing a given transaction, it may not be immediately apparent which NFT was purchased. To find this information, you need to locate the “More Details” section, open it, and find the “Input Data” section. Decoding the input data will reveal that the transaction was executed for the function buyL2 with a tokenId value of 46378. This is exactly the information we were searching for.

Let’s take a closer look at the transaction details on Etherscan. If you scroll up a bit, you’ll see two other fields called “ERC-20 Tokens Transferred” and “Value”. In this transaction, the “Value” field shows 0 ETH, while “ERC-20 Tokens Transferred” shows 8.2217 sILV2. It is this way because during the Illuvium Land sale, buyers had the option to use either sILV2 or ETH to purchase the land. It’s important to keep this in mind when searching for the appropriate API and writing the code.

Note! Just for reference, that’s how an ETH transaction looks like.

Before moving on to the APIs I wanted to briefly talk about that Input Data.
In Ethereum, the data sent along with a transaction is encoded using the ABI (Application Binary Interface) encoding. The input data section in a transaction represents the encoded function call data, which includes the function signature and the function arguments.
As you might have already noticed an encoded version of this field looks rather unreadable:

We will get back to this later in the Coding part where we will need to decode it in Rust.

Now that we know which data to look for, let’s go ahead and find the corresponding APIs to retrieve it.

Exploring Etherscan and ImmutableX APIs

The reason we even need to explore the Etherscan API is that at the moment of writing, the ImmutableX API does not offer a way to fetch the mint price. However, to retrieve all minted assets, we still need to start at ImmutableX. With the help of listMints API with the specified token_address parameter, we can fetch all the minted assets. That will serve as a starting point for the Coding part.

Let’s dive into the Etherscan API. Before we get started, make sure you’ve created an account and obtained your API key. Once you have that, we can start exploring the available endpoints. However, I must warn you that Etherscan’s API structure can be a bit counter-intuitive. For instance, you might expect to find transaction-related operations under the Transactions section(which for some reason has stats in its URL), but they’re actually located under the Accounts section.
Here we will be exploring two APIs: “Get a list of ‘Normal’ Transactions By Address” for when the land was purchased with ETH, and “Get a list of ‘ERC20 — Token Transfer Events’ by Address” for when the land was purchased with sILV2. It’s important to note that both APIs are limited to 10,000 records, which can be specified with the offset parameter. If the response exceeds this limit, it will be paginated and can be retrieved using the page parameter. Additionally, the startblock parameter can significantly reduce the overhead of the API call on the server side. Since we know that the Land contract was created in block 14846665, it should be specified in the API call.

Now that the APIs are defined, it’s time to start coding. In the next section, I will outline all the specifics of using both APIs, along with the Rust implementation.

Coding

To begin, let’s clarify the technologies that will be employed in this section. The application will run on tokio runtime, while the reqwest crate with json feature enabled will handle requests and responses processing. Relevant environment variables will be retrieved with dotenvy. And requests will be executed in parallel with futures. If you haven’t already, I recommend checking out my previous tutorial on Rust: How to consume REST API and persist results in Postgres, where I dive into the code with greater detail.

Now, let’s delve into the APIs themselves, we will begin with ImmutableX.
As it was mentioned earlier, we will be calling the listMints API with the specified token_address parameter. Since we have a few API calls to make, all of which are returning a JSON body, let’s create a helper script to facilitate that:

Skipping the nitty-gritty details of the ImmutableX API contract, here is the mint model.

We will use only two fields of it — the user (aka wallet) and token_id.

Ready to fetch those mints? Just be aware that the ImmutableX API uses pagination, so for simplicity’s sake, we’ll focus on the first 200 responses.

Although it’s not strictly necessary, I prefer to convert the Result into an Option here, as it keeps the error handling closer to the point of the function call, rather than propagating it all the way up to the top level.

Note! in a real-world application, it would be best to store the entire API response in a database to minimize the number of calls to the third-party API.

Now, with the mints successfully fetched from the ImmutableX API, we can move on to the most interesting part of this project: using the Etherscan API to retrieve the price paid by the land buyer. As a quick reminder, we’re only able to retrieve this information from the L1 blockchain.
In the previous chapter, we established that there are two possible scenarios to consider: the simpler scenario when the land was purchased with ETH, and the more complex scenario that requires an additional API call when the land was purchased with sILV2.
But both of them require to fetch the transactions first, thus we should begin with defining the model for this API call

Let’s closely examine the response model for the Etherscan API. There are several important fields to consider. Firstly, a successful response is indicated by a status value of 1. Secondly, the value field provides the price paid for the transaction, excluding gas. Thirdly, the to field shows where the transaction was sent to. Finally, the input field contains all the necessary information about the function call, which is the same data that we previously saw in the “Input Data” section on Etherscan. Specifically, we’ll extract the token_id from this field.

With the transaction model defined, it’s time to move on to the implementation. Let’s start with the simpler scenario when the land was bought with ETH.

Let me walk you through the code in detail. First, we use the previously created mint_reader to fetch the mints to process in parallel. We set the buffer_unordered value to 3, as the Etherscan API Free Plan limits requests to 5 per second. We then query the API page by page for the given wallet, processing each page as we go. When the response status is not 1, there is no more data to fetch. As previously mentioned, we set the startblock parameter explicitly to reduce the load on the Etherscan backend and get a faster response.

For each transaction, we need to check three conditions: it should be successful (is_error != 1), it should be sent to the Land contract and the function_name should have the expected name. Then, we need to decode the input data to retrieve the token_id. To do this, we need to know the function signature, which consists of the function name followed by the types of its arguments in parentheses. For example, the function signature for buyL2 is buyL2(tuple plotData,bytes32[] proof), where tuple has the following parameters: (uint32,uint32,uint8,uint16,uint16,uint8,uint16). You can find this information on the Etherscan website. We can use the ABI encoding rules to decode the input data, which specify how to represent different data types such as integers, strings, arrays, and structs. Fortunately, we don’t need to implement this part ourselves because there is a crate with the required functionality — ethabi. Our job here is to provide it with the correct data set and parse the response to retrieve the token_id.
Finally, if the value field is not 0, it means the land was purchased with ETH, and we can match it with the token_id and persist the price.

Now that we have implemented the simpler scenario with ETH transactions, we can cover approximately 17% of all land mints. However, we don’t stop here. Let’s move on to the more complex scenario where we need to call the tokens API. To start with, we define the necessary model as follows:

This model is simpler compared to the transaction model, but there’s an interesting detail to note. The value field returns a whole number, and the position of the decimal point is indicated in a separate field called tokenDecimal. To obtain the actual price value, we will need to perform some arithmetic operations.
Let’s go ahead and fetch the remaining data.

I have removed some code and provided the relevant parts for clarity. To minimize API calls, we save transaction hashes for token_ids that we need to fetch later. The usage of this API is similar to the transaction API, so the information from the previous section is still applicable. Whenever there is a transaction hash match, we calculate the price in the currency specified in tokenSymbol and store it. With both implementations in place, we can cover all the mint events for the first batch of Illuvium Land.

Note! There are actually a few missing transactions that I wasn’t able to find on Etherscan, if you are curious to learn more — check out this forum thread.

Afterwards

Congratulations, you’ve made it to the end of this tutorial! By following along, you’ve gained a solid understanding of how to interact with the Ethereum blockchain through Etherscan and ImmutableX APIs and how to extract valuable data from it.

And with the power of Rust, this solution is able to process a large number of land mints efficiently and accurately. By combining data from these two APIs, we were able to cover all possible scenarios of land purchases on the first batch of Illuvium Land.

Moreover, this implementation is just the tip of the iceberg when it comes to utilizing the power of Rust and blockchain technology. Rust’s safety and performance features make it an ideal language for developing robust and secure blockchain applications. As the blockchain ecosystem continues to grow and evolve, Rust’s role in this space will undoubtedly become more prominent.

Keep learning, keep building, and keep exploring the exciting world of blockchain technology. Its possibilities are endless.

Support

If you like the content you read and want to support the author — thank you very much!

Here is my Ethereum wallet for tips:

0xB34C2BcE674104a7ca1ECEbF76d21fE1099132F0

--

--

Pudding Entertainment

Serious software engineer with everlasting passion for GameDev. Dreaming of next big project. https://pudding.pro