From 10331890ef53ce2a36e3c0e2323788b19105b0b6 Mon Sep 17 00:00:00 2001
From: petarjuki7 <36903459+petarjuki7@users.noreply.github.com>
Date: Sun, 22 Dec 2024 20:31:29 +0100
Subject: [PATCH] feat: diferentiate rust sdk and tutorial (#81)
---
.../03-protocol-sdk/02-protocol-rs-sdk.mdx | 661 ++----------------
docs/06-tutorials/04-build-app.mdx | 638 +++++++++++++++++
docs/06-tutorials/_04-build-app.mdx | 6 -
3 files changed, 715 insertions(+), 590 deletions(-)
create mode 100644 docs/06-tutorials/04-build-app.mdx
delete mode 100644 docs/06-tutorials/_04-build-app.mdx
diff --git a/docs/05-developer-tools/02-SDK/03-protocol-sdk/02-protocol-rs-sdk.mdx b/docs/05-developer-tools/02-SDK/03-protocol-sdk/02-protocol-rs-sdk.mdx
index d0032c9d..422a63ed 100644
--- a/docs/05-developer-tools/02-SDK/03-protocol-sdk/02-protocol-rs-sdk.mdx
+++ b/docs/05-developer-tools/02-SDK/03-protocol-sdk/02-protocol-rs-sdk.mdx
@@ -3,640 +3,133 @@ id: protocol-rs-sdk
title: Rust Protocol SDK
---
-## Getting Started with Calimero SDK for Rust
+## Calimero SDK Macros for Rust
-The Calimero SDK for Rust empowers developers to build applications that compile
-to WebAssembly (Wasm) and run securely within the Calimero virtual machine (VM).
-This guide will walk you through setting up a Rust project using the Calimero
-SDK, writing an application, and preparing it for deployment.
+This guide provides a comprehensive reference of the essential
+macros provided by the Calimero SDK for building Rust applications.
-### Prerequisites
+## Core Macros
-Before you begin, ensure you have Rust installed on your system. If not, follow
-the official Rust installation guide for your platform:
-[Rust Installation Guide](https://www.rust-lang.org/tools/install).
+### #[app::state]
-You should ensure you have the `wasm32-unknown-unknown` target installed. Run
-the following command in your terminal to install the target:
-
-```bash title="Terminal"
-rustup target add wasm32-unknown-unknown
-```
-
-### Setting Up Your Project
-
-To create a new project, initialize a Rust library project using Cargo. Run the
-following command in your terminal:
-
-```bash title="Terminal"
-cargo new --lib kv-store
-```
-
-You should have a tree that looks like this:
-
-```bash title="Terminal"
-$ tree kv-store
-kv-store
-├── Cargo.toml
-└── src
- └── lib.rs
-
-2 directories, 2 files
-```
-
-At this point, we can `cd` into the `kv-store` directory:
-
-```bash title="Terminal"
-cd kv-store
-```
-
-Next, you need to specify the crate-type as `cdylib` in your `Cargo.toml` file
-to generate a dynamic library that can be compiled to Wasm:
-
-```toml title="File: Cargo.toml"
-[lib]
-crate-type = ["cdylib"]
-```
-
-You can now configure your project to use the Calimero SDK by adding it as a
-dependency in your `Cargo.toml` file:
-
-```toml title="File: Cargo.toml"
-[dependencies]
-calimero-sdk = { git = "https://github.com/calimero-network/core" }
-calimero-storage = { git = "https://github.com/calimero-network/core" }
-```
-
-Then, we need to specify a custom build profile for the most compact Wasm
-output:
-
-```toml title="File: Cargo.toml"
-[profile.app-release]
-inherits = "release"
-codegen-units = 1
-opt-level = "z"
-lto = true
-debug = false
-panic = "abort"
-overflow-checks = true
-```
-
-
- Your `Cargo.toml` file should now look like this
-
-```toml title="File: Cargo.toml" showLineNumbers
-[package]
-name = "kv-store"
-version = "0.1.0"
-edition = "2021"
-
-# highlight-start
-[lib]
-crate-type = ["cdylib"]
-# highlight-end
-
-# highlight-start
-[dependencies]
-calimero-sdk = { git = "https://github.com/calimero-network/core" }
-calimero-storage = { git = "https://github.com/calimero-network/core" }
-# highlight-end
-
-# highlight-start
-[profile.app-release]
-inherits = "release"
-codegen-units = 1
-opt-level = "z"
-lto = true
-debug = false
-panic = "abort"
-overflow-checks = true
-# highlight-end
-```
-
-
-
-And finally, create a `build.sh` script to compile your application into Wasm
-format, for example:
-
-```bash title="File: build.sh" showLineNumbers
-#!/bin/bash
-set -e
-
-cd "$(dirname $0)"
-
-TARGET="${CARGO_TARGET_DIR:-../../target}"
-
-rustup target add wasm32-unknown-unknown
-
-cargo build --target wasm32-unknown-unknown --profile app-release
-
-mkdir -p res
-
-cp $TARGET/wasm32-unknown-unknown/app-release/kv_store.wasm ./res/
-```
-
-You can optionally choose to install and use
-[`wasm-opt`](https://github.com/WebAssembly/binaryen), for an additional
-optimization step in the build script. This step is not required but can help
-reduce the size of the generated Wasm file:
-
-```bash title="File: build.sh"
-if command -v wasm-opt > /dev/null; then
- wasm-opt -Oz ./res/kv_store.wasm -o ./res/kv_store.wasm
-fi
-```
-
-Don't forget to make the `build.sh` script executable:
-
-```bash title="Terminal"
-chmod +x build.sh
-```
-
-At this point, your project structure should look like this:
-
-```bash title="Terminal"
-$ tree
-.
-├── Cargo.toml
-├── build.sh
-└── src
- └── lib.rs
-
-2 directories, 3 files
-```
-
-### Writing Your Application
-
-Now, let's create a simple key-value store application using the Calimero SDK.
-Start by defining your application logic in `lib.rs`:
-
-```rust title="File: src/lib.rs" showLineNumbers
-use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize};
-use calimero_sdk::app;
+Marks a struct as the application state.
+The state struct must implement `BorshSerialize` and `BorshDeserialize`.
+```rust
#[app::state]
#[derive(Default, BorshSerialize, BorshDeserialize)]
#[borsh(crate = "calimero_sdk::borsh")]
-struct KvStore {}
-
-#[app::logic]
-impl KvStore {
- #[app::init]
- pub fn init() -> KvStore {
- KvStore {}
- }
+struct MyAppState {
+ // Your state fields here
}
```
-The `KvStore` struct represents the state of your application, which will be
-borsh-encoded in the app-scoped state partition on the node's storage. The
-`#[app::state]` attribute macro marks the struct as the application state,
-permitting its use by Calimero SDK.
-
-The `#[app::logic]` attribute macro marks the implementation block as the
-application logic, allowing you to define the methods that interact with the
-application state. An initializer method (named `init`) is denoted by the
-`#[app::init]` attribute macro, which is called when the application is executed
-against a freshly created context.
-
-Consider a method like `get` that retrieves a value from the key-value store:
+When emitting events, spcify the event type:
```rust
-pub fn get(&self, key: &str) -> Result, Error> {
- // Snip...
+#[app::state(emits = for<'a> MyEvent<'a>)]
+struct MyAppState {
+ // Your state fields here
}
```
-The inputs must be deserializable from the transaction data, and the output must
-be serializable to the response data. The `Option` type is used to represent the
-possibility of the key not being present in the store. The `Error` type is used
-to represent the possible error conditions that may occur during the execution
-of the method.
-And now, here's a complete example of a key-value store application:
+### #[app::logic]
-```rust title="File: src/lib.rs" showLineNumbers
-use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize};
-use calimero_sdk::types::Error;
-use calimero_sdk::{app, env};
-use calimero_storage::collections::UnorderedMap;
-
-#[app::state]
-#[derive(Default, BorshSerialize, BorshDeserialize)]
-#[borsh(crate = "calimero_sdk::borsh")]
-struct KvStore {
- entries: UnorderedMap,
-}
+Marks an implementation block as containing the application logic.
+```rust
#[app::logic]
-impl KvStore {
- #[app::init]
- pub fn init() -> KvStore {
- // highlight-start
- KvStore {
- items: UnorderedMap::new(),
- }
- // highlight-end
- }
-
- // highlight-start
- pub fn set(&mut self, key: String, value: String) -> Result<(), Error> {
- env::log(&format!("Setting key: {:?} to value: {:?}", key, value));
-
- self.entries.insert(key, value)?;
-
- Ok(())
- }
- // highlight-end
-
- // highlight-start
- pub fn entries(&self) -> Result, Error> {
- env::log("Getting all entries");
-
- Ok(self.items.entries()?.collect())
- }
- // highlight-end
-
- // highlight-start
- pub fn get(&self, key: &str) -> Result, Error> {
- env::log(&format!("Getting key: {:?}", key));
-
- self.items.get(key).map_err(Into::into)
- }
- // highlight-end
-
- // highlight-start
- pub fn remove(&mut self, key: &str) -> Result , Error> {
- env::log(&format!("Removing key: {:?}", key));
-
- self.items.remove(key).map_err(Into::into)
- }
- // highlight-end
+impl MyAppState {
+ // Your methods here
}
```
-### Building Your Application
-
-Once your application logic is defined, run the `./build.sh` script to compile
-your application into Wasm format. This script will generate `kv_store.wasm` in
-the `res` folder of your application.
+### #[app::init]
-```bash title="Terminal"
-$ ./build.sh
-info: component 'rust-std' for target 'wasm32-unknown-unknown' is up to date
- # Snip...
- Compiling calimero-sdk v0.1.0
- Compiling calimero-storage v0.1.0
- Compiling kv-store v0.1.0 (/apps/kv-store)
- Finished `app-release` profile [optimized] target(s) in 1.20s
+Marks a method as the initializer, which is called when the application is first deployed.
-$ tree
-.
-├── Cargo.toml
-├── build.sh
-├── res
-│ └── kv_store.wasm
-└── src
- └── lib.rs
-
-3 directories, 4 files
+```rust
+#[app::logic]
+impl MyAppState {
+ #[app::init]
+ pub fn init() -> Self {
+ Self::default()
+ }
+}
```
-### Deploying Your Application
+### #[app::event]
-After successfully building your application, you can upload the compiled
-`kv_store.wasm` to the registry for use by a live Calimero node.
+Defines an event type that can be emitted by your application.
-### Writing Efficient Code with Calimero SDK
-
-In the following code snippet:
-
-```rust title="File: src/lib.rs"
-pub fn get(&self, key: &str) -> Result , Error> {
- // Snip...
+```rust
+#[app::event]
+pub enum MyEvent<'a> {
+ ValueUpdated { key: &'a str, value: &'a str },
+ ValueRemoved { key: &'a str },
}
```
-you'll notice that we prioritize using references instead of owned values. This
-approach optimizes performance and memory usage by minimizing unnecessary data
-copying.
-
-For input parameters, such as `&str` and `&[u8]`, utilizing references allows
-you to avoid unnecessary copying of data. Similarly, for output values, you can
-return references to data that live as long as `&self` or any of the input
-parameters. By doing so, you reduce memory overhead and improve the overall
-efficiency of your application.
-
-### Handling Errors with Calimero SDK
-
-When designing methods that may potentially fail, it's recommended to return a
-`Result` type with an error variant representing the possible failure cases.
-This enables you to handle errors more effectively and communicate error
-conditions to users of your application. This is recommended over using the
-`Error` type exported from `calimero_sdk` and over panicking. Both of which only
-return a string message.
-
-#### Error Report Comparison
-
-Let's take the following cases (all of which fail when the key does not exist);
-
-1. Using `calimero_sdk::types::Error`:
-
- This is provided for convenience, since most errors already don't implement
- `Serialize`, and so they cannot be immediately returned. This first converts
- the error to a string and then returns it. Which then JSON-encodes the string
- representation.
-
- ```rust title="File: src/lib.rs"
- use calimero_sdk::types::Error;
-
- pub fn get(&self, key: &str) -> Result {
- self.items.get(key)?.ok_or_else(|| Error::msg("key not found"))
- }
- ```
-
- This failure will result in this outcome:
-
- ```rust
- ExecutionError([ 107, 75, 101, 121, 32, 110, 111, 116, 32, 102, 111, 117, 110, 100, 34 ])
- ```
-
- which can be decoded to
-
- ```json
- "key not found"
- ```
-
- This `Error` can be constructed with `?` so long as the source error
- implements `std::error::Error`.
-
- Behaviourally similar to
- [`anyhow::Error`](https://docs.rs/anyhow/latest/anyhow/struct.Error.html) or
- [`eyre::Report`](https://docs.rs/eyre/latest/eyre/struct.Report.html).
-
-2. Using a custom error type (recommended):
-
- For structured error handling, we recommend defining a custom error type that
- encodes all the possible error variants for that method. This allows you to
- provide more context about the error condition and handle different error
- scenarios more effectively. As opposed to string parsing.
-
- ```rust title="File: src/lib.rs"
- use calimero_sdk::serde::Serialize;
-
- #[derive(Debug, Serialize)]
- #[serde(crate = "calimero_sdk::serde")]
- #[serde(tag = "kind", content = "data")]
- pub enum Error<'a> {
- NotFound(&'a str),
- }
- ```
-
- ```rust title="File: src/lib.rs"
- pub fn get<'a>(&self, key: &'a str) -> Result> {
- // Snip...
- Err(Error::NotFound(key))
- }
- ```
-
- This failure will result in this outcome:
-
- ```rust
- ExecutionError([ 123, 34, 107, 105, 110, 100, 34, 58, 34, 78, 111, 116, 70, 111, 117, 110, 100, 34, 44, 34, 100, 97, 116, 97, 34, 58, 34, 116, 104, 105, 110, 103, 34, 125 ])
- ```
-
- which can be decoded to
-
- ```json
- { "kind": "NotFound", "data": "thing" }
- ```
-
- As will most likely be the case, you may need to work with storage errors
- while you've defined a custom error type.
-
- In this case, you can pull in
- [`thiserror`](https://docs.rs/thiserror/latest/thiserror/) to help.
-
- ```rust title="File: src/lib.rs"
- use thiserror::Error;
-
- #[derive(Debug, Error, Serialize)]
- #[serde(crate = "calimero_sdk::serde")]
- #[serde(tag = "kind", content = "data")]
- pub enum Error<'a> {
- #[error("key not found: {0}")]
- NotFound(&'a str),
- #[error("store error: {0}")]
- StoreError(#[from] StoreError),
- }
- ```
-
- ```rust title="File: src/lib.rs"
- pub fn get<'a>(&self, key: &'a str) -> Result> {
- // Snip...
- self.items.get(key)?.ok_or_else(|| Error::NotFound(key))
- }
- ```
-
- An example store error would then be represented as:
-
- ```rust
- ExecutionError(
- [
- 123, 34, 107, 105, 110, 100, 34, 58, 34, 83, 116, 111, 114, 101, 69, 114, 114, 111, 114, 34, 44, 34, 100, 97, 116, 97, 34, 58,
- 34, 73, 110, 100, 101, 120, 32, 110, 111, 116, 32, 102, 111, 117, 110, 100, 32, 102, 111, 114, 32, 73, 68, 58, 32, 57, 51, 49, 53,
- 97, 98, 101, 49, 101, 97, 101, 48, 102, 102, 53, 98, 48, 48, 52, 53, 51, 97, 100, 97, 102, 99, 99, 53, 102, 101, 102, 50, 49, 100,
- 55, 52, 49, 51, 57, 55, 101, 50, 49, 99, 53, 49, 53, 51, 55, 99, 51, 54, 52, 52, 50, 52, 50, 48, 56, 55, 52, 57, 99, 57, 34, 125,
- ],
- )
- ```
-
- which can be decoded to
+## Event emission
- ```json
- {
- "kind": "StoreError",
- "data": "Index not found for ID: 9315abe1eae0ff5b00453adafcc5fef21d741397e21c51537c364424208749c9"
- }
- ```
+Use the `app::emit!` macro to emit events from your application:
-3. Panic (ideally, development only)
-
- ```rust title="File: src/lib.rs"
- pub fn get(&self, key: &str) -> String {
- self.items.get(key).expect("store error").expect("key not found")
- }
- ```
-
- A non-existent key would then lead to this outcome:
-
- ```rust
- HostError(
- Panic {
- context: Guest,
- message: "key not found",
- location: At {
- file: "apps/kv-store/src/lib.rs",
- line: 98,
- column: 14,
- },
- },
- )
- ```
-
- And a storage error, would produce this:
-
- ```rust
- HostError(
- Panic {
- context: Guest,
- message: "store error: StorageError(IndexNotFound(Id { bytes: [123, 240, 135, 21, 77, 143, 81, 169, 15, 202, 99, 210, 167, 165, 188, 156, 87, 146, 7, 211, 100, 92, 169, 189, 124, 115, 200, 242, 240, 73, 68, 123] }))",
- location: At {
- file: "apps/kv-store/src/lib.rs",
- line: 98,
- column: 14,
- },
- },
- )
- ```
-
-By following the second (recommended) approach, you can handle errors more
-gracefully and provide meaningful feedback to users of your Calimero
-application.
-
-And the first approach, if you want a hassle-free method of dealing with errors.
-
-### Emitting Events with Calimero SDK
-
-To facilitate real-time monitoring of state transitions within your Calimero
-application, you can emit events using the `app::emit!` macro provided by the
-Calimero SDK. Event emission is particularly useful for handling live state
-transitions triggered by other actors, allowing subscribed clients to receive
-immediate updates about relevant actions.
+```rust
+app::emit!(MyEvent::ValueUpdated {
+ key: &key,
+ value: &new_value
+});
+```
-Let's focus on emitting events for mutating calls, specifically `set` and
-`remove` methods:
+## Complete Example
-First, define your custom events using the `#[app::event]` proc macro. In this
-example, we'll define events for setting a new key-value pair (`Inserted`),
-updating an existing value (`Updated`), and removing a key-value pair
-(`Removed`):
+Here's a minimal example showing how these macros work together:
-```rust title="File: src/lib.rs"
-use calimero_sdk::app;
+```rust
+use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize};
+use calimero_sdk::{app, env};
+use calimero_storage::collections::UnorderedMap;
#[app::event]
-pub enum Event<'a> {
- Inserted { key: &'a str, value: &'a str },
- Updated { key: &'a str, value: &'a str },
- Removed { key: &'a str },
+pub enum StoreEvent<'a> {
+ ValueSet { key: &'a str, value: &'a str },
}
-```
-Each event variant can carry additional data to provide context about the event.
-
-Now, you need to associate the event with the application logic by annotating
-the application state.
-
-```rust title="File: src/lib.rs"
-// highlight-start
-#[app::state(emits = for<'a> Event<'a>)]
-// highlight-end
+#[app::state(emits = for<'a> StoreEvent<'a>)]
#[derive(Default, BorshSerialize, BorshDeserialize)]
#[borsh(crate = "calimero_sdk::borsh")]
-struct KvStore {
- // Snip...
+struct Store {
+ values: UnorderedMap,
}
-```
-And finally, within your application logic methods, emit events using the
-`app::emit!` macro:
+#[app::logic]
+impl Store {
+ #[app::init]
+ pub fn init() -> Self {
+ Self {
+ values: UnorderedMap::new(),
+ }
+ }
-```rust title="File: src/lib.rs"
-pub fn set(&mut self, key: String, value: String) -> Result<(), Error> {
- if self.items.contains(&key)? {
- app::emit!(Event::Updated {
- key: &key,
- value: &value,
- });
- } else {
- app::emit!(Event::Inserted {
- key: &key,
- value: &value,
+ pub fn set(&mut self, key: String, value: String) {
+ self.values.insert(key, value).unwrap();
+ app::emit!(StoreEvent::ValueSet {
+ key: &key,
+ value: &value
});
}
-
- self.items.insert(key, value)?;
-
- Ok(())
-}
-
-pub fn remove(&mut self, key: &str) -> Result {
- app::emit!(Event::Removed { key });
-
- self.entries.remove(key)?.ok_or_else(|| Error::msg("key not found"))
}
```
+## Important Notes
-In each method, we emit the corresponding event with relevant data. This allows
-external observers to react to these events and take appropriate actions.
-
-By emitting events, you can ensure connected clients receive real-time updates
-about state transitions within your Calimero application, enabling them to
-respond to changes as they occur.
-
-### Ensuring Atomicity and Event Reliability in Calimero Applications
-
-In Calimero applications, ensuring atomicity of state changes and reliability of
-event emission is crucial for maintaining data consistency and facilitating
-reliable communication between actors. Here's how atomicity and event
-reliability are enforced:
-
-#### Atomic State Changes
-
-When a method call fails, whether due to panics or returning an `Err`, all state
-changes made up to that point are discarded. This ensures that if an operation
-cannot be completed successfully, the application's state remains consistent and
-unaffected by partial updates. By enforcing atomicity, Calimero promotes data
-integrity and prevents inconsistencies that may arise from incomplete
-transactions.
-
-#### Reliable Event Emission
-
-Similarly, event emission in Calimero applications is tied to the successful
-execution of transactions. Events are only relayed when a transaction has been
-successfully executed, ensuring that external observers receive updates about
-state changes reliably. By linking event emission to transaction execution,
-Calimero guarantees that event notifications accurately reflect the
-application's current state, enhancing the reliability and consistency of
-communication between actors.
-
-This also means it doesn't matter if the event emission is done before or after
-the state change, as the event will only be emitted if the state change is
-successful.
-
-By adhering to these principles of atomicity and event reliability, Calimero
-applications maintain data integrity and enable robust interaction between
-different components, facilitating the development of secure and dependable
-decentralized systems.
-
-### Local-First Efficiency: No Network Overhead for Read-Only Calls
-
-In Calimero, adherence to the local-first principle eliminates the need for
-network communication in read-only calls. Since read-only operations don't
-modify the state, there's no associated network overhead. This local-first
-approach streamlines data access, promoting efficient and responsive application
-performance without unnecessary network activity.
-
-### Conclusion
-
-You've now learned how to set up a Rust project using the Calimero SDK, write a
-simple application, build it into Wasm, and prepare it for deployment.
-Experiment with different features and functionalities to create powerful and
-secure applications with Calimero.
+
+ State changes are atomic - if a method fails, all changes are rolled back
+ Events are only emitted if the transaction succeeds
+ Read-only operations have no network overhead
+ All public methods in the `#[app::logic]` block become available as application endpoints
+
-
+For a detailed guide on building a complete application using these macros,
+see our [Tutorial](../../../../tutorials/build-app) Guide on building a Key Value Store.
-Happy coding! 🚀
diff --git a/docs/06-tutorials/04-build-app.mdx b/docs/06-tutorials/04-build-app.mdx
new file mode 100644
index 00000000..09d89aa9
--- /dev/null
+++ b/docs/06-tutorials/04-build-app.mdx
@@ -0,0 +1,638 @@
+---
+id: build-app
+title: Key-Value Store tutorial
+---
+
+## Building a Key-Value Store with Calimero SDK
+
+The Calimero SDK for Rust empowers developers to build applications that compile
+to WebAssembly (Wasm) and run securely within the Calimero virtual machine.
+This guide will walk you through creating a complete key-value store application
+and preparing it for deployment.
+
+### Prerequisites
+
+Before you begin, ensure you have Rust installed on your system. If not, follow
+the official Rust installation guide for your platform:
+[Rust Installation Guide](https://www.rust-lang.org/tools/install).
+
+You should ensure you have the `wasm32-unknown-unknown` target installed. Run
+the following command in your terminal to install the target:
+
+```bash title="Terminal"
+rustup target add wasm32-unknown-unknown
+```
+
+### Setting Up Your Project
+
+To create a new project, initialize a Rust library project using Cargo. Run the
+following command in your terminal:
+
+```bash title="Terminal"
+cargo new --lib kv-store
+```
+
+You should have a tree that looks like this:
+
+```bash title="Terminal"
+$ tree kv-store
+kv-store
+├── Cargo.toml
+└── src
+ └── lib.rs
+
+2 directories, 2 files
+```
+
+At this point, we can `cd` into the `kv-store` directory:
+
+```bash title="Terminal"
+cd kv-store
+```
+
+Next, you need to specify the crate-type as `cdylib` in your `Cargo.toml` file
+to generate a dynamic library that can be compiled to Wasm:
+
+```toml title="File: Cargo.toml"
+[lib]
+crate-type = ["cdylib"]
+```
+
+You can now configure your project to use the Calimero SDK by adding it as a
+dependency in your `Cargo.toml` file:
+
+```toml title="File: Cargo.toml"
+[dependencies]
+calimero-sdk = { git = "https://github.com/calimero-network/core" }
+calimero-storage = { git = "https://github.com/calimero-network/core" }
+```
+
+Then, we need to specify a custom build profile for the most compact Wasm
+output:
+
+```toml title="File: Cargo.toml"
+[profile.app-release]
+inherits = "release"
+codegen-units = 1
+opt-level = "z"
+lto = true
+debug = false
+panic = "abort"
+overflow-checks = true
+```
+
+
+ Your `Cargo.toml` file should now look like this
+
+```toml title="File: Cargo.toml" showLineNumbers
+[package]
+name = "kv-store"
+version = "0.1.0"
+edition = "2021"
+
+# highlight-start
+[lib]
+crate-type = ["cdylib"]
+# highlight-end
+
+# highlight-start
+[dependencies]
+calimero-sdk = { git = "https://github.com/calimero-network/core" }
+calimero-storage = { git = "https://github.com/calimero-network/core" }
+# highlight-end
+
+# highlight-start
+[profile.app-release]
+inherits = "release"
+codegen-units = 1
+opt-level = "z"
+lto = true
+debug = false
+panic = "abort"
+overflow-checks = true
+# highlight-end
+```
+
+
+
+And finally, create a `build.sh` script to compile your application into Wasm
+format, for example:
+
+```bash title="File: build.sh" showLineNumbers
+#!/bin/bash
+set -e
+
+cd "$(dirname $0)"
+
+TARGET="${CARGO_TARGET_DIR:-../../target}"
+
+rustup target add wasm32-unknown-unknown
+
+cargo build --target wasm32-unknown-unknown --profile app-release
+
+mkdir -p res
+
+cp $TARGET/wasm32-unknown-unknown/app-release/kv_store.wasm ./res/
+```
+
+You can optionally choose to install and use
+[`wasm-opt`](https://github.com/WebAssembly/binaryen), for an additional
+optimization step in the build script. This step is not required but can help
+reduce the size of the generated Wasm file:
+
+```bash title="File: build.sh"
+if command -v wasm-opt > /dev/null; then
+ wasm-opt -Oz ./res/kv_store.wasm -o ./res/kv_store.wasm
+fi
+```
+
+Don't forget to make the `build.sh` script executable:
+
+```bash title="Terminal"
+chmod +x build.sh
+```
+
+At this point, your project structure should look like this:
+
+```bash title="Terminal"
+$ tree
+.
+├── Cargo.toml
+├── build.sh
+└── src
+ └── lib.rs
+
+2 directories, 3 files
+```
+
+### Writing Your Application
+
+Now, let's create a simple key-value store application using the Calimero SDK.
+Start by defining your application logic in `lib.rs`:
+
+```rust title="File: src/lib.rs" showLineNumbers
+use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize};
+use calimero_sdk::app;
+
+#[app::state]
+#[derive(Default, BorshSerialize, BorshDeserialize)]
+#[borsh(crate = "calimero_sdk::borsh")]
+struct KvStore {}
+
+#[app::logic]
+impl KvStore {
+ #[app::init]
+ pub fn init() -> KvStore {
+ KvStore {}
+ }
+}
+```
+
+The `KvStore` struct represents the state of your application, which will be
+borsh-encoded in the app-scoped state partition on the node's storage. The
+`#[app::state]` attribute macro marks the struct as the application state,
+permitting its use by Calimero SDK.
+
+The `#[app::logic]` attribute macro marks the implementation block as the
+application logic, allowing you to define the methods that interact with the
+application state. An initializer method (named `init`) is denoted by the
+`#[app::init]` attribute macro, which is called when the application is executed
+against a freshly created context.
+
+Consider a method like `get` that retrieves a value from the key-value store:
+
+```rust
+pub fn get(&self, key: &str) -> Result, Error> {
+ // Snip...
+}
+```
+
+The inputs must be deserializable from the transaction data, and the output must
+be serializable to the response data. The `Option` type is used to represent the
+possibility of the key not being present in the store. The `Error` type is used
+to represent the possible error conditions that may occur during the execution
+of the method.
+
+And now, here's a complete example of a key-value store application:
+
+```rust title="File: src/lib.rs" showLineNumbers
+use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize};
+use calimero_sdk::types::Error;
+use calimero_sdk::{app, env};
+use calimero_storage::collections::UnorderedMap;
+
+#[app::state]
+#[derive(Default, BorshSerialize, BorshDeserialize)]
+#[borsh(crate = "calimero_sdk::borsh")]
+struct KvStore {
+ entries: UnorderedMap,
+}
+
+#[app::logic]
+impl KvStore {
+ #[app::init]
+ pub fn init() -> KvStore {
+ // highlight-start
+ KvStore {
+ items: UnorderedMap::new(),
+ }
+ // highlight-end
+ }
+
+ // highlight-start
+ pub fn set(&mut self, key: String, value: String) -> Result<(), Error> {
+ env::log(&format!("Setting key: {:?} to value: {:?}", key, value));
+
+ self.entries.insert(key, value)?;
+
+ Ok(())
+ }
+ // highlight-end
+
+ // highlight-start
+ pub fn entries(&self) -> Result, Error> {
+ env::log("Getting all entries");
+
+ Ok(self.items.entries()?.collect())
+ }
+ // highlight-end
+
+ // highlight-start
+ pub fn get(&self, key: &str) -> Result, Error> {
+ env::log(&format!("Getting key: {:?}", key));
+
+ self.items.get(key).map_err(Into::into)
+ }
+ // highlight-end
+
+ // highlight-start
+ pub fn remove(&mut self, key: &str) -> Result , Error> {
+ env::log(&format!("Removing key: {:?}", key));
+
+ self.items.remove(key).map_err(Into::into)
+ }
+ // highlight-end
+}
+```
+
+### Building Your Application
+
+Once your application logic is defined, run the `./build.sh` script to compile
+your application into Wasm format. This script will generate `kv_store.wasm` in
+the `res` folder of your application.
+
+```bash title="Terminal"
+$ ./build.sh
+info: component 'rust-std' for target 'wasm32-unknown-unknown' is up to date
+ # Snip...
+ Compiling calimero-sdk v0.1.0
+ Compiling calimero-storage v0.1.0
+ Compiling kv-store v0.1.0 (/apps/kv-store)
+ Finished `app-release` profile [optimized] target(s) in 1.20s
+
+$ tree
+.
+├── Cargo.toml
+├── build.sh
+├── res
+│ └── kv_store.wasm
+└── src
+ └── lib.rs
+
+3 directories, 4 files
+```
+
+### Deploying Your Application
+
+After successfully building your application, you can upload the compiled
+`kv_store.wasm` to the registry for use by a live Calimero node.
+
+### Writing Efficient Code with Calimero SDK
+
+In the following code snippet:
+
+```rust title="File: src/lib.rs"
+pub fn get(&self, key: &str) -> Result , Error> {
+ // Snip...
+}
+```
+
+you'll notice that we prioritize using references instead of owned values. This
+approach optimizes performance and memory usage by minimizing unnecessary data
+copying.
+
+For input parameters, such as `&str` and `&[u8]`, utilizing references allows
+you to avoid unnecessary copying of data. Similarly, for output values, you can
+return references to data that live as long as `&self` or any of the input
+parameters. By doing so, you reduce memory overhead and improve the overall
+efficiency of your application.
+
+### Handling Errors with Calimero SDK
+
+When designing methods that may potentially fail, it's recommended to return a
+`Result` type with an error variant representing the possible failure cases.
+This enables you to handle errors more effectively and communicate error
+conditions to users of your application. This is recommended over using the
+`Error` type exported from `calimero_sdk` and over panicking. Both of which only
+return a string message.
+
+#### Error Report Comparison
+
+Let's take the following cases (all of which fail when the key does not exist);
+
+1. Using `calimero_sdk::types::Error`:
+
+ This is provided for convenience, since most errors already don't implement
+ `Serialize`, and so they cannot be immediately returned. This first converts
+ the error to a string and then returns it. Which then JSON-encodes the string
+ representation.
+
+ ```rust title="File: src/lib.rs"
+ use calimero_sdk::types::Error;
+
+ pub fn get(&self, key: &str) -> Result {
+ self.items.get(key)?.ok_or_else(|| Error::msg("key not found"))
+ }
+ ```
+
+ This failure will result in this outcome:
+
+ ```rust
+ ExecutionError([ 107, 75, 101, 121, 32, 110, 111, 116, 32, 102, 111, 117, 110, 100, 34 ])
+ ```
+
+ which can be decoded to
+
+ ```json
+ "key not found"
+ ```
+
+ This `Error` can be constructed with `?` so long as the source error
+ implements `std::error::Error`.
+
+ Behaviourally similar to
+ [`anyhow::Error`](https://docs.rs/anyhow/latest/anyhow/struct.Error.html) or
+ [`eyre::Report`](https://docs.rs/eyre/latest/eyre/struct.Report.html).
+
+2. Using a custom error type (recommended):
+
+ For structured error handling, we recommend defining a custom error type that
+ encodes all the possible error variants for that method. This allows you to
+ provide more context about the error condition and handle different error
+ scenarios more effectively. As opposed to string parsing.
+
+ ```rust title="File: src/lib.rs"
+ use calimero_sdk::serde::Serialize;
+
+ #[derive(Debug, Serialize)]
+ #[serde(crate = "calimero_sdk::serde")]
+ #[serde(tag = "kind", content = "data")]
+ pub enum Error<'a> {
+ NotFound(&'a str),
+ }
+ ```
+
+ ```rust title="File: src/lib.rs"
+ pub fn get<'a>(&self, key: &'a str) -> Result> {
+ // Snip...
+ Err(Error::NotFound(key))
+ }
+ ```
+
+ This failure will result in this outcome:
+
+ ```rust
+ ExecutionError([ 123, 34, 107, 105, 110, 100, 34, 58, 34, 78, 111, 116, 70, 111, 117, 110, 100, 34, 44, 34, 100, 97, 116, 97, 34, 58, 34, 116, 104, 105, 110, 103, 34, 125 ])
+ ```
+
+ which can be decoded to
+
+ ```json
+ { "kind": "NotFound", "data": "thing" }
+ ```
+
+ As will most likely be the case, you may need to work with storage errors
+ while you've defined a custom error type.
+
+ In this case, you can pull in
+ [`thiserror`](https://docs.rs/thiserror/latest/thiserror/) to help.
+
+ ```rust title="File: src/lib.rs"
+ use thiserror::Error;
+
+ #[derive(Debug, Error, Serialize)]
+ #[serde(crate = "calimero_sdk::serde")]
+ #[serde(tag = "kind", content = "data")]
+ pub enum Error<'a> {
+ #[error("key not found: {0}")]
+ NotFound(&'a str),
+ #[error("store error: {0}")]
+ StoreError(#[from] StoreError),
+ }
+ ```
+
+ ```rust title="File: src/lib.rs"
+ pub fn get<'a>(&self, key: &'a str) -> Result> {
+ // Snip...
+ self.items.get(key)?.ok_or_else(|| Error::NotFound(key))
+ }
+ ```
+
+ An example store error would then be represented as:
+
+ ```rust
+ ExecutionError(
+ [
+ 123, 34, 107, 105, 110, 100, 34, 58, 34, 83, 116, 111, 114, 101, 69, 114, 114, 111, 114, 34, 44, 34, 100, 97, 116, 97, 34, 58,
+ 34, 73, 110, 100, 101, 120, 32, 110, 111, 116, 32, 102, 111, 117, 110, 100, 32, 102, 111, 114, 32, 73, 68, 58, 32, 57, 51, 49, 53,
+ 97, 98, 101, 49, 101, 97, 101, 48, 102, 102, 53, 98, 48, 48, 52, 53, 51, 97, 100, 97, 102, 99, 99, 53, 102, 101, 102, 50, 49, 100,
+ 55, 52, 49, 51, 57, 55, 101, 50, 49, 99, 53, 49, 53, 51, 55, 99, 51, 54, 52, 52, 50, 52, 50, 48, 56, 55, 52, 57, 99, 57, 34, 125,
+ ],
+ )
+ ```
+
+ which can be decoded to
+
+ ```json
+ {
+ "kind": "StoreError",
+ "data": "Index not found for ID: 9315abe1eae0ff5b00453adafcc5fef21d741397e21c51537c364424208749c9"
+ }
+ ```
+
+3. Panic (ideally, development only)
+
+ ```rust title="File: src/lib.rs"
+ pub fn get(&self, key: &str) -> String {
+ self.items.get(key).expect("store error").expect("key not found")
+ }
+ ```
+
+ A non-existent key would then lead to this outcome:
+
+ ```rust
+ HostError(
+ Panic {
+ context: Guest,
+ message: "key not found",
+ location: At {
+ file: "apps/kv-store/src/lib.rs",
+ line: 98,
+ column: 14,
+ },
+ },
+ )
+ ```
+
+ And a storage error, would produce this:
+
+ ```rust
+ HostError(
+ Panic {
+ context: Guest,
+ message: "store error: StorageError(IndexNotFound(Id { bytes: [123, 240, 135, 21, 77, 143, 81, 169, 15, 202, 99, 210, 167, 165, 188, 156, 87, 146, 7, 211, 100, 92, 169, 189, 124, 115, 200, 242, 240, 73, 68, 123] }))",
+ location: At {
+ file: "apps/kv-store/src/lib.rs",
+ line: 98,
+ column: 14,
+ },
+ },
+ )
+ ```
+
+By following the second (recommended) approach, you can handle errors more
+gracefully and provide meaningful feedback to users of your Calimero
+application.
+
+And the first approach, if you want a hassle-free method of dealing with errors.
+
+### Emitting Events with Calimero SDK
+
+To facilitate real-time monitoring of state transitions within your Calimero
+application, you can emit events using the `app::emit!` macro provided by the
+Calimero SDK. Event emission is particularly useful for handling live state
+transitions triggered by other actors, allowing subscribed clients to receive
+immediate updates about relevant actions.
+
+Let's focus on emitting events for mutating calls, specifically `set` and
+`remove` methods:
+
+First, define your custom events using the `#[app::event]` proc macro. In this
+example, we'll define events for setting a new key-value pair (`Inserted`),
+updating an existing value (`Updated`), and removing a key-value pair
+(`Removed`):
+
+```rust title="File: src/lib.rs"
+use calimero_sdk::app;
+
+#[app::event]
+pub enum Event<'a> {
+ Inserted { key: &'a str, value: &'a str },
+ Updated { key: &'a str, value: &'a str },
+ Removed { key: &'a str },
+}
+```
+
+Each event variant can carry additional data to provide context about the event.
+
+Now, you need to associate the event with the application logic by annotating
+the application state.
+
+```rust title="File: src/lib.rs"
+// highlight-start
+#[app::state(emits = for<'a> Event<'a>)]
+// highlight-end
+#[derive(Default, BorshSerialize, BorshDeserialize)]
+#[borsh(crate = "calimero_sdk::borsh")]
+struct KvStore {
+ // Snip...
+}
+```
+
+And finally, within your application logic methods, emit events using the
+`app::emit!` macro:
+
+```rust title="File: src/lib.rs"
+pub fn set(&mut self, key: String, value: String) -> Result<(), Error> {
+ if self.items.contains(&key)? {
+ app::emit!(Event::Updated {
+ key: &key,
+ value: &value,
+ });
+ } else {
+ app::emit!(Event::Inserted {
+ key: &key,
+ value: &value,
+ });
+ }
+
+ self.items.insert(key, value)?;
+
+ Ok(())
+}
+
+pub fn remove(&mut self, key: &str) -> Result {
+ app::emit!(Event::Removed { key });
+
+ self.entries.remove(key)?.ok_or_else(|| Error::msg("key not found"))
+}
+```
+
+In each method, we emit the corresponding event with relevant data. This allows
+external observers to react to these events and take appropriate actions.
+
+By emitting events, you can ensure connected clients receive real-time updates
+about state transitions within your Calimero application, enabling them to
+respond to changes as they occur.
+
+### Ensuring Atomicity and Event Reliability in Calimero Applications
+
+In Calimero applications, ensuring atomicity of state changes and reliability of
+event emission is crucial for maintaining data consistency and facilitating
+reliable communication between actors. Here's how atomicity and event
+reliability are enforced:
+
+#### Atomic State Changes
+
+When a method call fails, whether due to panics or returning an `Err`, all state
+changes made up to that point are discarded. This ensures that if an operation
+cannot be completed successfully, the application's state remains consistent and
+unaffected by partial updates. By enforcing atomicity, Calimero promotes data
+integrity and prevents inconsistencies that may arise from incomplete
+transactions.
+
+#### Reliable Event Emission
+
+Similarly, event emission in Calimero applications is tied to the successful
+execution of transactions. Events are only relayed when a transaction has been
+successfully executed, ensuring that external observers receive updates about
+state changes reliably. By linking event emission to transaction execution,
+Calimero guarantees that event notifications accurately reflect the
+application's current state, enhancing the reliability and consistency of
+communication between actors.
+
+This also means it doesn't matter if the event emission is done before or after
+the state change, as the event will only be emitted if the state change is
+successful.
+
+By adhering to these principles of atomicity and event reliability, Calimero
+applications maintain data integrity and enable robust interaction between
+different components, facilitating the development of secure and dependable
+decentralized systems.
+
+### Local-First Efficiency
+
+Read-only operations (like `get`) have no network overhead, as they don't modify state and can be executed locally.
+
+### Conclusion
+
+You've now learned how to set up a Rust project using the Calimero SDK, write a
+simple application, build it into Wasm, and prepare it for deployment.
+Experiment with different features and functionalities to create powerful and
+secure applications with Calimero.
+
+
+
+Happy coding! 🚀
diff --git a/docs/06-tutorials/_04-build-app.mdx b/docs/06-tutorials/_04-build-app.mdx
deleted file mode 100644
index 9ef178dc..00000000
--- a/docs/06-tutorials/_04-build-app.mdx
+++ /dev/null
@@ -1,6 +0,0 @@
----
-id: build-app
-title: Build app
----
-
-## Building an Application