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
19const 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 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 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 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 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 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 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 indices.sort_unstable();
231 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 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}