For AI agents: Documentation index at /llms.txt

Skip to content

Canister Interface (System API)

A canister module is a WebAssembly module that is either in binary format (typically .wasm) or gzip-compressed (typically .wasm.gz). If the module starts with byte sequence [0x1f, 0x8b, 0x08], then the system decompresses the contents as a gzip stream according to RFC-1952 and then parses the output as a WebAssembly binary.

The System API is the interface between the running canister and the Internet Computer. It allows the WebAssembly module of a canister to expose functionality to the users (method entry points) and the IC (e.g. initialization), and exposes functionality of the IC to the canister (e.g. calling other canisters). Because WebAssembly is rather low-level, it also explains how to express higher level concepts (e.g. binary blobs).

We want to leverage advanced WebAssembly features, such as WebAssembly host references. But as they are not yet supported by all tools involved, this section describes an initial System API that does not rely on host references. In section Outlook: Using Host References, we outline some of the proposed uses of WebAssembly host references.

In order for a WebAssembly module to be usable as the code for the canister, it needs to conform to the following requirements:

  • It may declare (import or export) at most one memory.

  • It may only import a function if it is listed in Overview of imports. In particular, all imported functions belong to the ic0 module (denoted by the prefix ic0.). The value of I ∈ {i32, i64} specifying whether the imported functions have 32-bit or 64-bit pointers is derived from the bit-width of the declared memory defaulting to I = i32 if the canister declares no memory.

  • It may have a (start) function.

  • If it exports a function called canister_init, the function must have type () -> ().

  • If it exports a function called canister_inspect_message, the function must have type () -> ().

  • If it exports a function called canister_pre_upgrade, the function must have type () -> ().

  • If it exports a function called canister_post_upgrade, the function must have type () -> ().

  • If it exports a function called canister_heartbeat, the function must have type () -> ().

  • If it exports a function called canister_on_low_wasm_memory, the function must have type () -> ().

  • If it exports a function called canister_global_timer, the function must have type () -> ().

  • If it exports any functions called canister_update <name>, canister_query <name>, or canister_composite_query <name> for some name, the functions must have type () -> ().

  • It may not export more than one function called canister_update <name>, canister_query <name>, or canister_composite_query <name> with the same name.

  • It may not export other methods the names of which start with the prefix canister_ besides the methods allowed above.

  • It may not have both icp:public <name> and icp:private <name> with the same name as the custom section name.

  • It may not have other custom sections the names of which start with the prefix icp: besides the icp:public and icp:private .

  • The IC may reject WebAssembly modules that

    • declare more than 50,000 functions, or

    • declare more than 1,000 globals, or

    • declare more than 16 exported custom sections (the custom section names with prefix icp:), or

    • the number of all exported functions called canister_update <name>, canister_query <name>, or canister_composite_query <name> exceeds 1,000, or

    • the sum of <name> lengths in all exported functions called canister_update <name>, canister_query <name>, or canister_composite_query <name> exceeds 20,000, or

    • the total size of the custom sections (the sum of <name> lengths in their names icp:public <name> and icp:private <name> plus the sum of their content lengths) exceeds 1MiB.

WebAssembly number types (i32, i64) do not indicate if the numbers are to be interpreted as signed or unsigned. Unless noted otherwise, whenever the System API interprets them as numbers (e.g. memory pointers, buffer offsets, array sizes), they are to be interpreted as unsigned.

The canister provides entry points which are invoked by the IC under various circumstances:

  • The canister may export a function with name canister_init and type () -> ().

  • The canister may export a function with name canister_pre_upgrade and type () -> ().

  • The canister may export a function with name canister_post_upgrade and type () -> ().

  • The canister may export functions with name canister_inspect_message with type () -> ().

  • The canister may export a function with name canister_heartbeat with type () -> ().

  • The canister may export a function with name canister_global_timer with type () -> ().

  • The canister may export functions with name canister_update <name> and type () -> ().

  • The canister may export functions with name canister_query <name> and type () -> ().

  • The canister may export functions with name canister_composite_query <name> and type () -> ().

  • The canister may export a function with the name canister_on_low_wasm_memory and type () -> ().

  • The canister table may contain functions of type (env : I) -> () which may be used as callbacks for inter-canister calls and composite query methods. The value of I ∈ {i32, i64} specifying whether the imported functions have 32-bit or 64-bit pointers is derived from the bit-width of the declared memory defaulting to I = i32 if the canister declares no memory.

If the execution of any of these entry points traps for any reason, then all changes to the WebAssembly state, as well as the effect of any externally visible system call (like ic0.msg_reply, ic0.msg_reject, ic0.call_perform), are discarded. For upgrades, this transactional behavior applies to the canister_pre_upgrade/canister_post_upgrade sequence as a whole.

If canister_init is present, then this is the first exported WebAssembly function invoked by the IC. The argument that was passed along with the canister initialization call (see IC method) is available to the canister via ic0.msg_arg_data_size/copy.

The IC assumes the canister to be fully instantiated if the canister_init method entry point returns. If the canister_init method entry point traps, then canister installation has failed, and the canister is reverted to its previous state (i.e. empty with install, or whatever it was for a reinstall).

When a canister is upgraded to a new WebAssembly module, the IC:

  1. Invokes canister_pre_upgrade (if exported by the current canister code and skip_pre_upgrade is not opt true) on the old instance, to give the canister a chance to clean up (e.g. move data to stable memory).

  2. Instantiates the new module, including the execution of (start), with a fresh WebAssembly state.

  3. Invokes canister_post_upgrade (if present) on the new instance, passing the arg provided in the install_code call (IC method).

The stable memory is preserved throughout the process; the WebAssembly memory is discarded unless wasm_memory_persistence is opt keep; any other WebAssembly state is discarded.

During these steps, no other entry point of the old or new canister is invoked. The canister_init function of the new canister is not invoked.

These steps are atomic: If canister_pre_upgrade or canister_post_upgrade trap, the upgrade has failed, and the canister is reverted to the previous state. Otherwise, the upgrade has succeeded, and the old instance is discarded.

To define a public method of name name, a WebAssembly module exports a function with name canister_update <name>, canister_query <name>, or canister_composite_query <name> and type () -> (). We call this the method entry point. The name of the exported function distinguishes update, query, and composite query methods.

