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