ICE is a powerful task runner and CLI tool for the Internet Computer (similar to hardhat). With ICE, you can create composable workflows, simplify complex setups, and manage canister deployments using TypeScript instead of bash, taking the development experience of the Internet Computer to the next level.
- Composable: Easily build complex workflows.
- Type-Safe: Get instant feedback on configuration errors.
- NPM Canisters: Install popular canisters (ICRC1, Ledger, NFID, Internet Identity) with zero setup.
- Smart Context & dependencies: Automatic setup of actors, users, and other env variables from inside your scripts
- VSCode Extension: Run tasks directly in your editor with inline results.
-
Install the necessary packages:
npm i -S @ice.ts/runner @ice.ts/canisters
-
Create an
ice.config.ts
file in your project root:import { motokoCanister } from '@ice.ts/runner' export const my_canister = motokoCanister({ src: 'canisters/my_canister/main.mo', })
-
Now its available under the exported name:
npx ice run my_canister
-
Or deploy all canisters:
npx ice
Tasks are the main building block of ICE. They are composable, type-safe and can depend on other tasks.
In this example ICE will make sure the icrc1_ledger is deployed and and its actor created, prior to running the task.
import { task } from "@ice.ts/runner"
export const mint_tokens = task("mint tokens")
.deps({
icrc1_ledger,
})
.run(async ({ deps: { icrc1_ledger } }) => {
const symbol = await icrc1_ledger.actor.icrc1_symbol()
console.log(symbol)
})
Run the task:
npx ice run mint_tokens
Define inter-canister or task dependencies easily.
import { motokoCanister } from "@ice.ts/runner"
// Create a canister that depends on another one
export const my_other_canister = motokoCanister({
src: "canisters/my_other_canister/main.mo",
})
.deps({ my_canister })
You can also declare requirements and provide them later:
export const MyCanister = () => motokoCanister({
src: "canisters/my_canister/main.mo",
})
.dependsOn({ my_other_canister })
Later, we can provide it through .deps(). You’ll get type-level warnings if dependencies aren’t met:

Pass fully typed install arguments instead of manual candid strings.
import { motokoCanister, task } from "@ice.ts/runner"
export const my_other_canister = motokoCanister({
src: "canisters/my_other_canister/main.mo",
})
.deps({ my_canister })
.installArgs(async ({ deps }) => {
const someVal = await deps.my_canister.actor.someMethod()
return [deps.my_canister.canisterId, someVal]
})
We get type-level warnings for invalid args

Every task and canister gets a shared context with env variables, user info, and more.
import { task } from "@ice.ts/runner";
export const example_task = task("example task")
.run(async ({ ctx }) => {
console.log(ctx.users.default.principal)
console.log(ctx.users.default.accountId)
// Run another task dynamically
const result = await ctx.runTask(my_other_task)
})
Tasks can also be grouped into scopes for better organization.
import { scope } from "@ice.ts/runner";
import { NNSDapp, NNSGovernance } from "@ice.ts/canisters";
export const my_custom_nns = scope("NNS canisters", {
nns_dapp: NNSDapp(),
nns_governance: NNSGovernance(),
// ...etc.
})
These can now be accessed in the following way:
npx ice run my_custom_nns:nns_dapp
npx ice run my_custom_nns:nns_governance:install
Use popular canisters with a single line of code. ICE provides ready-to-use setups for many common canisters.
import {
InternetIdentity,
ICRC1Ledger,
DIP721,
Ledger,
DIP20,
CapRouter,
NNS,
CandidUI,
ICRC7NFT,
CyclesWallet,
CyclesLedger,
NFID,
} from "@ice.ts/canisters"
export const nns = NNS()
export const icrc7_nft = ICRC7NFT()
export const nfid = NFID()
// And more...
It's that easy.
Install from the Visual Studio Marketplace.
- Inline CodeLens: Quickly execute tasks directly from your source code.
- Actor Call Results: results are displayed inline in your code. No more console logs.
Feel free to open an issue, PR or join the Discord and message me there.