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
28pub const DEVNET_HASH: &str = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
31
32pub const MAINNET_HASH: &str = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
35
36pub 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
51pub 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
70pub 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 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 + 32 + 32 + 4 + MAX_NAME_LENGTH + 4 + MAX_URI_LENGTH + 4 + MAX_SYMBOL_LENGTH + 2 + 1 + 4 + position * (
217 32 + 1 + 1 ),
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}