1use async_trait::async_trait;
10use iota_interaction::OptionalSync;
11use iota_interaction::rpc_types::{IotaTransactionBlockEffects, IotaTransactionBlockEvents};
12use iota_interaction::types::base_types::{IotaAddress, ObjectID};
13use iota_interaction::types::transaction::ProgrammableTransaction;
14use product_common::core_client::CoreClientReadOnly;
15use product_common::transaction::transaction_builder::Transaction;
16use tokio::sync::OnceCell;
17
18use super::operations::AccessOps;
19use crate::core::types::{
20 CapabilityDestroyed, CapabilityIssueOptions, CapabilityIssued, CapabilityRevoked, Event, PermissionSet,
21 RawRoleCreated, RawRoleDeleted, RawRoleUpdated, RevokedCapabilitiesCleanedUp, RoleCreated, RoleDeleted, RoleTags,
22 RoleUpdated,
23};
24use crate::error::Error;
25
26#[derive(Debug, Clone)]
37pub struct CreateRole {
38 trail_id: ObjectID,
39 owner: IotaAddress,
40 name: String,
41 permissions: PermissionSet,
42 role_tags: Option<RoleTags>,
43 selected_capability_id: Option<ObjectID>,
44 cached_ptb: OnceCell<ProgrammableTransaction>,
45}
46
47impl CreateRole {
48 pub fn new(
52 trail_id: ObjectID,
53 owner: IotaAddress,
54 name: String,
55 permissions: PermissionSet,
56 role_tags: Option<RoleTags>,
57 selected_capability_id: Option<ObjectID>,
58 ) -> Self {
59 Self {
60 trail_id,
61 owner,
62 name,
63 permissions,
64 role_tags,
65 selected_capability_id,
66 cached_ptb: OnceCell::new(),
67 }
68 }
69
70 async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
71 where
72 C: CoreClientReadOnly + OptionalSync,
73 {
74 AccessOps::create_role(
75 client,
76 self.trail_id,
77 self.owner,
78 self.name.clone(),
79 self.permissions.clone(),
80 self.role_tags.clone(),
81 self.selected_capability_id,
82 )
83 .await
84 }
85}
86
87#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
88#[cfg_attr(feature = "send-sync", async_trait)]
89impl Transaction for CreateRole {
90 type Error = Error;
91 type Output = RoleCreated;
92
93 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
94 where
95 C: CoreClientReadOnly + OptionalSync,
96 {
97 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
98 }
99
100 async fn apply_with_events<C>(
101 mut self,
102 _: &mut IotaTransactionBlockEffects,
103 events: &mut IotaTransactionBlockEvents,
104 _: &C,
105 ) -> Result<Self::Output, Self::Error>
106 where
107 C: CoreClientReadOnly + OptionalSync,
108 {
109 let event = events
110 .data
111 .iter()
112 .find_map(|data| bcs::from_bytes::<RawRoleCreated>(data.bcs.bytes()).ok().map(Into::into))
113 .ok_or_else(|| Error::UnexpectedApiResponse("RoleCreated event not found".to_string()))?;
114
115 Ok(event)
116 }
117
118 async fn apply<C>(mut self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
119 where
120 C: CoreClientReadOnly + OptionalSync,
121 {
122 unreachable!("RoleCreated output requires transaction events")
123 }
124}
125
126#[derive(Debug, Clone)]
135pub struct UpdateRole {
136 trail_id: ObjectID,
137 owner: IotaAddress,
138 name: String,
139 permissions: PermissionSet,
140 role_tags: Option<RoleTags>,
141 selected_capability_id: Option<ObjectID>,
142 cached_ptb: OnceCell<ProgrammableTransaction>,
143}
144
145impl UpdateRole {
146 pub fn new(
148 trail_id: ObjectID,
149 owner: IotaAddress,
150 name: String,
151 permissions: PermissionSet,
152 role_tags: Option<RoleTags>,
153 selected_capability_id: Option<ObjectID>,
154 ) -> Self {
155 Self {
156 trail_id,
157 owner,
158 name,
159 permissions,
160 role_tags,
161 selected_capability_id,
162 cached_ptb: OnceCell::new(),
163 }
164 }
165
166 async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
167 where
168 C: CoreClientReadOnly + OptionalSync,
169 {
170 AccessOps::update_role(
171 client,
172 self.trail_id,
173 self.owner,
174 self.name.clone(),
175 self.permissions.clone(),
176 self.role_tags.clone(),
177 self.selected_capability_id,
178 )
179 .await
180 }
181}
182
183#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
184#[cfg_attr(feature = "send-sync", async_trait)]
185impl Transaction for UpdateRole {
186 type Error = Error;
187 type Output = RoleUpdated;
188
189 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
190 where
191 C: CoreClientReadOnly + OptionalSync,
192 {
193 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
194 }
195
196 async fn apply_with_events<C>(
197 mut self,
198 _: &mut IotaTransactionBlockEffects,
199 events: &mut IotaTransactionBlockEvents,
200 _: &C,
201 ) -> Result<Self::Output, Self::Error>
202 where
203 C: CoreClientReadOnly + OptionalSync,
204 {
205 let event = events
206 .data
207 .iter()
208 .find_map(|data| bcs::from_bytes::<RawRoleUpdated>(data.bcs.bytes()).ok().map(Into::into))
209 .ok_or_else(|| Error::UnexpectedApiResponse("RoleUpdated event not found".to_string()))?;
210
211 Ok(event)
212 }
213
214 async fn apply<C>(mut self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
215 where
216 C: CoreClientReadOnly + OptionalSync,
217 {
218 unreachable!()
219 }
220}
221
222#[derive(Debug, Clone)]
230pub struct DeleteRole {
231 trail_id: ObjectID,
232 owner: IotaAddress,
233 name: String,
234 selected_capability_id: Option<ObjectID>,
235 cached_ptb: OnceCell<ProgrammableTransaction>,
236}
237
238impl DeleteRole {
239 pub fn new(trail_id: ObjectID, owner: IotaAddress, name: String, selected_capability_id: Option<ObjectID>) -> Self {
241 Self {
242 trail_id,
243 owner,
244 name,
245 selected_capability_id,
246 cached_ptb: OnceCell::new(),
247 }
248 }
249
250 async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
251 where
252 C: CoreClientReadOnly + OptionalSync,
253 {
254 AccessOps::delete_role(
255 client,
256 self.trail_id,
257 self.owner,
258 self.name.clone(),
259 self.selected_capability_id,
260 )
261 .await
262 }
263}
264
265#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
266#[cfg_attr(feature = "send-sync", async_trait)]
267impl Transaction for DeleteRole {
268 type Error = Error;
269 type Output = RoleDeleted;
270
271 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
272 where
273 C: CoreClientReadOnly + OptionalSync,
274 {
275 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
276 }
277
278 async fn apply_with_events<C>(
279 mut self,
280 _: &mut IotaTransactionBlockEffects,
281 events: &mut IotaTransactionBlockEvents,
282 _: &C,
283 ) -> Result<Self::Output, Self::Error>
284 where
285 C: CoreClientReadOnly + OptionalSync,
286 {
287 let event = events
288 .data
289 .iter()
290 .find_map(|data| bcs::from_bytes::<RawRoleDeleted>(data.bcs.bytes()).ok().map(Into::into))
291 .ok_or_else(|| Error::UnexpectedApiResponse("RoleDeleted event not found".to_string()))?;
292
293 Ok(event)
294 }
295
296 async fn apply<C>(mut self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
297 where
298 C: CoreClientReadOnly + OptionalSync,
299 {
300 unreachable!()
301 }
302}
303
304#[derive(Debug, Clone)]
312pub struct IssueCapability {
313 trail_id: ObjectID,
314 owner: IotaAddress,
315 role: String,
316 options: CapabilityIssueOptions,
317 selected_capability_id: Option<ObjectID>,
318 cached_ptb: OnceCell<ProgrammableTransaction>,
319}
320
321impl IssueCapability {
322 pub fn new(
324 trail_id: ObjectID,
325 owner: IotaAddress,
326 role: String,
327 options: CapabilityIssueOptions,
328 selected_capability_id: Option<ObjectID>,
329 ) -> Self {
330 Self {
331 trail_id,
332 owner,
333 role,
334 options,
335 selected_capability_id,
336 cached_ptb: OnceCell::new(),
337 }
338 }
339
340 async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
341 where
342 C: CoreClientReadOnly + OptionalSync,
343 {
344 AccessOps::issue_capability(
345 client,
346 self.trail_id,
347 self.owner,
348 self.role.clone(),
349 self.options.clone(),
350 self.selected_capability_id,
351 )
352 .await
353 }
354}
355
356#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
357#[cfg_attr(feature = "send-sync", async_trait)]
358impl Transaction for IssueCapability {
359 type Error = Error;
360 type Output = CapabilityIssued;
361
362 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
363 where
364 C: CoreClientReadOnly + OptionalSync,
365 {
366 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
367 }
368
369 async fn apply_with_events<C>(
370 mut self,
371 _: &mut IotaTransactionBlockEffects,
372 events: &mut IotaTransactionBlockEvents,
373 _: &C,
374 ) -> Result<Self::Output, Self::Error>
375 where
376 C: CoreClientReadOnly + OptionalSync,
377 {
378 let event = events
379 .data
380 .iter()
381 .find_map(|data| serde_json::from_value::<Event<CapabilityIssued>>(data.parsed_json.clone()).ok())
382 .ok_or_else(|| Error::UnexpectedApiResponse("CapabilityIssued event not found".to_string()))?;
383
384 Ok(event.data)
385 }
386
387 async fn apply<C>(mut self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
388 where
389 C: CoreClientReadOnly + OptionalSync,
390 {
391 unreachable!()
392 }
393}
394
395#[derive(Debug, Clone)]
403pub struct RevokeCapability {
404 trail_id: ObjectID,
405 owner: IotaAddress,
406 capability_id: ObjectID,
407 capability_valid_until: Option<u64>,
408 selected_capability_id: Option<ObjectID>,
409 cached_ptb: OnceCell<ProgrammableTransaction>,
410}
411
412impl RevokeCapability {
413 pub fn new(
415 trail_id: ObjectID,
416 owner: IotaAddress,
417 capability_id: ObjectID,
418 capability_valid_until: Option<u64>,
419 selected_capability_id: Option<ObjectID>,
420 ) -> Self {
421 Self {
422 trail_id,
423 owner,
424 capability_id,
425 capability_valid_until,
426 selected_capability_id,
427 cached_ptb: OnceCell::new(),
428 }
429 }
430
431 async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
432 where
433 C: CoreClientReadOnly + OptionalSync,
434 {
435 AccessOps::revoke_capability(
436 client,
437 self.trail_id,
438 self.owner,
439 self.capability_id,
440 self.capability_valid_until,
441 self.selected_capability_id,
442 )
443 .await
444 }
445}
446
447#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
448#[cfg_attr(feature = "send-sync", async_trait)]
449impl Transaction for RevokeCapability {
450 type Error = Error;
451 type Output = CapabilityRevoked;
452
453 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
454 where
455 C: CoreClientReadOnly + OptionalSync,
456 {
457 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
458 }
459
460 async fn apply_with_events<C>(
461 mut self,
462 _: &mut IotaTransactionBlockEffects,
463 events: &mut IotaTransactionBlockEvents,
464 _: &C,
465 ) -> Result<Self::Output, Self::Error>
466 where
467 C: CoreClientReadOnly + OptionalSync,
468 {
469 let event = events
470 .data
471 .iter()
472 .find_map(|data| serde_json::from_value::<Event<CapabilityRevoked>>(data.parsed_json.clone()).ok())
473 .ok_or_else(|| Error::UnexpectedApiResponse("CapabilityRevoked event not found".to_string()))?;
474
475 Ok(event.data)
476 }
477
478 async fn apply<C>(mut self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
479 where
480 C: CoreClientReadOnly + OptionalSync,
481 {
482 unreachable!()
483 }
484}
485
486#[derive(Debug, Clone)]
493pub struct DestroyCapability {
494 trail_id: ObjectID,
495 owner: IotaAddress,
496 capability_id: ObjectID,
497 selected_capability_id: Option<ObjectID>,
498 cached_ptb: OnceCell<ProgrammableTransaction>,
499}
500
501impl DestroyCapability {
502 pub fn new(
504 trail_id: ObjectID,
505 owner: IotaAddress,
506 capability_id: ObjectID,
507 selected_capability_id: Option<ObjectID>,
508 ) -> Self {
509 Self {
510 trail_id,
511 owner,
512 capability_id,
513 selected_capability_id,
514 cached_ptb: OnceCell::new(),
515 }
516 }
517
518 async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
519 where
520 C: CoreClientReadOnly + OptionalSync,
521 {
522 AccessOps::destroy_capability(
523 client,
524 self.trail_id,
525 self.owner,
526 self.capability_id,
527 self.selected_capability_id,
528 )
529 .await
530 }
531}
532
533#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
534#[cfg_attr(feature = "send-sync", async_trait)]
535impl Transaction for DestroyCapability {
536 type Error = Error;
537 type Output = CapabilityDestroyed;
538
539 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
540 where
541 C: CoreClientReadOnly + OptionalSync,
542 {
543 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
544 }
545
546 async fn apply_with_events<C>(
547 mut self,
548 _: &mut IotaTransactionBlockEffects,
549 events: &mut IotaTransactionBlockEvents,
550 _: &C,
551 ) -> Result<Self::Output, Self::Error>
552 where
553 C: CoreClientReadOnly + OptionalSync,
554 {
555 let event = events
556 .data
557 .iter()
558 .find_map(|data| serde_json::from_value::<Event<CapabilityDestroyed>>(data.parsed_json.clone()).ok())
559 .ok_or_else(|| Error::UnexpectedApiResponse("CapabilityDestroyed event not found".to_string()))?;
560
561 Ok(event.data)
562 }
563
564 async fn apply<C>(mut self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
565 where
566 C: CoreClientReadOnly + OptionalSync,
567 {
568 unreachable!()
569 }
570}
571
572#[derive(Debug, Clone)]
585pub struct DestroyInitialAdminCapability {
586 trail_id: ObjectID,
587 capability_id: ObjectID,
588 cached_ptb: OnceCell<ProgrammableTransaction>,
589}
590
591impl DestroyInitialAdminCapability {
592 pub fn new(trail_id: ObjectID, capability_id: ObjectID) -> Self {
594 Self {
595 trail_id,
596 capability_id,
597 cached_ptb: OnceCell::new(),
598 }
599 }
600
601 async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
602 where
603 C: CoreClientReadOnly + OptionalSync,
604 {
605 AccessOps::destroy_initial_admin_capability(client, self.trail_id, self.capability_id).await
606 }
607}
608
609#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
610#[cfg_attr(feature = "send-sync", async_trait)]
611impl Transaction for DestroyInitialAdminCapability {
612 type Error = Error;
613 type Output = CapabilityDestroyed;
614
615 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
616 where
617 C: CoreClientReadOnly + OptionalSync,
618 {
619 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
620 }
621
622 async fn apply_with_events<C>(
623 mut self,
624 _: &mut IotaTransactionBlockEffects,
625 events: &mut IotaTransactionBlockEvents,
626 _: &C,
627 ) -> Result<Self::Output, Self::Error>
628 where
629 C: CoreClientReadOnly + OptionalSync,
630 {
631 let event = events
632 .data
633 .iter()
634 .find_map(|data| serde_json::from_value::<Event<CapabilityDestroyed>>(data.parsed_json.clone()).ok())
635 .ok_or_else(|| Error::UnexpectedApiResponse("CapabilityDestroyed event not found".to_string()))?;
636
637 Ok(event.data)
638 }
639
640 async fn apply<C>(mut self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
641 where
642 C: CoreClientReadOnly + OptionalSync,
643 {
644 unreachable!()
645 }
646}
647
648#[derive(Debug, Clone)]
662pub struct RevokeInitialAdminCapability {
663 trail_id: ObjectID,
664 owner: IotaAddress,
665 capability_id: ObjectID,
666 capability_valid_until: Option<u64>,
667 selected_capability_id: Option<ObjectID>,
668 cached_ptb: OnceCell<ProgrammableTransaction>,
669}
670
671impl RevokeInitialAdminCapability {
672 pub fn new(
674 trail_id: ObjectID,
675 owner: IotaAddress,
676 capability_id: ObjectID,
677 capability_valid_until: Option<u64>,
678 selected_capability_id: Option<ObjectID>,
679 ) -> Self {
680 Self {
681 trail_id,
682 owner,
683 capability_id,
684 capability_valid_until,
685 selected_capability_id,
686 cached_ptb: OnceCell::new(),
687 }
688 }
689
690 async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
691 where
692 C: CoreClientReadOnly + OptionalSync,
693 {
694 AccessOps::revoke_initial_admin_capability(
695 client,
696 self.trail_id,
697 self.owner,
698 self.capability_id,
699 self.capability_valid_until,
700 self.selected_capability_id,
701 )
702 .await
703 }
704}
705
706#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
707#[cfg_attr(feature = "send-sync", async_trait)]
708impl Transaction for RevokeInitialAdminCapability {
709 type Error = Error;
710 type Output = CapabilityRevoked;
711
712 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
713 where
714 C: CoreClientReadOnly + OptionalSync,
715 {
716 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
717 }
718
719 async fn apply_with_events<C>(
720 mut self,
721 _: &mut IotaTransactionBlockEffects,
722 events: &mut IotaTransactionBlockEvents,
723 _: &C,
724 ) -> Result<Self::Output, Self::Error>
725 where
726 C: CoreClientReadOnly + OptionalSync,
727 {
728 let event = events
729 .data
730 .iter()
731 .find_map(|data| serde_json::from_value::<Event<CapabilityRevoked>>(data.parsed_json.clone()).ok())
732 .ok_or_else(|| Error::UnexpectedApiResponse("CapabilityRevoked event not found".to_string()))?;
733
734 Ok(event.data)
735 }
736
737 async fn apply<C>(mut self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
738 where
739 C: CoreClientReadOnly + OptionalSync,
740 {
741 unreachable!()
742 }
743}
744
745#[derive(Debug, Clone)]
757pub struct CleanupRevokedCapabilities {
758 trail_id: ObjectID,
759 owner: IotaAddress,
760 selected_capability_id: Option<ObjectID>,
761 cached_ptb: OnceCell<ProgrammableTransaction>,
762}
763
764impl CleanupRevokedCapabilities {
765 pub fn new(trail_id: ObjectID, owner: IotaAddress, selected_capability_id: Option<ObjectID>) -> Self {
767 Self {
768 trail_id,
769 owner,
770 selected_capability_id,
771 cached_ptb: OnceCell::new(),
772 }
773 }
774
775 async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
776 where
777 C: CoreClientReadOnly + OptionalSync,
778 {
779 AccessOps::cleanup_revoked_capabilities(client, self.trail_id, self.owner, self.selected_capability_id).await
780 }
781}
782
783#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
784#[cfg_attr(feature = "send-sync", async_trait)]
785impl Transaction for CleanupRevokedCapabilities {
786 type Error = Error;
787 type Output = RevokedCapabilitiesCleanedUp;
788
789 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
790 where
791 C: CoreClientReadOnly + OptionalSync,
792 {
793 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
794 }
795
796 async fn apply_with_events<C>(
797 self,
798 _: &mut IotaTransactionBlockEffects,
799 events: &mut IotaTransactionBlockEvents,
800 _: &C,
801 ) -> Result<Self::Output, Self::Error>
802 where
803 C: CoreClientReadOnly + OptionalSync,
804 {
805 let event = events
806 .data
807 .iter()
808 .find_map(|data| {
809 serde_json::from_value::<Event<RevokedCapabilitiesCleanedUp>>(data.parsed_json.clone()).ok()
810 })
811 .ok_or_else(|| Error::UnexpectedApiResponse("RevokedCapabilitiesCleanedUp event not found".to_string()))?;
812
813 Ok(event.data)
814 }
815
816 async fn apply<C>(self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
817 where
818 C: CoreClientReadOnly + OptionalSync,
819 {
820 unreachable!("RevokedCapabilitiesCleanedUp output requires transaction events")
821 }
822}