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).

2 Likes

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.

2 Likes

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.

2 Likes

@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' });

1 Like

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.

1 Like

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?

1 Like