Blockchain interactions
Components can interact with blockchains and smart contracts by using crates like wavs-wasi-utils
. This page provides an overview of the dependencies and configuration needed to interact with Ethereum and other EVM-compatible chains.
wavs-wasi-utils
crate
The wavs-wasi-utils
crate provides a set of helpful functions for making HTTP requests and interacting with the blockchain. It also provides a macro for decoding trigger data for use in the component.
Learn more in the crate documentation.
The decode_event_log_data
macro is a utility for decoding Ethereum event logs from triggers into typed Rust events. It takes raw log data (topics and data) from the WAVS worker bindings and converts it into a Rust type that implements SolEvent
.
Dependencies
The following dependencies are commonly required in your component's Cargo.toml
for Ethereum interactions:
[dependencies]# Core WAVS blockchain functionalitywit-bindgen-rt = { workspace = true, features = ["bitflags"] } # Required for WASI bindingswavs-wasi-utils = "0.4.0" # Blockchain interaction utilitieswstd = "0.5.3" # WASI standard library# Alloy crates for Ethereum interactionalloy-sol-macro = { version = "1.1.0", features = ["json"] } # sol! macro for interfacesalloy-sol-types = "1.1.0" # ABI handling & type generationalloy-network = "0.15.10" # Network trait and Ethereum network typealloy-provider = { version = "0.15.10", default-features = false, features = ["rpc-api"] } # RPC provideralloy-rpc-types = "0.15.10" # RPC type definitionsalloy-contract = "0.15.10" # Contract interaction utilities# Other useful cratesanyhow = "1.0.98" # Error handlingserde = { version = "1.0.219", features = ["derive"] } # Serialization/deserializationserde_json = "1.0.140" # JSON handling
Note: The workspace = true
syntax can be used if your project is part of a workspace that defines these versions centrally. Otherwise, use the explicit versions shown above.
Chain configuration
Chain configurations are defined in the root wavs.toml
file. This allows components to access RPC endpoints and chain IDs without hardcoding them.
# Local / Testnet[default.chains.evm.local]chain_id = "31337"ws_endpoint = "ws://localhost:8545"http_endpoint = "http://localhost:8545"poll_interval_ms = 7000# Mainnet[default.chains.evm.ethereum]chain_id = "1"ws_endpoint = "wss://eth.drpc.org"http_endpoint = "https://eth.drpc.org"
Sol! macro
The sol!
macro from alloy-sol-macro
allows you to generate Rust types from Solidity interface files.
You can write Solidity definitions (interfaces, structs, enums, custom errors, events, and function signatures) directly inside the sol!
macro invocation in your Rust code.
At compile time, the sol!
macro parses that Solidity syntax and automatically generates the equivalent Rust types, structs, enums, and associated functions (like abi_encode()
for calls or abi_decode()
for return data/events) needed to interact with smart contracts based on those definitions.
Required Dependencies:
[dependencies]alloy-sol-macro = { workspace = true } # For Solidity type generationalloy-sol-types = { workspace = true } # For ABI handling
Basic Pattern:
mod solidity {use alloy_sol_macro::sol;// Generate types from Solidity filesol!("../../src/interfaces/ITypes.sol");// Or define types inlinesol! {struct TriggerInfo {uint64 triggerId;bytes data;}event NewTrigger(TriggerInfo _triggerInfo);}}
In the template, the sol!
macro is used in the trigger.rs
component file to generate Rust types from the ITypes.sol
file.
mod solidity {use alloy_sol_macro::sol;pub use ITypes::*;// The objects here will be generated automatically into Rust types.// If you update the .sol file, you must re-run `cargo build` to see the changes.sol!("../../src/interfaces/ITypes.sol");}
The macro reads a Solidity interface file and generates corresponding Rust types and encoding/decoding functions. In the example above, it reads ITypes.sol
which defines:
NewTrigger
eventTriggerInfo
structDataWithId
struct
More documentation on the sol!
macro can be found at: https://docs.rs/alloy-sol-macro/latest/alloy_sol_macro/macro.sol.html
Accessing configuration and provider
WAVS provides host bindings to get the chain config for a given chain name in the wavs.toml file. The new_evm_provider
from wavs-wasi-utils
can be used to create a provider for a given chain.
use crate::bindings::host::get_evm_chain_config;use alloy_network::Ethereum;use alloy_provider::RootProvider;use wavs_wasi_utils::evm::new_evm_provider;// Get the chain config for a specific chain defined in wavs.tomllet chain_config = get_evm_chain_config("local").unwrap();// Create an Alloy provider instance using the HTTP endpointlet provider: RootProvider<Ethereum> = new_evm_provider::<Ethereum>(chain_config.http_endpoint.unwrap(),);
Example: Querying NFT balance
Here's an example demonstrating how to query the balance of an ERC721 NFT contract for a given owner address:
// Import required dependenciesuse crate::bindings::host::get_evm_chain_config; // WAVS host binding to get chain configurationuse alloy_network::Ethereum; // Ethereum network typeuse alloy_provider::RootProvider; // Provider for making RPC callsuse alloy_sol_types::sol; // Macro for generating Solidity bindingsuse wavs_wasi_utils::evm::{ // WAVS utilities for EVM interactionalloy_primitives::{Address, U256}, // Ethereum primitive typesnew_evm_provider, // Function to create EVM provider};use alloy_rpc_types::TransactionInput;use wstd::runtime::block_on; // Utility to run async code in sync context// Define the ERC721 interface using the sol! macro// This generates Rust types and functions for interacting with the contractsol! {interface IERC721 {// Define the balanceOf function that returns how many NFTs an address ownsfunction balanceOf(address owner) external view returns (uint256);}}// Function to check if an address owns any NFTs from a specific contractpub fn query_nft_ownership(address: Address, nft_contract: Address) -> Result<bool, String> {// block_on allows us to run async code in a synchronous functionblock_on(async move {// Get the chain configuration for the local networklet chain_config = get_evm_chain_config("local").unwrap();// Create a provider that will handle RPC communicationlet provider: RootProvider<Ethereum> = new_evm_provider::<Ethereum>(chain_config.http_endpoint.unwrap());// Create a contract instance using the generated IERC721 interfacelet balance_call = IERC721::balanceOf { owner: address };let tx = alloy_rpc_types::eth::TransactionRequest {to: Some(TxKind::Call(nft_contract)),input: TransactionInput { input: Some(balance_call.abi_encode().into()), data: None },..Default::default()};// Call the balanceOf function on the contract// .call() executes the function as a view call (no state changes)let result = provider.call(tx).await.map_err(|e| e.to_string())?;// Return true if the address owns at least one NFT (balance > 0)let balance: U256 = U256::from_be_slice(&result);Ok(balance > U256::ZERO)})}
You can also use the alloy-contract
crate to interact with smart contracts. See the alloy-contract docs page for more information.
See the wavs-wasi-utils documentation and the Alloy documentation for more detailed information.
Alloy ecosystem crates
The Alloy ecosystem provides a comprehensive set of crates for Ethereum development:
alloy-primitives
: Core Ethereum types (Address
,U256
,Bytes
, etc.)alloy-provider
: Ethereum node interaction (RPC, WebSocket, batching)alloy-network
: Network types and chain-specific functionalityalloy-sol-types
: ABI handling and type generationalloy-contract
: Contract interaction utilities
Utility crates
Essential utility crates for WAVS components:
wstd
: WASI standard library withblock_on
for async operationsserde
/serde_json
: Data serialization and JSON handlinganyhow
: Error handling and propagation