Making simple inter-canister calls

One of the most important features of the Internet Computer for developers is the ability to build, deploy, and call shared functions in one canister from a program in 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 applications.

For example, you might want to create an application for professional networking, organizing community events, or hosting fundraising activities. Each of these applications 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.

The Motoko-based LinkedUp sample application provides a simple implementation of an open professional network that demonstrates how to use inter-canister calls within a project.

The LinkedUp sample application is implemented using the following canisters:

  • The linkedup canister creates and stores basic profile information for a user, including work experience and educational background.

  • The connectd canister creates and stores a user’s connections.

  • The linkedup_assets canister stores the front-end assets—including the JavaScript, HTML, and CSS files—that define the user interface

Before you begin

Before building the sample application, verify the following:

  • You have downloaded and installed the DFINITY Canister SDK package as described in Download and install.

  • You have stopped any Internet Computer network processes running on the local computer.

Download the demo

To experiment with inter-canister calls using the LinkedUp sample application:

  1. Open a terminal shell and change to the folder you are using for your Internet Computer sample projects.

  2. Clone the linkedup repository.

    git clone [email protected]:dfinity-lab/linkedup.git
  3. Change to the local working directory for the linkedup repository.

    cd linkedup
  4. Install node modules by running the following command:

    npm install

    If necessary, fix any vulnerabilities found by running the following command:

    npm audit fix
  5. Open the dfx.json file in a text editor and verify the dfx setting has same the version number as the dfx executable you have installed.

Start the local network

Before you can build the linkedup project, you need to connect to the Internet Computer network either running locally in your development environment or running remotely on a sub-network that you can access.

To start the network locally:

  1. Open a new terminal window or tab on your local computer and navigate to your project directory.

    For example, you can do either of the following if running Terminal on macOS:

    • Click Shell, then select New Tab to open a new terminal in your current working directory.

    • Click Shell and select New Window, then run cd ~/ic-projects/linkedup in the new terminal if your linkedup project is in the ic-projects working folder.

    You should now have two terminals open with your project directory as your current working directory.

  2. Start the Internet Computer network on your local computer by running the following command:

    dfx start

    After you start the local network, the terminal displays messages about network operations.

  3. Leave the terminal that displays network operations open and switch your focus to the terminal where you cloned the linkedup repository.

Register canister identifiers

After you connect to the Internet Computer network running locally in your development environment, you can register with the network to generate unique canister identifiers for your project.

To register canister identifiers for the local network:

  1. Check that you are still in your project directory, if needed.

  2. Register unique canister identifiers for the project by running the following command:

    dfx canister create --all

    The command displays the network-specific canister identifiers for the canisters defined in the dfx.json configuration file.

    "connectd" canister created with canister id: "75hes-oqbaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q"
    "linkedup" canister created with canister id: "cxeji-wacaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q"
    "linkedup_assets" canister created with canister id: "7kncf-oidaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q"

    Keep in mind that because you are running the Internet Computer locally, these identifiers are only valid on the local network. To deploy canisters on a remote network, you must connect to that network using the --network command-line option and a specific network name or address to register identifiers on that network.

Build and deploy the demo project

To build and deploy the LinkUp sample application, take the following steps:

  1. Check that you are still in your project directory by running the pwd command, if necessary.

  2. Build the LinkedUp canisters by running the following command:

    dfx build
  3. Deploy the project on the local network by running the following command:

    dfx canister install --all

    You should see canister identifiers for the connectd, linkedup and linkedup_assets canisters with a message similar to the following:

    Installing code for canister connectd, with canister_id 75hes-oqbaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q
    Installing code for canister linkedup, with canister_id cxeji-wacaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q
    Installing code for canister linkedup_assets, with canister_id 7kncf-oidaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q
  4. Copy the linkedup_assets canister identifier returned by the dfx canister install command.

    In this sample application, only the linkedup_assets canister includes the front-end assets used to access the application’s features. To open the application in a browser, therefore, you need to specify the linkedup_assets canister identifier.

  5. Open the linkedup_assets canister in your web browser.

    For example, if binding to the default localhost address and port number, the URL looks similar to this:

    http://127.0.0.1:8000/?canisterId=7kncf-oidaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q

Create a profile and connections

