Layer LogoWAVS Docs
WAVS builders handbookComponents

Component overview

Service components contain the main business logic of a WAVS service.

Component languages

WAVS enables developers to write components in different programming languages. These languages are compiled to WebAssembly (WASM) bytecode where they can be executed off-chain in the WAVS runtime.

The examples in this documentation are mainly written in Rust, but there are also examples of components written in the following languages:

Component structure

A basic component has three main parts:

  • Decoding incoming trigger data.
  • Processing the data (this is the custom business logic of your component).
  • Encoding and returning the result for submission (if applicable).

Trigger inputs

When building WASI components, keep in mind that components can receive the trigger data in two ways:

  1. On-chain events: When triggered by an EVM event, the data comes through the TriggerAction with TriggerData::EvmContractEvent.

  2. Local testing: When using make wasi-exec command in the template to test a component, the data comes through TriggerData::Raw. No abi decoding is required, and the output is returned as raw bytes.

Here's how the example component handles both cases in trigger.rs:

// In trigger.rs
pub fn decode_trigger_event(trigger_data: TriggerData) -> Result<(u64, Vec<u8>, Destination)> {
match trigger_data {
// On-chain Event
// - Receive a log that needs to be decoded using the contract's ABI
// - Decode into our Solidity types generated by the sol! macro from the Solidity interface
TriggerData::EvmContractEvent(TriggerDataEvmContractEvent { log, .. }) => {
// Decode Ethereum event logs using the `decode_event_log_data!` macro
let event: solidity::NewTrigger = decode_event_log_data!(log)?;
let trigger_info = solidity::TriggerInfo::abi_decode(&event._triggerInfo)?;
Ok((trigger_info.triggerId, trigger_info.data.to_vec(), Destination::Ethereum))
}
// Local Testing (wasi-exec)
// - Receive raw bytes directly
// - No ABI decoding is needed
TriggerData::Raw(data) => Ok((0, data.clone(), Destination::CliOutput)),
_ => Err(anyhow::anyhow!("Unsupported trigger data type")),
}
}
mod solidity { // Define the Solidity types for the incoming trigger event using the `sol!` macro
use alloy_sol_macro::sol;
pub use ITypes::*;
// The objects here will be generated automatically into Rust types.
// the interface shown here is used in the example trigger contract in the template.
sol!("../../src/interfaces/ITypes.sol");
}

The component decodes the incoming event trigger data using the decode_event_log_data! macro from the wavs-wasi-utils crate.

The sol! macro from alloy-sol-macro is usedto define Solidity types in Rust. This macro reads a Solidity interface file and generates corresponding Rust types and encoding/decoding functions. For more information, visit the Blockchain interactions page.

Component logic

Components must implement the Guest trait, which is the main interface between your component and the WAVS runtime.

The run function is the entry point for your business logic: it receives and decodes the trigger data, processes it according to your component's logic, and returns the results.

// In lib.rs
impl Guest for Component {
fn run(action: TriggerAction) -> Result<Option<WasmResponse>, String> {
// 1. Decode the trigger data using the decode_trigger_event function from trigger.rs
let (trigger_id, req, dest) = decode_trigger_event(action.data)?;
// 2. Process the data (your business logic)
let res = block_on(async move {
let resp_data = get_price_feed(id).await?;
serde_json::to_vec(&resp_data)
})?;
// 3. Encode the output based on destination
let output = match dest {
// For on-chain submissions, the output is abi encoded using the encode_trigger_output function from trigger.rs
Destination::Ethereum => Some(encode_trigger_output(trigger_id, &res)),
// For local testing via wasi-exec, the output is returned as raw bytes
Destination::CliOutput => Some(WasmResponse {
payload: res.into(),
ordering: None
}),
};
Ok(output)
}
}

Components can contain any compatible logic, including blockchain interactions, network requests , off-chain computations, and more. To learn about the types of components that WAVS is best suited for, visit the design considerations page.

Logging in a component

Components can use logging to debug and track the execution of the component.

Logging in development:

Use println!() to write to stdout/stderr. This is visible when running make wasi-exec locally in the template.

lib.rs
println!("Debug message: {:?}", data);

Logging in production

For production, you can use a host::log() function which takes a LogLevel and writes its output via the tracing mechanism. Along with the string that the developer provides, it attaches additional context such as the ServiceID, WorkflowID, and component Digest.

lib.rs
use bindings::host::{self, LogLevel};
host::log(LogLevel::Info, "Production logging message");

Component output

After processing data in the run function, the component can encode the output data for submission back to Ethereum. In the template example, this is done using the encode_trigger_output function in the trigger.rs file.

/// Encodes the output data for submission back to Ethereum
pub fn encode_trigger_output(trigger_id: u64, output: impl AsRef<[u8]>) -> WasmResponse {
WasmResponse {
payload: solidity::DataWithId {
triggerId: trigger_id,
data: output.as_ref().to_vec().into(),
}
.abi_encode(), // ABI encode the struct for blockchain submission
ordering: None, // Optional ordering parameter for transaction sequencing
}
}

Outputs for components are returned as a WasmResponse struct, which is a wrapper around the output data of the component for encoding and submission back to Ethereum. It contains a payload field that is the encoded output data and an optional ordering field that is used to order the transactions in the workflow. The WasmResponse is submitted to WAVS which routes it according to the workflow's submission logic.

Component definition

A component is defined in the workflow object of the service.json file. Below is an example of the different fields that can be defined in the component object.

service.json
// ... other parts of the service manifest
// "workflows": {
// "workflow-example": {
// "trigger": { ... }, the trigger for the workflow
"component": { // the WASI component containing the business logic of the workflow
"source": { // Where the component code comes from
"Registry": {
"registry": {
"digest": "882b992af8f78e0aaceaf9609c7ba2ce80a22c521789c94ae1960c43a98295f5", // SHA-256 hash of the component's bytecode
"domain": "localhost:8090",
"version": "0.1.0",
"package": "example:evmrustoracle"
}
}
},
"permissions": { // What the component can access
"allowed_http_hosts": "all", // Can make HTTP requests to any host
"file_system": true // Can access the filesystem
},
"fuel_limit": null, // Computational limits for the component
"time_limit_seconds": 1800, // Can run for up to 30 minutes
"config": { // Configuration variables passed to the component
"variable_1": "0xb5d4D4a87Cb07f33b5FAd6736D8F1EE7D255d9E9", // NFT contract address
"variable_2": "0x34045B4b0cdfADf87B840bCF544161168c8ab85A" // Reward token address
},
"env_keys": [ // Secret environment variables the component can access from .env
"WAVS_ENV_API_KEY", // secret API key with prefix WAVS_ENV_
]
},
// "submit": { ... } // submission logic for the workflow
// ... the rest of the service manifest

For more information on component configuration variables and keys, visit the variables page.

Registry

WAVS uses a registry to store the WASM components. A service like wa.dev is recommended for proper distribution in production. A similar registry environment is emulated locally in docker compose for rapid development without an API key:

  • Build your component
  • Compile the component
  • Upload the component to the registry
  • Set the registry in your service using the wavs-cli command in the build_service.sh script:

wavs-cli workflow component --id ${WORKFLOW_ID} set-source-registry --domain ${REGISTRY} --package ${PKG_NAMESPACE}:${PKG_NAME} --version ${PKG_VERSION}

Edit on GitHub

On this page