ord/subcommand/
decode.rs

1use super::*;
2
3#[derive(Serialize, Eq, PartialEq, Deserialize, Debug)]
4pub struct CompactOutput {
5  pub inscriptions: Vec<CompactInscription>,
6  pub runestone: Option<Artifact>,
7}
8
9#[derive(Serialize, Eq, PartialEq, Deserialize, Debug)]
10pub struct RawOutput {
11  pub inscriptions: Vec<ParsedEnvelope>,
12  pub runestone: Option<Artifact>,
13}
14
15#[derive(Serialize, Eq, PartialEq, Deserialize, Debug)]
16#[serde_with::skip_serializing_none]
17pub struct CompactInscription {
18  pub body: Option<String>,
19  pub content_encoding: Option<String>,
20  pub content_type: Option<String>,
21  #[serde(default, skip_serializing_if = "std::ops::Not::not")]
22  pub duplicate_field: bool,
23  #[serde(default, skip_serializing_if = "std::ops::Not::not")]
24  pub incomplete_field: bool,
25  pub metadata: Option<String>,
26  pub metaprotocol: Option<String>,
27  #[serde(default, skip_serializing_if = "Vec::is_empty")]
28  pub parents: Vec<InscriptionId>,
29  pub pointer: Option<u64>,
30  #[serde(default, skip_serializing_if = "std::ops::Not::not")]
31  pub unrecognized_even_field: bool,
32}
33
34impl TryFrom<Inscription> for CompactInscription {
35  type Error = Error;
36
37  fn try_from(inscription: Inscription) -> Result<Self> {
38    Ok(Self {
39      content_encoding: inscription
40        .content_encoding()
41        .map(|header_value| header_value.to_str().map(str::to_string))
42        .transpose()?,
43      content_type: inscription.content_type().map(str::to_string),
44      metaprotocol: inscription.metaprotocol().map(str::to_string),
45      parents: inscription.parents(),
46      pointer: inscription.pointer(),
47      body: inscription.body.map(hex::encode),
48      duplicate_field: inscription.duplicate_field,
49      incomplete_field: inscription.incomplete_field,
50      metadata: inscription.metadata.map(hex::encode),
51      unrecognized_even_field: inscription.unrecognized_even_field,
52    })
53  }
54}
55
56#[derive(Debug, Parser)]
57pub(crate) struct Decode {
58  #[arg(
59    long,
60    conflicts_with = "file",
61    help = "Fetch transaction with <TXID> from Bitcoin Core."
62  )]
63  txid: Option<Txid>,
64  #[arg(long, conflicts_with = "txid", help = "Load transaction from <FILE>.")]
65  file: Option<PathBuf>,
66  #[arg(
67    long,
68    help = "Serialize inscriptions in a compact, human-readable format."
69  )]
70  compact: bool,
71}
72
73impl Decode {
74  pub(crate) fn run(self, settings: Settings) -> SubcommandResult {
75    let transaction = if let Some(txid) = self.txid {
76      settings
77        .bitcoin_rpc_client(None)?
78        .get_raw_transaction(&txid, None)?
79    } else if let Some(file) = self.file {
80      Transaction::consensus_decode(&mut fs::File::open(file)?)?
81    } else {
82      Transaction::consensus_decode(&mut io::stdin())?
83    };
84
85    let inscriptions = ParsedEnvelope::from_transaction(&transaction);
86
87    let runestone = Runestone::decipher(&transaction);
88
89    if self.compact {
90      Ok(Some(Box::new(CompactOutput {
91        inscriptions: inscriptions
92          .clone()
93          .into_iter()
94          .map(|inscription| inscription.payload.try_into())
95          .collect::<Result<Vec<CompactInscription>>>()?,
96        runestone,
97      })))
98    } else {
99      Ok(Some(Box::new(RawOutput {
100        inscriptions,
101        runestone,
102      })))
103    }
104  }
105}