The argument of the call (e.g. the content of the arg field in the API request to call a canister method) is copied into the canister on demand using the System API functions shown below.

Eventually, a method will want to send a response, using ic0.reply or ic0.reject

For periodic or time-based execution, the WebAssembly module can export a function with name canister_heartbeat. The heartbeats scheduling algorithm is implementation-defined.

canister_heartbeat is triggered by the IC, and therefore has no arguments and cannot reply or reject. Still, the function canister_heartbeat can initiate new calls.

For time-based execution, the WebAssembly module can export a function with name canister_global_timer. This function is called if the canister has set its global timer (using the System API function ic0.global_timer_set) and the current time (as returned by the System API function ic0.time) has passed the value of the global timer.

Once the function canister_global_timer is scheduled, the canister’s global timer is deactivated. The global timer is also deactivated upon changes to the canister’s Wasm module (calling install_code, install_chunked_code, uninstall_code methods of the management canister or if the canister runs out of cycles). In particular, the function canister_global_timer won’t be scheduled again unless the canister sets the global timer again (using the System API function ic0.global_timer_set). The global timer scheduling algorithm is implementation-defined.

canister_global_timer is triggered by the IC, and therefore has no arguments and cannot reply or reject. Still, the function canister_global_timer can initiate new calls.

A canister can export a function with the name canister_on_low_wasm_memory, which is scheduled whenever the canister’s remaining wasm memory size in bytes falls from at least a threshold t to strictly less than t. The threshold t can be defined in the field wasm_memory_threshold in the canister’s settings and by default it is set to 0.

Callbacks are addressed by their table index (as a proxy for a Wasm funcref).

In the reply callback of a inter-canister method call, the argument refers to the response to that call. In reject callbacks, no argument is available.

Replicated and Non-Replicated execution mode

Section titled “Replicated and Non-Replicated execution mode”

Canister methods can be executed either in replicated mode where the method runs on all subnet nodes and the results go through consensus or in non-replicated mode where the method runs on a single node and the result does not go through consensus. The trade-off between replicated and non-replicated mode is therefore one between the result’s latency and trustworthiness.

The following table captures the modes that different canister methods can be executed in.

Canister methodReplicated ModeNon-Replicated Mode
canister_updateYesNo
canister_queryYesYes
canister_composite_queryNoYes
canister_inspect_messageNoYes
canister_initYesNo
canister_pre_upgradeYesNo
canister_post_upgradeYesNo
canister_heartbeatYesNo
canister_global_timerYesNo
canister_on_low_wasm_memoryYesNo

The following sections describe various System API functions, also referred to as system calls, which we summarize here.

All the following functions belong to the ic0 module (denoted by the prefix ic0.).

In the following, the value of I ∈ {i32, i64} specifies whether the imported functions have 32-bit or 64-bit pointers. Given a canister module, the value of I ∈ {i32, i64} is derived from the bit-width of the declared memory defaulting to I = i32 if the canister declares no memory.

ic0.msg_arg_data_size : () -> I; // I U RQ NRQ TQ CQ Ry CRy F
ic0.msg_arg_data_copy : (dst : I, offset : I, size : I) -> (); // I U RQ NRQ TQ CQ Ry CRy F
ic0.msg_caller_size : () -> I; // *
ic0.msg_caller_copy : (dst : I, offset : I, size : I) -> (); // *
ic0.msg_caller_info_data_size : () -> I; // U RQ NRQ CQ Ry Rt CRy CRt C CC F
ic0.msg_caller_info_data_copy : (dst : I, offset : I, size : I) -> (); // U RQ NRQ CQ Ry Rt CRy CRt C CC F
ic0.msg_caller_info_signer_size : () -> I; // U RQ NRQ CQ Ry Rt CRy CRt C CC F
ic0.msg_caller_info_signer_copy : (dst : I, offset : I, size : I) -> (); // U RQ NRQ CQ Ry Rt CRy CRt C CC F
ic0.msg_reject_code : () -> i32; // Ry Rt CRy CRt C
ic0.msg_reject_msg_size : () -> I ; // Rt CRt
ic0.msg_reject_msg_copy : (dst : I, offset : I, size : I) -> (); // Rt CRt
ic0.msg_deadline : () -> i64; // U Q CQ Ry Rt CRy CRt
ic0.msg_reply_data_append : (src : I, size : I) -> (); // U RQ NRQ TQ CQ Ry Rt CRy CRt
ic0.msg_reply : () -> (); // U RQ NRQ TQ CQ Ry Rt CRy CRt
ic0.msg_reject : (src : I, size : I) -> (); // U RQ NRQ TQ CQ Ry Rt CRy CRt
ic0.msg_cycles_available128 : (dst : I) -> (); // U RQ Rt Ry
ic0.msg_cycles_refunded128 : (dst : I) -> (); // Rt Ry
ic0.msg_cycles_accept128 : (max_amount_high : i64, max_amount_low: i64, dst : I)
-> (); // U RQ Rt Ry
ic0.cycles_burn128 : (amount_high : i64, amount_low : i64, dst : I)
-> (); // I G U RQ Ry Rt C T
ic0.canister_self_size : () -> I; // *
ic0.canister_self_copy : (dst : I, offset : I, size : I) -> (); // *
ic0.canister_cycle_balance128 : (dst : I) -> (); // *
ic0.canister_liquid_cycle_balance128 : (dst : I) -> (); // *
ic0.canister_status : () -> i32; // *
ic0.canister_version : () -> i64; // *
ic0.subnet_self_size : () -> I; // *
ic0.subnet_self_copy : (dst : I, offset : I, size : I) -> (); // *
ic0.msg_method_name_size : () -> I; // F
ic0.msg_method_name_copy : (dst : I, offset : I, size : I) -> (); // F
ic0.accept_message : () -> (); // F
ic0.call_new :
( callee_src : I,
callee_size : I,
name_src : I,
name_size : I,
reply_fun : I,
reply_env : I,
reject_fun : I,
reject_env : I
) -> (); // U CQ Ry Rt CRy CRt T
ic0.call_on_cleanup : (fun : I, env : I) -> (); // U CQ Ry Rt CRy CRt T
ic0.call_data_append : (src : I, size : I) -> (); // U CQ Ry Rt CRy CRt T
ic0.call_with_best_effort_response : (timeout_seconds : i32) -> (); // U CQ Ry Rt CRy CRt T
ic0.call_cycles_add128 : (amount_high : i64, amount_low: i64) -> (); // U Ry Rt T
ic0.call_perform : () -> ( err_code : i32 ); // U CQ Ry Rt CRy CRt T
ic0.stable64_size : () -> (page_count : i64); // * s
ic0.stable64_grow : (new_pages : i64) -> (old_page_count : i64); // * s
ic0.stable64_write : (offset : i64, src : i64, size : i64) -> (); // * s
ic0.stable64_read : (dst : i64, offset : i64, size : i64) -> (); // * s
ic0.root_key_size : () -> I; // I G U RQ Ry Rt C T
ic0.root_key_copy : (dst : I, offset : I, size : I) -> (); // I G U RQ Ry Rt C T
ic0.certified_data_set : (src : I, size : I) -> (); // I G U Ry Rt T
ic0.data_certificate_present : () -> i32; // *
ic0.data_certificate_size : () -> I; // NRQ CQ
ic0.data_certificate_copy : (dst : I, offset : I, size : I) -> (); // NRQ CQ
ic0.time : () -> (timestamp : i64); // *
ic0.global_timer_set : (timestamp : i64) -> i64; // I G U Ry Rt C T
ic0.performance_counter : (counter_type : i32) -> (counter : i64); // * s
ic0.is_controller : (src : I, size : I) -> ( result : i32); // * s
ic0.in_replicated_execution : () -> (result : i32); // * s
ic0.cost_call : (method_name_size: i64, payload_size : i64, dst : I) -> (); // * s
ic0.cost_create_canister : (dst : I) -> (); // * s
ic0.cost_http_request : (request_size : i64, max_res_bytes : i64, dst : I) -> (); // * s
ic0.cost_sign_with_ecdsa : (src : I, size : I, ecdsa_curve: i32, dst : I) -> i32; // * s
ic0.cost_sign_with_schnorr : (src : I, size : I, algorithm: i32, dst : I) -> i32; // * s
ic0.cost_vetkd_derive_key : (src : I, size : I, vetkd_curve: i32, dst : I) -> i32; // * s
ic0.env_var_count : () -> I; // *
ic0.env_var_name_size : (index: I) -> I; // *
ic0.env_var_name_copy : (index: I, dst: I, offset: I, size: I) -> (); // *
ic0.env_var_name_exists : (name_src: I, name_size: I) -> i32; // *
ic0.env_var_value_size : (name_src: I, name_size: I) -> I; // *
ic0.env_var_value_copy : (name_src: I, name_size: I, dst: I, offset: I, size: I) -> (); // *
ic0.debug_print : (src : I, size : I) -> (); // * s
ic0.trap : (src : I, size : I) -> (); // * s

