How to use a canister defined in another project

I can use canisters defined in the same project, for instance I can use my my_canister canister from within the my_canister_assets by using the following import statement:

import 'ic:canisters/my_canister';

But what if I want to interact with that canister from a completely different project? The same import statement, expectedly, gives me Target of URI doesn't exist: 'ic:canisters/my_canister'.

How can I import and interact with the canister from an external project?

Hi Mike

You can create an instance of an actor using the id of your external canister and give it a type that matches only the methods in that canister which you want to call, see this example: Calling another canister's methods

Use the canister id that dfx gives you when you deploy (note the ids no longer have an “ic:” prefix).

Thanks for the reply. Good news that I can use external canisters from Motoko, but is it possible to use them in JavaScript? Is there the concept of an “actor” there? What would I import to be able to create one?

I’m trying to write an interop library for my canisters so I can use them in Dart/Flutter. If I can access the canister in JavaScript in a remote project, it’s trivial to expose it to Dart.

In JS, you need to know the JS binding of the Candid type and the canister id of the external canister,

import candid from './external_candid_binding';
const actor = Actor.createActor(candid, { canisterId: 'external_canister_id' });
await actor.any_canister_method();

The JS binding can be get from .dfx/local/canisters/your_canister/your_canister.did.js in your dfx project.

@mymikemiller If you’re in a browser you could use the global ic object instead of line 2 there:

const actor = ic.agent.makeActorFactory(candid)({ canisterId: 'external_canister_id' });

Is there something I need to do to make sure ic is available in a JavaScript file launched with Node? I’m currently getting an error in the @dfinity/agent npm library stating that global.ic is not defined.

Here’s my code:

import Agent from '@dfinity/agent';
import candid from '../../.dfx/local/canisters/credits/credits.did.js';
const actor = Agent.Actor.createActor(candid, { canisterId: '75hes-oqbaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q' });

(async () => {
    var v = await actor.getValue();
    print(v);
})();

On the actor.getValue line, I get TypeError: Cannot read property 'agent' of undefined in /node_modules/@dfinity/agent/src/actor.js:35:25 which is the global.ic.agent line here:

function getDefaultAgent() {
    const agent = typeof window === 'undefined'
        ? typeof global === 'undefined'
            ? typeof self === 'undefined'
                ? undefined
                : self.ic.agent
            : global.ic.agent
        : window.ic.agent;

This only works in browser with bootstrap server, i.e. JS code is bundled in the asset canister.

To work with node, you need to polyfill global.ic. For example, https://github.com/FloorLamp/dfinity-react-ts-tailwind-starter/blob/master/src/frontend/setupTests.js. But this may break across different releases.

Awesome, it’s working! I had to polyfill a couple more libraries that it was complaining about: fetch and crypto. I used node-fetch and node-webcrypto-ossl.

In addition to copying the createAgent function from the project you linked, here’s everything I needed:

import fetch from "node-fetch";
import { Crypto } from "node-webcrypto-ossl";
const { HttpAgent, IDL, Principal } = ic;

global.fetch = fetch;
global.crypto = new Crypto();
global.ic = { agent: createAgent(host), HttpAgent, IDL };

node-webcrypto-ossl’s readme says At this time this solution should be considered suitable for research and experimentation, further code and security review is needed before utilization in a production application. Other libraries, such as NfWebCrypto, say similar things.

Is it possible to do this securely at this time? Or am I trying to do something inherently insecure?