sugar_cli/
utils.rs

1use std::{str::FromStr, thread::sleep, time::Duration};
2
3pub use anchor_client::solana_sdk::hash::Hash;
4use anchor_client::{
5    solana_sdk::{
6        account::Account,
7        commitment_config::{CommitmentConfig, CommitmentLevel},
8        program_pack::{IsInitialized, Pack},
9        pubkey::Pubkey,
10    },
11    Program,
12};
13pub use anyhow::{anyhow, Result};
14use console::{style, Style};
15use dialoguer::theme::ColorfulTheme;
16pub use indicatif::{ProgressBar, ProgressStyle};
17use miraland_account_decoder::UiAccountEncoding;
18use miraland_client::{
19    rpc_client::RpcClient,
20    rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
21    rpc_filter::{Memcmp, RpcFilterType},
22};
23use mpl_token_metadata::ID as TOKEN_METADATA_PROGRAM_ID;
24use spl_token::state::{Account as SplAccount, Mint};
25
26use crate::{common::*, config::data::Cluster};
27
28// TODO: devnet genesis hash, MI
29/// Hash for devnet cluster
30pub const DEVNET_HASH: &str = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
31
32// TODO: mainnet genesis hash, MI
33/// Hash for mainnet-beta cluster
34pub const MAINNET_HASH: &str = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
35
36/// Return the environment of the current connected RPC.
37pub fn get_cluster(rpc_client: RpcClient) -> Result<Cluster> {
38    let devnet_hash = Hash::from_str(DEVNET_HASH).unwrap();
39    let mainnet_hash = Hash::from_str(MAINNET_HASH).unwrap();
40    let genesis_hash = rpc_client.get_genesis_hash()?;
41
42    Ok(if genesis_hash == devnet_hash {
43        Cluster::Devnet
44    } else if genesis_hash == mainnet_hash {
45        Cluster::Mainnet
46    } else {
47        Cluster::Unknown
48    })
49}
50
51/// Check that the mint token is a valid address.
52pub fn check_spl_token(program: &Program, input: &str) -> Result<Mint> {
53    let pubkey = Pubkey::from_str(input)?;
54    let token_data = program.rpc().get_account_data(&pubkey)?;
55    if token_data.len() != 82 {
56        return Err(anyhow!("Invalid spl-token passed in."));
57    }
58    let token_mint = Mint::unpack_from_slice(&token_data)?;
59
60    if token_mint.is_initialized {
61        Ok(token_mint)
62    } else {
63        Err(anyhow!(format!(
64            "The specified spl-token is not initialized: {}",
65            input
66        )))
67    }
68}
69
70/// Check that the mint token account is a valid account.
71pub fn check_spl_token_account(program: &Program, input: &str) -> Result<()> {
72    let pubkey = Pubkey::from_str(input)?;
73    let ata_data = program.rpc().get_account_data(&pubkey)?;
74    let ata_account = SplAccount::unpack_unchecked(&ata_data)?;
75
76    if IsInitialized::is_initialized(&ata_account) {
77        Ok(())
78    } else {
79        Err(anyhow!(format!(
80            "The specified spl-token account is not initialized: {}",
81            input
82        )))
83    }
84}
85
86pub fn spinner_with_style() -> ProgressBar {
87    let pb = ProgressBar::new_spinner();
88    pb.enable_steady_tick(120);
89    pb.set_style(
90        ProgressStyle::default_spinner()
91            .tick_strings(&[
92                "▹▹▹▹▹",
93                "▸▹▹▹▹",
94                "▹▸▹▹▹",
95                "▹▹▸▹▹",
96                "▹▹▹▸▹",
97                "▹▹▹▹▸",
98                "▪▪▪▪▪",
99            ])
100            .template("{spinner:.dim} {msg}"),
101    );
102    pb
103}
104
105pub fn wait_with_spinner_and_countdown(seconds: u64) {
106    let pb = spinner_with_style();
107    pb.enable_steady_tick(120);
108    for i in 0..seconds {
109        pb.set_message(
110            style(format!("Waiting {} seconds...", seconds - i))
111                .dim()
112                .to_string(),
113        );
114        sleep(Duration::from_secs(1));
115    }
116    pb.finish_and_clear();
117}
118
119pub fn progress_bar_with_style(len: u64) -> ProgressBar {
120    let pb = ProgressBar::new(len);
121    // forces the progress bar to show immediately
122    pb.tick();
123    pb.enable_steady_tick(1000);
124    pb.set_style(
125        ProgressStyle::default_bar().template("[{elapsed_precise}] {msg}{wide_bar} {pos}/{len}"),
126    );
127    pb
128}
129
130pub fn get_dialoguer_theme() -> ColorfulTheme {
131    ColorfulTheme {
132        prompt_style: Style::new(),
133        checked_item_prefix: style("✔".to_string()).green().force_styling(true),
134        unchecked_item_prefix: style("✔".to_string()).black().force_styling(true),
135        ..Default::default()
136    }
137}
138
139pub fn assert_correct_authority(user_keypair: &Pubkey, update_authority: &Pubkey) -> Result<()> {
140    if user_keypair != update_authority {
141        return Err(anyhow!(
142            "Update authority does not match that of the candy machine."
143        ));
144    }
145
146    Ok(())
147}
148
149pub fn f64_to_u64_safe(f: f64) -> Result<u64, FloatConversionError> {
150    if f.fract() != 0.0 {
151        return Err(FloatConversionError::Fractional);
152    }
153    if f <= u64::MIN as f64 || f >= u64::MAX as f64 {
154        return Err(FloatConversionError::Overflow);
155    }
156    Ok(f.trunc() as u64)
157}
158
159pub fn get_cm_creator_metadata_accounts(
160    client: &RpcClient,
161    creator: &str,
162    position: usize,
163) -> Result<Vec<Pubkey>> {
164    let accounts = get_cm_creator_accounts(client, creator, position)?
165        .into_iter()
166        .map(|(pubkey, _account)| pubkey)
167        .collect::<Vec<Pubkey>>();
168
169    Ok(accounts)
170}
171
172pub fn get_cm_creator_mint_accounts(
173    client: &RpcClient,
174    creator: &str,
175    position: usize,
176) -> Result<Vec<Pubkey>> {
177    let accounts = get_cm_creator_accounts(client, creator, position)?
178        .into_iter()
179        .map(|(_, account)| account.data[33..65].to_vec())
180        .map(|data| {
181            Pubkey::from(
182                <std::vec::Vec<u8> as std::convert::TryInto<[u8; 32]>>::try_into(data)
183                    .expect("slice with incorrect length"),
184            )
185        })
186        .collect::<Vec<Pubkey>>();
187
188    Ok(accounts)
189}
190
191fn get_cm_creator_accounts(
192    client: &RpcClient,
193    creator: &str,
194    position: usize,
195) -> Result<Vec<(Pubkey, Account)>> {
196    if position > 4 {
197        error!("CM Creator position cannot be greator than 4");
198        std::process::exit(1);
199    }
200
201    let config = RpcProgramAccountsConfig {
202        filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
203            1 +  // key
204            32 + // update auth
205            32 + // mint
206            4 +  // name string length
207            MAX_NAME_LENGTH + // name
208            4 + // uri string length
209            MAX_URI_LENGTH + // uri*
210            4 + // symbol string length
211            MAX_SYMBOL_LENGTH + // symbol
212            2 + // seller fee basis points
213            1 + // whether or not there is a creators vec
214            4 + // creators
215            position * // index for each creator
216            (
217                32 + // address
218                1 + // verified
219                1 // share
220            ),
221            creator.as_ref(),
222        ))]),
223        account_config: RpcAccountInfoConfig {
224            encoding: Some(UiAccountEncoding::Base64),
225            data_slice: None,
226            commitment: Some(CommitmentConfig {
227                commitment: CommitmentLevel::Confirmed,
228            }),
229            min_context_slot: None,
230        },
231        with_context: None,
232    };
233
234    let results = client.get_program_accounts_with_config(&TOKEN_METADATA_PROGRAM_ID, config)?;
235
236    Ok(results)
237}