The following System API functions are only available if I = i32, i.e., if the bit-width of the declared memory is 32 or if the canister declares no memory.

ic0.msg_cycles_available : () -> i64; // U RQ Rt Ry
ic0.msg_cycles_refunded : () -> i64; // Rt Ry
ic0.msg_cycles_accept : (max_amount : i64) -> (amount : i64); // U RQ Rt Ry
ic0.canister_cycle_balance : () -> i64; // *
ic0.call_cycles_add : (amount : i64) -> (); // U Ry Rt T
ic0.stable_size : () -> (page_count : i32); // * s
ic0.stable_grow : (new_pages : i32) -> (old_page_count : i32); // * s
ic0.stable_write : (offset : i32, src : i32, size : i32) -> (); // * s
ic0.stable_read : (dst : i32, offset : i32, size : i32) -> (); // * s

The comment after each function lists from where these functions may be invoked:

  • I: from canister_init or canister_post_upgrade

  • G: from canister_pre_upgrade

  • U: from canister_update …

  • RQ: from canister_query … in replicated mode

  • NRQ: from canister_query … in non-replicated mode

  • TQ: from canister_query … in canister http outcall transform

  • CQ: from canister_composite_query …

  • Ry: from a reply callback

  • Rt: from a reject callback

  • CRy: from a reply callback in composite query

  • CRt: from a reject callback in composite query

  • C: from a cleanup callback

  • CC: from a cleanup callback in composite query

  • s: the (start) module initialization function

  • F: from canister_inspect_message

  • T: from system task (canister_heartbeat or canister_global_timer or canister_on_low_wasm_memory)

  • * = I G U RQ NRQ TQ CQ Ry Rt CRy CRt C CC F T (NB: Not (start))

If the canister invokes a system call from somewhere else, it will trap.

Since Wasm doesn’t have a 128-bit number type, calls requiring 128-bit arguments (e.g., the 128-bit versions of cycle operations) encode such arguments as a pair of 64-bit numbers containing the high and low bits.

WebAssembly functions parameter and result types can only be primitive number types. To model functions that accept or return blobs or text values, the following idiom is used:

To provide access to a string or blob foo, the System API provides two functions:

ic0.foo_size : () -> I; I ∈ {i32, i64}
ic0.foo_copy : (dst : I, offset : I, size : I) -> (); I ∈ {i32, i64}

The *_size function indicates the size, in bytes, of foo. The *_copy function copies size bytes from foo[offset..offset+size] to memory[dst..dst+size]. This traps if offset+size is greater than the size of foo, or if dst+size exceeds the size of the Wasm memory.

Dually, a System API function that conceptually takes a blob or string as a parameter foo has two parameters:

ic0.set_foo : (src : I, size : I) -> …; I ∈ {i32, i64}

which copies, at the time of function invocation, the data referred to by src/size out of the canister. Unless otherwise noted, this traps if src+size exceeds the size of the WebAssembly memory.

