nifty_cli/commands/
decode.rs

1use std::{fmt, ops::Deref};
2
3use nifty_asset::{
4    constraints::{
5        And, Constraint, FromBytes, Not, Operator, OperatorType, Or, OwnedBy, PubkeyMatch,
6    },
7    extensions::{
8        Attributes, Blob, Extension, ExtensionData, ExtensionType, Grouping, Links, Manager,
9        Metadata, Proxy, Royalties,
10    },
11    types::Creator,
12    JsonCreator,
13};
14use podded::ZeroCopy;
15use serde_json::{json, Value};
16
17use super::*;
18
19pub struct DecodeArgs {
20    pub rpc_url: Option<String>,
21    pub asset: Pubkey,
22    pub field: Option<String>,
23    pub raw: bool,
24}
25
26const ASSET_LEN: usize = 168;
27
28// Use a wrapper struct to override the debug implementation on Asset
29// to print a str instead of bytes for the name field.
30pub struct AssetWrapper(pub Asset);
31
32impl fmt::Debug for AssetWrapper {
33    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34        let name = name_to_string(&self.name);
35
36        f.debug_struct("Asset")
37            .field("discriminator", &self.0.discriminator)
38            .field("state", &self.0.state)
39            .field("standard", &self.0.standard)
40            .field("mutable", &self.0.mutable)
41            .field("owner", &self.0.owner)
42            .field("group", &self.0.group)
43            .field("authority", &self.0.authority)
44            .field("delegate", &self.0.delegate)
45            .field("name", &name)
46            .finish()
47    }
48}
49
50impl AssetWrapper {
51    pub fn name(&self) -> String {
52        name_to_string(&self.name)
53    }
54}
55
56impl Deref for AssetWrapper {
57    type Target = Asset;
58
59    fn deref(&self) -> &Self::Target {
60        &self.0
61    }
62}
63
64pub fn name_to_string(bytes: &[u8]) -> String {
65    std::str::from_utf8(bytes)
66        .unwrap_or("[invalid UTF-8]")
67        .trim_end_matches('\0')
68        .to_string()
69}
70
71pub fn handle_decode(args: DecodeArgs) -> Result<()> {
72    let config = CliConfig::new(None, args.rpc_url)?;
73
74    let data = config.client.get_account_data(&args.asset)?;
75
76    if args.raw {
77        println!("{:?}", data);
78        return Ok(());
79    }
80
81    let asset = Asset::from_bytes(&data).unwrap();
82    let asset = AssetWrapper(asset);
83
84    if let Some(field) = args.field {
85        match field.to_lowercase().as_str() {
86            "discriminator" => {
87                println!("discriminator: {:?}", asset.discriminator);
88            }
89            "state" => {
90                println!("state: {:?}", asset.state);
91            }
92            "standard" => {
93                println!("standard: {:?}", asset.standard);
94            }
95            "mutable" => {
96                println!("mutable: {:?}", asset.mutable);
97            }
98            "owner" => {
99                println!("owner: {:?}", asset.owner);
100            }
101            "group" => {
102                println!("group: {:?}", asset.group);
103            }
104            "authority" => {
105                println!("authority: {:?}", asset.authority);
106            }
107            "delegate" => {
108                println!("delegate address: {:?}", asset.delegate.address);
109                println!("delegate roles: {:?}", asset.delegate.roles);
110            }
111            "name" => {
112                println!("name: {:?}", asset.name());
113            }
114            _ => {
115                println!("Unknown field: {:?}", field);
116            }
117        }
118        return Ok(());
119    } else {
120        println!("Asset: {:#?}", asset);
121    }
122
123    let mut cursor = ASSET_LEN;
124
125    // Decode extensions.
126    while cursor < data.len() {
127        let extension = Extension::load(&data[cursor..cursor + Extension::LEN]);
128        let extension_type = extension.extension_type();
129        let extension_length = extension.length();
130
131        let start = cursor + Extension::LEN;
132        let end = start + extension_length as usize;
133
134        let extension_data = &data[start..end];
135
136        match extension_type {
137            ExtensionType::Attributes => {
138                let attributes: Attributes = Attributes::from_bytes(extension_data);
139                println!("{attributes:#?}");
140            }
141            ExtensionType::Blob => {
142                let blob: Blob = Blob::from_bytes(extension_data);
143                // write to a file based on the content type
144                let extension = blob.content_type.as_str().split('/').last().unwrap();
145                let filename = format!("blob.{}", extension);
146                std::fs::write(filename, blob.data).unwrap();
147
148                println!("Blob: {:?}", blob.content_type.as_str());
149            }
150            ExtensionType::Creators => {
151                let creators: Vec<JsonCreator> = extension_data
152                    .chunks(std::mem::size_of::<Creator>())
153                    .map(JsonCreator::from_data)
154                    .collect();
155                println!("{creators:#?}");
156            }
157            ExtensionType::Links => {
158                let links: Links = Links::from_bytes(extension_data);
159                println!("{links:#?}");
160            }
161            ExtensionType::Metadata => {
162                let metadata: Metadata = Metadata::from_bytes(extension_data);
163                println!("{metadata:#?}");
164            }
165            ExtensionType::Grouping => {
166                let grouping: Grouping = Grouping::from_bytes(extension_data);
167                println!("{grouping:#?}");
168            }
169            ExtensionType::Royalties => {
170                let royalties: Royalties = Royalties::from_bytes(extension_data);
171                let constraint = royalties.constraint;
172                let basis_points = royalties.basis_points;
173
174                println!("royalties:");
175                println!("basis points:{:#?}", basis_points);
176
177                // Basis Points: u64
178                let index = std::mem::size_of::<u64>();
179
180                let constraints = handle_constraints(&constraint, index, extension_data);
181                println!("Constraints: {constraints:#?}");
182            }
183            ExtensionType::Manager => {
184                let manager: Manager = Manager::from_bytes(extension_data);
185                let delegate = manager.delegate;
186                println!("authority: {delegate:#?}");
187            }
188            ExtensionType::Proxy => {
189                let proxy = Proxy::from_bytes(extension_data);
190                println!("proxy: {proxy:#?}");
191            }
192            ExtensionType::None => {
193                println!("None");
194            }
195        }
196
197        cursor = extension.boundary() as usize;
198    }
199
200    Ok(())
201}
202
203fn handle_constraints(constraint: &Constraint, mut index: usize, extension_data: &[u8]) -> Value {
204    let operator_type = constraint.operator.operator_type();
205    let constraint_size = constraint.operator.size() as usize;
206    // Operator: [u32; 2]
207    index += std::mem::size_of::<Operator>();
208
209    match operator_type {
210        OperatorType::And => {
211            let and = And::from_bytes(&extension_data[index..]);
212            let constraints: Vec<Value> = and
213                .constraints
214                .iter()
215                .map(|constraint| handle_constraints(constraint, index, extension_data))
216                .collect();
217            json!({
218                "AND": constraints
219            })
220        }
221        OperatorType::Not => {
222            let constraint = Not::from_bytes(&extension_data[index..]);
223            json!({
224                "NOT": handle_constraints(&constraint.constraint, index, extension_data)
225            })
226        }
227        OperatorType::Or => {
228            let or = Or::from_bytes(&extension_data[index..]);
229            let constraints: Vec<Value> = or
230                .constraints
231                .iter()
232                .map(|constraint| handle_constraints(constraint, index, extension_data))
233                .collect();
234            json!({
235                "OR": constraints
236            })
237        }
238        OperatorType::OwnedBy => {
239            let owned_by = OwnedBy::from_bytes(&extension_data[index..index + constraint_size]);
240            json!({
241                "OWNED_BY": {
242                    "account": owned_by.account.to_string(),
243                    "owners": owned_by.owners.iter().map(|owner| owner.to_string()).collect::<Vec<String>>()
244                }
245            })
246        }
247        OperatorType::PubkeyMatch => {
248            let pubkey_match =
249                PubkeyMatch::from_bytes(&extension_data[index..index + constraint_size]);
250            json!({
251                "PUBKEY_MATCH": {
252                    "account": pubkey_match.account.to_string(),
253                    "pubkeys": pubkey_match.pubkeys.iter().map(|pubkey| pubkey.to_string()).collect::<Vec<String>>()
254                }
255            })
256        }
257        OperatorType::Empty => {
258            json!({ "EMPTY": {} })
259        }
260    }
261}