# WAVS Documentation This file contains all WAVS documentation in a format optimized for large language models. Website: https://docs.wavs.xyz --- # WAVS Docs URL: / Description: Welcome page with links to WAVS documentation sections ![WAVS](https://raw.githubusercontent.com/Lay3rLabs/WAVS-docs/refs/heads/main/public/wavs.png) Welcome to the WAVS Docs! WAVS is a next-generation AVS platform that makes it easy to create, manage, and operate high-performance AVSs. Use this documentation to learn [about WAVS](/overview), [how it works](/how-it-works), and how to [start building your AVS](./tutorial/1-overview). ## Get started } href="/overview" title="WAVS Overview" description="Learn about WAVS" /> } href="/how-it-works" title="How it works" description="Explore the inner workings of WAVS" /> } href="/benefits" title="WAVS Benefits" description="Discover how WAVS revolutionizes AVS creation" /> } href="/tutorial/1-overview" title="Build a service" description="Follow the WAVS tutorial to build a service" /> --- # WAVS benefits URL: /benefits Description: Key advantages and use cases of WAVS platform ![WAVS](https://raw.githubusercontent.com/Lay3rLabs/WAVS-docs/refs/heads/main/public/banners/benefits.png) >WAVS is a platform that makes building AVSs easier. **The problem**: creating an AVS the traditional way is complicated. It requires a lot of preliminary development work, such as building custom contracts, scaffolding infrastructure, working with Dockerized components, and coordinating with operators. Most of the development centers around creating AVS infrastructure, which is generally more complicated than the core logic of the service itself. **There is an easier way**: WAVS provides a base layer of AVS infrastructure so you can focus solely on creating the core logic of your service. This logic is written in Rust (with other languages available soon) and compiled as a lightweight WASI component which can be deployed to the WAVS platform and run as an AVS by operators. These components are run off-chain by operators in the WAVS (WASI-AVS) runtime at near-native speed, and the results are brought verifiably on-chain. A service of services, WAVS allows an AVS to dynamically run and manage multiple components that work together to build flexible and intelligent applications. ## Why WAVS? WAVS redefines the AVS paradigm, making AVSs easier to build, less expensive to run, and enabling the next generation of composable, intelligent blockchain protocols. 1. Dynamic and Cost-Effective Service Management - Flexibility: Add, update, or manage components dynamically without having to coordinate upgrades with an entire operator set. - Cost-effective and performant: Multiple AVSs run on the WAVS runtime - WASI service components are lightweight compared to Docker, saving storage and startup time. - WASI components have instantaneous initialization vs. Docker's redundant OS layers and slower boot times. 2. Simplified Development - Focus on your application logic, not overhead: - With templates, there's no need to write multiple custom contracts to parse events or aggregate signatures. - WAVS handles common AVS infrastructure, leaving AVS developers to focus on their core logic. 3. Multichain Ready - WAVS is built to operate across multiple blockchain environments and will be released with support for EVM networks. - WAVS will foster a multichain ecosystem for AVSs to interact and interoperate. 4. Intelligent Protocols & Composability - Enable asynchronous, verifiable execution flows across multiple on and off-chain components. - Compose multiple services to create dynamic intelligent protocols that surpass the limitations of traditional smart contracts. --- # WAVS design considerations URL: /design Description: Best practices and design patterns for WAVS services The WAVS approach to AVS design focuses on the lightweight and agile nature of components, with an emphasis on performance and security. WAVS works best with the "serverless function" approach, wherein the priority of service component logic is to process data from external sources rather than store it locally. In this model, a component can receive input data from external sources, process it, and submit the verifiable outputs to external sources. ## Aggregation and deterministic queries WAVS currently supports "exact match" [aggregation](./handbook/submission#aggregator), meaning that consensus is reached if a threshold amount of submitted responses from operators are identical. This approach fits many common use cases, but it means that developers must build their components to receive and process data only from deterministic or immutable sources, such as: - Data from the input event trigger - Ethereum queries at a given block height - IPFS data or other Content-Addressable Storage - Web2 APIs that are trusted to return the same result on every query - Seeded application parameters (e.g. Ollama for AI models) For example, when designing a price oracle, a **blockheight** or **timestamp** should be specified in the query logic. This ensures that the query is deterministic and that the same data point is retrieved by all operators. Conversely, a component that is designed to fetch "current price" would be non-deterministic and may result in a consensus error. Operators may run components at slightly different times, leading to discrepancies in the data they receive. This design should be avoided, as aggregation for this would require custom BFT averaging logic which is not currently supported. However, adding custom logic to process non-exact matches will be available in future releases. ## State Persistent state introduces additional challenges in AVS design with WAVS: operators may execute triggers at different times or, in some cases, not run them at all if they join an AVS after it has been running for some time. Components that rely on operator-local mutable state risk failing consensus due to inconsistencies in execution. For these reasons, it is best practice to avoid storing operator-local mutable state within your components. This functionality would require features such as state synchronization (P2P), guaranteed execution ordering, and Merkle-proof validation which are not yet supported. ## Caching WAVS provides components with an optional local data directory that can be used for caching. This storage method should only be used to cache data as a performance optimization and not as a replacement for external state stores. It is best practice when using a cache to reference external data from deterministic immutable sources to avoid consensus failures. Keep the following points in mind when using cached data: 1. Caching should be used as a performance optimization only. 2. Use immutable or deterministic external data sources with specific time stamps or block heights. 3. Design your component logic to work even if the cache is cleared. Cache state is not shared among operators, and any operator should be able to rebuild the cache from scratch. 4. Don't use caching to store state that depends on previous executions or mutable data. --- # Overview URL: /overview Description: Introduction to WAVS platform and its core concepts ![WAVS](https://raw.githubusercontent.com/Lay3rLabs/WAVS-docs/refs/heads/main/public/banners/intro.png) ## What is WAVS? WAVS is a [WASI](./how-it-works#wasm-and-wasi) runtime for building AVSs (Autonomous Verifiable Services). With WAVS, you can create, manage, and operate high-performance AVSs using the languages you already know and love (like [Rust, Go, and JavaScript](./handbook/components/component#languages), with more languages like Python coming soon). By providing a base layer of AVS infrastructure, WAVS lets you focus on implementing the core logic of your service. WAVS compiles that logic to [WASM](./how-it-works#wasm-and-wasi), and lets you deploy it as lightweight service components. Better yet, WAVS solves the trust problem in off-chain compute: it separates services from the operators that run them. Builders create their components, and operators run them in WAVS runtime at near-native speed. Then, operators sign the results of the off-chain computation and place them on-chain. Boom: off-chain compute with on-chain verifiability. > In simple terms, WAVS streamlines the process of building and managing an AVS. Finally, WAVS utilizes restaking (via EigenLayer) to secure its AVSs. A service of services, WAVS is composable by nature, allowing an AVS to dynamically run and manage multiple components that work together to build flexible and intelligent applications. ## Use cases WAVS supports a wide range of AVS use cases, enabling powerful, verifiable off-chain computation across different domains: - **Decentralized AI**: WAVS unlocks decentralized AI that is [deterministic and verifiable](https://www.layer.xyz/news-and-insights/deterministic-ai), enabling trustless decision-making and autonomous governance through DAO-controlled AI agents. - **Oracles**: [Create](./tutorial/1-overview) and dynamically deploy new oracles as easily as uploading a component to your service to verifiably bring data from any source on-chain. - **Zero Knowledge Proofs**: ZK verifiers, ZK Prover Marketplaces, and ZK aggregation layers can be deployed as lightweight WAVS [service components](#service-components), making it simple to build modular and composable proof services. - **Crosschain Bridges**: Build secure, decentralized bridges with WAVS. Lock assets on one chain, trigger a verifiable service component, and mint them on anotherβ€”all with trust-minimized execution. - **Dynamic applications**: WAVS combines on-chain and off-chain services to build a cross-chain, decentralized application layer. - **Intelligent protocols**: Build protocols that are verifiably aware of on-chain and off-chain events without relying on centralized infrastructure. Compose services and applications to enable complex webs of decentralized transaction flows. - **TEEs**: WAVS can be used to build TEEs (Trusted Execution Environments) that run off-chain computations in a secure and verifiable manner, ensuring data integrity and confidentiality. ## Building a service WAVS removes the complexity of building an AVS, making it easy to develop and deploy custom services. With built-in AVS infrastructure and developer tooling, WAVS powers a new multichain ecosystem of composable, decentralized, and verifiable applications. **Learn more:** The following is a basic overview of a WAVS service. For a more in-depth overview of WAVS, visit the [How it works section](./how-it-works). Check out the WAVS tutorial to learn how to build a service. This example will cover a basic AVS with three parts: a trigger, a service component, and submission logic. ### Defining triggers Triggers are the actions or events that prompt your service to be run. Currently, WAVS supports triggers from on-chain events from EVM and Cosmos chains, cron schedules, and block intervals for EVM or Cosmos chains. Triggers can be used to pass arbitrary data as the inputs for service components to be run. Operators running a service listen for specific trigger events and run the corresponding service component. ![WAVS](https://raw.githubusercontent.com/Lay3rLabs/WAVS-docs/refs/heads/main/public/diagrams/trigger.png) To learn more about triggers, visit the [triggers page](./handbook/triggers). ### Service components Service components are the core logic of an AVS. They are written in [Rust, Go, or JavaScript](./handbook/components/component#languages) and compiled to [WASM](./how-it-works#wasm-and-wasi) as lightweight WASI components. WAVS provides a base layer of AVS infrastructure, allowing you to focus solely on the logic of your service. Service components can contain logic for processing input data from triggers. If a trigger passes data, a service component can use that data as input. For example, a simple service component could contain logic for receiving a number as input and returning the square of that number as output. ![WAVS](https://raw.githubusercontent.com/Lay3rLabs/WAVS-docs/refs/heads/main/public/diagrams/service.png) To learn more about service components, visit the [How it works](/how-it-works#service-components) page. Check out the [WAVS tutorial](./tutorial/1-overview) to learn how to create a service component. ### Submission logic Along with your component, you'll also need to define how the results of your service are submitted on-chain. With WAVS, you can use an [aggregator and submission contract](/how-it-works#submission) to define this logic. ### Run your service AVS builders define their service in a [service manifest or service.json file](./handbook/service) with a workflow that includes a trigger, service component, and submission logic. Registered operators will then listen for the triggers specified by your service. Once triggered, operators will run your service off-chain, where data from a trigger is passed to a service component and run in a sandboxed WASI environment. Operators sign the result of the service computation and the verified result can be returned as an on-chain response. ![WAVS](https://raw.githubusercontent.com/Lay3rLabs/WAVS-docs/refs/heads/main/public/diagrams/wavs-flow.png) With WAVS, service updates are streamlined: updates to services can be made by AVS builders directly without needing to coordinate with operators. Operators only need to upgrade if there is a change to the WAVS node software itself. ## Multichain capability WAVS is built to be multichain. A service can be triggered by events on one chain, run by operators off-chain, and write verified responses to another chain. This interoperability is what makes WAVS so flexible, creating a decentralized computational layer that can function across multiple chains. ## Composability WAVS is composable by nature, allowing an AVS to dynamically run and manage multiple workflows that work together to build flexible and intelligent applications. Workflows include a trigger, service component, and submission logic. To [compose services and workflows](./handbook/workflows), the trigger of one workflow can be the submission logic of another workflow. The registry model allows component bytecode to be stored in one location and reused across multiple workflows. ## Security The WAVS platform is secured via Ethereum restaking on EigenLayer, which provides a base security layer for AVSs built using WAVS. Restaking refers to the utilization of staked Ethereum to secure AVSs by imposing additional slashing terms on the staked Ethereum for operator misbehavior. In this way, the cryptoeconomic security of Ethereum is extended to WAVS AVSs. ## Full-stack decentralization WAVS enables full-stack decentralization by unifying off-chain computation with on-chain verifiability. The chain layer connects to Ethereum and other chains, while the security layer extends cryptoeconomic security via EigenLayer restaking. At the AVS layer, lightweight WAVS powers WASM-based services to process off-chain computations triggered by on-chain events. Operators validate, sign, and commit the results back on-chain, ensuring verifiability and trust-minimized execution. This architecture makes WAVS a scalable, decentralized framework for full-stack applications. ![WAVS](https://raw.githubusercontent.com/Lay3rLabs/WAVS-docs/refs/heads/main/public/diagrams/security.png) --- # How it works URL: /how-it-works Description: Technical overview of WAVS architecture and components ![WAVS](https://raw.githubusercontent.com/Lay3rLabs/WAVS-docs/refs/heads/main/public/banners/how.png) WAVS is a decentralized execution framework for AVSs (Autonomous Verifiable Services), enabling the results of off-chain computation to be brought verifiably on-chain. It provides a runtime for executing WASI-based service components, allowing developers to define event-driven off-chain workflows while inheriting Ethereum's security via EigenLayer restaking. ## The flow Before diving into each part of a WAVS service individually, it's important to understand the basic execution flow of WAVS. ![WAVS overview](https://raw.githubusercontent.com/Lay3rLabs/WAVS-docs/refs/heads/main/public/diagrams/workflows.png) 1. A service is a collection of on-chain contracts and off-chain logic run by a set of registered operators, bringing the results of off-chain computation on-chain verifiably. A service's operator set is defined in a service manager contract. A service manifest (service.json) defines the configuration and different parts of a WAVS service, including information about the service, workflows, components, submission, watch trigger, aggregator, and more. The service contracts and manifest are deployed and operators register to run the service. 2. Services contain one or more workflows, which define the different execution paths in your service. Each workflow contains a trigger, a service component, and submission logic. Triggers are defined events that activate a workflow. Registered operators running WAVS nodes maintain lookup maps to track and detect triggers. Triggers can be: - Any on-chain EVM or Cosmos event from a specified contract address and event signature - Cron jobs that activate at specified intervals - Blockheight triggers that activate at a specified block height on EVM or Cosmos chains 3. When a trigger is detected, operators run the workflow's corresponding service component off-chain in the WAVS runtime. 4. Each operator signs the result of their off-chain computation and submits it to the aggregator. 5. The aggregator accepts results from the operators and verifies that the result payloads match and that the signatures are valid. Once enough verified submissions are received (the threshold is defined in the service manager), the aggregator submits the bundled signatures and result payloads as a single transaction to the submission contract specified in the service's manifest. 6. The `handleSignedEnvelope()` function on the submission contract validates the data and signatures via the service manager contract. The workflow is complete, bringing the result of the off-chain computation verifiably on-chain. Services and workflows can be chained together by setting the submission event of one workflow to be the trigger of another. ## WAVS parts ### Service manager The service manager is a contract that defines a service's operator set. It is used to register operators and define their weights, set the threshold for aggregator submissions, and maintain the URI of the service manifest. For more information, see the [Service page](./handbook/service#service-manager). ### Service manifest The service manifest is a JSON file that defines the configuration and different parts of a WAVS service, including information about the service, workflows, components, submission, service manager contract, aggregator configuration, and more. For more information, see the [Service page](./handbook/service). ### Workflows Workflows are the building blocks of a WAVS service flow. They define the different execution paths of a service, including the trigger, component, and submission logic. For more information, see the [Workflows page](./handbook/workflows). ### Triggers A trigger is the event that activates a WAVS service. Triggers can be EVM or Cosmos events, cron, or blockheight triggers. When a specified event is triggered, WAVS operators detect it and execute the corresponding service component off-chain. The results are then verified and submitted back on-chain, completing the execution cycle. For more information, see the [Triggers page](./handbook/triggers). ### Service components Service components are the heart of the WAVS platform, encapsulating the core logic that powers a service. They contain the off-chain business logic for a service, [written in Rust, Go, or JavaScript](./handbook/components/component#component-languages) (with other languages coming soon). These service components are compiled to WASM and are uploaded to the WAVS platform where they can be run by operators. In the WAVS runtime, service components are sandboxed from each other and from the node's operating system. This way, operators and AVS services maintain a clean separation, with AVS builders defining service components and operators having to opt in to each service. Service components are lightweight and built for adaptability. Building a service used to take thousands of lines of code and the configuration of dozens of files to execute even the simplest logic. With WAVS, service components can consist of a few lines of code that can be dynamically deployed to the WAVS platform. Service components are also designed for composability: an AVS can chain multiple components together, creating decentralized flows of off-chain and on-chain executions. These intelligent protocols merge the performance of off-chain computation with on-chain verifiability, creating a complex mesh of decentralized transaction flows. To learn more, visit the [Components page](./handbook/components/component). For a hands-on example of a service component, visit the [tutorial](./tutorial/1-overview). ### WAVS runtime The WAVS (WASI-AVS) runtime serves as the off-chain execution environment for all services running on the WAVS platform. Powered by operators running the WAVS node software, it provides a structured framework for deploying and managing service components that run within a WASI (WebAssembly System Interface) environment. You can think of WASI as a lightweight OS-like interface, offering a standard set of APIs that allow service components to perform system-level operations such as file handling and environment interactions. WAVS enables service components to execute securely within this WASI-powered sandbox, ensuring isolation from both the host system and other components. ### WASM and WASI [WASM (Web Assembly)](https://webassembly.org/) is a high-performance, low-level binary format that can be compiled from multiple programming languages. WASM can even run in web browsers at near-native speed. By leveraging WASM, AVSs built with WAVS are lightning-fast, lightweight, and easy to develop. WASI (WebAssembly System Interface) is a standardized API that enables WASM (WebAssembly) modules to interact with a host system in a secure and platform-independent way. It provides WASM modules with a standardized set of APIs to access system resources. For more information, visit the [WASI documentation](https://wasi.dev/). There are significant advantages in leveraging a WASM/WASI-based platform for AVSs: - Lightweight execution: Service components are lightweight WASM binaries ideal for high-frequency, low-latency AVS tasks. - Speed: Components can run in the WASI environment at near-native speeds, providing a significant advantage over Dockerized AVSs. - Low overhead: Instead of each service needing its own dedicated Docker container, the WAVS runtime provides a computational layer that can be used by many components, saving storage and startup time. - Dynamic deployment: To upgrade a service, simply upload a new component and update your service metadata to point to the new component. No more downtime or coordination of new binaries among operators. - Security and separation: The WAVS WASI runtime enforces security by sandboxing service components, allowing them to interact with the host (WAVS) only through explicitly permitted WASI APIs. ### Registry WAVS uses a registry to store the WASM components. A service like [wa.dev](https://wa.dev) is recommended for proper distribution in production. For more information, visit the [Component page](./handbook/components/component#registry). ### Signing and aggregation When a service is triggered, each operator registered to the service will run the service component on their machine and generate the result. These results are signed by the operator before being submitted to an aggregator. For services that submit results on Ethereum, an off-chain aggregator can be used to conserve gas fees. Instead of each individual operator submitting results of a service directly on-chain, operators sign the results and submit them off-chain to an aggregator, which aggregates the results and submits a result to be posted to the chain in a single transaction. Results are signed using an operator's individual private key to produce an signature, which is used to prove that the result is associated with an operator's specific private and public key pair. The aggregator accepts the result submissions from operators, verifies their validity, and compares the responses. If there is a consensus among valid operator results, the results and verified signatures are submitted on-chain as a single transaction. Support for BLS signatures and a decentralized aggregator are currently under development. To learn more about the aggregator, visit the [Submission and aggregator page](./handbook/submission#aggregator). For more discussion on aggregation considerations, visit the [Design considerations page](./design). ### Submission A submission contract is the final destination of the results of a service component. Known as a Service Handler, this logic can be any contract as long as it implements the [`handleSignedEnvelope()` function using the `IWavsServiceHandler`](./handbook/submission#submission-contract) interface to validate data and signatures using the service manager. After the execution of a service component in the WAVS runtime, the results are aggregated in the [aggregator](./handbook/submission#aggregator) and passed to the submission contract. Developers can use this contract to define their own logic for results and implement different rules depending on the specific needs of their service. To learn more about the submission contract, visit the [Submission page](./handbook/submission). ### Updating a service Because of the lightweight and portable nature of WebAssembly, WAVS operators only need to run a single Docker image. WAVS provides a runtime for all registered services to run, each sandboxed from the other and from the node's operating system due to the nature of [WASI](#wasm-and-wasi). Operators will need to opt in to running different services by registering to an AVS. Updates to service logic do not require node upgrades. Instead, developers can dynamically deploy a new service component and update their service manifest. Instead of needing to run a new Docker image every time a service is updated, operators only need to upgrade if there is a breaking change to the WAVS node software itself. --- # 1. Oracle service tutorial URL: /tutorial/1-overview Description: Introduction to WAVS tutorial series ![Start-building](https://raw.githubusercontent.com/Lay3rLabs/WAVS-docs/refs/heads/main/public/start-building.png) In this guide, you will build a simple oracle service that fetches Bitcoin price data from [coinmarketcap.com](https://coinmarketcap.com/api/). This example is built using the [WAVS Foundry Template](https://github.com/Lay3rLabs/wavs-foundry-template), which contains the tools you need to build your own custom service. The price oracle service example has three basic parts: 1. [A trigger contract](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/src/contracts/WavsTrigger.sol): A trigger can be any on-chain event emitted from a contract. This event **triggers** a service to run. In the WAVS Foundry Template, there is a simple trigger contract that stores trigger requests, assigns them unique IDs, and emits an event when a new trigger is added. In this example, the trigger event `addTrigger` will pass data pertaining to the ID of an asset for the CoinMarketCap price feed. 2. [A service component](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/components/evm-price-oracle/src/lib.rs): The service component contains the business logic of a service. It is written in Rust, compiled to WASM, and run by operators in the WAVS runtime. In this example, operators will listen for a new trigger event to be emitted and then run the service component off-chain, using the asset ID data from the trigger event as input. The component contains logic to fetch the price of the asset from the CoinMarketCap price feed API, which is then processed and encoded before being sent back on-chain. 3. [A submission contract](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/src/contracts/WavsSubmit.sol): Also known as the "service handler," this contract contains the on-chain submission logic for the service. It validates and stores the processed data returned by the WAVS component. When an operator submits a response, the contract verifies the data's integrity by checking the operator's signature and then associates it with the original trigger ID, bringing the queried price on-chain. These three parts come together to create a basic oracle service using WAVS. To learn more about services and how they work, visit the [How it works page](../how-it-works). ## Video tutorial {/* todo: needs a 0.4 demo video */} You can follow along with this guide by watching the video tutorial: } href="/tutorial/2-setup" title="Get Started" description="Click here to set up your environment and start building your service." /> --- # 2. System setup URL: /tutorial/2-setup Description: Setting up development environment for WAVS The following installations are required to run this example. Follow the steps below to set up your system. **System recommendations:** This tutorial is designed for Windows WSL, Linux, and macOS systems. ## Environment Install [VS Code](https://code.visualstudio.com/download) and the [Solidity extension](https://marketplace.visualstudio.com/items?itemName=JuanBlanco.solidity) if you don't already have them. ## Rust Run the following command to install [Rust](https://www.rust-lang.org/tools/install). ```bash docci-ignore curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` **Homebrew Rust not supported:** If you installed Rust using Homebrew, you will need to uninstall it and install it again using the rustup command. ```bash docci-ignore brew uninstall rust ``` Then run: ```bash docci-ignore curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` **Fresh Install:** If you just installed Rust for the first time, you will need to run the following commands: ```bash docci-ignore # Install required target and toolchain rustup toolchain install stable rustup target add wasm32-wasip2 ``` **Upgrade Rust:** If you already have a previous version of Rust installed, you will need to run the following commands to upgrade it to the latest stable version: ```bash docci-ignore # Remove old targets if present rustup target remove wasm32-wasi || true rustup target remove wasm32-wasip1 || true # Update and add required target rustup update stable rustup target add wasm32-wasip2 ``` ## Cargo components Install the following for building WebAssembly components. Visit the [Cargo Component documentation](https://github.com/bytecodealliance/cargo-component#installation) for more information. {/* This section is also in [](./5-build.mdx). Remember to update there as well */} ```bash docci-ignore cargo install cargo-binstall cargo binstall cargo-component wasm-tools warg-cli wkg --locked --no-confirm --force # Configure default registry # Found at: $HOME/.config/wasm-pkg/config.toml wkg config --default-registry wa.dev # Allow publishing to a registry # # if WSL: `warg config --keyring-backend linux-keyutils` warg key new ``` **WSL Ubuntu GLIB out of date:** If you are on Ubuntu LTS but encounter an error like `wkg: /lib/x86_64-linux-gnu/libm.so.6: version 'GLIBC_2.38' not found (required by wkg)`: ``` sudo do-release-upgrade ``` ## Foundry [Foundry](https://book.getfoundry.sh/) is a solidity development suite. The Foundry toolchain contains Anvil (a local testnet node), Forge (build and test smart contracts), Cast (an RPC call CLI), and Chisel (a Solidity REPL). 1. Install Foundryup, the official Foundry installer. ```bash docci-ignore curl -L https://foundry.paradigm.xyz | bash ``` 2. Install Foundry ```bash docci-ignore foundryup ``` ## Docker Visit the [Docker Documentation](https://docs.docker.com/get-started/get-docker/) for more info. ```bash docci-ignore brew install --cask docker ``` The following commands will install Docker and [Docker Compose](https://docs.docker.com/compose/). ```bash docci-ignore # Install Docker sudo apt -y install docker.io # Install Docker Compose sudo apt-get install docker-compose-v2 ``` **Docker for MacOS:** {/* This section is also in [](./5-build.mdx). Remember to update there as well */} If prompted, remove container with `sudo apt remove containerd.io` If you are using Docker Desktop, make sure it is open and running for this tutorial. Before proceeding, make sure that the following setting is updated: **Enable Host Networking**: Open Docker and navigate to -> Settings -> Resources -> Network. Make sure that 'Enable Host Networking' is turned on. Alternatively, you can install the following: ```bash docci-ignore brew install chipmk/tap/docker-mac-net-connect && sudo brew services start chipmk/tap/docker-mac-net-connect ``` If you are running on a Mac with an ARM chip, you will need to do the following: - Set up Rosetta: ```bash docci-ignore softwareupdate --install-rosetta ``` - Enable Rosetta (Docker Desktop: Settings -> General -> enable "Use Rosetta for x86_64/amd64 emulation on Apple Silicon") ## Make Visit the [Make Documentation](https://www.gnu.org/software/make/manual/make.html) for more info. ```bash docci-ignore brew install make ``` ```bash docci-ignore sudo apt -y install make ``` ## JQ Visit the [JQ Documentation](https://jqlang.org/download/) for more info. ```bash docci-ignore brew install jq ``` ```bash docci-ignore sudo apt -y install jq ``` ## Node.js Node v21+ is needed for the WAVS template. Visit the [NVM Installation guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-and-updating) to install Node Version Manager and update your Node version. ``` curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash nvm install --lts ``` After setting up your system, continue to the next page to create your project. --- # 3. Create your project URL: /tutorial/3-project Description: Creating and configuring WAVS project structure {/* todo: update --branch main once the template is updated */} 1. After setting up your environment, open a terminal and run the following command to create your WAVS Foundry Template project. In this example, your project will be called `my-wavs`. ```bash docci-ignore forge init --template Lay3rLabs/wavs-foundry-template my-wavs --branch main ``` 2. Then, enter your project: ```bash docci-ignore cd my-wavs ``` 3. Run the following command to open your project in VS code, or open it in the editor of your choice: ```bash docci-ignore code . ``` ## Explore the template This template repo contains all the files you'll need to build, run, and test WAVS services locally. The template already contains the necessary files for the oracle example to run. For example, the trigger ([`WavsTrigger.sol`](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/src/contracts/WavsTrigger.sol)) and submission ([`WavsSubmit.sol`](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/src/contracts/WavsSubmit.sol)) contracts can be found in the [`/my-wavs/src/contracts`](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/src) folder. In [`/evm-price-oracle/src/lib.rs`](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/components/evm-price-oracle/src/lib.rs) you'll find the oracle service component. This template uses a [`Makefile`](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/Makefile) and environment variables to help with your developer experience. If you are ever curious about one of the `Make` commands in the following sections, you can always look at the [`Makefile`](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/Makefile) to learn more. **Info:** You can run the following command from the root of the repo to see all of the commands and environment variable overrides available: ```bash docci-ignore make help ``` The next sections will show you how to deploy your contracts and components, set up WAVS, and run the oracle example. ## Build and test your contracts Run the following commands from the root of your project to install necessary dependencies, build the template contracts, and run tests using Forge. ``` # Install dependencies make setup # Build the contracts forge build # Run the solidity tests. forge test ``` The last command runs a basic unit test which verifies that the `SimpleTrigger` contract in `/WavsTrigger.sol` correctly stores and retrieves trigger data. --- # 4. Oracle component walkthrough URL: /tutorial/4-component Description: evm-price-oracle component walkthrough The core logic of the price oracle in this example is located in the [`/evm-price-oracle/src/lib.rs` file](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/components/evm-price-oracle/src/lib.rs). Scroll down to follow a walkthrough of the code for the oracle component. ## trigger.rs The [trigger.rs](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/components/evm-price-oracle/src/trigger.rs) file handles the decoding of incoming trigger data from the trigger event emitted by the trigger contract. The component uses `decode_event_log_data!()` from the [wavs-wasi-utils crate](https://docs.rs/wavs-wasi-utils/latest/wavs_wasi_utils/) to decode the event log data and prepares it for processing within the WAVS component. Trigger.rs handles both ABI encoded data for trigger and submission data and raw data for local testing. For more information on different trigger types, visit the [Triggers page](../handbook/triggers). To learn more about trigger input handling, visit the [Component page](../handbook/components/component#trigger-inputs). ```rust trigger.rs use crate::bindings::wavs::worker::layer_types::{ TriggerData, TriggerDataEvmContractEvent, WasmResponse, }; use alloy_sol_types::SolValue; use anyhow::Result; use wavs_wasi_utils::decode_event_log_data; /// Represents the destination where the trigger output should be sent pub enum Destination { Ethereum, CliOutput, } /// Decodes incoming trigger event data into its components /// Handles two types of triggers: /// 1. EvmContractEvent - Decodes Ethereum event logs using the NewTrigger ABI /// 2. Raw - Used for direct CLI testing with no encoding pub fn decode_trigger_event(trigger_data: TriggerData) -> Result, Destination)> { match trigger_data { TriggerData::EvmContractEvent(TriggerDataEvmContractEvent { log, .. }) => { let event: solidity::NewTrigger = decode_event_log_data!(log)?; let trigger_info = ::abi_decode(&event._triggerInfo)?; Ok((trigger_info.triggerId, trigger_info.data.to_vec(), Destination::Ethereum)) } TriggerData::Raw(data) => Ok((0, data.clone(), Destination::CliOutput)), _ => Err(anyhow::anyhow!("Unsupported trigger data type")), } } /// Encodes the output data for submission back to Ethereum pub fn encode_trigger_output(trigger_id: u64, output: impl AsRef) -> WasmResponse { WasmResponse { payload: solidity::DataWithId { triggerId: trigger_id, data: output.as_ref().to_vec().into(), } .abi_encode(), ordering: None, } } /// The `sol!` macro from alloy_sol_macro reads a Solidity interface file /// and generates corresponding Rust types and encoding/decoding functions. pub mod solidity { use alloy_sol_macro::sol; pub use ITypes::*; // The objects here will be generated automatically into Rust types. sol!("../../src/interfaces/ITypes.sol"); // Encode string input from the trigger contract function sol! { function addTrigger(string data) external; } } ``` Visit the [Blockchain interactions page](../handbook/components/blockchain-interactions) for more information on the `sol!` macro and how to use it to generate Rust types from Solidity interfaces. ## Oracle component logic The [`lib.rs`](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/components/evm-price-oracle/src/lib.rs) file contains the main component logic for the oracle. The first section of the code req.headers_mut().insert("Content-Type", HeaderValue::from_static("application/json")); req.headers_mut() .insert("User-Agent", HeaderValue::from_static("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36")); req.headers_mut().insert( "Cookie", HeaderValue::from_str(&format!("myrandom_cookie={}", current_time)).unwrap(), ); let json: Root = fetch_json(req).await.map_err(|e| e.to_string())?; // round to the nearest 3 decimal places let price = (json.data.statistics.price * 100.0).round() / 100.0; // timestamp is 2025-04-30T19:59:44.161Z, becomes 2025-04-30T19:59:44 let timestamp = json.status.timestamp.split('.').next().unwrap_or(""); Ok(PriceFeedData { symbol: json.data.symbol, price, timestamp: timestamp.to_string() }) } ``` ## Handling the response The processed price data is returned as a `WasmResponse` which contains the response payload. The response is formatted differently based on the destination. ```rust lib.rs let output = match dest { Destination::Ethereum => Some(encode_trigger_output(trigger_id, &res)), Destination::CliOutput => Some(WasmResponse { payload: res.into(), ordering: None }), }; Ok(output) ``` - For `Destination::CliOutput`, the raw data is returned directly for local testing and debugging using the `wasi-exec` command. - For `Destination::Ethereum`, the data is ABI encoded using `encode_trigger_output`. This ensures that processed data is formatted correctly before being sent to the [submission contract](../handbook/submission). In `trigger.rs`, the `WasmResponse` struct is used to standardize the format of data returned from components. The `payload` field contains the processed data from the component. ```rust trigger.rs pub fn encode_trigger_output(trigger_id: u64, output: impl AsRef) -> WasmResponse { WasmResponse { payload: solidity::DataWithId { triggerId: trigger_id, data: output.as_ref().to_vec().into(), } .abi_encode(), ordering: None, } } ``` For more information on component outputs, visit the [Component page](../handbook/components/component#component-output). To learn more about submission logic, visit the [Submission page](../handbook/submission). The Service handbook contains more detailed information on each part of developing services and components. Visit the [Service handbook overview](../handbook/overview) to learn more. ## Next steps Continue to the [next section](./5-build) to learn how to build and test your component. --- # 5. Build and test components URL: /tutorial/5-build Description: Building and testing WAVS service components **Before proceeding:** {/* This section is also in [](./2-setup.mdx). Remember to update there as well */} 1. Make sure that Docker is installed. If you are using Docker Desktop, make sure it is open and running. If you are using Mac OS, make sure that your[ Docker app is configured correctly](./2-setup#docker). 2. Make sure that you have already run the following commands from the [system setup section](./2-setup#cargo-components). ```bash docci-ignore cargo install cargo-binstall cargo binstall cargo-component wasm-tools warg-cli wkg --locked --no-confirm --force # Configure default registry wkg config --default-registry wa.dev # Allow publishing to a registry # # if WSL: `warg config --keyring-backend linux-keyutils` warg key new ``` ## Build components Run the following command in your terminal to build your component. Exclude `WASI_BUILD_DIR` to build all components. ```bash docci-if-file-not-exists="./compiled/evm_price_oracle.wasm" WASI_BUILD_DIR=components/evm-price-oracle make wasi-build ``` This command will build any components present in the `/components` directory, as well as auto-generate bindings and compile the components to WASM. The output will be placed in the `compiled` directory. **Build command:** You can also use the command below to build your solidity contracts and components in one command: ```bash docci-if-file-not-exists="./compiled/evm_price_oracle.wasm" make build ``` ## Testing and debugging You can use the following command to execute the component using Cast. This command is handy for testing components without having to deploy WAVS. An ID of 1 is Bitcoin. Nothing will be saved on-chain, just the output of the component is shown. ``` make wasi-exec ``` This command runs your component locally in a simulated environment and lets you easily view `print` statements for debugging. Running this command in the oracle example will print the information from the oracle [component code](./4-component). Visit the [component walkthrough](../handbook/components/component#logging-in-a-component) for more information on logging during testing and production. Upon successful execution, you should receive a result similar to the following: ```bash docci-ignore resp_data: Ok(PriceFeedData { symbol: "BTC", timestamp: "2025-02-14T01:23:03.963Z", price: 96761.74120116462 }) INFO Fuel used: 1477653 Result (hex encoded): 7b2273796d626f6c223a22425443222c2274696d657374616d70223a22323032352d30322d31345430313a32333a30332e3936335a222c227072696365223a39363736312e37343132303131363436327d Result (utf8): {"symbol":"BTC","timestamp":"2025-02-14T01:23:03.963Z","price":96761.74120116462} ``` **Fuel:** In the output above, the `INFO Fuel used` value represents the computational power consumed during execution. Similar to how on-chain transactions have a gas limit to cap transaction costs, WAVS enforces a fuel limit to control off-chain computational workload and protect against DoS attacks. The maximum fuel allocation can be adjusted in the `Makefile` to accommodate different processing needs. --- # 6. Run your service URL: /tutorial/6-run-service Description: Deploying and running WAVS services ## Local: Start Anvil, WAVS, and deploy Eigenlayer 1. Create a `.env` file for your project by copying over the example with the following command: ``` cp .env.example .env ``` 2. Use the following command to start an Anvil test chain, IPFS, Registry, and some optional telemetry. This only runs with `LOCAL` being set in the `.env` (default). ```bash docci-background docci-delay-after=5 make start-all-local ``` **Keep WAVS running:** The command must remain running in your terminal. Open another terminal to run other commands. You can stop the services with `ctrl+c`. Some MacOS terminals require pressing this twice. **Run super simple:** You can skip all the setup steps below and just run a single command to deploy and run the entire service setup ``` export RPC_URL=`bash ./script/get-rpc.sh` export AGGREGATOR_URL=http://127.0.0.1:8001 bash ./script/deploy-script.sh ``` This performs all the below steps (with the exception of actually triggering the contract). With the chain running, you can deploy and run your service. ## Create Deployer An account is required to upload the contracts and to be the original admin of them. The `create-deployer.sh` script creates a new wallet then sets a balance if using a local deployment, or waits until it has testnet funds before returning. You can skip this step by setting `FUNDED_KEY=` in `.env` to a private key of your choice that has network funds. ```bash docci-delay-after=2 bash ./script/create-deployer.sh ``` ## Deploy EigenLayer Middleware Local deployments use the real testnet contracts via a forked anvil instance. This middleware will setup all the required contracts and configurations for the base of your AVS. ```bash docci-delay-after=2 COMMAND=deploy make wavs-middleware ``` ## Deploy solidity contracts The `deploy-contracts.sh` script is used to deploy the trigger and submission solidity contracts to the chain. ```bash docci-delay-per-cmd=2 source script/deploy-contracts.sh ``` ## Deploy Service Deploy the compiled component with the contract information from the previous steps. ```bash docci-delay-per-cmd=3 export COMPONENT_FILENAME=evm_price_oracle.wasm export PKG_NAME="evmrustoracle" export PKG_VERSION="0.1.0" # ** Testnet Setup: https://wa.dev/account/credentials/new -> warg login source script/upload-to-wasi-registry.sh || true # Testnet: set values (default: local if not set) # export TRIGGER_CHAIN=holesky # export SUBMIT_CHAIN=holesky # Package not found with wa.dev? -- make sure it is public export AGGREGATOR_URL=http://127.0.0.1:8001 REGISTRY=${REGISTRY} source ./script/build-service.sh ``` The build-service.sh script is used to create a service manifest (service.json) with the configuration for the service, including a workflow with the trigger event, component, aggregator, submission logic, and more. Visit the [Service handbook](../handbook/service) for more information on service configuration. ## Upload to IPFS The `ipfs-upload.sh` script is used to upload the service manifest to IPFS where it can be referenced by its URI. ```bash docci-delay-per-cmd=2 # Upload service.json to IPFS SERVICE_FILE=.docker/service.json source ./script/ipfs-upload.sh ``` ## Aggregator Start the [aggregator](../handbook/submission#aggregator) and register the service with the aggregator. The aggregator is used to collect and validate responses from multiple operators before submitting them to the blockchain. ```bash docci-delay-per-cmd=2 bash ./script/create-aggregator.sh 1 IPFS_GATEWAY=${IPFS_GATEWAY} bash ./infra/aggregator-1/start.sh wget -q --header="Content-Type: application/json" --post-data="{\"uri\": \"${IPFS_URI}\"}" ${AGGREGATOR_URL}/register-service -O - ``` ## Start WAVS Create an operator and start WAVS. The create-operator.sh script configures the operator's environment and starts running WAVS. ``` bash ./script/create-operator.sh 1 IPFS_GATEWAY=${IPFS_GATEWAY} bash ./infra/wavs-1/start.sh # Deploy the service JSON to WAVS so it now watches and submits. # 'opt in' for WAVS to watch (this is before we register to Eigenlayer) WAVS_ENDPOINT=http://127.0.0.1:8000 SERVICE_URL=${IPFS_URI} IPFS_GATEWAY=${IPFS_GATEWAY} make deploy-service ``` ## Register service specific operator Each service gets its own key path (hd_path). The first service starts at 1 and increments from there. The following commands are used to register the operator with the [service manager contract](../handbook/service#service-manager). ``` SERVICE_INDEX=0 source ./script/avs-signing-key.sh # Local: export WAVS_SERVICE_MANAGER_ADDRESS=$(jq -r .addresses.WavsServiceManager ./.nodes/avs_deploy.json) # TESTNET: set WAVS_SERVICE_MANAGER_ADDRESS COMMAND="register ${OPERATOR_PRIVATE_KEY} ${AVS_SIGNING_ADDRESS} 0.001ether" make wavs-middleware # Verify registration COMMAND="list_operators" PAST_BLOCKS=500 make wavs-middleware ``` ## Trigger the service Next, use your deployed trigger contract to trigger the oracle to be run. In the following command, you'll specify the `INPUT_DATA` as abi encoded `1`, which corresponds to the ID of Bitcoin. Running this command will execute [`/script/Trigger.s.sol`](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/script/Trigger.s.sol) and pass the ID to the trigger contract, starting the following chain of events: 1. The trigger contract will emit an event with the specified ID as its data. 2. Operators listening for the event will receive the data and run it in the oracle component off-chain. 3. The oracle component will use the ID to query the price of Bitcoin from the CoinMarketCap API. 4. The returned data will be signed by operators and passed to the [aggregator and then the submission contract](../handbook/submission), which will verify the operator's signature and submit the price of Bitcoin on-chain πŸŽ‰ ```bash docci-delay-per-cmd=2 # Request BTC from CMC export INPUT_DATA=`cast abi-encode "addTrigger(string)" "1"` # Get the trigger address from previous Deploy forge script export SERVICE_TRIGGER_ADDR=`make get-trigger-from-deploy` # uses FUNDED_KEY as the executor (local: anvil account) source .env forge script ./script/Trigger.s.sol ${SERVICE_TRIGGER_ADDR} ${INPUT_DATA} --sig 'run(string,string)' --rpc-url ${RPC_URL} --broadcast ``` ## Show the result Run the following to view the result of your service in your terminal: ```bash docci-delay-per-cmd=2 docci-output-contains="BTC" # Get the latest TriggerId and show the result via `script/ShowResult.s.sol` TRIGGER_ID=1 RPC_URL=${RPC_URL} make show-result ``` Congratulations, you've just made a simple Bitcoin price oracle service using WAVS! Proceed to the [Prediction Market demo](./7-prediction) to learn how a similar oracle service can be used in a prediction market. Check out the [Service handbook](../handbook/overview) to learn more about services and creating components. --- # 7. Prediction market URL: /tutorial/7-prediction Description: prediction market demo overview {/* todo: verify the info in this page and update if needed */} Now that you've built a simple oracle service, take a look at the [WAVS Demo Repo](https://github.com/Lay3rLabs/wavs-demos/blob/main/demos/PREDICTION_MARKET_DEMO.md) to see a similar component used in action to resolve a prediction market. This page will give an overview of the prediction market demo, how it works, and how the oracle component is used to resolve markets. Prediction market demo repo: https://github.com/Lay3rLabs/wavs-demos/tree/main ## What is a prediction market? A prediction market is a marketplace that gathers insights about the future by rewarding participants for making accurate predictions based on available information. For example, a prediction market could be created for whether it will snow in Oslo on November 5th. Users can create positions by depositing money based on two outcomes: yes or no. After the event transpires, an oracle service can be used to bring in the weather outcome, resolving the market and rewarding those who predicted correctly. ## How does it work? ### Market Solidity contracts These contracts handle the creation of markets and conditional tokens. [`conditional-tokens-contracts`](https://github.com/Lay3rLabs/conditional-tokens-contracts) - these contracts are forked from Gnosis and updated to a recent Solidity version, and they are the core protocol that creates a conditional share in a future outcome. [`conditional-tokens-market-makers`](https://github.com/Lay3rLabs/conditional-tokens-market-makers) - these contracts are forked from Gnosis and updated to a recent Solidity version, and they are the market makers that create a market based on the conditional shares above. [`PredictionMarketFactory.sol`](https://github.com/Lay3rLabs/wavs-prediction-market/blob/main/src/contracts/PredictionMarketFactory.sol) - this contract sets up all the contracts required for a functioning prediction market using the forked contracts above, and it has the power to resolve the market once the outcome is determined by the oracle AVS. ### Extending the trigger contract In this demo, the oracle that resolves the market is triggered by the [`PredictionMarketOracleController.sol`](https://github.com/Lay3rLabs/wavs-prediction-market/blob/main/src/contracts/PredictionMarketOracleController.sol) contract. This contract contains modifications to the `WavsTrigger.sol` contract from the [WAVS Foundry Template repo](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/src/contracts/WavsTrigger.sol). Similar to the simple trigger contract, it passes data to the oracle AVS via the `NewTrigger` event. It extends the contract by storing trigger metadata, validating signed AVS outputs, and interacting with the external contracts mentioned above (`PredictionMarketFactory`, `MarketMaker`, and `ConditionalTokens`). It also contains logic to enforce a payment when a trigger is added: ``` function addTrigger( TriggerInputData calldata triggerData ) external payable returns (ITypes.TriggerId triggerId) { require(msg.value == 0.1 ether, "Payment must be exactly 0.1 ETH"); ``` Take a look at the [`PredictionMarketOracleController.sol`](https://github.com/Lay3rLabs/wavs-prediction-market/blob/main/src/contracts/PredictionMarketOracleController.sol) file to get an idea of how the contract is structured. This contract is responsible for interacting with the oracle service, triggering WAVS to run the oracle, waiting for the oracle's response, and telling the market factory to resolve the market. ### Oracle WASI component Similar to the oracle you created in the tutorial, the prediction market uses a simple oracle service to resolve a market by bringing off-chain data on-chain. This[ WASI component](https://github.com/Lay3rLabs/wavs-prediction-market/blob/main/components/prediction-market-oracle/src/lib.rs) runs in WAVS and fetches the prediction market's resolution when necessary. A wallet executes the [`PredictionMarketOracleController.sol`](https://github.com/Lay3rLabs/wavs-prediction-market/blob/main/src/contracts/PredictionMarketOracleController.sol#L65) contract's `addTrigger` function, which triggers WAVS to run this oracle by emitting an event. Then, WAVS commits the response from this oracle component with a signature back to the contract on-chain, and the market is resolved. Below is the `run` function for the Prediction Market oracle component. This function is responsible for fetching the price of Bitcoin and resolving the market based on the price. ``` impl Guest for Component { fn run(action: TriggerAction) -> std::result::Result, String> { let market_maker_address = config_var("market_maker").ok_or_else(|| "Failed to get market maker address")?; let conditional_tokens_address = config_var("conditional_tokens") .ok_or_else(|| "Failed to get conditional tokens address")?; let trigger_info = decode_trigger_event(action.data)?; let bitcoin_price = block_on(get_price_feed(1))?; // Resolve the market as YES if the price of Bitcoin is over $1. let result = bitcoin_price > 1.0; Ok(Some(WasmResponse { payload: encode_trigger_output( trigger_info.triggerId, Address::from_str(&market_maker_address).unwrap(), Address::from_str(&conditional_tokens_address).unwrap(), result, ), ordering: None, })) } } ``` Performing this market resolution via WAVS means prediction markets can exist in a fully decentralized manner. Because money is on the line, entrusting any party to honestly resolve the market is a critical security decisionβ€”WAVS enables distributing this trust over multiple independent parties, taking advantage of the verifiability and security of the existing WAVS infrastructure instead of relying on any centralized individual. ## Try it out You can run the prediction market demo locally by following the steps from the [README](https://github.com/Lay3rLabs/wavs-prediction-market/tree/main). Follow along in the video tutorial to see how to run the prediction market demo locally: --- # AI-powered component creation URL: /handbook/ai Description: Use Claude or Cursor to create one-shot components with minimal prompting The WAVS Foundry Template contains built-in AI rulefiles for creating "one-shot" components with minimal prompting in Cursor or Claude Code. These rulefiles are an experimental feature and may not work as expected every time. Components created with AI should not be used in production without thorough review and testing. **LLM resources:** For more information on AI tools and AI-accessible documentation, visit the [LLM resources page](/resources/llms). ## Claude Code - Follow the [Claude Code installation instructions](https://docs.anthropic.com/en/docs/claude-code/getting-started) to install Claude Code and link your account. - The Claude rulefile is `claude.md` and contains instructions for Claude on how to create a component. - Learn more about Claude rulefiles: https://docs.anthropic.com/en/docs/claude-code/memory ## Cursor - Download Cursor: https://www.cursor.com/downloads - The Cursor rulefiles are located in the `.cursor/rules` directory. - When using Cursor, always attach the `component-rules.mdc` file to the chat with your prompt. - Learn more about Cursor rulefiles: https://docs.cursor.com/context/rules ## Using AI to create components 1. Clone the [WAVS Foundry Template](https://github.com/Lay3rLabs/wavs-foundry-template) and follow the system setup requirements in the README. ``` git clone https://github.com/Lay3rLabs/wavs-foundry-template.git cd wavs-foundry-template git checkout main # Follow the system setup requirements in the README. ``` 2. Open Claude Code or Cursor in the root of the template. ``` claude # or cursor . ``` **Sandboxed Claude Code:** You can run a sandboxed instance of [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview) in a Docker container that only has access to this project's files by running the following command from the root of the project: ```bash docci-ignore npm run claude-code # or with no restrictions (--dangerously-skip-permissions) npm run claude-code:unrestricted ``` 3. Enter your prompt in the agent chat. You can use the following examples as a starting point, or you can create your own prompt. **Attaching rulefiles:** If you are using cursor, always attach `component-rules.mdc` file to the chat with your prompt. ``` @component-rules.mdc ``` ### Prompt examples These simple examples are provided to get you started. #### API component You can make a very simple prompt to create a component that can bring API responses verifiably onchain by including the API endpoint: ``` Let's make a component that takes the input of a zip code, queries the openbrewerydb, and returns the breweries in the area. @https://api.openbrewerydb.org/v1/breweries?by_postal=92101&per_page=3 ``` #### Contract balance component You can also make components that interact with the blockchain: ``` I want to build a new component that takes the input of a wallet address, queries the usdt contract, and returns the balance of that address. ``` #### Verifiable AI component ``` Please make a component that takes a prompt as input, sends an api request to OpenAI, and returns the response. Use this api structure: { "seed": $SEED, "model": "gpt-4o", "messages": [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": ""} ] } My api key is WAVS_ENV_OPENAI_KEY in my .env file. ``` You'll need an [OPENAI API account and key](https://platform.openai.com/login) to use this prompt. The agent will include your API key in the component as a [private variable](./components/variables). Make sure to include your API key in a `.env` file: ``` # copy the .env.example file cp .env.example .env # place your key in .env (must be prefixed with WAVS_ENV_) WAVS_ENV_OPENAI_KEY=your_api_key ``` This example utilizes the OpenAI API with a [seed](https://platform.openai.com/docs/advanced-usage#reproducible-outputs) to make the response more deterministic. Please note that OpenAI models are not guaranteed to be 100% deterministic. This example is for demonstration purposes and should not be used in production. ## Component creation process 4. After receiving the prompt, the agent will start creating your component. Review the agent's work and accept changes carefully. Make sure to double check what the agent is doing and be safe about accepting changes. 5. The agent will start by planning its component and will create a `plan.md` file. The agent will then make a new component and files according to this plan. 6. The agent will test its component for errors by running validation tests using `make validate-component COMPONENT=your-component`. 7. The agent may need to make changes after running the Validation tests. After making changes, the agent will build the component using `WASI_BUILD_DIR=components/my-component make wasi-build`. 8. After successfully building your component, it's time to test it. The following command can be used to test your component logic without deploying WAVS. Make sure to replace the placeholders with the correct inputs. ``` # Run this command to build the component: WASI_BUILD_DIR=components/openai-response make wasi-build # Once built, test it with: export COMPONENT_FILENAME=openai_response.wasm export INPUT_DATA="Only respond with yes or no: Is AI beneficial to the world?" make wasi-exec ``` The agent may try to run the `make wasi-exec` command themselves. You should prompt the agent to give you the command instead, as it can't run the command without permissions. 9. Your component should execute and return a response. If there are any errors, share them with the agent for troubleshooting. If you have any questions, join the WAVS DEVS Telegram channel: https://t.me/layer_xyz/818 ## Tips for working with AI agents - While this repo contains rulefiles with enough context for creating simple components, coding agents are unpredictable and may inevitably run into problems. - Feel free to update the rulefiles for your specific purposes or if you run into regular errors. - Coding agents can sometimes try to over-engineer their fixes for errors. If you feel it is not being productive, it may be beneficial to start fresh. You may need to adjust your prompt. - If you are building a complex component, it may be helpful to have the agent build a simple component first and then expand upon it. - The agent may try to fix warnings unnecessarily. You can tell the agent to ignore minor warnings and any errors found in `bindings.rs` (it is auto-generated). ### Prompting This repo is designed to be used with short prompts for simple components. However, often, coding agents will do better with more context. When creating a prompt, consider the following: - Agents work best with short, clear instructions. - Provide relevant documentation (preferably as an `.md` file or other ai-digestible content). - Provide endpoints. - You may need to provide API response structure if the agent is not understanding responses. - Be specific about what you want the agent to build. - Agents work systematically to build components. For best results, agent should make a plan before they start building. - Be patient. Coding agents are not perfect. They may make mistakes. ## Troubleshooting - You can ask the agent to fix errors it may not be able to catch when executing components. Make sure to give the agent full context of the error. - LLMs can be unpredictable. Minimal prompts provide a lot of room for creativity/error. If the agent is not able to fix an error after trying, sometimes deleting the component, clearing the history, and starting fresh can help. - The agent may try to edit the bindings.rs file to "fix" it. The agent never needs to do this, and you should tell the agent to not do this. - The agent is supposed to provide you with the `make wasi-exec` command. Sometimes it will try to run this itself and it will fail. Instead, ask it to give you the command. - When copying and pasting the full `make wasi-exec` command, be careful with line breaks. You may need to reformat long lines to avoid breaking the command. --- # Makefile commands URL: /handbook/commands Description: CLI commands for WAVS development ## Commands Use `make help` to see all the commands: ``` make help ``` Here are the available `make` commands and their descriptions: ``` build building the project wasi-build building WAVS wasi components | WASI_BUILD_DIR wasi-exec executing the WAVS wasi component(s) with ABI function | COMPONENT_FILENAME, INPUT_DATA wasi-exec-fixed the same as wasi-exec, except uses a fixed input as bytes (used in Go & TS components) | COMPONENT_FILENAME, INPUT_DATA clean cleaning the project files clean-docker remove unused docker containers validate-component validate a WAVS component against best practices fmt formatting solidity and rust code test running tests setup install initial dependencies start-all-local starting anvil and core services (like IPFS for example) get-trigger-from-deploy getting the trigger address from the script deploy get-submit-from-deploy getting the submit address from the script deploy wavs-cli running wavs-cli in docker upload-component uploading the WAVS component | COMPONENT_FILENAME, WAVS_ENDPOINT deploy-service deploying the WAVS component service json | SERVICE_URL, CREDENTIAL, WAVS_ENDPOINT get-trigger get the trigger id | SERVICE_TRIGGER_ADDR, RPC_URL show-result showing the result | SERVICE_SUBMISSION_ADDR, TRIGGER_ID, RPC_URL upload-to-ipfs uploading the a service config to IPFS | SERVICE_FILE, [PINATA_API_KEY] update-submodules update the git submodules check-requirements verify system requirements are installed ``` For more information on commands when using the template, visit the [WAVS tutorial](/tutorial/1-overview). --- # Handbook overview URL: /handbook/overview Description: Guide to WAVS handbook structure and contents **Follow the tutorial:** {/* todo: verify all links are correct. */} Before reading this guide, follow the [Oracle component tutorial](/tutorial/1-overview) to learn the basics of building a WAVS service. This handbook provides an overview of the different parts that make up a WAVS AVS. ## Tutorial - [Oracle component tutorial](/tutorial/1-overview) - Start here to learn the basics of building a WAVS service. ## Core Concepts - [How it works](../how-it-works) - Learn about the different parts that make up a WAVS AVS. - [Design](../design) - Learn about the design considerations for building a WAVS AVS. ## Services - [Service](./service) - Learn about WAVS services, their structure, and how they are defined in the service manifest. - [Workflows](./workflows) - Understand how workflows define execution paths in your service, including triggers, components, and submissions. - [Triggers](./triggers) - Explore the different types of triggers that can initiate your service, including EVM events, Cosmos events, cron jobs, and block intervals. - [Submission and Aggregator](./submission) - Discover how results are submitted to the blockchain through the aggregator service and submission contracts. ## Components - [Component overview](./components/component) - Learn about the structure and lifecycle of WAVS components, including how to handle triggers and process data. - [Variables](./components/variables) - Understand how to configure components with public and private variables. - [Blockchain interactions](./components/blockchain-interactions) - Discover how to interact with blockchains and smart contracts from your components. - [Network requests](./components/network-requests) - Learn how to make HTTP requests to external APIs from your components. ## Development - [Template](./template) - Get started with the WAVS template, including its structure, configuration files, and how to customize it for your service. - [Makefile commands](./commands) - Reference for the available makefile commands to build, deploy, and manage your service. - [AI-powered component creation](./ai) - Learn how to use AI coding agents to create components. Each section provides detailed information and examples to help you understand and build your WAVS service. Start with the Service section to understand the basic concepts, then explore the other sections based on your needs. --- # Service manifest and manager URL: /handbook/service Description: Overview of the service.json manifest file and service manager contract A service is a collection of smart contracts, operators, and offchain components that make up a WAVS AVS. The different parts of a service are defined in a service manifest or `service.json` file. This file can be stored on IPFS or an HTTP/HTTPS server, and its URL is set on the service manager contract during deployment, allowing the system to fetch the service definition when needed. The service manifest defines the configuration and different parts of a WAVS service, including information about the service, [workflows](/handbook/workflows), [components](/handbook/components/component), [submission](/handbook/submission), [service manager contract](#service-manager), and more. ## Generate Manifest You can create the service.json file using the `wavs-cli service` command. The template provides a script to generate a single-component service with ease, [build_service.sh](https://github.com/Lay3rLabs/wavs-foundry-template/blob/main/script/build-service.sh). ## Example Manifest ```json service.json { // Basic service information "id": "example-service-123", "name": "Example WAVS Service", // Workflows define the different execution paths in your service "workflows": { // Each workflow has a unique ID "default": { // Trigger defines what initiates the workflow "trigger": { // This example uses an EVM contract event trigger "evm_contract_event": { "chain_name": "ethereum", "address": "0x1234567890123456789012345678901234567890", "event_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" } }, // Component defines the WASI component to execute "component": { "source": { "Registry": { "registry": { // (SHA-256) hash that identifies a specific version of a WASI component's code in WAVS "digest": "882b992af8f78e0aaceaf9609c7ba2ce80a22c521789c94ae1960c43a98295f5", // The domain the warg WASI component is hosted on "domain": "localhost:8090", // The version of this component "version": "0.1.0", // The package name of the component on the domain registry "package": "example:evmrustoracle" } } }, // Permissions define what the component can access "permissions": { // HTTP host permissions "allowed_http_hosts": "all" // Options: "all", ["host1", "host2"], or "none" // File system permissions "file_system": true }, // Resource limits "fuel_limit": 1000000, // Optional: Maximum compute units "time_limit_seconds": 30, // Optional: Maximum execution time // Component-specific configuration "config": { "endpoint": "https://api.example.com", "timeout": "30s" }, // Environment variables to be passed to the component "env_keys": [ "WAVS_ENV_API_KEY", "WAVS_ENV_SECRET" ] }, // Submit defines where the results are sent "submit": { // The aggregator configuration "aggregator": { "url": "http://127.0.0.1:8001" } }, "aggregators": [ { "evm": { // The identifier for the chain the submission contract (set in wavs.toml) "chain_name": "ethereum", // The address of the submission contract with the service handler interface "address": "0xfedcba9876543210fedcba9876543210fedcba98", // The maximum amount of gas for submission "max_gas": 1000000 // Optional } } ] } //other workflows can be added here }, // Service status "status": "active", // Options: "active" or "inactive" // Service manager configuration "manager": { "evm": { "chain_name": "ethereum", // The address of the service manager contract "address": "0xabcdef1234567890abcdef1234567890abcdef12" } } } ``` ## Upload This file should be uploaded to IPFS, or some other hosted service that all operators can access. The template launches a local IPFS for testing. Use a service like [Pinata](https://app.pinata.cloud/developers/api-keys) for production services. ``` # Upload to local or remote IPFS (smart routes based on .env deploy configuration) SERVICE_FILE=${SERVICE_FILE} make upload-to-ipfs # smart grabs the IPFS gateway and fetches the content that was uploaded export IPFS_GATEWAY=$(sh script/get-ipfs-gateway.sh) curl "${IPFS_GATEWAY}${ipfs_cid}" # Then the admin of the contracts can set it cast send ${WAVS_SERVICE_MANAGER_ADDRESS} 'setServiceURI(string)' "${SERVICE_URI}" -r ${RPC_URL} --private-key ${DEPLOYER_PK} ``` For more information on the different parts of a service manifest, see the following sections: - [Workflows](./workflows) - [Triggers](./triggers) - [Components](./components/component) - [Submission and aggregator](./submission) ## Service manager The service manager contract defines the set of registered operators for a service. Only operators registered in this contract are considered valid signers for result submissions in a service. Each registered operator is assigned a weight. These weights count toward a threshold for their submission power. The service manager also maintains a service URI that points to the service manifest, connecting the operators to the service. Signatures are created by operators using their private keys to sign an envelope containing the data, and these signatures are collected by the aggregator which then submits them to the service manager contract for validation. The service manager contract validates that the signatures are from registered operators, checks that their total weight meets the threshold, and ensures the operators are properly sorted before allowing the data to be processed by the [service handler](/handbook/submission) contract. --- # Submission and aggregator URL: /handbook/submission Description: Creating and configuring submission contracts and the aggregator This page describes the submission and aggregator services used to submit results from a workflow to a submission contract on an EVM chain. ## Submit definition The `submit` field in a service.json file specifies the submission logic for a service. The `aggregator` type sends results to an aggregator service, which validates the results and submits them to a target contract on an EVM chain. ```json service.json "submit": { // Where results are sent "aggregator": { // Type of submission (aggregator) "url": "http://127.0.0.1:8001" // Local aggregator endpoint } }, "aggregators": [ // The final submission address that the aggregator will submit to { "evm": { // EVM chain configuration "chain_name": "local", // Local Ethereum chain "address": "0xd6f8ff0036d8b2088107902102f9415330868109", // Submission contract address "max_gas": 5000000 // Maximum gas limit for transactions } } ] ``` Submit can also be set to `none` if the service does not need to submit results to a contract. The component will still run, but the results will not be submitted. ## Submission contract A service handler or submission contract handles the logic for verifying the submission of a component's output to the blockchain. The only requirement for a submission contract is that it must implement the `handleSignedEnvelope()` function using the `IWavsServiceHandler` interface to validate data and signatures using the service manager. This interface is defined in the `@wavs` package: https://www.npmjs.com/package/@wavs/solidity?activeTab=code **Chaining workflows:** Workflows can be chained together by setting the trigger event of one workflow to the submission event of another workflow. For more information on chaining workflows, see the [Workflows page](./workflows). ## Template submission example The [template submission contract](https://github.com/Lay3rLabs/wavs-foundry-template/blob/main/src/contracts/WavsSubmit.sol) uses the `handleSignedEnvelope()` function to validate operator signatures and store the processed data from the component. The `DataWithId` struct must match the output format from the component. In the template, each trigger has a unique ID that links the data to its source. Below is a simplified version of the template submission contract: ```solidity WavsSubmit.sol // Contract must implement IWavsServiceHandler to receive data // ITypes provides the DataWithId struct and other type definitions contract SimpleSubmit is ITypes, IWavsServiceHandler { /// @notice Service manager instance - used to validate incoming data and signatures IWavsServiceManager private _serviceManager; /** * @notice Initialize the contract with a service manager * @param serviceManager The service manager instance that will validate data */ constructor(IWavsServiceManager serviceManager) { _serviceManager = serviceManager; } /// @inheritdoc IWavsServiceHandler /// @notice Main entry point for receiving and processing data /// @param envelope Contains the event ID and the actual data payload /// @param signatureData Contains operator signatures for validation function handleSignedEnvelope(Envelope calldata envelope, SignatureData calldata signatureData) external { // First validate the data and signatures through the service manager // This ensures the data is properly signed by authorized operators _serviceManager.validate(envelope, signatureData); // Decode the payload into your expected data structure // The payload format must match what your component outputs DataWithId memory dataWithId = abi.decode(envelope.payload, (DataWithId)); // At this point, you can safely process the validated data // Add your custom logic here to handle the data } } ``` ## Aggregator The aggregator is used to collect and validate responses from multiple operators before submitting them to the blockchain. It acts as an intermediary that receives signed responses from operators, validates each operator's signature, aggregates signatures when enough operators have responded, and submits the aggregated data to the submission contract. The aggregator supports exact match aggregation, meaning that consensus is reached if a threshold amount of submitted responses from operators are identical. Visit the [design considerations page](/design) for more information on aggregation and service design. WAVS currently uses ECDSA signatures for aggregation, but will also support BLS signatures in the future. ## Aggregator submission flow 1. An operator runs a component which returns a `WasmResponse` containing: - `payload`: The result data - `ordering`: Optional ordering information 2. The operator creates an Envelope containing the result data and signs it with their private key, creating an signature. 3. A Packet containing the envelope, signature, and route information (service ID and workflow ID) is created and sent to the aggregator's `/packet` endpoint. 4. The aggregator validates the packet's signature by recovering the operator's address and adds it to a queue of packets with the same trigger event and service ID. 5. When enough packets accumulate to meet the threshold (determined by the service manager contract), the aggregator: - Combines the signatures from all packets into a single SignatureData structure - Validates the combined signatures on-chain through the service manager contract 6. If validation succeeds, the aggregator sends the operator signatures and payload result data as a single transaction to the `handleSignedEnvelope()` function on the submit contract specified in the service's manifest. 7. The `handleSignedEnvelope()` function validates the data and signatures via the service manager contract. --- # Template overview URL: /handbook/template Description: Overview of the WAVS foundry template **Follow the tutorial:** Before reading this guide, follow the [Oracle component tutorial](/tutorial/1-overview) to learn the basics of building a WAVS service. Use the info in this guide to customize the template to create your own custom service. Check out the [WAVS design considerations](/design) page to learn which use-cases WAVS is best suited for. ## Foundry Template structure [The WAVS template](https://github.com/Lay3rLabs/wavs-foundry-template) is made up of the following main files: ``` wavs-foundry-template/ β”œβ”€β”€ README.md β”œβ”€β”€ makefile # Commands, variables, and configs β”œβ”€β”€ components/ # WASI components β”‚ └── evm-price-oracle/ β”‚ β”œβ”€β”€ src/ β”‚ β”‚ β”œβ”€β”€ lib.rs # Main Component logic β”‚ β”‚ β”œβ”€β”€ trigger.rs # Trigger handling β”‚ β”‚ └── bindings.rs # Bindings generated by `make build` β”‚ └── Cargo.toml # Component dependencies β”œβ”€β”€ compiled/ # WASM files compiled by `make build` β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ contracts/ # Trigger and submission contracts β”‚ └── interfaces/ # Solidity interfaces β”œβ”€β”€ script/ # Deployment & interaction scripts β”œβ”€β”€ wavs.toml # WAVS service configuration β”œβ”€β”€ docs/ # Documentation β”œβ”€β”€ .cursor/rules/ # Cursor AI rulefiles β”œβ”€β”€ claude.md # Claude AI rulefile └── .env # Private environment variables ``` - The `README` file contains the tutorial commands. - The `makefile` contains commands for building and deploying the service. It also contains configurable variables for the service and deployment. - The `components` directory contains the component logic for your service. Running `make wasi-build` will automatically generate bindings and compile components into the `compiled` directory. - The `src` directory contains the Solidity contracts and interfaces for trigger and submission contracts. - The `script` directory contains the scripts used in the makefile commands to deploy, trigger, and test the service. - The `.env` file contains private environment variables and keys. Use `cp .env.example .env` to copy the example `.env` file. - The `.cursor/rules` directory and `claude.md` file contain rulefiles for [building components with Cursor AI and Claude AI agents](/handbook/ai). ## Toml files There are several toml files in the template that are used to configure the service: - `wavs.toml` is used to configure the WAVS service itself, including chains (local, testnets, mainnet) and configurations. - `Cargo.toml` in the root directory is used to configure the workspace and includes dependencies, build settings, and component metadata. - `components/*/Cargo.toml` in each component directory is used to configure the Rust component and includes dependencies, build settings, and component metadata. It can inherit dependencies from the root `Cargo.toml` file using `workspace = true`. These files can be customized to suit your specific needs, and many settings can be overridden using environment variables. The following is an example of a component's `Cargo.toml` file structure: ``` [package] name = "evm-price-oracle" edition.workspace = true version.workspace = true authors.workspace = true rust-version.workspace = true repository.workspace = true [dependencies] wit-bindgen-rt ={ workspace = true } wavs-wasi-utils = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } alloy-sol-macro = { workspace = true } wstd = { workspace = true } alloy-sol-types = { workspace = true } anyhow = { workspace = true } [lib] crate-type = ["cdylib"] [profile.release] codegen-units = 1 opt-level = "s" debug = false strip = true lto = true [package.metadata.component] package = "component:evm-price-oracle" target = "wavs:worker/layer-trigger-world@0.4.0" ``` ## wavs.toml config The [`wavs.toml`](https://github.com/Lay3rLabs/wavs-foundry-template/blob/main/wavs.toml) file contains configuration settings for all WAVS components: - Default general settings (shared across all processes) - WAVS server-specific settings - CLI-specific settings - Aggregator-specific settings ### Environment Variable Overrides Environment variables can override configuration values using these patterns: - WAVS server settings: `WAVS_` - CLI settings: `WAVS_CLI_` - Aggregator settings: `WAVS_AGGREGATOR_` --- # Triggers URL: /handbook/triggers Description: Setting up and managing WAVS service triggers {/* todo: verify all code examples. link to appropriate pages. */} A trigger prompts a WAVS service to run. Operators listen for the trigger event specified by the service and execute the corresponding component off-chain. Triggers can be any onchain event emitted from any contract. ### Trigger lifecycle 1. A service is deployed with a service.json manifest which contains information on the service and [workflow](/handbook/workflows) logic (components, triggers, [submission](/handbook/submission) logic). 2. Operators maintain lookup maps to track and verify triggers. For EVM and Cosmos events, they map chain names, contract addresses, and event identifiers to trigger IDs. Block interval triggers are tracked by chain name with countdown timers, while cron triggers are managed in a priority queue ordered by execution time. 3. When a trigger is detected, operators verify it against their lookup maps according to trigger type. If a match is found, a TriggerAction is created with the trigger configuration and event data. 4. `TriggerAction` has 2 fields: `TriggerConfig` which contains the service, workflow, and trigger configuration, and `TriggerData`contains the trigger data based on the trigger type. ``` pub struct TriggerAction { pub config: TriggerConfig, // Contains service_id, workflow_id, and trigger type pub data: TriggerData, // Contains the actual trigger data } pub struct TriggerConfig { pub service_id: ServiceID, pub workflow_id: WorkflowID, pub trigger: Trigger, } pub enum TriggerData { CosmosContractEvent { //For Cosmos event triggers contract_address: layer_climb_address::Address, /// The address of the contract that emitted the event chain_name: ChainName, /// The name of the chain where the event was emitted event: cosmwasm_std::Event, /// The data that was emitted by the contract block_height: u64, /// The block height where the event was emitted }, EvmContractEvent { //For EVM event triggers contract_address: alloy_primitives::Address, /// The address of the contract that emitted the event chain_name: ChainName, /// The name of the chain where the event was emitted log: LogData, /// The raw event log block_height: u64, /// The block height where the event was emitted }, BlockInterval { //For block interval triggers chain_name: ChainName, /// The name of the chain where the blocks are checked block_height: u64, /// The block height where the event was emitted }, Cron { //For cron triggers trigger_time: Timestamp, /// The trigger time } } ``` 5. The TriggerAction is converted to a WASI-compatible format and passed to the component where it is decoded and processed. The component decodes the incoming event trigger data using the `decode_event_log_data!` macro from the [`wavs-wasi-utils` crate](https://docs.rs/wavs-wasi-utils/latest/wavs_wasi_utils/macro.decode_event_log_data.html). Visit the [components page](./components/component) for more information on decoding and processing trigger data in your component. ## Trigger configuration Triggers define when and how the component should be executed. Each workflow needs a trigger to be set. They are set in the `trigger` field of the [`service.json` file](/handbook/service). ### EVM event trigger This trigger listens for specific events emitted by contracts on EVM-compatible chains, executing the component when a matching event is detected. Event triggers pass raw log data to the component. ``` "trigger": { "evm_contract_event": { "address": "0x00000000219ab540356cbb839cbe05303d7705fa", // Contract address to monitor "chain_name": "ethereum", // Chain to monitor "event_hash": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" // Event hash (32 bytes) } } ``` Your evm chain information must be set in `wavs.toml` under the `[default.chains.evm.]` section: ```toml wavs.toml # Mainnet [default.chains.evm.ethereum] chain_id = "1" ws_endpoint = "wss://eth.drpc.org" http_endpoint = "https://eth.drpc.org" ``` You'll need to set your EVM chain credential in your `.env` file to establish a connection for monitoring the EVM chain: ```env .env WAVS_CLI_EVM_CREDENTIAL="0x5ze146f435835b1762ed602088740d201b68fd94bf808f97fd04588f1a63c9ab" ``` ### Cosmos event trigger This trigger monitors events emitted by Cosmos smart contracts, executing your component when a matching event type is detected from the specified contract address. Cosmos event triggers pass the contract data that was emitted by the contract to the component. ``` "trigger": { "cosmos_contract_event": { "address": { "Cosmos": { "bech32_addr": "neutron1qlaq54uh9f52d3p66q77s6kh9k9ee3vasy8gkdkk3yvgezcs6zts0mkcv4", // Contract address to monitor "prefix_len": 7 // Length of the Bech32 prefix (7 for Neutron) } }, "chain_name": "neutron", // Chain to monitor "event_type": "send_nft" // Event type to watch } }, ``` Your chain information must be set in `wavs.toml` under the `[default.chains.cosmos.]` section: ```toml wavs.toml # == Cosmos chains == [default.chains.cosmos.neutron] chain_id = "pion-1" bech32_prefix = "neutron" rpc_endpoint = "https://rpc-falcron.pion-1.ntrn.tech" grpc_endpoint = "http://grpc-falcron.pion-1.ntrn.tech:80" gas_price = 0.0053 gas_denom = "untrn" ``` Your Cosmos mnemonic must be set in your `.env` file to establish a connection for monitoring the Cosmos chain: ```env .env WAVS_CLI_COSMOS_MNEMONIC="large slab plate twenty laundry illegal vacuum phone drum example topic reason" ``` ### Cron trigger Executes your component on a schedule defined by a cron expression, with optional start and end times to control the execution window. If no start or end time is provided, the component will start immediately and run indefinitely. Cron triggers pass the trigger timestamp to the component. ``` "trigger": { "cron": { "schedule": "0 */5 * * * *", // Every 5 minutes (at 0 seconds) "start_time": 1704067200000000000, // Optional start time in nanoseconds since Unix epoch (2024-01-01T00:00:00Z) (default: null) "end_time": 1735689599000000000 // Optional end time in nanoseconds since Unix epoch (default: null) } } // Cron Expression Format: // * * * * * * // β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ // β”‚ β”‚ β”‚ β”‚ β”‚ └── Day of week (0-6, where 0 is Sunday) // β”‚ β”‚ β”‚ β”‚ └──── Month (1-12) // β”‚ β”‚ β”‚ └────── Day of month (1-31) // β”‚ β”‚ └──────── Hour (0-23) // β”‚ └────────── Minute (0-59) // └──────────── Second (0-59) // // Each field can be: // - A number: `5` (exact time) // - A range: `1-5` (1 through 5) // - A list: `1,3,5` (1, 3, and 5) // - A step: `*/5` (every 5 units) // - An asterisk: `*` (every unit) // // Common examples: // - `0 */5 * * * *` - Every 5 minutes (at 0 seconds) // - `0 0 */6 * * *` - Every 6 hours (at 0 minutes and 0 seconds) // - `0 0 0 * * *` - Every day at midnight (00:00:00) // - `0 0 12 * * *` - Every day at noon (12:00:00) // - `0 0 12 1 * *` - Noon on the first day of every month // - `0 0 12 * * 1` - Noon every Monday ``` [Crontab.guru](https://crontab.guru/) is a helpful tool for making cron expressions. **Cron trigger latency:** There may be slight variations in Cron trigger execution time between operators due to network latency and clock drift. Cron triggers are best suited for tasks that don't require precise synchronization between operators: - Triggering components that don't need exact synchronization. - Collecting data from external services with monotonic pagination. - Background tasks where eventual consistency is acceptable. If you need precise timing synchronization between operators, consider using [block-based triggers](#block-trigger) instead. ### Block trigger Executes your component at regular block intervals on a specified EVM or Cosmos chain, useful for chain-specific operations that need to run periodically. Block interval triggers pass the block height and chain name to the component. ``` "trigger": { "block_interval": { "chain_name": "ethereum-mainnet", "n_blocks": 10, "start_block": null, // Optional blockheight to start "end_block": null // Optional blockheight to end } } ``` --- # Workflows URL: /handbook/workflows Description: Building and managing WAVS service workflows A WAVS service is a collection of one or more workflows that define the different execution paths in your service. Each workflow consists of three parts: - [**Trigger**](./triggers): Defines what event initiates the workflow - [**Component**](./components/component): The WASM component that processes the event - [**Submit**](./submission): Specifies where to send the results ## Workflow Structure Workflows are defined in the service manifest JSON file, which contains the necessary information on which trigger, component, and submission logic are needed. The following example shows a workflow with a cron trigger and a submission to an aggregator: ```json service.json // ... other parts of the service manifest "workflows": { //workflows are added here "0196c34d-003d-7412-a3f3-70f8ec664e12": { // a unique workflow ID (default is a generated UUID v7) "trigger": { // Defines what starts the workflow "cron": { // Type of trigger (cron job) "schedule": "0 * * * * *", // Runs every minute at 0 seconds "start_time": null, "end_time": null } }, "component": { // the WASI component containing the business logic of the workflow "source": { // Where the component code comes from "Digest": "65747b4b1a7fa98cab6abd9a81a6102068de77b1040b94de904112272b226f51" // SHA-256 hash of the component's bytecode }, "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 passed to the component "nft": "0xb5d4D4a87Cb07f33b5FAd6736D8F1EE7D255d9E9", // NFT contract address "reward_token": "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": { // Where results are sent "aggregator": { // Type of submission (aggregator) "url": "http://127.0.0.1:8001" // Local aggregator endpoint } }, "aggregators": [ // The final submission address that the aggregator will submit to { "evm": { // EVM chain configuration "chain_name": "local", // Local Ethereum chain "address": "0xd6f8ff0036d8b2088107902102f9415330868109", // Contract address "max_gas": 5000000 // Maximum gas limit for transactions } } ] } // other workflows can be added here... }, // ... the rest of the service manifest ``` ## Multi-workflow services A WAVS service can have one or multiple workflows. You can specify multiple workflows as objects in the service manifest. Each workflow can have a different trigger, component, and submission logic. All workflows in a service will share the same service manager and operator set. {/* todo: link above to service manager page. */} ``` { "workflows": { "workflow-uuid-1": { "trigger": { ... }, "component": { ... }, "submit": { ... } }, "workflow-uuid-2": { "trigger": { ... }, "component": { ... }, "submit": { ... } } // ... more workflows can be added here } } ``` ## Workflow isolation Each workflow execution is completely isolated with components running in separate WebAssembly environments. Each execution has its own memory space and components cannot directly access each other's memory or state. ## Sharing state WAVS services are designed to process data rather than store data. Data should be stored externally. To share data between workflows or components, the first workflow should submit data to an external system such as an onchain smart contract and the second workflow should read the data from the same system. ``` A: Trigger -> component -> onchain submission storage B: Trigger -> component (reads from A's onchain submission storage) -> onchain submission storage ``` 1. Workflow A submits data to a contract or external system 2. Workflow B reads data from the same contract or system Visit the [WAVS design considerations page](../design) for more information on best practices for WAVS services and storing data. ## Chaining workflows You can chain workflows together to create more complex execution flows. To have one workflow trigger another, set the event trigger of the second workflow to the onchain submission event of the first workflow. ``` { "workflows": { "workflow-uuid-1": { "trigger": { ... }, // trigger for first workflow "component": { ... }, // component for first workflow "submit": { ... } // submission logic for first workflow }, "workflow-uuid-2": { "trigger": { ... }, // trigger for second workflow is the onchain submission event of the first workflow "component": { ... }, // component for second workflow "submit": { ... } // submission logic for second workflow } } } ``` You can also chain different services together with this method by setting the trigger of the second service to the onchain submission event of the first service. ## Multichain services WAVS enables multichain services by allowing contract event or block height triggers on Cosmos or EVM chains (with more coming soon). This architecture lets you create cross-chain services that monitor events or block heights on one chain and submit the results to Ethereum. Visit the [Trigger page](./triggers) for more info. --- # Blockchain interactions URL: /handbook/components/blockchain-interactions Description: Interacting with blockchains from WAVS components Components can interact with blockchains and smart contracts by using crates like [`wavs-wasi-utils`](https://docs.rs/wavs-wasi-utils/latest/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](https://docs.rs/wavs-wasi-utils/latest/wavs_wasi_utils/index.html). 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. ```toml 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. ```rust trigger.rs pub 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"); // Define a simple struct representing the function that encodes string input sol! { function addTrigger(string data) external; } } ``` 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`](https://docs.rs/wavs-wasi-utils/latest/wavs_wasi_utils/evm/fn.new_evm_provider.html) can be used to create a provider for a given chain. ```rust 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 = new_evm_provider::( chain_config.http_endpoint.unwrap(), ); ``` ## Example: Querying NFT balance Here's an [example](https://github.com/Lay3rLabs/wavs-art/blob/main/components/autonomous-artist/src/evm.rs) demonstrating how to query the balance of an ERC721 NFT contract for a given owner address: ```rust 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 { // 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 = new_evm_provider::( 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](https://crates.io/crates/alloy-contract) page for more information. See the [wavs-wasi-utils documentation](https://docs.rs/wavs-wasi-utils/latest/wavs_wasi_utils/) and the [Alloy documentation](https://docs.rs/alloy/latest/alloy/) for more detailed information. ### Alloy ecosystem crates The Alloy ecosystem provides a comprehensive set of crates for Ethereum development: - [`alloy-primitives`](https://docs.rs/alloy-primitives/latest/alloy_primitives/): Core Ethereum types (`Address`, `U256`, `Bytes`, etc.) - [`alloy-provider`](https://docs.rs/alloy-provider/latest/alloy_provider/): Ethereum node interaction (RPC, WebSocket, batching) - [`alloy-network`](https://docs.rs/alloy-network/latest/alloy_network/): Network types and chain-specific functionality - [`alloy-sol-types`](https://docs.rs/alloy-sol-types/latest/alloy_sol_types/): ABI handling and type generation - [`alloy-contract`](https://docs.rs/alloy-contract/latest/alloy_contract/): Contract interaction utilities ### Utility crates Essential utility crates for WAVS components: - [`wstd`](https://docs.rs/wstd/latest/wstd/): WASI standard library with `block_on` for async operations - [`serde`](https://docs.rs/serde/latest/serde/)/[`serde_json`](https://docs.rs/serde_json/latest/serde_json/): Data serialization and JSON handling - [`anyhow`](https://docs.rs/anyhow/latest/anyhow/): Error handling and propagation --- # Component overview URL: /handbook/components/component Description: Understanding WAVS service components and their structure 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: - [Go example](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/components/golang-evm-price-oracle) - [Typescript / JS example](https://github.com/Lay3rLabs/wavs-foundry-template/tree/main/components/js-evm-price-oracle) ## Component structure A basic component has three main parts: - Decoding incoming [trigger data](../triggers#trigger-lifecycle). - 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](../triggers#trigger-lifecycle) 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`. Here's how the example component handles both cases in `trigger.rs`: ``` // In trigger.rs pub fn decode_trigger_event(trigger_data: TriggerData) -> Result, 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 = ::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")), } } pub 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 addTrigger function from the trigger contract sol! { function addTrigger(string data) external; } } ``` The component decodes the incoming event trigger data using the `decode_event_log_data!` macro from the [`wavs-wasi-utils` crate](https://docs.rs/wavs-wasi-utils/latest/wavs_wasi_utils/macro.decode_event_log_data.html). The `sol!` macro from `alloy-sol-macro` is used to 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](./blockchain-interactions#sol-macro). ### 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, 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](./blockchain-interactions), [network requests](./network-requests), off-chain computations, and more. To learn about the types of components that WAVS is best suited for, visit the [design considerations](../../design) 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. ```rust 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`. ```rust 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) -> 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](../workflows) object of the [service.json](../service) file. Below is an example of the different fields that can be defined in the component object. ```json 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](./variables) page. ## Registry WAVS uses a registry to store the WASM components. A service like [wa.dev](https://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}` --- # Network requests URL: /handbook/components/network-requests Description: Making HTTP requests from WAVS components Components can make network requests to external APIs using the [`wavs-wasi-utils` crate](https://docs.rs/wavs-wasi-utils/latest/wavs_wasi_utils/index.html). To learn how to use variables like API keys in a component, visit the [Variables page](./variables). ### Dependencies The following dependencies are required for making HTTP requests from a component: ```toml Cargo.toml [dependencies] wavs-wasi-utils = "0.4.0" # HTTP utilities wstd = "0.5.3" # Runtime utilities (includes block_on) serde = { version = "1.0.219", features = ["derive"] } # Serialization serde_json = "1.0.140" # JSON handling ``` Since WASI components run in a synchronous environment but network requests are asynchronous, you can use `block_on` from the `wstd` crate to bridge this gap. The `block_on` function allows you to run async code within a synchronous context, which is essential for making HTTP requests in WAVS components. ### Making HTTP requests The `wavs-wasi-utils` crate provides several functions for making HTTP requests. See the [HTTP module documentation](https://docs.rs/wavs-wasi-utils/latest/wavs_wasi_utils/http/index.html) for more details. ``` // Request functions http_request_get(url) // Creates a GET request http_request_post_json(url, data) // Creates a POST request with JSON data http_request_post_form(url, data) // Creates a POST request with form data // Response functions fetch_json(request) // Fetches and parses JSON response fetch_string(request) // Fetches response as string fetch_bytes(request) // Fetches raw response bytes ``` ### Example: GET request with headers Here's an example showing how to make a GET request with custom headers: ```rust lib.rs use wstd::runtime::block_on; use wstd::http::HeaderValue; use wavs_wasi_utils::http::{fetch_json, http_request_get}; use serde::{Deserialize, Serialize}; // Define response type with serde derive for automatic JSON parsing #[derive(Debug, Serialize, Deserialize)] struct ApiResponse { // ... your response fields } async fn make_request() -> Result { let url = "https://api.example.com/endpoint"; let mut req = http_request_get(&url).map_err(|e| e.to_string())?; // Set headers for API requests req.headers_mut().insert( "Accept", HeaderValue::from_static("application/json") ); req.headers_mut().insert( "Content-Type", HeaderValue::from_static("application/json") ); req.headers_mut().insert( "User-Agent", HeaderValue::from_static("Mozilla/5.0") ); // Use fetch_json to automatically parse the response let json: ApiResponse = fetch_json(req) .await .map_err(|e| e.to_string())?; Ok(json) } // Use block_on to handle async code in sync context fn process_data() -> Result { block_on(async move { make_request().await })? } ``` ### Example: POST request with JSON data For making POST requests with JSON data, you can use the `http_request_post_json` helper function: ```rust lib.rs use wstd::runtime::block_on; use wavs_wasi_utils::http::{fetch_json, http_request_post_json}; use serde::{Deserialize, Serialize}; // Define request and response types with serde derive #[derive(Debug, Serialize, Deserialize)] struct PostData { key1: String, key2: i32, } #[derive(Debug, Serialize, Deserialize)] struct PostResponse { // ... response fields } async fn make_post_request() -> Result { let url = "https://api.example.com/endpoint"; let post_data = PostData { key1: "value1".to_string(), key2: 42, }; // http_request_post_json automatically sets JSON headers let response: PostResponse = fetch_json( http_request_post_json(&url, &post_data)? ).await.map_err(|e| e.to_string())?; Ok(response) } fn process_data() -> Result { block_on(async move { make_post_request().await })? } ``` For more details, visit the [wavs-wasi-utils documentation](https://docs.rs/wavs-wasi-utils/latest/wavs_wasi_utils/). --- # Utilities and crates URL: /handbook/components/utilities Description: Utilities and crates for WAVS component development {/* todo: verify content. Should we merge this into blockchain interactions? it may get too long. */} ### `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](https://docs.rs/wavs-wasi-utils/latest/wavs_wasi_utils/index.html). 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`. ### 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. ```rust 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 ### `alloy-contract` crate {/* todo: verify this is correct */} The `alloy-contract` crate provides a high-level interface for interacting with Ethereum smart contracts. It offers a type-safe way to call contract functions and handle events, with features like automatic ABI encoding/decoding, gas estimation, and transaction management. The crate works with the `sol!` macro. You can use the `#[sol(rpc)]` attribute in components to generate contract bindings that work with WASI: ``` use alloy_primitives::{Address, U256}; use alloy_sol_types::sol; use alloy_provider::RootProvider; use alloy_network::Ethereum; use wavs_wasi_utils::evm::new_evm_provider; use crate::bindings::host::get_evm_chain_config; // Define your contract interface with RPC bindings sol! { #[sol(rpc)] contract ERC721 { function balanceOf(address owner) external view returns (uint256); function totalSupply() external view returns (uint256); function ownerOfTokenByIndex(uint256 index) external view returns (address); } } // Get chain config and create provider let chain_config = get_evm_chain_config("local")?; let provider: RootProvider = new_evm_provider::( chain_config.http_endpoint.unwrap() )?; // Create contract instance with the provider let contract = ERC721::new(contract_address, &provider); // Call contract functions directly let balance = contract.balanceOf(owner).call().await?; let total_supply = contract.totalSupply().call().await?; let owner = contract.ownerOfTokenByIndex(index).call().await?; ``` ### Alloy Ecosystem Crates The Alloy ecosystem provides a comprehensive set of crates for Ethereum development: - [`alloy-primitives`](https://docs.rs/alloy-primitives/latest/alloy_primitives/): Core Ethereum types (`Address`, `U256`, `Bytes`, etc.) - [`alloy-provider`](https://docs.rs/alloy-provider/latest/alloy_provider/): Ethereum node interaction (RPC, WebSocket, batching) - [`alloy-network`](https://docs.rs/alloy-network/latest/alloy_network/): Network types and chain-specific functionality - [`alloy-sol-types`](https://docs.rs/alloy-sol-types/latest/alloy_sol_types/): ABI handling and type generation ### Utility Crates Essential utility crates for WAVS components: - [`wstd`](https://docs.rs/wstd/latest/wstd/): WASI standard library with `block_on` for async operations - [`serde`](https://docs.rs/serde/latest/serde/)/[`serde_json`](https://docs.rs/serde_json/latest/serde_json/): Data serialization and JSON handling - [`anyhow`](https://docs.rs/anyhow/latest/anyhow/): Error handling and propagation --- # Variables URL: /handbook/components/variables Description: Managing configuration variables in WAVS components Components can be configured with two types of variables: ## Public variables These variables can be used for non-sensitive information that can be viewed publicly. These variables are set in the `config` field of a service manifest. All config values are stored as strings, even for numbers. To add public variables: 1. Add the public variables to the `config` field in the service manifest: ``` "component": { "config": { "api_endpoint": "https://api.example.com", // Access using host::config_var() "max_retries": "3" // Config values are always strings } } ``` 2. Access them in the component using `host::config_var()`: ``` let value = host::config_var("api_endpoint"); ``` ## Environment keys Environment keys are private and can be used for sensitive data like API keys. These variables are set by operators in their environment and are not viewable by anyone. These variables must be prefixed with `WAVS_ENV_`. Each operator must set these variables in their environment before deploying the service. WAVS validates that all environment variables are set before allowing the service to run. To add private variables: 1. Create a new `.env` file in WAVS template: ``` # copy the example file cp .env.example .env ``` Variables can also be set in your `~/.bashrc`, `~/.zshrc`, or `~/.profile` files. 2. Set the environment variable in your `.env` file: ``` # .env file WAVS_ENV_MY_API_KEY=your_secret_key_here ``` 3. Access the environment key from a component: ``` let api_key = std::env::var("WAVS_ENV_MY_API_KEY")?; ``` 4. Before deploying a service, add the environment key to the `env_keys` array in the service manifest: ``` "component": { "env_keys": [ "WAVS_ENV_API_KEY" // Environment variables the component can access. Must be prefixed with WAVS_ENV_ ] } ``` ## Local Execution When running components locally (raw), use the `--config` flag to set values in a KEY=VALUE format, comma-separated: `--config a=1,b=2`. ``` wavs-cli exec --component --input --config api_endpoint=https://api.example.com ``` --- # LLM docs URL: /resources/llms Description: Access WAVS documentation in formats optimized for AI tools and integration. **AI components:** To learn how to create one-shot components with AI, visit the [AI-powered component creation page](/handbook/ai) The LLM text format presents documentation in a clean, plain text format optimized for large language models (LLMs) like Claude, ChatGPT, and others. ## llms.txt The `llms.txt` format is a structured index of documentation pages organized by sections, including page titles, URLs and descriptions. This format is ideal for AI assistants to understand the documentation structure without processing the full content. [https://docs.wavs.xyz/llms.txt](https://docs.wavs.xyz/llms.txt) ``` curl https://docs.wavs.xyz/llms.txt ``` ## llms-full.txt The `llms-full.txt` format returns all documentation pages as a single text document. [https://docs.wavs.xyz/llms-full.txt](https://docs.wavs.xyz/llms-full.txt) ``` curl https://docs.wavs.xyz/llms-full.txt ``` ## Markdown Format Get any page as standard Markdown by appending `.md` to its URL. ``` curl https://docs.wavs.xyz/path/to/page.md ``` Examples: - `/overview.md` - Overview page as Markdown - `/tutorial/1-overview.md` - Tutorial introduction as Markdown - `/handbook/service.md` - Service handbook as Markdown ## WAVS foundry template An llm-ingestible full markdown version of the [WAVS foundry template](https://github.com/Lay3rLabs/wavs-foundry-template) is available at [https://docs.wavs.xyz/wavs-foundry-template.md](https://docs.wavs.xyz/wavs-foundry-template.md). ``` curl https://docs.wavs.xyz/wavs-foundry-template.md ``` ## WAVS-WASI-utils crate docs An llm-ingestible full markdown version of the WAVS-WASI-Utils docs is available at [https://docs.wavs.xyz/wavs-wasi-utils.md](https://docs.wavs.xyz/wavs-wasi-utils.md). ``` curl https://docs.wavs.xyz/wavs-wasi-utils.md ``` ## Search API Search the documentation programmatically using the search API endpoint. ``` curl "https://docs.wavs.xyz/api/search-custom?q=your_query" ``` Examples: - Search for "component": `https://docs.wavs.xyz/api/search-custom?q=component` The search API returns JSON results with page titles, URLs, and content snippets. ---