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 endpoints.
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 endpoint.
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 endpoints 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
57use crate::api::{
58    canister_version, cost_create_canister, cost_http_request as ic0_cost_http_request,
59    cost_sign_with_ecdsa as ic0_cost_sign_with_ecdsa,
60    cost_sign_with_schnorr as ic0_cost_sign_with_schnorr, SignCostError,
61};
62use crate::call::{Call, CallFailed, CallResult, CandidDecodeFailed};
63use candid::{CandidType, Nat, Principal};
64use serde::{Deserialize, Serialize};
65
66// Re-export types from the `ic-management-canister-types` crate.
67pub use ic_management_canister_types::{
68    Bip341, CanisterId, CanisterInfoArgs, CanisterInfoResult, CanisterInstallMode,
69    CanisterSettings, CanisterStatusArgs, CanisterStatusResult, CanisterStatusType, Change,
70    ChangeDetails, ChangeOrigin, ChunkHash, ClearChunkStoreArgs, CodeDeploymentMode,
71    CodeDeploymentRecord, ControllersChangeRecord, CreateCanisterResult, CreationRecord,
72    DefiniteCanisterSettings, DeleteCanisterArgs, DeleteCanisterSnapshotArgs, DepositCyclesArgs,
73    EcdsaCurve, EcdsaKeyId, EcdsaPublicKeyArgs, EcdsaPublicKeyResult, FromCanisterRecord,
74    FromUserRecord, HttpHeader, HttpMethod, HttpRequestArgs, HttpRequestResult,
75    ListCanisterSnapshotsArgs, ListCanisterSnapshotsResult, LoadSnapshotRecord, LogVisibility,
76    NodeMetrics, NodeMetricsHistoryArgs, NodeMetricsHistoryRecord, NodeMetricsHistoryResult,
77    ProvisionalCreateCanisterWithCyclesResult, ProvisionalTopUpCanisterArgs, QueryStats,
78    RawRandResult, SchnorrAlgorithm, SchnorrAux, SchnorrKeyId, SchnorrPublicKeyArgs,
79    SchnorrPublicKeyResult, SignWithEcdsaArgs, SignWithEcdsaResult, SignWithSchnorrArgs,
80    SignWithSchnorrResult, Snapshot, SnapshotId, StartCanisterArgs, StopCanisterArgs,
81    StoredChunksArgs, StoredChunksResult, SubnetInfoArgs, SubnetInfoResult,
82    TakeCanisterSnapshotArgs, TakeCanisterSnapshotResult, TransformArgs, TransformContext,
83    TransformFunc, UpgradeFlags, UploadChunkArgs, UploadChunkResult, WasmMemoryPersistence,
84    WasmModule,
85};
86// Following Args types contain `sender_canister_version` field which is set automatically in the corresponding functions.
87// We provide reduced versions of these types to avoid duplication of the field.
88use ic_management_canister_types::{
89    CreateCanisterArgs as CreateCanisterArgsComplete,
90    InstallChunkedCodeArgs as InstallChunkedCodeArgsComplete,
91    InstallCodeArgs as InstallCodeArgsComplete,
92    LoadCanisterSnapshotArgs as LoadCanisterSnapshotArgsComplete,
93    ProvisionalCreateCanisterWithCyclesArgs as ProvisionalCreateCanisterWithCyclesArgsComplete,
94    UninstallCodeArgs as UninstallCodeArgsComplete,
95    UpdateSettingsArgs as UpdateSettingsArgsComplete,
96};
97
98/// The error type for the [`sign_with_ecdsa`] and [`sign_with_schnorr`] functions.
99#[derive(thiserror::Error, Debug, Clone)]
100pub enum SignCallError {
101    /// The signature cost calculation failed.
102    #[error(transparent)]
103    SignCostError(#[from] SignCostError),
104    /// Failed to make the inter-canister call to the management canister.
105    #[error(transparent)]
106    CallFailed(#[from] CallFailed),
107    /// Failed to decode the response from the management canister.
108    #[error(transparent)]
109    CandidDecodeFailed(#[from] CandidDecodeFailed),
110}
111
112/// Creates a new canister.
113///
114/// **Unbounded-wait call**
115///
116/// See [IC method `create_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-create_canister).
117///
118/// # Note
119///
120/// Canister creation costs cycles. That amount will be deducted from the newly created canister.
121/// This method will only attach the required cycles for the canister creation (detemined by [`cost_create_canister`]).
122/// The new canister will have a 0 cycle balance.
123///
124/// To ensure the new canister has extra cycles after creation, use [`create_canister_with_extra_cycles`] instead.
125///
126/// Cycles can also be deposited to the new canister using [`deposit_cycles`].
127///
128/// Check [Gas and cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost#canister-creation) for more details.
129pub async fn create_canister(arg: &CreateCanisterArgs) -> CallResult<CreateCanisterResult> {
130    let complete_arg = CreateCanisterArgsComplete {
131        settings: arg.settings.clone(),
132        sender_canister_version: Some(canister_version()),
133    };
134    let cycles = cost_create_canister();
135    Ok(
136        Call::unbounded_wait(Principal::management_canister(), "create_canister")
137            .with_arg(&complete_arg)
138            .with_cycles(cycles)
139            .await?
140            .candid()?,
141    )
142}
143
144/// Creates a new canister with extra cycles.
145///
146/// **Unbounded-wait call**
147///
148/// See [IC method `create_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-create_canister).
149///
150/// # Note
151///
152/// Canister creation costs cycles. That amount will be deducted from the newly created canister.
153/// This method will attach the required cycles for the canister creation (detemined by [`cost_create_canister`]) plus the `extra_cycles` to the call.
154/// The new cansiter will have a cycle balance of `extra_cycles`.
155///
156/// To simply create a canister with 0 cycle balance, use [`create_canister`] instead.
157///
158/// Check [Gas and cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost#canister-creation) for more details.
159pub async fn create_canister_with_extra_cycles(
160    arg: &CreateCanisterArgs,
161    extra_cycles: u128,
162) -> CallResult<CreateCanisterResult> {
163    let complete_arg = CreateCanisterArgsComplete {
164        settings: arg.settings.clone(),
165        sender_canister_version: Some(canister_version()),
166    };
167    let cycles = cost_create_canister() + extra_cycles;
168    Ok(
169        Call::unbounded_wait(Principal::management_canister(), "create_canister")
170            .with_arg(&complete_arg)
171            .with_cycles(cycles)
172            .await?
173            .candid()?,
174    )
175}
176
177/// Argument type of [`create_canister`] and [`create_canister_with_extra_cycles`].
178///
179/// # Note
180///
181/// This type is a reduced version of [`ic_management_canister_types::CreateCanisterArgs`].
182///
183/// The `sender_canister_version` field is removed as it is set automatically in [`create_canister`] and [`create_canister_with_extra_cycles`].
184#[derive(
185    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
186)]
187pub struct CreateCanisterArgs {
188    /// See [`CanisterSettings`].
189    pub settings: Option<CanisterSettings>,
190}
191
192/// Updates the settings of a canister.
193///
194/// **Unbounded-wait call**
195///
196/// See [IC method `update_settings`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-update_settings).
197pub async fn update_settings(arg: &UpdateSettingsArgs) -> CallResult<()> {
198    let complete_arg = UpdateSettingsArgsComplete {
199        canister_id: arg.canister_id,
200        settings: arg.settings.clone(),
201        sender_canister_version: Some(canister_version()),
202    };
203    Ok(
204        Call::unbounded_wait(Principal::management_canister(), "update_settings")
205            .with_arg(&complete_arg)
206            .await?
207            .candid()?,
208    )
209}
210
211/// Argument type of [`update_settings`]
212///
213/// # Note
214///
215/// This type is a reduced version of [`ic_management_canister_types::UpdateSettingsArgs`].
216///
217/// The `sender_canister_version` field is removed as it is set automatically in [`update_settings`].
218#[derive(
219    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone,
220)]
221pub struct UpdateSettingsArgs {
222    /// Canister ID.
223    pub canister_id: CanisterId,
224    /// See [CanisterSettings].
225    pub settings: CanisterSettings,
226}
227
228/// Uploads a chunk to the chunk store of a canister.
229///
230/// **Unbounded-wait call**
231///
232/// See [IC method `upload_chunk`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-upload_chunk).
233pub async fn upload_chunk(arg: &UploadChunkArgs) -> CallResult<UploadChunkResult> {
234    Ok(
235        Call::unbounded_wait(Principal::management_canister(), "upload_chunk")
236            .with_arg(arg)
237            .await?
238            .candid()?,
239    )
240}
241
242/// Clears the chunk store of a canister.
243///
244/// **Unbounded-wait call**
245///
246/// See [IC method `clear_chunk_store`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-clear_chunk_store).
247pub async fn clear_chunk_store(arg: &ClearChunkStoreArgs) -> CallResult<()> {
248    Ok(
249        Call::unbounded_wait(Principal::management_canister(), "clear_chunk_store")
250            .with_arg(arg)
251            .await?
252            .candid()?,
253    )
254}
255
256/// Gets the hashes of all chunks stored in the chunk store of a canister.
257///
258/// **Bounded-wait call**
259///
260/// See [IC method `stored_chunks`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-stored_chunks).
261pub async fn stored_chunks(arg: &StoredChunksArgs) -> CallResult<StoredChunksResult> {
262    Ok(
263        Call::bounded_wait(Principal::management_canister(), "stored_chunks")
264            .with_arg(arg)
265            .await?
266            .candid()?,
267    )
268}
269
270/// Installs code into a canister.
271///
272/// **Unbounded-wait call**
273///
274/// See [IC method `install_code`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-install_code).
275pub async fn install_code(arg: &InstallCodeArgs) -> CallResult<()> {
276    let complete_arg = InstallCodeArgsComplete {
277        mode: arg.mode,
278        canister_id: arg.canister_id,
279        wasm_module: arg.wasm_module.clone(),
280        arg: arg.arg.clone(),
281        sender_canister_version: Some(canister_version()),
282    };
283    Ok(
284        Call::unbounded_wait(Principal::management_canister(), "install_code")
285            .with_arg(&complete_arg)
286            .await?
287            .candid()?,
288    )
289}
290
291/// Argument type of [`install_code`].
292///
293/// # Note
294///
295/// This type is a reduced version of [`ic_management_canister_types::InstallCodeArgs`].
296///
297/// The `sender_canister_version` field is removed as it is set automatically in [`install_code`].
298#[derive(
299    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone,
300)]
301pub struct InstallCodeArgs {
302    /// See [CanisterInstallMode].
303    pub mode: CanisterInstallMode,
304    /// Canister ID.
305    pub canister_id: CanisterId,
306    /// Code to be installed.
307    pub wasm_module: WasmModule,
308    /// The argument to be passed to `canister_init` or `canister_post_upgrade`.
309    #[serde(with = "serde_bytes")]
310    pub arg: Vec<u8>,
311}
312
313/// Installs code into a canister where the code has previously been uploaded in chunks.
314///
315/// **Unbounded-wait call**
316///
317/// See [IC method `install_chunked_code`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-install_chunked_code).
318pub async fn install_chunked_code(arg: &InstallChunkedCodeArgs) -> CallResult<()> {
319    let complete_arg = InstallChunkedCodeArgsComplete {
320        mode: arg.mode,
321        target_canister: arg.target_canister,
322        store_canister: arg.store_canister,
323        chunk_hashes_list: arg.chunk_hashes_list.clone(),
324        wasm_module_hash: arg.wasm_module_hash.clone(),
325        arg: arg.arg.clone(),
326        sender_canister_version: Some(canister_version()),
327    };
328    Ok(
329        Call::unbounded_wait(Principal::management_canister(), "install_chunked_code")
330            .with_arg(&complete_arg)
331            .await?
332            .candid()?,
333    )
334}
335
336/// Argument type of [`install_chunked_code`].
337///
338/// # Note
339///
340/// This type is a reduced version of [`ic_management_canister_types::InstallChunkedCodeArgs`].
341///
342/// The `sender_canister_version` field is removed as it is set automatically in [`install_chunked_code`].
343#[derive(
344    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone,
345)]
346pub struct InstallChunkedCodeArgs {
347    /// See [`CanisterInstallMode`].
348    pub mode: CanisterInstallMode,
349    /// Principal of the canister being installed.
350    pub target_canister: CanisterId,
351    /// The canister in whose chunk storage the chunks are stored (defaults to target_canister if not specified).
352    pub store_canister: Option<CanisterId>,
353    /// The list of chunks that make up the canister wasm.
354    pub chunk_hashes_list: Vec<ChunkHash>,
355    /// The sha256 hash of the wasm.
356    #[serde(with = "serde_bytes")]
357    pub wasm_module_hash: Vec<u8>,
358    /// The argument to be passed to `canister_init` or `canister_post_upgrade`.
359    #[serde(with = "serde_bytes")]
360    pub arg: Vec<u8>,
361}
362
363/// Removes a canister's code and state, making the canister empty again.
364///
365/// **Unbounded-wait call**
366///
367/// See [IC method `uninstall_code`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-uninstall_code).
368pub async fn uninstall_code(arg: &UninstallCodeArgs) -> CallResult<()> {
369    let complete_arg = UninstallCodeArgsComplete {
370        canister_id: arg.canister_id,
371        sender_canister_version: Some(canister_version()),
372    };
373    Ok(
374        Call::unbounded_wait(Principal::management_canister(), "uninstall_code")
375            .with_arg(&complete_arg)
376            .await?
377            .candid()?,
378    )
379}
380
381/// Argument type of [`uninstall_code`].
382///
383/// # Note
384///
385/// This type is a reduced version of [`ic_management_canister_types::UninstallCodeArgs`].
386///
387/// The `sender_canister_version` field is removed as it is set automatically in [`uninstall_code`].
388#[derive(
389    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone,
390)]
391pub struct UninstallCodeArgs {
392    /// Canister ID.
393    pub canister_id: CanisterId,
394}
395
396/// Starts a canister if the canister status was `stopped` or `stopping`.
397///
398/// **Unbounded-wait call**
399///
400/// See [IC method `start_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-start_canister).
401pub async fn start_canister(arg: &StartCanisterArgs) -> CallResult<()> {
402    Ok(
403        Call::unbounded_wait(Principal::management_canister(), "start_canister")
404            .with_arg(arg)
405            .await?
406            .candid()?,
407    )
408}
409
410/// Stops a canister.
411///
412/// **Unbounded-wait call**
413///
414/// See [IC method `stop_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-stop_canister).
415pub async fn stop_canister(arg: &StopCanisterArgs) -> CallResult<()> {
416    Ok(
417        Call::unbounded_wait(Principal::management_canister(), "stop_canister")
418            .with_arg(arg)
419            .await?
420            .candid()?,
421    )
422}
423
424/// Gets status information about the canister.
425///
426/// **Bounded-wait call**
427///
428/// See [IC method `canister_status`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-canister_status).
429pub async fn canister_status(arg: &CanisterStatusArgs) -> CallResult<CanisterStatusResult> {
430    Ok(
431        Call::bounded_wait(Principal::management_canister(), "canister_status")
432            .with_arg(arg)
433            .await?
434            .candid()?,
435    )
436}
437
438/// Gets public information about the canister.
439///
440/// **Bounded-wait call**
441///
442/// See [IC method `canister_info`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-canister_info).
443pub async fn canister_info(arg: &CanisterInfoArgs) -> CallResult<CanisterInfoResult> {
444    Ok(
445        Call::bounded_wait(Principal::management_canister(), "canister_info")
446            .with_arg(arg)
447            .await?
448            .candid()?,
449    )
450}
451
452/// Deletes a canister.
453///
454/// **Unbounded-wait call**
455///
456/// See [IC method `delete_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-delete_canister).
457pub async fn delete_canister(arg: &DeleteCanisterArgs) -> CallResult<()> {
458    Ok(
459        Call::unbounded_wait(Principal::management_canister(), "delete_canister")
460            .with_arg(arg)
461            .await?
462            .candid()?,
463    )
464}
465
466/// Deposits cycles to a canister.
467///
468/// **Unbounded-wait call**
469///
470/// See [IC method `deposit_cycles`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-deposit_cycles).
471pub async fn deposit_cycles(arg: &DepositCyclesArgs, cycles: u128) -> CallResult<()> {
472    Ok(
473        Call::unbounded_wait(Principal::management_canister(), "deposit_cycles")
474            .with_arg(arg)
475            .with_cycles(cycles)
476            .await?
477            .candid()?,
478    )
479}
480
481/// Gets 32 pseudo-random bytes.
482///
483/// **Bounded-wait call**
484///
485/// See [IC method `raw_rand`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-raw_rand).
486pub async fn raw_rand() -> CallResult<RawRandResult> {
487    Ok(
488        Call::bounded_wait(Principal::management_canister(), "raw_rand")
489            .await?
490            .candid()?,
491    )
492}
493
494/// Calculates the cost of making an HTTP outcall with the given [`HttpRequestArgs`].
495///
496/// [`http_request`] and [`http_request_with_closure`] invoke this method internally and attach the required cycles to the call.
497///
498/// # Note
499///
500/// Alternatively, [`api::cost_http_request`][ic0_cost_http_request] requires manually calculating the request size and the maximum response size.
501/// This method handles the calculation internally.
502pub fn cost_http_request(arg: &HttpRequestArgs) -> u128 {
503    let request_size = (arg.url.len()
504        + arg
505            .headers
506            .iter()
507            .map(|h| h.name.len() + h.value.len())
508            .sum::<usize>()
509        + arg.body.as_ref().map(|b| b.len()).unwrap_or(0)
510        + arg
511            .transform
512            .as_ref()
513            .map(|t| t.context.len() + t.function.0.method.len())
514            .unwrap_or(0)) as u64;
515    // As stated here: https://internetcomputer.org/docs/references/ic-interface-spec#ic-http_request:
516    // "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."
517    let max_res_bytes = arg.max_response_bytes.unwrap_or(2_000_000);
518    ic0_cost_http_request(request_size, max_res_bytes)
519}
520
521/// Makes an HTTP outcall with a user-specified amount of cycles.
522///
523/// **Unbounded-wait call**
524///
525/// See [IC method `http_request`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-http_request).
526///
527/// # Note
528///
529/// HTTP outcall costs cycles which varies with the request size and the maximum response size.
530/// This method attaches the required cycles (detemined by [`cost_http_request`]) to the call.
531///
532/// Check [HTTPS outcalls cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost#https-outcalls) for more details.
533pub async fn http_request(arg: &HttpRequestArgs) -> CallResult<HttpRequestResult> {
534    let cycles = cost_http_request(arg);
535    Ok(
536        Call::unbounded_wait(Principal::management_canister(), "http_request")
537            .with_arg(arg)
538            .with_cycles(cycles)
539            .await?
540            .candid()?,
541    )
542}
543
544/// Constructs a [`TransformContext`] from a query method name and context.
545pub fn transform_context_from_query(
546    candid_function_name: String,
547    context: Vec<u8>,
548) -> TransformContext {
549    TransformContext {
550        context,
551        function: TransformFunc(candid::Func {
552            method: candid_function_name,
553            principal: crate::api::canister_self(),
554        }),
555    }
556}
557
558#[cfg(feature = "transform-closure")]
559mod transform_closure {
560    use super::{
561        http_request, transform_context_from_query, CallResult, HttpRequestArgs, HttpRequestResult,
562        Principal, TransformArgs,
563    };
564    use candid::{decode_one, encode_one};
565    use slotmap::{DefaultKey, Key, KeyData, SlotMap};
566    use std::cell::RefCell;
567
568    thread_local! {
569        #[allow(clippy::type_complexity)]
570        static TRANSFORMS: RefCell<SlotMap<DefaultKey, Box<dyn FnOnce(HttpRequestResult) -> HttpRequestResult>>> = RefCell::default();
571    }
572
573    #[export_name = "canister_query <ic-cdk internal> http_transform"]
574    extern "C" fn http_transform() {
575        use crate::api::{msg_arg_data, msg_caller, msg_reply};
576        if msg_caller() != Principal::management_canister() {
577            crate::trap("This function is internal to ic-cdk and should not be called externally.");
578        }
579        crate::setup();
580        let arg_bytes = msg_arg_data();
581        let transform_args: TransformArgs = decode_one(&arg_bytes).unwrap();
582        let int = u64::from_be_bytes(transform_args.context[..].try_into().unwrap());
583        let key = DefaultKey::from(KeyData::from_ffi(int));
584        let func = TRANSFORMS.with(|transforms| transforms.borrow_mut().remove(key));
585        let Some(func) = func else {
586            crate::trap(format!("Missing transform function for request {int}"));
587        };
588        let transformed = func(transform_args.response);
589        let encoded = encode_one(transformed).unwrap();
590        msg_reply(encoded);
591    }
592
593    /// Makes an HTTP outcall and transforms the response using a closure.
594    ///
595    /// **Unbounded-wait call**
596    ///
597    /// See [IC method `http_request`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-http_request).
598    ///
599    /// # Panics
600    ///
601    /// This method will panic if the `transform` field in `arg` is not `None`,
602    /// as it would conflict with the transform function provided by the closure.
603    ///
604    /// # Note
605    ///
606    /// This method provides a straightforward way to transform the HTTP outcall result.
607    /// If you need to specify a custom transform [`context`](`ic_management_canister_types::TransformContext::context`),
608    /// please use [`http_request`] instead.
609    ///
610    /// HTTP outcall costs cycles which varies with the request size and the maximum response size.
611    /// This method attaches the required cycles (detemined by [`cost_http_request`](crate::api::cost_http_request)) to the call.
612    ///
613    /// Check [Gas and cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost) for more details.
614    #[cfg_attr(docsrs, doc(cfg(feature = "transform-closure")))]
615    pub async fn http_request_with_closure(
616        arg: &HttpRequestArgs,
617        transform_func: impl FnOnce(HttpRequestResult) -> HttpRequestResult + 'static,
618    ) -> CallResult<HttpRequestResult> {
619        assert!(
620            arg.transform.is_none(),
621            "The `transform` field in `HttpRequestArgs` must be `None` when using a closure"
622        );
623        let transform_func = Box::new(transform_func) as _;
624        let key = TRANSFORMS.with(|transforms| transforms.borrow_mut().insert(transform_func));
625        struct DropGuard(DefaultKey);
626        impl Drop for DropGuard {
627            fn drop(&mut self) {
628                TRANSFORMS.with(|transforms| transforms.borrow_mut().remove(self.0));
629            }
630        }
631        let key = DropGuard(key);
632        let context = key.0.data().as_ffi().to_be_bytes().to_vec();
633        let arg = HttpRequestArgs {
634            transform: Some(transform_context_from_query(
635                "<ic-cdk internal> http_transform".to_string(),
636                context,
637            )),
638            ..arg.clone()
639        };
640        http_request(&arg).await
641    }
642}
643
644#[cfg(feature = "transform-closure")]
645pub use transform_closure::http_request_with_closure;
646
647/// Gets a SEC1 encoded ECDSA public key for the given canister using the given derivation path.
648///
649/// **Bounded-wait call**
650///
651/// See [IC method `ecdsa_public_key`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-ecdsa_public_key).
652pub async fn ecdsa_public_key(arg: &EcdsaPublicKeyArgs) -> CallResult<EcdsaPublicKeyResult> {
653    Ok(
654        Call::bounded_wait(Principal::management_canister(), "ecdsa_public_key")
655            .with_arg(arg)
656            .await?
657            .candid()?,
658    )
659}
660
661/// Calculates the cost of ECDSA signanature with the given [`SignWithEcdsaArgs`].
662///
663/// [`sign_with_ecdsa`] invokes this method internally and attaches the required cycles to the call.
664///
665/// # Note
666///
667/// Alternatively, [`api::cost_sign_with_ecdsa`][ic0_cost_sign_with_ecdsa] takes the numeric representation of the curve.
668pub fn cost_sign_with_ecdsa(arg: &SignWithEcdsaArgs) -> Result<u128, SignCostError> {
669    ic0_cost_sign_with_ecdsa(&arg.key_id.name, arg.key_id.curve.into())
670}
671
672/// Gets a new ECDSA signature of the given message_hash with a user-specified amount of cycles.
673///
674/// **Unbounded-wait call**
675///
676/// The signature can be separately verified against a derived ECDSA public key.
677///
678/// See [IC method `sign_with_ecdsa`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-sign_with_ecdsa).
679///
680/// # Errors
681///
682/// This method returns an error of type [`SignCallError`].
683///
684/// The signature cost calculation may fail before the inter-canister call is made, resulting in a [`SignCallError::SignCostError`].
685///
686/// Since the call argument is constructed as [`SignWithEcdsaArgs`], the `ecdsa_curve` field is guaranteed to be valid.
687/// Therefore, [`SignCostError::InvalidCurveOrAlgorithm`] should not occur. If it does, it is likely an issue with the IC. Please report it.
688///
689/// # Note
690///
691/// Signature costs cycles which varies for different curves and key names.
692/// This method attaches the required cycles (detemined by [`cost_sign_with_ecdsa`]) to the call.
693///
694/// Check [Threshold signatures](https://internetcomputer.org/docs/current/references/t-sigs-how-it-works/#api-fees) for more details.
695pub async fn sign_with_ecdsa(
696    arg: &SignWithEcdsaArgs,
697) -> Result<SignWithEcdsaResult, SignCallError> {
698    let cycles = cost_sign_with_ecdsa(arg)?;
699    Ok(
700        Call::unbounded_wait(Principal::management_canister(), "sign_with_ecdsa")
701            .with_arg(arg)
702            .with_cycles(cycles)
703            .await?
704            .candid()?,
705    )
706}
707
708/// Gets a SEC1 encoded Schnorr public key for the given canister using the given derivation path.
709///
710/// **Bounded-wait call**
711///
712/// See [IC method `schnorr_public_key`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-schnorr_public_key).
713pub async fn schnorr_public_key(arg: &SchnorrPublicKeyArgs) -> CallResult<SchnorrPublicKeyResult> {
714    Ok(
715        Call::bounded_wait(Principal::management_canister(), "schnorr_public_key")
716            .with_arg(arg)
717            .await?
718            .candid()?,
719    )
720}
721
722/// Calculates the cost of Schnorr signanature with the given [`SignWithSchnorrArgs`].
723///
724/// [`sign_with_schnorr`] invokes this method internally and attaches the required cycles to the call.
725///
726/// # Note
727///
728/// Alternatively, [`api::cost_sign_with_schnorr`][ic0_cost_sign_with_schnorr] takes the numeric representation of the algorithm.
729pub fn cost_sign_with_schnorr(arg: &SignWithSchnorrArgs) -> Result<u128, SignCostError> {
730    ic0_cost_sign_with_schnorr(&arg.key_id.name, arg.key_id.algorithm.into())
731}
732
733/// Gets a new Schnorr signature of the given message with a user-specified amount of cycles.
734///
735/// **Unbounded-wait call**
736///
737/// The signature can be separately verified against a derived Schnorr public key.
738///
739/// See [IC method `sign_with_schnorr`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-sign_with_schnorr).
740///
741/// # Errors
742///
743/// This method returns an error of type [`SignCallError`].
744///
745/// The signature cost calculation may fail before the inter-canister call is made, resulting in a [`SignCallError::SignCostError`].
746///
747/// Since the call argument is constructed as [`SignWithSchnorrArgs`], the `algorithm` field is guaranteed to be valid.
748/// Therefore, [`SignCostError::InvalidCurveOrAlgorithm`] should not occur. If it does, it is likely an issue with the IC. Please report it.
749///
750/// # Note
751///
752/// Signature costs cycles which varies for different algorithms and key names.
753/// This method attaches the required cycles (detemined by [`cost_sign_with_schnorr`]) to the call.
754///
755/// Check [Threshold signatures](https://internetcomputer.org/docs/current/references/t-sigs-how-it-works/#api-fees) for more details.
756pub async fn sign_with_schnorr(
757    arg: &SignWithSchnorrArgs,
758) -> Result<SignWithSchnorrResult, SignCallError> {
759    let cycles = cost_sign_with_schnorr(arg)?;
760    Ok(
761        Call::unbounded_wait(Principal::management_canister(), "sign_with_schnorr")
762            .with_arg(arg)
763            .with_cycles(cycles)
764            .await?
765            .candid()?,
766    )
767}
768
769/// Gets a time series of subnet's node metrics.
770///
771/// **Bounded-wait call**
772///
773/// See [IC method `node_metrics_history`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-node_metrics_history).
774pub async fn node_metrics_history(
775    arg: &NodeMetricsHistoryArgs,
776) -> CallResult<NodeMetricsHistoryResult> {
777    Ok(
778        Call::bounded_wait(Principal::management_canister(), "node_metrics_history")
779            .with_arg(arg)
780            .await?
781            .candid()?,
782    )
783}
784
785/// Gets the metadata about a subnet.
786///
787/// **Bounded-wait call**
788///
789/// See [IC method `subnet_info`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-subnet_info).
790pub async fn subnet_info(arg: &SubnetInfoArgs) -> CallResult<SubnetInfoResult> {
791    Ok(
792        Call::bounded_wait(Principal::management_canister(), "subnet_info")
793            .with_arg(arg)
794            .await?
795            .candid()?,
796    )
797}
798
799/// Creates a new canister with specified amount of cycles balance.
800///
801/// **Unbounded-wait call**
802///
803/// # Note
804///
805/// This method is only available in local development instances.
806///
807/// See [IC method `provisional_create_canister_with_cycles`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-provisional_create_canister_with_cycles).
808pub async fn provisional_create_canister_with_cycles(
809    arg: &ProvisionalCreateCanisterWithCyclesArgs,
810) -> CallResult<ProvisionalCreateCanisterWithCyclesResult> {
811    let complete_arg = ProvisionalCreateCanisterWithCyclesArgsComplete {
812        amount: arg.amount.clone(),
813        settings: arg.settings.clone(),
814        specified_id: arg.specified_id,
815        sender_canister_version: Some(canister_version()),
816    };
817    Ok(Call::unbounded_wait(
818        Principal::management_canister(),
819        "provisional_create_canister_with_cycles",
820    )
821    .with_arg(&complete_arg)
822    .await?
823    .candid()?)
824}
825
826/// Argument type of [`provisional_create_canister_with_cycles`].
827///
828/// # Note
829///
830/// This type is a reduced version of [`ic_management_canister_types::ProvisionalCreateCanisterWithCyclesArgs`].
831///
832/// The `sender_canister_version` field is removed as it is set automatically in [`provisional_create_canister_with_cycles`].
833#[derive(
834    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
835)]
836pub struct ProvisionalCreateCanisterWithCyclesArgs {
837    /// The created canister will have this amount of cycles.
838    pub amount: Option<Nat>,
839    /// Canister settings.
840    pub settings: Option<CanisterSettings>,
841    /// If set, the canister will be created under this id.
842    pub specified_id: Option<CanisterId>,
843}
844
845/// Adds cycles to a canister.
846///
847/// **Unbounded-wait call**
848///
849/// # Note
850///
851/// This method is only available in local development instances.
852///
853/// See [IC method `provisional_top_up_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-provisional_top_up_canister).
854pub async fn provisional_top_up_canister(arg: &ProvisionalTopUpCanisterArgs) -> CallResult<()> {
855    Ok(Call::unbounded_wait(
856        Principal::management_canister(),
857        "provisional_top_up_canister",
858    )
859    .with_arg(arg)
860    .await?
861    .candid()?)
862}
863
864/// Takes a snapshot of the specified canister.
865///
866/// **Unbounded-wait call**
867///
868/// A snapshot consists of the wasm memory, stable memory, certified variables, wasm chunk store and wasm binary.
869///
870/// See [IC method `take_canister_snapshot`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-take_canister_snapshot).
871pub async fn take_canister_snapshot(
872    arg: &TakeCanisterSnapshotArgs,
873) -> CallResult<TakeCanisterSnapshotResult> {
874    Ok(
875        Call::unbounded_wait(Principal::management_canister(), "take_canister_snapshot")
876            .with_arg(arg)
877            .await?
878            .candid()?,
879    )
880}
881
882/// Loads a snapshot onto the canister.
883///
884/// **Unbounded-wait call**
885///
886/// It fails if no snapshot with the specified `snapshot_id` can be found.
887///
888/// See [IC method `load_canister_snapshot`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-load_canister_snapshot).
889pub async fn load_canister_snapshot(arg: &LoadCanisterSnapshotArgs) -> CallResult<()> {
890    let complete_arg = LoadCanisterSnapshotArgsComplete {
891        canister_id: arg.canister_id,
892        snapshot_id: arg.snapshot_id.clone(),
893        sender_canister_version: Some(canister_version()),
894    };
895    Ok(
896        Call::unbounded_wait(Principal::management_canister(), "load_canister_snapshot")
897            .with_arg(&complete_arg)
898            .await?
899            .candid()?,
900    )
901}
902
903/// Argument type of [`load_canister_snapshot`].
904///
905/// # Note
906///
907/// This type is a reduced version of [`ic_management_canister_types::LoadCanisterSnapshotArgs`].
908///
909/// The `sender_canister_version` field is removed as it is set automatically in [`load_canister_snapshot`].
910#[derive(
911    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone,
912)]
913pub struct LoadCanisterSnapshotArgs {
914    /// Canister ID.
915    pub canister_id: CanisterId,
916    /// ID of the snapshot to be loaded.
917    pub snapshot_id: SnapshotId,
918}
919
920/// Lists the snapshots of the canister.
921///
922/// **Bounded-wait call**
923///
924/// See [IC method `list_canister_snapshots`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-list_canister_snapshots).
925pub async fn list_canister_snapshots(
926    arg: &ListCanisterSnapshotsArgs,
927) -> CallResult<ListCanisterSnapshotsResult> {
928    Ok(
929        Call::bounded_wait(Principal::management_canister(), "list_canister_snapshots")
930            .with_arg(arg)
931            .await?
932            .candid()?,
933    )
934}
935
936/// Deletes a specified snapshot that belongs to an existing canister.
937///
938/// **Unbounded-wait call**
939///
940/// An error will be returned if the snapshot is not found.
941///
942/// See [IC method `delete_canister_snapshot`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-delete_canister_snapshot).
943pub async fn delete_canister_snapshot(arg: &DeleteCanisterSnapshotArgs) -> CallResult<()> {
944    Ok(
945        Call::unbounded_wait(Principal::management_canister(), "delete_canister_snapshot")
946            .with_arg(arg)
947            .await?
948            .candid()?,
949    )
950}