diff --git a/components/RustSdkExperimental.tsx b/components/RustSdkExperimental.tsx
new file mode 100644
index 00000000..0f9d76e8
--- /dev/null
+++ b/components/RustSdkExperimental.tsx
@@ -0,0 +1,10 @@
+import { Callout } from 'nextra-theme-docs'
+
+export function RustSdkExperimental() {
+ return (
+
+ The Rust SDK is experimental. Support and bug fixes are low priority compared to the Python SDK.
+ See genlayer_sdk on crates.io for the latest published version.
+
+ )
+}
diff --git a/pages/developers/_meta.json b/pages/developers/_meta.json
index a289e997..39d0d3d3 100644
--- a/pages/developers/_meta.json
+++ b/pages/developers/_meta.json
@@ -1,6 +1,7 @@
{
"networks": "Networks & RPCs",
"intelligent-contracts": "Intelligent Contracts",
+ "rust-contracts": "Rust Contracts",
"decentralized-applications": "Frontend & SDK Integration",
"staking-guide": "Staking Contract Guide"
}
diff --git a/pages/developers/rust-contracts/_meta.json b/pages/developers/rust-contracts/_meta.json
new file mode 100644
index 00000000..c5cf92c4
--- /dev/null
+++ b/pages/developers/rust-contracts/_meta.json
@@ -0,0 +1,6 @@
+{
+ "introduction": "Introduction",
+ "first-contract": "Your First Contract",
+ "storage": "Storage",
+ "first-intelligent-contract": "Your First Intelligent Contract"
+}
diff --git a/pages/developers/rust-contracts/first-contract.mdx b/pages/developers/rust-contracts/first-contract.mdx
new file mode 100644
index 00000000..3553b339
--- /dev/null
+++ b/pages/developers/rust-contracts/first-contract.mdx
@@ -0,0 +1,184 @@
+import { Callout } from 'nextra-theme-docs'
+import { RustSdkExperimental } from '../../../components/RustSdkExperimental'
+
+# Your First Contract
+
+
+
+### Project Setup
+
+Create a new Rust project and configure it for WebAssembly:
+
+```bash
+cargo init --name my_contract
+```
+
+Your `Cargo.toml` should look like this:
+
+```toml
+[package]
+name = "my_contract"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+genlayer_sdk = "0.0.2"
+bytes = "1"
+```
+
+
+ Contracts **must** be compiled for the `wasm32-wasip1` target. Floating point operations are forbidden in deterministic mode and will cause a VM error.
+
+
+### The Contract Trait
+
+A Rust contract is a struct that implements the `Contract` trait. The trait has three methods corresponding to GenVM's entry points:
+
+```rust
+use genlayer_sdk::abi::entry::MessageData;
+use genlayer_sdk::abi::entry::contract_def::Contract;
+use genlayer_sdk::calldata::Value;
+
+#[derive(Default)]
+pub struct MyContract;
+
+impl Contract for MyContract {
+ fn handle_main(
+ &mut self,
+ message: MessageData,
+ data: bytes::Bytes,
+ ) -> Result {
+ Ok(Value::Str("Hello from Rust!".to_owned()))
+ }
+
+ fn handle_sandbox(
+ &mut self,
+ _message: MessageData,
+ _data: bytes::Bytes,
+ ) -> Result, String> {
+ unimplemented!()
+ }
+
+ fn handle_consensus_stage(
+ &mut self,
+ _message: MessageData,
+ _data: bytes::Bytes,
+ _stage_data: genlayer_sdk::abi::entry::contract_def::ConsensusStageData,
+ ) -> Result {
+ unimplemented!()
+ }
+}
+
+genlayer_sdk::contract_main!(MyContract);
+```
+
+The `contract_main!` macro generates the `main` function that reads the incoming message from stdin, dispatches to the correct handler, and sends the result back to GenVM.
+
+
+ Your struct must implement `Default`. GenVM creates a fresh instance for each invocation.
+
+
+### Handling Deploy vs Method Calls
+
+The `Contract` trait gives you raw calldata bytes. For a more ergonomic API that splits deployment from method calls, implement `ContractExt` instead:
+
+```rust
+use genlayer_sdk::abi::entry::MessageData;
+use genlayer_sdk::abi::entry::contract_def::{Contract, ContractExt};
+use genlayer_sdk::calldata::{self, Map, Value};
+
+#[derive(Default)]
+pub struct MyContract;
+
+impl ContractExt for MyContract {
+ type Error = String;
+
+ fn handle_deploy(
+ &mut self,
+ message: MessageData,
+ args: Vec,
+ kwargs: Map,
+ ) -> Result {
+ // Called when is_init == true
+ println!("Contract deployed by {:?}", message.sender_address);
+ Ok(Value::Null)
+ }
+
+ fn handle_method(
+ &mut self,
+ message: MessageData,
+ method: String,
+ args: Vec,
+ kwargs: Map,
+ ) -> Result {
+ match method.as_str() {
+ "greet" => {
+ let name = args.first()
+ .and_then(|v| v.as_str())
+ .unwrap_or("World");
+ Ok(Value::Str(format!("Hello, {name}!")))
+ }
+ _ => Err(format!("unknown method: {method}")),
+ }
+ }
+}
+
+// ContractExt provides the Contract impl automatically,
+// but you still need to implement handle_sandbox and handle_consensus_stage:
+impl Contract for MyContract {
+ fn handle_main(
+ &mut self,
+ message: MessageData,
+ data: bytes::Bytes,
+ ) -> Result {
+ ContractExt::handle_main(self, message, data.to_vec())
+ }
+
+ fn handle_sandbox(
+ &mut self,
+ _message: MessageData,
+ _data: bytes::Bytes,
+ ) -> Result, String> {
+ unimplemented!()
+ }
+
+ fn handle_consensus_stage(
+ &mut self,
+ _message: MessageData,
+ _data: bytes::Bytes,
+ _stage_data: genlayer_sdk::abi::entry::contract_def::ConsensusStageData,
+ ) -> Result {
+ unimplemented!()
+ }
+}
+
+genlayer_sdk::contract_main!(MyContract);
+```
+
+### Message Data
+
+Every handler receives a `MessageData` struct with the transaction context:
+
+| Field | Type | Description |
+|--------------------|----------------------------|-------------------------------------|
+| `contract_address` | `Address` (`[u8; 20]`) | This contract's address |
+| `sender_address` | `Address` | Immediate caller |
+| `origin_address` | `Address` | Original transaction sender |
+| `value` | `BigInt` | Value sent with the call |
+| `is_init` | `bool` | Whether this is a deployment call |
+| `datetime` | `DateTime` | Transaction timestamp |
+| `chain_id` | `BigInt` | Chain identifier |
+
+### Building
+
+Compile your contract:
+
+```bash
+cargo build --target wasm32-wasip1 --release
+```
+
+The resulting `.wasm` file will be in `target/wasm32-wasip1/release/my_contract.wasm`.
+
+### Debugging
+
+You can use `println!` for debug output. Print statements are included in the GenVM execution log and are visible during development.
diff --git a/pages/developers/rust-contracts/first-intelligent-contract.mdx b/pages/developers/rust-contracts/first-intelligent-contract.mdx
new file mode 100644
index 00000000..a84621cc
--- /dev/null
+++ b/pages/developers/rust-contracts/first-intelligent-contract.mdx
@@ -0,0 +1,258 @@
+import { Callout } from 'nextra-theme-docs'
+import { RustSdkExperimental } from '../../../components/RustSdkExperimental'
+
+# Your First **Intelligent** Contract
+
+
+
+Non-deterministic operations (web requests, LLM prompts) require the **leader/validator consensus pattern**. The leader executes the operation first, then validators independently verify the result.
+
+This is done through GenVM's `RunNondet` GL call, which triggers the `handle_consensus_stage` entry point on both leader and validator nodes.
+
+### How Consensus Works
+
+1. Your `handle_main` sends a `RunNondet` GL call with payload data
+2. GenVM calls `handle_consensus_stage` on the **leader** node with `ConsensusStageData::Leader`
+3. The leader performs the non-deterministic operation and returns a result
+4. GenVM calls `handle_consensus_stage` on each **validator** with `ConsensusStageData::Validator { leaders_result }`, containing the leader's result
+5. Validators perform their own operation, compare with the leader's result, and vote `true` (agree) or `false` (disagree)
+
+### GL Calls
+
+All interactions with the GenVM host go through `gl_call`. You encode a request as calldata, call `wasi::gl_call`, and read the response from the returned file descriptor:
+
+```rust
+use genlayer_sdk::abi::wasi;
+use genlayer_sdk::calldata::{self, Value};
+use std::collections::BTreeMap;
+use std::io::Read;
+use std::os::fd::FromRawFd;
+
+fn gl_call_with_response(message: &Value) -> Result {
+ let encoded = calldata::encode(message);
+ let fd = wasi::gl_call(&encoded).map_err(|e| e.to_string())?;
+
+ if fd == u32::MAX {
+ return Ok(Value::Null); // no response data
+ }
+
+ let mut file = unsafe { std::fs::File::from_raw_fd(fd as i32) };
+ let mut buffer = Vec::new();
+ file.read_to_end(&mut buffer).map_err(|e| e.to_string())?;
+
+ let value = calldata::decode(&buffer).map_err(|e| format!("{e:?}"))?;
+
+ // GL calls wrap responses in {"ok": ...} or {"error": ...}
+ if let Value::Map(ref map) = value {
+ if let Some(ok_value) = map.get("ok") {
+ return Ok(ok_value.clone());
+ }
+ if let Some(err_value) = map.get("error") {
+ return Err(format!("{err_value:?}"));
+ }
+ }
+
+ Ok(value)
+}
+```
+
+### Fetching a Webpage
+
+To fetch a webpage from a consensus stage handler, use the `WebRender` GL call:
+
+```rust
+fn fetch_webpage(url: &str) -> Result {
+ let message = Value::Map(BTreeMap::from([(
+ "WebRender".to_owned(),
+ Value::Map(BTreeMap::from([
+ ("mode".to_owned(), Value::Str("text".to_owned())),
+ ("url".to_owned(), Value::Str(url.to_owned())),
+ ("wait_after_loaded".to_owned(), Value::Str("0ms".to_owned())),
+ ])),
+ )]));
+
+ let response = gl_call_with_response(&message)?;
+
+ #[derive(serde::Deserialize)]
+ struct WebRenderResponse { text: String }
+
+ let parsed: WebRenderResponse = calldata::from_value(response)
+ .map_err(|e| e.to_string())?;
+ Ok(parsed.text)
+}
+```
+
+
+ `WebRender` (and other non-deterministic operations) can only be called from within `handle_consensus_stage`. Calling them from `handle_main` directly will result in an error.
+
+
+### Running a Non-Deterministic Operation
+
+From `handle_main`, you trigger the consensus flow using `RunNondet`. The `data_leader` and `data_validator` payloads are passed back to `handle_consensus_stage`:
+
+```rust
+use genlayer_sdk::abi::entry::contract_def::LeaderResult;
+
+fn run_nondet(entry_data: &[u8]) -> Result {
+ #[derive(serde::Serialize)]
+ struct RunNondet {
+ #[serde(with = "serde_bytes")]
+ data_leader: Vec,
+ #[serde(with = "serde_bytes")]
+ data_validator: Vec,
+ }
+
+ let request = RunNondet {
+ data_leader: entry_data.to_vec(),
+ data_validator: entry_data.to_vec(),
+ };
+
+ let message = calldata::to_value(&request).map_err(|e| e.to_string())?;
+ let wrapped = Value::Map(BTreeMap::from([
+ ("RunNondet".to_owned(), message),
+ ]));
+
+ let encoded = calldata::encode(&wrapped);
+ let fd = wasi::gl_call(&encoded).map_err(|e| e.to_string())?;
+
+ if fd == u32::MAX {
+ return Err("no response from RunNondet".to_owned());
+ }
+
+ let mut file = unsafe { std::fs::File::from_raw_fd(fd as i32) };
+ let mut buffer = Vec::new();
+ file.read_to_end(&mut buffer).map_err(|e| e.to_string())?;
+
+ let result = LeaderResult::parse(&buffer).map_err(|e| e.to_string())?;
+
+ match result {
+ LeaderResult::Return(value) => Ok(value),
+ LeaderResult::UserError(msg) => Err(msg),
+ LeaderResult::VmError(msg) => Err(format!("vm error: {msg}")),
+ }
+}
+```
+
+### Full Example: Fetch and Verify a Webpage
+
+This contract fetches a webpage and uses leader/validator consensus to verify the content:
+
+```rust
+use std::collections::BTreeMap;
+use std::io::Read;
+use std::os::fd::FromRawFd;
+
+use genlayer_sdk::abi::entry::MessageData;
+use genlayer_sdk::abi::entry::contract_def::{
+ ConsensusStageData, Contract, LeaderResult,
+};
+use genlayer_sdk::abi::wasi;
+use genlayer_sdk::calldata::{self, Value};
+
+const TARGET_URL: &str = "https://example.org";
+
+// -- gl_call_with_response and fetch_webpage as shown above --
+# fn gl_call_with_response(message: &Value) -> Result {
+# let encoded = calldata::encode(message);
+# let fd = wasi::gl_call(&encoded).map_err(|e| e.to_string())?;
+# if fd == u32::MAX { return Ok(Value::Null); }
+# let mut file = unsafe { std::fs::File::from_raw_fd(fd as i32) };
+# let mut buffer = Vec::new();
+# file.read_to_end(&mut buffer).map_err(|e| e.to_string())?;
+# let value = calldata::decode(&buffer).map_err(|e| format!("{e:?}"))?;
+# if let Value::Map(ref map) = value {
+# if let Some(ok) = map.get("ok") { return Ok(ok.clone()); }
+# if let Some(err) = map.get("error") { return Err(format!("{err:?}")); }
+# }
+# Ok(value)
+# }
+# fn fetch_webpage(url: &str) -> Result {
+# let message = Value::Map(BTreeMap::from([(
+# "WebRender".to_owned(),
+# Value::Map(BTreeMap::from([
+# ("mode".to_owned(), Value::Str("text".to_owned())),
+# ("url".to_owned(), Value::Str(url.to_owned())),
+# ("wait_after_loaded".to_owned(), Value::Str("0ms".to_owned())),
+# ])),
+# )]));
+# let response = gl_call_with_response(&message)?;
+# #[derive(serde::Deserialize)]
+# struct R { text: String }
+# let parsed: R = calldata::from_value(response).map_err(|e| e.to_string())?;
+# Ok(parsed.text)
+# }
+
+#[derive(Default)]
+pub struct FetchContract;
+
+impl Contract for FetchContract {
+ fn handle_main(
+ &mut self,
+ _message: MessageData,
+ _data: bytes::Bytes,
+ ) -> Result {
+ // Trigger consensus -- both leader and validator will run
+ // handle_consensus_stage with this data
+ let entry_data = calldata::encode(&Value::Null);
+ run_nondet(&entry_data)
+ }
+
+ fn handle_sandbox(
+ &mut self,
+ _message: MessageData,
+ _data: bytes::Bytes,
+ ) -> Result, String> {
+ unimplemented!()
+ }
+
+ fn handle_consensus_stage(
+ &mut self,
+ _message: MessageData,
+ _data: bytes::Bytes,
+ stage_data: ConsensusStageData,
+ ) -> Result {
+ match stage_data {
+ ConsensusStageData::Leader => {
+ // Leader fetches the page and returns the content
+ let content = fetch_webpage(TARGET_URL)?;
+ Ok(Value::Str(content))
+ }
+ ConsensusStageData::Validator { leaders_result } => {
+ let LeaderResult::Return(Value::Str(leader_content)) = leaders_result
+ else {
+ return Ok(Value::Bool(false)); // disagree
+ };
+
+ // Validator fetches independently and compares
+ let our_content = fetch_webpage(TARGET_URL)?;
+ Ok(Value::Bool(leader_content == our_content))
+ }
+ }
+ }
+}
+
+# fn run_nondet(entry_data: &[u8]) -> Result {
+# // ... as shown above
+# todo!()
+# }
+
+genlayer_sdk::contract_main!(FetchContract);
+```
+
+### Other GL Call Operations
+
+Beyond `WebRender`, the GL call interface supports:
+
+| GL Call | Description |
+|---------|-------------|
+| `WebRender` | Render a webpage (text, HTML, or screenshot) |
+| `WebRequest` | Make an HTTP request (GET, POST, etc.) |
+| `ExecPrompt` | Run an LLM prompt |
+| `ExecPromptTemplate` | Run a templated LLM prompt (comparative, non-comparative) |
+| `CallContract` | Call another contract |
+| `PostMessage` | Send a message to another contract |
+| `DeployContract` | Deploy a new contract |
+| `EthCall` / `EthSend` | EVM interoperability |
+| `EmitEvent` | Emit a blockchain event |
+
+All follow the same pattern: construct a `Value::Map` with the operation name as key, encode with `calldata::encode`, and call `wasi::gl_call`.
diff --git a/pages/developers/rust-contracts/introduction.mdx b/pages/developers/rust-contracts/introduction.mdx
new file mode 100644
index 00000000..025b2a65
--- /dev/null
+++ b/pages/developers/rust-contracts/introduction.mdx
@@ -0,0 +1,31 @@
+import { RustSdkExperimental } from '../../../components/RustSdkExperimental'
+
+# Rust Contracts
+
+
+
+GenLayer supports writing intelligent contracts in Rust via the [`genlayer_sdk`](https://crates.io/crates/genlayer_sdk) crate. Rust contracts compile to WebAssembly (`wasm32-wasip1`) and run inside GenVM just like Python contracts.
+
+### Why Rust?
+
+- **Performance** -- Rust compiles to efficient WebAssembly with no runtime overhead.
+- **Type safety** -- The storage system leverages Rust's type system to catch errors at compile time.
+- **Low-level control** -- Direct access to GenVM host functions when needed.
+
+### How It Works
+
+Rust contracts implement the `Contract` trait (or the higher-level `ContractExt` trait), which defines handlers for GenVM's three entry points:
+
+- **Main** -- regular contract method calls and deployment
+- **Sandbox** -- isolated execution context
+- **ConsensusStage** -- leader/validator consensus for non-deterministic operations
+
+The `contract_main!` macro generates the `main` function that bridges GenVM's WASI interface to your trait implementation.
+
+### Prerequisites
+
+- Rust toolchain (stable)
+- The `wasm32-wasip1` target:
+ ```bash
+ rustup target add wasm32-wasip1
+ ```
diff --git a/pages/developers/rust-contracts/storage.mdx b/pages/developers/rust-contracts/storage.mdx
new file mode 100644
index 00000000..94a7ff0b
--- /dev/null
+++ b/pages/developers/rust-contracts/storage.mdx
@@ -0,0 +1,239 @@
+import { Callout } from 'nextra-theme-docs'
+import { RustSdkExperimental } from '../../../components/RustSdkExperimental'
+
+# Storage
+
+
+
+Intelligent contracts store data on chain in persistent storage. The Rust SDK provides a type-safe storage system where each type maps to a **handle** that reads and writes directly from blockchain storage slots. Storage starts zero-initialized.
+
+### Storage Concepts
+
+Storage is organized around two core traits:
+
+- **`StorageType`** -- maps a value type to its storage handle and size
+- **`StorageValue`** -- provides `get()` and `set()` on scalar handles
+
+A **`Slot`** is a 32-byte storage region. Complex types derive child slots via SHA3-256 hashing.
+
+### Defining Contract State with `record!`
+
+The `record!` macro defines a struct whose fields are laid out in storage:
+
+```rust
+use genlayer_sdk::storage::{Root, DynArray};
+use genlayer_sdk::calldata::Address;
+
+genlayer_sdk::record!(MyState {
+ owner: Address,
+ counter: u32,
+ items: DynArray,
+});
+```
+
+Each field becomes a method that returns the field's storage handle. You use `.get()` and `.set()` on scalar handles:
+
+```rust
+let root = Root::::get();
+let state = root.contract_instance().get();
+
+// Read
+let owner = state.owner().get();
+let count = state.counter().get();
+
+// Write
+state.counter().set(count + 1);
+```
+
+
+ `Root::::get()` returns the contract root, which wraps your state type `T` along with internal fields (contract code, locked slots, upgraders). Always access your state via `root.contract_instance().get()`.
+
+
+### Scalar Types
+
+| Rust Type | Storage Handle | Size |
+|------------|------------------|---------|
+| `u8`..`u128` | `StorageU8`..`StorageU128` | 1..16 bytes |
+| `i8`..`i128` | `StorageI8`..`StorageI128` | 1..16 bytes |
+| `f32`, `f64` | `StorageF32`, `StorageF64` | 4, 8 bytes |
+| `bool` | `StorageBool` | 1 byte |
+| `Address` | `StorageAddress` | 20 bytes |
+| `U256` | `StorageU256` | 32 bytes |
+| `String` | `StorageStr` | indirect |
+| `Vec` | `StorageBytes` | indirect |
+| `()` | -- | 0 bytes |
+
+Scalar handles have `.get()` and `.set()` methods:
+
+```rust
+state.counter().set(42);
+let val = state.counter().get(); // 42
+```
+
+`StorageStr` and `StorageBytes` have additional methods:
+
+```rust
+state.name().store("Alice");
+let name = state.name().load(); // "Alice"
+let len = state.name().len();
+```
+
+### Fixed-Size Arrays
+
+Use `[T; N]` for fixed-size arrays. The handle is `StorageArray`:
+
+```rust
+genlayer_sdk::record!(Board {
+ cells: [u8; 9],
+});
+
+let board = /* ... */;
+board.cells().index(0).set(1);
+let val = board.cells().index(4).get();
+```
+
+### Dynamic Arrays (`DynArray`)
+
+`DynArray` is a variable-length array stored indirectly (like Python's `DynArray`):
+
+```rust
+use genlayer_sdk::storage::{DynArray, Slot, StorageType};
+
+let arr = >::handle_at(slot, 0);
+
+// Append elements
+arr.append_slot().set(10);
+arr.append_slot().set(20);
+
+// Read
+let len = arr.len(); // 2
+let val = arr.index(0).get(); // 10
+
+// Remove
+arr.pop();
+arr.clear();
+```
+
+### Tree Maps (`TreeMap`)
+
+`TreeMap` is a sorted key-value map backed by an AVL tree (like Python's `TreeMap`):
+
+```rust
+use genlayer_sdk::storage::TreeMap;
+
+genlayer_sdk::record!(Balances {
+ balances: TreeMap,
+});
+
+let state = /* ... */;
+let balances = state.balances();
+
+// Insert / update (returns the value handle)
+if let Some(handle) = balances.get(&addr) {
+ let cur = handle.get();
+ handle.set(cur + amount);
+} else {
+ balances.insert(&addr).set(amount);
+}
+
+// Remove
+balances.remove(&addr);
+```
+
+### Nested Records
+
+Records can be nested and generic:
+
+```rust
+genlayer_sdk::record!(Pair[K, V] {
+ first: K,
+ second: V,
+});
+
+genlayer_sdk::record!(MyState {
+ pair: Pair[u32, u64],
+});
+```
+
+### Indirection
+
+`Indirection` stores the data at a derived slot rather than inline. This is used internally by `Root` and can be used for large nested structures:
+
+```rust
+use genlayer_sdk::storage::Indirection;
+
+genlayer_sdk::record!(MyState {
+ big_data: Indirection>,
+});
+```
+
+### Default Values
+
+Storage is zero-initialized:
+
+| Type | Default value |
+|------------|---------------|
+| `u*`, `i*` | `0` |
+| `f32/f64` | `0.0` |
+| `bool` | `false` |
+| `Address` | `[0; 20]` |
+| `U256` | `0` |
+| `String` | `""` |
+| `DynArray` | empty (len 0) |
+| `TreeMap` | empty |
+
+Records are zero-initialized recursively.
+
+### Full Example
+
+```rust
+use genlayer_sdk::abi::entry::MessageData;
+use genlayer_sdk::abi::entry::contract_def::Contract;
+use genlayer_sdk::calldata::Value;
+use genlayer_sdk::storage::Root;
+
+genlayer_sdk::record!(CounterState {
+ counter: u32,
+});
+
+#[derive(Default)]
+pub struct CounterContract;
+
+impl Contract for CounterContract {
+ fn handle_main(
+ &mut self,
+ _message: MessageData,
+ _data: bytes::Bytes,
+ ) -> Result {
+ let root = Root::::get();
+ let state = root.contract_instance().get();
+
+ let current = state.counter().get();
+ state.counter().set(current + 1);
+
+ Ok(Value::Map(std::collections::BTreeMap::from([(
+ "counter".to_owned(),
+ Value::Int(num_bigint::BigInt::from(current + 1)),
+ )])))
+ }
+
+ fn handle_sandbox(
+ &mut self,
+ _message: MessageData,
+ _data: bytes::Bytes,
+ ) -> Result, String> {
+ unimplemented!()
+ }
+
+ fn handle_consensus_stage(
+ &mut self,
+ _message: MessageData,
+ _data: bytes::Bytes,
+ _stage_data: genlayer_sdk::abi::entry::contract_def::ConsensusStageData,
+ ) -> Result {
+ unimplemented!()
+ }
+}
+
+genlayer_sdk::contract_main!(CounterContract);
+```