nifty_cli/commands/
decode.rs1use 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
28pub 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 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 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 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 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}