### Install Nightly Rust Toolchain for Wasm64 Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk/V18_GUIDE.md To compile for Wasm64, you need to install the nightly Rust toolchain. This is the first step in setting up the Rust toolchain for Wasm64 support. ```bash rustup toolchain install nightly ``` -------------------------------- ### Management Canister Wrappers Demo Source: https://context7.com/dfinity/cdk-rs/llms.txt Demonstrates creating a canister, installing Wasm code, making an HTTPS outcall, and performing ECDSA signing using `ic-cdk-management-canister`. Ensure necessary imports are present. ```rust use ic_cdk::update; use ic_cdk_management_canister::{ create_canister, install_code, http_request, sign_with_ecdsa, CreateCanisterArgs, InstallCodeArgs, CanisterInstallMode, HttpRequestArgs, HttpMethod, SignWithEcdsaArgs, }; use candid::Principal; #[update] async fn management_canister_demo(wasm_module: Vec) -> String { // 1. Create a new canister (cycles are auto-attached). let create_result = create_canister(CreateCanisterArgs::default(), 200_000_000_000u128) .await .expect("create_canister failed"); let new_canister_id: Principal = create_result.canister_id; // 2. Install Wasm code into the new canister. install_code(InstallCodeArgs { mode: CanisterInstallMode::Install, canister_id: new_canister_id, wasm_module, arg: vec![], }) .await .expect("install_code failed"); // 3. Make an HTTPS outcall. let http_res = http_request(HttpRequestArgs { url: "https://api.example.com/data".to_string(), method: HttpMethod::GET, headers: vec![], body: None, max_response_bytes: Some(4096), transform: None, }, 0) .await .expect("http_request failed"); // 4. Threshold ECDSA signing. let sig_result = sign_with_ecdsa(SignWithEcdsaArgs { message_hash: vec![0u8; 32], derivation_path: vec![], key_id: ic_management_canister_types::EcdsaKeyId { curve: ic_management_canister_types::EcdsaCurve::Secp256k1, name: "test_key_1".to_string(), }, }) .await .expect("sign_with_ecdsa failed"); format!( "new_canister={new_canister_id} \ http_status={} \ sig_len={}", http_res.status, sig_result.signature.len() ) } ``` -------------------------------- ### Stable Memory I/O Example with Buffered and Direct Access Source: https://context7.com/dfinity/cdk-rs/llms.txt Demonstrates writing to and reading from stable memory using buffered (`BufferedStableWriter`, `BufferedStableReader`) and direct (`StableWriter`, `StableReader`) interfaces. Shows writing byte slices and primitive types, flushing buffers, and seeking to specific offsets. ```rust use ic_cdk::stable::{StableWriter, StableReader, BufferedStableWriter, BufferedStableReader}; use std::io::{Write, Read, Seek, SeekFrom}; fn stable_io_example() { // --- Write --- let mut writer = BufferedStableWriter::new(4096); // 4 KiB in-memory buffer writer.write_all(b"header:v1\n").unwrap(); writer.write_all(&42u64.to_le_bytes()).unwrap(); let written = writer.offset(); // bytes written so far writer.flush().unwrap(); // --- Read back --- let mut reader = BufferedStableReader::new(4096); let mut header = vec![0u8; 10]; reader.read_exact(&mut header).unwrap(); assert_eq!(&header, b"header:v1\n"); let mut num_buf = [0u8; 8]; reader.read_exact(&mut num_buf).unwrap(); let value = u64::from_le_bytes(num_buf); assert_eq!(value, 42); // --- Seek to absolute offset --- let mut direct_reader = StableReader::default(); direct_reader.seek(SeekFrom::Start(0)).unwrap(); let mut first_byte = [0u8; 1]; direct_reader.read_exact(&mut first_byte).unwrap(); assert_eq!(first_byte[0], b'h'); // --- Direct (unbuffered) write at a specific offset --- let mut direct_writer = StableWriter::default(); direct_writer.seek(SeekFrom::Start(written)).unwrap(); direct_writer.write_all(b"appended data").unwrap(); } ``` -------------------------------- ### Add rust-src Component for Nightly Toolchain Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk/V18_GUIDE.md After installing the nightly toolchain, add the `rust-src` component. This is necessary for Wasm64 compilation. ```bash rustup component add rust-src --toolchain nightly ``` -------------------------------- ### Build for Experimental Target: wasm64-unknown-unknown Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk/README.md Compile your Rust project for the experimental WebAssembly 64-bit target. This requires installing the nightly Rust toolchain and using specific build flags. ```bash rustup toolchain install nightly rustup component add rust-src --toolchain nightly cargo +nightly build -Z build-std=std,panic_abort --target wasm64-unknown-unknown ``` -------------------------------- ### Making an Unbounded-Wait Call to Bitcoin Canisters Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk-bitcoin-canister/README.md Demonstrates how to make an unbounded-wait inter-canister call to the Bitcoin canisters, for example, to retrieve UTXOs when a bounded-wait call is not suitable. It shows how to construct the request, determine the canister ID, calculate cycles, and make the call using `Call::unbounded_wait`. ```rust use ic_cdk_bitcoin_canister::{cost_get_utxos, get_bitcoin_canister_id, GetUtxosRequest, GetUtxosResponse, Network}; use ic_cdk::call::Call; async fn example() -> ic_cdk::call::CallResult { let arg = GetUtxosRequest { address: "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq".to_string(), network: Network::Mainnet.into(), filter: None, }; let canister_id = get_bitcoin_canister_id(Network::from(arg.network)); let cycles = cost_get_utxos(&arg); let res: GetUtxosResponse = Call::unbounded_wait(canister_id, "bitcoin_get_utxos") .with_arg(&arg) .with_cycles(cycles) .await?; Ok(res) } ``` -------------------------------- ### High-Level Stable Storage with `stable_save` and `stable_restore` Source: https://context7.com/dfinity/cdk-rs/llms.txt Illustrates persisting canister state using `stable_save` and `stable_restore` for Candid-encoded data. This example shows how to serialize a `BTreeMap` into stable memory before an upgrade and restore it afterward using `pre_upgrade` and `post_upgrade` hooks. ```rust use ic_cdk::{pre_upgrade, post_upgrade, query, update}; use std::cell::RefCell; use std::collections::BTreeMap; type Db = BTreeMap>; thread_local! { static DB: RefCell = RefCell::default(); } #[update] fn put(key: String, value: Vec) { DB.with(|db| db.borrow_mut().insert(key, value)); } #[query] fn get(key: String) -> Option> { DB.with(|db| db.borrow().get(&key).cloned()) } #[pre_upgrade] fn pre_upgrade() { // Serialize the entire DB to stable memory before upgrade. DB.with(|db| { ic_cdk::storage::stable_save((db.borrow().clone(),)) .expect("stable_save failed") }); } #[post_upgrade] fn post_upgrade() { // Restore DB from stable memory after upgrade. let (restored_db,): (Db,) = ic_cdk::storage::stable_restore().expect("stable_restore failed"); DB.with(|db| *db.borrow_mut() = restored_db); } ``` -------------------------------- ### Inter-Canister Calls using Call Builder Source: https://context7.com/dfinity/cdk-rs/llms.txt This example demonstrates various ways to use the `Call` builder for inter-canister communication, including simple calls, calls with arguments and cycles, concurrent calls, and one-way calls. ```APIDOC ## `ic_cdk::call::Call` Builder The `Call` builder provides a flexible way to make inter-canister calls. ### Methods - **`Call::bounded_wait(canister_id: Principal, method: &str)`**: Creates a `Call` builder with a default 300-second timeout, using a best-effort response strategy. - **`Call::unbounded_wait(canister_id: Principal, method: &str)`**: Creates a `Call` builder with an unbounded wait strategy. - **`.with_arg(arg: T)`**: Configures a single argument for the call. - **`.with_args(args: Vec)`**: Configures multiple arguments for the call. - **`.with_raw_args(raw_args: Vec)`**: Configures raw arguments for the call. - **`.with_cycles(cycles: u64)`**: Attaches a specified number of cycles to the call. - **`.change_timeout(seconds: u64)`**: Overrides the default timeout with a new value in seconds. - **`.await`**: Executes the call and returns a `Result`. - **`.oneway()`**: Makes a one-way, fire-and-forget call. ### Response Handling - **`res.candid::()`**: Decodes the response bytes into a Candid type `T`. - **`res.candid_tuple::()`**: Decodes the response bytes into a Candid tuple type `T`. ### Error Handling - **`CallErrorExt` trait**: Provides helper methods on `CallFailed` errors. - **`is_clean_reject()`**: Checks if the error is a clean reject, which might be safe to retry. - **`is_immediately_retryable()`**: Checks if the error is immediately retryable. ### Example Usage ```rust use ic_cdk::call::{Call, CallErrorExt, Error}; use ic_cdk::update; use candid::{CandidType, Deserialize, Principal}; use futures::future::join_all; #[derive(CandidType, Deserialize)] struct TransferArgs { to: Principal, amount: u64 } #[update] async fn cross_canister_example(ledger: Principal, recipient: Principal) -> String { // 1. Simple bounded-wait call with a Candid argument. let balance: u64 = match Call::bounded_wait(ledger, "account_balance") .with_arg(recipient) .await { Ok(res) => res.candid().unwrap_or(0), Err(e) if e.is_clean_reject() => return "clean reject – safe to retry".to_string(), Err(e) => return format!("call failed: {e}"), }; // 2. Bounded-wait call with cycle attachment and custom timeout. let transfer_result: Result = Call::bounded_wait(ledger, "transfer") .with_arg(TransferArgs { to: recipient, amount: 100 }) .with_cycles(1_000_000) .change_timeout(60) .await .map_err(|e| e.to_string()) .and_then(|r| r.candid().map_err(|e| e.to_string())); // 3. Concurrent calls using join_all. let peers: Vec = vec![ledger]; // extend as needed let futs: Vec<_> = peers.iter() .map(|p| Call::bounded_wait(*p, "ping").into_future()) .collect(); let results = join_all(futs).await; let ok_count = results.iter().filter(|r| r.is_ok()).count(); // 4. Fire-and-forget (one-way) call. let _ = Call::bounded_wait(ledger, "notify") .with_arg(balance) .oneway(); format!("balance={balance}, transfer={transfer_result:?}, pings_ok={ok_count}") } ``` ``` -------------------------------- ### Building Canisters with Cargo Source: https://context7.com/dfinity/cdk-rs/llms.txt Commands to build Rust canisters for stable Wasm32 or experimental Wasm64 targets. Includes setup for nightly Rust and extracting the Candid interface. ```sh # Build for stable Wasm32 cargo build --target wasm32-unknown-unknown --release # Build for experimental Wasm64 (requires nightly + rust-src) rustup toolchain install nightly rustup component add rust-src --toolchain nightly cargo +nightly build -Z build-std=std,panic_abort --target wasm64-unknown-unknown --release # Extract Candid interface (requires candid-extractor) candid-extractor target/wasm32-unknown-unknown/release/my_canister.wasm > my_canister.did ``` -------------------------------- ### Update Context Boilerplate Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk-executor/README.md Use this boilerplate for canister methods or other entrypoints in update contexts. It ensures proper setup for spawned tasks and panic handling. ```rust pub extern "C" fn function() { in_tracking_executor_context(|| { // method goes here }); } ``` -------------------------------- ### Demonstrate System API Bindings Source: https://context7.com/dfinity/cdk-rs/llms.txt This snippet showcases various System API functions for identity, cycle management, stable memory, timing, environment variables, and debugging. Ensure functions are called in the correct execution context. ```rust use ic_cdk::api::{ msg_caller, msg_cycles_available, msg_cycles_accept, canister_self, canister_cycle_balance, canister_liquid_cycle_balance, certified_data_set, data_certificate, time, instruction_counter, call_context_instruction_counter, in_replicated_execution, is_controller, stable_size, stable_grow, stable_read, stable_write, env_var_count, env_var_name, env_var_value, debug_print, }; use ic_cdk::update; #[update] fn system_api_demo() -> String { // Identity & authorization let caller = msg_caller(); let is_ctrl = is_controller(&caller); let self_id = canister_self(); // Cycle management: accept up to 1 billion cycles from the message let available = msg_cycles_available(); let accepted = msg_cycles_accept(available.min(1_000_000_000)); let balance = canister_cycle_balance(); let liquid = canister_liquid_cycle_balance(); // Timing & performance let now_ns = time(); // nanoseconds since Unix epoch let instructions = instruction_counter(); let ctx_instructions = call_context_instruction_counter(); // Stable memory: grow 1 page (64 KiB) and write/read a value let prev_pages = stable_grow(1); let offset = prev_pages * 64 * 1024; stable_write(offset, b"hello stable"); let mut buf = vec![0u8; 12]; stable_read(offset, &mut buf); assert_eq!(&buf, b"hello stable"); // Certified data (max 32 bytes; readable in queries via data_certificate) certified_data_set(b"certified_hash_32_bytes_padding!!"); // Environment variables set at canister install time let env_info: Vec = (0..env_var_count()) .map(|i| { let name = env_var_name(i); let value = env_var_value(&name); format!(name=value) }) .collect(); debug_print(format!(caller={balance})); format!( caller={caller} is_ctrl={is_ctrl} self={self_id} accepted={accepted} balance={balance} liquid={liquid} now={now_ns} instrs={instructions} ctx_instrs={ctx_instructions} stable_pages={} env={env_info:?} replicated={}, stable_size(), in_replicated_execution() ) } ``` -------------------------------- ### Cargo Dependency Conflict Error Example Source: https://github.com/dfinity/cdk-rs/blob/main/TROUBLESHOOTING.md This is an example of a Cargo error message indicating a conflict between different versions of `ic-cdk-executor` in the dependency graph. It highlights the need to ensure only one version of a native library is linked. ```rust error: failed to select a version for `ic-cdk-executor`. ... required by package `yourcrate v0.1.0 (/Users/you/yourcrate)` versions that meet the requirements `0.1.0` are: 0.1.0 the package `ic-cdk-executor` links to the native library `ic-cdk async executor`, but it conflicts with a previous package which links to `ic-cdk async executor` as well: package `ic-cdk-executor v1.0.0` ... which satisfies dependency `ic-cdk-executor = "^1.0.0` of package `someothercrate v0.1.0` Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the `links = "ic-cdk async executor"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links. failed to select a version for `ic-cdk-executor` which could resolve this conflict ``` -------------------------------- ### Canister and Subnet Information Source: https://github.com/dfinity/cdk-rs/blob/main/ic0/ic0.txt Functions to get information about the canister and its subnet. ```APIDOC ## Canister Information ### `ic0.canister_self_size` Retrieves the current size of the canister's stable memory. ### `ic0.canister_self_copy` Copies data from the canister's stable memory. Parameters: - `dst` (I): Destination buffer pointer. - `offset` (I): Offset within the stable memory. - `size` (I): Number of bytes to copy. ### `ic0.canister_cycle_balance128` Retrieves the canister's current cycle balance (128-bit). Parameters: - `dst` (I): Destination buffer pointer to store the cycle balance. ### `ic0.canister_liquid_cycle_balance128` Retrieves the canister's liquid cycle balance (128-bit). Parameters: - `dst` (I): Destination buffer pointer to store the liquid cycle balance. ### `ic0.canister_status` Retrieves the status of the canister. Returns: - `i32`: The canister status code. ### `ic0.canister_version` Retrieves the version of the canister. Returns: - `i64`: The canister version. ## Subnet Information ### `ic0.subnet_self_size` Retrieves the size of the subnet's memory. ### `ic0.subnet_self_copy` Copies data from the subnet's memory. Parameters: - `dst` (I): Destination buffer pointer. - `offset` (I): Offset within the subnet memory. - `size` (I): Number of bytes to copy. ``` -------------------------------- ### Inter-Canister Call Callback Boilerplate Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk-executor/README.md This boilerplate is for inter-canister call callbacks. It handles method unpacking, context setup for callbacks, and trap recovery. ```rust unsafe extern "C" fn callback(env: usize) { let method = unpack_env(env); in_callback_executor_context_for(method, || { // wake the call future }); } ``` ```rust unsafe extern "C" fn cleanup(env: usize) { let method = unpack_env(env); in_trap_recovery_context_for(method, || { cancel_all_tasks_attached_to_current_method(); }); } ``` -------------------------------- ### Generic Protobuf Serialization with Macros Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk/V18_GUIDE.md Demonstrates using generic Protobuf decoders and encoders with `update` macros for alternative wire formats. Ensure the `prost` crate is included for Protobuf support. ```rust use prost::Message; #[update(decode_with = "from_proto_bytes", encode_with = "to_proto_bytes")] fn protobuf_onwire1(a: u32) -> u32 { a + 42 } #[update(decode_with = "from_proto_bytes", encode_with = "to_proto_bytes")] fn protobuf_onwire2(a: String) -> String { format!("{} world!", a) } // Generic decoder function that works with any Protobuf message fn from_proto_bytes(bytes: Vec) -> T { T::decode(&bytes[..]).unwrap_or_default() } // Generic encoder function that works with any Protobuf message fn to_proto_bytes(message: T) -> Vec { message.encode_to_vec() } ``` -------------------------------- ### Add ic-cdk-timers Dependency Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk-timers/README.md Add the `ic-cdk-timers` crate to your `Cargo.toml` file to include timer functionality in your project. ```toml [dependencies] ic-cdk-timers = "1.0.0" ``` -------------------------------- ### Rust Canister Entry Point Macros Source: https://context7.com/dfinity/cdk-rs/llms.txt Use macros like `#[query]`, `#[update]`, `#[init]`, `#[pre_upgrade]`, `#[post_upgrade]`, `#[heartbeat]`, `#[inspect_message]`, and `#[on_low_wasm_memory]` to register Rust functions as IC canister entry points. These macros handle argument decoding, return-value encoding, and guard functions. All macros support async functions. ```rust use ic_cdk::{init, pre_upgrade, post_upgrade, query, update, inspect_message}; use std::cell::RefCell; thread_local! { static COUNTER: RefCell = RefCell::new(0); } fn only_controller() -> Result<(), String> { if ic_cdk::api::is_controller(&ic_cdk::api::msg_caller()) { Ok(()) } else { Err("Caller is not a controller".to_string()) } } #[init] fn canister_init(initial_value: u64) { COUNTER.with(|c| *c.borrow_mut() = initial_value); } #[pre_upgrade] fn save_state() { COUNTER.with(|c| ic_cdk::storage::stable_save((*c.borrow(),)).unwrap()); } #[post_upgrade] fn restore_state() { let (saved,): (u64,) = ic_cdk::storage::stable_restore().unwrap(); COUNTER.with(|c| *c.borrow_mut() = saved); } /// Read the counter without committing state (fast, free query). #[query] fn get() -> u64 { COUNTER.with(|c| *c.borrow()) } /// Increment: only the controller may call this. #[update(guard = "only_controller")] fn increment(by: u64) -> u64 { COUNTER.with(|c| { let new = *c.borrow() + by; *c.borrow_mut() = new; new }) } /// Custom-named update method exposed as "reset_counter". #[update(name = "reset_counter", guard = "only_controller")] fn reset() { COUNTER.with(|c| *c.borrow_mut() = 0); } /// Filter ingress messages: reject calls to "increment" from non-controllers. #[inspect_message] fn inspect() { let method = ic_cdk::api::msg_method_name(); if method == "increment" && !ic_cdk::api::is_controller(&ic_cdk::api::msg_caller()) { ic_cdk::trap("Not allowed"); } ic_cdk::api::accept_message(); } ic_cdk::export_candid!(); ``` -------------------------------- ### ic0.call_new Source: https://github.com/dfinity/cdk-rs/blob/main/ic0/manual_safety_comments.txt Initiates a new outgoing call to another canister. This function manages the setup for inter-canister communication, including specifying the callee, method name, and reply/reject handlers. ```APIDOC ## ic0.call_new ### Description Initiates a new outgoing call to another canister, setting up handlers for replies and rejections. ### Parameters #### Path Parameters - **callee_src** (I) - Required - Pointer to the callee canister's ID (readable sequence of bytes). - **callee_size** (I) - Required - Size of the callee canister's ID in bytes. - **name_src** (I) - Required - Pointer to the method name (readable UTF-8 string). - **name_size** (I) - Required - Size of the method name in bytes. - **reply_fun** (I) - Required - Function pointer for the reply handler. - **reply_env** (I) - Required - Environment data for the reply handler. - **reject_fun** (I) - Required - Function pointer for the reject handler. - **reject_env** (I) - Required - Environment data for the reject handler. ### Notes - This function takes ownership of `reply_env` and `reject_env`. - If called, `reply_fun` will take ownership of `reply_env`, `reject_env`, and the `ic0.call_on_cleanup` `env`. - If called, `reject_fun` will take ownership of `reply_env`, `reject_env`, and the `ic0.call_on_cleanup` `env`. ### Return Value - () - No return value. ``` -------------------------------- ### Build Project with Wasm64 Target and Flags Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk/V18_GUIDE.md Compile your Rust project for the `wasm64-unknown-unknown` target using the nightly toolchain and necessary build flags for Wasm64 compilation. ```bash cargo +nightly build -Z build-std=std,panic_abort --target wasm64-unknown-unknown ``` -------------------------------- ### System API Bindings (`ic_cdk::api`) Source: https://context7.com/dfinity/cdk-rs/llms.txt Provides ergonomic wrappers around the IC System API for various functionalities including caller identity, cycle management, stable memory, certified data, timestamps, performance counters, environment variables, and debugging utilities. ```APIDOC ## System API Bindings (`ic_cdk::api`) `ic_cdk::api` exposes ergonomic wrappers around the IC System API, including caller identity, cycle management, stable memory, certified data, timestamps, performance counters, environment variables, and debugging utilities. Functions that inspect messages (`msg_caller`, `msg_cycles_available`, etc.) are only valid in the appropriate execution context. ```rust use ic_cdk::api::{ msg_caller, msg_cycles_available, msg_cycles_accept, canister_self, canister_cycle_balance, canister_liquid_cycle_balance, certified_data_set, data_certificate, time, instruction_counter, call_context_instruction_counter, in_replicated_execution, is_controller, stable_size, stable_grow, stable_read, stable_write, env_var_count, env_var_name, env_var_value, debug_print, }; use ic_cdk::update; #[update] fn system_api_demo() -> String { // Identity & authorization let caller = msg_caller(); let is_ctrl = is_controller(&caller); let self_id = canister_self(); // Cycle management: accept up to 1 billion cycles from the message let available = msg_cycles_available(); let accepted = msg_cycles_accept(available.min(1_000_000_000)); let balance = canister_cycle_balance(); let liquid = canister_liquid_cycle_balance(); // Timing & performance let now_ns = time(); // nanoseconds since Unix epoch let instructions = instruction_counter(); let ctx_instructions = call_context_instruction_counter(); // Stable memory: grow 1 page (64 KiB) and write/read a value let prev_pages = stable_grow(1); let offset = prev_pages * 64 * 1024; stable_write(offset, b"hello stable"); let mut buf = vec![0u8; 12]; stable_read(offset, &mut buf); assert_eq!(&buf, b"hello stable"); // Certified data (max 32 bytes; readable in queries via data_certificate) certified_data_set(b"certified_hash_32_bytes_padding!!"); // Environment variables set at canister install time let env_info: Vec = (0..env_var_count()) .map(|i| { let name = env_var_name(i); let value = env_var_value(&name); format!("{name}={value}") }) .collect(); debug_print(format!("caller={caller}, balance={balance}")); format!( "caller={caller} is_ctrl={is_ctrl} self={self_id} \n accepted={accepted} balance={balance} liquid={liquid} \n now={now_ns} instrs={instructions} ctx_instrs={ctx_instructions} \n stable_pages={} env={env_info:?} replicated={}", stable_size(), in_replicated_execution() ) } ``` ``` -------------------------------- ### Custom Entry Point Execution Context Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk/V18_GUIDE.md When exporting canister entry points manually using `#[export_name]`, wrap the code in `ic_cdk::futures::in_executor_context` or `in_query_executor_context` to set the panic hook and enable `spawn`. This is automatically handled by attribute macros. ```rust #[unsafe(export_name = "canister_global_timer")] pub extern "C" fn canister_global_timer() { ic_cdk::futures::in_executor_context(|| { /* code goes here */ }); } #[unsafe(export_name = "canister_inspect_message")] pub extern "C" fn canister_inspect_message() { ic_cdk::futures::in_query_executor_context(|| { /* code goes here */ }) } ``` -------------------------------- ### Candid Bindgen Configuration Source: https://context7.com/dfinity/cdk-rs/llms.txt Shows how to configure `ic-cdk-bindgen` in `build.rs` for generating Rust bindings from Candid files, supporting types-only, static callee, and dynamic callee modes. ```APIDOC ## Candid Bindgen — `ic-cdk-bindgen` `ic-cdk-bindgen` generates Rust bindings from a Candid `.did` interface file at build time. It is used inside `build.rs`. Three modes are available: `TypesOnly` (default, generates only type definitions), `static_callee` (embeds a fixed canister Principal), and `dynamic_callee` (resolves the canister ID from an ICP environment variable). The output file is written to `$OUT_DIR/.rs` and included with `include!`. ```rust // build.rs use ic_cdk_bindgen::Config; fn main() { // Mode 1: Types only — useful when you form the call yourself. let mut types_config = Config::new("token_types", "token.did"); types_config.generate(); // Mode 2: Static callee — canister ID known at compile time. let mut static_config = Config::new("ledger", "ledger.did"); static_config.static_callee("ryjl3-tyaaa-aaaaa-aaaba-cai"); static_config.generate(); // Mode 3: Dynamic callee — canister ID from an ICP env var at runtime. let mut dynamic_config = Config::new("governance", "governance.did"); dynamic_config.dynamic_callee("GOVERNANCE_CANISTER_ID"); dynamic_config.generate(); } // src/lib.rs — include the generated bindings mod bindings { include!(concat!(env!("OUT_DIR"), "/ledger.rs")); } use ic_cdk::update; use bindings::LedgerService; // generated service struct with call helpers #[update] async fn query_balance(account: candid::Principal) -> u64 { let service = LedgerService::new(account); // static callee uses embedded ID service.account_balance(account).await.unwrap_or(0) } ``` ``` -------------------------------- ### Add ic-cdk-bindgen as a build dependency Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk-bindgen/README.md Add ic-cdk-bindgen to your Cargo.toml to enable build-time code generation. ```bash cargo add --build ic-cdk-bindgen ``` -------------------------------- ### Configure Cargo.toml for Canister Compilation Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk/README.md Set the crate-type to 'cdylib' in your Cargo.toml file to enable compilation as a dynamic library, which is required for WebAssembly canisters. ```toml [lib] crate-type = ["cdylib"] ``` -------------------------------- ### High-Level Stable Storage with `ic_cdk::storage` Source: https://context7.com/dfinity/cdk-rs/llms.txt Explains the use of `stable_save` and `stable_restore` for persisting canister state across upgrades by serializing and deserializing Candid-encoded data to/from stable memory. ```APIDOC ## High-Level Stable Storage — `stable_save` / `stable_restore` (`ic_cdk::storage`) `stable_save` and `stable_restore` serialise/deserialise a Candid-encoded tuple to/from stable memory. They are the simplest mechanism for persisting canister state across upgrades, suited for small to medium datasets. For larger or more complex state, use `ic-stable-structures`. ```rust use ic_cdk::{pre_upgrade, post_upgrade, query, update}; use std::cell::RefCell; use std::collections::BTreeMap; type Db = BTreeMap>; thread_local! { static DB: RefCell = RefCell::default(); } #[update] fn put(key: String, value: Vec) { DB.with(|db| db.borrow_mut().insert(key, value)); } #[query] fn get(key: String) -> Option> { DB.with(|db| db.borrow().get(&key).cloned()) } #[pre_upgrade] fn pre_upgrade() { // Serialize the entire DB to stable memory before upgrade. DB.with(|db| { ic_cdk::storage::stable_save((db.borrow().clone(),)) .expect("stable_save failed") }); } #[post_upgrade] fn post_upgrade() { // Restore DB from stable memory after upgrade. let (restored_db,): (Db,) = ic_cdk::storage::stable_restore().expect("stable_restore failed"); DB.with(|db| *db.borrow_mut() = restored_db); } ``` ``` -------------------------------- ### Async Task Spawning Patterns Source: https://context7.com/dfinity/cdk-rs/llms.txt This snippet demonstrates three methods for spawning asynchronous tasks: `spawn` (protected), `spawn_weak` (silently dropped if outlived), and `spawn_migratory` (can migrate across invocations). It also shows how to detect trap recovery in `Drop` implementations using `is_recovering_from_trap`. ```rust use ic_cdk::{update}; use ic_cdk::futures::{spawn, spawn_weak, spawn_migratory, is_recovering_from_trap}; use ic_cdk::call::Call; use candid::Principal; #[update] async fn async_patterns(peer: Principal) { // 1. Protected spawn: panics if it doesn't finish within the method lifetime. spawn(async move { let _ = Call::bounded_wait(peer, "ping").await; ic_cdk::println!("ping completed"); }); // 2. Weak spawn: silently dropped if it outlives the method. spawn_weak(async move { let _ = Call::bounded_wait(peer, "optional_work").await; }); // 3. Migratory spawn: outlives the current method, runs in whatever method context // next drives the executor. Must not trap. spawn_migratory(async move { // This will resume in the next message's execution context. let _ = Call::bounded_wait(peer, "background_job").await; }); // 4. Detect trap recovery in a Drop guard. struct CleanupGuard; impl Drop for CleanupGuard { fn drop(&mut self) { if is_recovering_from_trap() { ic_cdk::println!("Rolling back due to trap"); // perform compensating actions here } } } let _guard = CleanupGuard; // Await something; if this traps, _guard's Drop runs with is_recovering_from_trap()==true let _ = Call::bounded_wait(peer, "risky_call").await; } ``` -------------------------------- ### Define a Query Entry Point in Rust Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk/README.md Use the #[ic_cdk::query] attribute to register a Rust function as a query entry point for your canister. This function will be callable by other canisters or external parties. ```rust #[ic_cdk::query] fn hello() -> String { "world".to_string() } ``` -------------------------------- ### Setting and Clearing Timers in Rust Source: https://context7.com/dfinity/cdk-rs/llms.txt Demonstrates how to use `set_timer`, `set_timer_interval`, `set_timer_interval_serial`, and `clear_timer` for managing scheduled tasks. Timers are not persisted across upgrades and must be re-registered. ```rust use ic_cdk::{init, post_upgrade, update}; use ic_cdk_timers::{set_timer, set_timer_interval, set_timer_interval_serial, clear_timer, TimerId}; use std::cell::RefCell; use std::time::Duration; thread_local! { static HEARTBEAT_TIMER: RefCell> = RefCell::new(None); } #[init] fn init() { start_timers(); } #[post_upgrade] fn post_upgrade() { // Re-register timers after upgrade because they are not persisted. start_timers(); } fn start_timers() { // One-shot: run once after 5 seconds. set_timer(Duration::from_secs(5), async { ic_cdk::println!("One-shot timer fired!"); }); // Repeating (concurrent): every 60 seconds; multiple invocations may overlap. let id = set_timer_interval(Duration::from_secs(60), || async { // Fetch data from another canister every minute. let peer = ic_cdk::api::canister_self(); // placeholder let _ = ic_cdk::call::Call::bounded_wait(peer, "sync").await; }); // Repeating (serial): every 10 seconds; skips tick if previous is still running. set_timer_interval_serial(Duration::from_secs(10), async || { ic_cdk::println!("Serial heartbeat tick"); }); HEARTBEAT_TIMER.with(|t| *t.borrow_mut() = Some(id)); } #[update] fn stop_heartbeat() { HEARTBEAT_TIMER.with(|t| { if let Some(id) = t.borrow_mut().take() { clear_timer(id); ic_cdk::println!("Heartbeat stopped"); } }); } ``` -------------------------------- ### Time and Timer Functions Source: https://github.com/dfinity/cdk-rs/blob/main/ic0/ic0.txt Functions for retrieving the current time and setting global timers. ```APIDOC ## ic0.time ### Description Returns the current timestamp in nanoseconds since the genesis epoch. ### Method `ic0.time()` ### Parameters None ### Response - `timestamp` (i64): The current timestamp in nanoseconds. ``` ```APIDOC ## ic0.global_timer_set ### Description Sets a global timer to trigger at a specified timestamp. If the timestamp is in the past, the timer will trigger immediately. ### Method `ic0.global_timer_set(timestamp: i64)` ### Parameters - `timestamp` (i64): The timestamp in nanoseconds when the timer should trigger. ### Response - `i64`: The timestamp at which the timer was actually set. This might differ from the requested timestamp due to system scheduling. ``` -------------------------------- ### Handle dfx environment variables in build.rs Source: https://github.com/dfinity/cdk-rs/blob/main/ic-cdk-bindgen/README.md Manually parse dfx-related environment variables in build.rs to configure static callee bindings. ```rust // build.rs fn main() { let name = "callee"; let normalized_name = name.replace('-', "_").to_uppercase(); let canister_id_var_name = format!("CANISTER_ID_{}", normalized_name); let canister_id_str = std::env::var(&canister_id_var_name).unwrap(); let canister_id = candid::Principal::from_text(&canister_id_str).unwrap(); let candid_path_var_name = format!("CANISTER_CANDID_PATH_{}", normalized_name); let candid_path = std::env::var(&candid_path_var_name).unwrap(); ic_cdk_bindgen::Config::new(name, candid_path) .static_callee(canister_id) .generate(); } ``` -------------------------------- ### Stable Memory I/O with `ic_cdk::stable` Source: https://context7.com/dfinity/cdk-rs/llms.txt Demonstrates the usage of `StableWriter`, `StableReader`, `BufferedStableWriter`, and `BufferedStableReader` for reading from and writing to the canister's stable memory. These types implement standard Rust I/O traits. ```APIDOC ## Stable Memory I/O — `StableWriter`, `StableReader`, `BufferedStableWriter` (`ic_cdk::stable`) The `stable` module provides `StableWriter`, `StableReader`, `BufferedStableWriter`, and `BufferedStableReader` types that implement `std::io::{Read, Write, Seek}` over IC stable memory. Stable memory persists across canister upgrades. Pages are 64 KiB; growth is automatic in the writers. The `StableIO` type underpins all four and can also be used directly. ```rust use ic_cdk::stable::{StableWriter, StableReader, BufferedStableWriter, BufferedStableReader}; use std::io::{Write, Read, Seek, SeekFrom}; fn stable_io_example() { // --- Write --- let mut writer = BufferedStableWriter::new(4096); // 4 KiB in-memory buffer writer.write_all(b"header:v1\n").unwrap(); writer.write_all(&42u64.to_le_bytes()).unwrap(); let written = writer.offset(); // bytes written so far writer.flush().unwrap(); // --- Read back --- let mut reader = BufferedStableReader::new(4096); let mut header = vec![0u8; 10]; reader.read_exact(&mut header).unwrap(); assert_eq!(&header, b"header:v1\n"); let mut num_buf = [0u8; 8]; reader.read_exact(&mut num_buf).unwrap(); let value = u64::from_le_bytes(num_buf); assert_eq!(value, 42); // --- Seek to absolute offset --- let mut direct_reader = StableReader::default(); direct_reader.seek(SeekFrom::Start(0)).unwrap(); let mut first_byte = [0u8; 1]; direct_reader.read_exact(&mut first_byte).unwrap(); assert_eq!(first_byte[0], b'h'); // --- Direct (unbuffered) write at a specific offset --- let mut direct_writer = StableWriter::default(); direct_writer.seek(SeekFrom::Start(written)).unwrap(); direct_writer.write_all(b"appended data").unwrap(); } ``` ``` -------------------------------- ### Async Executor (`ic_cdk::futures`) Source: https://context7.com/dfinity/cdk-rs/llms.txt Provides three task-spawning functions (`spawn`, `spawn_weak`, `spawn_migratory`) for running asynchronous work within a canister. Includes functionality to detect trap recovery for rollback logic. ```APIDOC ## Async Executor — `spawn`, `spawn_weak`, `spawn_migratory` (`ic_cdk::futures`) `ic_cdk::futures` provides three task-spawning functions for running async work. `spawn` creates a protected task that panics if it outlives the canister method. `spawn_weak` creates a weak task that is silently dropped if it outlives the method. `spawn_migratory` allows a task to migrate across canister method invocations but must never trap. The `is_recovering_from_trap` flag can be checked inside `Drop` implementations for rollback logic. ```rust use ic_cdk::{update}; use ic_cdk::futures::{spawn, spawn_weak, spawn_migratory, is_recovering_from_trap}; use ic_cdk::call::Call; use candid::Principal; #[update] async fn async_patterns(peer: Principal) { // 1. Protected spawn: panics if it doesn't finish within the method lifetime. spawn(async move { let _ = Call::bounded_wait(peer, "ping").await; ic_cdk::println!("ping completed"); }); // 2. Weak spawn: silently dropped if it outlives the method. spawn_weak(async move { let _ = Call::bounded_wait(peer, "optional_work").await; }); // 3. Migratory spawn: outlives the current method, runs in whatever method context // next drives the executor. Must not trap. spawn_migratory(async move { // This will resume in the next message's execution context. let _ = Call::bounded_wait(peer, "background_job").await; }); // 4. Detect trap recovery in a Drop guard. struct CleanupGuard; impl Drop for CleanupGuard { fn drop(&mut self) { if is_recovering_from_trap() { ic_cdk::println!("Rolling back due to trap"); // perform compensating actions here } } } let _guard = CleanupGuard; // Await something; if this traps, _guard's Drop runs with is_recovering_from_trap()==true let _ = Call::bounded_wait(peer, "risky_call").await; } ``` ``` -------------------------------- ### Cargo.toml Configuration Source: https://context7.com/dfinity/cdk-rs/llms.txt Defines the package name, version, edition, and dependencies for a Rust canister project. Ensure `crate-type` is set to `cdylib`. ```toml # Cargo.toml [package] name = "my_canister" version = "0.1.0" edition = "2024" [lib] crate-type = ["cdylib"] [dependencies] ic-cdk = "0.20" ic-cdk-timers = "1.0" ic-cdk-macros = "=0.20" # pinned — macros must match ic-cdk exactly candid = "0.10" serde = { version = "1", features = ["derive"] } serde_bytes = "0.11" [dev-dependencies] pocket-ic = { git = "https://github.com/dfinity/ic", tag = "release-2026-04-16_04-20-base" } ``` -------------------------------- ### ic0.root_key_copy Source: https://github.com/dfinity/cdk-rs/blob/main/ic0/manual_safety_comments.txt Copies the root key to a specified destination buffer. The `dst` must point to a writable memory region of at least `size` bytes. The `offset` parameter does not affect safety. ```APIDOC ## ic0.root_key_copy ### Description Copies the root key to a specified destination buffer. ### Parameters #### Path Parameters - **dst** (I) - Required - Pointer to a writable sequence of bytes. - **offset** (I) - Required - Offset within the destination buffer (does not affect safety). - **size** (I) - Required - The number of bytes to copy. ### Return Value - () - No return value. ```