audit_trails/core/create/
transactions.rs1use async_trait::async_trait;
5use iota_interaction::OptionalSync;
6use iota_interaction::rpc_types::{IotaTransactionBlockEffects, IotaTransactionBlockEvents};
7use iota_interaction::types::base_types::{IotaAddress, ObjectID};
8use iota_interaction::types::transaction::ProgrammableTransaction;
9use product_common::core_client::CoreClientReadOnly;
10use product_common::transaction::transaction_builder::Transaction;
11use tokio::sync::OnceCell;
12
13use super::operations::{CreateOps, CreateTrailArgs};
14use crate::core::builder::AuditTrailBuilder;
15use crate::core::internal::trail as trail_reader;
16use crate::core::types::{AuditTrailCreated, Event, OnChainAuditTrail};
17use crate::error::Error;
18
19#[derive(Debug, Clone)]
21pub struct TrailCreated {
22 pub trail_id: ObjectID,
24 pub creator: IotaAddress,
26 pub timestamp: u64,
28}
29
30impl TrailCreated {
31 pub async fn fetch_audit_trail<C>(&self, client: &C) -> Result<OnChainAuditTrail, Error>
37 where
38 C: CoreClientReadOnly + OptionalSync,
39 {
40 trail_reader::get_audit_trail(self.trail_id, client).await
41 }
42}
43
44#[derive(Debug, Clone)]
55pub struct CreateTrail {
56 builder: AuditTrailBuilder,
57 cached_ptb: OnceCell<ProgrammableTransaction>,
58}
59
60impl CreateTrail {
61 pub fn new(builder: AuditTrailBuilder) -> Self {
63 Self {
64 builder,
65 cached_ptb: OnceCell::new(),
66 }
67 }
68
69 async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
70 where
71 C: CoreClientReadOnly + OptionalSync,
72 {
73 let AuditTrailBuilder {
74 admin,
75 initial_record,
76 locking_config,
77 trail_metadata,
78 updatable_metadata,
79 record_tags,
80 } = self.builder.clone();
81
82 let admin = admin.ok_or_else(|| {
83 Error::InvalidArgument(
84 "admin address is required; use `client.create_trail()` with signer or call `with_admin(...)`"
85 .to_string(),
86 )
87 })?;
88 let tf_package_id = client
89 .tf_components_package_id()
90 .expect("TfComponents package ID should be present for Audit Trail clients");
91
92 CreateOps::create_trail(CreateTrailArgs {
93 audit_trail_package_id: client.package_id(),
94 tf_components_package_id: tf_package_id,
95 admin,
96 initial_record,
97 locking_config,
98 trail_metadata,
99 updatable_metadata,
100 record_tags,
101 })
102 }
103}
104
105#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
106#[cfg_attr(feature = "send-sync", async_trait)]
107impl Transaction for CreateTrail {
108 type Error = Error;
109 type Output = TrailCreated;
110
111 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
112 where
113 C: CoreClientReadOnly + OptionalSync,
114 {
115 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
116 }
117
118 async fn apply_with_events<C>(
119 mut self,
120 _: &mut IotaTransactionBlockEffects,
121 events: &mut IotaTransactionBlockEvents,
122 _: &C,
123 ) -> Result<Self::Output, Self::Error>
124 where
125 C: CoreClientReadOnly + OptionalSync,
126 {
127 let event = events
128 .data
129 .iter()
130 .find_map(|data| serde_json::from_value::<Event<AuditTrailCreated>>(data.parsed_json.clone()).ok())
131 .ok_or_else(|| Error::UnexpectedApiResponse("AuditTrailCreated event not found".to_string()))?;
132
133 Ok(TrailCreated {
134 trail_id: event.data.trail_id,
135 creator: event.data.creator,
136 timestamp: event.data.timestamp,
137 })
138 }
139
140 async fn apply<C>(mut self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
141 where
142 C: CoreClientReadOnly + OptionalSync,
143 {
144 unreachable!()
145 }
146}