1use std::sync::Arc;
4
5use hex::ToHex;
6
7use zebra_chain::{
8 block,
9 parameters::Network,
10 sapling::NotSmallOrderValueCommitment,
11 transaction::{SerializedTransaction, Transaction},
12 transparent::Script,
13};
14use zebra_consensus::groth16::Description;
15use zebra_state::IntoDisk;
16
17use crate::methods::types;
18
19#[derive(Clone, Debug, PartialEq, serde::Serialize)]
22pub struct TransactionObject {
23 #[serde(with = "hex")]
25 pub hex: SerializedTransaction,
26 #[serde(skip_serializing_if = "Option::is_none")]
29 pub height: Option<u32>,
30 #[serde(skip_serializing_if = "Option::is_none")]
33 pub confirmations: Option<u32>,
34
35 #[serde(rename = "vin", skip_serializing_if = "Option::is_none")]
37 pub inputs: Option<Vec<Input>>,
38
39 #[serde(rename = "vout", skip_serializing_if = "Option::is_none")]
41 pub outputs: Option<Vec<Output>>,
42
43 #[serde(rename = "vShieldedSpend", skip_serializing_if = "Option::is_none")]
45 pub shielded_spends: Option<Vec<ShieldedSpend>>,
46
47 #[serde(rename = "vShieldedOutput", skip_serializing_if = "Option::is_none")]
49 pub shielded_outputs: Option<Vec<ShieldedOutput>>,
50
51 #[serde(rename = "orchard", skip_serializing_if = "Option::is_none")]
53 pub orchard: Option<Orchard>,
54
55 #[serde(rename = "valueBalance", skip_serializing_if = "Option::is_none")]
57 pub value_balance: Option<f64>,
58
59 #[serde(rename = "valueBalanceZat", skip_serializing_if = "Option::is_none")]
61 pub value_balance_zat: Option<i64>,
62 }
64
65#[derive(Clone, Debug, PartialEq, serde::Serialize)]
67#[serde(untagged)]
68pub enum Input {
69 Coinbase {
71 #[serde(with = "hex")]
73 coinbase: Vec<u8>,
74 sequence: u32,
76 },
77 NonCoinbase {
79 txid: String,
81 vout: u32,
83 script_sig: ScriptSig,
85 sequence: u32,
87 #[serde(skip_serializing_if = "Option::is_none")]
89 value: Option<f64>,
90 #[serde(rename = "valueZat", skip_serializing_if = "Option::is_none")]
92 value_zat: Option<i64>,
93 #[serde(skip_serializing_if = "Option::is_none")]
95 address: Option<String>,
96 },
97}
98
99#[derive(Clone, Debug, PartialEq, serde::Serialize)]
101pub struct Output {
102 value: f64,
104 #[serde(rename = "valueZat")]
106 value_zat: i64,
107 n: u32,
109 #[serde(rename = "scriptPubKey")]
111 script_pub_key: ScriptPubKey,
112}
113
114#[derive(Clone, Debug, PartialEq, serde::Serialize)]
116pub struct ScriptPubKey {
117 asm: String,
120 #[serde(with = "hex")]
122 hex: Script,
123 #[serde(rename = "reqSigs")]
125 req_sigs: u32,
126 r#type: String,
129 addresses: Vec<String>,
131}
132
133#[derive(Clone, Debug, PartialEq, serde::Serialize)]
135pub struct ScriptSig {
136 asm: String,
139 #[serde(with = "hex")]
141 hex: Script,
142}
143
144#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
146pub struct ShieldedSpend {
147 #[serde(with = "hex")]
149 cv: NotSmallOrderValueCommitment,
150 #[serde(with = "hex")]
152 anchor: [u8; 32],
153 #[serde(with = "hex")]
155 nullifier: [u8; 32],
156 #[serde(with = "hex")]
158 rk: [u8; 32],
159 #[serde(with = "hex")]
161 proof: [u8; 192],
162 #[serde(rename = "spendAuthSig", with = "hex")]
164 spend_auth_sig: [u8; 64],
165}
166
167#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
169pub struct ShieldedOutput {
170 #[serde(with = "hex")]
172 cv: NotSmallOrderValueCommitment,
173 #[serde(rename = "cmu", with = "hex")]
175 cm_u: [u8; 32],
176 #[serde(rename = "ephemeralKey", with = "hex")]
178 ephemeral_key: [u8; 32],
179 #[serde(rename = "encCiphertext", with = "hex")]
181 enc_ciphertext: [u8; 580],
182 #[serde(rename = "outCiphertext", with = "hex")]
184 out_ciphertext: [u8; 80],
185 #[serde(with = "hex")]
187 proof: [u8; 192],
188}
189
190#[derive(Clone, Debug, PartialEq, serde::Serialize)]
192pub struct Orchard {
193 actions: Vec<OrchardAction>,
195 #[serde(rename = "valueBalance")]
197 value_balance: f64,
198 #[serde(rename = "valueBalanceZat")]
200 value_balance_zat: i64,
201}
202
203#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
205pub struct OrchardAction {
206 #[serde(with = "hex")]
208 cv: [u8; 32],
209 #[serde(with = "hex")]
211 nullifier: [u8; 32],
212 #[serde(with = "hex")]
214 rk: [u8; 32],
215 #[serde(rename = "cmx", with = "hex")]
217 cm_x: [u8; 32],
218 #[serde(rename = "ephemeralKey", with = "hex")]
220 ephemeral_key: [u8; 32],
221 #[serde(rename = "encCiphertext", with = "hex")]
223 enc_ciphertext: [u8; 580],
224 #[serde(rename = "spendAuthSig", with = "hex")]
226 spend_auth_sig: [u8; 64],
227 #[serde(rename = "outCiphertext", with = "hex")]
229 out_ciphertext: [u8; 80],
230}
231
232impl Default for TransactionObject {
233 fn default() -> Self {
234 Self {
235 hex: SerializedTransaction::from(
236 [0u8; zebra_chain::transaction::MIN_TRANSPARENT_TX_SIZE as usize].to_vec(),
237 ),
238 height: Option::default(),
239 confirmations: Option::default(),
240 inputs: None,
241 outputs: None,
242 shielded_spends: None,
243 shielded_outputs: None,
244 orchard: None,
245 value_balance: None,
246 value_balance_zat: None,
247 }
248 }
249}
250
251impl TransactionObject {
252 #[allow(clippy::unwrap_in_result)]
254 pub(crate) fn from_transaction(
255 tx: Arc<Transaction>,
256 height: Option<block::Height>,
257 confirmations: Option<u32>,
258 network: &Network,
259 ) -> Self {
260 Self {
261 hex: tx.clone().into(),
262 height: height.map(|height| height.0),
263 confirmations,
264 inputs: Some(
265 tx.inputs()
266 .iter()
267 .map(|input| match input {
268 zebra_chain::transparent::Input::Coinbase { sequence, .. } => {
269 Input::Coinbase {
270 coinbase: input
271 .coinbase_script()
272 .expect("we know it is a valid coinbase script"),
273 sequence: *sequence,
274 }
275 }
276 zebra_chain::transparent::Input::PrevOut {
277 sequence,
278 unlock_script,
279 outpoint,
280 } => Input::NonCoinbase {
281 txid: outpoint.hash.encode_hex(),
282 vout: outpoint.index,
283 script_sig: ScriptSig {
284 asm: "".to_string(),
285 hex: unlock_script.clone(),
286 },
287 sequence: *sequence,
288 value: None,
289 value_zat: None,
290 address: None,
291 },
292 })
293 .collect(),
294 ),
295 outputs: Some(
296 tx.outputs()
297 .iter()
298 .enumerate()
299 .map(|output| {
300 let addresses = match output.1.address(network) {
301 Some(address) => vec![address.to_string()],
302 None => vec![],
303 };
304
305 Output {
306 value: types::Zec::from(output.1.value).lossy_zec(),
307 value_zat: output.1.value.zatoshis(),
308 n: output.0 as u32,
309 script_pub_key: ScriptPubKey {
310 asm: "".to_string(),
311 hex: output.1.lock_script.clone(),
312 req_sigs: addresses.len() as u32,
313 r#type: "".to_string(),
314 addresses,
315 },
316 }
317 })
318 .collect(),
319 ),
320 shielded_spends: Some(
321 tx.sapling_spends_per_anchor()
322 .map(|spend| {
323 let mut anchor = spend.per_spend_anchor.as_bytes();
324 anchor.reverse();
325
326 let mut nullifier = spend.nullifier.as_bytes();
327 nullifier.reverse();
328
329 let mut rk: [u8; 32] = spend.clone().rk.into();
330 rk.reverse();
331
332 let spend_auth_sig: [u8; 64] = spend.spend_auth_sig.into();
333
334 ShieldedSpend {
335 cv: spend.cv,
336 anchor,
337 nullifier,
338 rk,
339 proof: spend.proof().0,
340 spend_auth_sig,
341 }
342 })
343 .collect(),
344 ),
345 shielded_outputs: Some(
346 tx.sapling_outputs()
347 .map(|output| {
348 let mut ephemeral_key: [u8; 32] = output.ephemeral_key.into();
349 ephemeral_key.reverse();
350 let enc_ciphertext: [u8; 580] = output.enc_ciphertext.into();
351 let out_ciphertext: [u8; 80] = output.out_ciphertext.into();
352
353 ShieldedOutput {
354 cv: output.cv,
355 cm_u: output.cm_u.to_bytes(),
356 ephemeral_key,
357 enc_ciphertext,
358 out_ciphertext,
359 proof: output.proof().0,
360 }
361 })
362 .collect(),
363 ),
364 value_balance: Some(
365 types::Zec::from(tx.sapling_value_balance().sapling_amount()).lossy_zec(),
366 ),
367 value_balance_zat: Some(tx.sapling_value_balance().sapling_amount().zatoshis()),
368
369 orchard: if !tx.has_orchard_shielded_data() {
370 None
371 } else {
372 Some(Orchard {
373 actions: tx
374 .orchard_actions()
375 .collect::<Vec<_>>()
376 .iter()
377 .map(|action| {
378 let spend_auth_sig: [u8; 64] = tx
379 .orchard_shielded_data()
380 .and_then(|shielded_data| {
381 shielded_data
382 .actions
383 .iter()
384 .find(|authorized_action| {
385 authorized_action.action == **action
386 })
387 .map(|authorized_action| {
388 authorized_action.spend_auth_sig.into()
389 })
390 })
391 .unwrap_or([0; 64]);
392
393 let cv: [u8; 32] = action.cv.into();
394 let nullifier: [u8; 32] = action.nullifier.into();
395 let rk: [u8; 32] = action.rk.into();
396 let cm_x: [u8; 32] = action.cm_x.into();
397 let ephemeral_key: [u8; 32] = action.ephemeral_key.into();
398 let enc_ciphertext: [u8; 580] = action.enc_ciphertext.into();
399 let out_ciphertext: [u8; 80] = action.out_ciphertext.into();
400
401 OrchardAction {
402 cv,
403 nullifier,
404 rk,
405 cm_x,
406 ephemeral_key,
407 enc_ciphertext,
408 spend_auth_sig,
409 out_ciphertext,
410 }
411 })
412 .collect(),
413 value_balance: types::Zec::from(tx.orchard_value_balance().orchard_amount())
414 .lossy_zec(),
415 value_balance_zat: tx.orchard_value_balance().orchard_amount().zatoshis(),
416 })
417 },
418 }
419 }
420}