SDK & Apps
calimero-sdk · calimero-sdk-macros · calimero-sys · calimero-wasm-abi
Purpose
Proc-macro SDK for building Calimero applications. Developers define state with #[app::state], implement methods with #[app::logic], and emit events with #[app::event]. The SDK compiles to WASM and exposes methods as JSON-RPC-callable endpoints. All persistent collections use CRDT types for conflict-free replicated state across peers.
Proc Macro Attributes
Each attribute transforms user-written Rust into the glue code needed for WASM execution, storage integration, and ABI generation.
#[app::state] — persistent state struct
Marks the persistent state struct. Generates automatic storage keys, versioning info, Borsh derives, and CRDT merge registration. Each field with a CRDT type gets its own storage subtree.
#[app::logic] — WASM-exported methods
Transforms an impl block's public methods into WASM exports. Each method gets input deserialization, output serialization, and error propagation. Private methods are not exported.
#[app::event] — typed event declarations
Declares event types that can be emitted via app::emit!. Events are Borsh-serialized and delivered to subscribers via SSE/WebSocket.
#[app::init] — initialization hook
Called on first deployment. Sets up initial state and default values. Runs before any logic methods are available.
#[app::migrate] — state migration between versions
Reads raw bytes from the previous schema, transforms into the new layout, and returns the new state. Use BorshDeserialize::deserialize (not try_from_slice) to handle trailing storage metadata.
#[app::destroy] — cleanup hook
Called on application removal. Can perform resource cleanup, final event emission, or state archival before the context is torn down.
#[app::private] — internal helper methods
Marks a method as internal to the application. Private methods are not exported as WASM entry points and cannot be called via the admin API or JSON-RPC. They can only be called by other methods within the same impl block. Useful for shared validation, computation, or state access helpers.
app::emit! — emit events
Emits events from logic methods. Supports fire-and-forget or handler-triggered patterns.
app::err! / bail! / log! — error handling and logging
Error and logging macros that integrate with the WASM host function ABI.
App Lifecycle
From writing application code to executing methods in a live context.
Write App with SDK
Define your state struct with #[app::state], implement methods with #[app::logic], declare events with #[app::event]. Use CRDT collections (Map, Set, LwwRegister) for conflict-free replicated state.
Build with cargo-mero
Run cargo mero build to compile to .wasm. The build tool runs proc-macro expansion, generates the ABI manifest, and targets wasm32-unknown-unknown. The output is a single .wasm binary ready for installation.
Install Application
Use meroctl app install to upload the .wasm binary to a running node. The node stores it as a blob, computes its BlobId (content hash), and registers it in the application catalog.
Create Context
Create a context (group + application binding) via the admin API or meroctl context create. This associates the installed application with a new group, sets up storage, and subscribes to the context's gossipsub topic.
Execute Methods via JSON-RPC
External clients call methods on the running context via JSON-RPC 2.0. Each call is deserialized, executed in the WASM runtime, and produces a delta that is broadcast to all peers via gossip. State converges automatically.
CRDT Collections
Conflict-free replicated data types available to application state. All implement the Mergeable trait for automatic convergence across peers.
Map<K, V>
Key-value store with per-key merge semantics. Supports nested maps — inner maps merge recursively. Values must implement Mergeable. Used for most application state.
struct State {
users: Map<AccountId, UserProfile>,
nested: Map<String, Map<String, Value>>,
}
Set<T>
Add-wins set. Concurrent additions and removals resolve in favor of additions. Elements must be Eq + Hash + Borsh. Ideal for membership lists, tags, and unique collections.
struct State {
members: Set<AccountId>,
tags: Set<String>,
}
LwwRegister<T>
Last-write-wins register with logical timestamp. Concurrent writes resolve by highest timestamp. Simple but effective for single-value fields where latest value is always preferred.
struct State {
title: LwwRegister<String>,
config: LwwRegister<AppConfig>,
}
RGA (Replicated Growable Array)
Sequence CRDT for ordered collections and text. Supports concurrent insert, delete, and move operations. Preserves user intent for collaborative editing scenarios.
// Supports: insert_at, delete_at, move
// Conflict resolution: position-based
Counter
Distributed G-Counter. Each node increments its own slot; value() returns the sum. Naturally commutative and idempotent.
Vector
Ordered append-only list. Items are pushed to the end. Concurrent pushes from different nodes both get appended.
UnorderedMap
Key-value store with LWW (Last-Write-Wins) semantics per entry. Entries inserted, updated, and removed independently across nodes.
Mergeable Trait
Universal merge interface implemented by all CRDT types. The runtime calls merge() automatically when applying incoming deltas from peers. Custom types can implement this trait for domain-specific merge logic.
fn merge(&mut self, other: Self) -> MergeResult;
fn is_empty(&self) -> bool;
}
Sample Apps
Reference applications demonstrating SDK features, CRDT patterns, and migration workflows.
kv-store
Basic key-value store using Map<String, String>. Demonstrates #[app::state], #[app::logic], and simple CRUD operations. The canonical "hello world" for Calimero apps.
apps/kv-storecollaborative-editor
Real-time collaborative text editor. Uses RGA for character-level CRDT operations. Demonstrates event emission for cursor position sharing and multi-user editing.
apps/collaborative-editoraccess-control
Role-based access control. Uses Map<AccountId, Set<Role>> for per-user role sets. Demonstrates #[app::private] for internal admin checks.
apps/access-controlblobs
Binary large object storage. Demonstrates the blob API for storing and retrieving arbitrary binary data via content-addressed BlobId handles.
apps/blobsnested-crdt-test
Nested CRDT stress test. Uses Map<K, Map<K, Set<V>>> to verify recursive merge behavior under concurrent modifications.
apps/nested-crdt-testteam-metrics
Dual-implementation app: one using SDK macros, one with a custom ABI. Demonstrates both approaches side-by-side for comparison.
apps/team-metricsprivate_data
Private data patterns with encrypted fields and selective disclosure. Uses LwwRegister for per-field encryption keys and #[app::private] for access gates.
apps/private_datamigrations
5-version migration suite demonstrating #[app::migrate] across schema changes. Each version adds fields, renames keys, or restructures the state tree.
apps/migrationse2e-kv-store
End-to-end test harness for the kv-store app. Runs multi-node scenarios with concurrent writes, verifies CRDT convergence, and validates sync protocol correctness.
apps/e2e-kv-storeDeveloper Guide
Handler Safety Requirements
Event handlers may execute in parallel across nodes. All handlers must be:
Commutative
Order-independent. Counter increments are safe; operations that depend on prior state (create then update) are not.
Independent
No shared mutable state. Each handler should use unique keys. Two handlers writing the same map key causes a race condition.
Idempotent
Safe to retry. CRDT operations are naturally idempotent. External API calls (payments, notifications) are not.
Pure
No external side effects. Only modify CRDT state and emit logs. HTTP calls, file I/O, and non-deterministic operations are forbidden.
Common Patterns
Counter
Use Counter (G-Counter) for distributed counting. Each node tracks increments separately; value() returns the sum.
Key-Value
Use UnorderedMap<K, LwwRegister<V>> for key-value storage. Last-write-wins on concurrent updates to the same key.
Events
Use app::emit! for fire-and-forget, or app::emit!((event, "handler_name")) to trigger a handler on receiving nodes.