Layer LogoLayer AVS Docs
Examples

Squaring Service example

Before following this guide, make sure you have completed the getting started guide, which walks you through creating the squaring service from scratch.

Introduction

In this guide, we'll walk through a minimal example of an Actively Validated Service (AVS) on the Layer platform. We'll recreate the "square" example, which demonstrates how to create a simple AVS that squares a given number. It will also be clear from this process how to make a custom AVS that does anything you like.

The Square Example

Our end goal is to build the component found in this repo: https://github.com/Lay3rLabs/avs-toolkit/tree/main/wasi/square.

Let's examine the core of our square example, found in lib.rs:

  1. The first is to follow the instructions for creating a project in the Develop Service section.

  2. You should end up with a scaffolded out Rust project with the following unimplemented code block:

impl Guest for Component {
fn run_task(request: TaskQueueInput) -> Output {
unimplemented!()
}
}
  1. Add the dependencies used in the example:
cargo add anyhow serde-json serde --features serde/derive
  1. With the dependencies installed, you should be able to add the imports needed for the project to the top.
use anyhow::anyhow;
use bindings::{Guest, Output, TaskQueueInput};
use serde::{Deserialize, Serialize};
  1. You can then copy the logic from the lib.rs file into your lib.rs folder.

Afterwards your file should look like so:

#[allow(warnings)]
mod bindings;
use anyhow::anyhow;
use bindings::{Guest, Output, TaskQueueInput};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Debug)]
pub struct TaskRequestData {
pub x: u64,
}
#[derive(Serialize, Debug)]
pub struct TaskResponseData {
pub y: u64,
}
struct Component;
impl Guest for Component {
fn run_task(request: TaskQueueInput) -> Output {
let TaskRequestData { x } = serde_json::from_slice(&request.request)
.map_err(|e| anyhow!("Could not deserialize input request from JSON: {}", e))
.unwrap();
let y = x * x;
println!("{}^2 = {}", x, y);
Ok(serde_json::to_vec(&TaskResponseData { y })
.map_err(|e| anyhow!("Could not serialize output data into JSON: {}", e))
.unwrap())
}
}
bindings::export!(Component with_types_in bindings);

This code defines a simple AVS that:

  1. Accepts a TaskRequestData containing a number x
  2. Squares the number
  3. Returns a TaskResponseData with the result y

The structs, TaskRequestData and TaskResponseData above the logic define the types we need to serialize and deserialize this input and output.

The logic defined inside actually perform the computation (squaring) that we desire.

In case you're wondering where the type TaskQueueInput comes from, it's part of the binding generation needed for us to target WebAssembly properly, which is generated automatically by the tooling. In the example, you can find it in the bindings.rs. You'll see this file in your project too after running the final command needed to generate your WebAssembly binary.

cargo component build --release

Voila! You should see a wasm binary located at ./target/wasm32-wasip1/release/my-task.wasm.

If you have followed the getting started guide, you'll know how to use the squaring service and submit tasks to the queue.

System Components and Their Interactions

The AVS ecosystem on Layer consists of several key components:

  1. The Service (WASM Component): This is the core of your AVS, containing the business logic (in this case, squaring a number). It's compiled to WebAssembly and runs in a WASI (WebAssembly System Interface) environment.

  2. Operators Contract: Manages voting power and quorum thresholds. It ensures only valid operators can participate in task verification.

  3. Validators (Simple Verifier) Contract: Aggregates operator votes on tasks and determines if a quorum is met for a given result. It interfaces with the Operators contract to verify operator eligibility.

  4. Task Queue Contract: Manages the lifecycle of tasks, from submission to completion. It interacts with the Validators contract to ensure tasks are properly verified.

Here's how these components interact in a typical AVS workflow:

  1. A task (in our case, a number to be squared) is submitted to the Task Queue Contract.
  2. Operators retrieve the task and run the WASM component (our squaring function) off-chain.
  3. Operators submit their results to the Validators Contract.
  4. The Validators Contract checks with the Operators Contract to ensure the operators are eligible to vote.
  5. If a quorum is reached (enough operators agree on the result), the Validators Contract notifies the Task Queue Contract of the task's completion.
  6. The Task Queue Contract marks the task as complete and makes the result available.

Customization Points

When creating your own AVS, you can customize various aspects of this process:

  1. Service (WASM Component) Logic: This is the most obvious customization point. Instead of squaring a number, you could implement any arbitrary computation, such as complex financial calculations, AI model inference, or data processing.

  2. Input/Output Formats: Modify the TaskRequestData and TaskResponseData structures to accept and return different types of data, allowing for more complex inputs and outputs.

  3. Quorum Thresholds: Adjust the quorum thresholds in the Operators Contract to require more or fewer agreeing operators.

Squaring Service frontend

Follow the frontend connection guide to learn how to connect the frontend to your service. You may need to edit the frontend code to add the correct task queue address.

On this page