1mod isp_did;
2use candid::{CandidType, Decode, Encode, Nat};
3use garcon::Delay;
4use hex::{self};
5use ic_agent::agent::http_transport::ReqwestHttpReplicaV2Transport;
6use ic_agent::{identity::Secp256k1Identity, Agent};
7pub use isp_did::{CreateICSPResult, Error, TopUpArgs, TopUpResult, TransferResult};
8use serde::Deserialize;
9
10static ISP_CANISTER_ID_TEXT: &'static str = "p2pki-xyaaa-aaaan-qatua-cai";
11
12pub async fn get_user_icsps(pem_identity_path: &str) -> Vec<(String, candid::Principal)> {
30 let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
31 let response_blob = build_agent(pem_identity_path)
32 .query(&canister_id, "getUserICSPs")
33 .with_arg(Encode!().expect("encode error"))
34 .call()
35 .await
36 .expect("response error");
37 let response = Decode!(&response_blob, Vec<(String, candid::Principal)>).unwrap();
38 response
39}
40
41pub async fn get_sub_account(pem_identity_path: &str) -> String {
58 let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
59 let response_blob = build_agent(pem_identity_path)
60 .query(&canister_id, "getSubAccount")
61 .with_arg(Encode!().expect("encode error"))
62 .call()
63 .await
64 .expect("response error");
65 let response = Decode!(&response_blob, Vec<u8>).unwrap();
66 hex::encode(response)
67}
68
69pub async fn get_user_sub_account_icp_balance(pem_identity_path: &str) -> u64 {
86 let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
87 let response_blob = build_agent(pem_identity_path)
88 .update(&canister_id, "getUserSubAccountICPBalance")
89 .with_arg(Encode!().expect("encode error"))
90 .call_and_wait()
91 .await
92 .expect("response error");
93 let response = Decode!(&response_blob, u64).unwrap();
94 response
95}
96
97pub async fn transfer_out_user_sub_account_icp(
119 pem_identity_path: &str,
120 to: &str,
121 amount: u64,
122) -> TransferResult {
123 let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
124 let response_blob = build_agent(pem_identity_path)
125 .update(&canister_id, "transferOutUserSubAccountICP")
126 .with_arg(Encode!(&(hex::decode(to).unwrap()), &amount).expect("encode error"))
127 .call_and_wait()
128 .await
129 .expect("response error");
130 let response = Decode!(&response_blob, TransferResult).unwrap();
131 response
132}
133
134pub async fn get_isp_admins(pem_identity_path: &str) -> Vec<candid::Principal> {
150 let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
151 let response_blob = build_agent(pem_identity_path)
152 .query(&canister_id, "getAdmins")
153 .with_arg(Encode!().expect("encode error"))
154 .call()
155 .await
156 .expect("response error");
157 let response = Decode!(&response_blob, Vec<candid::Principal>).unwrap();
158 response
159}
160
161pub async fn get_version(pem_identity_path: &str) -> String {
176 let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
177 let response_blob = build_agent(pem_identity_path)
178 .query(&canister_id, "getVersion")
179 .with_arg(Encode!().expect("encode error"))
180 .call()
181 .await
182 .expect("response error");
183 Decode!(&response_blob, String).unwrap()
184}
185
186pub async fn create_icsp(
225 pem_identity_path: &str,
226 icsp_name: &str,
227 icp_to_create_amount: u64,
228 xtc_to_topup_amount: u64,
229) -> (CreateICSPResult, Option<BurnResult>) {
230 let isp_canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
232 let agent = build_agent(pem_identity_path);
233 let response_blob = agent
234 .update(&isp_canister_id, "createICSP")
235 .with_arg(Encode!(&icsp_name, &icp_to_create_amount).expect("encode error"))
236 .call_and_wait()
237 .await
238 .expect("response error");
239 let response = Decode!(&response_blob, CreateICSPResult).unwrap();
240 match response {
241 CreateICSPResult::ok(icsp_canister_id) => {
242 let top_up_response = top_up_icsp_with_xtc(
244 pem_identity_path,
245 BurnArgs {
246 canister_id: icsp_canister_id,
247 amount: xtc_to_topup_amount,
248 },
249 )
250 .await;
251 match top_up_response {
252 BurnResult::Ok(block_index) => {
253 let _init_response = agent
255 .update(
256 &candid::Principal::from_text(icsp_canister_id.to_text()).unwrap(),
257 "init",
258 )
259 .with_arg(Encode!().expect("encode error"))
260 .call_and_wait()
261 .await
262 .expect("response error");
263 return (
264 CreateICSPResult::ok(icsp_canister_id),
265 Some(BurnResult::Ok(block_index)),
266 );
267 }
268 BurnResult::Err(burn_err) => {
269 return (
270 CreateICSPResult::ok(icsp_canister_id),
271 Some(BurnResult::Err(burn_err)),
272 );
273 }
274 }
275 }
276 CreateICSPResult::err(create_err) => {
277 return (CreateICSPResult::err(create_err), None);
278 }
279 }
280}
281
282pub async fn top_up_icsp(pem_identity_path: &str, args: TopUpArgs) -> TopUpResult {
305 let canister_id = candid::Principal::from_text(ISP_CANISTER_ID_TEXT).unwrap();
306 let response_blob = build_agent(pem_identity_path)
307 .update(&canister_id, "topUpICSP")
308 .with_arg(Encode!(&args).expect("encode error"))
309 .call_and_wait()
310 .await
311 .expect("response error");
312 let response = Decode!(&response_blob, TopUpResult).unwrap();
313 response
314}
315
316#[derive(CandidType, Deserialize, Debug)]
317pub struct BurnArgs {
318 pub canister_id: candid::Principal,
319 pub amount: u64,
320}
321
322#[derive(CandidType, Deserialize, Debug)]
323pub enum BurnResult {
324 Ok(u64),
325 Err(BurnError),
326}
327
328#[derive(CandidType, Deserialize, Debug)]
329pub enum BurnError {
330 InsufficientBalance,
331 InvalidTokenContract,
332 NotSufficientLiquidity,
333}
334
335pub async fn top_up_icsp_with_xtc(pem_identity_path: &str, args: BurnArgs) -> BurnResult {
360 let canister_id = candid::Principal::from_text("aanaa-xaaaa-aaaah-aaeiq-cai").unwrap();
361 let response_blob = build_agent(pem_identity_path)
362 .update(&canister_id, "burn")
363 .with_arg(Encode!(&args).expect("encode error"))
364 .call_and_wait()
365 .await
366 .expect("response error");
367 let response = Decode!(&response_blob, BurnResult).unwrap();
368 response
369}
370
371fn get_waiter() -> Delay {
372 let waiter = garcon::Delay::builder()
373 .throttle(std::time::Duration::from_millis(500))
374 .timeout(std::time::Duration::from_secs(60 * 5))
375 .build();
376 waiter
377}
378
379fn build_agent(pem_identity_path: &str) -> Agent {
380 let url = "https://ic0.app".to_string();
381 let identity = Secp256k1Identity::from_pem_file(String::from(pem_identity_path)).unwrap();
382 let transport = ReqwestHttpReplicaV2Transport::create(url).expect("transport error");
383 let agent = Agent::builder()
384 .with_transport(transport)
385 .with_identity(identity)
386 .build()
387 .expect("build agent error");
388 agent
389}