To run through a demonstration of the LinkedUp sample application, take the following steps:

  1. Open a browser tab or window.

  2. Type the web server host name, port, and the canisterId keyword, then paste the linkedup_assets canister identifier as the URL to display.

    127.0.0.1:8000/?canisterId=<ic-identifier-for-linkedup-assets>

    The browser displays an introductory page.

    A public-private key pair will be automatically generated to establish your identity for accessing the canister, so there’s no need to provide a user name and password or register an account to store your identity before using the service.

  3. Click Login.

    The browser displays an empty profile page.

    linkedup empty maya

  4. Click Edit, type profile information, copy and paste the image address for an avatar photo, then click Submit.

    linkedup edit maya

    After you click Submit, you have a profile with some work history that can be viewed.

    For example:

    linkedup profile maya

Add another profile

At this point, there are no other profiles to search for or to add as connections. To try out the Search and Connect features, you can:

  • Run a script that populates the sample application with some additional profiles.

  • Create a profile manually by opening a private window.

For this tutorial, you will create another profile manually.

To add a user profile with different identity:

  1. At the top right of the browser window, click the appropriate icon to display the browser’s menu options.

    For example, if you are using Google Chrome, click the vertical ellipse to display the More menu.

  2. Click New Incognito Window if you are using Google Chrome or New Private Window if you are using Firefox to enable you to navigate to the canister without the user identity generated in your initial browser connection to the canister.

  3. Copy and paste the URL from your first browser session into the private browsing window, then click Login.

    linkedup incognito

    Notice that there’s no profile in the private browsing window but your original profile is still visible in your initial browser tab.

  4. Click Edit, type profile information, copy and paste the image address for an avatar photo, then click Submit.

    linkedup edit dylan

    After clicking Submit, you have a second profile with some work history that can be viewed.

    For example:

    linkedup profile dylan

  5. Type the first name or last name from the first profile you created—for example, if you created a profile for Maya Garcia, type Maya—then click Search.

    linkedup search from dylan for maya

    The profile matching your search criteria is displayed.

    linkedup search result

  6. Select the contact from the search results, wait for the Connect button to be displayed, then click Connect.

    linkedup connect from dylan to maya

    When the connection request completes, the second profile displays the connection to the first profile. For example:

    linkedup connected to maya

  7. Return to the browser tab with your original profile.

    If you want to create a connection between the original profile and the profile you created in the private browsing window, you can do so by repeating the search, select, and connect steps.

Save identity information for the incognito profile

Currently, user identities get generated when a user connects to a canister using a device such as a browser running on a laptop. You used an incognito browser window to create a second LinkedUp profile. If you close that private browsing window, the incognito user identity will no longer be available.

This section demonstrates how user identities are currently associated with a canister and stored by the browser. You might find the information useful for testing programs that involve more than one user. Keep in mind, however, that this specific approach is only a temporary solution for handling authentication and user identities.

To illustrate saving the incognito user identity, take the following steps:

  1. At the top right of the browser window, click the vertical ellipse to display the More menu.

  2. Click More Tools, then select Developer Tools to display the developer tools explorer.

  3. Click Application, expand Local storage, then select the host name and port running the LinkedUp application.

    local storage

  4. Copy the Value of the dfinity-ic-user-identity key.

    The dfinity-ic-user-identity key value stores the public and private keys for the user.

  5. Return to the standard browser window.

    For example, the window that displays the Maya profile.

  6. Click the vertical ellipse to display the More menu, click More Tools, then select Developer Tools to display the developer tools explorer.

  7. Click Application, expand Local storage, then select the host name and port running the LinkedUp application.

  8. Rename the dfinity-ic-user-identity key to preserve the existing public-private key.

    For example, rename the dfinity-ic-user-identity to maya-dfinity-ic-user-identity.

  9. Add a dfinity-ic-user-identity key and paste the value from the incognito user to save the incognito user’s identity in the browser.

  10. Refresh or close and reopen the browser window, then click Login to log in using the profile you created in the incognito window.

  11. Open another incognito window, type Maya in the Search field, then click Search to see that Maya’s profile and connections are persisted.

    You can create another new profile or close the incognito window.

Explore the configuration file

Now that you have explored the basic features of the sample application, you have some context for exploring how the configuration settings and source files are used.

