Skip to main content

Inter-canister calls

One of the most important features of ICP for developers is the ability to call functions in one canister from another canister. This capability to make calls between canisters, also sometimes referred to as inter-canister calls, enables you to reuse and share functionality in multiple dapps.

For example, you might want to create a dapp for professional networking, organizing community events, or hosting fundraising activities. Each of these dapps might have a social component that enables users to identify social relationships based on some criteria or shared interest, such as friends and family or current and former colleagues.

To address this social component, you might want to create a single canister for storing user relationships then write your professional networking, community organizing, or fundraising application to import and call functions that are defined in the canister for social connections. You could then build additional applications to use the social connections canister or extend the features provided by the social connections canister to make it useful to an even broader community of other developers.

Basic usage

A simple way to set up inter-canister calls is through your project's dfx.json file.

For example, let's say that you want to build a canister named foo which calls the canister bar. Here is the dfx.json file:

{
"canisters": {
"foo": {
"dependencies": ["bar"],
"type": "motoko",
"main": "src/Foo.mo"
},
"bar": {
"type": "motoko",
"main": "src/Bar.mo"
}
}
}

Notice that foo includes bar as a canister dependency.

Below is an example implementation of foo (src/Foo.mo) which calls the bar canister:

import Bar "canister:bar";

persistent actor Foo {

public func main() : async Nat {
let value = await Bar.getValue(); // Call a method on the `bar` canister
value;
};

};

Below is an implementation for bar (src/Bar.mo):

import Debug "mo:base/Debug";

persistent actor Bar {

public func getValue() : async Nat {
Debug.print("Hello from the `bar` canister!");
123;
};

};

To run this example, you can use the dfx canister call subcommand (after deploying the canisters with dfx deploy):

dfx canister call foo main

The output should resemble the following:

2025-02-21 15:53:39.567801 UTC: [Canister ajuq4-ruaaa-aaaaa-qaaga-cai] Hello from the `bar` canister!
(123 : nat)

You can also use a canister ID to access a previously deployed canister as shown in this alternate implementation of foo:

persistent actor Foo {
public func main(canisterId: Text) : async Nat {
let Bar = actor(canisterId): actor {
getValue: () -> async Nat; // Define the expected canister interface
};
let value = await Bar.getValue(); // Call the canister
value;
};

};

Then, use the following call, replacing canister-id with the ID of a previously deployed canister:

dfx canister call foo main "canister-id"

Advanced usage

If the method name or input types are unknown at compile time, it's possible to call arbitrary canister methods using the ExperimentalInternetComputer module.

Here is an example which you can modify for your specific use case:

import IC "mo:base/ExperimentalInternetComputer";
import Debug "mo:base/Debug";

persistent actor AdvancedCanister1 {

public func main(canisterId : Principal) : async Nat {
// Define the method name and input args
let name = "getValue";
let args = (123);

// Call the method
let encodedArgs = to_candid (args);
let encodedValue = await IC.call(canisterId, name, encodedArgs);

// Decode the return value
let ?value : ?Nat = from_candid encodedValue else Debug.trap("Unexpected return value");
value;
};

}
import Debug "mo:base/Debug";

persistent actor AdvancedCanister2 {

public func getValue(number: Nat) : async Nat {
Debug.print("Hello from advanced canister 2!");
return number * 2;
};

};

In some situations, it may be useful to reference a canister by ID. This is possible with the following import syntax:

import Canister "ic:7hfb6-caaaa-aaaar-qadga-cai";

If you do this, double check that the referenced canister is available and has the same canister ID in all intended runtime environments (usually the local replica and ICP mainnet).

In addition, a corresponding .did file with name 7hfb6-caaaa-aaaar-qadga-cai.did, containing the Candid interface of the imported canister, must be available on the moc compiler's actor-idl path.

Logo