The canister can access an argument. For canister_init, canister_post_upgrade and method entry points, the argument is the argument of the call; in a reply callback, it refers to the received reply. So the lifetime of the argument data is a single WebAssembly function execution, not the whole method call tree.

  • ic0.msg_arg_data_size : () → I and ic0.msg_arg_data_copy : (dst : I, offset : I, size : I) → (); I ∈ {i32, i64}

    The message argument data.

  • ic0.msg_caller_size : () → I and ic0.msg_caller_copy : (dst : I, offset : I, size : I) → (); I ∈ {i32, i64}

    The identity of the caller, which may be a canister id or a user id. During canister installation or upgrade, this is the id of the user or canister requesting the installation or upgrade. During a system task (heartbeat or global timer), this is the id of the management canister.

  • ic0.msg_caller_info_data_size : () → I, ic0.msg_caller_info_signer_size : () → I and ic0.msg_caller_info_data_copy : (dst : I, offset : I, size : I) → (); and ic0.msg_caller_info_signer_copy : (dst : I, offset : I, size : I) → (); I ∈ {i32, i64}

    Auxiliary information about the caller as provided by the canister with which the caller’s identity is associated (i.e., the public key of the canister signature is equal to the public key of the caller’s identity). These functions can only return non-empty values if the caller is a self-authenticating principal authenticated by canister signatures. In particular, they always return empty values when the caller is another canister.

    The caller_info_data may include information such as identity attributes of the caller. The _signer_ functions return the canister ID of the canister providing the signature, and the _data_ functions return the data provided by the canister. This auxiliary information can only be set if the caller principal is derived from the public key corresponding to a canister signature, and it is guaranteed to be properly signed by the issuing canister.

    These functions trap in canister_init, canister_post_upgrade, canister_pre_upgrade, canister http outcall transform, the (start) module initialization function, and system tasks (canister_heartbeat or canister_global_timer or canister_on_low_wasm_memory).

  • ic0.msg_reject_code : () → i32

    Returns the reject code, if the current function is invoked as a reject callback or as a cleanup callback of a reject callback.

    It returns the special “no error” code 0 if the callback is a reply callback or a cleanup callback of a reply callback; this allows canisters to use a single entry point for both the reply and reject callback, if they choose to do so.

  • ic0.msg_reject_msg_size : () → I and ic0.msg_reject_msg_copy : (dst : I, offset : I, size : I) → (); I ∈ {i32, i64}

    The reject message. Traps if there is no reject message (i.e. if reject_code is 0).

  • ic0.msg_deadline : () -> i64

    The deadline, in nanoseconds since 1970-01-01, after which the caller might stop waiting for a response.

    For update methods and their callbacks, if the method was called via a bounded-wait inter-canister call the deadline is computed based on the time the call was made, and the timeout_seconds parameter provided by the caller. For other calls (including ingress messages and all calls to query and composite query methods, including calls in replicated mode) a deadline of 0 is returned.

Eventually, the canister will want to respond to the original call, either by replying (indicating success) or rejecting (signalling an error):

  • ic0.msg_reply_data_append : (src : I, size : I) → (); I ∈ {i32, i64}

    Appends data it to the (initially empty) data reply. Traps if the total appended data exceeds the maximum response size.

    This traps if the current call already has been or does not need to be responded to.

    Any data assembled, but not replied using ic0.msg_reply, gets discarded at the end of the current message execution. In particular, the reply buffer gets reset when the canister yields control without calling ic0.msg_reply.

  • ic0.msg_reply : () → ()

    Replies to the sender with the data assembled using ic0.msg_reply_data_append.

    This function can be called at most once (a second call will trap), and must be called exactly once to indicate success.

    See Cycles for how this interacts with cycles available on this call.

  • ic0.msg_reject : (src : I, size : I) → (); I ∈ {i32, i64}

    Rejects the call. The data referred to by src/size is used for the diagnostic message.

    This system call traps if src+size exceeds the size of the WebAssembly memory, or if the current call already has been or does not need to be responded to, or if the data referred to by src/size is not valid UTF8.

    The other end will receive this reject with reject code CANISTER_REJECT, see Reject codes.

    Possible reply data assembled using ic0.msg_reply_data_append is discarded.

    See Cycles for how this interacts with cycles available on this call.

A canister can inspect ingress messages before executing them. When the IC receives an update call from a user, the IC will use the canister method canister_inspect_message to determine whether the message shall be accepted. If the canister is empty (i.e. does not have a Wasm module), then the ingress message will be rejected. If the canister is not empty and does not implement canister_inspect_message, then the ingress message will be accepted.

In canister_inspect_message, the canister can determine the name of the method called by the message using ic0.msg_method_name_size : () → I and ic0.msg_method_name_copy : (dst : I, offset : I, size : I) → (); I ∈ {i32, i64}.

In canister_inspect_message, the canister can accept the message by invoking ic0.accept_message : () → (). This function traps if invoked twice. If the canister traps in canister_inspect_message or does not call ic0.accept_message, then the access is denied.

A canister can learn about its own identity:

  • ic0.canister_self_size : () → I and ic0.canister_self_copy: (dst : I, offset : I, size : I) → (); I ∈ {i32, i64}

    These functions allow the canister to query its own canister id (as a blob).

A canister can learn about the subnet it is running on:

  • ic0.subnet_self_size : () → I and ic0.subnet_self_copy: (dst : I, offset : I, size : I) → (); I ∈ {i32, i64}

    These functions allow the canister to query the subnet id (as a blob) of the subnet on which the canister is running.

This function allows a canister to find out if it is running, stopping or stopped (see IC method and IC method for context).

  • ic0.canister_status : () → i32

    returns the current status of the canister:

    Status 1 indicates running, 2 indicates stopping, and 3 indicates stopped.

    Observing the canister status is particularly useful during canister_post_upgrade. Confirming that the status is 3 (stopped) helps prevent accidentally upgrading a canister that has not fully stopped.

For each canister, the system maintains a canister version. Upon canister creation, it is set to 0, and it is guaranteed to be incremented upon every change of the canister ID, canister’s code, settings, running status (Running, Stopping, Stopped), cycles balance, and memory (WASM and stable memory), i.e., upon every successful canister renaming, management canister call of methods update_settings, load_canister_snapshot, install_code, install_chunked_code, uninstall_code, start_canister, and stop_canister on that canister, code uninstallation due to that canister running out of cycles, canister’s running status transitioning from Stopping to Stopped, and successful execution of update methods, replicated query methods, response callbacks, heartbeats, and global timers. The system can also arbitrarily increment the canister version at any time.

  • ic0.canister_version : () → i64

    returns the current canister version.

During the canister upgrade process, canister_pre_upgrade sees the old counter value, and canister_post_upgrade sees the new counter value.

