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