1use std::{
6 future::{Future, IntoFuture},
7 ops::Deref,
8};
9
10use crate::{
11 call::{AsyncCall, CallFuture, SyncCall},
12 canister::Argument,
13 interfaces::management_canister::{
14 attributes::{ComputeAllocation, FreezingThreshold, MemoryAllocation},
15 builders::CanisterSettings,
16 },
17 Canister,
18};
19use async_trait::async_trait;
20use candid::{decode_args, utils::ArgumentDecoder, CandidType, Deserialize, Nat};
21use ic_agent::{agent::CallResponse, export::Principal, Agent, AgentError};
22use once_cell::sync::Lazy;
23use semver::{Version, VersionReq};
24
25const REPLICA_ERROR_NO_SUCH_QUERY_METHOD: &str = "has no query method 'wallet_api_version'";
26const IC_REF_ERROR_NO_SUCH_QUERY_METHOD: &str = "query method does not exist";
27
28#[derive(Debug)]
30pub struct CallForwarder<'agent, 'canister, Out>
31where
32 Out: for<'de> ArgumentDecoder<'de> + Send + Sync,
33{
34 wallet: &'canister WalletCanister<'agent>,
35 destination: Principal,
36 method_name: String,
37 amount: u128,
38 u128: bool,
39 arg: Argument,
40 phantom_out: std::marker::PhantomData<Out>,
41}
42
43#[derive(Debug, Clone, CandidType, Deserialize)]
46pub struct CanisterSettingsV1 {
47 pub controller: Option<Principal>,
49 pub compute_allocation: Option<Nat>,
51 pub memory_allocation: Option<Nat>,
53 pub freezing_threshold: Option<Nat>,
55}
56
57impl<'agent: 'canister, 'canister, Out> CallForwarder<'agent, 'canister, Out>
58where
59 Out: for<'de> ArgumentDecoder<'de> + Send + Sync + 'agent,
60{
61 pub fn with_arg<Argument>(mut self, arg: Argument) -> Self
63 where
64 Argument: CandidType + Sync + Send,
65 {
66 self.arg.set_idl_arg(arg);
67 self
68 }
69 pub fn with_args(mut self, tuple: impl candid::utils::ArgumentEncoder) -> Self {
71 if self.arg.0.is_some() {
72 panic!("argument is being set more than once");
73 }
74 self.arg = Argument::from_candid(tuple);
75 self
76 }
77
78 pub fn with_arg_raw(mut self, arg: Vec<u8>) -> Self {
80 self.arg.set_raw_arg(arg);
81 self
82 }
83
84 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = Out>, AgentError> {
86 #[derive(CandidType, Deserialize)]
87 struct In<TCycles> {
88 canister: Principal,
89 method_name: String,
90 #[serde(with = "serde_bytes")]
91 args: Vec<u8>,
92 cycles: TCycles,
93 }
94 Ok(if self.u128 {
95 self.wallet.update("wallet_call128").with_arg(In {
96 canister: self.destination,
97 method_name: self.method_name,
98 args: self.arg.serialize()?,
99 cycles: self.amount,
100 })
101 } else {
102 self.wallet.update("wallet_call").with_arg(In {
103 canister: self.destination,
104 method_name: self.method_name,
105 args: self.arg.serialize()?,
106 cycles: u64::try_from(self.amount).map_err(|_| {
107 AgentError::WalletUpgradeRequired(
108 "The installed wallet does not support cycle counts >2^64-1".to_string(),
109 )
110 })?,
111 })
112 }
113 .build()
114 .and_then(|(result,): (Result<CallResult, String>,)| async move {
115 let result = result.map_err(AgentError::WalletCallFailed)?;
116 decode_args::<Out>(result.r#return.as_slice())
117 .map_err(|e| AgentError::CandidError(Box::new(e)))
118 }))
119 }
120
121 pub fn call(self) -> impl Future<Output = Result<CallResponse<Out>, AgentError>> + 'agent {
123 let call = self.build();
124 async { call?.call().await }
125 }
126
127 pub fn call_and_wait(self) -> impl Future<Output = Result<Out, AgentError>> + 'agent {
129 let call = self.build();
130 async { call?.call_and_wait().await }
131 }
132}
133
134#[cfg_attr(target_family = "wasm", async_trait(?Send))]
135#[cfg_attr(not(target_family = "wasm"), async_trait)]
136impl<'agent: 'canister, 'canister, Out> AsyncCall for CallForwarder<'agent, 'canister, Out>
137where
138 Out: for<'de> ArgumentDecoder<'de> + Send + Sync + 'agent,
139{
140 type Value = Out;
141
142 async fn call(self) -> Result<CallResponse<Out>, AgentError> {
143 self.call().await
144 }
145
146 async fn call_and_wait(self) -> Result<Out, AgentError> {
147 self.call_and_wait().await
148 }
149}
150
151impl<'agent: 'canister, 'canister, Out> IntoFuture for CallForwarder<'agent, 'canister, Out>
152where
153 Out: for<'de> ArgumentDecoder<'de> + Send + Sync + 'agent,
154{
155 type IntoFuture = CallFuture<'agent, Out>;
156 type Output = Result<Out, AgentError>;
157 fn into_future(self) -> Self::IntoFuture {
158 Box::pin(self.call_and_wait())
159 }
160}
161
162#[derive(Debug, Clone)]
165pub struct WalletCanister<'agent> {
166 canister: Canister<'agent>,
167 version: Version,
168}
169
170impl<'agent> Deref for WalletCanister<'agent> {
171 type Target = Canister<'agent>;
172 fn deref(&self) -> &Self::Target {
173 &self.canister
174 }
175}
176
177#[derive(CandidType, Debug, Deserialize)]
179pub enum EventKind<TCycles = u128> {
180 CyclesSent {
182 to: Principal,
184 amount: TCycles,
186 refund: TCycles,
188 },
189 CyclesReceived {
191 from: Principal,
193 amount: TCycles,
195 memo: Option<String>,
197 },
198 AddressAdded {
200 id: Principal,
202 name: Option<String>,
204 role: Role,
206 },
207 AddressRemoved {
209 id: Principal,
211 },
212 CanisterCreated {
214 canister: Principal,
216 cycles: TCycles,
218 },
219 CanisterCalled {
221 canister: Principal,
223 method_name: String,
225 cycles: TCycles,
227 },
228}
229
230impl From<EventKind<u64>> for EventKind {
231 fn from(kind: EventKind<u64>) -> Self {
232 use EventKind::*;
233 match kind {
234 AddressAdded { id, name, role } => AddressAdded { id, name, role },
235 AddressRemoved { id } => AddressRemoved { id },
236 CanisterCalled {
237 canister,
238 cycles,
239 method_name,
240 } => CanisterCalled {
241 canister,
242 cycles: cycles.into(),
243 method_name,
244 },
245 CanisterCreated { canister, cycles } => CanisterCreated {
246 canister,
247 cycles: cycles.into(),
248 },
249 CyclesReceived { amount, from, memo } => CyclesReceived {
250 amount: amount.into(),
251 from,
252 memo,
253 },
254 CyclesSent { amount, refund, to } => CyclesSent {
255 amount: amount.into(),
256 refund: refund.into(),
257 to,
258 },
259 }
260 }
261}
262
263#[derive(CandidType, Debug, Deserialize)]
265pub struct Event<TCycles = u128> {
266 pub id: u32,
268 pub timestamp: u64,
270 pub kind: EventKind<TCycles>,
272}
273
274impl From<Event<u64>> for Event {
275 fn from(
276 Event {
277 id,
278 timestamp,
279 kind,
280 }: Event<u64>,
281 ) -> Self {
282 Self {
283 id,
284 timestamp,
285 kind: kind.into(),
286 }
287 }
288}
289
290#[derive(CandidType, Debug, Deserialize)]
292pub enum Role {
293 Contact,
295 Custodian,
297 Controller,
299}
300
301#[derive(CandidType, Debug, Deserialize)]
303pub enum Kind {
304 Unknown,
306 User,
308 Canister,
310}
311
312#[derive(CandidType, Debug, Deserialize)]
314pub struct AddressEntry {
315 pub id: Principal,
317 pub name: Option<String>,
319 pub kind: Kind,
321 pub role: Role,
323}
324
325#[derive(CandidType, Debug, Deserialize)]
327pub struct ManagedCanisterInfo {
328 pub id: Principal,
330 pub name: Option<String>,
332 pub created_at: u64,
334}
335
336#[derive(CandidType, Debug, Deserialize)]
338pub enum ManagedCanisterEventKind<TCycles = u128> {
339 CyclesSent {
341 amount: TCycles,
343 refund: TCycles,
345 },
346 Called {
348 method_name: String,
350 cycles: TCycles,
352 },
353 Created {
355 cycles: TCycles,
357 },
358}
359
360impl From<ManagedCanisterEventKind<u64>> for ManagedCanisterEventKind {
361 fn from(event: ManagedCanisterEventKind<u64>) -> Self {
362 use ManagedCanisterEventKind::*;
363 match event {
364 Called {
365 cycles,
366 method_name,
367 } => Called {
368 cycles: cycles.into(),
369 method_name,
370 },
371 Created { cycles } => Created {
372 cycles: cycles.into(),
373 },
374 CyclesSent { amount, refund } => CyclesSent {
375 amount: amount.into(),
376 refund: refund.into(),
377 },
378 }
379 }
380}
381
382#[derive(CandidType, Deserialize, Debug)]
384pub struct ManagedCanisterEvent<TCycles = u128> {
385 pub id: u32,
387 pub timestamp: u64,
389 pub kind: ManagedCanisterEventKind<TCycles>,
391}
392
393impl From<ManagedCanisterEvent<u64>> for ManagedCanisterEvent {
394 fn from(
395 ManagedCanisterEvent {
396 id,
397 timestamp,
398 kind,
399 }: ManagedCanisterEvent<u64>,
400 ) -> Self {
401 Self {
402 id,
403 timestamp,
404 kind: kind.into(),
405 }
406 }
407}
408
409#[derive(Debug, Copy, Clone, CandidType, Deserialize)]
411pub struct BalanceResult<TCycles = u128> {
412 pub amount: TCycles,
414}
415
416#[derive(Debug, Copy, Clone, CandidType, Deserialize)]
418pub struct CreateResult {
419 pub canister_id: Principal,
421}
422
423#[derive(Debug, Clone, CandidType, Deserialize)]
425pub struct CallResult {
426 #[serde(with = "serde_bytes")]
428 pub r#return: Vec<u8>,
429}
430
431impl<'agent> WalletCanister<'agent> {
432 pub async fn create(
434 agent: &'agent Agent,
435 canister_id: Principal,
436 ) -> Result<WalletCanister<'agent>, AgentError> {
437 let canister = Canister::builder()
438 .with_agent(agent)
439 .with_canister_id(canister_id)
440 .build()
441 .unwrap();
442 Self::from_canister(canister).await
443 }
444
445 pub async fn from_canister(
447 canister: Canister<'agent>,
448 ) -> Result<WalletCanister<'agent>, AgentError> {
449 static DEFAULT_VERSION: Lazy<Version> = Lazy::new(|| Version::parse("0.1.0").unwrap());
450 let version: Result<(String,), _> =
451 canister.query("wallet_api_version").build().call().await;
452 let version = match version {
453 Err(AgentError::UncertifiedReject {
454 reject: replica_error,
455 ..
456 }) if replica_error
457 .reject_message
458 .contains(REPLICA_ERROR_NO_SUCH_QUERY_METHOD)
459 || replica_error
460 .reject_message
461 .contains(IC_REF_ERROR_NO_SUCH_QUERY_METHOD) =>
462 {
463 DEFAULT_VERSION.clone()
464 }
465 version => Version::parse(&version?.0).unwrap_or_else(|_| DEFAULT_VERSION.clone()),
466 };
467 Ok(Self { canister, version })
468 }
469
470 pub fn from_canister_with_version(canister: Canister<'agent>, version: Version) -> Self {
474 Self { canister, version }
475 }
476}
477
478impl<'agent> WalletCanister<'agent> {
479 pub fn fetch_wallet_api_version(&self) -> impl 'agent + SyncCall<Value = (Option<String>,)> {
481 self.query("wallet_api_version").build()
482 }
483
484 pub fn wallet_api_version(&self) -> &Version {
486 &self.version
487 }
488
489 pub fn name(&self) -> impl 'agent + SyncCall<Value = (Option<String>,)> {
491 self.query("name").build()
492 }
493
494 pub fn set_name(&self, name: String) -> impl 'agent + AsyncCall<Value = ()> {
496 self.update("set_name").with_arg(name).build()
497 }
498
499 pub fn get_controllers(&self) -> impl 'agent + SyncCall<Value = (Vec<Principal>,)> {
501 self.query("get_controllers").build()
502 }
503
504 pub fn add_controller(&self, principal: Principal) -> impl 'agent + AsyncCall<Value = ()> {
506 self.update("add_controller").with_arg(principal).build()
507 }
508
509 pub fn remove_controller(&self, principal: Principal) -> impl 'agent + AsyncCall<Value = ()> {
511 self.update("remove_controller").with_arg(principal).build()
512 }
513
514 pub fn get_custodians(&self) -> impl 'agent + SyncCall<Value = (Vec<Principal>,)> {
516 self.query("get_custodians").build()
517 }
518
519 pub fn authorize(&self, custodian: Principal) -> impl 'agent + AsyncCall<Value = ()> {
521 self.update("authorize").with_arg(custodian).build()
522 }
523
524 pub fn deauthorize(&self, custodian: Principal) -> impl 'agent + AsyncCall<Value = ()> {
526 self.update("deauthorize").with_arg(custodian).build()
527 }
528
529 pub fn wallet_balance64(&self) -> impl 'agent + SyncCall<Value = (BalanceResult<u64>,)> {
531 self.query("wallet_balance").build()
532 }
533
534 pub fn wallet_balance128(&self) -> impl 'agent + SyncCall<Value = (BalanceResult,)> {
536 self.query("wallet_balance128").build()
537 }
538
539 pub async fn wallet_balance(&self) -> Result<BalanceResult, AgentError> {
541 if self.version_supports_u128_cycles() {
542 self.wallet_balance128().call().await.map(|(r,)| r)
543 } else {
544 self.wallet_balance64()
545 .call()
546 .await
547 .map(|(r,)| BalanceResult {
548 amount: r.amount.into(),
549 })
550 }
551 }
552
553 pub fn wallet_send64(
555 &self,
556 destination: Principal,
557 amount: u64,
558 ) -> impl 'agent + AsyncCall<Value = (Result<(), String>,)> {
559 #[derive(CandidType)]
560 struct In {
561 canister: Principal,
562 amount: u64,
563 }
564
565 self.update("wallet_send")
566 .with_arg(In {
567 canister: destination,
568 amount,
569 })
570 .build()
571 }
572
573 pub fn wallet_send128<'canister: 'agent>(
575 &'canister self,
576 destination: Principal,
577 amount: u128,
578 ) -> impl 'agent + AsyncCall<Value = (Result<(), String>,)> {
579 #[derive(CandidType)]
580 struct In {
581 canister: Principal,
582 amount: u128,
583 }
584
585 self.update("wallet_send128")
586 .with_arg(In {
587 canister: destination,
588 amount,
589 })
590 .build()
591 }
592
593 pub async fn wallet_send(
595 &self,
596 destination: Principal,
597 amount: u128,
598 ) -> Result<(), AgentError> {
599 if self.version_supports_u128_cycles() {
600 self.wallet_send128(destination, amount)
601 .call_and_wait()
602 .await?
603 } else {
604 let amount = u64::try_from(amount).map_err(|_| {
605 AgentError::WalletUpgradeRequired(
606 "The installed wallet does not support cycle counts >2^64-1.".to_string(),
607 )
608 })?;
609 self.wallet_send64(destination, amount)
610 .call_and_wait()
611 .await?
612 }
613 .0
614 .map_err(AgentError::WalletError)
615 }
616
617 pub fn wallet_receive(&self, memo: Option<String>) -> impl 'agent + AsyncCall<Value = ((),)> {
619 #[derive(CandidType)]
620 struct In {
621 memo: Option<String>,
622 }
623 self.update("wallet_receive")
624 .with_arg(memo.map(|memo| In { memo: Some(memo) }))
625 .build()
626 }
627
628 pub fn wallet_create_canister64_v1(
630 &self,
631 cycles: u64,
632 controller: Option<Principal>,
633 compute_allocation: Option<ComputeAllocation>,
634 memory_allocation: Option<MemoryAllocation>,
635 freezing_threshold: Option<FreezingThreshold>,
636 ) -> impl 'agent + AsyncCall<Value = (Result<CreateResult, String>,)> {
637 #[derive(CandidType)]
638 struct In {
639 cycles: u64,
640 settings: CanisterSettingsV1,
641 }
642
643 let settings = CanisterSettingsV1 {
644 controller,
645 compute_allocation: compute_allocation.map(u8::from).map(Nat::from),
646 memory_allocation: memory_allocation.map(u64::from).map(Nat::from),
647 freezing_threshold: freezing_threshold.map(u64::from).map(Nat::from),
648 };
649
650 self.update("wallet_create_canister")
651 .with_arg(In { cycles, settings })
652 .build()
653 .map(|result: (Result<CreateResult, String>,)| (result.0,))
654 }
655
656 pub fn wallet_create_canister64_v2(
658 &self,
659 cycles: u64,
660 controllers: Option<Vec<Principal>>,
661 compute_allocation: Option<ComputeAllocation>,
662 memory_allocation: Option<MemoryAllocation>,
663 freezing_threshold: Option<FreezingThreshold>,
664 ) -> impl 'agent + AsyncCall<Value = (Result<CreateResult, String>,)> {
665 #[derive(CandidType)]
666 struct In {
667 cycles: u64,
668 settings: CanisterSettings,
669 }
670
671 let settings = CanisterSettings {
672 controllers,
673 compute_allocation: compute_allocation.map(u8::from).map(Nat::from),
674 memory_allocation: memory_allocation.map(u64::from).map(Nat::from),
675 freezing_threshold: freezing_threshold.map(u64::from).map(Nat::from),
676 reserved_cycles_limit: None,
677 wasm_memory_limit: None,
678 wasm_memory_threshold: None,
679 log_visibility: None,
680 log_memory_limit: None,
681 environment_variables: None,
682 };
683
684 self.update("wallet_create_canister")
685 .with_arg(In { cycles, settings })
686 .build()
687 .map(|result: (Result<CreateResult, String>,)| (result.0,))
688 }
689
690 pub fn wallet_create_canister128(
692 &self,
693 cycles: u128,
694 controllers: Option<Vec<Principal>>,
695 compute_allocation: Option<ComputeAllocation>,
696 memory_allocation: Option<MemoryAllocation>,
697 freezing_threshold: Option<FreezingThreshold>,
698 ) -> impl 'agent + AsyncCall<Value = (Result<CreateResult, String>,)> {
699 #[derive(CandidType)]
700 struct In {
701 cycles: u128,
702 settings: CanisterSettings,
703 }
704
705 let settings = CanisterSettings {
706 controllers,
707 compute_allocation: compute_allocation.map(u8::from).map(Nat::from),
708 memory_allocation: memory_allocation.map(u64::from).map(Nat::from),
709 freezing_threshold: freezing_threshold.map(u64::from).map(Nat::from),
710 reserved_cycles_limit: None,
711 wasm_memory_limit: None,
712 wasm_memory_threshold: None,
713 log_visibility: None,
714 log_memory_limit: None,
715 environment_variables: None,
716 };
717
718 self.update("wallet_create_canister128")
719 .with_arg(In { cycles, settings })
720 .build()
721 .map(|result: (Result<CreateResult, String>,)| (result.0,))
722 }
723
724 pub async fn wallet_create_canister(
734 &self,
735 cycles: u128,
736 controllers: Option<Vec<Principal>>,
737 compute_allocation: Option<ComputeAllocation>,
738 memory_allocation: Option<MemoryAllocation>,
739 freezing_threshold: Option<FreezingThreshold>,
740 ) -> Result<CreateResult, AgentError> {
741 if self.version_supports_u128_cycles() {
742 self.wallet_create_canister128(
743 cycles,
744 controllers,
745 compute_allocation,
746 memory_allocation,
747 freezing_threshold,
748 )
749 .call_and_wait()
750 .await?
751 } else {
752 let cycles = u64::try_from(cycles).map_err(|_| {
753 AgentError::WalletUpgradeRequired(
754 "The installed wallet does not support cycle counts >2^64-1.".to_string(),
755 )
756 })?;
757 if self.version_supports_multiple_controllers() {
758 self.wallet_create_canister64_v2(
759 cycles,
760 controllers,
761 compute_allocation,
762 memory_allocation,
763 freezing_threshold,
764 )
765 .call_and_wait()
766 .await?
767 } else {
768 let controller: Option<Principal> = match &controllers {
769 Some(c) if c.len() == 1 => {
770 let first: Option<&Principal> = c.first();
771 let first: Principal = *first.unwrap();
772 Ok(Some(first))
773 }
774 Some(_) => Err(AgentError::WalletUpgradeRequired(
775 "The installed wallet does not support multiple controllers.".to_string(),
776 )),
777 None => Ok(None),
778 }?;
779 self.wallet_create_canister64_v1(
780 cycles,
781 controller,
782 compute_allocation,
783 memory_allocation,
784 freezing_threshold,
785 )
786 .call_and_wait()
787 .await?
788 }
789 }
790 .0
791 .map_err(AgentError::WalletError)
792 }
793
794 pub fn wallet_create_wallet64_v1(
796 &self,
797 cycles: u64,
798 controller: Option<Principal>,
799 compute_allocation: Option<ComputeAllocation>,
800 memory_allocation: Option<MemoryAllocation>,
801 freezing_threshold: Option<FreezingThreshold>,
802 ) -> impl 'agent + AsyncCall<Value = (Result<CreateResult, String>,)> {
803 #[derive(CandidType)]
804 struct In {
805 cycles: u64,
806 settings: CanisterSettingsV1,
807 }
808
809 let settings = CanisterSettingsV1 {
810 controller,
811 compute_allocation: compute_allocation.map(u8::from).map(Nat::from),
812 memory_allocation: memory_allocation.map(u64::from).map(Nat::from),
813 freezing_threshold: freezing_threshold.map(u64::from).map(Nat::from),
814 };
815
816 self.update("wallet_create_wallet")
817 .with_arg(In { cycles, settings })
818 .build()
819 .map(|result: (Result<CreateResult, String>,)| (result.0,))
820 }
821
822 pub fn wallet_create_wallet64_v2(
824 &self,
825 cycles: u64,
826 controllers: Option<Vec<Principal>>,
827 compute_allocation: Option<ComputeAllocation>,
828 memory_allocation: Option<MemoryAllocation>,
829 freezing_threshold: Option<FreezingThreshold>,
830 ) -> impl 'agent + AsyncCall<Value = (Result<CreateResult, String>,)> {
831 #[derive(CandidType)]
832 struct In {
833 cycles: u64,
834 settings: CanisterSettings,
835 }
836
837 let settings = CanisterSettings {
838 controllers,
839 compute_allocation: compute_allocation.map(u8::from).map(Nat::from),
840 memory_allocation: memory_allocation.map(u64::from).map(Nat::from),
841 freezing_threshold: freezing_threshold.map(u64::from).map(Nat::from),
842 reserved_cycles_limit: None,
843 wasm_memory_limit: None,
844 wasm_memory_threshold: None,
845 log_visibility: None,
846 log_memory_limit: None,
847 environment_variables: None,
848 };
849
850 self.update("wallet_create_wallet")
851 .with_arg(In { cycles, settings })
852 .build()
853 .map(|result: (Result<CreateResult, String>,)| (result.0,))
854 }
855
856 pub fn wallet_create_wallet128(
858 &self,
859 cycles: u128,
860 controllers: Option<Vec<Principal>>,
861 compute_allocation: Option<ComputeAllocation>,
862 memory_allocation: Option<MemoryAllocation>,
863 freezing_threshold: Option<FreezingThreshold>,
864 ) -> impl 'agent + AsyncCall<Value = (Result<CreateResult, String>,)> {
865 #[derive(CandidType)]
866 struct In {
867 cycles: u128,
868 settings: CanisterSettings,
869 }
870
871 let settings = CanisterSettings {
872 controllers,
873 compute_allocation: compute_allocation.map(u8::from).map(Nat::from),
874 memory_allocation: memory_allocation.map(u64::from).map(Nat::from),
875 freezing_threshold: freezing_threshold.map(u64::from).map(Nat::from),
876 reserved_cycles_limit: None,
877 wasm_memory_limit: None,
878 wasm_memory_threshold: None,
879 log_visibility: None,
880 log_memory_limit: None,
881 environment_variables: None,
882 };
883
884 self.update("wallet_create_wallet128")
885 .with_arg(In { cycles, settings })
886 .build()
887 .map(|result: (Result<CreateResult, String>,)| (result.0,))
888 }
889
890 pub async fn wallet_create_wallet(
892 &self,
893 cycles: u128,
894 controllers: Option<Vec<Principal>>,
895 compute_allocation: Option<ComputeAllocation>,
896 memory_allocation: Option<MemoryAllocation>,
897 freezing_threshold: Option<FreezingThreshold>,
898 ) -> Result<CreateResult, AgentError> {
899 if self.version_supports_u128_cycles() {
900 self.wallet_create_wallet128(
901 cycles,
902 controllers,
903 compute_allocation,
904 memory_allocation,
905 freezing_threshold,
906 )
907 .call_and_wait()
908 .await?
909 } else {
910 let cycles = u64::try_from(cycles).map_err(|_| {
911 AgentError::WalletUpgradeRequired(
912 "The installed wallet does not support cycle counts >2^64-1.".to_string(),
913 )
914 })?;
915 if self.version_supports_multiple_controllers() {
916 self.wallet_create_wallet64_v2(
917 cycles,
918 controllers,
919 compute_allocation,
920 memory_allocation,
921 freezing_threshold,
922 )
923 .call_and_wait()
924 .await?
925 } else {
926 let controller: Option<Principal> = match &controllers {
927 Some(c) if c.len() == 1 => Ok(Some(c[0])),
928 Some(_) => Err(AgentError::WalletUpgradeRequired(
929 "The installed wallet does not support multiple controllers.".to_string(),
930 )),
931 None => Ok(None),
932 }?;
933 self.wallet_create_wallet64_v1(
934 cycles,
935 controller,
936 compute_allocation,
937 memory_allocation,
938 freezing_threshold,
939 )
940 .call_and_wait()
941 .await?
942 }
943 }
944 .0
945 .map_err(AgentError::WalletError)
946 }
947
948 pub fn wallet_store_wallet_wasm(
951 &self,
952 wasm_module: Vec<u8>,
953 ) -> impl 'agent + AsyncCall<Value = ()> {
954 #[derive(CandidType, Deserialize)]
955 struct In {
956 #[serde(with = "serde_bytes")]
957 wasm_module: Vec<u8>,
958 }
959 self.update("wallet_store_wallet_wasm")
960 .with_arg(In { wasm_module })
961 .build()
962 }
963
964 pub fn add_address(&self, address: AddressEntry) -> impl 'agent + AsyncCall<Value = ()> {
966 self.update("add_address").with_arg(address).build()
967 }
968
969 pub fn list_addresses(&self) -> impl 'agent + SyncCall<Value = (Vec<AddressEntry>,)> {
971 self.query("list_addresses").build()
972 }
973
974 pub fn remove_address(&self, principal: Principal) -> impl 'agent + AsyncCall<Value = ()> {
976 self.update("remove_address").with_arg(principal).build()
977 }
978
979 pub fn get_events64(
981 &self,
982 from: Option<u32>,
983 to: Option<u32>,
984 ) -> impl 'agent + SyncCall<Value = (Vec<Event<u64>>,)> {
985 #[derive(CandidType)]
986 struct In {
987 from: Option<u32>,
988 to: Option<u32>,
989 }
990
991 let arg = if from.is_none() && to.is_none() {
992 None
993 } else {
994 Some(In { from, to })
995 };
996
997 self.query("get_events").with_arg(arg).build()
998 }
999
1000 pub fn get_events128(
1002 &self,
1003 from: Option<u32>,
1004 to: Option<u32>,
1005 ) -> impl 'agent + SyncCall<Value = (Vec<Event>,)> {
1006 #[derive(CandidType)]
1007 struct In {
1008 from: Option<u32>,
1009 to: Option<u32>,
1010 }
1011 let arg = if from.is_none() && to.is_none() {
1012 None
1013 } else {
1014 Some(In { from, to })
1015 };
1016 self.query("get_events128").with_arg(arg).build()
1017 }
1018
1019 pub async fn get_events(
1021 &self,
1022 from: Option<u32>,
1023 to: Option<u32>,
1024 ) -> Result<Vec<Event>, AgentError> {
1025 if self.version_supports_u128_cycles() {
1026 self.get_events128(from, to)
1027 .call()
1028 .await
1029 .map(|(events,)| events)
1030 } else {
1031 self.get_events64(from, to)
1032 .call()
1033 .await
1034 .map(|(events,)| events.into_iter().map(Event::into).collect())
1035 }
1036 }
1037
1038 pub fn call64<'canister, Out, M: Into<String>>(
1041 &'canister self,
1042 destination: Principal,
1043 method_name: M,
1044 arg: Argument,
1045 amount: u64,
1046 ) -> CallForwarder<'agent, 'canister, Out>
1047 where
1048 Out: for<'de> ArgumentDecoder<'de> + Send + Sync,
1049 {
1050 CallForwarder {
1051 wallet: self,
1052 destination,
1053 method_name: method_name.into(),
1054 amount: amount.into(),
1055 arg,
1056 phantom_out: std::marker::PhantomData,
1057 u128: false,
1058 }
1059 }
1060
1061 pub fn call128<'canister, Out, M: Into<String>>(
1064 &'canister self,
1065 destination: Principal,
1066 method_name: M,
1067 arg: Argument,
1068 amount: u128,
1069 ) -> CallForwarder<'agent, 'canister, Out>
1070 where
1071 Out: for<'de> ArgumentDecoder<'de> + Send + Sync,
1072 {
1073 CallForwarder {
1074 wallet: self,
1075 destination,
1076 method_name: method_name.into(),
1077 amount,
1078 arg,
1079 phantom_out: std::marker::PhantomData,
1080 u128: true,
1081 }
1082 }
1083
1084 pub fn call<'canister, Out, M: Into<String>>(
1087 &'canister self,
1088 destination: Principal,
1089 method_name: M,
1090 arg: Argument,
1091 amount: u128,
1092 ) -> CallForwarder<'agent, 'canister, Out>
1093 where
1094 Out: for<'de> ArgumentDecoder<'de> + Send + Sync,
1095 {
1096 CallForwarder {
1097 wallet: self,
1098 destination,
1099 method_name: method_name.into(),
1100 amount,
1101 arg,
1102 phantom_out: std::marker::PhantomData,
1103 u128: self.version_supports_u128_cycles(),
1104 }
1105 }
1106
1107 pub fn list_managed_canisters(
1109 &self,
1110 from: Option<u32>,
1111 to: Option<u32>,
1112 ) -> impl 'agent + SyncCall<Value = (Vec<ManagedCanisterInfo>, u32)> {
1113 #[derive(CandidType)]
1114 struct In {
1115 from: Option<u32>,
1116 to: Option<u32>,
1117 }
1118 self.query("list_managed_canisters")
1119 .with_arg((In { from, to },))
1120 .build()
1121 }
1122
1123 pub fn get_managed_canister_events64(
1125 &self,
1126 canister: Principal,
1127 from: Option<u32>,
1128 to: Option<u32>,
1129 ) -> impl 'agent + SyncCall<Value = (Option<Vec<ManagedCanisterEvent<u64>>>,)> {
1130 #[derive(CandidType)]
1131 struct In {
1132 canister: Principal,
1133 from: Option<u32>,
1134 to: Option<u32>,
1135 }
1136 self.query("get_managed_canister_events")
1137 .with_arg((In { canister, from, to },))
1138 .build()
1139 }
1140
1141 pub fn get_managed_canister_events128(
1143 &self,
1144 canister: Principal,
1145 from: Option<u32>,
1146 to: Option<u32>,
1147 ) -> impl 'agent + SyncCall<Value = (Option<Vec<ManagedCanisterEvent>>,)> {
1148 #[derive(CandidType)]
1149 struct In {
1150 canister: Principal,
1151 from: Option<u32>,
1152 to: Option<u32>,
1153 }
1154 self.query("get_managed_canister_events128")
1155 .with_arg((In { canister, from, to },))
1156 .build()
1157 }
1158
1159 pub async fn get_managed_canister_events(
1161 &self,
1162 canister: Principal,
1163 from: Option<u32>,
1164 to: Option<u32>,
1165 ) -> Result<Option<Vec<ManagedCanisterEvent>>, AgentError> {
1166 if self.version_supports_u128_cycles() {
1167 self.get_managed_canister_events128(canister, from, to)
1168 .call()
1169 .await
1170 .map(|(events,)| events)
1171 } else {
1172 self.get_managed_canister_events64(canister, from, to)
1173 .call()
1174 .await
1175 .map(|(events,)| {
1176 events
1177 .map(|events| events.into_iter().map(ManagedCanisterEvent::into).collect())
1178 })
1179 }
1180 }
1181
1182 pub fn version_supports_multiple_controllers(&self) -> bool {
1184 static CONTROLLERS: Lazy<VersionReq> = Lazy::new(|| VersionReq::parse(">=0.2.0").unwrap());
1185 CONTROLLERS.matches(&self.version)
1186 }
1187
1188 pub fn version_supports_u128_cycles(&self) -> bool {
1190 static U128_CYCLES: Lazy<VersionReq> = Lazy::new(|| VersionReq::parse(">=0.3.0").unwrap());
1191 U128_CYCLES.matches(&self.version)
1192 }
1193}