Making 30k canister to canister calls

I’m trying to send tokens from my canister’s default sub account to about 30,000 sub accounts.

In my rust based canister I’m create a vector of Futures and then using join_all from the futures crate.

The code looks very similar to this but I’ve stripped out some code for readability.


let mut transfer_futures = Vec::new();

    for (sub_account, icp_amount) in neurons {
        let icp_transfer_future = transfer_icp_to_sub_account(sub_account, icp_amount);
        transfer_futures.push((sub_account, icp_transfer_future));

    // Execute all transfer futures concurrently and collect results
    let results = join_all(transfer_futures.into_iter().map(|(sub_account, future)| async move {
        match future.await {
            Ok(_) => Ok(sub_account),
            Err(e) => {
                debug!("!!error in transfer - sub_account ::: {} - error ::: {}", sub_account, e);    
            }, // Handle error if needed

The errors I get back look like this.

{"timestamp":"1710344202865","level":"ERROR","fields":{"message":"Error calling c2c","method_name":"icrc1_transfer","canister_id":"ete3q-rqaaa-aaaal-qdlva-cai","error_code":"SysTransient","error_message":"Couldn't send message"},"target":"canister_client","filename":"backend/libraries/canister_client/src/","line_number":75}

The key part of the error here as I understand is the SysTransient part.

After looking at some documentation I can see SysTransient is related to a frozen canister either due to cycle balances or resource usage. I checked the cycles and I have plenty in there.

So I have some questions.

How can i monitor the resource usage of my canister?
How can i better structure this to make sure my calls go through? Would it be better to perhaps batch the calls in chunks of lets say 100? because it seems 30,000 calls is too much

Thanks :slight_smile:

There are various system limits in place that will prevent you from sending too many messages (calls) simultaneously, potentially making your call fail with a SYS_TRANSIENT error. One of the most relevant ones here is that the NNS subnet imposes a limit of roughly 50 calls it will accept per block from each other subnet (it’s a bit more complicated than that but 50 is what you can expect for many calls). So in your case it makes sense to batch less than 50 calls at a time, and wait on their completion before sending the next batch.

1 Like

Thanks Oggy, I’ll batch them to batches of 50

Just FYI, I’ve found from Motoko a practical limit of about 8-9 if I’m making an x subnet call. Infu reports being able to do higher, but I haven’t gone back and tested since time slicing was introduced.

1 Like