When handling an update call (or a callback), a canister can do further calls to another canister. Calls are assembled in a builder-like fashion, starting with ic0.call_new, adding more attributes using the ic0.call_* functions, and eventually performing the call with ic0.call_perform.

  • ic0.call_new : ( callee_src : I, callee_size : I, name_src : I, name_size : I, reply_fun : I, reply_env : I, reject_fun : I, reject_env : I, ) → (); I ∈ {i32, i64}

Begins assembling a call to the canister specified by callee_src/_size at method name_src/_size.

The IC records two mandatory callback functions, represented by a table entry index *_fun and some additional value *_env. When the response comes back, the table is read at the corresponding index, expected to be a function of type (env : I) -> (), and passed the corresponding *_env value.

The reply callback is executed upon successful completion of the method call, which can query the reply using ic0.msg_arg_data_*.

The reject callback is executed if the method call fails asynchronously or the other canister explicitly rejects the call. The reject code and message can be queried using ic0.msg_reject_code and ic0.msg_reject_msg_*.

Subsequent calls to the following functions set further attributes of that call, until the call is concluded (with ic0.call_perform) or discarded (by returning without calling ic0.call_perform or by starting a new call with ic0.call_new.)

  • ic0.call_on_cleanup : (fun : I, env : I) → (); I ∈ {i32, i64}

If a cleanup callback (of type (env : I) -> ()) is specified for this call, it is executed if and only if the reply or the reject callback was executed and trapped (for any reason).

During the execution of the cleanup function, only a subset of the System API is available. The cleanup function is expected to run swiftly (within a fixed, yet to be specified cycle limit) and serves to free resources associated with the callback.

If this traps (e.g. runs out of cycles), the state changes from the cleanup function are discarded, as usual, and no further actions are taken related to that call. Canisters likely want to avoid this from happening.

There must be at most one call to ic0.call_on_cleanup between ic0.call_new and ic0.call_perform.

  • ic0.call_with_best_effort_response : (timeout_seconds : i32) -> ()

    Turns the call into a bounded-wait call, by relaxing the response delivery guarantee to be best effort, and asking the system to respond at the latest after timeout_seconds have elapsed. Best effort means the system may also respond with a SYS_UNKNOWN reject code, signifying that the call may or may not have been processed by the callee. Then, even if the callee produces a response, it will not be delivered to the caller.

    Any value for timeout_seconds is permitted, but is silently bounded from above by the MAX_CALL_TIMEOUT system constant; i.e., larger timeouts are treated as equivalent to MAX_CALL_TIMEOUT and do not cause an error. The implementation may add a specific error code to a reject message to indicate the cause, in particular whether the timeout expired. Note that the reject callback may be executed (possibly significantly) later than the specified time (e.g., if the caller is under high load), or before timeout expiration (e.g., if the system is under load).

    A caller that receives a SYS_UNKNOWN reject code, yet needs to learn the call outcome, must find an out-of-band way of doing so. For example, if the callee provides idempotent function calls, the caller can simply retry the call. Sample causes of SYS_UNKNOWN include the call not being delivered in time, call processing not completing in time, reply delivery taking too long, and the system shedding load.

    This method can be called only in between ic0.call_new and ic0.call_perform, and at most once at that. Otherwise, it traps. A different timeout can be specified for each call.

  • ic0.call_data_append : (src : I, size : I) -> (); I ∈ {i32, i64}

    Appends the specified bytes to the argument of the call. Initially, the argument is empty. Traps if the total appended data exceeds the maximum inter-canister call payload.

    This may be called multiple times between ic0.call_new and ic0.call_perform.

  • ic0.call_cycles_add : (amount : i64) -> ()

    This system call moves cycles from the canister balance onto the call under construction, to be transferred with that call.

    The cycles are deducted from the balance as shown by ic0.canister_cycle_balance128 immediately, and moved back if the call cannot be performed (e.g. if ic0.call_perform signals an error, if the canister invokes ic0.call_new, or returns without calling ic0.call_perform).

    This system call may be called multiple times between ic0.call_new and ic0.call_perform.

    This system call traps if there is no call under construction, i.e., if not called between ic0.call_new and ic0.call_perform.

    This system call traps if trying to transfer more cycles than returned by ic0.canister_liquid_cycle_balance128.

  • ic0.call_cycles_add128 : (amount_high : i64, amount_low : i64) -> ()

    This system call moves cycles from the canister balance onto the call under construction, to be transferred with that call.

    The amount of cycles it moves is represented by a 128-bit value which can be obtained by combining the amount_high and amount_low parameters.

    The cycles are deducted from the balance as shown by ic0.canister_cycle_balance128 immediately, and moved back if the call cannot be performed (e.g. if ic0.call_perform signals an error, if the canister invokes ic0.call_new, or returns without calling ic0.call_perform).

    This system call may be called multiple times between ic0.call_new and ic0.call_perform.

    This system call traps if there is no call under construction, i.e., if not called between ic0.call_new and ic0.call_perform.

    This system call traps if trying to transfer more cycles than returned by ic0.canister_liquid_cycle_balance128.

  • ic0.call_perform : () -> ( err_code : i32 )

    This concludes assembling the call. It queues the call message to the given destination, but does not actually act on it until the current WebAssembly function returns without trapping.

    This deducts MAX_CYCLES_PER_RESPONSE cycles from the canister balance and sets them aside for response processing.

    The returned err_code is always one of 0 and 2. An err_code of 0 means that no error occurred and that the IC could enqueue the call. In this case, the call will either be delivered, returned because the destination canister does not exist, returned due to a lack of resources within the IC, or returned because of an out of cycles condition. This also means that exactly one of the reply or reject callbacks will be executed.

    A non-zero value of err_code (2) indicates that the call could not be performed and the semantics of that value are the same as for the corresponding SYS_FATAL reject code. The non-zero value of err_code (2) could arise due to a lack of resources within the IC, but also if the call would reduce the current cycle balance to a level below where the canister would be frozen. No callbacks are executed in this case.

    After ic0.call_perform and before the next call to ic0.call_new, all other ic0.call_* function calls trap.

