TypeScript CDK for the Internet Computer.
Please exercise caution when using Azle. It is beta software that you use at your own risk and according to the terms of this MIT license.
Demergent Labs may officially recommend Azle for production use when at least the following have occurred:
- Many example-based unit/integration tests
- Feature parity with the Rust CDK and Motoko
- Extensive automated benchmarking
- Extensive automated property testing
- Multiple independent security reviews/audits
- Boa is no longer experimental
Feel free to open issues or join us in the DFINITY DEV TypeScript Discord channel.
Most of Azle's documentation is currently found in this README. The Azle Book, similar to Sudograph's, will later be hosted on the Internet Computer.
- Examples
- Installation
- Deployment
- Canisters
- Canister Methods
- Candid Types
- Canister APIs
- Call APIs
- Stable Memory
- Feature Parity
- Roadmap
- Gotchas and Caveats
- Decentralization
- Contributing
- License
You should have the following installed on your system:
After installing the prerequisites, you can make a project and install Azle.
Run the following commands to install Node.js and npm. nvm is highly recommended and its use is shown below:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
# restart your terminal
nvm install 18
Run the following command to install Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Run the following command to install dfx 0.11.0:
DFX_VERSION=0.11.0 sh -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)"
- Ubuntu
- error: linker cc not found (sudo apt install build-essential)
- is cmake not installed? (sudo apt install cmake)
Follow these steps to create an Azle project. The steps below assume a project called backend
:
- Create a directory for your project (
mkdir backend && cd backend
) - Create a
package.json
file (npm init -y
) - Install Azle (
npm install azle
) - Create a
dfx.json
file (touch dfx.json
) - Create a directory and an entry TypeScript file for your canister (
mkdir src && cd src && touch index.ts
)
Your dfx.json
file should look like this:
{
"canisters": {
"backend": {
"type": "custom",
"build": "npx azle backend",
"root": "src",
"ts": "src/index.ts",
"candid": "src/index.did",
"wasm": "target/wasm32-unknown-unknown/release/backend.wasm"
}
}
}
Your index.ts
file should look like this:
import { Query } from 'azle';
export function hello_world(): Query<string> {
return 'Hello world!';
}
You are now ready to deploy your application.
Start up an IC replica and deploy. The first deploy will likely take multiple minutes as it downloads and compiles many Rust dependencies. Subsequent deploys should be much quicker:
# Open a terminal and navigate to your project's root directory, then run the following command to start a local IC replica
dfx start
# Alternatively to the above command, you can run the replica in the background
dfx start --background
# If you are running the replica in the background, you can run these commands within the same terminal as the dfx start --background command
# If you are not running the replica in the background, then open another terminal and run these commands from the root directory of your project
# first deploy
dfx canister create backend
dfx build backend
dfx canister install backend --wasm target/wasm32-unknown-unknown/release/backend.wasm.gz
# subsequent deploys
dfx build backend
dfx canister install --mode upgrade backend --wasm target/wasm32-unknown-unknown/release/backend.wasm.gz
You can then interact with your canister like any other canister written in Motoko or Rust. For more information about calling your canister using dfx
, see here.
dfx commands for the query example:
dfx canister call query query
# The result is: ("This is a query function")
dfx commands for the update example:
dfx canister call update update '("Why hello there")'
# The result is: ()
dfx canister call update query
# The result is: ("Why hello there")
dfx commands for the simple_erc20 example:
dfx canister call simple_erc20 initializeSupply '("TOKEN", "Token", 1_000_000, "0")'
# The result is: (true)
dfx canister call simple_erc20 name
# The result is: ("Token")
dfx canister call simple_erc20 ticker
# The result is: ("TOKEN")
dfx canister call simple_erc20 totalSupply
# The result is: (1_000_000 : nat64)
dfx canister call simple_erc20 balance '("0")'
# The result is: (1_000_000 : nat64)
dfx canister call simple_erc20 transfer '("0", "1", 100)'
# The result is: (true)
Deploying to the live Internet Computer generally only requires adding the --network ic
option to the deploy command: dfx canister --network ic install backend --wasm target/wasm32-unknown-unknown/release/backend.wasm.gz
. This assumes you already have converted ICP into cycles appropriately. See here for more information on getting ready to deploy to production.
More information:
- https://smartcontracts.org/docs/developers-guide/concepts/canisters-code.html
- https://wiki.internetcomputer.org/wiki/Canisters_(dapps/smart_contracts)
- https://smartcontracts.org/docs/developers-guide/design-apps.html#_single_or_multiple_canister_architecture
In many ways developing canisters with Azle is similar to any other TypeScript/JavaScript project. To see what canister source code looks like, see the examples.
A canister is the fundamental application unit on the Internet Computer. It contains the code and state of your application. When deployed to the Internet Computer, your canister becomes an everlasting process. Its global variables automatically persist.
Users of your canister interact with it through RPC calls performed using HTTP requests. These calls will hit your canister's Query
and Update
methods. These methods, with their parameter and return types, are the interface to your canister.
Azle allows you to write canisters while embracing much of what the TypeScript and JavaScript ecosystems have to offer.
- init
- pre upgrade
- post upgrade
- inspect message
- heartbeat
- update
- query
- http_request and http_request_update
Examples:
import { Init } from 'azle';
export function init(): Init {
console.log('This runs once when the canister is first initialized');
}
Examples:
import { PreUpgrade } from 'azle';
export function pre_upgrade(): PreUpgrade {
console.log('This runs before every canister upgrade');
}
Examples:
import { PostUpgrade } from 'azle';
export function post_upgrade(): PostUpgrade {
console.log('This runs after every canister upgrade');
}
Examples:
import { ic, InspectMessage, Update } from 'azle';
export function inspect_message(): InspectMessage {
console.log('this runs before executing update calls');
if (ic.method_name() === 'accessible') {
ic.accept_message();
return;
}
if (ic.method_name() === 'inaccessible') {
return;
}
throw `Method "${ic.method_name()}" not allowed`;
}
export function accessible(): Update<boolean> {
return true;
}
export function inaccessible(): Update<boolean> {
return false;
}
export function also_inaccessible(): Update<boolean> {
return false;
}
Examples:
import { Heartbeat } from 'azle';
export function heartbeat(): Heartbeat {
console.log('this runs ~1 time per second');
}
Examples:
More information:
Update methods expose public callable functions that are writable. All state changes will be persisted after the function call completes.
Update calls go through consensus and thus return very slowly (a few seconds) relative to query calls. This also means they are more secure than query calls unless certified data is used in conjunction with the query call.
To create an update method, simply wrap the return type of your function in the azle Update
type.
import { Query, Update } from 'azle';
let currentMessage: string = '';
export function query(): Query<string> {
return currentMessage;
}
export function update(message: string): Update<void> {
currentMessage = message;
}
Examples:
More information:
- https://smartcontracts.org/docs/developers-guide/concepts/canisters-code.html#query-update
- https://smartcontracts.org/docs/developers-guide/design-apps.html#_using_query_calls
Query methods expose public callable functions that are read-only. All state changes will be discarded after the function call completes.
Query calls do not go through consensus and thus return very quickly relative to update calls. This also means they are less secure than update calls unless certified data is used in conjunction with the query call.
To create a query method, simply wrap the return type of your function in the Azle Query
type.
import { Query } from 'azle';
export function query(): Query<string> {
return 'This is a query function';
}
Examples:
import { blob, Func, ic, nat, nat16, Opt, Query, Update, Variant } from 'azle';
type HttpRequest = {
method: string;
url: string;
headers: HeaderField[];
body: blob;
};
type HttpResponse = {
status_code: nat16;
headers: HeaderField[];
body: blob;
streaming_strategy: Opt<StreamingStrategy>;
upgrade: Opt<boolean>;
};
type HeaderField = [string, string];
type StreamingStrategy = Variant<{
Callback: CallbackStrategy;
}>;
type CallbackStrategy = {
callback: Callback;
token: Token;
};
type Callback = Func<(t: Token) => Query<StreamingCallbackHttpResponse>>;
type Token = {
// add whatever fields you'd like
arbitrary_data: string;
};
type StreamingCallbackHttpResponse = {
body: blob;
token: Opt<Token>;
};
export function http_request(req: HttpRequest): Query<HttpResponse> {
return {
status_code: 200,
headers: [['content-type', 'text/plain']],
body: Uint8Array.from([]),
streaming_strategy: null,
upgrade: true
};
}
export function http_request_update(req: HttpRequest): Update<HttpResponse> {
return {
status_code: 200,
headers: [['content-type', 'text/plain']],
body: Uint8Array.from([]),
streaming_strategy: null,
upgrade: null
};
}
Examples:
Candid is an interface description language created by DFINITY. It defines interfaces between services (in our context canisters), allowing canisters and clients written in various languages to easily interact with each other.
Much of what Azle is doing under-the-hood is translating TypeScript code into various formats that Candid understands (for example Azle will generate a Candid .did
file from your TypeScript code). To do this your TypeScript code must use various Azle-provided types.
Please note that these types are only needed in specific locations in your code, including but not limited to the following areas:
Query
,Update
,Init
, andPostUpgrade
method parameters and return typesCanister
method declaration parameters and return typesStable
variable declaration types
Basically, you only need to write in TypeScript and use the Azle types when Candid serialization or deserialization is necessary. You could write the rest of your application in plain JavaScript if you'd like.
Data types:
- text
- blob
- nat
- nat64
- nat32
- nat16
- nat8
- int
- int64
- int32
- int16
- int8
- float64
- float32
- bool
- null
- vec
- opt
- record
- variant
- func
- service
- principal
- reserved
- empty
The TypeScript type string
corresponds to the Candid type text and will become a JavaScript String at runtime.
TypeScript:
import { Query } from 'azle';
export function get_string(): Query<string> {
return 'Hello world!';
}
export function print_string(string: string): Query<string> {
console.log(typeof string);
return string;
}
Candid:
service: {
"get_string": () -> (text) query;
"print_string": (text) -> (text) query;
}
The Azle type blob
corresponds to the Candid type blob and will become a JavaScript Uint8Array at runtime.
TypeScript:
import { blob, Query } from 'azle';
export function get_blob(): Query<blob> {
return Uint8Array.from([68, 73, 68, 76, 0, 0]);
}
export function print_blob(blob: blob): Query<blob> {
console.log(typeof blob);
return blob;
}
Candid:
service: {
"get_blob": () -> (blob) query;
"print_blob": (blob) -> (blob) query;
}
The Azle type nat
corresponds to the Candid type nat and will become a JavaScript BigInt at runtime.
TypeScript:
import { nat, Query } from 'azle';
export function get_nat(): Query<nat> {
return 340_282_366_920_938_463_463_374_607_431_768_211_455n;
}
export function print_nat(nat: nat): Query<nat> {
console.log(typeof nat);
return nat;
}
Candid:
service: {
"get_nat": () -> (nat) query;
"print_nat": (nat) -> (nat) query;
}
The Azle type nat64
corresponds to the Candid type nat64 and will become a JavaScript BigInt at runtime.
TypeScript:
import { nat64, Query } from 'azle';
export function get_nat64(): Query<nat64> {
return 18_446_744_073_709_551_615n;
}
export function print_nat64(nat64: nat64): Query<nat64> {
console.log(typeof nat64);
return nat64;
}
Candid:
service: {
"get_nat64": () -> (nat64) query;
"print_nat64": (nat64) -> (nat64) query;
}
The Azle type nat32
corresponds to the Candid type nat32 and will become a JavaScript Number at runtime.
TypeScript:
import { nat32, Query } from 'azle';
export function get_nat32(): Query<nat32> {
return 4_294_967_295;
}
export function print_nat32(nat32: nat32): Query<nat32> {
console.log(typeof nat32);
return nat32;
}
Candid:
service: {
"get_nat32": () -> (nat32) query;
"print_nat32": (nat32) -> (nat32) query;
}
The Azle type nat16
corresponds to the Candid type nat16 and will become a JavaScript Number at runtime.
TypeScript:
import { nat16, Query } from 'azle';
export function get_nat16(): Query<nat16> {
return 65_535;
}
export function print_nat16(nat16: nat16): Query<nat16> {
console.log(typeof nat16);
return nat16;
}
Candid:
service: {
"get_nat16": () -> (nat16) query;
"print_nat16": (nat16) -> (nat16) query;
}
The Azle type nat8
corresponds to the Candid type nat8 and will become a JavaScript Number at runtime.
TypeScript:
import { nat8, Query } from 'azle';
export function get_nat8(): Query<nat8> {
return 255;
}
export function print_nat8(nat8: nat8): Query<nat8> {
console.log(typeof nat8);
return nat8;
}
Candid:
service: {
"get_nat8": () -> (nat8) query;
"print_nat8": (nat8) -> (nat8) query;
}
The Azle type int
corresponds to the Candid type int and will become a JavaScript BigInt at runtime.
TypeScript:
import { int, Query } from 'azle';
export function get_int(): Query<int> {
return 170_141_183_460_469_231_731_687_303_715_884_105_727n;
}
export function print_int(int: int): Query<int> {
console.log(typeof int);
return int;
}
Candid:
service: {
"get_int": () -> (int) query;
"print_int": (int) -> (int) query;
}
The Azle type int64
corresponds to the Candid type int64 and will become a JavaScript BigInt at runtime.
TypeScript:
import { int64, Query } from 'azle';
export function get_int64(): Query<int64> {
return 9_223_372_036_854_775_807n;
}
export function print_int64(int64: int64): Query<int64> {
console.log(typeof int64);
return int64;
}
Candid:
service: {
"get_int64": () -> (int64) query;
"print_int64": (int64) -> (int64) query;
}
The Azle type int32
corresponds to the Candid type int32 and will become a JavaScript Number at runtime.
TypeScript:
import { int32, Query } from 'azle';
export function get_int32(): Query<int32> {
return 2_147_483_647;
}
export function print_int32(int32: int32): Query<int32> {
console.log(typeof int32);
return int32;
}
Candid:
service: {
"get_int32": () -> (int32) query;
"print_int32": (int32) -> (int32) query;
}
The Azle type int16
corresponds to the Candid type int16 and will become a JavaScript Number at runtime.
TypeScript:
import { int16, Query } from 'azle';
export function get_int16(): Query<int16> {
return 32_767;
}
export function print_int16(int16: int16): Query<int16> {
console.log(typeof int16);
return int16;
}
Candid:
service: {
"get_int16": () -> (int16) query;
"print_int16": (int16) -> (int16) query;
}
The Azle type int8
corresponds to the Candid type int8 and will become a JavaScript Number at runtime.
TypeScript:
import { int8, Query } from 'azle';
export function get_int8(): Query<int8> {
return 127;
}
export function print_int8(int8: int8): Query<int8> {
console.log(typeof int8);
return int8;
}
Candid:
service: {
"get_int8": () -> (int8) query;
"print_int8": (int8) -> (int8) query;
}
The Azle type float64
corresponds to the Candid type float64 and will become a JavaScript Number at runtime.
TypeScript:
import { float64, Query } from 'azle';
export function get_float64(): Query<float64> {
return Math.E;
}
export function print_float64(float64: float64): Query<float64> {
console.log(typeof float64);
return float64;
}
Candid:
service: {
"get_float64": () -> (float64) query;
"print_float64": (float64) -> (float64) query;
}
The Azle type float32
corresponds to the Candid type float32 and will become a JavaScript Number at runtime.
TypeScript:
import { float32, Query } from 'azle';
export function get_float32(): Query<float32> {
return Math.PI;
}
export function print_float32(float32: float32): Query<float32> {
console.log(typeof float32);
return float32;
}
Candid:
service: {
"get_float32": () -> (float32) query;
"print_float32": (float32) -> (float32) query;
}
The TypeScript type boolean
corresponds to the Candid type bool and will become a JavaScript Boolean at runtime.
TypeScript:
import { Query } from 'azle';
export function get_bool(): Query<boolean> {
return true;
}
export function print_bool(bool: boolean): Query<boolean> {
console.log(typeof bool);
return bool;
}
Candid:
service: {
"get_bool": () -> (bool) query;
"print_bool": (bool) -> (bool) query;
}
The TypeScript type null
corresponds to the Candid type null and will become a JavaScript null at runtime.
TypeScript:
import { Query } from 'azle';
export function get_null(): Query<null> {
return null;
}
export function print_null(_null: null): Query<null> {
console.log(typeof _null);
return _null;
}
Candid:
service: {
"get_null": () -> (null) query;
"print_null": (null) -> (null) query;
}
TypeScript []
array syntax corresponds to the Candid type vec and will become an array of the specified type at runtime (except for nat8[]
which will become a Uint8Array
, thus it is recommended to use the blob
type instead of nat8[]
). Only the []
array syntax is supported at this time (i.e. not Array
or ReadonlyArray
etc).
TypeScript:
import { int32, Query } from 'azle';
export function get_numbers(): Query<int32[]> {
return [0, 1, 2, 3];
}
Candid:
service: {
"get_numbers": () -> (vec int32) query;
}
The Azle type Opt
corresponds to the Candid type opt and will become the enclosed JavaScript type or null at runtime.
TypeScript:
import { Opt, Query } from 'azle';
export function get_opt_some(): Query<Opt<boolean>> {
return true;
}
export function get_opt_none(): Query<Opt<boolean>> {
return null;
}
Candid:
service: {
"get_opt_some": () -> (opt bool) query;
"get_opt_none": () -> (opt bool) query;
}
TypeScript type aliases referring to object literals correspond to the Candid record type and will become JavaScript Objects at runtime.
TypeScript:
type Post = {
id: string;
author: User;
text: string;
thread: Thread;
};
type Thread = {
id: string;
author: User;
posts: Post[];
title: string;
};
type User = {
id: string;
posts: Post[];
threads: Thread[];
username: string;
};
Candid:
type Post = record {
"id": text;
"author": User;
"text": text;
"thread": Thread;
};
type Thread = record {
"id": text;
"author": User;
"posts": vec Post;
"title": text;
};
type User = record {
"id": text;
"posts": vec Post;
"threads": vec Thread;
"username": text;
};
TypeScript type aliases referring to object literals wrapped in the Variant
Azle type correspond to the Candid variant type and will become JavaScript Objects at runtime.
TypeScript:
import { nat32, Variant } from 'azle';
type ReactionType = Variant<{
Fire: null;
ThumbsUp: null;
ThumbsDown: null;
Emotion: Emotion;
Firework: Firework;
}>;
type Emotion = Variant<{
Happy: null;
Sad: null;
}>;
type Firework = {
Color: string;
NumStreaks: nat32;
};
Candid:
type ReactionType = variant {
"Fire": null;
"ThumbsUp": null;
"ThumbsDown": null;
"Emotion": Emotion;
"Firework": Firework
};
type Emotion = variant {
"Happy": null;
"Sad": null
};
type Firework = record {
"Color": text;
"NumStreaks": nat32;
};
The Azle type func
corresponds to the Candid type func and at runtime will become a JavaScript array with two elements, the first being an @dfinity/principal and the second being a JavaScript string. The @dfinity/principal
represents the principal
of the canister/service where the function exists, and the string
represents the function's name.
TypeScript:
import { Func, nat64, Principal, Query, Update, Variant } from 'azle';
type User = {
id: string;
basic_func: BasicFunc;
complex_func: ComplexFunc;
};
type Reaction = Variant<{
Good: null;
Bad: null;
BasicFunc: BasicFunc;
ComplexFunc: ComplexFunc;
}>;
type BasicFunc = Func<(param1: string) => Query<string>>;
type ComplexFunc = Func<(user: User, reaction: Reaction) => Update<nat64>>;
export function get_basic_func(): Query<BasicFunc> {
return [
Principal.fromText('rrkah-fqaaa-aaaaa-aaaaq-cai'),
'simple_function_name'
];
}
export function get_complex_func(): Query<ComplexFunc> {
return [
Principal.fromText('ryjl3-tyaaa-aaaaa-aaaba-cai'),
'complex_function_name'
];
}
Candid:
type User = record {
"id": text;
"basic_func": BasicFunc;
"complex_func": ComplexFunc;
};
type Reaction = variant { "Good": null; "Bad": null; "BasicFunc": BasicFunc; "ComplexFunc": ComplexFunc };
type BasicFunc = func (text) -> (text) query;
type ComplexFunc = func (User, Reaction) -> (nat64);
service: () -> {
"get_basic_func": () -> (BasicFunc) query;
"get_complex_func": () -> (ComplexFunc) query;
}
The Azle type Principal
corresponds to the Candid type principal and will become an @dfinity/principal at runtime.
TypeScript:
import { Principal, Query } from 'azle';
export function get_principal(): Query<Principal> {
return Principal.fromText('rrkah-fqaaa-aaaaa-aaaaq-cai');
}
export function print_principal(principal: Principal): Query<Principal> {
console.log(typeof principal);
return principal;
}
Candid:
service: {
"get_principal": () -> (principal) query;
"print_principal": (principal) -> (principal) query;
}
- Note that
Principal.selfAuthenticating
will not function properly until this issue is resolved
The Azle type reserved
corresponds to the Candid type reserved and will become a JavaScript null at runtime.
TypeScript:
import { Query, reserved } from 'azle';
export function get_reserved(): Query<reserved> {
return 'anything';
}
export function print_reserved(reserved: reserved): Query<reserved> {
console.log(typeof reserved);
return reserved;
}
Candid:
service: {
"get_reserved": () -> (reserved) query;
"print_reserved": (reserved) -> (reserved) query;
}
The Azle type empty
corresponds to the Candid type empty and has no JavaScript value at runtime.
TypeScript:
import { empty, Query } from 'azle';
export function get_empty(): Query<empty> {
throw 'Anything you want';
}
// Note: It is impossible to call this function because it requires an argument
// but there is no way to pass an "empty" value as an argument.
export function print_empty(empty: empty): Query<empty> {
console.log(typeof empty);
throw 'Anything you want';
}
Candid:
service: {
"get_empty": () -> (empty) query;
"print_empty": (empty) -> (empty) query;
}
- canister balance
- canister balance 128
- data certificate
- canister id
- set certified data
- time
- trap
Examples:
import { ic, nat, Query } from 'azle';
// returns the amount of cycles available in the canister
export function canister_balance(): Query<nat64> {
return ic.canister_balance();
}
Examples:
import { ic, nat, Query } from 'azle';
// returns the amount of cycles available in the canister
export function canister_balance128(): Query<nat> {
return ic.canister_balance128();
}
Examples:
import { blob, ic, Opt, Query } from 'azle';
// When called from a query call, returns the data certificate authenticating certified_data set by this canister. Returns None if called not from a query call.
export function data_certificate(): Query<Opt<blob>> {
return ic.data_certificate();
}
Examples:
import { ic, Principal, Query } from 'azle';
// returns this canister's id
export function id(): Query<Principal> {
return ic.id();
}
Examples:
import { ic, Query } from 'azle';
// prints a message through the local replica's output
export function print(message: string): Query<boolean> {
ic.print(message);
return true;
}
Examples:
import { blob, ic, Update } from 'azle';
// sets up to 32 bytes of certified data
export function set_certified_data(data: blob): Update<void> {
ic.set_certified_data(data);
}
Examples:
import { ic, nat64, Query } from 'azle';
// returns the current timestamp
export function time(): Query<nat64> {
return ic.time();
}
Examples:
import { ic, Query } from 'azle';
// traps with a message, stopping execution and discarding all state within the call
export function trap(message: string): Query<boolean> {
ic.trap(message);
return true;
}
- caller
- accept message
- arg data
- arg data raw
- arg data raw size
- call
- call raw
- call raw 128
- call with payment
- call with payment 128
- method name
- msg cycles accept
- msg cycles accept 128
- msg cycles available
- msg cycles available 128
- msg cycles refunded
- msg cycles refunded 128
- notify
- notify raw
- notify with payment 128
- performance counter
- reject
- reject code
- reject message
- reply
- reply raw
- result
Examples:
import { ic, Principal, Query } from 'azle';
// returns the principal of the identity that called this function
export function caller(): Query<Principal> {
return ic.caller();
}
Examples:
import { ic, InspectMessage, Update } from 'azle';
export function inspect_message(): InspectMessage {
console.log('this runs before executing update calls');
if (ic.method_name() === 'accessible') {
ic.accept_message();
return;
}
if (ic.method_name() === 'inaccessible') {
return;
}
throw `Method "${ic.method_name()}" not allowed`;
}
export function accessible(): Update<boolean> {
return true;
}
export function inaccessible(): Update<boolean> {
return false;
}
export function also_inaccessible(): Update<boolean> {
return false;
}
Examples:
// returns the argument data as bytes.
export function arg_data_raw(
arg1: blob,
arg2: int8,
arg3: boolean,
arg4: string
): Query<blob> {
return ic.arg_data_raw();
}
Examples:
// returns the length of the argument data in bytes
export function arg_data_raw_size(
arg1: blob,
arg2: int8,
arg3: boolean,
arg4: string
): Query<nat32> {
return ic.arg_data_raw_size();
}
Examples:
import { Canister, CanisterResult, ic, ok, Update, Variant } from 'azle';
type Canister1 = Canister<{
method(): CanisterResult<boolean>;
}>;
const canister1 = ic.canisters.Canister1<Canister1>(
Principal.fromText('rkp4c-7iaaa-aaaaa-aaaca-cai')
);
type CallCanister1MethodResult = Variant<{
ok: boolean;
err: string;
}>;
export function* call_canister1_method(): Update<CallCanister1MethodResult> {
const canister_result: CanisterResult<boolean> = yield canister1.method();
if (!ok(canister_result)) {
return {
err: canister_result.err
};
}
return {
ok: canister_result.ok
};
}
Examples:
import { blob, ic, ok, Principal, Update } from 'azle';
export function* get_randomness(): Update<blob> {
const canister_result: CanisterResult<blob> = yield ic.call_raw(
Principal.fromText('aaaaa-aa'),
'raw_rand',
Uint8Array.from([68, 73, 68, 76, 0, 0]),
0n // this is a nat64
);
if (!ok(canister_result)) {
return Uint8Array.from([]);
}
return canister_result.ok;
}
Examples:
import { blob, ic, ok, Principal, Update } from 'azle';
export function* get_randomness(): Update<blob> {
const canister_result: CanisterResult<blob> = yield ic.call_raw128(
Principal.fromText('aaaaa-aa'),
'raw_rand',
Uint8Array.from([68, 73, 68, 76, 0, 0]),
0n // this is a nat
);
if (!ok(canister_result)) {
return Uint8Array.from([]);
}
return canister_result.ok;
}
Examples:
import { Canister, CanisterResult, ic, ok, Update, Variant } from 'azle';
type Canister1 = Canister<{
method(): CanisterResult<boolean>;
}>;
const canister1 = ic.canisters.Canister1<Canister1>(
Principal.fromText('rkp4c-7iaaa-aaaaa-aaaca-cai')
);
type CallCanister1MethodResult = Variant<{
ok: boolean;
err: string;
}>;
export function* call_canister1_method(): Update<CallCanister1MethodResult> {
const canister_result: CanisterResult<boolean> = yield canister1
.method()
.with_cycles(100_000_000_000n);
if (!ok(canister_result)) {
return {
err: canister_result.err
};
}
return {
ok: canister_result.ok
};
}
Examples:
import { Canister, CanisterResult, ic, ok, Update, Variant } from 'azle';
type Canister1 = Canister<{
method(): CanisterResult<boolean>;
}>;
const canister1 = ic.canisters.Canister1<Canister1>(
Principal.fromText('rkp4c-7iaaa-aaaaa-aaaca-cai')
);
type CallCanister1MethodResult = Variant<{
ok: boolean;
err: string;
}>;
export function* call_canister1_method(): Update<CallCanister1MethodResult> {
const canister_result: CanisterResult<boolean> = yield canister1
.method()
.with_cycles128(100_000_000_000n);
if (!ok(canister_result)) {
return {
err: canister_result.err
};
}
return {
ok: canister_result.ok
};
}
Examples:
import { ic, InspectMessage, Update } from 'azle';
export function inspect_message(): InspectMessage {
console.log('this runs before executing update calls');
if (ic.method_name() === 'accessible') {
ic.accept_message();
return;
}
if (ic.method_name() === 'inaccessible') {
return;
}
throw `Method "${ic.method_name()}" not allowed`;
}
export function accessible(): Update<boolean> {
return true;
}
export function inaccessible(): Update<boolean> {
return false;
}
export function also_inaccessible(): Update<boolean> {
return false;
}
Examples:
import { ic, nat64, Update } from 'azle';
// Moves all transferred cycles to the canister
export function receive_cycles(): Update<nat64> {
return ic.msg_cycles_accept(ic.msg_cycles_available());
}
Examples:
import { ic, nat, Update } from 'azle';
// Moves all transferred cycles to the canister
export function receive_cycles128(): Update<nat> {
return ic.msg_cycles_accept128(ic.msg_cycles_available128());
}
Examples:
import { ic, nat64, Update } from 'azle';
// Moves all transferred cycles to the canister
export function receive_cycles(): Update<nat64> {
return ic.msg_cycles_accept(ic.msg_cycles_available());
}
Examples:
import { ic, nat64, Update } from 'azle';
// Moves all transferred cycles to the canister
export function receive_cycles128(): Update<nat64> {
return ic.msg_cycles_accept128(ic.msg_cycles_available128());
}
Examples:
import { Canister, CanisterResult, ic, nat64, ok, Update, Variant } from 'azle';
type Canister1 = Canister<{
method(): CanisterResult<boolean>;
}>;
const canister1 = ic.canisters.Canister1<Canister1>(
Principal.fromText('rkp4c-7iaaa-aaaaa-aaaca-cai')
);
type CallCanister1MethodResult = Variant<{
ok: nat64;
err: string;
}>;
export function* call_canister1_method(): Update<CallCanister1MethodResult> {
const canister_result: CanisterResult<boolean> = yield canister1
.method()
.with_cycles(100_000_000_000n);
if (!ok(canister_result)) {
return {
err: canister_result.err
};
}
return {
ok: ic.msg_cycles_refunded()
};
}
Examples:
import { Canister, CanisterResult, ic, nat, ok, Update, Variant } from 'azle';
type Canister1 = Canister<{
method(): CanisterResult<boolean>;
}>;
const canister1 = ic.canisters.Canister1<Canister1>(
Principal.fromText('rkp4c-7iaaa-aaaaa-aaaca-cai')
);
type CallCanister1MethodResult = Variant<{
ok: nat;
err: string;
}>;
export function* call_canister1_method(): Update<CallCanister1MethodResult> {
const canister_result: CanisterResult<boolean> = yield canister1
.method()
.with_cycles128(100_000_000_000n);
if (!ok(canister_result)) {
return {
err: canister_result.err
};
}
return {
ok: ic.msg_cycles_refunded128()
};
}
Examples:
import { Canister, CanisterResult, ic, ok, Update } from 'azle';
type Canister1 = Canister<{
method(): CanisterResult<boolean>;
}>;
const canister1 = ic.canisters.Canister1<Canister1>(
Principal.fromText('rkp4c-7iaaa-aaaaa-aaaca-cai')
);
export function call_canister1_method(): Update<boolean> {
const canister_result: CanisterResult<null> = canister1.method().notify();
if (!ok(canister_result)) {
return false;
}
return true;
}
Examples:
import { ic, ok, Principal, Update } from 'azle';
export function send_notification(): Update<boolean> {
const result = ic.notify_raw(
Principal.fromText('ryjl3-tyaaa-aaaaa-aaaba-cai'),
'receive_notification',
Uint8Array.from([68, 73, 68, 76, 0, 0]),
0n
);
if (!ok(result)) {
return false;
}
return true;
}
Examples:
import { Canister, CanisterResult, ic, ok, Update } from 'azle';
type Canister1 = Canister<{
method(): CanisterResult<boolean>;
}>;
const canister1 = ic.canisters.Canister1<Canister1>(
Principal.fromText('rkp4c-7iaaa-aaaaa-aaaca-cai')
);
export function call_canister1_method(): Update<boolean> {
const canister_result: CanisterResult<null> = canister1
.method()
.notify()
.with_cycles128(100_000_000_000n);
if (!ok(canister_result)) {
return false;
}
return true;
}
Examples:
export function performance_counter(): Query<nat64> {
return ic.performance_counter(0);
}
Examples:
import { empty, ic, QueryManual } from 'azle';
export function reject(message: string): QueryManual<empty> {
ic.reject(message);
}
Examples:
import { Canister, CanisterResult, ic, RejectionCode, Update } from 'azle';
type Canister1 = Canister<{
method(): CanisterResult<boolean>;
}>;
const canister1 = ic.canisters.Canister1<Canister1>(
Principal.fromText('rkp4c-7iaaa-aaaaa-aaaca-cai')
);
export function* get_rejection_code(): Update<RejectionCode> {
yield canister1.method();
return ic.reject_code();
}
Examples:
import { Canister, CanisterResult, ic, Update } from 'azle';
type Canister1 = Canister<{
method(): CanisterResult<boolean>;
}>;
const canister1 = ic.canisters.Canister1<Canister1>(
Principal.fromText('rkp4c-7iaaa-aaaaa-aaaca-cai')
);
export function* get_rejection_message(): Update<string> {
yield canister1.method();
return ic.reject_message();
}
Examples:
import { ic, UpdateManual } from 'azle';
export function manual_update(message: string): UpdateManual<string> {
if (message === 'reject') {
ic.reject(message);
return;
}
ic.reply(message);
}
reply raw
Examples:
import { blob, ic, int, UpdateManual, Variant } from 'azle';
type RawReply = {
int: int;
text: string;
bool: boolean;
blob: blob;
variant: Options;
};
type Options = Variant<{
Small: null;
Medium: null;
Large: null;
}>;
export function reply_raw(): UpdateManual<RawReply> {
// const response = '(record { "int" = 42; "text" = "text"; "bool" = true; "blob" = blob "Surprise!"; "variant" = variant {Medium} })';
// const hex = execSync(`didc encode '${response}'`).toString().trim();
// TODO expose candid encoding/decoding in azle.
// See https://github.com/demergent-labs/azle/issues/400
const candidEncodedArgumentsHexString =
'4449444c036c05ef99c0027cddfae4880401aa88ee88047ead99e7e70471858189e70d026d7b6b019591f39a037f01002a0953757270726973652101047465787400';
const candidEncodedArgumentsByteArray =
candidEncodedArgumentsHexString
.match(/.{1,2}/g)
?.map((byte) => parseInt(byte, 16)) ?? [];
ic.reply_raw(new Uint8Array(candidEncodedArgumentsByteArray));
}
- stable storage
- stable64 grow
- stable64 read
- stable64 size
- stable64 write
- stable bytes
- stable grow
- stable read
- stable size
- stable write
Examples:
- basic-dao
- func_types
- http_counter
- persistent_storage
- pre_and_post_upgrade
- stable_storage
- tuple_types
import { Init, nat, Stable, Update } from 'azle';
type StableStorage = Stable<{
stable_nat: nat;
}>;
let stable_storage: StableStorage = ic.stable_storage();
export function init(_stable_nat: nat): Init {
stable_storage.stable_nat = _stable_nat;
}
Examples:
import { ic, nat64, Stable64GrowResult, Update } from 'azle';
export function stable64_grow(new_pages: nat64): Update<Stable64GrowResult> {
return ic.stable64_grow(new_pages);
}
Examples:
import { blob, ic, nat64, Query } from 'azle';
export function stable64_read(offset: nat64, length: nat64): Query<blob> {
return ic.stable64_read(offset, length);
}
Examples:
import { ic, nat64, Query } from 'azle';
export function stable64_size(): Query<nat64> {
return ic.stable64_size();
}
Examples:
import { blob, ic, nat64, Update } from 'azle';
export function stable64_write(offset: nat64, buf: blob): Update<void> {
ic.stable64_write(offset, buf);
}
Examples:
import { blob, ic, Query } from 'azle';
export function stable_bytes(): Query<blob> {
return ic.stable_bytes();
}
Examples:
import { ic, nat32, StableGrowResult, Update } from 'azle';
export function stable_grow(new_pages: nat32): Update<StableGrowResult> {
return ic.stable_grow(new_pages);
}
Examples:
import { blob, ic, nat32, Query } from 'azle';
export function stable_read(offset: nat32, length: nat32): Query<blob> {
return ic.stable_read(offset, length);
}
Examples:
import { ic, nat32, Query } from 'azle';
export function stable_size(): Query<nat32> {
return ic.stable_size();
}
Examples:
import { blob, ic, nat32, Update } from 'azle';
export function stable_write(offset: nat32, buf: blob): Update<void> {
ic.stable_write(offset, buf);
}
The following is a comparison of all of the major features of the Rust CDK, Motoko, and Azle.
- ✔️ = supported
- ❌ = not supported
- ❔ = unknown
- ✅ = not applicable
Feature | Rust CDK | Motoko | Azle |
---|---|---|---|
init | ✔️ | ✔️ | ✔️ |
pre upgrade | ✔️ | ✔️ | ✔️ |
post upgrade | ✔️ | ✔️ | ✔️ |
inspect message | ✔️ | ✔️ | ✔️ |
heartbeat | ✔️ | ✔️ | ✔️ |
update | ✔️ | ✔️ | ✔️ |
query | ✔️ | ✔️ | ✔️ |
http_request | ✔️ | ✔️ | ✔️ |
http_request_update | ✔️ | ✔️ | ✔️ |
Feature | Rust CDK | Motoko | Azle |
---|---|---|---|
text | ✔️ | ✔️ | ✔️ |
blob | ✔️ | ✔️ | ✔️ |
nat | ✔️ | ✔️ | ✔️ |
nat64 | ✔️ | ✔️ | ✔️ |
nat32 | ✔️ | ✔️ | ✔️ |
nat16 | ✔️ | ✔️ | ✔️ |
nat8 | ✔️ | ✔️ | ✔️ |
int | ✔️ | ✔️ | ✔️ |
int64 | ✔️ | ✔️ | ✔️ |
int32 | ✔️ | ✔️ | ✔️ |
int16 | ✔️ | ✔️ | ✔️ |
int8 | ✔️ | ✔️ | ✔️ |
float64 | ✔️ | ✔️ | ✔️ |
float32 | ✔️ | ✔️ | ✔️ |
bool | ✔️ | ✔️ | ✔️ |
null | ✔️ | ✔️ | ✔️ |
vec | ✔️ | ✔️ | ✔️ |
opt | ✔️ | ✔️ | ✔️ |
record | ✔️ | ✔️ | ✔️ |
variant | ✔️ | ✔️ | ✔️ |
func | ✔️ | ✔️ | ✔️ |
service | ✔️ | ✔️ | ❌ |
principal | ✔️ | ✔️ | ✔️ |
reserved | ✔️ | ✔️ | ✔️ |
empty | ✔️ | ✔️ | ✔️ |
Feature | Rust CDK | Motoko | Azle |
---|---|---|---|
canister balance | ✔️ | ✅ | ✔️ |
canister balance 128 | ✔️ | ✔️ | ✔️ |
data certificate | ✔️ | ✔️ | ✔️ |
canister id | ✔️ | ✔️ | ✔️ |
✔️ | ✔️ | ✔️ | |
set certified data | ✔️ | ✔️ | ✔️ |
time | ✔️ | ✔️ | ✔️ |
trap | ✔️ | ✔️ | ✔️ |
Feature | Rust CDK | Motoko | Azle |
---|---|---|---|
caller | ✔️ | ✔️ | ✔️ |
accept message | ✔️ | ✔️ | ✔️ |
arg data | ✔️ | ❔ | ❌ |
arg data raw | ✔️ | ❔ | ✔️ |
arg data raw size | ✔️ | ❔ | ✔️ |
call | ✔️ | ✔️ | ✔️ |
call raw | ✔️ | ✅ | ✔️ |
call raw 128 | ✔️ | ✔️ | ✔️ |
call with payment | ✔️ | ✅ | ✔️ |
call with payment 128 | ✔️ | ✔️ | ✔️ |
method name | ✔️ | ✔️ | ✔️ |
msg cycles accept | ✔️ | ✅ | ✔️ |
msg cycles accept 128 | ✔️ | ✔️ | ✔️ |
msg cycles available | ✔️ | ✅ | ✔️ |
msg cycles available 128 | ✔️ | ✔️ | ✔️ |
msg cycles refunded | ✔️ | ✅ | ✔️ |
msg cycles refunded 128 | ✔️ | ✔️ | ✔️ |
notify | ✔️ | ❔ | ✔️ |
notify raw | ✔️ | ❔ | ✔️ |
notify with payment 128 | ✔️ | ❔ | ✔️ |
performance counter | ✔️ | ✔️ | ✔️ |
reject | ✔️ | ❔ | ✔️ |
reject code | ✔️ | ❔ | ✔️ |
reject message | ✔️ | ❔ | ✔️ |
reply | ✔️ | ❔ | ✔️ |
reply raw | ✔️ | ❔ | ✔️ |
result | ✔️ | ❔ | ❌ |
Feature | Rust CDK | Motoko | Azle |
---|---|---|---|
stable storage | ✔️ | ✔️ | ✔️ |
stable64 grow | ✔️ | ✔️ | ✔️ |
stable64 read | ✔️ | ✔️ | ✔️ |
stable64 size | ✔️ | ✔️ | ✔️ |
stable64 write | ✔️ | ✔️ | ✔️ |
stable bytes | ✔️ | ❌ | ✔️ |
stable grow | ✔️ | ✅ | ✔️ |
stable read | ✔️ | ✅ | ✔️ |
stable size | ✔️ | ✅ | ✔️ |
stable write | ✔️ | ✅ | ✔️ |
stable read nat64 | ❌ | ✔️ | ❌ |
stable write nat64 | ❌ | ✔️ | ❌ |
stable read nat32 | ❌ | ✔️ | ❌ |
stable write nat32 | ❌ | ✔️ | ❌ |
stable read nat16 | ❌ | ✔️ | ❌ |
stable write nat16 | ❌ | ✔️ | ❌ |
stable read nat8 | ❌ | ✔️ | ❌ |
stable write nat8 | ❌ | ✔️ | ❌ |
stable read int64 | ❌ | ✔️ | ❌ |
stable write int64 | ❌ | ✔️ | ❌ |
stable read int32 | ❌ | ✔️ | ❌ |
stable write int32 | ❌ | ✔️ | ❌ |
stable read int16 | ❌ | ✔️ | ❌ |
stable write int16 | ❌ | ✔️ | ❌ |
stable read int8 | ❌ | ✔️ | ❌ |
stable write int8 | ❌ | ✔️ | ❌ |
stable read float64 | ❌ | ✔️ | ❌ |
stable write float64 | ❌ | ✔️ | ❌ |
- July 2022
- Extensive automated benchmarking
- August 2022
- Compiler error DX revamp
- Rust rewrite
- September 2022
- Extensive automated property testing
- Multiple independent security reviews/audits
- Because Azle is built on Rust, to ensure the best compatibility use underscores to separate words in directory, file, and canister names
- You must use type names directly when importing them (TODO do an example)
- Varied missing TypeScript syntax or JavaScript features
- Really bad compiler errors (you will probably not enjoy them)
- Limited asynchronous TypeScript/JavaScript (generators only for now, no promises or async/await)
- Imported npm packages may use unsupported syntax or APIs
- Avoid inline types and use type aliases instead
import *
syntax is not supported for any type that will undergo Candid serialization or deserialization- Inefficient Wasm instruction usage relative to Rust and Motoko, especially with arrays
- Unknown security vulnerabilities
- Many small inconveniences
Please note that the following plan is very subject to change, especially in response to compliance with government regulations. Please carefully read the Azle License Extension to understand Azle's copyright and the AZLE token in more detail.
Azle's tentative path towards decentralization is focused on traditional open source governance paired with a new token concept known as Open Source tokens (aka OS tokens or OSTs). The goal for OS tokens is to legally control the copyright and to fully control the repository for open source projects. In other words, OS tokens are governance tokens for open source projects.
Azle's OS token is called AZLE. Currently it only controls Azle's copyright and not the Azle repository. Demergent Labs controls its own Azle repository. Once a decentralized git repository is implemented on the Internet Computer, the plan is to move Demergent Labs' Azle repository there and give full control of that repository to the AZLE token holders.
Demergent Labs currently owns the majority of AZLE tokens, and thus has ultimate control over Azle's copyright and AZLE token allocations. Demergent Labs will use its own discretion to distribute AZLE tokens over time to contributors and other parties, eventually owning much less than 50% of the tokens.
All contributors must agree to and sign the Azle License Extension.
Please reach out before working on anything that is not in the good first issues or help wanted issues. Before beginning work on a contribution, please create or comment on the issue you want to work on and wait for clearance from Demergent Labs.
See Demergent Labs' Coding Guidelines for what to expect during code reviews.
If you want to ensure running the examples with a fresh clone works, run npm link
from the Azle root directory and then npm link azle
inside of the example's root directory. Not all of the examples are currently kept up-to-date with the correct Azle npm package.
Azle's copyright is governed by the LICENSE and LICENSE_EXTENSION.