There are a couple of flows you can follow, the main approach i usually take is adding cycles to the “parent” canister and use that to spin up a “child” canister which is the flow explained below.
You can also spin up a canister directly from the CMC canister by using the right memo and skip a call, but i was running into issues with pocket-ic that it tried to create the new canister on the NNS subnet.
You should send ICP
to the “parent canister” and then following the flow below, optionally using a subaccount (left it out to make it easier to follow)
–
Ledger
Structs used are coming from the following package
ic-ledger-types = "0.13"
pub static MEMO_TOP_UP_CANISTER: Memo = Memo(1347768404);
pub static MEMO_CREATE_CANISTER: Memo = Memo(1095062083);
pub async fn topup_self(
icp_amount_e8s: u64,
canister: Principal,
) -> CanisterResult<Nat> {
let amount = Tokens::from_e8s(icp_amount_e8s) - ICP_TRANSACTION_FEE;
let args = TransferArgs {
memo: MEMO_TOP_UP_CANISTER, // important
amount,
fee: ICP_TRANSACTION_FEE,
from_subaccount: None,
to: AccountIdentifier::new(
&MAINNET_CYCLES_MINTING_CANISTER_ID, // imported from ic-ledger-types
&Subaccount::from(canister), // important
),
created_at_time: None
};
match transfer(MAINNET_LEDGER_CANISTER_ID, args).await {
Ok(result) => match result {
Ok(block_index) => notify_top_up_cycles(block_index).await,
Err(err) => todo!("handle error"),
},
Err(err) => todo!("handle error"),
}
}
–
CMC
The CyclesMintingService can be pulled from the rust declarations on the bottom of the linked dashboard page, this holds the methods and structs needed for the method below.
pub async fn notify_top_up_cycles(block_index: u64) -> CanisterResult<Nat> {
match CyclesMintingService(MAINNET_CYCLES_MINTING_CANISTER_ID)
.notify_top_up(NotifyTopUpArg {
block_index, // block index of the transaction
canister_id: id(), // principal set in the transfer.to subaccount
})
.await
{
Ok((result,)) => match result {
NotifyTopUpResult::Ok(cycles) => Ok(cycles),
NotifyTopUpResult::Err(err) => todo!("handle error"),
},
Err((_, err)) => todo!("handle error"),
}
}
–
Create canister
Finally creating the canister
ic-cdk = "0.16"
pub async fn deploy_canister(
cycles: u64,
controllers: Vec<Principal>,
) -> CanisterResult<Principal> {
let args = CreateCanisterArgument {
settings: Some(CanisterSettings {
controllers: Some(controllers),
compute_allocation: None,
memory_allocation: None,
freezing_threshold: None,
reserved_cycles_limit: None,
wasm_memory_limit: None,
log_visibility: None,
}),
};
create_canister(args, cycles as u128)
.await
.map(|(result,)| result.canister_id)
.map_err(|(_, err)| todo!("handle error"))
}
also some helper functions that are used for converting the cycles to the right format.
pub fn nat_to_f64(n: &Nat) -> f64 {
let n_str = n.0.to_string();
n_str.parse::<f64>().unwrap()
}
pub fn f64_to_u64(f: f64) -> u64 {
f.round() as u64
}
pub fn nat_to_u64(n: &Nat) -> u64 {
f64_to_u64(nat_to_f64(n))
}
so in the end it would look something like
let cycles = topup_self(icp_e8s, id()).await?;
let new_canister = deploy_canister(nat_to_u64(&cycles), vec![id()]).await?;