Skip to main content

Development workflow

The Internet Computer Protocol (ICP) accepts and executes smart contracts in the WebAssembly (Wasm) binary format. In theory, developers could write valid smart contracts directly in the Wasm bytecode. Since that would be too tedious and time consuming, the standard practice is to write smart contract code in a higher-level language, such as JavaScript/TypeScript, Motoko, Python, or Rust, then compile it into Wasm.

The primary developer tool in the ICP ecosystem is dfx. It is a command line multi-tool that assists the developer throughout the entire development process, starting from the generation of developer keys and setting up a new project, to compiling, deploying, and managing smart contracts.

ICP in the tradeoff space

By default, dfx generates a project structure consisting of two smart contracts: the frontend canister and the backend canister. The frontend canister contains web assets such as JavaScript, HTML, CSS, and images that are served to the browser. The actual program logic of the smart contract is defined in the backend canister.

The configuration with a frontend and a backend canisters is just a convention. It is possible to write a single canister that contains both the program logic and hosts the web assets.

Since smart contracts can be written in different languages, it is important that they all agree on the binary format in which they accept their arguments and return results. This common format allows users and smart contracts to call other smart contracts regardless of the language they were written in. In traditional programming, this concept is known as Application Binary Interface (ABI). ICP has its own ABI language called Candid, which is similar to JSON or Protobuf, but tailored for ICP.

During the build process, dfx uses the backend ABI to automatically generate JavaScript boilerplate code for the frontend. Under the hood, the boilerplate code uses a library called agent-js to encode Candid values and make HTTP requests from the browser to the backend canister. This is similar to web3.js in Ethereum.

The Wasm standard defines only the instructions and the memory of the Wasm virtual machine; how a Wasm program interacts with other programs and users is left up to the host that embeds the virtual machine. ICP as a Wasm host provides a set of functions that Wasm code can use to read the incoming arguments, call the system and other smart contracts, and return results. Collectively these functions are referred to as the System API. Developers are not intended to use the System API directly because that would be too low-level and error-prone. Instead, developers should use language-specific Canister Development Kit (CDK) libraries that are high-level wrappers around the System API.

Deploying smart contracts

A developer can deploy smart contracts either on a local testnet or on the mainnet. The local testnet comes built-in with dfx and can be run with the dfx start command. The local testnet consists of a single node – the local machine. Note that as of writing, ICP doesn’t have an official public testnet.

In order to deploy smart contracts on the mainnet, the developer needs either ICP tokens or cycles. When using ICP tokens, dfx automatically converts the necessary amount of tokens to cycles since all operations with smart contracts require payment in cycles. See this guide for more information on how to obtain cycles.

The command for deploying smart contracts is dfx deploy. It automatically builds the source code into Wasm binaries and deploys them to the target network. By default the command targets the local testnet. Passing an additional --network ic parameter instructs commands to use the mainnet. There is no need to specify the URL of a node to connect to because dfx is already pre-configured with URLs of the boundary (RPC) nodes of the mainnet. However, it is possible to change the URL by editing the dfx configuration file.

Calling smart contracts

There are three ways to call the functions of the backend smart contract:

  1. Use the browser to load the webpage hosted in the frontend canister and use the UI of the webpage to interact with the backend canister. Under the hood, the UI uses JavaScript and agent-js to send messages to the backend smart contract. This is the standard way to interact with the smart contract that regular users would also use.
  2. Use the dfx canister call command and pass the input arguments as command line arguments. Under the hood, dfx uses a Rust library called ic-agent to send messages to the smart contract.
  3. Write an off-chain program that uses an agent library to send messages to the smart contract.
ICP in the tradeoff space