Each canister maintains a balance of cycles, which are used to pay for platform usage. Cycles are represented by 128-bit values.

  • ic0.canister_cycle_balance : () → i64

    Indicates the current cycle balance of the canister. It is the canister balance before the execution of the current message, minus a reserve to pay for the execution of the current message and calls finalized via ic0.call_perform, minus any cycles queued up to be sent via ic0.call_cycles_add and ic0.call_cycles_add128. After execution of the message, the IC may add unused cycles from the reserve back to the balance.

  • ic0.canister_cycle_balance128 : (dst : I) → (); I ∈ {i32, i64}

    Indicates the current cycle balance of the canister by copying the value at the location dst in the canister memory. It is the canister balance before the execution of the current message, minus a reserve to pay for the execution of the current message and calls finalized via ic0.call_perform, minus any cycles queued up to be sent via ic0.call_cycles_add and ic0.call_cycles_add128. After execution of the message, the IC may add unused cycles from the reserve back to the balance.

  • ic0.canister_liquid_cycle_balance128 : (dst : I) → (); I ∈ {i32, i64}

    Indicates the current amount of cycles that is available for spending in calls and execution by copying the value at the location dst in the canister memory. This amount of cycles can be safely attached to a call via ic0.call_cycles_add128 as long as the memory usage of the canister does not increase for the rest of the current message execution. Hence, it is recommended to never attach the entire ic0.canister_liquid_cycle_balance128 to a call, but leave some slack based on the expected canister memory usage and freezing threshold.

  • ic0.msg_cycles_available : () → i64

    Returns the amount of cycles that were transferred by the caller of the current call, and is still available in this message.

    Initially, in the update method entry point, this is the amount that the caller passed to the canister. When cycles are accepted (ic0.msg_cycles_accept), this reports fewer cycles accordingly. When the call is responded to (reply or reject), all available cycles are refunded to the caller, and this will return 0.

  • ic0.msg_cycles_available128 : (dst : I) → (); I ∈ {i32, i64}

    Indicates the number of cycles transferred by the caller of the current call, still available in this message. The amount of cycles is represented by a 128-bit value. This call copies this value starting at the location dst in the canister memory.

    Initially, in the update method entry point, this is the amount that the caller passed to the canister. When cycles are accepted (ic0.msg_cycles_accept128), this reports fewer cycles accordingly. When the call is responded to (reply or reject), all available cycles are refunded to the caller, and this will report 0 cycles.

  • ic0.msg_cycles_accept : (max_amount : i64) → (amount : i64)

    This moves cycles from the call to the canister balance. It moves as many cycles as possible, up to these constraints:

    It moves no more cycles than max_amount.

    It moves no more cycles than available according to ic0.msg_cycles_available, and

    It can be called multiple times, each time possibly adding more cycles to the balance.

    The return value indicates how many cycles were actually moved.

    This system call does not trap.

  • ic0.msg_cycles_accept128 : (max_amount_high : i64, max_amount_low : i64, dst : I) → (); I ∈ {i32, i64}

    This moves cycles from the call to the canister balance. It moves as many cycles as possible, up to these constraints:

    It moves no more cycles than the amount obtained by combining max_amount_high and max_amount_low. Cycles are represented by 128-bit values.

    It moves no more cycles than available according to ic0.msg_cycles_available128, and

    It can be called multiple times, each time possibly adding more cycles to the balance.

    This call also copies the amount of cycles that were actually moved starting at the location dst in the canister memory.

    This does not trap.

  • ic0.cycles_burn128 : (amount_high : i64, amount_low : i64, dst : I) -> (); I ∈ {i32, i64}

    This burns cycles from the canister. It burns as many cycles as possible, up to these constraints:

    It burns no more cycles than the amount obtained by combining amount_high and amount_low. Cycles are represented by 128-bit values.

    It burns no more cycles than the amount of cycles available for spending liquid_balance(balance, reserved_balance, freezing_limit), where reserved_balance are cycles reserved for resource payments and freezing_limit is the amount of idle cycles burned by the canister during its freezing_threshold.

    It can be called multiple times, each time possibly burning more cycles from the balance.

    This call also copies the amount of cycles that were actually burned starting at the location dst in the canister memory.

    This system call does not trap.

  • ic0.msg_cycles_refunded : () → i64

    This function can only be used in a callback handler (reply or reject), and indicates the amount of cycles that came back with the response as a refund. The refund has already been added to the canister balance automatically.

  • ic0.msg_cycles_refunded128 : (dst : I) → (); I ∈ {i32, i64}

    This function can only be used in a callback handler (reply or reject), and indicates the amount of cycles that came back with the response as a refund. The refund has already been added to the canister balance automatically.

Canisters have the ability to store and retrieve data from a secondary memory. The purpose of this stable memory is to provide space to store data beyond upgrades. The interface mirrors roughly the memory-related instructions of WebAssembly, and tries to be forward compatible with exposing this feature as an additional memory.

The stable memory is initially empty and can be grown up to the Wasm stable memory limit (provided the subnet has capacity).

  • ic0.stable_size : () → (page_count : i32)

    returns the current size of the stable memory in WebAssembly pages. (One WebAssembly page is 64KiB)

    This system call traps if the size of the stable memory exceeds 232 bytes.

  • ic0.stable_grow : (new_pages : i32) → (old_page_count : i32)

    tries to grow the memory by new_pages many pages containing zeroes.

    This system call traps if the previous size of the memory exceeds 232 bytes.

    If the new size of the memory exceeds 232 bytes or growing is unsuccessful, then it returns -1.

    Otherwise, it grows the memory and returns the previous size of the memory in pages.

  • ic0.stable_write : (offset : i32, src : i32, size : i32) → ()

    copies the data referred to by src/size out of the canister and replaces the corresponding segment starting at offset in the stable memory.

    This system call traps if the size of the stable memory exceeds 232 bytes.

    It also traps if src+size exceeds the size of the WebAssembly memory or offset+size exceeds the size of the stable memory.

  • ic0.stable_read : (dst : i32, offset : i32, size : i32) → ()

    copies the data referred to by offset/size out of the stable memory and replaces the corresponding bytes starting at dest in the canister memory.

    This system call traps if the size of the stable memory exceeds 232 bytes.

    It also traps if dst+size exceeds the size of the WebAssembly memory or offset+size exceeds the size of the stable memory

  • ic0.stable64_size : () → (page_count : i64)

    returns the current size of the stable memory in WebAssembly pages. (One WebAssembly page is 64KiB)

  • ic0.stable64_grow : (new_pages : i64) → (old_page_count : i64)

    tries to grow the memory by new_pages many pages containing zeroes.

    If successful, returns the previous size of the memory (in pages). Otherwise, returns -1.

  • ic0.stable64_write : (offset : i64, src : i64, size : i64) → ()

    Copies the data from location [src, src+size) of the canister memory to location [offset, offset+size) in the stable memory.

    This system call traps if src+size exceeds the size of the WebAssembly memory or offset+size exceeds the size of the stable memory.

  • ic0.stable64_read : (dst : i64, offset : i64, size : i64) → ()

    Copies the data from location [offset, offset+size) of the stable memory to the location [dst, dst+size) in the canister memory.

    This system call traps if dst+size exceeds the size of the WebAssembly memory or offset+size exceeds the size of the stable memory.

