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_id
canister_settings
definite_canister_settings
wasm_module
Self
Public functions
create_canister
install_code
start_canister
canister_status
deposit_cycles
update_settings
stop_canister
uninstall_code
delete_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 theExperimentalCycles
module
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 theExperimentalCycles
module
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.