Layer LogoWAVS Docs
WAVS builders handbookComponents

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 functionality
wit-bindgen-rt = { workspace = true, features = ["bitflags"] } # Required for WASI bindings
wavs-wasi-utils = "0.4.0" # Blockchain interaction utilities
wstd = "0.5.3" # WASI standard library
# Alloy crates for Ethereum interaction
alloy-sol-macro = { version = "1.1.0", features = ["json"] } # sol! macro for interfaces
alloy-sol-types = "1.1.0" # ABI handling & type generation
alloy-network = "0.15.10" # Network trait and Ethereum network type
alloy-provider = { version = "0.15.10", default-features = false, features = ["rpc-api"] } # RPC provider
alloy-rpc-types = "0.15.10" # RPC type definitions
alloy-contract = "0.15.10" # Contract interaction utilities
# Other useful crates
anyhow = "1.0.98" # Error handling
serde = { version = "1.0.219", features = ["derive"] } # Serialization/deserialization
serde_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.

wavs.toml
# 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 generation
alloy-sol-types = { workspace = true } # For ABI handling

Basic Pattern:

mod solidity {
use alloy_sol_macro::sol;
// Generate types from Solidity file
sol!("../../src/interfaces/ITypes.sol");
// Or define types inline
sol! {
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.

trigger.rs
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 event
  • TriggerInfo struct
  • DataWithId 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.

lib.rs
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.toml
let chain_config = get_evm_chain_config("local").unwrap();
// Create an Alloy provider instance using the HTTP endpoint
let 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:

lib.rs
// Import required dependencies
use crate::bindings::host::get_evm_chain_config; // WAVS host binding to get chain configuration
use alloy_network::Ethereum; // Ethereum network type
use alloy_provider::RootProvider; // Provider for making RPC calls
use alloy_sol_types::sol; // Macro for generating Solidity bindings
use wavs_wasi_utils::evm::{ // WAVS utilities for EVM interaction
alloy_primitives::{Address, U256}, // Ethereum primitive types
new_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 contract
sol! {
interface IERC721 {
// Define the balanceOf function that returns how many NFTs an address owns
function balanceOf(address owner) external view returns (uint256);
}
}
// Function to check if an address owns any NFTs from a specific contract
pub fn query_nft_ownership(address: Address, nft_contract: Address) -> Result<bool, String> {
// block_on allows us to run async code in a synchronous function
block_on(async move {
// Get the chain configuration for the local network
let chain_config = get_evm_chain_config("local").unwrap();
// Create a provider that will handle RPC communication
let provider: RootProvider<Ethereum> = new_evm_provider::<Ethereum>(
chain_config.http_endpoint.unwrap()
);
// Create a contract instance using the generated IERC721 interface
let 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:

Utility crates

Essential utility crates for WAVS components:

  • wstd: WASI standard library with block_on for async operations
  • serde/serde_json: Data serialization and JSON handling
  • anyhow: Error handling and propagation

Edit on GitHub

On this page