Dynamically Create Canister Smart Contracts in Motoko

How to generate on-the-fly decentralized smart contracts on the Internet Computer.

Architecture

Bucket

import Nat "mo:base/Nat";

actor class Bucket(user: Text) = this {

var version: Nat = 1;

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

}

Manager

actor Main {
private stable var canisterId: ?Principal = null;

public query func getCanisterId() : async ?Principal {
canisterId
};
};
  • ​the bucket — i.e., the actor of previous chapter
  • the cycles library
import Cycles "mo:base/ExperimentalCycles";
import Principal "mo:base/Principal";
import Error "mo:base/Error";

import Bucket "./bucket";

actor Main {
private stable var canisterId: ?Principal = null;

public shared({ caller }) func init(): async (Principal) {
Cycles.add(1_000_000_000_000);

let b = await Bucket.Bucket("User1");

canisterId := ?(Principal.fromActor(b));

switch (canisterId) {
case null {
throw Error.reject("Bucket init error");
};
case (?canisterId) {
return canisterId;
};
};
};

public query func getCanisterId() : async ?Principal {
canisterId
};
};
import Cycles "mo:base/ExperimentalCycles";
import Principal "mo:base/Principal";
import Error "mo:base/Error";

import IC "./ic.types";

import Bucket "./bucket";

actor Main {
private stable var canisterId: ?Principal = null;

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

public shared({ caller }) func init(): async (Principal) {
Cycles.add(1_000_000_000_000);
let b = await Bucket.Bucket("User1");

canisterId := ?(Principal.fromActor(b));

switch (canisterId) {
case null {
throw Error.reject("Bucket init error");
};
case (?canisterId) {
let self: Principal = Principal.fromActor(Main);

let controllers: ?[Principal] = ?[canisterId, caller, self];

await ic.update_settings(({canister_id = canisterId;
settings = {
controllers = controllers;
freezing_threshold = null;
memory_allocation = null;
compute_allocation = null;
}}));

return canisterId;
};
};
};

public query func getCanisterId() : async ?Principal {
canisterId
};
};

Web Application

<html lang="en">
<body>
<main>
<button id="init">Init</button>

<button id="say">Say</button>
</main>
</body>
</html>
import { buckets_sample } from '../../declarations/buckets_sample';

let bucket;

const initCanister = async () => {
try {
bucket = await buckets_sample.init();
console.log('New bucket:', bucket.toText());
} catch (err) {
console.error(err);
}
};

const init = () => {
const btnInit = document.querySelector('button#init');
btnInit.addEventListener('click', initCanister);
};

document.addEventListener('DOMContentLoaded', init);
"canisters": {
"buckets_sample": {
"main": "src/buckets_sample/main.mo",
"type": "motoko"
},
"bucket": { <----- add an entry for the bucket
"main": "src/buckets_sample/bucket.mo",
"type": "motoko"
},
rsync -av .dfx/local/canisters/bucket ./src/declarations --exclude=bucket.wasm
import { Actor, HttpAgent } from '@dfinity/agent';
import { idlFactory } from '../../declarations/bucket';

export const createBucketActor = async ({ canisterId }) => {
const agent = new HttpAgent();

if (process.env.NODE_ENV !== 'production') {
await agent.fetchRootKey();
}

return Actor.createActor(idlFactory, {
agent,
canisterId
});
};
import { Actor, HttpAgent } from '@dfinity/agent';
import { buckets_sample } from '../../declarations/buckets_sample';
import { idlFactory } from '../../declarations/bucket';

export const createBucketActor = async ({ canisterId }) => {
const agent = new HttpAgent();

if (process.env.NODE_ENV !== 'production') {
await agent.fetchRootKey();
}

return Actor.createActor(idlFactory, {
agent,
canisterId
});
};

let bucket;

const initCanister = async () => {
try {
bucket = await buckets_sample.init();
console.log('New bucket:', bucket.toText());
} catch (err) {
console.error(err);
}
};

const sayHello = async () => {
try {
const actor = await createBucketActor({
canisterId: bucket
});
console.log(await actor.say());
} catch (err) {
console.error(err);
}
};

const init = () => {
const btnInit = document.querySelector('button#init');
btnInit.addEventListener('click', initCanister);

const btnSay = document.querySelector('button#say');
btnSay.addEventListener('click', sayHello);
};

document.addEventListener('DOMContentLoaded', init);

Demo

Conclusion

Start building at smartcontracts.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