How to Programmatically topup ICP Canister
How to topup an ICP Canister Programmatically - Part 1
There are times in your business logic, where you would love your canister to automatically topup itself, maybe use some of its profits, in exchange for cycles. In this Blog i will show you how to do that.
Step 1.
You can create a new project with these command
dfx new auto_topup
In the backend subfolder, your cargo.toml should be similar to the one below
//... other attributes
[dependencies]
candid = "0.10"
ic-cdk = "0.15"
ic-cdk-timers = "0.9" # Feel free to remove this dependency if you don't need timers
ic-ledger-types = "0.12.0"
serde = "1"
Step 2
add a module called types.rs, this module will contain our types
use candid::{CandidType, Deserialize, Nat, Principal};
use ic_ledger_types::BlockIndex;
pub type NotifyTopUpResult = Result<Nat, NotifyError>;
#[derive(CandidType, Deserialize, Debug)]
pub enum NotifyError {
Refunded {
reason: String,
block_index: Option<BlockIndex>,
},
Processing,
TransactionTooOld(BlockIndex),
InvalidTransaction(String),
Other {
error_code: u64,
error_message: String,
},
}
#[derive(CandidType)]
pub struct NotifyTopUpArg {
pub block_index: BlockIndex,
pub canister_id: Principal,
}
Your lib.rs should have something similar to this
use candid::{Nat, Principal};
use ic_cdk::{api::canister_balance, call, id};
use ic_ledger_types::{transfer, AccountIdentifier, Memo, Subaccount, Tokens, TransferArgs};
use types::{NotifyTopUpArg, NotifyTopUpResult};
pub static MEMO_TOP_UP_CANISTER: Memo = Memo(1347768404_u64);
mod types;
pub static ICP_TRANSACTION_FEE: Tokens = Tokens::from_e8s(10000);
#[ic_cdk::query]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
#[ic_cdk::query]
async fn update_func() {
// After computation and business logic
let cycles = canister_balance();
//carry out checks
if cycles <= 2_000_000_000_000 {
// using 1 as 1 ICP, must be multiplied by 10^8
let icp_amount: u64 = 1 * 100_000_000;
mint_cycles(Tokens::from_e8s(icp_amount)).await;
}
}
pub async fn mint_cycles(amount: Tokens) -> Result<Nat, String> {
let CMC = Principal::from_text("rkp4c-7iaaa-aaaaa-aaaca-cai").unwrap();
let transfer_args = TransferArgs {
memo: MEMO_TOP_UP_CANISTER,
amount,
fee: ICP_TRANSACTION_FEE,
from_subaccount: None,
to: AccountIdentifier::new(
&Principal::from_text("rkp4c-7iaaa-aaaaa-aaaca-cai").unwrap(), //cycles minting canister ID
&Subaccount::from(id()),
),
created_at_time: None,
};
let block_index = transfer(Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(), transfer_args).await.unwrap().map_err(|err| err.to_string())?;
let topup_args = NotifyTopUpArg {
block_index,
canister_id: id()
};
let (rslt,): (NotifyTopUpResult,) = call(CMC, "notify_top_up", (topup_args,)).await.unwrap();
let deposited_cycles = rslt.unwrap();
Ok(deposited_cycles)
}
Code Explanation
In the update_func
, after computation and business logic canister_balance
is used to check for remaining cycles.
we call on mint_cycles
A function used to mint cycles.
Important codes segment to take note of
ryjl3-tyaaa-aaaaa-aaaba-cai
is NNS Ledger Canister Id
rkp4c-7iaaa-aaaaa-aaaca-cai
is NNS Cycles Minting Canister id
transfer
function used to transfer tokens from one account to another
call
ICP CDK API function used to call another canister method. docs
Extras
I am working on a Web3 Freelancer DAO product that tackles fundamental issues of Freelancing, which I would appreciate you checkout and subscribe
About Me
I am Declan, Rust Engineer, specializing in the ICP and Solana Web3 ecosystem. if i resonate with you. follow me on
See you on the next Part