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 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 \
githttps://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/itsumura-h/nicp_cdkNow you can use the ndfx command.
ndfx c_headers/root/.ic-c-headers will be created.
ndfx new hello
cd helloWarning
Check the hello/src/hello_backend/config.nims file:
- Is the
ic wasi polyfill pathcorrect? - Is the
WASI SDK sysrootcorrect?
dfx stop && dfx start --clean --background --host 0.0.0.0:4943
dfx deployStable 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 table 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 table 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
You can also store custom Nim objects in a stable table:
import nicp_cdk
import nicp_cdk/storage/stable_table
type UserProfile = object
id: uint
name: string
active: bool
# Create a stable table 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.