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 environment_variables: None,
681 };
682
683 self.update("wallet_create_canister")
684 .with_arg(In { cycles, settings })
685 .build()
686 .map(|result: (Result<CreateResult, String>,)| (result.0,))
687 }
688
689 pub fn wallet_create_canister128(
691 &self,
692 cycles: u128,
693 controllers: Option<Vec<Principal>>,
694 compute_allocation: Option<ComputeAllocation>,
695 memory_allocation: Option<MemoryAllocation>,
696 freezing_threshold: Option<FreezingThreshold>,
697 ) -> impl 'agent + AsyncCall<Value = (Result<CreateResult, String>,)> {
698 #[derive(CandidType)]
699 struct In {
700 cycles: u128,
701 settings: CanisterSettings,
702 }
703
704 let settings = CanisterSettings {
705 controllers,
706 compute_allocation: compute_allocation.map(u8::from).map(Nat::from),
707 memory_allocation: memory_allocation.map(u64::from).map(Nat::from),
708 freezing_threshold: freezing_threshold.map(u64::from).map(Nat::from),
709 reserved_cycles_limit: None,
710 wasm_memory_limit: None,
711 wasm_memory_threshold: None,
712 log_visibility: None,
713 environment_variables: None,
714 };
715
716 self.update("wallet_create_canister128")
717 .with_arg(In { cycles, settings })
718 .build()
719 .map(|result: (Result<CreateResult, String>,)| (result.0,))
720 }
721
722 pub async fn wallet_create_canister(
732 &self,
733 cycles: u128,
734 controllers: Option<Vec<Principal>>,
735 compute_allocation: Option<ComputeAllocation>,
736 memory_allocation: Option<MemoryAllocation>,
737 freezing_threshold: Option<FreezingThreshold>,
738 ) -> Result<CreateResult, AgentError> {
739 if self.version_supports_u128_cycles() {
740 self.wallet_create_canister128(
741 cycles,
742 controllers,
743 compute_allocation,
744 memory_allocation,
745 freezing_threshold,
746 )
747 .call_and_wait()
748 .await?
749 } else {
750 let cycles = u64::try_from(cycles).map_err(|_| {
751 AgentError::WalletUpgradeRequired(
752 "The installed wallet does not support cycle counts >2^64-1.".to_string(),
753 )
754 })?;
755 if self.version_supports_multiple_controllers() {
756 self.wallet_create_canister64_v2(
757 cycles,
758 controllers,
759 compute_allocation,
760 memory_allocation,
761 freezing_threshold,
762 )
763 .call_and_wait()
764 .await?
765 } else {
766 let controller: Option<Principal> = match &controllers {
767 Some(c) if c.len() == 1 => {
768 let first: Option<&Principal> = c.first();
769 let first: Principal = *first.unwrap();
770 Ok(Some(first))
771 }
772 Some(_) => Err(AgentError::WalletUpgradeRequired(
773 "The installed wallet does not support multiple controllers.".to_string(),
774 )),
775 None => Ok(None),
776 }?;
777 self.wallet_create_canister64_v1(
778 cycles,
779 controller,
780 compute_allocation,
781 memory_allocation,
782 freezing_threshold,
783 )
784 .call_and_wait()
785 .await?
786 }
787 }
788 .0
789 .map_err(AgentError::WalletError)
790 }
791
792 pub fn wallet_create_wallet64_v1(
794 &self,
795 cycles: u64,
796 controller: Option<Principal>,
797 compute_allocation: Option<ComputeAllocation>,
798 memory_allocation: Option<MemoryAllocation>,
799 freezing_threshold: Option<FreezingThreshold>,
800 ) -> impl 'agent + AsyncCall<Value = (Result<CreateResult, String>,)> {
801 #[derive(CandidType)]
802 struct In {
803 cycles: u64,
804 settings: CanisterSettingsV1,
805 }
806
807 let settings = CanisterSettingsV1 {
808 controller,
809 compute_allocation: compute_allocation.map(u8::from).map(Nat::from),
810 memory_allocation: memory_allocation.map(u64::from).map(Nat::from),
811 freezing_threshold: freezing_threshold.map(u64::from).map(Nat::from),
812 };
813
814 self.update("wallet_create_wallet")
815 .with_arg(In { cycles, settings })
816 .build()
817 .map(|result: (Result<CreateResult, String>,)| (result.0,))
818 }
819
820 pub fn wallet_create_wallet64_v2(
822 &self,
823 cycles: u64,
824 controllers: Option<Vec<Principal>>,
825 compute_allocation: Option<ComputeAllocation>,
826 memory_allocation: Option<MemoryAllocation>,
827 freezing_threshold: Option<FreezingThreshold>,
828 ) -> impl 'agent + AsyncCall<Value = (Result<CreateResult, String>,)> {
829 #[derive(CandidType)]
830 struct In {
831 cycles: u64,
832 settings: CanisterSettings,
833 }
834
835 let settings = CanisterSettings {
836 controllers,
837 compute_allocation: compute_allocation.map(u8::from).map(Nat::from),
838 memory_allocation: memory_allocation.map(u64::from).map(Nat::from),
839 freezing_threshold: freezing_threshold.map(u64::from).map(Nat::from),
840 reserved_cycles_limit: None,
841 wasm_memory_limit: None,
842 wasm_memory_threshold: None,
843 log_visibility: None,
844 environment_variables: None,
845 };
846
847 self.update("wallet_create_wallet")
848 .with_arg(In { cycles, settings })
849 .build()
850 .map(|result: (Result<CreateResult, String>,)| (result.0,))
851 }
852
853 pub fn wallet_create_wallet128(
855 &self,
856 cycles: u128,
857 controllers: Option<Vec<Principal>>,
858 compute_allocation: Option<ComputeAllocation>,
859 memory_allocation: Option<MemoryAllocation>,
860 freezing_threshold: Option<FreezingThreshold>,
861 ) -> impl 'agent + AsyncCall<Value = (Result<CreateResult, String>,)> {
862 #[derive(CandidType)]
863 struct In {
864 cycles: u128,
865 settings: CanisterSettings,
866 }
867
868 let settings = CanisterSettings {
869 controllers,
870 compute_allocation: compute_allocation.map(u8::from).map(Nat::from),
871 memory_allocation: memory_allocation.map(u64::from).map(Nat::from),
872 freezing_threshold: freezing_threshold.map(u64::from).map(Nat::from),
873 reserved_cycles_limit: None,
874 wasm_memory_limit: None,
875 wasm_memory_threshold: None,
876 log_visibility: None,
877 environment_variables: None,
878 };
879
880 self.update("wallet_create_wallet128")
881 .with_arg(In { cycles, settings })
882 .build()
883 .map(|result: (Result<CreateResult, String>,)| (result.0,))
884 }
885
886 pub async fn wallet_create_wallet(
888 &self,
889 cycles: u128,
890 controllers: Option<Vec<Principal>>,
891 compute_allocation: Option<ComputeAllocation>,
892 memory_allocation: Option<MemoryAllocation>,
893 freezing_threshold: Option<FreezingThreshold>,
894 ) -> Result<CreateResult, AgentError> {
895 if self.version_supports_u128_cycles() {
896 self.wallet_create_wallet128(
897 cycles,
898 controllers,
899 compute_allocation,
900 memory_allocation,
901 freezing_threshold,
902 )
903 .call_and_wait()
904 .await?
905 } else {
906 let cycles = u64::try_from(cycles).map_err(|_| {
907 AgentError::WalletUpgradeRequired(
908 "The installed wallet does not support cycle counts >2^64-1.".to_string(),
909 )
910 })?;
911 if self.version_supports_multiple_controllers() {
912 self.wallet_create_wallet64_v2(
913 cycles,
914 controllers,
915 compute_allocation,
916 memory_allocation,
917 freezing_threshold,
918 )
919 .call_and_wait()
920 .await?
921 } else {
922 let controller: Option<Principal> = match &controllers {
923 Some(c) if c.len() == 1 => Ok(Some(c[0])),
924 Some(_) => Err(AgentError::WalletUpgradeRequired(
925 "The installed wallet does not support multiple controllers.".to_string(),
926 )),
927 None => Ok(None),
928 }?;
929 self.wallet_create_wallet64_v1(
930 cycles,
931 controller,
932 compute_allocation,
933 memory_allocation,
934 freezing_threshold,
935 )
936 .call_and_wait()
937 .await?
938 }
939 }
940 .0
941 .map_err(AgentError::WalletError)
942 }
943
944 pub fn wallet_store_wallet_wasm(
947 &self,
948 wasm_module: Vec<u8>,
949 ) -> impl 'agent + AsyncCall<Value = ()> {
950 #[derive(CandidType, Deserialize)]
951 struct In {
952 #[serde(with = "serde_bytes")]
953 wasm_module: Vec<u8>,
954 }
955 self.update("wallet_store_wallet_wasm")
956 .with_arg(In { wasm_module })
957 .build()
958 }
959
960 pub fn add_address(&self, address: AddressEntry) -> impl 'agent + AsyncCall<Value = ()> {
962 self.update("add_address").with_arg(address).build()
963 }
964
965 pub fn list_addresses(&self) -> impl 'agent + SyncCall<Value = (Vec<AddressEntry>,)> {
967 self.query("list_addresses").build()
968 }
969
970 pub fn remove_address(&self, principal: Principal) -> impl 'agent + AsyncCall<Value = ()> {
972 self.update("remove_address").with_arg(principal).build()
973 }
974
975 pub fn get_events64(
977 &self,
978 from: Option<u32>,
979 to: Option<u32>,
980 ) -> impl 'agent + SyncCall<Value = (Vec<Event<u64>>,)> {
981 #[derive(CandidType)]
982 struct In {
983 from: Option<u32>,
984 to: Option<u32>,
985 }
986
987 let arg = if from.is_none() && to.is_none() {
988 None
989 } else {
990 Some(In { from, to })
991 };
992
993 self.query("get_events").with_arg(arg).build()
994 }
995
996 pub fn get_events128(
998 &self,
999 from: Option<u32>,
1000 to: Option<u32>,
1001 ) -> impl 'agent + SyncCall<Value = (Vec<Event>,)> {
1002 #[derive(CandidType)]
1003 struct In {
1004 from: Option<u32>,
1005 to: Option<u32>,
1006 }
1007 let arg = if from.is_none() && to.is_none() {
1008 None
1009 } else {
1010 Some(In { from, to })
1011 };
1012 self.query("get_events128").with_arg(arg).build()
1013 }
1014
1015 pub async fn get_events(
1017 &self,
1018 from: Option<u32>,
1019 to: Option<u32>,
1020 ) -> Result<Vec<Event>, AgentError> {
1021 if self.version_supports_u128_cycles() {
1022 self.get_events128(from, to)
1023 .call()
1024 .await
1025 .map(|(events,)| events)
1026 } else {
1027 self.get_events64(from, to)
1028 .call()
1029 .await
1030 .map(|(events,)| events.into_iter().map(Event::into).collect())
1031 }
1032 }
1033
1034 pub fn call64<'canister, Out, M: Into<String>>(
1037 &'canister self,
1038 destination: Principal,
1039 method_name: M,
1040 arg: Argument,
1041 amount: u64,
1042 ) -> CallForwarder<'agent, 'canister, Out>
1043 where
1044 Out: for<'de> ArgumentDecoder<'de> + Send + Sync,
1045 {
1046 CallForwarder {
1047 wallet: self,
1048 destination,
1049 method_name: method_name.into(),
1050 amount: amount.into(),
1051 arg,
1052 phantom_out: std::marker::PhantomData,
1053 u128: false,
1054 }
1055 }
1056
1057 pub fn call128<'canister, Out, M: Into<String>>(
1060 &'canister self,
1061 destination: Principal,
1062 method_name: M,
1063 arg: Argument,
1064 amount: u128,
1065 ) -> CallForwarder<'agent, 'canister, Out>
1066 where
1067 Out: for<'de> ArgumentDecoder<'de> + Send + Sync,
1068 {
1069 CallForwarder {
1070 wallet: self,
1071 destination,
1072 method_name: method_name.into(),
1073 amount,
1074 arg,
1075 phantom_out: std::marker::PhantomData,
1076 u128: true,
1077 }
1078 }
1079
1080 pub fn call<'canister, Out, M: Into<String>>(
1083 &'canister self,
1084 destination: Principal,
1085 method_name: M,
1086 arg: Argument,
1087 amount: u128,
1088 ) -> CallForwarder<'agent, 'canister, Out>
1089 where
1090 Out: for<'de> ArgumentDecoder<'de> + Send + Sync,
1091 {
1092 CallForwarder {
1093 wallet: self,
1094 destination,
1095 method_name: method_name.into(),
1096 amount,
1097 arg,
1098 phantom_out: std::marker::PhantomData,
1099 u128: self.version_supports_u128_cycles(),
1100 }
1101 }
1102
1103 pub fn list_managed_canisters(
1105 &self,
1106 from: Option<u32>,
1107 to: Option<u32>,
1108 ) -> impl 'agent + SyncCall<Value = (Vec<ManagedCanisterInfo>, u32)> {
1109 #[derive(CandidType)]
1110 struct In {
1111 from: Option<u32>,
1112 to: Option<u32>,
1113 }
1114 self.query("list_managed_canisters")
1115 .with_arg((In { from, to },))
1116 .build()
1117 }
1118
1119 pub fn get_managed_canister_events64(
1121 &self,
1122 canister: Principal,
1123 from: Option<u32>,
1124 to: Option<u32>,
1125 ) -> impl 'agent + SyncCall<Value = (Option<Vec<ManagedCanisterEvent<u64>>>,)> {
1126 #[derive(CandidType)]
1127 struct In {
1128 canister: Principal,
1129 from: Option<u32>,
1130 to: Option<u32>,
1131 }
1132 self.query("get_managed_canister_events")
1133 .with_arg((In { canister, from, to },))
1134 .build()
1135 }
1136
1137 pub fn get_managed_canister_events128(
1139 &self,
1140 canister: Principal,
1141 from: Option<u32>,
1142 to: Option<u32>,
1143 ) -> impl 'agent + SyncCall<Value = (Option<Vec<ManagedCanisterEvent>>,)> {
1144 #[derive(CandidType)]
1145 struct In {
1146 canister: Principal,
1147 from: Option<u32>,
1148 to: Option<u32>,
1149 }
1150 self.query("get_managed_canister_events128")
1151 .with_arg((In { canister, from, to },))
1152 .build()
1153 }
1154
1155 pub async fn get_managed_canister_events(
1157 &self,
1158 canister: Principal,
1159 from: Option<u32>,
1160 to: Option<u32>,
1161 ) -> Result<Option<Vec<ManagedCanisterEvent>>, AgentError> {
1162 if self.version_supports_u128_cycles() {
1163 self.get_managed_canister_events128(canister, from, to)
1164 .call()
1165 .await
1166 .map(|(events,)| events)
1167 } else {
1168 self.get_managed_canister_events64(canister, from, to)
1169 .call()
1170 .await
1171 .map(|(events,)| {
1172 events
1173 .map(|events| events.into_iter().map(ManagedCanisterEvent::into).collect())
1174 })
1175 }
1176 }
1177
1178 pub fn version_supports_multiple_controllers(&self) -> bool {
1180 static CONTROLLERS: Lazy<VersionReq> = Lazy::new(|| VersionReq::parse(">=0.2.0").unwrap());
1181 CONTROLLERS.matches(&self.version)
1182 }
1183
1184 pub fn version_supports_u128_cycles(&self) -> bool {
1186 static U128_CYCLES: Lazy<VersionReq> = Lazy::new(|| VersionReq::parse(">=0.3.0").unwrap());
1187 U128_CYCLES.matches(&self.version)
1188 }
1189}