To explore the configuration file:

  1. Change to the linkedup directory, then open the project’s dfx.json file.

  2. Note that there are two main canisters defined—connectd and linkedup—each with a main.mo source file.

  3. Note that the linkedup_assets canister specifies a frontend entry point of main.js and assets in the form of CSS and HTML files.

  4. Note that the application uses the default server IP address and port number.

Explore the connectd source code

The source code for the social connections canister, connectd, is organized into the following files:

  • The digraph.mo file provides the functions to create a directed graph of vertices and edges to describe a user’s connections.

  • The main.mo contains the actor and key functions for defining the connections associated with a user profile that can be called by the LinkedUp sample application.

  • The types.mo file defines the custom type that maps a vertex to a user identity for use in the digraph and main program files.

Explore the linkedup source code

The source code for the professional profile with work history and educational background is organized into the following files:

  • The main.mo file contains the actor and key functions for the LinkedUp sample application.

  • The types.mo file defines the custom types that describe the user identity and profile fields that are imported and used in the main program file for the linkedup canister.

  • The utils.mo file provides helper functions.

Query and update operations

In working with the LinkedUp sample application, you might notice that some operations—such as viewing a profile or performing a search—returned results almost immediately. Other operations—such as creating a profile or adding a connection—took a little longer.

These differences in performance illustrate the difference between using query and update calls in the linkedup canister.

For example, in the src/linkedup/main.mo file, the create and update functions are update calls that change the state of the canister, but the program uses query calls for the get and search functions to view or search for a profile:

  // Profiles

  public shared(msg) func create(profile: NewProfile): async () {
    directory.createOne(msg.caller, profile);
  };

  public shared(msg) func update(profile: Profile): async () {
    if(Utils.hasAccess(msg.caller, profile)) {
      directory.updateOne(profile.id, profile);
    };
  };

  public query func get(userId: UserId): async Profile {
    Utils.getProfile(directory, userId)
  };

  public query func search(term: Text): async [Profile] {
    directory.findBy(term)
  };

Interaction between the canisters

In this sample application, the linkedup canister leverages functions defined in the connectd canister. This separation simplifies the code in each canister and illustrates how you can extend a project by calling common functions defined in one canister from one or more other canisters.

To make the public functions defined in one canister available in the another canister:

  1. Add an import statement in the calling canister.

    In this example, the public functions are defined in the connectd canister and are called by the linkedup canister.

    Therefore, the src/linkedup/main.mo includes the following code:

    // Make the Connectd app's public methods available locally
    import Connectd "canister:connectd";
  2. Use the canister.function syntax to call public methods in the imported canister.

    In this example, the linkedup canister calls the connect and getConnections function in the imported connectd canister.

You can see the code that enables interaction between the linkedup canister and the connectd canister in the main.mo source files.

For example, the src/connectd/main.mo defines the following functions:

+

actor Connectd {
  flexible var graph: Digraph.Digraph = Digraph.Digraph();

  public func healthcheck(): async Bool { true };

  public func connect(userA: Vertex, userB: Vertex): async () {
    graph.addEdge(userA, userB);
  };

  public func getConnections(user: Vertex): async [Vertex] {
    graph.getAdjacent(user)
  };

};

Because of the Import statement, the connectd functions are available to the linkedup canister and the src/linkedup/main.mo includes the following code:

  // Connections

  public shared(msg) func connect(userId: UserId): async () {
    // Call Connectd's public methods without an API
    await Connectd.connect(msg.caller, userId);
  };

  public func getConnections(userId: UserId): async [Profile] {
    let userIds = await Connectd.getConnections(userId);
    directory.findMany(userIds)
  };

  public shared(msg) func isConnected(userId: UserId): async Bool {
    let userIds = await Connectd.getConnections(msg.caller);
    Utils.includes(userId, userIds)
  };

  // User Auth

  public shared query(msg) func getOwnId(): async UserId { msg.caller }

};

Stop the local network

After you finish experimenting with the linkedup program, you can stop the local Internet Computer network so that it doesn’t continue running in the background.

To stop the local network:

  1. In the terminal that displays network operations, press Control-C to interrupt the local network process.

  2. Stop the Internet Computer network by running the following command:

    dfx stop