mod isp_did;
use candid::{CandidType, Decode, Encode, Nat};
use garcon::Delay;
use hex::{self};
use ic_agent::agent::http_transport::ReqwestHttpReplicaV2Transport;
use ic_agent::{identity::Secp256k1Identity, Agent};
pub use isp_did::{CreateICSPResult, Error, TopUpArgs, TopUpResult, TransferResult};
use serde::Deserialize;
static ISP_CANISTER_ID_TEXT: &'static str = "p2pki-xyaaa-aaaan-qatua-cai";
pub async fn get_user_icsps(pem_identity_path: &str) -> Vec<(String, candid::Principal)> {
let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
let response_blob = build_agent(pem_identity_path)
.query(&canister_id, "getUserICSPs")
.with_arg(Encode!().expect("encode error"))
.call()
.await
.expect("response error");
let response = Decode!(&response_blob, Vec<(String, candid::Principal)>).unwrap();
response
}
pub async fn get_sub_account(pem_identity_path: &str) -> String {
let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
let response_blob = build_agent(pem_identity_path)
.query(&canister_id, "getSubAccount")
.with_arg(Encode!().expect("encode error"))
.call()
.await
.expect("response error");
let response = Decode!(&response_blob, Vec<u8>).unwrap();
hex::encode(response)
}
pub async fn get_user_sub_account_icp_balance(pem_identity_path: &str) -> u64 {
let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
let response_blob = build_agent(pem_identity_path)
.update(&canister_id, "getUserSubAccountICPBalance")
.with_arg(Encode!().expect("encode error"))
.call_and_wait()
.await
.expect("response error");
let response = Decode!(&response_blob, u64).unwrap();
response
}
pub async fn transfer_out_user_sub_account_icp(
pem_identity_path: &str,
to: &str,
amount: u64,
) -> TransferResult {
let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
let response_blob = build_agent(pem_identity_path)
.update(&canister_id, "transferOutUserSubAccountICP")
.with_arg(Encode!(&(hex::decode(to).unwrap()), &amount).expect("encode error"))
.call_and_wait()
.await
.expect("response error");
let response = Decode!(&response_blob, TransferResult).unwrap();
response
}
pub async fn get_isp_admins(pem_identity_path: &str) -> Vec<candid::Principal> {
let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
let response_blob = build_agent(pem_identity_path)
.query(&canister_id, "getAdmins")
.with_arg(Encode!().expect("encode error"))
.call()
.await
.expect("response error");
let response = Decode!(&response_blob, Vec<candid::Principal>).unwrap();
response
}
pub async fn get_version(pem_identity_path: &str) -> String {
let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
let response_blob = build_agent(pem_identity_path)
.query(&canister_id, "getVersion")
.with_arg(Encode!().expect("encode error"))
.call()
.await
.expect("response error");
Decode!(&response_blob, String).unwrap()
}
pub async fn create_icsp(
pem_identity_path: &str,
icsp_name: &str,
icp_to_create_amount: u64,
xtc_to_topup_amount: u64,
) -> (CreateICSPResult, Option<BurnResult>) {
let isp_canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
let agent = build_agent(pem_identity_path);
let response_blob = agent
.update(&isp_canister_id, "createICSP")
.with_arg(Encode!(&icsp_name, &icp_to_create_amount).expect("encode error"))
.call_and_wait()
.await
.expect("response error");
let response = Decode!(&response_blob, CreateICSPResult).unwrap();
match response {
CreateICSPResult::ok(icsp_canister_id) => {
let top_up_response = top_up_icsp_with_xtc(
pem_identity_path,
BurnArgs {
canister_id: icsp_canister_id,
amount: xtc_to_topup_amount,
},
)
.await;
match top_up_response {
BurnResult::Ok(block_index) => {
let _init_response = agent
.update(
&candid::Principal::from_text(icsp_canister_id.to_text()).unwrap(),
"init",
)
.with_arg(Encode!().expect("encode error"))
.call_and_wait()
.await
.expect("response error");
return (
CreateICSPResult::ok(icsp_canister_id),
Some(BurnResult::Ok(block_index)),
);
}
BurnResult::Err(burn_err) => {
return (
CreateICSPResult::ok(icsp_canister_id),
Some(BurnResult::Err(burn_err)),
);
}
}
}
CreateICSPResult::err(create_err) => {
return (CreateICSPResult::err(create_err), None);
}
}
}
pub async fn top_up_icsp(pem_identity_path: &str, args: TopUpArgs) -> TopUpResult {
let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
let response_blob = build_agent(pem_identity_path)
.update(&canister_id, "topUpICSP")
.with_arg(Encode!(&args).expect("encode error"))
.call_and_wait()
.await
.expect("response error");
let response = Decode!(&response_blob, TopUpResult).unwrap();
response
}
#[derive(CandidType, Deserialize, Debug)]
pub struct BurnArgs {
pub canister_id: candid::Principal,
pub amount: u64,
}
#[derive(CandidType, Deserialize, Debug)]
pub enum BurnResult {
Ok(u64),
Err(BurnError),
}
#[derive(CandidType, Deserialize, Debug)]
pub enum BurnError {
InsufficientBalance,
InvalidTokenContract,
NotSufficientLiquidity,
}
pub async fn top_up_icsp_with_xtc(pem_identity_path: &str, args: BurnArgs) -> BurnResult {
let canister_id = candid::Principal::from_text("aanaa-xaaaa-aaaah-aaeiq-cai").unwrap();
let response_blob = build_agent(pem_identity_path)
.update(&canister_id, "burn")
.with_arg(Encode!(&args).expect("encode error"))
.call_and_wait()
.await
.expect("response error");
let response = Decode!(&response_blob, BurnResult).unwrap();
response
}
fn get_waiter() -> Delay {
let waiter = garcon::Delay::builder()
.throttle(std::time::Duration::from_millis(500))
.timeout(std::time::Duration::from_secs(60 * 5))
.build();
waiter
}
fn build_agent(pem_identity_path: &str) -> Agent {
let url = "https://ic0.app".to_string();
let identity = Secp256k1Identity::from_pem_file(String::from(pem_identity_path)).unwrap();
let transport = ReqwestHttpReplicaV2Transport::create(url).expect("transport error");
let agent = Agent::builder()
.with_transport(transport)
.with_identity(identity)
.build()
.expect("build agent error");
agent
}