Installation¶
Prerequisites¶
1. Rust toolchain¶
Install Rust via rustup:
Add the WASM compilation target:
Verify:
rustc --version # 1.75+ recommended
cargo --version
rustup target list --installed | grep wasm32
# wasm32-unknown-unknown
2. Citizen Protocol node¶
You need a running Citizen node to deploy and test contracts. See the Node Operators guide for full instructions, or use the local stack:
The node API will be available at http://127.0.0.1:8091.
3. Python (for deployment scripts)¶
SDK crates¶
The Citizen Contract SDK consists of two crates inside the protocol workspace:
| Crate | Path | Purpose |
|---|---|---|
citizen-contract-sdk |
crates/contract-sdk/ |
Runtime library: storage, context, host FFI |
citizen-contract-derive |
crates/contract-derive/ |
Proc macros: #[contract] auto-dispatch |
You don't install these from crates.io — you reference them by path in your contract's Cargo.toml.
Verify your setup¶
Create a minimal contract and build it:
Edit Cargo.toml (see Project Structure for details):
[package]
name = "hello-citizen"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
name = "hello_citizen"
[dependencies]
citizen-contract-sdk = { path = "../citizen/citizen-protocol/crates/contract-sdk" }
citizen-contract-derive = { path = "../citizen/citizen-protocol/crates/contract-derive" }
serde = { version = "1", default-features = false, features = ["derive", "alloc"] }
serde_json = { version = "1", default-features = false, features = ["alloc"] }
[profile.release]
opt-level = "s"
lto = true
strip = "none"
[workspace]
Write src/lib.rs:
#![no_std]
#![no_main]
extern crate alloc;
#[global_allocator]
static ALLOC: citizen_contract_sdk::Allocator = citizen_contract_sdk::Allocator;
use citizen_contract_sdk::prelude::*;
use citizen_contract_derive::contract;
#[contract]
mod contract {
use super::*;
pub fn ping(ctx: Context) -> Result<Response> {
Ok(Response::data(json!({"pong": true})))
}
}
Build:
If you see a .wasm file in target/wasm32-unknown-unknown/release/, your setup is correct.
Troubleshooting: link-arg=--allow-undefined
The RUSTFLAGS setting tells the linker to allow unresolved extern declarations (the host functions). Without it, linking fails because host_store_state, host_verify_signature, etc. are not defined at compile time — they're provided by the node at runtime.
Troubleshooting: error: no global memory allocator found
Make sure you have #[global_allocator] static ALLOC: citizen_contract_sdk::Allocator = citizen_contract_sdk::Allocator; at the top of your lib.rs. This wires Rust's alloc crate (Vec, String) to the SDK's bump allocator.
Next steps¶
- Hello World — Build and deploy your first useful contract
- Project Structure — Understand the Cargo.toml and file layout