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}