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}