Calimero SDK Guide for Builders¶
The Calimero SDK (core/crates/sdk and core/crates/storage) provides everything you need to build distributed, peer-to-peer applications with automatic conflict-free synchronization.
Overview¶
The SDK consists of two main components:
- Application SDK (
core/crates/sdk): Macros, event system, private storage, and runtime integration - Storage SDK (
core/crates/storage): CRDT collections with automatic merge semantics
Together, they enable you to build applications with: - Automatic conflict resolution via CRDTs - Real-time event propagation - Private node-local storage - Type-safe state management
Core Concepts¶
State Definition¶
Applications define state using the #[app::state] macro:
use calimero_sdk::app;
use calimero_storage::collections::UnorderedMap;
use calimero_sdk::borsh::{BorshSerialize, BorshDeserialize};
#[app::state(emits = Event)]
#[derive(BorshSerialize, BorshDeserialize)]
#[borsh(crate = "calimero_sdk::borsh")]
pub struct MyApp {
// CRDT-backed state (automatically synchronized)
items: UnorderedMap<String, String>,
// Can nest CRDTs arbitrarily
nested: UnorderedMap<String, UnorderedMap<String, u64>>,
}
Key points:
- State is persisted and synchronized across nodes
- Must derive BorshSerialize and BorshDeserialize for persistence
- Use #[app::state(emits = Event)] to enable event emission
Logic Implementation¶
Implement logic using the #[app::logic] macro:
#[app::logic]
impl MyApp {
// Initialize state (called once on context creation)
#[app::init]
pub fn init() -> MyApp {
MyApp {
items: UnorderedMap::new(),
nested: UnorderedMap::new(),
}
}
// Mutation method (changes state, generates delta)
pub fn add_item(&mut self, key: String, value: String) -> app::Result<()> {
self.items.insert(key, value)?;
Ok(())
}
// View method (read-only, no delta generated)
#[app::view]
pub fn get_item(&self, key: &str) -> app::Result<Option<String>> {
self.items.get(key)?.map(|v| v.get().clone()).ok_or_else(|| app::Error::NotFound)
}
}
Key points:
- #[app::init] marks the initialization function
- Mutation methods (&mut self) generate deltas and sync
- View methods (#[app::view]) are read-only and faster
- Use app::Result<T> for error handling
CRDT Collections¶
The SDK provides several CRDT collection types:
UnorderedMap¶
Key-value storage with automatic conflict resolution:
use calimero_storage::collections::UnorderedMap;
let mut map: UnorderedMap<String, String> = UnorderedMap::new();
// Insert value (conflict-free)
map.insert("key".to_string(), "value".to_string())?;
// Get value
let value = map.get("key")?; // Returns Option<V>
// Remove value
map.remove("key")?;
// Check existence
if map.contains("key")? {
// ...
}
// Iterate entries
for (key, value) in map.entries()? {
// ...
}
Vector¶
Ordered list with element-wise merging:
use calimero_storage::collections::Vector;
let mut vec: Vector<String> = Vector::new();
// Append element
vec.push("item".to_string())?;
// Get element by index
let item = vec.get(0)?; // Returns Option<T>
// Insert at position
vec.insert(0, "first".to_string())?;
// Remove element
vec.remove(0)?;
Counter¶
Distributed counter with automatic summation:
use calimero_storage::collections::Counter;
let mut counter = Counter::new();
// Increment
counter.increment()?;
// Decrement
counter.decrement()?;
// Get value
let value = counter.value(); // Returns i64
LwwRegister¶
Last-Write-Wins register for single values:
use calimero_storage::collections::LwwRegister;
let mut register: LwwRegister<String> = LwwRegister::new("initial".to_string());
// Set value (latest timestamp wins)
register.set("updated".to_string())?;
// Get value
let value = register.get().clone();
UnorderedSet¶
Set with union-based merging:
use calimero_storage::collections::UnorderedSet;
let mut set: UnorderedSet<String> = UnorderedSet::new();
// Insert element
set.insert("item".to_string())?;
// Check membership
if set.contains("item")? {
// ...
}
// Remove element
set.remove("item")?;
Event System¶
Applications can emit events for real-time updates:
#[app::state(emits = Event)]
pub struct MyApp {
items: UnorderedMap<String, String>,
}
// Define event types
#[app::event]
pub enum Event<'a> {
ItemAdded {
key: &'a str,
value: &'a str,
},
ItemRemoved {
key: &'a str,
},
}
#[app::logic]
impl MyApp {
pub fn add_item(&mut self, key: String, value: String) -> app::Result<()> {
self.items.insert(key.clone(), value.clone())?;
// Emit event (propagated to all peers)
app::emit!(Event::ItemAdded {
key: &key,
value: &value,
});
Ok(())
}
}
Event lifecycle: 1. Emitted during method execution 2. Included in delta broadcast 3. Handlers execute on peer nodes (not author node) 4. Handlers can update UI or trigger side effects
Private Storage¶
For node-local data (secrets, caches, per-node counters):
use calimero_sdk::private_storage;
pub fn use_private_storage() {
// Create private entry
let secrets = private_storage::entry::<Secrets>("my-secrets");
// Read value
let current = secrets.get_or_init(|| Secrets::default());
// Modify value (never synced, stays on node)
secrets.write(|s| {
s.token = "rotated-token".to_string();
});
}
Key properties:
- Never replicated across nodes
- Stored via storage_read / storage_write directly
- Never included in CRDT deltas
- Only accessible on the executing node
Common Patterns¶
Pattern 1: Simple Key-Value Store¶
#[app::state(emits = Event)]
pub struct KvStore {
items: UnorderedMap<String, LwwRegister<String>>,
}
#[app::logic]
impl KvStore {
#[app::init]
pub fn init() -> KvStore {
KvStore {
items: UnorderedMap::new(),
}
}
pub fn set(&mut self, key: String, value: String) -> app::Result<()> {
self.items.insert(key, value.into())?;
Ok(())
}
pub fn get(&self, key: &str) -> app::Result<Option<String>> {
Ok(self.items.get(key)?.map(|v| v.get().clone()))
}
}
Pattern 2: Counter with Metrics¶
#[app::state]
pub struct Metrics {
page_views: UnorderedMap<String, Counter>,
}
#[app::logic]
impl Metrics {
#[app::init]
pub fn init() -> Metrics {
Metrics {
page_views: UnorderedMap::new(),
}
}
pub fn track_page_view(&mut self, page: String) -> app::Result<()> {
if let Some(counter) = self.page_views.get(&page)? {
counter.increment()?;
} else {
let mut counter = Counter::new();
counter.increment()?;
self.page_views.insert(page, counter)?;
}
Ok(())
}
pub fn get_views(&self, page: &str) -> app::Result<i64> {
Ok(self.page_views.get(page)?.map(|c| c.value()).unwrap_or(0))
}
}
Pattern 3: Nested Structures¶
#[app::state]
pub struct TeamMetrics {
// Map of team → Map of member → Counter
teams: UnorderedMap<String, UnorderedMap<String, Counter>>,
}
#[app::logic]
impl TeamMetrics {
pub fn increment_metric(
&mut self,
team: String,
member: String,
) -> app::Result<()> {
let members = self.teams
.entry(team)?
.or_insert_with(|| UnorderedMap::new());
let counter = members
.entry(member)?
.or_insert_with(|| Counter::new());
counter.increment()?;
Ok(())
}
}
Building Applications¶
Project Setup¶
# Create new Rust project
cargo new my-calimero-app
cd my-calimero-app
# Add dependencies to Cargo.toml
[dependencies]
calimero-sdk = { path = "../../core/crates/sdk" }
calimero-storage = { path = "../../core/crates/storage" }
calimero-sdk-macros = { path = "../../core/crates/sdk/macros" }
borsh = { version = "1.0", features = ["derive"] }
[lib]
crate-type = ["cdylib"]
[dependencies.calimero-sdk]
features = ["macro"]
Build to WASM¶
# Add WASM target
rustup target add wasm32-unknown-unknown
# Build WASM binary
cargo build --target wasm32-unknown-unknown --release
# Output: target/wasm32-unknown-unknown/release/my_calimero_app.wasm
Extract ABI¶
# Extract ABI from WASM
calimero-abi extract \
target/wasm32-unknown-unknown/release/my_calimero_app.wasm \
-o abi.json
Best Practices¶
- Always use CRDTs: Don't use regular Rust collections for synchronized state
- Use
&selffor views: Methods with&selfare read-only and faster (no attribute needed) - Handle errors properly: Use
app::Result<T>and meaningful error types - Use private storage for secrets: Never put secrets in CRDT state
- Emit events for UI updates: Enable real-time updates across nodes
- Test with multiple nodes: Verify sync behavior in multi-node scenarios
Deep Dives¶
For detailed SDK documentation:
- Application SDK:
core/crates/sdk/README.md- Complete API reference - Storage Collections:
core/crates/storage/README.md- CRDT types and semantics - Examples:
core/apps- Working application examples
Related Topics¶
- Getting Started - Complete getting started guide
- Applications - Application architecture overview
- Core Apps Examples - Reference implementations