This is the Nim CDK for the Internet Computer.
I chose Nim for developing ICP canisters because:
- Nim can be transpiled to C, which allows it to target WebAssembly (WASM).
- Nim is a high-level language, making it as easy to write and read as Python.
- Nim is a statically typed language.
- Nim has a package manager, which makes it easy to install and manage dependencies.
- Nim has one of the best memory management systems: the compiler automatically controls the lifetime of variables without garbage collection (GC) or manual memory management.
Another motivational essay:
The Strength in Simplicity: The Aesthetics of Japanese Traditional Crafts and Distributed Systems
- Nim
- WASI SDK (includes Clang)
- ic-wasi-polyfill
- wasi2ic
- Binaryen (
wasm-opt) (required for production builds) - Internet Computer SDK
- Nim language server (recommended for Nim development)
- WebAssembly Binary Toolkit
- Rust (for building ic-wasi-polyfill)
See also Dockerfile.
These instructions assume Ubuntu or Debian:
apt install -y \
build-essential \
libunwind-dev \
lldb \
lld \
gcc-multilib \
xz-utils \
wget \
curl \
git \
binaryenhttps://www.rust-lang.org/tools/install
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shcd /root
git clone https://github.com/wasm-forge/ic-wasi-polyfill.git
cd ic-wasi-polyfill
rustup target add wasm32-wasip1
cargo build --release --target wasm32-wasip1
export IC_WASI_POLYFILL_PATH "/root/ic-wasi-polyfill/target/wasm32-wasip1/release"https://github.com/WebAssembly/wasi-sdk
cd /root
WASI_VERSION="25"
WASI_VERSION_FULL="$WASI_VERSION.0"
curl -L -o wasi-sdk.tar.gz https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_VERSION}/wasi-sdk-${WASI_VERSION_FULL}-x86_64-linux.tar.gz
tar -xzf wasi-sdk.tar.gz
rm wasi-sdk.tar.gz
mv "wasi-sdk-${WASI_VERSION_FULL}-x86_64-linux" ".wasi-sdk"
export WASI_SDK_PATH "/root/.wasi-sdk"
PATH $PATH:"${WASI_SDK_PATH}/bin"https://github.com/wasm-forge/wasi2ic
cargo install wasi2ichttps://nim-lang.org/install.html
curl https://nim-lang.org/choosenim/init.sh -sSf | shnimble install https://github.com/dumblepy/nicp_cdkNow you can use the nicp command.
nicp c_headers/root/.ic-c-headers will be created.
nicp new hello
cd helloUse nicp new hello none if you want a backend-only project.
Warning
Check the hello/backend/config.nims file:
- Is the
ic wasi polyfill pathcorrect? - Is the
WASI SDK sysrootcorrect?
icp network start -d
icp deployIf you want to call the backend directly, use:
icp canister call backend greet '("Internet Computer")'Stable memory allows you to persist data across canister upgrades. The NICP CDK provides three main types for managing persistent storage:
Store a single value of a primitive type or Principal that persists across canister upgrades.
import nicp_cdk/storage/stable_value
# Create a stable storage for an integer
var intDb = initIcStableValue(int)
# Store a value
intDb.set(42)
# Retrieve the value
let value = intDb.get()Supported types: int, uint, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, bool, char, string, Principal
Store a sequence (array) of elements that persists across canister upgrades.
import nicp_cdk/storage/stable_seq
# Create a stable sequence of integers
var seqIntDb = initIcStableSeq[int]()
# Add elements
seqIntDb.add(1)
seqIntDb.add(2)
seqIntDb.add(3)
# Access elements
let firstElement = seqIntDb[0]
# Get sequence length
let length = seqIntDb.len()
# Delete an element
seqIntDb.delete(1)
# Clear all elements
seqIntDb.clear()Supported element types: primitive types and Principal
Store key-value pairs that persist across canister upgrades.
import nicp_cdk/storage/stable_table
# Create a stable B+Tree mapping strings to integers
var scoreTable = initIcStableTable[string, uint]()
# Store a key-value pair
scoreTable["alice"] = 100
scoreTable["bob"] = 95
# Retrieve a value
let aliceScore = scoreTable["alice"]
# Check if a key exists
if scoreTable.hasKey("alice"):
echo "Alice has a score"
# Get map size
let numPlayers = scoreTable.len()
# Iterate over all pairs
for key, value in scoreTable.pairs():
echo key, ": ", value
# Clear all entries
scoreTable.clear()Supported key types: string, Principal, and other primitive types
Supported value types: primitive types, Principal, and Nim objects
Both are key-value stores persisted in stable memory, but they use different
index structures. In most cases, choose IcStableTable when you need ordered
iteration or range queries.
| Type | Index | Best for | Ordered APIs |
|---|---|---|---|
IcStableTable[K, V] |
B+Tree | General-purpose KV storage, ordered iteration, and range queries | pairs, lowerBound, range |
IcStableHashMap[K, V] |
Linear hashing | Exact-match workloads dominated by get and hasKey |
None (pairs order is unspecified) |
IcStableHashMap splits one bucket at a time as it grows, avoiding a full
rehash in a single operation. It does not preserve key order, so use
IcStableTable whenever you need range queries.
import nicp_cdk/storage/stable_hash_map
# Persistent HashMap for exact-match lookups
var sessionByToken = initIcStableHashMap[string, string]()
sessionByToken["token-123"] = "alice"
if sessionByToken.hasKey("token-123"):
echo sessionByToken["token-123"]
for token, user in sessionByToken.pairs():
# Iteration order is unspecified.
echo token, ": ", userWhen using multiple stable storage structures in one canister, assign each one a distinct, non-overlapping stable-memory region.
You can also store custom Nim objects in a stable B+Tree:
import nicp_cdk
import nicp_cdk/storage/stable_table
type UserProfile = object
id: uint
name: string
active: bool
# Create a stable B+Tree mapping principals to user profiles
var userTable = initIcStableTable[Principal, UserProfile]()
# Store a user profile
let caller = Msg.caller()
userTable[caller] = UserProfile(id: 1, name: "Alice", active: true)
# Retrieve the user profile
let profile = userTable[caller]
echo profile.name # Output: AliceHere's a practical example of using stable storage in canister update and query methods:
import nicp_cdk
import nicp_cdk/storage/stable_value
var counterDb = initIcStableValue(uint64)
proc increment() {.update.} =
let currentValue = counterDb.get()
counterDb.set(currentValue + 1)
reply(currentValue + 1)
proc getCounter() {.query.} =
let value = counterDb.get()
reply(value)Stable memory is organized as follows:
- Header area: Magic bytes, version, and metadata
- Data area: Serialized key-value pairs or sequence elements
When data needs to grow beyond available stable memory pages, the CDK automatically extends the memory using the IC's stable memory API.
The NICP CDK uses a custom, efficient serialization format optimized for stable memory:
- Fixed-size types (integers, floats, bools): Stored directly as little-endian bytes
- Variable-size types (strings, Principal): Prefixed with a 4-byte length field, followed by the data
This approach is more efficient than Candid encoding, which includes type information in the serialized data.
For a complete working example, see examples/stable_memory. For detailed storage and layout notes, see docs/en/stable_memory.md.
- No need to manually build ic-wasi-polyfill.
- Support all IC types.
- Access and call the management canister.
- HTTP outcall example.
- Stable memory.
- t-ECDSA example.
- t-RSA example.
- Bitcoin example.
- Ethersum example.
- Solana example.
- VetKey example.