sui_indexer_alt_jsonrpc/api/transactions/
response.rs1use std::str::FromStr;
5use std::sync::Arc;
6
7use anyhow::Context as _;
8use move_core_types::annotated_value::MoveDatatypeLayout;
9use move_core_types::annotated_value::MoveTypeLayout;
10use sui_indexer_alt_reader::kv_loader::TransactionContents;
11use sui_indexer_alt_reader::objects::VersionedObjectKey;
12use sui_json_rpc_types::BalanceChange as SuiBalanceChange;
13use sui_json_rpc_types::ObjectChange as SuiObjectChange;
14use sui_json_rpc_types::SuiEvent;
15use sui_json_rpc_types::SuiTransactionBlock;
16use sui_json_rpc_types::SuiTransactionBlockData;
17use sui_json_rpc_types::SuiTransactionBlockEffects;
18use sui_json_rpc_types::SuiTransactionBlockEvents;
19use sui_json_rpc_types::SuiTransactionBlockResponse;
20use sui_json_rpc_types::SuiTransactionBlockResponseOptions;
21use sui_types::TypeTag;
22use sui_types::base_types::ObjectID;
23use sui_types::base_types::SequenceNumber;
24use sui_types::base_types::SuiAddress;
25use sui_types::digests::ObjectDigest;
26use sui_types::digests::TransactionDigest;
27use sui_types::effects::ObjectChange;
28use sui_types::effects::TransactionEffects;
29use sui_types::effects::TransactionEffectsAPI;
30use sui_types::object::Object;
31use sui_types::object::Owner;
32use sui_types::signature::GenericSignature;
33use sui_types::transaction::SenderSignedData;
34use sui_types::transaction::TransactionData;
35use sui_types::transaction::TransactionDataAPI;
36
37use crate::api::to_sui_object_change;
38use crate::api::transactions::error::Error;
39use crate::context::Context;
40use crate::error::RpcError;
41use crate::error::invalid_params;
42use crate::error::rpc_bail;
43
44pub(super) async fn transaction(
47 ctx: &Context,
48 digest: TransactionDigest,
49 options: &SuiTransactionBlockResponseOptions,
50) -> Result<SuiTransactionBlockResponse, RpcError<Error>> {
51 let tx = ctx
52 .kv_loader()
53 .load_one_transaction(digest)
54 .await
55 .context("Failed to fetch transaction from store")?
56 .ok_or_else(|| invalid_params(Error::NotFound(digest)))?;
57
58 let digest = tx.digest()?;
59
60 let mut response = SuiTransactionBlockResponse::new(digest);
61
62 response.timestamp_ms = tx.timestamp_ms();
63 response.checkpoint = tx.cp_sequence_number();
64
65 if options.show_input {
66 response.transaction = Some(input(ctx, &tx).await?);
67 }
68
69 if options.show_raw_input {
70 response.raw_transaction = raw_input(&tx)?;
71 }
72
73 if options.show_effects {
74 response.effects = Some(effects(&tx)?);
75 }
76
77 if options.show_raw_effects {
78 response.raw_effects = tx.raw_effects()?;
79 }
80
81 if options.show_events {
82 response.events = Some(events(ctx, digest, &tx).await?);
83 }
84
85 if options.show_balance_changes {
86 response.balance_changes = Some(balance_changes(&tx)?);
87 }
88
89 if options.show_object_changes {
90 response.object_changes = Some(object_changes(ctx, digest, &tx).await?);
91 }
92
93 Ok(response)
94}
95
96async fn input(
98 ctx: &Context,
99 tx: &TransactionContents,
100) -> Result<SuiTransactionBlock, RpcError<Error>> {
101 let data: TransactionData = tx.data()?;
102 let tx_signatures: Vec<GenericSignature> = tx.signatures()?;
103
104 Ok(SuiTransactionBlock {
105 data: SuiTransactionBlockData::try_from_with_package_resolver(data, ctx.package_resolver())
106 .await
107 .context("Failed to resolve types in transaction data")?,
108 tx_signatures,
109 })
110}
111
112fn raw_input(tx: &TransactionContents) -> Result<Vec<u8>, RpcError<Error>> {
114 let data = SenderSignedData::new(tx.data()?, tx.signatures()?);
115 Ok(bcs::to_bytes(&data).context("Failed to serialize transaction")?)
116}
117
118fn effects(tx: &TransactionContents) -> Result<SuiTransactionBlockEffects, RpcError<Error>> {
120 let effects: TransactionEffects = tx.effects()?;
121 Ok(effects
122 .try_into()
123 .context("Failed to convert Effects into response")?)
124}
125
126async fn events(
128 ctx: &Context,
129 digest: TransactionDigest,
130 tx: &TransactionContents,
131) -> Result<SuiTransactionBlockEvents, RpcError<Error>> {
132 let events = tx.events()?;
133 let mut sui_events = Vec::with_capacity(events.len());
134
135 for (ix, event) in events.into_iter().enumerate() {
136 let layout = match ctx
137 .package_resolver()
138 .type_layout(event.type_.clone().into())
139 .await
140 .with_context(|| {
141 format!(
142 "Failed to resolve layout for {}",
143 event.type_.to_canonical_display(true)
144 )
145 })? {
146 MoveTypeLayout::Struct(s) => MoveDatatypeLayout::Struct(s),
147 MoveTypeLayout::Enum(e) => MoveDatatypeLayout::Enum(e),
148 _ => rpc_bail!(
149 "Event {ix} is not a struct or enum: {}",
150 event.type_.to_canonical_string(true)
151 ),
152 };
153
154 let sui_event = SuiEvent::try_from(
155 Arc::unwrap_or_clone(event),
156 digest,
157 ix as u64,
158 tx.timestamp_ms(),
159 layout,
160 )
161 .with_context(|| format!("Failed to convert Event {ix} into response"))?;
162
163 sui_events.push(sui_event)
164 }
165
166 Ok(SuiTransactionBlockEvents { data: sui_events })
167}
168
169fn balance_changes(tx: &TransactionContents) -> Result<Vec<SuiBalanceChange>, RpcError<Error>> {
171 let balance_changes = tx.balance_changes();
172 let mut response = Vec::with_capacity(balance_changes.len());
173
174 for bc in balance_changes {
175 let addr: SuiAddress = bc
176 .address
177 .as_ref()
178 .context("Missing address in balance change")?
179 .parse()
180 .context("Invalid owner address in balance change")?;
181
182 let coin_type = TypeTag::from_str(
183 bc.coin_type
184 .as_ref()
185 .context("Missing coin_type in balance change")?,
186 )
187 .context("Invalid coin type in balance change")?;
188
189 let amount: i128 = bc
190 .amount
191 .as_ref()
192 .context("Missing amount in balance change")?
193 .parse()
194 .context("Invalid balance change amount")?;
195
196 response.push(SuiBalanceChange {
197 owner: Owner::AddressOwner(addr),
198 coin_type,
199 amount,
200 });
201 }
202
203 Ok(response)
204}
205
206async fn object_changes(
209 ctx: &Context,
210 digest: TransactionDigest,
211 tx: &TransactionContents,
212) -> Result<Vec<SuiObjectChange>, RpcError<Error>> {
213 let tx_data: TransactionData = tx.data()?;
214 let effects: TransactionEffects = tx.effects()?;
215
216 let mut keys = vec![];
217 let native_changes = effects.object_changes();
218 for change in &native_changes {
219 let id = change.id;
220 if let Some(version) = change.input_version {
221 keys.push(VersionedObjectKey(id, version.value()));
222 }
223 if let Some(version) = change.output_version {
224 keys.push(VersionedObjectKey(id, version.value()));
225 }
226 }
227
228 let objects = ctx
229 .kv_loader()
230 .load_many_objects(keys)
231 .await
232 .context("Failed to fetch object contents")?;
233
234 let fetch_object = |id: ObjectID,
239 v: Option<SequenceNumber>,
240 d: Option<ObjectDigest>|
241 -> Result<Option<(Object, ObjectDigest)>, RpcError<Error>> {
242 let Some(v) = v else { return Ok(None) };
243 let Some(d) = d else { return Ok(None) };
244
245 let v = v.value();
246
247 let o = objects
248 .get(&VersionedObjectKey(id, v))
249 .ok_or_else(|| invalid_params(Error::PrunedObject(digest, id, v)))?;
250
251 Ok(Some((o.clone(), d)))
252 };
253
254 let mut changes = Vec::with_capacity(native_changes.len());
255
256 for change in native_changes {
257 let &ObjectChange {
258 id: object_id,
259 id_operation,
260 input_version,
261 input_digest,
262 output_version,
263 output_digest,
264 ..
265 } = &change;
266
267 let input = fetch_object(object_id, input_version, input_digest)?;
268 let output = fetch_object(object_id, output_version, output_digest)?;
269
270 changes.extend(to_sui_object_change(
271 tx_data.sender(),
272 object_id,
273 id_operation,
274 input,
275 output,
276 effects.lamport_version(),
277 )?);
278 }
279
280 Ok(changes)
281}