Where to call deposit_cycles

I’ve got a main canister (manager) that generates (sub) canisters. I would like to transfer the remaining cycles of these (sub) canisters, before deleting them but, I am not sure where I should call deposit_cycles?

In my manager, I have implemented following functions:

private func deleteCanister(manager: Principal, bucketId: Principal): async() {
    await transferCycles(manager, bucketId);
    
    await ic.stop_canister({ canister_id = bucketId });

    await ic.delete_canister({ canister_id = bucketId });
};

private func transferCycles(manager: Principal, bucketId: Principal): async() {
    let status = await ic.canister_status({ canister_id = bucketId });
    Cycles.add(status.cycles);
    await ic.deposit_cycles({ canister_id = manager });
};

Above does compile and does delete these (sub) canisters but, it does not seem to transfer the cycles.

Am I missing any steps in my functions?

Or should the transferCycles happens within the (sub) canisters instead of within the manager?

1 Like

I think the transfer should happen in the subcanisters.

At the moment, I think you are depositing the manager’s cycles.

2 Likes

Send cycles to the canister identifier by running a command similar to the following:

dfx wallet --network ic send <destination> <amount>

For example:

dfx wallet --network ic send gastn-uqaaa-aaaae-aaafq-cai 10000000000

If the transfer is successful, the command does not displays any output.

1 Like

You should implement receive_cycles first.

Rust example: dfinity-fungible-token-standard/token.rs at 1afbc38cd904aa3042b9fbf739f92be98252dd98 · Deland-Labs/dfinity-fungible-token-standard · GitHub

Send cycles to a canister

You can use dfx wallet send command of the wallet_send method to send a specific number of cycles to a specific canister. Keep in mind that the canister you specify must be a cycles wallet or have a wallet_receive method to accept the cycles.

If you have deployed a cycles wallet on the Internet Computer main network, you can use the dfx wallet send command to send cycles between canisters running on the network.

if you are using Motoko.

1 Like

Thank you for your answers. I tried both ideas, moving the deposit_cycles to the sub-canister and the example of the motoko doc.

Good news, it works in a sample repo.

Bad news, it does not work in my app (:cry:).

Not sure the issue is linked to the auth or the permissions but, I think it definitely has to do with something like that. When I try to deposit the cycles from the sub containers I get an error that only the controllers of the canister … can control it even though I do set the controllers (update_settings) when I create the sub-canisters.

Still debugging…

Meanwhile, for those interested, here my sample repo that transfer cycles before deleting canisters a manager generate on the fly: GitHub - peterpeterparker/transfercycles

I have solved (I think) the above error only the controllers of the canister … can control it by also adding the sub-canister principal ID in the list of controllers (somehow I had it in my example but, not in my app).

Now I am facing the next error Canister (principal id of the sub-canister) trapped explicitly: could not perform call

1 Like

Not sure what is going on, but

Looks like it’s making an unnecessary call to retrieve the status to discover the balance. Did you try just using (Experimental)Cycles.balance() instead? It’s synchronous, and doesn’t require the canister to be its own controller.

1 Like

Also, sending the entire balance might cause the call itself to fail… maybe leave some slack.

1 Like

I’m not sure if this is still the case with dfx 0.8.0, but prior releases shipped with a local replica that would not charge any cycles for execution. If that is still the case, that could explain some difference in behaviour between testing on a local replica and running on the real network.

Thanks for the idea Claudio. This looks like promising. I tested on real network with my app (not the sample one) and did not face the error as above but got another one.

Canister pmtxw… attempted to send 897991667468 cycles when only 897991667468 were available in its balance

I will now try out your other suggestion to “leave some slack”. Not sure what value to use though, maybe 100000000000?

Yoohoo it worked out :partying_face:. On the real network, I was able to delete a canister and transfer remaining cycles to my principal. I checked the balance of my manager canister in NNS and did notice the cycles being credited !

Manager:

public func delete(bucketId: Principal) : async () {
  let bucket = actor(Principal.toText(bucketId)): actor { transferCycles: () -> async () };

  await bucket.transferCycles();

  await ic.stop_canister({ canister_id = bucketId });

  await ic.delete_canister({ canister_id = bucketId });
};

Sub-canister:

public shared({ caller }) func transferCycles(): async() {
  let balance: Nat = Cycles.balance();

  let cycles: Nat = balance - 100_000_000_000;

  if (cycles > 0) {
    Cycles.add(cycles);
    await ic.deposit_cycles({ canister_id = caller });
  };
};

I have updated the sample repo accordingly.

To the remaining cycles of the sub-canister I subtract 100_000_000_000 which is currently the cost for the creation of a canister (see documentation). Not sure it cannot be less, I did not test.

I mark this post as the solution but, the credits go to @claudio ! Thanks a lot for the help :pray:

1 Like

Great!

I believe it can be much less. Maybe measure it?

Also, it would be faster to inline the call to 'await b.id() ’ in ‘utils.mo’ by calling ‘Principal.fromActor(b)’ instead. Then you can delete the ‘id()’ function too. Awaits are not cheap.

1 Like

Before going live, it will need some tests to find the best suitable value. I also hope it costs less as the current value I used is the most expensive, the one to create a canister. Added to my long list of TODOs :wink:

Freak I did not know it was possible to pass b to fromActor directly. Sure happy to drop any unnecessary await.

That’s a super cool input, thanks a lot!!!

From my tests, seems like ~5B cycles is the minimum.

1 Like