Creating a Simple Nucleus

In this chapter, we will guide you step-by-step through building a simple Nucleus (AVS).

Let's begin by setting up the code structure directly:

Step 1: Create a New Rust Project

Navigate to your desired project directory and create a new Rust project using cargo:

cargo new --lib hello-avs
cd hello-avs

Step 2: Update the Cargo.toml File

Replace the contents of your Cargo.toml with the following:

[package]
name = "hello-avs"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
vrs-core-sdk = { version = "0.1.0", default-features = false }
parity-scale-codec = { version = "3.6", features = ["derive"] }

Step 3: Implement the Core Logic

Edit the file src/lib.rs and insert the following code:

#![allow(unused)]
fn main() {
use vrs_core_sdk::codec::{Decode, Encode};
use vrs_core_sdk::{get, post, storage};

#[derive(Debug, Decode, Encode)]
pub struct User {
    pub id: u64,
    pub name: String,
}

#[post]
pub fn add_user(user: User) -> Result<u64, String> {
    let key = [&b"user:"[..], &user.id.to_be_bytes()[..]].concat();
    storage::put(&key, &user.encode()).map_err(|e| e.to_string())?;
    Ok(user.id)
}

#[get]
pub fn get_user(id: u64) -> Result<Option<User>, String> {
    let key = [&b"user:"[..], &id.to_be_bytes()[..]].concat();
    let result = storage::get(&key).map_err(|e| e.to_string())?;
    let user = result.map(|data| User::decode(&mut &data[..]).unwrap());
    Ok(user)
}

}

Step 4: Compile the Project to WebAssembly

Finally, compile your project to WebAssembly by running the following command in your project's root directory (hello-avs):

cargo build --release --target wasm32-unknown-unknown

Upon successful compilation, you'll see a message similar to:

Finished `release` profile [optimized] target(s) in 0.09s

You have successfully created a simple Nucleus (AVS). The compiled WebAssembly executable (hello_avs.wasm) will be available in the directory target/wasm32-unknown-unknown/release.

What Does hello-avs Do?

Let's break down the functionality provided by this simple Nucleus step-by-step:

First, a struct named User is defined:

#![allow(unused)]
fn main() {
#[derive(Debug, Decode, Encode)]
pub struct User {
    pub id: u64,
    pub name: String,
}
}

Nucleus interfaces are exposed externally using macros such as #[post] and #[get].

  • The #[post] macro is used for interfaces that modify the blockchain's storage, as modifications consume gas.
  • The #[get] macro is used for interfaces that do not modify the storage.

The storage interaction APIs are as follows:

  • storage::put writes data to storage.
  • storage::get reads data from storage.

Add a New User with an ID

#![allow(unused)]
fn main() {
#[post]
pub fn add_user(user: User) -> Result<u64, String> {
    // Construct the user's key
    let key = [&b"user:"[..], &user.id.to_be_bytes()[..]].concat();
    // Write data to storage
    storage::put(&key, &user.encode()).map_err(|e| e.to_string())?;
    Ok(user.id)
}
}

Retrieve User by ID

#![allow(unused)]
fn main() {
#[get]
pub fn get_user(id: u64) -> Result<Option<User>, String> {
    // Construct the user's key
    let key = [&b"user:"[..], &id.to_be_bytes()[..]].concat();
    // Retrieve data from storage
    let result = storage::get(&key).map_err(|e| e.to_string())?;
    // Decode the data into a User struct
    let user = result.map(|data| User::decode(&mut &data[..]).unwrap());
    Ok(user)
}
}