Module handler creation
StreamingFast Substreams module handler creation
Module handler creation overview
After generating the ABI and protobuf Rust code, you need to write the handler code. Save the code into the src
directory and use the filename lib.rs
.
View the lib.rs
file in the repository.
Module handler breakdown
The logical sections of the lib.rs
file are outlined and described in greater detail.
Import the necessary modules.
Store the tracked contract in the example in a constant
.
Define the map
module in the Substreams manifest.
Notice the: name: map_transfers
, the module in the manifest name matches the handler function name. Also notice, there is one inputs
and one output
definition.
The inputs
uses the standard Ethereum Block, sf.ethereum.type.v2.Block,
provided by the substreams-ethereum
crate.
The output uses the type
proto:eth.erc721.v1.Transfers
which is a custom protobuf definition provided by the generated Rust code.
The function signature produced resembles:
Rust macros
Did you notice the #[substreams::handlers::map]
on top of the function? It is a Rust macro provided by the substreams
crate.
The macro decorates the handler function as a map.
Define store
modules by using the syntax #[substreams::handlers::store]
.
Module handler function
The map
extracts ERC721 transfers from a Block
object. The code finds all the Transfer
events
emitted by the tracked smart contract. As the events are encountered they are decoded into Transfer
objects.
Define the store
module in the Substreams manifest.
Note: name: store_transfers
corresponds to the handler function name.
The inputs
corresponds to the output
of the map_transfers
map
module typed as proto:eth.erc721.v1.Transfers
. The custom protobuf definition is provided by the generated Rust code.
Note: the store
always receives itself as its own last input.
In the example the store
module uses an updatePolicy
set to add
and a valueType
set to int64
yielding a writable store
typed as StoreAddInt64
.
Note: Store types
The writable
store
is always the last parameter of astore
module function.The
type
of the writablestore
is determined by theupdatePolicy
andvalueType
of thestore
module.
The goal of the store
in the example is to track a holder's current NFT count
for the smart contract provided. The tracking is achieved through the analysis of Transfers
.
Transfers
in detail
If the "
from
" address of thetransfer
is thenull
address (0x0000000000000000000000000000000000000000
) and the "to
" address is not thenull
address, the "to
" address is minting a token, which results in thecount
being incremented.If the "
from
" address of thetransfer
is not thenull
address and the "to
" address is thenull
address, the "from
" address has burned a token, which results in thecount
being decremented.If both the "
from
" and the "to
" address is not thenull
address, thecount
is decremented from the "from
" address and incremented for the "to
" address.
store
concepts
store
conceptsThere are three important things to consider when writing to a store
:
ordinal
key
value
ordinal
ordinal
ordinal
represents the order in which the store
operations are applied.
The store
handler is called once per block.
The add
operation may be called multiple times during execution, for various reasons such as discovering a relevant event or encountering a call responsible for triggering a method call.
Note: Blockchain execution models are linear. Operations to add must be added linearly and deterministically.
If an ordinal
is specified, the order of execution is guaranteed. In the example, when the store
handler is executed by a given set of inputs
, such as a list of Transfers
, it emits the same number of add
calls and ordinal
values for the execution.
key
key
Stores are key-value stores. Care needs to be taken when crafting a key
to ensure it is unique and flexible.
If the generate_key
function in the example returns the TRACKED_CONTRACT
address as the key
, it is not unique among different token holders.
The generate_key
function returns a unique key
for holders if it contains only the holder's address.
Important: Issues are expected when attempting to track multiple contracts.
value
value
The value being stored. The type
is dependent on the store
type
being used.
Summary
Both handler functions have been written.
One handler function for extracting relevant transfers
, and a second to store the token count per recipient.
Build Substreams to continue the setup process.
The next step is to run Substreams with all of the changes made by using the generated code.
Last updated