The canister can query the IC for the current time.

ic0.time : () -> i64

The time is given as nanoseconds since 1970-01-01. The IC guarantees that

  • the time, as observed by the canister, is monotonically increasing, even across canister upgrades.

  • within an invocation of one entry point, the time is constant.

The times observed by different canisters are unrelated, and calls from one canister to another may appear to travel “backwards in time”.

The canister can set a global timer to make the system schedule a call to the exported canister_global_timer Wasm method after the specified time. The time must be provided as nanoseconds since 1970-01-01.

ic0.global_timer_set : (timestamp : i64) -> i64

The function returns the previous value of the timer. If no timer is set before invoking the function, then the function returns zero.

Passing zero as an argument to the function deactivates the timer and thus prevents the system from scheduling calls to the canister’s canister_global_timer Wasm method.

The canister can query one of the “performance counters”, which is a deterministic monotonically increasing integer approximating the amount of work the canister has done. Developers might use this data to profile and optimize the canister performance.

ic0.performance_counter : (counter_type : i32) -> i64

The argument type decides which performance counter to return:

  • 0 : current execution instruction counter. The number of WebAssembly instructions the canister has executed since the beginning of the current Message execution.

  • 1 : call context instruction counter.

    • For replicated message execution, it is the number of WebAssembly instructions the canister has executed within the call context of the current Message execution since Call context creation. The counter monotonically increases across all message executions in the call context until the corresponding call context is removed.

    • For non-replicated message execution, it is the number of WebAssembly instructions the canister has executed within the corresponding composite_query_helper in Query call. The counter monotonically increases across the executions of the composite query method and the composite query callbacks until the corresponding composite_query_helper returns (ignoring WebAssembly instructions executed within any further downstream calls of composite_query_helper).

In the future, the IC might expose more performance counters.

The canister can check whether it is currently running in replicated or non replicated execution.

ic0.in_replicated_execution : () -> (result: i32)

Returns 1 if the canister is being run in replicated mode and 0 otherwise.

The canister can check whether a given principal is one of its controllers.

ic0.is_controller : (src : I, size : I) -> (result: i32); I ∈ {i32, i64}

Checks whether the principal identified by src/size is one of the controllers of the canister. If yes, then a value of 1 is returned, otherwise a 0 is returned. It can be called multiple times.

This system call traps if src+size exceeds the size of the WebAssembly memory or the principal identified by src/size is not a valid binary encoding of a principal.

For each canister, the IC keeps track of “certified data”, a canister-defined blob. For fresh canisters (upon install or reinstall), this blob is the empty blob ("").

  • ic0.root_key_size: () → I and ic0.root_key_copy : (dst : I, offset : I, size : I) → (); I ∈ {i32, i64}

    Copies the public key (a DER-encoded BLS key) of the IC root key of this instance of the Internet Computer Protocol to the canister.

    This traps in non-replicated mode (NRQ, TQ, CQ, CRy, CRt, CC, and F modes, as defined in Overview of imports).

  • ic0.certified_data_set : (src : I, size : I) -> (); I ∈ {i32, i64}

    The canister can update the certified data with this call. The passed data must be no larger than 32 bytes. This can be used any number of times.

When executing a query or composite query method via a query call (i.e. in non-replicated mode), the canister can fetch a certificate that authenticates to third parties the value last set via ic0.certified_data_set. The certificate is not available in composite query method callbacks and in query and composite query methods evaluated on canisters other than the target canister of the query call.

  • ic0.data_certificate_present : () -> i32

    returns 1 if a certificate is present, and 0 otherwise.

    This will return 1 when called from a query or composite query method on the target canister of a query call.

    This will return 0 for update methods, if a query method is executed in replicated mode (e.g. when invoked via an update call or inter-canister call) or as canister http outcall transform, and in composite query method callbacks and in query and composite query methods evaluated on canisters other than the target canister of a query call.

  • ic0.data_certificate_size : () → I and ic0.data_certificate_copy : (dst : I, offset : I, size : I) → (); I ∈ {i32, i64}

    Copies the certificate for the current value of the certified data to the canister.

    The certificate is a blob as described in Certification that contains the values at path /canister/<canister_id>/certified_data and at path /time of The system state tree.

    If this certificate includes a subnet delegation, then the id of the current canister will be included in the delegation’s canister id range.

    This traps if ic0.data_certificate_present() returns 0.

Inter-canister calls have an implicit cost, and some calls to the management canister require the caller to attach cycles to the call explicitly.
The various cost factors may change over time, so the following system calls give the canister programmatic, up-to-date information about the costs.

