cycles-management

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cycles & Canister Management

Cycles与Canister管理

What This Is

内容概述

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.
Cycles是Internet Computer上Canister的计算燃料。每个Canister操作(执行、存储、消息传递)都会消耗Cycles。当Canister的Cycles耗尽时,它会被冻结并最终被删除。1万亿Cycles(1T)的成本约等于1美元的ICP(具体汇率由NNS设定,并会随ICP价格通过CMC波动)。
注意: icp-cli默认使用cycles账本
um5iw-rqaaa-aaaaq-qaaba-cai
)。cycles账本是一个单独的Canister,用于跟踪所有主体的Cycles余额,类似于代币账本。
icp cycles balance
icp cycles mint
icp canister top-up
等命令都通过cycles账本执行。icp-cli中没有传统钱包的概念。无论使用哪种资金机制,以下编程模式(接收Cycles、通过管理Canister创建Canister)都保持不变。

Prerequisites

前置条件

  • For Motoko:
    mops
    package manager,
    core = "2.0.0"
    in mops.toml
  • For Rust:
    ic-cdk >= 0.19
  • 对于Motoko:
    mops
    包管理器,mops.toml中配置
    core = "2.0.0"
  • 对于Rust:
    ic-cdk >= 0.19

Canister IDs

Canister ID列表

ServiceCanister IDPurpose
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.
服务Canister ID用途
Cycles Minting Canister (CMC)
rkp4c-7iaaa-aaaaa-aaaca-cai
将ICP转换为Cycles,创建Canister
Cycles Ledger
um5iw-rqaaa-aaaaq-qaaba-cai
跟踪所有主体的Cycles余额
Management Canister
aaaaa-aa
Canister生命周期管理(创建、部署、停止、删除、状态查询)
管理Canister(
aaaaa-aa
)是一个虚拟Canister——它不存在于特定子网,而是由每个子网的执行层处理。

Mistakes That Break Your Build

常见错误及解决方案

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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"
    .
  1. Cycles耗尽导致Canister静默冻结——没有任何警告。Canister会停止响应所有调用。如果在冻结阈值前未充值Cycles,Canister及其所有数据将被永久删除。请设置冻结阈值并监控余额。
  2. 未设置freezing_threshold——默认值为30天。如果你的Canister消耗Cycles很快(高流量、大容量稳定内存),30天的预警时间可能不够。生产环境的Canister应设置更高的阈值。冻结阈值定义了Canister在冻结前必须保留的空闲Cycles可支撑的秒数。
  3. 混淆本地与主网Cycles——本地副本为Canister提供几乎无限的Cycles。在本地运行正常的代码可能在主网失败,因为Canister的Cycles不足。主网部署前务必使用真实的Cycles数量进行测试。
  4. 将Cycles发送到错误的Canister——发送到Canister的Cycles无法取回。没有针对误转至错误主体的Cycles的退款机制。充值前请仔细检查Canister ID。
  5. 忘记设置Canister控制器——如果你丢失了控制器身份,将永久失去升级、充值或管理Canister的权限。请务必添加备份控制器。使用
    icp canister update-settings --add-controller PRINCIPAL
    命令添加。
  6. 在mo:core中使用ExperimentalCycles——在mo:core 2.0中,该模块已重命名为
    Cycles
    import ExperimentalCycles "mo:base/ExperimentalCycles"
    会执行失败,请使用
    import Cycles "mo:core/Cycles"

Implementation

代码实现

Motoko

Motoko

Checking and Accepting Cycles

余额查询与接收Cycles

motoko
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();
  };
}
motoko
import Cycles "mo:core/Cycles";
import Principal "mo:core/Principal";
import Runtime "mo:core/Runtime";

persistent actor {

  // 查询当前Canister的Cycles余额
  public query func getBalance() : async Nat {
    Cycles.balance()
  };

  // 接收随调用发送的Cycles(适用于“小费罐”或支付场景)
  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
  };

  // 通过跨Canister调用向其他Canister发送Cycles
  public func topUpCanister(target : Principal) : async () {
    let targetActor = actor (Principal.toText(target)) : actor {
      deposit_cycles : shared () -> async ();
    };
    // 附加1T Cycles到调用中
    await (with cycles = 1_000_000_000_000) targetActor.deposit_cycles();
  };
}

Creating a Canister Programmatically

程序化创建Canister

motoko
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 });
  };
}
motoko
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;
  };

  // 管理Canister接口
  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 ();
  };

  // 创建一个带有1T Cycles的新Canister
  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天(秒数)
      };
    });
    result.canister_id
  };

  // 查询Canister的状态与Cycles余额
  public func checkStatus(canisterId : Principal) : async Nat {
    let status = await ic.canister_status({ canister_id = canisterId });
    status.cycles
  };

  // 为其他Canister充值Cycles
  public func topUp(canisterId : Principal, amount : Nat) : async () {
    await (with cycles = amount) ic.deposit_cycles({ canister_id = canisterId });
  };
}

