sugar_cli/show/
process.rs

1use std::str::FromStr;
2
3use anchor_client::solana_sdk::pubkey::Pubkey;
4use anyhow::Result;
5use console::style;
6use mpl_candy_machine_core::{constants::NULL_STRING, AccountVersion};
7use mpl_token_metadata::state::TokenStandard;
8
9use crate::{cache::load_cache, candy_machine::*, common::*, utils::*};
10
11pub struct ShowArgs {
12    pub keypair: Option<String>,
13    pub rpc_url: Option<String>,
14    pub cache: String,
15    pub candy_machine: Option<String>,
16    pub unminted: bool,
17}
18
19// number of indices per line
20const PER_LINE: usize = 11;
21
22pub fn process_show(args: ShowArgs) -> Result<()> {
23    println!(
24        "{} {}Looking up candy machine",
25        if args.unminted {
26            style("[1/2]").bold().dim()
27        } else {
28            style("[1/1]").bold().dim()
29        },
30        LOOKING_GLASS_EMOJI
31    );
32
33    let pb = spinner_with_style();
34    pb.set_message("Connecting...");
35
36    // the candy machine id specified takes precedence over the one from the cache
37
38    let candy_machine_id = if let Some(candy_machine) = args.candy_machine {
39        candy_machine
40    } else {
41        let cache = load_cache(&args.cache, false)?;
42        cache.program.candy_machine
43    };
44
45    let sugar_config = sugar_setup(args.keypair, args.rpc_url)?;
46    let client = setup_client(&sugar_config)?;
47    let program = client.program(CANDY_MACHINE_ID);
48
49    let candy_machine_id = match Pubkey::from_str(&candy_machine_id) {
50        Ok(candy_machine_id) => candy_machine_id,
51        Err(_) => {
52            let error = anyhow!("Failed to parse candy machine id: {}", candy_machine_id);
53            error!("{:?}", error);
54            return Err(error);
55        }
56    };
57
58    let (cndy_state, rule_set) = load_candy_machine(&sugar_config, &candy_machine_id)?;
59    let cndy_data = cndy_state.data;
60
61    pb.finish_and_clear();
62
63    println!(
64        "\n{}{} {}",
65        CANDY_EMOJI,
66        style("Candy machine ID:").dim(),
67        &candy_machine_id
68    );
69
70    // candy machine state and data
71
72    println!(" {}", style(":").dim());
73    print_with_style("", "authority", cndy_state.authority.to_string());
74    print_with_style("", "mint authority", cndy_state.mint_authority.to_string());
75    print_with_style(
76        "",
77        "collection mint",
78        cndy_state.collection_mint.to_string(),
79    );
80
81    if matches!(cndy_state.version, AccountVersion::V1) {
82        print_with_style("", "account version", "V1");
83        print_with_style("", "token standard", "NonFungible (NFT)");
84        print_with_style("", "rule set", "none");
85    } else {
86        print_with_style("", "account version", "V2");
87        print_with_style(
88            "",
89            "token standard",
90            if cndy_state.token_standard == TokenStandard::NonFungible as u8 {
91                "NonFungible"
92            } else {
93                "ProgrammableNonFungible (pNFT)"
94            },
95        );
96
97        if let Some(rule_set) = rule_set {
98            print_with_style("", "rule set", rule_set.to_string());
99        }
100    }
101    print_with_style("", "features", "none");
102
103    print_with_style("", "max supply", cndy_data.max_supply.to_string());
104    print_with_style("", "items redeemed", cndy_state.items_redeemed.to_string());
105    print_with_style("", "items available", cndy_data.items_available.to_string());
106
107    print_with_style("", "symbol", cndy_data.symbol.trim_end_matches(NULL_STRING));
108    print_with_style(
109        "",
110        "seller fee basis points",
111        format!(
112            "{}% ({})",
113            cndy_data.seller_fee_basis_points / 100,
114            cndy_data.seller_fee_basis_points
115        ),
116    );
117    print_with_style("", "is mutable", cndy_data.is_mutable.to_string());
118    print_with_style("", "creators", "".to_string());
119
120    let creators = &cndy_data.creators;
121
122    for (index, creator) in creators.iter().enumerate() {
123        let info = format!(
124            "{} ({}%{})",
125            creator.address,
126            creator.percentage_share,
127            if creator.verified { ", verified" } else { "" },
128        );
129        print_with_style(":   ", &(index + 1).to_string(), info);
130    }
131
132    // hidden settings
133
134    if let Some(hidden_settings) = &cndy_data.hidden_settings {
135        print_with_style("", "hidden settings", "".to_string());
136        print_with_style(":   ", "name", &hidden_settings.name);
137        print_with_style(":   ", "uri", &hidden_settings.uri);
138        print_with_style(
139            ":   ",
140            "hash",
141            String::from_utf8(hidden_settings.hash.to_vec())?,
142        );
143    } else {
144        print_with_style("", "hidden settings", "none".to_string());
145    }
146
147    // config line settings
148
149    if let Some(config_line_settings) = &cndy_data.config_line_settings {
150        print_with_style("", "config line settings", "");
151
152        let prefix_name = if config_line_settings.prefix_name.is_empty() {
153            style("<empty>").dim()
154        } else {
155            style(config_line_settings.prefix_name.as_str())
156        };
157        print_with_style("    ", "prefix_name", &prefix_name.to_string());
158        print_with_style(
159            "    ",
160            "name_length",
161            &config_line_settings.name_length.to_string(),
162        );
163
164        let prefix_uri = if config_line_settings.prefix_uri.is_empty() {
165            style("<empty>").dim()
166        } else {
167            style(config_line_settings.prefix_uri.as_str())
168        };
169        print_with_style("    ", "prefix_uri", &prefix_uri.to_string());
170        print_with_style(
171            "    ",
172            "uri_length",
173            &config_line_settings.uri_length.to_string(),
174        );
175        print_with_style(
176            "    ",
177            "is_sequential",
178            if config_line_settings.is_sequential {
179                "true"
180            } else {
181                "false"
182            },
183        );
184    } else {
185        print_with_style("", "config line settings", "none");
186    }
187
188    // unminted indices
189
190    if args.unminted {
191        println!(
192            "\n{} {}Retrieving unminted indices",
193            style("[2/2]").bold().dim(),
194            LOOKING_GLASS_EMOJI
195        );
196
197        let start = CONFIG_ARRAY_START
198            + STRING_LEN_SIZE
199            + (cndy_data.items_available as usize * cndy_data.get_config_line_size())
200            + cndy_data
201                .items_available
202                .checked_div(8)
203                .expect("Numerical overflow error") as usize
204            + 1;
205
206        let pb = spinner_with_style();
207        pb.set_message("Connecting...");
208        // retrieve the (raw) candy machine data
209        let data = program.rpc().get_account_data(&candy_machine_id)?;
210
211        pb.finish_and_clear();
212        let mut indices = vec![];
213
214        let remaining = cndy_data.items_available - cndy_state.items_redeemed;
215        for i in 0..remaining {
216            let slice = start + (i * 4) as usize;
217            indices.push(u32::from_le_bytes(
218                data[slice..slice + 4].try_into().unwrap(),
219            ));
220        }
221
222        if indices.is_empty() {
223            println!(
224                "\n{}{}",
225                PAPER_EMOJI,
226                style("All items of the candy machine have been minted.").dim()
227            );
228        } else {
229            // makes sure all items are in order
230            indices.sort_unstable();
231            // logs all indices
232            info!("unminted list: {:?}", indices);
233
234            println!(
235                "\n{}{}",
236                PAPER_EMOJI,
237                style(format!("Unminted list ({} total):", indices.len())).dim()
238            );
239            let mut current = 0;
240
241            for i in indices {
242                if current == 0 {
243                    println!("{}", style(" :").dim());
244                    print!("{}", style(" :.. ").dim());
245                }
246                current += 1;
247
248                print!(
249                    "{:<5}{}",
250                    i,
251                    if current == PER_LINE {
252                        current = 0;
253                        "\n"
254                    } else {
255                        " "
256                    }
257                );
258            }
259            // just adds a new line break
260            println!();
261        }
262    }
263
264    Ok(())
265}
266
267pub fn print_with_style<S>(indent: &str, key: &str, value: S)
268where
269    S: core::fmt::Display,
270{
271    println!(
272        " {} {}",
273        style(format!("{}:.. {}:", indent, key)).dim(),
274        value
275    );
276}