IC Management Canister
The Internet Computer (IC) provides a management canister to manage canisters programmatically. This canister, like any other canister, has a Candid Interface and can be called by other canisters or ingress messages.
In this chapter, we will only look at a subset of the interface for canister management.
Motoko Interface
This is a subset of the interface as a Motoko module. It only includes canister management related types and functions. It is available as ic-management-interface.mo
Types
canister_idcanister_settingsdefinite_canister_settingswasm_moduleSelf
Public functions
create_canisterinstall_codestart_canistercanister_statusdeposit_cyclesupdate_settingsstop_canisteruninstall_codedelete_canister
module {
public type canister_id = Principal;
public type canister_settings = {
freezing_threshold : ?Nat;
controllers : ?[Principal];
memory_allocation : ?Nat;
compute_allocation : ?Nat;
};
public type definite_canister_settings = {
freezing_threshold : Nat;
controllers : [Principal];
memory_allocation : Nat;
compute_allocation : Nat;
};
public type wasm_module = [Nat8];
public type Self = actor {
canister_status : shared { canister_id : canister_id } -> async {
status : { #stopped; #stopping; #running };
memory_size : Nat;
cycles : Nat;
settings : definite_canister_settings;
idle_cycles_burned_per_day : Nat;
module_hash : ?[Nat8];
};
create_canister : shared { settings : ?canister_settings } -> async {
canister_id : canister_id;
};
delete_canister : shared { canister_id : canister_id } -> async ();
deposit_cycles : shared { canister_id : canister_id } -> async ();
install_code : shared {
arg : [Nat8];
wasm_module : wasm_module;
mode : { #reinstall; #upgrade; #install };
canister_id : canister_id;
} -> async ();
start_canister : shared { canister_id : canister_id } -> async ();
stop_canister : shared { canister_id : canister_id } -> async ();
uninstall_code : shared { canister_id : canister_id } -> async ();
update_settings : shared {
canister_id : Principal;
settings : canister_settings;
} -> async ();
};
};
Import
We import the management canister by importing the interface file and declaring an actor by principle aaaaa-aa and type it as the Self (which is declared in the interface).
import Interface "ic-management-interface";
import Cycles "mo:base/ExperimentalCycles";
actor {
// The IC Management Canister ID
let IC = "aaaaa-aa";
let ic = actor(IC) : Interface.Self;
}
We can now reference the canister as ic.
We also imported ExperimentalCycles because some of our function calls require cycles to be added.
Public functions
The source file with function calls is available here including a test to run all functions. You can deploy it locally for testing.
Create canister
To create a new canister, we call the create_canister function.
create_canister : shared { settings : ?canister_settings } -> async {
canister_id : canister_id;
};
The function may take an optional canisters_settings record to set initial settings for the canister, but this argument may be null.
The function returns a record containing the Principal of the newly created canister.
NOTE
To create a new canister, you must add cycles to the call using theExperimentalCyclesmodule
Example
var canister_principal : Text = "";
func create_canister() : async* () {
Cycles.add(10 ** 12);
let newCanister = await ic.create_canister({ settings = null });
canister_principal := Principal.toText(newCanister.canister_id);
};
Canister status
To get the current status of a canister we call canister_status. We only provide a simple record with a canister_id (principal) of the canister we are interested in. Only controllers of the canister can ask for its settings.
canister_status : shared { canister_id : canister_id } -> async {
status : { #stopped; #stopping; #running };
memory_size : Nat;
cycles : Nat;
settings : definite_canister_settings;
idle_cycles_burned_per_day : Nat;
module_hash : ?[Nat8];
};
The function returns a record containing the status of the canister, the memory_size in bytes, the cycles balance, a definite_canister_settings with its current settings, the idle_cycles_burned_per_day which indicates the average cycle consumption of the canister and a module_hash if the canister has a wasm module installed on it.
Example
var controllers : [Principal] = [];
func canister_status() : async* () {
let canister_id = Principal.fromText(canister_principal);
let canisterStatus = await ic.canister_status({ canister_id });
controllers := canisterStatus.settings.controllers;
};
Deposit cycles
To deposit cycles into a canister we call deposit_cycles. Anyone can call this function.
We only need to provide a record with the canister_id of the canister we want to deposit into.
deposit_cycles : shared { canister_id : canister_id } -> async ();
NOTE
To deposit cycles into a canister, you must add cycles to the call using theExperimentalCyclesmodule
Example
func deposit_cycles() : async* () {
Cycles.add(10 ** 12);
let canister_id = Principal.fromText(canister_principal);
await ic.deposit_cycles({ canister_id });
};
Update settings
To update the settings of a canister, we call update_settings and provide the canister_id together with the new canister_settings.
update_settings : shared {
canister_id : Principal;
settings : canister_settings;
} -> async ();
Example
func update_settings() : async* () {
let settings : Interface.canister_settings = {
controllers = ?controllers;
compute_allocation = null;
memory_allocation = null;
freezing_threshold = ?(60 * 60 * 24 * 7);
};
let canister_id = Principal.fromText(canister_principal);
await ic.update_settings({ canister_id; settings });
};
Uninstall code
To uninstall (remove) the wasm module from a canister we call uninstall_code with a record containing the canister_id. Only controllers of the canister can call this function.
uninstall_code : shared { canister_id : canister_id } -> async ();
Example
func uninstall_code() : async* () {
let canister_id = Principal.fromText(canister_principal);
await ic.uninstall_code({ canister_id });
};
Stop canister
To stop a running canister we call stop_canister with a record containing the canister_id. Only controllers of the canister can call this function.
stop_canister : shared { canister_id : canister_id } -> async ();
Example
func stop_canister() : async* () {
let canister_id = Principal.fromText(canister_principal);
await ic.stop_canister({ canister_id });
};
Start canister
To start a stopped canister we call start_canister with a record containing the canister_id. Only controllers of the canister can call this function.
start_canister : shared { canister_id : canister_id } -> async ();
Example
func start_canister() : async* () {
let canister_id = Principal.fromText(canister_principal);
await ic.start_canister({ canister_id });
};
Delete canister
To delete a stopped canister we call delete_canister with a record containing the canister_id. Only stopped canisters can be deleted and only controllers of the canister can call this function.
delete_canister : shared { canister_id : canister_id } -> async ();
Example
func delete_canister() : async* () {
let canister_id = Principal.fromText(canister_principal);
await ic.delete_canister({ canister_id });
};
Install code
To install a wasm module in a canister, we call install_code. Only controllers of a canister can call this function.
We need to provide a wasm module install arguments as [Nat8] arrays. We also pick a mode to indicate whether we are freshly installing or upgrading the canister. And finally, we provide the canister id (principal) that we want to install code into.
install_code : shared {
arg : [Nat8];
wasm_module : wasm_module;
mode : { #reinstall; #upgrade; #install };
canister_id : canister_id;
} -> async ();
This function is atomic meaning that it either succeeds and returns () or it has no effect.
Test
To test all the functions, we await* all of them in a try-catch block inside a regular shared public function. This test is available in ic-management-public-functions.mo.
public func ic_management_canister_test() : async { #OK; #ERR : Text } {
try {
await* create_canister();
await* canister_status();
await* deposit_cycles();
await* update_settings();
await* uninstall_code();
await* stop_canister();
await* start_canister();
await* stop_canister();
await* delete_canister();
#OK;
} catch (e) {
#ERR(Error.message(e));
};
};
Our function either returns #OK or #ERR with a caught error message that is converted into text.