sugar_cli/guard/
show.rs

1use std::str::FromStr;
2
3use anchor_client::solana_sdk::pubkey::Pubkey;
4use anyhow::Result;
5use chrono::NaiveDateTime;
6use console::style;
7use mpl_candy_guard::state::{CandyGuard, CandyGuardData, GuardSet, DATA_OFFSET};
8use mpl_candy_machine_core::constants::EMPTY_STR;
9use solana_program::native_token::LAMPORTS_PER_MLN;
10
11use crate::{cache::load_cache, common::*, show::print_with_style, utils::*};
12
13pub struct GuardShowArgs {
14    pub keypair: Option<String>,
15    pub rpc_url: Option<String>,
16    pub cache: String,
17    pub candy_guard: Option<String>,
18}
19
20pub fn process_guard_show(args: GuardShowArgs) -> Result<()> {
21    println!("[1/1] {}Loading candy guard", LOOKING_GLASS_EMOJI);
22
23    // the candy guard id specified takes precedence over the one from the cache
24
25    let candy_guard_id = if let Some(candy_guard) = args.candy_guard {
26        candy_guard
27    } else {
28        let cache = load_cache(&args.cache, false)?;
29        cache.program.candy_guard
30    };
31
32    if candy_guard_id.is_empty() {
33        return Err(anyhow!("Missing candy guard id."));
34    }
35
36    let candy_guard_id = match Pubkey::from_str(&candy_guard_id) {
37        Ok(candy_guard_id) => candy_guard_id,
38        Err(_) => {
39            let error = anyhow!("Failed to parse candy guard id: {}", candy_guard_id);
40            error!("{:?}", error);
41            return Err(error);
42        }
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(mpl_candy_guard::ID);
48
49    let pb = spinner_with_style();
50    pb.set_message("Connecting...");
51
52    let account: CandyGuard = program.account(candy_guard_id)?;
53    let account_data = program.rpc().get_account_data(&candy_guard_id)?;
54    // load the guard set information
55    let candy_guard_data = CandyGuardData::load(&account_data[DATA_OFFSET..])?;
56
57    pb.finish_with_message("Done");
58
59    println!(
60        "\n{}{} {}",
61        GUARD_EMOJI,
62        style("Candy Guard ID:").dim(),
63        &candy_guard_id
64    );
65
66    // candy guard configuration
67
68    println!(" {}", style(":").dim());
69    print_with_style("", "base", account.base.to_string());
70    print_with_style("", "bump", account.bump.to_string());
71    print_with_style("", "authority", account.authority.to_string());
72    print_with_style("", "data", EMPTY_STR.to_string());
73
74    // default guard set
75    print_with_style("    ", "default", EMPTY_STR.to_string());
76    print_guard_set(&candy_guard_data.default, "    :   ".to_string())?;
77
78    // groups
79    if let Some(groups) = candy_guard_data.groups {
80        println!("     {}", style(":").dim());
81        print_with_style("    ", "groups", EMPTY_STR.to_string());
82
83        for (index, group) in groups.iter().enumerate() {
84            if index > 0 {
85                // padding between groups
86                println!("          {}", style(":").dim());
87            }
88            print_with_style("         ", "label", &group.label);
89            print_guard_set(
90                &group.guards,
91                if index == (groups.len() - 1) {
92                    "             ".to_string()
93                } else {
94                    "         :   ".to_string()
95                },
96            )?;
97        }
98    } else {
99        print_with_style("    ", "groups", "none".to_string());
100    }
101
102    Ok(())
103}
104
105fn print_guard_set(guard_set: &GuardSet, padding: String) -> Result<()> {
106    // bot tax
107    if let Some(bot_tax) = &guard_set.bot_tax {
108        print_with_style(&padding, "bot tax", EMPTY_STR.to_string());
109        print_with_style(
110            &format!("{}:   ", padding),
111            "lamports",
112            format!(
113                "{} (◎ {})",
114                bot_tax.lamports,
115                bot_tax.lamports as f64 / LAMPORTS_PER_MLN as f64
116            ),
117        );
118        print_with_style(
119            &format!("{}:   ", padding),
120            "last instruction",
121            bot_tax.last_instruction.to_string(),
122        );
123    } else {
124        print_with_style(&padding, "bot tax", "none".to_string());
125    }
126
127    // sol payment
128    if let Some(sol_payment) = &guard_set.sol_payment {
129        print_with_style(&padding, "sol payment", EMPTY_STR.to_string());
130        print_with_style(
131            &format!("{}:   ", padding),
132            "lamports",
133            format!(
134                "{} (◎ {})",
135                sol_payment.lamports,
136                sol_payment.lamports as f64 / LAMPORTS_PER_MLN as f64
137            ),
138        );
139        print_with_style(
140            &format!("{}:   ", padding),
141            "destination",
142            sol_payment.destination.to_string(),
143        );
144    } else {
145        print_with_style(&padding, "sol payment", "none".to_string());
146    }
147
148    // token payment
149    if let Some(token_payment) = &guard_set.token_payment {
150        print_with_style(&padding, "token payment", EMPTY_STR.to_string());
151        print_with_style(
152            &format!("{}:   ", padding),
153            "amount",
154            token_payment.amount.to_string(),
155        );
156        print_with_style(
157            &format!("{}:   ", padding),
158            "token mint",
159            token_payment.mint.to_string(),
160        );
161        print_with_style(
162            &format!("{}:   ", padding),
163            "destination",
164            token_payment.destination_ata.to_string(),
165        );
166    } else {
167        print_with_style(&padding, "token payment", "none".to_string());
168    }
169
170    // start date
171    if let Some(start_date) = &guard_set.start_date {
172        print_with_style(&padding, "start date", EMPTY_STR.to_string());
173        if let Some(date) = NaiveDateTime::from_timestamp_opt(start_date.date, 0) {
174            print_with_style(
175                &format!("{}:   ", padding),
176                "date",
177                date.format("%a %B %e %Y %H:%M:%S UTC").to_string(),
178            );
179        } else {
180            // this should not happen, but adding a message so it can be
181            // flag to the user
182            print_with_style(&format!("{}:   ", padding), "date", "<parse error>");
183        }
184    } else {
185        print_with_style(&padding, "start date", "none".to_string());
186    }
187
188    // third party signer
189    if let Some(third_party_signer) = &guard_set.third_party_signer {
190        print_with_style(&padding, "third party signer", EMPTY_STR.to_string());
191        print_with_style(
192            &format!("{}:   ", padding),
193            "signer key",
194            third_party_signer.signer_key.to_string(),
195        );
196    } else {
197        print_with_style(&padding, "third party signer", "none".to_string());
198    }
199
200    // token gate
201    if let Some(token_gate) = &guard_set.token_gate {
202        print_with_style(&padding, "token gate", EMPTY_STR.to_string());
203        print_with_style(
204            &format!("{}:   ", padding),
205            "amount",
206            token_gate.amount.to_string(),
207        );
208        print_with_style(
209            &format!("{}:   ", padding),
210            "mint",
211            token_gate.mint.to_string(),
212        );
213    } else {
214        print_with_style(&padding, "token gate", "none".to_string());
215    }
216
217    // gatekeeper
218    if let Some(gatekeeper) = &guard_set.gatekeeper {
219        print_with_style(&padding, "gatekeeper", EMPTY_STR.to_string());
220        print_with_style(
221            &format!("{}:   ", padding),
222            "gatekeeper network",
223            gatekeeper.gatekeeper_network.to_string(),
224        );
225        print_with_style(
226            &format!("{}:   ", padding),
227            "expire_on_use",
228            gatekeeper.expire_on_use.to_string(),
229        );
230    } else {
231        print_with_style(&padding, "gatekeeper", "none".to_string());
232    }
233
234    // end date
235    if let Some(end_date) = &guard_set.end_date {
236        print_with_style(&padding, "end date", EMPTY_STR.to_string());
237        if let Some(date) = NaiveDateTime::from_timestamp_opt(end_date.date, 0) {
238            print_with_style(
239                &format!("{}:   ", padding),
240                "date",
241                date.format("%a %B %e %Y %H:%M:%S UTC").to_string(),
242            );
243        } else {
244            // this should not happen, but adding a message so it can be
245            // flag to the user
246            print_with_style(&format!("{}:   ", padding), "date", "<parse error>");
247        }
248    } else {
249        print_with_style(&padding, "end date", "none".to_string());
250    }
251
252    // allow list
253    if let Some(allow_list) = &guard_set.allow_list {
254        print_with_style(&padding, "allow list", EMPTY_STR.to_string());
255        print_with_style(
256            &format!("{}:   ", padding),
257            "merkle root",
258            hex::encode(allow_list.merkle_root),
259        );
260    } else {
261        print_with_style(&padding, "allow list", "none".to_string());
262    }
263
264    // mint limit
265    if let Some(mint_limit) = &guard_set.mint_limit {
266        print_with_style(&padding, "mint limit", EMPTY_STR.to_string());
267        print_with_style(&format!("{}:   ", padding), "id", mint_limit.id.to_string());
268        print_with_style(
269            &format!("{}:   ", padding),
270            "amount",
271            mint_limit.limit.to_string(),
272        );
273    } else {
274        print_with_style(&padding, "mint limit", "none".to_string());
275    }
276
277    // nft payment
278    if let Some(nft_payment) = &guard_set.nft_payment {
279        print_with_style(&padding, "nft payment", EMPTY_STR.to_string());
280        print_with_style(
281            &format!("{}:   ", padding),
282            "required collection",
283            nft_payment.required_collection.to_string(),
284        );
285        print_with_style(
286            &format!("{}:   ", padding),
287            "destination",
288            nft_payment.destination.to_string(),
289        );
290    } else {
291        print_with_style(&padding, "nft payment", "none".to_string());
292    }
293
294    // redeemed amount
295    if let Some(redeemed_amount) = &guard_set.redeemed_amount {
296        print_with_style(&padding, "redeemed amount", EMPTY_STR.to_string());
297        print_with_style(
298            &format!("{}:   ", padding),
299            "amount",
300            redeemed_amount.maximum.to_string(),
301        );
302    } else {
303        print_with_style(&padding, "redeemed amount", "none".to_string());
304    }
305
306    // address gate
307    if let Some(address_gate) = &guard_set.address_gate {
308        print_with_style(&padding, "address gate", EMPTY_STR.to_string());
309        print_with_style(
310            &format!("{}:   ", padding),
311            "address",
312            address_gate.address.to_string(),
313        );
314    } else {
315        print_with_style(&padding, "address gate", "none".to_string());
316    }
317
318    // nft gate
319    if let Some(nft_gate) = &guard_set.nft_gate {
320        print_with_style(&padding, "nft gate", EMPTY_STR.to_string());
321        print_with_style(
322            &format!("{}:   ", padding),
323            "required_collection",
324            nft_gate.required_collection.to_string(),
325        );
326    } else {
327        print_with_style(&padding, "nft gate", "none".to_string());
328    }
329
330    // nft burn
331    if let Some(nft_burn) = &guard_set.nft_burn {
332        print_with_style(&padding, "nft burn", EMPTY_STR.to_string());
333        print_with_style(
334            &format!("{}:   ", padding),
335            "required_collection",
336            nft_burn.required_collection.to_string(),
337        );
338    } else {
339        print_with_style(&padding, "nft burn", "none".to_string());
340    }
341
342    // token burn
343    if let Some(token_burn) = &guard_set.token_burn {
344        print_with_style(&padding, "token burn", EMPTY_STR.to_string());
345        print_with_style(
346            &format!("{}    ", padding),
347            "amount",
348            token_burn.amount.to_string(),
349        );
350        print_with_style(
351            &format!("{}    ", padding),
352            "mint",
353            token_burn.mint.to_string(),
354        );
355    } else {
356        print_with_style(&padding, "token burn", "none".to_string());
357    }
358
359    // freeze sol payment
360    if let Some(freeze_sol_payment) = &guard_set.freeze_sol_payment {
361        print_with_style(&padding, "freeze sol payment", EMPTY_STR.to_string());
362        print_with_style(
363            &format!("{}:   ", padding),
364            "lamports",
365            format!(
366                "{} (◎ {})",
367                freeze_sol_payment.lamports,
368                freeze_sol_payment.lamports as f64 / LAMPORTS_PER_MLN as f64
369            ),
370        );
371        print_with_style(
372            &format!("{}:   ", padding),
373            "destination",
374            freeze_sol_payment.destination.to_string(),
375        );
376    } else {
377        print_with_style(&padding, "freeze sol payment", "none".to_string());
378    }
379
380    // freeze token payment
381    if let Some(freeze_token_payment) = &guard_set.freeze_token_payment {
382        print_with_style(&padding, "freeze token payment", EMPTY_STR.to_string());
383        print_with_style(
384            &format!("{}:   ", padding),
385            "amount",
386            freeze_token_payment.amount.to_string(),
387        );
388        print_with_style(
389            &format!("{}:   ", padding),
390            "token mint",
391            freeze_token_payment.mint.to_string(),
392        );
393        print_with_style(
394            &format!("{}:   ", padding),
395            "destination",
396            freeze_token_payment.destination_ata.to_string(),
397        );
398    } else {
399        print_with_style(&padding, "freeze token payment", "none".to_string());
400    }
401
402    Ok(())
403}