Setting Up Handlers
Now that we generated our Protobuf Rust code, let's initiate our Rust project and generate some code to set us up to write our handlers:
# This is to create a barebones rust project
cargo init
# Since we are building a library we need to rename the newly generated
mv ./src/ ./src/
Let's edit the newly created Cargo.toml file to look like this:
name = "substreams-example"
version = "0.1.0"
description = "Substream template demo project"
edition = "2021"
repository = ""
crate-type = ["cdylib"]
ethabi = "17.0"
hex-literal = "0.3.4"
prost = "0.10.1"
# Use latest from
substreams = "0.0.12"
# Use latest from
substreams-ethereum = "0.1.2"
# Required so that ethabi > ethereum-types build correctly under wasm32-unknown-unknown
getrandom = { version = "0.2", features = ["custom"] }
anyhow = "1"
substreams-ethereum = "0.1.0"
lto = true
opt-level = 's'
strip = "debuginfo"
Let's go through the important changes. Our Rust code will be compiled in wasm. Think of wasm code as a binary instruction format that can be run in a virtual machine. When your Rust code is compiled, it will generate a .so file.
Let's break down our Cargo.toml file
Since we are building a Rust dynamic system library, after the package, we first need to specify:
crate-type = ["cdylib"]
We then need to specify our dependencies. We specify explicitly the wasm32-unknown-unknown (using [target.wasm32-unknown-unknown.dependencies]) target since our handlers compile down to a WASM module:
  • ethabi: This crate will be used to decode events from your ABI, required for substreams-ethereum ABI functionalities.
  • hex-literal: This crate will be used to define bytes from hexadecimal string literal at compile time.
  • substreams: This crate offers all the basic building blocks for your handlers.
  • substreams-ethereum: This crate offers all the Ethereum constructs (blocks, transactions, eth) as well as useful ABI decoding capabilities.
Since we are building our code into wasm, we need to configure Rust to target the correct architecture. Add this file at the root of your Substreams directory
channel = "1.60.0"
components = [ "rustfmt" ]
targets = [ "wasm32-unknown-unknown" ]
We can now build our code
cargo build --target wasm32-unknown-unknown --release
Rust Build Target
Notice that when we run cargo build we specify the target to be wasm32-unknown-unknown. This is important, since the goal is to generate compiled wasm code. You can avoid having to manually specify --target wasm32-unknown-unknown for each cargo command by creating a file named config.toml under folder .cargo at the root of your project with the following content:
target = "wasm32-unknown-unknown"
With this config file, cargo build is now equivalent to cargo build --target wasm32-unknown-unknown.

ABI Generation

In order to make it easy and type-safe to work with smart contracts, the substreams-ethereum crate offers an Abigen API to generate Rust types from a contract's ABI.
We will first insert our contract ABI json file in our projects under an abi folder.
Now that we have an ABI in our project, let's add a Rust build script.
Rust Build Script
Just before a package is built, Cargo will compile a build script into an executable (if it has not already been built). It will then run the script, which may perform any number of tasks.
Placing a file named in the root of a package will cause Cargo to compile that script and execute it just before building the package.
We will create a file in the root of our Substreams project:
use anyhow::{Ok, Result};
use substreams_ethereum::Abigen;
fn main() -> Result<(), anyhow::Error> {
Abigen::new("ERC721", "abi/erc721.json")?
We will run the build script by building the project
cargo build --target wasm32-unknown-unknown --release
You should now have a generated ABI folder src/abi. Next, we will create a file in that folder to export the generated Rust code
pub mod erc721;
We can now write our Rust handlers!
Copy link