Service SDKs
A Service SDK is what you use to write the application logic that runs inside a node — your state and methods, compiled to WebAssembly and replicated across a context's members. There are two:
- Rust —
calimero-sdk— production-ready and the recommended path today. - JavaScript/TypeScript —
calimero-sdk-js— in active development, not yet production-ready.
A minimal service
Section titled “A minimal service”use calimero_sdk::app;use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize};use calimero_storage::collections::{LwwRegister, UnorderedMap};
#[app::state]#[derive(Debug, BorshSerialize, BorshDeserialize)]#[borsh(crate = "calimero_sdk::borsh")]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())) }}import { State, Logic, Init, View, createCounter } from '@calimero-network/calimero-sdk-js';import type { Counter } from '@calimero-network/calimero-sdk-js/collections';
@Stateexport class CounterApp { count: Counter = createCounter();}
@Logic(CounterApp)export class CounterLogic extends CounterApp { @Init static initialize(): CounterApp { return new CounterApp(); }
increment(): void { this.count.increment(); }
@View() getCount(): bigint { return this.count.value(); }}The rest of this page tours the Rust app model (the production path). For the complete, authoritative reference see the Core Build docs.
The app model
Section titled “The app model”An application is a single state struct plus the methods that operate on it:
#[app::state]marks the struct that is persisted and synchronized across context members.#[app::logic]marks theimplblock whose public methods become callable endpoints.#[app::init]marks the one-time initializer run when a context is created.- Methods taking
&mut selfare mutations (they produce deltas that sync to peers); methods taking&selfare views (read-only, no delta). - Methods return
app::Result<T>for error handling.
The caller's identity is available inside any method via calimero_sdk::env::executor_id() — use it for authorization and per-user data.
CRDT collections
Section titled “CRDT collections”Synchronized state must use CRDT collections (not plain Rust collections) so that concurrent edits on different nodes merge deterministically. The collection you pick decides the merge behavior:
| Collection | Use case | Merge strategy |
| --- | --- | --- |
| Counter | Counters, metrics | Max per writer, summed at read |
| LwwRegister<T> | Single values | Latest timestamp wins |
| ReplicatedGrowableArray | Text, documents | Character-level |
| UnorderedMap<K,V> | Key-value storage | Recursive per-entry |
| Vector<T> | Ordered lists | Element-wise |
| UnorderedSet<T> | Unique values | Union |
Custom structs made of CRDT fields can derive Mergeable to merge field-by-field. Primitives like String/u64 are not Mergeable — wrap them in LwwRegister<T>.
Events
Section titled “Events”State changes can emit events that propagate with the delta and run handlers on peer nodes — useful for driving real-time UI updates. Declare the event type with #[app::event], enable emission with #[app::state(emits = ...)], and emit with app::emit!(...). See the Core Build docs for the full lifecycle.
Storage kinds
Section titled “Storage kinds”Beyond the shared CRDT state, the SDK offers three specialized storage kinds:
- Private storage (
#[app::private]) — node-local data (secrets, caches). Never replicated, never in deltas. - User storage (
UserStorage<T>) — per-user data keyed by the owner's public key. Writes are signed by the executor and verified by other nodes (with replay protection). - Frozen storage (
FrozenStorage<T>) — immutable, content-addressed values keyed by their SHA-256 hash. Insert-only; good for audit logs and attestations.
Build to WASM
Section titled “Build to WASM”rustup target add wasm32-unknown-unknowncargo build --target wasm32-unknown-unknown --profile app-releaseA build.rs that calls the calimero-wasm-abi emitter generates res/abi.json during the build; feed the ABI to calimero-abi-codegen to generate a typed client.
Full reference
Section titled “Full reference”Related
Section titled “Related”- Client SDKs — call your app from JS, Python, Kotlin, or Swift
- Applications — the application architecture overview
- Getting Started and the Builder Directory