ic_cdk/management_canister.rs
1//! Functions and types for interacting with the [IC management canister](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-management-canister).
2//!
3//! # Type Definitions
4//!
5//! This module defines the types of arguments and results for the management canister entry points.
6//! Most of these types are re-exported from the `ic-management-canister-types` crate.
7//!
8//! The only exception is that for the argument types that has a `sender_canister_version` field, this module provides reduced versions instead.
9//! The reduced versions don't need the `sender_canister_version` field as it is set automatically in the corresponding functions.
10//!
11//! # Call
12//!
13//! The majority of the functions in this module are for making calls to the management canister.
14//!
15//! ## Bounded-wait vs. Unbounded-wait
16//!
17//! Interacting with the IC management canister involves making inter-canister calls,
18//! which can be either [bounded-wait](crate::call::Call::bounded_wait) or [unbounded-wait](crate::call::Call::unbounded_wait).
19//! This module selects the appropriate type of wait call for each method based on the characteristics of the entry point.
20//!
21//! The strategy for choosing the type of wait call is as follows:
22//! - Unbounded-wait call by default because the management canister is universally trusted.
23//! - Bounded-wait call (with the default 300s timeout) for methods that only read state.
24//!
25//! Please check the documentation of each function for the type of wait call it uses.
26//!
27//! If the default behavior is not suitable for a particular use case, the [`Call`] struct can be used directly to make the call.
28//!
29//! For example, [`sign_with_ecdsa`] makes an unbounded-wait call. If a bounded-wait call is preferred, the call can be made as follows:
30//! ```rust, no_run
31//! # use ic_cdk::management_canister::{cost_sign_with_ecdsa, SignCallError, SignWithEcdsaArgs, SignWithEcdsaResult};
32//! # use ic_cdk::call::Call;
33//! # use candid::Principal;
34//! # async fn example() -> Result<SignWithEcdsaResult, SignCallError> {
35//! let callee = Principal::management_canister();
36//! let arg = SignWithEcdsaArgs::default();
37//! let cycles = cost_sign_with_ecdsa(&arg)?;
38//! let res: SignWithEcdsaResult = Call::bounded_wait(callee, "sign_with_ecdsa")
39//! .with_arg(&arg)
40//! .with_cycles(cycles)
41//! .await?
42//! .candid()?;
43//! # Ok(res)
44//! # }
45//! ```
46//!
47//! ## Cycle Cost
48//!
49//! Some management canister entry points require cycles to be attached to the call.
50//! The functions for calling management canister automatically calculate the required cycles and attach them to the call.
51//!
52//! For completeness, this module also provides functions to calculate the cycle cost:
53//! - [`cost_http_request`]
54//! - [`cost_sign_with_ecdsa`]
55//! - [`cost_sign_with_schnorr`]
56//! - [`cost_vetkd_derive_key`]
57
58use crate::api::{
59 SignCostError, canister_version, cost_create_canister,
60 cost_http_request as ic0_cost_http_request, cost_sign_with_ecdsa as ic0_cost_sign_with_ecdsa,
61 cost_sign_with_schnorr as ic0_cost_sign_with_schnorr,
62 cost_vetkd_derive_key as ic0_cost_vetkd_derive_key,
63};
64use crate::call::{Call, CallFailed, CallResult, CandidDecodeFailed};
65use candid::{CandidType, Nat, Principal};
66use serde::{Deserialize, Serialize};
67
68// Re-export types from the `ic-management-canister-types` crate.
69pub use ic_management_canister_types::{
70 Bip341, CanisterId, CanisterIdRecord, CanisterInfoArgs, CanisterInfoResult,
71 CanisterInstallMode, CanisterMetadataArgs, CanisterMetadataResult, CanisterSettings,
72 CanisterStatusArgs, CanisterStatusResult, CanisterStatusType, CanisterTimer, Change,
73 ChangeDetails, ChangeOrigin, ChunkHash, ClearChunkStoreArgs, CodeDeploymentMode,
74 CodeDeploymentRecord, ControllersChangeRecord, CreateCanisterResult, CreationRecord,
75 DefiniteCanisterSettings, DeleteCanisterArgs, DeleteCanisterSnapshotArgs, DepositCyclesArgs,
76 EcdsaCurve, EcdsaKeyId, EcdsaPublicKeyArgs, EcdsaPublicKeyResult, EnvironmentVariable,
77 FromCanisterRecord, FromUserRecord, HttpHeader, HttpMethod, HttpRequestArgs, HttpRequestResult,
78 ListCanisterSnapshotsArgs, ListCanisterSnapshotsResult, LoadSnapshotRecord, LogVisibility,
79 MemoryMetrics, NodeMetrics, NodeMetricsHistoryArgs, NodeMetricsHistoryRecord,
80 NodeMetricsHistoryResult, OnLowWasmMemoryHookStatus, ProvisionalCreateCanisterWithCyclesResult,
81 ProvisionalTopUpCanisterArgs, QueryStats, RawRandResult, ReadCanisterSnapshotDataArgs,
82 ReadCanisterSnapshotDataResult, ReadCanisterSnapshotMetadataArgs,
83 ReadCanisterSnapshotMetadataResult, SchnorrAlgorithm, SchnorrAux, SchnorrKeyId,
84 SchnorrPublicKeyArgs, SchnorrPublicKeyResult, SignWithEcdsaArgs, SignWithEcdsaResult,
85 SignWithSchnorrArgs, SignWithSchnorrResult, Snapshot, SnapshotDataKind, SnapshotDataOffset,
86 SnapshotId, SnapshotMetadataGlobal, SnapshotSource, StartCanisterArgs, StopCanisterArgs,
87 StoredChunksArgs, StoredChunksResult, SubnetInfoArgs, SubnetInfoResult,
88 TakeCanisterSnapshotArgs, TakeCanisterSnapshotResult, TransformArgs, TransformContext,
89 TransformFunc, UpgradeFlags, UploadCanisterSnapshotDataArgs,
90 UploadCanisterSnapshotMetadataArgs, UploadCanisterSnapshotMetadataResult, UploadChunkArgs,
91 UploadChunkResult, VetKDCurve, VetKDDeriveKeyArgs, VetKDDeriveKeyResult, VetKDKeyId,
92 VetKDPublicKeyArgs, VetKDPublicKeyResult, WasmMemoryPersistence, WasmModule,
93};
94
95// Following Args types contain `sender_canister_version` field which is set automatically in the corresponding functions.
96// We provide reduced versions of these types to avoid duplication of the field.
97use ic_management_canister_types::{
98 CreateCanisterArgs as CreateCanisterArgsComplete,
99 InstallChunkedCodeArgs as InstallChunkedCodeArgsComplete,
100 InstallCodeArgs as InstallCodeArgsComplete,
101 LoadCanisterSnapshotArgs as LoadCanisterSnapshotArgsComplete,
102 ProvisionalCreateCanisterWithCyclesArgs as ProvisionalCreateCanisterWithCyclesArgsComplete,
103 UninstallCodeArgs as UninstallCodeArgsComplete,
104 UpdateSettingsArgs as UpdateSettingsArgsComplete,
105};
106
107/// The error type for the [`sign_with_ecdsa`] and [`sign_with_schnorr`] functions.
108#[derive(thiserror::Error, Debug, Clone)]
109pub enum SignCallError {
110 /// The signature cost calculation failed.
111 #[error(transparent)]
112 SignCostError(#[from] SignCostError),
113 /// Failed to make the inter-canister call to the management canister.
114 #[error(transparent)]
115 CallFailed(#[from] CallFailed),
116 /// Failed to decode the response from the management canister.
117 #[error(transparent)]
118 CandidDecodeFailed(#[from] CandidDecodeFailed),
119}
120
121/// Creates a new canister.
122///
123/// **Unbounded-wait call**
124///
125/// See [IC method `create_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-create_canister).
126///
127/// # Note
128///
129/// Canister creation costs cycles. That amount will be deducted from the newly created canister.
130/// This method will only attach the required cycles for the canister creation (detemined by [`cost_create_canister`]).
131/// The new canister will have a 0 cycle balance.
132///
133/// To ensure the new canister has extra cycles after creation, use [`create_canister_with_extra_cycles`] instead.
134///
135/// Cycles can also be deposited to the new canister using [`deposit_cycles`].
136///
137/// Check [Gas and cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost#canister-creation) for more details.
138pub async fn create_canister(arg: &CreateCanisterArgs) -> CallResult<CreateCanisterResult> {
139 let complete_arg = CreateCanisterArgsComplete {
140 settings: arg.settings.clone(),
141 sender_canister_version: Some(canister_version()),
142 };
143 let cycles = cost_create_canister();
144 Ok(
145 Call::unbounded_wait(Principal::management_canister(), "create_canister")
146 .with_arg(&complete_arg)
147 .with_cycles(cycles)
148 .await?
149 .candid()?,
150 )
151}
152
153/// Creates a new canister with extra cycles.
154///
155/// **Unbounded-wait call**
156///
157/// See [IC method `create_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-create_canister).
158///
159/// # Note
160///
161/// Canister creation costs cycles. That amount will be deducted from the newly created canister.
162/// This method will attach the required cycles for the canister creation (detemined by [`cost_create_canister`]) plus the `extra_cycles` to the call.
163/// The new cansiter will have a cycle balance of `extra_cycles`.
164///
165/// To simply create a canister with 0 cycle balance, use [`create_canister`] instead.
166///
167/// Check [Gas and cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost#canister-creation) for more details.
168pub async fn create_canister_with_extra_cycles(
169 arg: &CreateCanisterArgs,
170 extra_cycles: u128,
171) -> CallResult<CreateCanisterResult> {
172 let complete_arg = CreateCanisterArgsComplete {
173 settings: arg.settings.clone(),
174 sender_canister_version: Some(canister_version()),
175 };
176 let cycles = cost_create_canister() + extra_cycles;
177 Ok(
178 Call::unbounded_wait(Principal::management_canister(), "create_canister")
179 .with_arg(&complete_arg)
180 .with_cycles(cycles)
181 .await?
182 .candid()?,
183 )
184}
185
186/// Argument type of [`create_canister`] and [`create_canister_with_extra_cycles`].
187///
188/// # Note
189///
190/// This type is a reduced version of [`ic_management_canister_types::CreateCanisterArgs`].
191///
192/// The `sender_canister_version` field is removed as it is set automatically in [`create_canister`] and [`create_canister_with_extra_cycles`].
193#[derive(
194 CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
195)]
196pub struct CreateCanisterArgs {
197 /// See [`CanisterSettings`].
198 pub settings: Option<CanisterSettings>,
199}
200
201/// Updates the settings of a canister.
202///
203/// **Unbounded-wait call**
204///
205/// See [IC method `update_settings`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-update_settings).
206pub async fn update_settings(arg: &UpdateSettingsArgs) -> CallResult<()> {
207 let complete_arg = UpdateSettingsArgsComplete {
208 canister_id: arg.canister_id,
209 settings: arg.settings.clone(),
210 sender_canister_version: Some(canister_version()),
211 };
212 Ok(
213 Call::unbounded_wait(Principal::management_canister(), "update_settings")
214 .with_arg(&complete_arg)
215 .await?
216 .candid()?,
217 )
218}
219
220/// Argument type of [`update_settings`]
221///
222/// # Note
223///
224/// This type is a reduced version of [`ic_management_canister_types::UpdateSettingsArgs`].
225///
226/// The `sender_canister_version` field is removed as it is set automatically in [`update_settings`].
227#[derive(
228 CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone,
229)]
230pub struct UpdateSettingsArgs {
231 /// Canister ID.
232 pub canister_id: CanisterId,
233 /// See [`CanisterSettings`].
234 pub settings: CanisterSettings,
235}
236
237/// Uploads a chunk to the chunk store of a canister.
238///
239/// **Unbounded-wait call**
240///
241/// See [IC method `upload_chunk`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-upload_chunk).
242pub async fn upload_chunk(arg: &UploadChunkArgs) -> CallResult<UploadChunkResult> {
243 Ok(
244 Call::unbounded_wait(Principal::management_canister(), "upload_chunk")
245 .with_arg(arg)
246 .await?
247 .candid()?,
248 )
249}
250
251/// Clears the chunk store of a canister.
252///
253/// **Unbounded-wait call**
254///
255/// See [IC method `clear_chunk_store`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-clear_chunk_store).
256pub async fn clear_chunk_store(arg: &ClearChunkStoreArgs) -> CallResult<()> {
257 Ok(
258 Call::unbounded_wait(Principal::management_canister(), "clear_chunk_store")
259 .with_arg(arg)
260 .await?
261 .candid()?,
262 )
263}
264
265/// Gets the hashes of all chunks stored in the chunk store of a canister.
266///
267/// **Bounded-wait call**
268///
269/// See [IC method `stored_chunks`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-stored_chunks).
270pub async fn stored_chunks(arg: &StoredChunksArgs) -> CallResult<StoredChunksResult> {
271 Ok(
272 Call::bounded_wait(Principal::management_canister(), "stored_chunks")
273 .with_arg(arg)
274 .await?
275 .candid()?,
276 )
277}
278
279/// Installs code into a canister.
280///
281/// **Unbounded-wait call**
282///
283/// See [IC method `install_code`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-install_code).
284pub async fn install_code(arg: &InstallCodeArgs) -> CallResult<()> {
285 let complete_arg = InstallCodeArgsComplete {
286 mode: arg.mode,
287 canister_id: arg.canister_id,
288 wasm_module: arg.wasm_module.clone(),
289 arg: arg.arg.clone(),
290 sender_canister_version: Some(canister_version()),
291 };
292 Ok(
293 Call::unbounded_wait(Principal::management_canister(), "install_code")
294 .with_arg(&complete_arg)
295 .await?
296 .candid()?,
297 )
298}
299
300/// Argument type of [`install_code`].
301///
302/// # Note
303///
304/// This type is a reduced version of [`ic_management_canister_types::InstallCodeArgs`].
305///
306/// The `sender_canister_version` field is removed as it is set automatically in [`install_code`].
307#[derive(
308 CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone,
309)]
310pub struct InstallCodeArgs {
311 /// See [`CanisterInstallMode`].
312 pub mode: CanisterInstallMode,
313 /// Canister ID.
314 pub canister_id: CanisterId,
315 /// Code to be installed.
316 pub wasm_module: WasmModule,
317 /// The argument to be passed to `canister_init` or `canister_post_upgrade`.
318 #[serde(with = "serde_bytes")]
319 pub arg: Vec<u8>,
320}
321
322/// Installs code into a canister where the code has previously been uploaded in chunks.
323///
324/// **Unbounded-wait call**
325///
326/// See [IC method `install_chunked_code`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-install_chunked_code).
327pub async fn install_chunked_code(arg: &InstallChunkedCodeArgs) -> CallResult<()> {
328 let complete_arg = InstallChunkedCodeArgsComplete {
329 mode: arg.mode,
330 target_canister: arg.target_canister,
331 store_canister: arg.store_canister,
332 chunk_hashes_list: arg.chunk_hashes_list.clone(),
333 wasm_module_hash: arg.wasm_module_hash.clone(),
334 arg: arg.arg.clone(),
335 sender_canister_version: Some(canister_version()),
336 };
337 Ok(
338 Call::unbounded_wait(Principal::management_canister(), "install_chunked_code")
339 .with_arg(&complete_arg)
340 .await?
341 .candid()?,
342 )
343}
344
345/// Argument type of [`install_chunked_code`].
346///
347/// # Note
348///
349/// This type is a reduced version of [`ic_management_canister_types::InstallChunkedCodeArgs`].
350///
351/// The `sender_canister_version` field is removed as it is set automatically in [`install_chunked_code`].
352#[derive(
353 CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone,
354)]
355pub struct InstallChunkedCodeArgs {
356 /// See [`CanisterInstallMode`].
357 pub mode: CanisterInstallMode,
358 /// Principal of the canister being installed.
359 pub target_canister: CanisterId,
360 /// The canister in whose chunk storage the chunks are stored (defaults to `target_canister` if not specified).
361 pub store_canister: Option<CanisterId>,
362 /// The list of chunks that make up the canister wasm.
363 pub chunk_hashes_list: Vec<ChunkHash>,
364 /// The sha256 hash of the wasm.
365 #[serde(with = "serde_bytes")]
366 pub wasm_module_hash: Vec<u8>,
367 /// The argument to be passed to `canister_init` or `canister_post_upgrade`.
368 #[serde(with = "serde_bytes")]
369 pub arg: Vec<u8>,
370}
371
372/// Removes a canister's code and state, making the canister empty again.
373///
374/// **Unbounded-wait call**
375///
376/// See [IC method `uninstall_code`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-uninstall_code).
377pub async fn uninstall_code(arg: &UninstallCodeArgs) -> CallResult<()> {
378 let complete_arg = UninstallCodeArgsComplete {
379 canister_id: arg.canister_id,
380 sender_canister_version: Some(canister_version()),
381 };
382 Ok(
383 Call::unbounded_wait(Principal::management_canister(), "uninstall_code")
384 .with_arg(&complete_arg)
385 .await?
386 .candid()?,
387 )
388}
389
390/// Argument type of [`uninstall_code`].
391///
392/// # Note
393///
394/// This type is a reduced version of [`ic_management_canister_types::UninstallCodeArgs`].
395///
396/// The `sender_canister_version` field is removed as it is set automatically in [`uninstall_code`].
397#[derive(
398 CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone,
399)]
400pub struct UninstallCodeArgs {
401 /// Canister ID.
402 pub canister_id: CanisterId,
403}
404
405/// Starts a canister if the canister status was `stopped` or `stopping`.
406///
407/// **Unbounded-wait call**
408///
409/// See [IC method `start_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-start_canister).
410pub async fn start_canister(arg: &StartCanisterArgs) -> CallResult<()> {
411 Ok(
412 Call::unbounded_wait(Principal::management_canister(), "start_canister")
413 .with_arg(arg)
414 .await?
415 .candid()?,
416 )
417}
418
419/// Stops a canister.
420///
421/// **Unbounded-wait call**
422///
423/// See [IC method `stop_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-stop_canister).
424pub async fn stop_canister(arg: &StopCanisterArgs) -> CallResult<()> {
425 Ok(
426 Call::unbounded_wait(Principal::management_canister(), "stop_canister")
427 .with_arg(arg)
428 .await?
429 .candid()?,
430 )
431}
432
433/// Gets status information about the canister.
434///
435/// **Bounded-wait call**
436///
437/// See [IC method `canister_status`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-canister_status).
438pub async fn canister_status(arg: &CanisterStatusArgs) -> CallResult<CanisterStatusResult> {
439 Ok(
440 Call::bounded_wait(Principal::management_canister(), "canister_status")
441 .with_arg(arg)
442 .await?
443 .candid()?,
444 )
445}
446
447/// Gets public information about the canister.
448///
449/// **Bounded-wait call**
450///
451/// See [IC method `canister_info`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-canister_info).
452pub async fn canister_info(arg: &CanisterInfoArgs) -> CallResult<CanisterInfoResult> {
453 Ok(
454 Call::bounded_wait(Principal::management_canister(), "canister_info")
455 .with_arg(arg)
456 .await?
457 .candid()?,
458 )
459}
460/// Gets canister's metadata contained in custom sections whose names have the form `icp:public <name>` or `icp:private <name>`
461///
462/// **Bounded-wait call**
463///
464/// See [IC method `canister_metadata`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-canister_metadata).
465pub async fn canister_metadata(arg: &CanisterMetadataArgs) -> CallResult<CanisterMetadataResult> {
466 Ok(
467 Call::bounded_wait(Principal::management_canister(), "canister_metadata")
468 .with_arg(arg)
469 .await?
470 .candid()?,
471 )
472}
473
474/// Deletes a canister.
475///
476/// **Unbounded-wait call**
477///
478/// See [IC method `delete_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-delete_canister).
479pub async fn delete_canister(arg: &DeleteCanisterArgs) -> CallResult<()> {
480 Ok(
481 Call::unbounded_wait(Principal::management_canister(), "delete_canister")
482 .with_arg(arg)
483 .await?
484 .candid()?,
485 )
486}
487
488/// Deposits cycles to a canister.
489///
490/// **Unbounded-wait call**
491///
492/// See [IC method `deposit_cycles`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-deposit_cycles).
493pub async fn deposit_cycles(arg: &DepositCyclesArgs, cycles: u128) -> CallResult<()> {
494 Ok(
495 Call::unbounded_wait(Principal::management_canister(), "deposit_cycles")
496 .with_arg(arg)
497 .with_cycles(cycles)
498 .await?
499 .candid()?,
500 )
501}
502
503/// Gets 32 pseudo-random bytes.
504///
505/// **Bounded-wait call**
506///
507/// See [IC method `raw_rand`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-raw_rand).
508pub async fn raw_rand() -> CallResult<RawRandResult> {
509 Ok(
510 Call::bounded_wait(Principal::management_canister(), "raw_rand")
511 .await?
512 .candid()?,
513 )
514}
515
516/// Calculates the cost of making an HTTP outcall with the given [`HttpRequestArgs`].
517///
518/// [`http_request`] and [`http_request_with_closure`] invoke this method internally and attach the required cycles to the call.
519///
520/// # Note
521///
522/// Alternatively, [`api::cost_http_request`][ic0_cost_http_request] requires manually calculating the request size and the maximum response size.
523/// This method handles the calculation internally.
524pub fn cost_http_request(arg: &HttpRequestArgs) -> u128 {
525 let request_size = (arg.url.len()
526 + arg
527 .headers
528 .iter()
529 .map(|h| h.name.len() + h.value.len())
530 .sum::<usize>()
531 + arg.body.as_ref().map_or(0, |b| b.len())
532 + arg
533 .transform
534 .as_ref()
535 .map_or(0, |t| t.context.len() + t.function.0.method.len()))
536 as u64;
537 // As stated here: https://internetcomputer.org/docs/references/ic-interface-spec#ic-http_request:
538 // "The upper limit on the maximal size for the response is 2MB (2,000,000B) and this value also applies if no maximal size value is specified."
539 let max_res_bytes = arg.max_response_bytes.unwrap_or(2_000_000);
540 ic0_cost_http_request(request_size, max_res_bytes)
541}
542
543/// Makes an HTTP outcall.
544///
545/// **Unbounded-wait call**
546///
547/// See [IC method `http_request`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-http_request).
548///
549/// # Note
550///
551/// HTTP outcall costs cycles which varies with the request size and the maximum response size.
552/// This method attaches the required cycles (detemined by [`cost_http_request`]) to the call.
553///
554/// Check [HTTPS outcalls cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost#https-outcalls) for more details.
555pub async fn http_request(arg: &HttpRequestArgs) -> CallResult<HttpRequestResult> {
556 let cycles = cost_http_request(arg);
557 Ok(
558 Call::unbounded_wait(Principal::management_canister(), "http_request")
559 .with_arg(arg)
560 .with_cycles(cycles)
561 .await?
562 .candid()?,
563 )
564}
565
566/// Constructs a [`TransformContext`] from a query method name and context.
567pub fn transform_context_from_query(
568 candid_function_name: String,
569 context: Vec<u8>,
570) -> TransformContext {
571 TransformContext {
572 context,
573 function: TransformFunc(candid::Func {
574 method: candid_function_name,
575 principal: crate::api::canister_self(),
576 }),
577 }
578}
579
580#[cfg(feature = "transform-closure")]
581mod transform_closure {
582 use super::{
583 CallResult, HttpRequestArgs, HttpRequestResult, Principal, TransformArgs, http_request,
584 transform_context_from_query,
585 };
586 use candid::{decode_one, encode_one};
587 use slotmap::{DefaultKey, Key, KeyData, SlotMap};
588 use std::cell::RefCell;
589
590 thread_local! {
591 #[allow(clippy::type_complexity)]
592 static TRANSFORMS: RefCell<SlotMap<DefaultKey, Box<dyn FnOnce(HttpRequestResult) -> HttpRequestResult>>> = RefCell::default();
593 }
594
595 #[cfg_attr(
596 target_family = "wasm",
597 unsafe(export_name = "canister_query <ic-cdk internal> http_transform")
598 )]
599 #[cfg_attr(
600 not(target_family = "wasm"),
601 unsafe(export_name = "canister_query_ic_cdk_internal.http_transform")
602 )]
603 extern "C" fn http_transform() {
604 ic_cdk_executor::in_tracking_query_executor_context(|| {
605 use crate::api::{msg_arg_data, msg_caller, msg_reply};
606 if msg_caller() != Principal::management_canister() {
607 crate::trap(
608 "This function is internal to ic-cdk and should not be called externally.",
609 );
610 }
611 let arg_bytes = msg_arg_data();
612 let transform_args: TransformArgs = decode_one(&arg_bytes).unwrap();
613 let int = u64::from_be_bytes(transform_args.context[..].try_into().unwrap());
614 let key = DefaultKey::from(KeyData::from_ffi(int));
615 let func = TRANSFORMS.with(|transforms| transforms.borrow_mut().remove(key));
616 let Some(func) = func else {
617 crate::trap(format!("Missing transform function for request {int}"));
618 };
619 let transformed = func(transform_args.response);
620 let encoded = encode_one(transformed).unwrap();
621 msg_reply(encoded);
622 });
623 }
624
625 /// Makes an HTTP outcall and transforms the response using a closure.
626 ///
627 /// **Unbounded-wait call**
628 ///
629 /// See [IC method `http_request`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-http_request).
630 ///
631 /// # Panics
632 ///
633 /// This method will panic if the `transform` field in `arg` is not `None`,
634 /// as it would conflict with the transform function provided by the closure.
635 ///
636 /// # Note
637 ///
638 /// This method provides a straightforward way to transform the HTTP outcall result.
639 /// If you need to specify a custom transform [`context`](`ic_management_canister_types::TransformContext::context`),
640 /// please use [`http_request`] instead.
641 ///
642 /// HTTP outcall costs cycles which varies with the request size and the maximum response size.
643 /// This method attaches the required cycles (detemined by [`cost_http_request`](crate::api::cost_http_request)) to the call.
644 ///
645 /// Check [Gas and cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost) for more details.
646 #[cfg_attr(docsrs, doc(cfg(feature = "transform-closure")))]
647 pub async fn http_request_with_closure(
648 arg: &HttpRequestArgs,
649 transform_func: impl FnOnce(HttpRequestResult) -> HttpRequestResult + 'static,
650 ) -> CallResult<HttpRequestResult> {
651 assert!(
652 arg.transform.is_none(),
653 "The `transform` field in `HttpRequestArgs` must be `None` when using a closure"
654 );
655 let transform_func = Box::new(transform_func) as _;
656 let key = TRANSFORMS.with(|transforms| transforms.borrow_mut().insert(transform_func));
657 struct DropGuard(DefaultKey);
658 impl Drop for DropGuard {
659 fn drop(&mut self) {
660 TRANSFORMS.with(|transforms| transforms.borrow_mut().remove(self.0));
661 }
662 }
663 let key = DropGuard(key);
664 let context = key.0.data().as_ffi().to_be_bytes().to_vec();
665 let arg = HttpRequestArgs {
666 transform: Some(transform_context_from_query(
667 "<ic-cdk internal> http_transform".to_string(),
668 context,
669 )),
670 ..arg.clone()
671 };
672 http_request(&arg).await
673 }
674}
675
676#[cfg(feature = "transform-closure")]
677pub use transform_closure::http_request_with_closure;
678
679/// Gets a SEC1 encoded ECDSA public key for the given canister using the given derivation path.
680///
681/// **Bounded-wait call**
682///
683/// See [IC method `ecdsa_public_key`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-ecdsa_public_key).
684pub async fn ecdsa_public_key(arg: &EcdsaPublicKeyArgs) -> CallResult<EcdsaPublicKeyResult> {
685 Ok(
686 Call::bounded_wait(Principal::management_canister(), "ecdsa_public_key")
687 .with_arg(arg)
688 .await?
689 .candid()?,
690 )
691}
692
693/// Calculates the cost of ECDSA signanature with the given [`SignWithEcdsaArgs`].
694///
695/// [`sign_with_ecdsa`] invokes this method internally and attaches the required cycles to the call.
696///
697/// # Note
698///
699/// Alternatively, [`api::cost_sign_with_ecdsa`][ic0_cost_sign_with_ecdsa] takes the numeric representation of the curve.
700pub fn cost_sign_with_ecdsa(arg: &SignWithEcdsaArgs) -> Result<u128, SignCostError> {
701 ic0_cost_sign_with_ecdsa(&arg.key_id.name, arg.key_id.curve.into())
702}
703
704/// Gets a new ECDSA signature of the given `message_hash` with a user-specified amount of cycles.
705///
706/// **Unbounded-wait call**
707///
708/// The signature can be separately verified against a derived ECDSA public key.
709///
710/// See [IC method `sign_with_ecdsa`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-sign_with_ecdsa).
711///
712/// # Errors
713///
714/// This method returns an error of type [`SignCallError`].
715///
716/// The signature cost calculation may fail before the inter-canister call is made, resulting in a [`SignCallError::SignCostError`].
717///
718/// Since the call argument is constructed as [`SignWithEcdsaArgs`], the `ecdsa_curve` field is guaranteed to be valid.
719/// Therefore, [`SignCostError::InvalidCurveOrAlgorithm`] should not occur. If it does, it is likely an issue with the IC. Please report it.
720///
721/// # Note
722///
723/// Signature costs cycles which varies for different curves and key names.
724/// This method attaches the required cycles (detemined by [`cost_sign_with_ecdsa`]) to the call.
725///
726/// Check [Threshold signatures](https://internetcomputer.org/docs/current/references/t-sigs-how-it-works/#api-fees) for more details.
727pub async fn sign_with_ecdsa(
728 arg: &SignWithEcdsaArgs,
729) -> Result<SignWithEcdsaResult, SignCallError> {
730 let cycles = cost_sign_with_ecdsa(arg)?;
731 Ok(
732 Call::unbounded_wait(Principal::management_canister(), "sign_with_ecdsa")
733 .with_arg(arg)
734 .with_cycles(cycles)
735 .await?
736 .candid()?,
737 )
738}
739
740/// Gets a SEC1 encoded Schnorr public key for the given canister using the given derivation path.
741///
742/// **Bounded-wait call**
743///
744/// See [IC method `schnorr_public_key`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-schnorr_public_key).
745pub async fn schnorr_public_key(arg: &SchnorrPublicKeyArgs) -> CallResult<SchnorrPublicKeyResult> {
746 Ok(
747 Call::bounded_wait(Principal::management_canister(), "schnorr_public_key")
748 .with_arg(arg)
749 .await?
750 .candid()?,
751 )
752}
753
754/// Calculates the cost of Schnorr signanature with the given [`SignWithSchnorrArgs`].
755///
756/// [`sign_with_schnorr`] invokes this method internally and attaches the required cycles to the call.
757///
758/// # Note
759///
760/// Alternatively, [`api::cost_sign_with_schnorr`][ic0_cost_sign_with_schnorr] takes the numeric representation of the algorithm.
761pub fn cost_sign_with_schnorr(arg: &SignWithSchnorrArgs) -> Result<u128, SignCostError> {
762 ic0_cost_sign_with_schnorr(&arg.key_id.name, arg.key_id.algorithm.into())
763}
764
765/// Gets a new Schnorr signature of the given message with a user-specified amount of cycles.
766///
767/// **Unbounded-wait call**
768///
769/// The signature can be separately verified against a derived Schnorr public key.
770///
771/// See [IC method `sign_with_schnorr`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-sign_with_schnorr).
772///
773/// # Errors
774///
775/// This method returns an error of type [`SignCallError`].
776///
777/// The signature cost calculation may fail before the inter-canister call is made, resulting in a [`SignCallError::SignCostError`].
778///
779/// Since the call argument is constructed as [`SignWithSchnorrArgs`], the `algorithm` field is guaranteed to be valid.
780/// Therefore, [`SignCostError::InvalidCurveOrAlgorithm`] should not occur. If it does, it is likely an issue with the IC. Please report it.
781///
782/// # Note
783///
784/// Signature costs cycles which varies for different algorithms and key names.
785/// This method attaches the required cycles (detemined by [`cost_sign_with_schnorr`]) to the call.
786///
787/// Check [Threshold signatures](https://internetcomputer.org/docs/current/references/t-sigs-how-it-works/#api-fees) for more details.
788pub async fn sign_with_schnorr(
789 arg: &SignWithSchnorrArgs,
790) -> Result<SignWithSchnorrResult, SignCallError> {
791 let cycles = cost_sign_with_schnorr(arg)?;
792 Ok(
793 Call::unbounded_wait(Principal::management_canister(), "sign_with_schnorr")
794 .with_arg(arg)
795 .with_cycles(cycles)
796 .await?
797 .candid()?,
798 )
799}
800
801/// Gets a VetKD public key.
802///
803/// **Bounded-wait call**
804///
805/// As of 2025-05-01, the vetKD feature is not yet available on the IC mainnet.
806/// The lastest PocketIC with the `with_nonmainnet_features(true)` flag can be used to test it.
807///
808/// See [IC method `vetkd_public_key`](https://github.com/dfinity/portal/pull/3763).
809///
810/// Later, the description will be available in [the interface spec](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-vetkd_public_key).
811pub async fn vetkd_public_key(arg: &VetKDPublicKeyArgs) -> CallResult<VetKDPublicKeyResult> {
812 Ok(
813 Call::bounded_wait(Principal::management_canister(), "vetkd_public_key")
814 .with_arg(arg)
815 .await?
816 .candid()?,
817 )
818}
819
820/// Calculates the cost of VetKD key derivation with the given [`VetKDDeriveKeyArgs`].
821///
822/// [`vetkd_derive_key`] invokes this method internally and attaches the required cycles to the call.
823///
824/// # Note
825///
826/// Alternatively, [`api::cost_vetkd_derive_key`][ic0_cost_vetkd_derive_key] takes the numeric representation of the algorithm.
827pub fn cost_vetkd_derive_key(arg: &VetKDDeriveKeyArgs) -> Result<u128, SignCostError> {
828 ic0_cost_vetkd_derive_key(&arg.key_id.name, arg.key_id.curve.into())
829}
830
831/// Derives a key from the given input.
832///
833/// **Unbounded-wait call**
834///
835/// The returned encrypted key can be separately decrypted using the private secret key corresponding to the transport public key provided in the request, and the derivation correctness can be verified against the input and context provided in the request. See the [`ic_vetkeys` frontend library](https://github.com/dfinity/vetkd-devkit/tree/main/frontend/ic_vetkeys) for more details.
836///
837/// As of 2025-05-01, the vetKD feature is not yet available on the IC mainnet.
838/// The lastest PocketIC with the `with_nonmainnet_features(true)` flag can be used to test it.
839///
840/// See [IC method `vetkd_derive_key`](https://github.com/dfinity/portal/pull/3763) for the API specification.
841///
842/// Later, the description will be available in [the interface spec](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-vetkd_derive_key).
843///
844/// # Errors
845///
846/// This method returns an error of type [`SignCallError`].
847///
848/// The signature cost calculation may fail before the inter-canister call is made, resulting in a [`SignCallError::SignCostError`].
849///
850/// Since the call argument is constructed as [`VetKDDeriveKeyArgs`], the `curve` field is guaranteed to be valid.
851/// Therefore, [`SignCostError::InvalidCurveOrAlgorithm`] should not occur. If it does, it is likely an issue with the IC. Please report it.
852///
853/// # Note
854///
855/// VetKD key derivation costs cycles which varies for different algorithms and key names.
856/// This method attaches the required cycles (detemined by [`cost_vetkd_derive_key`]) to the call.
857///
858/// Check [Threshold signatures](https://internetcomputer.org/docs/current/references/t-sigs-how-it-works/#api-fees) for more details.
859pub async fn vetkd_derive_key(
860 arg: &VetKDDeriveKeyArgs,
861) -> Result<VetKDDeriveKeyResult, SignCallError> {
862 let cycles = cost_vetkd_derive_key(arg)?;
863 Ok(
864 Call::unbounded_wait(Principal::management_canister(), "vetkd_derive_key")
865 .with_arg(arg)
866 .with_cycles(cycles)
867 .await?
868 .candid()?,
869 )
870}
871
872/// Gets a time series of subnet's node metrics.
873///
874/// **Bounded-wait call**
875///
876/// See [IC method `node_metrics_history`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-node_metrics_history).
877pub async fn node_metrics_history(
878 arg: &NodeMetricsHistoryArgs,
879) -> CallResult<NodeMetricsHistoryResult> {
880 Ok(
881 Call::bounded_wait(Principal::management_canister(), "node_metrics_history")
882 .with_arg(arg)
883 .await?
884 .candid()?,
885 )
886}
887
888/// Gets the metadata about a subnet.
889///
890/// **Bounded-wait call**
891///
892/// See [IC method `subnet_info`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-subnet_info).
893pub async fn subnet_info(arg: &SubnetInfoArgs) -> CallResult<SubnetInfoResult> {
894 Ok(
895 Call::bounded_wait(Principal::management_canister(), "subnet_info")
896 .with_arg(arg)
897 .await?
898 .candid()?,
899 )
900}
901
902/// Creates a new canister with specified amount of cycles balance.
903///
904/// **Unbounded-wait call**
905///
906/// # Note
907///
908/// This method is only available in local development instances.
909///
910/// See [IC method `provisional_create_canister_with_cycles`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-provisional_create_canister_with_cycles).
911pub async fn provisional_create_canister_with_cycles(
912 arg: &ProvisionalCreateCanisterWithCyclesArgs,
913) -> CallResult<ProvisionalCreateCanisterWithCyclesResult> {
914 let complete_arg = ProvisionalCreateCanisterWithCyclesArgsComplete {
915 amount: arg.amount.clone(),
916 settings: arg.settings.clone(),
917 specified_id: arg.specified_id,
918 sender_canister_version: Some(canister_version()),
919 };
920 Ok(Call::unbounded_wait(
921 Principal::management_canister(),
922 "provisional_create_canister_with_cycles",
923 )
924 .with_arg(&complete_arg)
925 .await?
926 .candid()?)
927}
928
929/// Argument type of [`provisional_create_canister_with_cycles`].
930///
931/// # Note
932///
933/// This type is a reduced version of [`ic_management_canister_types::ProvisionalCreateCanisterWithCyclesArgs`].
934///
935/// The `sender_canister_version` field is removed as it is set automatically in [`provisional_create_canister_with_cycles`].
936#[derive(
937 CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
938)]
939pub struct ProvisionalCreateCanisterWithCyclesArgs {
940 /// The created canister will have this amount of cycles.
941 pub amount: Option<Nat>,
942 /// Canister settings.
943 pub settings: Option<CanisterSettings>,
944 /// If set, the canister will be created under this id.
945 pub specified_id: Option<CanisterId>,
946}
947
948/// Adds cycles to a canister.
949///
950/// **Unbounded-wait call**
951///
952/// # Note
953///
954/// This method is only available in local development instances.
955///
956/// See [IC method `provisional_top_up_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-provisional_top_up_canister).
957pub async fn provisional_top_up_canister(arg: &ProvisionalTopUpCanisterArgs) -> CallResult<()> {
958 Ok(Call::unbounded_wait(
959 Principal::management_canister(),
960 "provisional_top_up_canister",
961 )
962 .with_arg(arg)
963 .await?
964 .candid()?)
965}
966
967/// Takes a snapshot of the specified canister.
968///
969/// **Unbounded-wait call**
970///
971/// A snapshot consists of the wasm memory, stable memory, certified variables, wasm chunk store and wasm binary.
972///
973/// See [IC method `take_canister_snapshot`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-take_canister_snapshot).
974pub async fn take_canister_snapshot(
975 arg: &TakeCanisterSnapshotArgs,
976) -> CallResult<TakeCanisterSnapshotResult> {
977 Ok(
978 Call::unbounded_wait(Principal::management_canister(), "take_canister_snapshot")
979 .with_arg(arg)
980 .await?
981 .candid()?,
982 )
983}
984
985/// Loads a snapshot onto the canister.
986///
987/// **Unbounded-wait call**
988///
989/// It fails if no snapshot with the specified `snapshot_id` can be found.
990///
991/// See [IC method `load_canister_snapshot`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-load_canister_snapshot).
992pub async fn load_canister_snapshot(arg: &LoadCanisterSnapshotArgs) -> CallResult<()> {
993 let complete_arg = LoadCanisterSnapshotArgsComplete {
994 canister_id: arg.canister_id,
995 snapshot_id: arg.snapshot_id.clone(),
996 sender_canister_version: Some(canister_version()),
997 };
998 Ok(
999 Call::unbounded_wait(Principal::management_canister(), "load_canister_snapshot")
1000 .with_arg(&complete_arg)
1001 .await?
1002 .candid()?,
1003 )
1004}
1005
1006/// Argument type of [`load_canister_snapshot`].
1007///
1008/// # Note
1009///
1010/// This type is a reduced version of [`ic_management_canister_types::LoadCanisterSnapshotArgs`].
1011///
1012/// The `sender_canister_version` field is removed as it is set automatically in [`load_canister_snapshot`].
1013#[derive(
1014 CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone,
1015)]
1016pub struct LoadCanisterSnapshotArgs {
1017 /// Canister ID.
1018 pub canister_id: CanisterId,
1019 /// ID of the snapshot to be loaded.
1020 pub snapshot_id: SnapshotId,
1021}
1022
1023/// Reads metadata of a snapshot of a canister.
1024///
1025/// **Bounded-wait call**
1026///
1027/// See [IC method `read_canister_snapshot_metadata`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-read_canister_snapshot_metadata).
1028pub async fn read_canister_snapshot_metadata(
1029 arg: &ReadCanisterSnapshotMetadataArgs,
1030) -> CallResult<ReadCanisterSnapshotMetadataResult> {
1031 Ok(Call::bounded_wait(
1032 Principal::management_canister(),
1033 "read_canister_snapshot_metadata",
1034 )
1035 .with_arg(arg)
1036 .await?
1037 .candid()?)
1038}
1039
1040/// Reads data of a snapshot of a canister.
1041///
1042/// **Bounded-wait call**
1043///
1044/// See [IC method `read_canister_snapshot_data`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-read_canister_snapshot_data).
1045pub async fn read_canister_snapshot_data(
1046 arg: &ReadCanisterSnapshotDataArgs,
1047) -> CallResult<ReadCanisterSnapshotDataResult> {
1048 Ok(Call::bounded_wait(
1049 Principal::management_canister(),
1050 "read_canister_snapshot_data",
1051 )
1052 .with_arg(arg)
1053 .await?
1054 .candid()?)
1055}
1056
1057/// Creates a snapshot of that canister by uploading the snapshot's metadata.
1058///
1059/// **Bounded-wait call**
1060///
1061/// See [IC method `upload_canister_snapshot_metadata`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-upload_canister_snapshot_metadata).
1062pub async fn upload_canister_snapshot_metadata(
1063 arg: &UploadCanisterSnapshotMetadataArgs,
1064) -> CallResult<UploadCanisterSnapshotMetadataResult> {
1065 Ok(Call::bounded_wait(
1066 Principal::management_canister(),
1067 "upload_canister_snapshot_metadata",
1068 )
1069 .with_arg(arg)
1070 .await?
1071 .candid()?)
1072}
1073
1074/// Uploads data to a snapshot of that canister.
1075///
1076/// **Bounded-wait call**
1077///
1078/// See [IC method `upload_canister_snapshot_data`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-upload_canister_snapshot_data).
1079pub async fn upload_canister_snapshot_data(arg: &UploadCanisterSnapshotDataArgs) -> CallResult<()> {
1080 Ok(Call::bounded_wait(
1081 Principal::management_canister(),
1082 "upload_canister_snapshot_data",
1083 )
1084 .with_arg(arg)
1085 .await?
1086 .candid()?)
1087}
1088
1089/// Lists the snapshots of the canister.
1090///
1091/// **Bounded-wait call**
1092///
1093/// See [IC method `list_canister_snapshots`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-list_canister_snapshots).
1094pub async fn list_canister_snapshots(
1095 arg: &ListCanisterSnapshotsArgs,
1096) -> CallResult<ListCanisterSnapshotsResult> {
1097 Ok(
1098 Call::bounded_wait(Principal::management_canister(), "list_canister_snapshots")
1099 .with_arg(arg)
1100 .await?
1101 .candid()?,
1102 )
1103}
1104
1105/// Deletes a specified snapshot that belongs to an existing canister.
1106///
1107/// **Unbounded-wait call**
1108///
1109/// An error will be returned if the snapshot is not found.
1110///
1111/// See [IC method `delete_canister_snapshot`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-delete_canister_snapshot).
1112pub async fn delete_canister_snapshot(arg: &DeleteCanisterSnapshotArgs) -> CallResult<()> {
1113 Ok(
1114 Call::unbounded_wait(Principal::management_canister(), "delete_canister_snapshot")
1115 .with_arg(arg)
1116 .await?
1117 .candid()?,
1118 )
1119}