Upgrade Smart Contracts on the Internet Computer With JavaScript

How to install code in child canisters with NodeJS.

Photo by Milad Fakurian on Unsplash

Getting started

  1. Dynamically create smart contracts in Motoko
  2. Call Internet Computer canisters in NodeJS

​Child canister

import Nat "mo:base/Nat";

actor class Bucket(user: Text) = this {

var version: Nat = 2; // <-- Bump v2

public query func say() : async Text {
return "Hello World - " # user # " - v" # Nat.toText(version);
};

}
  1. ​Edit the configuration dfx.json​​ to list the bucket actor.
  2. Run the ​​dfx deploy command to generate the files. The command will end in error (“Error: Invalid data: Expected arguments but found none.”) that can safely be ignored 😉.
  3. Revert the change in ​dfx.json​.

​Backend

import IC "./ic.types";

actor Main {
private let ic : IC.Self = actor "aaaaa-aa";

public func installCode(canisterId: Principal, arg: Blob, wasmModule: Blob): async() {
await ic.install_code({
arg = arg;
wasm_module = wasmModule;
mode = #upgrade;
canister_id = canisterId;
});
};
};
  1. ​A target canister id.
  2. The Wasm module — the new version of the Wasm code I built in previous chapter with my workaround.
  3. A ​mode​ set to ​#upgrade to perform an update as described in Canister upgrades — with the goal to maintain the state.
  4. Arguments — those that are used to initialize the canister.

NodeJS script

import {Principal} from "@dfinity/principal";
import {IDL} from '@dfinity/candid';

const installCode = async () => {
// Param 1.
const canisterId =
Principal.fromText('renrk-eyaaa-aaaaa-aaada-cai');

// Param 2.
const wasmModule = loadWasm();

// Param 3.
const arg = IDL.encode([IDL.Text], ['User1']);

// Agent-js actor
const actor = await managerActor();

// Execute
await upgradeBucket({actor, wasmModule, canisterId, arg})
}

try {
await installCode()
} catch (err) {
console.error(err);
}
import {readFileSync} from 'fs';

const loadWasm = () => {
const localPath =
`${process.cwd()}/.dfx/local/canisters/bucket/bucket.wasm`;
const buffer = readFileSync(localPath);
return [...new Uint8Array(buffer)];
};
actor class Bucket(user: Text) = this {
// commented
}
import {IDL} from '@dfinity/candid';

const arg = IDL.encode([IDL.Text], ['User1']);
import {IDL} from '@dfinity/candid';
import {Principal} from "@dfinity/principal";

const arg = IDL.encode([IDL.Principal],
[Principal.fromText('rrrrr-ccccc-user-principal')]);
import {idlFactory} from './.dfx/local/canisters/manager/manager.did.mjs';
import fetch from 'node-fetch';
import {HttpAgent, Actor} from '@dfinity/agent';

const managerActor = async () => {
const canisterId = managerPrincipalLocal();

// Replace host with https://ic0.app for mainnet
const agent =
new HttpAgent({fetch, host: 'http://localhost:8000/'});

// Only if local IC
await agent.fetchRootKey();

return Actor.createActor(idlFactory, {
agent,
canisterId
});
};
cp ./.dfx/local/canisters/manager/manager.did.js ./.dfx/local/canisters/manager/manager.did.mjs
const managerPrincipalLocal = () => {
const buffer =
readFileSync('./.dfx/local/canister_ids.json');
const {manager} = JSON.parse(buffer.toString('utf-8'));
return Principal.fromText(manager.local);
};
const managerPrincipalIC = () => {
const buffer = readFileSync('./canister_ids.json');
const {manager} = JSON.parse(buffer.toString('utf-8'));
return Principal.fromText(manager.ic);
};
const upgradeBucket = 
async ({actor, wasmModule, canisterId, arg}) => {
console.log(`Upgrading: ${canisterId.toText()}`);

await actor.installCode(canisterId, [...arg], wasmModule);

console.log(`Done: ${canisterId.toText()}`);
};

​Test

​Conclusion and sample repo

Start building at internetcomputer.org and join the developer community at forum.dfinity.org.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store