Rust

Rust

Checking Balance and Accepting Cycles

余额查询与接收Cycles

rust
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)
}
rust
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)
}

Creating and Managing Canisters

Canister创建与管理

rust
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");
}
rust
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(); // 获取当前Canister的主体
    let user = ic_cdk::api::msg_caller(); // 在await前获取调用者

    let settings = CanisterSettings {
        controllers: Some(vec![caller, user]),
        compute_allocation: None,
        memory_allocation: None,
        freezing_threshold: Some(Nat::from(2_592_000u64)), // 30天
        reserved_cycles_limit: None,
        log_visibility: None,
        wasm_memory_limit: None,
        wasm_memory_threshold: None,
        environment_variables: None,
    };

    let arg = CreateCanisterArgs {
        settings: Some(settings),
    };

    // 随创建调用发送1T Cycles
    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");
}

Deploy & Test

部署与测试

Check Cycle Balance

查询Cycles余额

bash
undefined
bash
undefined

Check your canister's cycle balance

查询Canister的Cycles余额

icp canister status backend
icp canister status backend

Look for "Balance:" line in output

在输出中查找“Balance:”行

Check balance on mainnet

查询主网余额

icp canister status backend -e ic
icp canister status backend -e ic

Check any canister by ID

通过ID查询任意Canister

icp canister status ryjl3-tyaaa-aaaaa-aaaba-cai -e ic
undefined
icp canister status ryjl3-tyaaa-aaaaa-aaaba-cai -e ic
undefined

Top Up a Canister

为Canister充值

bash
undefined
bash
undefined

Top up with cycles from the cycles ledger (local)

从cycles账本为Canister充值(本地环境)

icp canister top-up backend --amount 1000000000000
icp canister top-up backend --amount 1000000000000

Adds 1T cycles to the backend canister

为backend Canister添加1T Cycles

Top up on mainnet

主网充值

icp canister top-up backend --amount 1000000000000 -e ic
icp canister top-up backend --amount 1000000000000 -e ic

Convert ICP to cycles and top up in one step (mainnet)

一步完成ICP转Cycles并充值(主网)

icp cycles mint --icp 1.0 -e ic icp canister top-up backend --amount 1000000000000 -e ic
undefined
icp cycles mint --icp 1.0 -e ic icp canister top-up backend --amount 1000000000000 -e ic
undefined

Create a Canister via icp

通过icp创建Canister

bash
undefined
bash
undefined

Create an empty canister (local)

创建空Canister(本地环境)

icp canister create my_canister
icp canister create my_canister

Create on mainnet with specific cycles

在主网创建并指定Cycles数量

icp canister create my_canister -e ic --cycles 2000000000000
icp canister create my_canister -e ic --cycles 2000000000000

Add a backup controller

添加备份控制器

icp canister update-settings my_canister --add-controller BACKUP_PRINCIPAL_HERE
undefined
icp canister update-settings my_canister --add-controller BACKUP_PRINCIPAL_HERE
undefined

Set Freezing Threshold

设置冻结阈值

bash
undefined
bash
undefined

Set freezing threshold to 90 days (in seconds: 90 * 24 * 60 * 60 = 7776000)

将冻结阈值设置为90天(秒数:90 * 24 * 60 * 60 = 7776000)

icp canister update-settings backend --freezing-threshold 7776000
icp canister update-settings backend --freezing-threshold 7776000

Mainnet

主网环境

icp canister update-settings backend --freezing-threshold 7776000 -e ic
undefined
icp canister update-settings backend --freezing-threshold 7776000 -e ic
undefined

Verify It Works

验证功能正常

bash
undefined
bash
undefined

Deploy and check status

部署并查询状态

icp network start -d icp deploy backend icp canister status backend
icp network start -d icp deploy backend icp canister status backend

Expected output includes:

预期输出包含:

Status: Running

Status: Running

Balance: 3_100_000_000_000 Cycles (local default, varies)

Balance: 3_100_000_000_000 Cycles(本地默认值,可能有所不同)

Freezing threshold: 2_592_000

Freezing threshold: 2_592_000

Check balance programmatically (if you added getBalance)

程序化查询余额(如果已添加getBalance方法)

icp canister call backend getBalance '()'
icp canister call backend getBalance '()'

Expected: a large nat value, e.g. (3_100_000_000_000 : nat)

预期结果:一个大的nat值,例如 (3_100_000_000_000 : nat)

On mainnet: verify cycles balance is not zero

主网环境:验证Cycles余额不为零

icp canister status backend -e ic
icp canister status backend -e ic

If Balance shows 0, the canister will freeze. Top up immediately.

如果Balance显示为0,Canister将被冻结,请立即充值。

undefined
undefined