I was trying to implement the cycle topup flow based on doing a icrc2_approve
on the frontend and an icrc2_transfer_from
on the canister to handle the topup. While the icrc2_transfer_from
uses the icrc1_memo
field as shown in this transaction it throws the following error
"Refunded: block_index: Some(20385278), reason: "Memo (0x000000) in the incoming ICP transfer does not correspond to any of the operations that the Cycles Minting canister offers.""
I tried a bunch of things like;
- setting the memo on the
icrc2_approve
- removing the default prefixed 0âs from the memo like shown on this transaction
- I also thought it might be do to the difference between the
icrc_ledger_types memo
and the ic_ledger_types memo
but so far no luck
So this is the code iâm working with, its based on the working code i had for just a regular transfer
, what am i doing wrong? Maybe only icrc1_transfer
âs are allowed?
static async approve(to: Icrc1Account, amount: bigint) {
return this.actor(false).icrc2_approve({
amount,
from_subaccount: [],
spender: {
owner: to.owner,
subaccount: to.subaccount
},
fee: [],
memo: [],
created_at_time: [],
expected_allowance: [],
expires_at: [BigInt(Date.now() * 1_000_000 + 5 * 60 * 1_000_000_000)]
});
}
pub async fn top_up_cycles_by_approve(
icp_amount: u64,
from: Principal,
canister: Principal,
) -> CanisterResult<u64> {
let amount = Tokens::from_e8s(icp_amount - ICP_TRANSACTION_FEE);
let args = TransferFromArgs {
// memo: Some(Memo::from(0x50555054), //
memo: Some(Memo::from(vec![0x50, 0x55, 0x50, 0x54])), // removes prefixed 0's attempt
amount: amount.e8s().into(),
fee: None,
to: Account {
owner: MAINNET_CYCLES_MINTING_CANISTER_ID,
subaccount: Some(Subaccount::from(canister).0),
},
created_at_time: None,
spender_subaccount: Some(Subaccount::from(from).0),
from: Account {
owner: from,
subaccount: None,
},
};
let (result,): (Result<Nat, TransferFromError>,) =
ic_cdk::call(MAINNET_LEDGER_CANISTER_ID, "icrc2_transfer_from", (args,))
.await
.map_err(|err| {
ApiError::external_service_error(&format!("{:?}", err))
.add_method_name("top_up_cycles_by_approve")
.add_source("toolkit_utils")
})?;
match result {
Ok(block_index) => Ok(nat_to_u64(&block_index)),
Err(err) => Err(ApiError::external_service_error(&format!("{:?}", err))
.add_method_name("top_up_cycles_by_approve")
.add_source("toolkit_utils")),
}
}
pub async fn notify_top_up_cycles(block_index: u64, canister_id: Principal) -> CanisterResult<Nat> {
let method_name = "notify_top_up_cycles";
let source = "toolkit_utils";
match CyclesMintingService(MAINNET_CYCLES_MINTING_CANISTER_ID)
.notify_top_up(NotifyTopUpArg {
block_index,
canister_id,
})
.await
{
Ok((result,)) => match result {
NotifyTopUpResult::Ok(cycles) => Ok(cycles),
NotifyTopUpResult::Err(err) => match err {
NotifyError::Refunded {
block_index,
reason,
} => Err(ApiError::external_service_error(&format!(
"Refunded: block_index: {:?}, reason: {:?}",
block_index, reason
))
.add_method_name(method_name)
.add_source(source)),
NotifyError::InvalidTransaction(value) => Err(ApiError::external_service_error(
&format!("InvalidTransaction: {:?}", value),
)
.add_method_name(method_name)
.add_source(source)),
NotifyError::Other {
error_message,
error_code,
} => Err(ApiError::external_service_error(&format!(
"Other: error_message: {}, error_code: {:?}",
error_message, error_code
))
.add_method_name(method_name)
.add_source(source)),
NotifyError::Processing => Err(ApiError::external_service_error("Processing")),
NotifyError::TransactionTooOld(value) => Err(ApiError::external_service_error(
&format!("TransactionTooOld: {:?}", value),
)
.add_method_name(method_name)
.add_source(source)),
},
},
Err((_, err)) => Err(ApiError::external_service_error(&err)
.add_method_name(method_name)
.add_source(source)),
}
}