Candid RPC

This section documents the Candid RPC methodology for developing Azle applications. This methodology embraces ICP's Candid language, exposing canister methods directly to Candid-speaking clients, and using Candid for serialization and deserialization purposes.

Candid RPC is heading towards 1.0 and production-readiness in 2024.

Get Started

Azle helps you to build secure decentralized/replicated servers in TypeScript or JavaScript on ICP. The current replication factor is 13-40 times.

Please remember that Azle is in beta and thus it may have unknown security vulnerabilities due to the following:

  • Azle is built with various software packages that have not yet reached maturity
  • Azle does not yet have multiple independent security reviews/audits
  • Azle does not yet have many live, successful, continuously operating applications deployed to ICP

Installation

Windows is only supported through a Linux virtual environment of some kind, such as WSL

You will need Node.js 20 and dfx to develop ICP applications with Azle:

Node.js 20

It's recommended to use nvm to install Node.js 20:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

Restart your terminal and then run:

nvm install 20

Check that the installation went smoothly by looking for clean output from the following command:

node --version

dfx

Install the dfx command line tools for managing ICP applications:

DFX_VERSION=0.22.0 sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"

Check that the installation went smoothly by looking for clean output from the following command:

dfx --version

Deployment

To create and deploy a simple sample application called hello_world:

# create a new default project called hello_world
npx azle new hello_world
cd hello_world
# install all npm dependencies including azle
npm install
# start up a local ICP replica
dfx start --clean

In a separate terminal in the hello_world directory:

# deploy your canister
dfx deploy

Examples

Some of the best documentation for creating Candid RPC canisters is currently in the tests directory.

Canister Class

Your canister's functionality must be encapsulated in a class exported using the default export:

import { IDL, query } from 'azle';

export default class {
    @query([], IDL.Text)
    hello(): string {
        return 'world!';
    }
}

You must use the @query, @update, @init, @postUpgrade, @preUpgrade, @inspectMessage, and @heartbeat decorators to expose your canister's methods. Adding TypeScript types is optional.

@dfinity/candid IDL

For each of your canister's methods, deserialization of incoming arguments and serialization of return values is handled with a combination of the @query, @update, @init, and @postUpgrade decorators and the IDL object from the @dfinity/candid library.

IDL is re-exported by Azle, and has properties that correspond to Candid's supported types. You must use IDL to instruct the method decorators on how to deserialize arguments and serialize the return value. Here's an example of accessing the Candid types from IDL:

import { IDL } from 'azle';

IDL.Text;

IDL.Vec(IDL.Nat8); // Candid blob

IDL.Nat;
IDL.Nat64;
IDL.Nat32;
IDL.Nat16;
IDL.Nat8;

IDL.Int;
IDL.Int64;
IDL.Int32;
IDL.Int16;
IDL.Int8;

IDL.Float64;
IDL.Float32;

IDL.Bool;

IDL.Null;

IDL.Vec(IDL.Int);

IDL.Opt(IDL.Text);

IDL.Record({
    prop1: IDL.Text,
    prop2: IDL.Bool
});

IDL.Variant({
    Tag1: IDL.Null,
    Tag2: IDL.Nat
});

IDL.Func([], [], ['query']);

IDL.Service({
    myQueryMethod: IDL.Func([IDL.Text, IDL.Text], [IDL.Bool])
});

IDL.Principal;

IDL.Reserved;

IDL.Empty;

Decorators

@query

Exposes the decorated method as a read-only canister_query method.

The first parameter to this decorator accepts IDL Candid type objects that will deserialize incoming Candid arguments. The second parameter to this decorator accepts an IDL Candid type object that will serialize the outgoing return value to Candid.

@update

Exposes the decorated method as a read-write canister_update method.

The first parameter to this decorator accepts IDL Candid type objects that will deserialize incoming Candid arguments. The second parameter to this decorator accepts an IDL Candid type object that will serialize the outgoing return value to Candid.

@init

Exposes the decorated method as the canister_init method called only once during canister initialization.

The first parameter to this decorator accepts IDL Candid type objects that will deserialize incoming Candid arguments.

@postUpgrade

Exposes the decorated method as the canister_post_upgrade method called during every canister upgrade.

The first parameter to this decorator accepts IDL Candid type objects that will deserialize incoming Candid arguments.

@preUpgrade

Exposes the decorated method as the canister_pre_upgrade method called before every canister upgrade.

@inspectMessage

Exposes the decorated method as the canister_inspect_message method called before every update call.

@heartbeat

Exposes the decorated method as the canister_heartbeat method called on a regular interval (every second or so).

IC API

The IC API is exposed as functions exported from azle. You can see the available functions in the source code.

Some of the best documentation for using the IC API is currently in the tests directory, especially the ic_api test example.

Here's an example of getting the caller's principal using the caller function:

import { caller, IDL, update } from 'azle';

export default class {
    @update([], IDL.Bool)
    isUserAnonymous(): boolean {
        if (caller().toText() === '2vxsx-fae') {
            return true;
        } else {
            return false;
        }
    }
}