skills/cycles-management/SKILL.md
Manage cycles and canister lifecycle. Covers cycle balance checks, top-ups, freezing thresholds, canister creation, and ICP-to-cycles conversion via the CMC. Use when working with cycles, canister funding, freezing threshold, frozen canister, out of cycles, top-up, canister creation, or cycle balance. Do NOT use for wallet-to-dApp integration or ICRC signer flows — use wallet-integration instead.
npx skillsauth add dfinity/icskills cycles-managementInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Cycles are the computation fuel for canisters on Internet Computer. Every canister operation (execution, storage, messaging) burns cycles. When a canister runs out of cycles, it freezes and eventually gets deleted. 1 trillion cycles (1T) costs approximately 1 USD equivalent in ICP (the exact rate is set by the NNS and fluctuates with ICP price via the CMC).
Note: icp-cli uses the cycles ledger (um5iw-rqaaa-aaaaq-qaaba-cai) by default. The cycles ledger is a single canister that tracks cycle balances for all principals, similar to a token ledger. Commands like icp cycles balance, icp cycles mint, and icp canister top-up go through the cycles ledger. There is no legacy wallet concept in icp-cli. The programmatic patterns below (accepting cycles, creating canisters via management canister) remain the same regardless of which funding mechanism is used.
mops package manager, core = "2.0.0" in mops.tomlic-cdk >= 0.19| Service | Canister ID | Purpose |
|---------|------------|---------|
| Cycles Minting Canister (CMC) | rkp4c-7iaaa-aaaaa-aaaca-cai | Converts ICP to cycles, creates canisters |
| Cycles Ledger | um5iw-rqaaa-aaaaq-qaaba-cai | Tracks cycle balances for all principals |
| Management Canister | aaaaa-aa | Canister lifecycle (create, install, stop, delete, status) |
The Management Canister (aaaaa-aa) is a virtual canister -- it does not exist on a specific subnet but is handled by every subnet's execution layer.
Running out of cycles silently freezes the canister -- There is no warning. The canister stops responding to all calls. If cycles are not topped up before the freezing threshold, the canister and all its data will be permanently deleted. Set a freezing threshold and monitor balances.
Not setting freezing_threshold -- Default is 30 days. If your canister burns cycles fast (high traffic, large stable memory), 30 days may not be enough warning. Set it higher for production canisters. The freezing threshold defines how many seconds worth of idle cycles the canister must retain before it freezes.
Confusing local vs mainnet cycles -- Local replicas give canisters virtually unlimited cycles. Code that works locally may fail on mainnet because the canister has insufficient cycles. Always test with realistic cycle amounts before mainnet deployment.
Sending cycles to the wrong canister -- Cycles sent to a canister cannot be retrieved. There is no refund mechanism for cycles transferred to the wrong principal. Double-check the canister ID before topping up.
Forgetting to set the canister controller -- If you lose the controller identity, you permanently lose the ability to upgrade, top up, or manage the canister. Always add a backup controller. Use icp canister update-settings --add-controller PRINCIPAL to add one.
Using ExperimentalCycles in mo:core -- In mo:core 2.0, the module is renamed to Cycles. import ExperimentalCycles "mo:base/ExperimentalCycles" will fail. Use import Cycles "mo:core/Cycles".
import Cycles "mo:core/Cycles";
import Principal "mo:core/Principal";
import Runtime "mo:core/Runtime";
persistent actor {
// Check this canister's cycle balance
public query func getBalance() : async Nat {
Cycles.balance()
};
// Accept cycles sent with a call (for "tip jar" or payment patterns)
public func deposit() : async Nat {
let available = Cycles.available();
if (available == 0) {
Runtime.trap("No cycles sent with this call")
};
let accepted = Cycles.accept<system>(available);
accepted
};
// Send cycles to another canister via inter-canister call
public func topUpCanister(target : Principal) : async () {
let targetActor = actor (Principal.toText(target)) : actor {
deposit_cycles : shared () -> async ();
};
// Attach 1T cycles to the call
await (with cycles = 1_000_000_000_000) targetActor.deposit_cycles();
};
}
import Principal "mo:core/Principal";
persistent actor Self {
type CanisterId = { canister_id : Principal };
type CreateCanisterSettings = {
controllers : ?[Principal];
compute_allocation : ?Nat;
memory_allocation : ?Nat;
freezing_threshold : ?Nat;
};
// Management canister interface
let ic = actor ("aaaaa-aa") : actor {
create_canister : shared { settings : ?CreateCanisterSettings } ->
async CanisterId;
canister_status : shared { canister_id : Principal } ->
async {
status : { #running; #stopping; #stopped };
memory_size : Nat;
cycles : Nat;
settings : CreateCanisterSettings;
module_hash : ?Blob;
};
deposit_cycles : shared { canister_id : Principal } -> async ();
stop_canister : shared { canister_id : Principal } -> async ();
delete_canister : shared { canister_id : Principal } -> async ();
};
// Create a new canister with 1T cycles
public func createNewCanister() : async Principal {
let result = await (with cycles = 1_000_000_000_000) ic.create_canister({
settings = ?{
controllers = ?[Principal.fromActor(Self)];
compute_allocation = null;
memory_allocation = null;
freezing_threshold = ?2_592_000; // 30 days in seconds
};
});
result.canister_id
};
// Check a canister's status and cycle balance
public func checkStatus(canisterId : Principal) : async Nat {
let status = await ic.canister_status({ canister_id = canisterId });
status.cycles
};
// Top up another canister
public func topUp(canisterId : Principal, amount : Nat) : async () {
await (with cycles = amount) ic.deposit_cycles({ canister_id = canisterId });
};
}
use ic_cdk::{query, update};
use candid::Nat;
#[query]
fn get_balance() -> Nat {
Nat::from(ic_cdk::api::canister_cycle_balance())
}
#[update]
fn deposit() -> Nat {
let available = ic_cdk::api::msg_cycles_available();
if available == 0 {
ic_cdk::trap("No cycles sent with this call");
}
let accepted = ic_cdk::api::msg_cycles_accept(available);
Nat::from(accepted)
}
use candid::{Nat, Principal};
use ic_cdk::update;
use ic_cdk::management_canister::{
create_canister_with_extra_cycles, canister_status, deposit_cycles, stop_canister, delete_canister,
CreateCanisterArgs, CanisterStatusArgs, DepositCyclesArgs, StopCanisterArgs, DeleteCanisterArgs,
CanisterSettings, CanisterStatusResult,
};
#[update]
async fn create_new_canister() -> Principal {
let caller = ic_cdk::api::canister_self(); // capture canister's own principal
let user = ic_cdk::api::msg_caller(); // capture caller before await
let settings = CanisterSettings {
controllers: Some(vec![caller, user]),
compute_allocation: None,
memory_allocation: None,
freezing_threshold: Some(Nat::from(2_592_000u64)), // 30 days
reserved_cycles_limit: None,
log_visibility: None,
wasm_memory_limit: None,
wasm_memory_threshold: None,
environment_variables: None,
};
let arg = CreateCanisterArgs {
settings: Some(settings),
};
// Send 1T cycles with the create call
let result = create_canister_with_extra_cycles(&arg, 1_000_000_000_000u128)
.await
.expect("Failed to create canister");
result.canister_id
}
#[update]
async fn check_status(canister_id: Principal) -> CanisterStatusResult {
canister_status(&CanisterStatusArgs { canister_id })
.await
.expect("Failed to get canister status")
}
#[update]
async fn top_up(canister_id: Principal, amount: u128) {
deposit_cycles(&DepositCyclesArgs { canister_id }, amount)
.await
.expect("Failed to deposit cycles");
}
#[update]
async fn stop_and_delete(canister_id: Principal) {
stop_canister(&StopCanisterArgs { canister_id })
.await
.expect("Failed to stop canister");
delete_canister(&DeleteCanisterArgs { canister_id })
.await
.expect("Failed to delete canister");
}
# Check your canister's cycle balance
icp canister status backend
# Look for "Balance:" line in output
# Check balance on mainnet
icp canister status backend -e ic
# Check any canister by ID
icp canister status ryjl3-tyaaa-aaaaa-aaaba-cai -e ic
# Top up with cycles from the cycles ledger (local)
icp canister top-up backend --amount 1000000000000
# Adds 1T cycles to the backend canister
# Top up on mainnet
icp canister top-up backend --amount 1000000000000 -e ic
# Convert ICP to cycles and top up in one step (mainnet)
icp cycles mint --icp 1.0 -e ic
icp canister top-up backend --amount 1000000000000 -e ic
# Create an empty canister (local)
icp canister create my_canister
# Create on mainnet with specific cycles
icp canister create my_canister -e ic --cycles 2000000000000
# Add a backup controller
icp canister update-settings my_canister --add-controller BACKUP_PRINCIPAL_HERE
# Set freezing threshold to 90 days (in seconds: 90 * 24 * 60 * 60 = 7776000)
icp canister update-settings backend --freezing-threshold 7776000
# Mainnet
icp canister update-settings backend --freezing-threshold 7776000 -e ic
# Deploy and check status
icp network start -d
icp deploy backend
icp canister status backend
# Expected output includes:
# Status: Running
# Balance: 3_100_000_000_000 Cycles (local default, varies)
# Freezing threshold: 2_592_000
# Check balance programmatically (if you added getBalance)
icp canister call backend getBalance '()'
# Expected: a large nat value, e.g. (3_100_000_000_000 : nat)
# On mainnet: verify cycles balance is not zero
icp canister status backend -e ic
# If Balance shows 0, the canister will freeze. Top up immediately.
tools
Deploys an already-built Internet Computer project to a user's own cloud engine (an OpenCloud / control-panel engine, administered from a web console). Covers verifying the icp CLI, linking the user's console identity to the CLI with `icp identity link web`, defaulting the console origin to https://opencloud.org (overridable when the user signs in to a different console), obtaining the engine's subnet id (asking the user when it is unknown), running `icp deploy` against that subnet, and tagging the canisters with `__META_*` environment variables so the engine console shows a named app with labelled backend/frontend canisters. Use when a developer wants to ship an app to their cloud engine, mentions a cloud engine, OpenCloud, an engine subnet id, linking the icp CLI to an engine console, or giving a deployed app a name in the console. Do NOT use for a general mainnet deploy with no specific engine or subnet (use the icp-cli skill) or for writing canister code.
tools
Guides use of the icp command-line tool for building and deploying Internet Computer applications. Covers project configuration (icp.yaml), recipes, environments, canister lifecycle, and identity management. Use when building, deploying, or managing any IC project. Use when the user mentions icp, dfx, canister deployment, local network, or project setup. Do NOT use for canister-level programming patterns like access control, inter-canister calls, or stable memory — use domain-specific skills instead.
development
Deploy frontend assets to the IC. Covers certified assets, SPA routing with .ic-assets.json5, content encoding, and programmatic uploads. Use when hosting a frontend, deploying static files, or setting up SPA routing on IC. Do NOT use for canister-level code patterns or custom domain setup — use custom-domains instead.
development
One-time installer that makes a Claude Code project keep its Internet Computer skills up to date automatically. Sets up a SessionStart hook plus a sync script so .claude/skills/ always mirrors the latest skills published at skills.internetcomputer.org. Use when a user wants to install, bootstrap, or enable "always-latest" Internet Computer / IC / ICP / Motoko skills in a project, or pastes the link to this skill. This is a one-time setup action, not ongoing IC knowledge — after it runs, the installed hook keeps skills current on every session. Do NOT use for IC coding questions themselves — this only configures auto-updating skills.