These system calls return costs in Cycles, represented by 128 bits, which will be written to the heap memory starting at offset dst. Note that the cost calculation is only correct for correct inputs, e.g., a method name length argument should not exceed 20’000, because such an argument would be rejected by ic0.call_new. The cost API will still return a number in this case, but it would not have a real meaning.

  • ic0.cost_call : (method_name_size: i64, payload_size : i64, dst : I) -> (); I ∈ {i32, i64}

    This system call returns the amount of cycles that a canister needs to be above the freezing threshold in order to successfully make an inter-canister call. This includes the base cost for an inter-canister call, the cost for each byte transmitted in the request, the cost for the transmission of the largest possible response, and the cost for executing the largest possible response callback. The last two are cost reservations, which must be possible for a call to succeed, but they will be partially refunded if the real response and callback are smaller. So the cost of the actual inter-canister call may be less than this system call predicts, but it cannot be more. method_name_size is the byte length of the method name, and payload_size is the byte length of the argument to the method.

  • ic0.cost_create_canister : (dst : I) -> (); I ∈ {i32, i64}

    The cost of creating a canister on the same subnet as the calling canister via create_canister. Note that canister creation via a call to the CMC can have a different cost if the target subnet has a different replication factor.

  • ic0.cost_http_request(request_size : i64, max_res_bytes : i64, dst : I) -> (); I ∈ {i32, i64}

    The cost of a canister http outcall via http_request. request_size is the sum of the byte lengths of the following components of an http request:

    • url
    • headers - i.e., the sum of the lengths of all keys and values
    • body
    • transform - i.e., the sum of the transform method name length and the length of the transform context

    max_res_bytes is the maximum response length the caller wishes to accept (the caller should provide the default value of 2,000,000 if no maximum response length is provided in the actual request to the management canister).

  • ic0.cost_sign_with_ecdsa(src : I, size : I, ecdsa_curve: i32, dst : I) -> i32; I ∈ {i32, i64}

  • ic0.cost_sign_with_schnorr(src : I, size : I, algorithm: i32, dst : I) -> i32; I ∈ {i32, i64}

  • ic0.cost_vetkd_derive_key(src : I, size : I, vetkd_curve: i32, dst : I) -> i32; I ∈ {i32, i64}

    These system calls accept a key name via a textual representation for the specific signing scheme / key of a given size stored in the heap memory starting at offset src. They also accept an i32 with the following interpretations:

    • ecdsa_curve: 0 → secp256k1
    • algorithm: 0 → bip340secp256k1, 1 → ed25519
    • vetkd_curve: 0 → bls12_381

    See sign_with_ecdsa, sign_with_schnorr and vetkd_derive_key for more information.

    These system calls trap if src + size or dst + 16 exceed the size of the WebAssembly memory. Otherwise, they return an i32 with the following meaning:

    • 0: Success. The result can be found at the memory address dst.
    • 1: Invalid curve or algorithm. Memory at dst is left unchanged.
    • 2: Invalid key name for the given combination of signing scheme and (valid) curve/algorithm. Memory at dst is left unchanged.

The following system calls provide access to the canister’s environment variables:

  • ic0.env_var_count : () -> I; I ∈ {i32, i64}

    Returns the number of environment variables set for this canister.

  • ic0.env_var_name_size : (index: I) -> I; I ∈ {i32, i64}

    Gets the size in bytes of the name of the environment variable at the given index.

    This system call traps if:

    • If the index is out of bounds (>= than value provided by ic0.env_var_count)
  • ic0.env_var_name_copy : (index: I, dst: I, offset: I, size: I) -> (); I ∈ {i32, i64}

    Copies the name of the environment variable at the given index into memory.

    This system call traps if:

    • The index is out of bounds (>= than value provided by ic0.env_var_count)
    • offset+size is greater than the size of the environment variable name
    • dst+size exceeds the size of the WebAssembly memory
  • ic0.env_var_name_exists : (name_src: I, name_size: I) -> i32; I ∈ {i32, i64}

    Checks if an environment variable with the given name exists. If yes, then a value of 1 is returned, otherwise a 0 is returned.

    This system call traps if:

    • name_size exceeds the maximum length of a variable name
    • name_src+name_size exceeds the size of the WebAssembly memory
    • If the data referred to by name_src/name_size is not valid UTF8.
  • ic0.env_var_value_size : (name_src: I, name_size: I) -> I; I ∈ {i32, i64}

    Gets the size in bytes of the value for the environment variable with the given name.

    This system call traps if:

    • name_size exceeds the maximum length of a variable name
    • name_src+name_size exceeds the size of the WebAssembly memory
    • If the data referred to by name_src/name_size is not valid UTF8.
    • The name does not match any existing environment variable.
  • ic0.env_var_value_copy : (name_src: I, name_size: I, dst: I, offset: I, size: I) -> (); I ∈ {i32, i64}

    Copies the value of the environment variable with the given name into memory.

    This system call traps if:

    • name_size exceeds the maximum length of a variable name
    • name_src+name_size exceeds the size of the WebAssembly memory
    • If the data referred to by name_src/name_size is not valid UTF8.
    • The name does not match any existing environment variable.
    • offset+size is greater than the size of the environment variable value
    • dst+size exceeds the size of the WebAssembly memory

These system calls allow canisters to:

  • Enumerate all environment variables
  • Look up values by name

Canister can produce logs available through the management canister endpoint fetch_canister_logs.

  • ic0.debug_print : (src : I, size : I) -> (); I ∈ {i32, i64}

    This copies out the data specified by src and size and appends that data to canister logs. The data can be trimmed to an implementation defined maximum size.

    This function never traps, even if the src+size exceeds the size of the memory (in which case system-generated data are used instead).

Similarly, the System API allows the canister to effectively trap and give some indication about why it trapped:

  • ic0.trap : (src : I, size : I) -> (); I ∈ {i32, i64}

    This copies out the data specified by src and size and appends that data to canister logs. The data can be trimmed to an implementation defined maximum size.

    Moreover, the data specified by src and size might be included in a reject message (trimmed to an implementation defined maximum size and omitting bytes that are not valid UTF-8).

    This function always traps.

The Internet Computer aims to make the most of the WebAssembly platform, and embraces WebAssembly features. With WebAssembly host references, we can make the platform more secure, the interfaces more abstract and more compositional. The above ic0 System API does not yet use WebAssembly host references. Once they become available on our platform, a new version of the System API using host references will be available via the ic module. The changes will be, at least

  1. The introduction of a api_nonce reference, which models the capability to use the System API. It is passed as an argument to canister_init, canister_update <name> etc., and expected as an argument by almost all System API function calls. (The debugging aids remain unconstrained.)

  2. The use of references, instead of binary blobs, to address principals (user ids, canister ids), e.g. in ic0.msg_caller or in ic0.call_new. Additional functions will be provided to convert between the transparent binary representation of principals and references.

  3. Making the builder interface to create calls build calls identified by a reference, rather than having an implicit partial call in the background.

A canister may only use the old or the new interface; the IC detects which interface the canister intends to use based on the names and types of its function imports and exports.