use std::{str::FromStr, thread::sleep, time::Duration};
pub use anchor_client::solana_sdk::hash::Hash;
use anchor_client::{
solana_sdk::{
account::Account,
commitment_config::{CommitmentConfig, CommitmentLevel},
program_pack::{IsInitialized, Pack},
pubkey::Pubkey,
},
Program,
};
pub use anyhow::{anyhow, Result};
use console::{style, Style};
use dialoguer::theme::ColorfulTheme;
pub use indicatif::{ProgressBar, ProgressStyle};
use miraland_account_decoder::UiAccountEncoding;
use miraland_client::{
rpc_client::RpcClient,
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType},
};
use mpl_token_metadata::ID as TOKEN_METADATA_PROGRAM_ID;
use spl_token::state::{Account as SplAccount, Mint};
use crate::{
common::*,
config::{data::Cluster, ConfigData},
};
pub const DEVNET_HASH: &str = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
pub const MAINNET_HASH: &str = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
pub fn get_cluster(rpc_client: RpcClient) -> Result<Cluster> {
let devnet_hash = Hash::from_str(DEVNET_HASH).unwrap();
let mainnet_hash = Hash::from_str(MAINNET_HASH).unwrap();
let genesis_hash = rpc_client.get_genesis_hash()?;
Ok(if genesis_hash == devnet_hash {
Cluster::Devnet
} else if genesis_hash == mainnet_hash {
Cluster::Mainnet
} else {
Cluster::Unknown
})
}
pub fn check_spl_token(program: &Program, input: &str) -> Result<Mint> {
let pubkey = Pubkey::from_str(input)?;
let token_data = program.rpc().get_account_data(&pubkey)?;
if token_data.len() != 82 {
return Err(anyhow!("Invalid solarti-token passed in."));
}
let token_mint = Mint::unpack_from_slice(&token_data)?;
if token_mint.is_initialized {
Ok(token_mint)
} else {
Err(anyhow!(format!(
"The specified solarti-token is not initialized: {input}",
)))
}
}
pub fn check_spl_token_account(program: &Program, input: &str) -> Result<()> {
let pubkey = Pubkey::from_str(input)?;
let ata_data = program.rpc().get_account_data(&pubkey)?;
let ata_account = SplAccount::unpack_unchecked(&ata_data)?;
if IsInitialized::is_initialized(&ata_account) {
Ok(())
} else {
Err(anyhow!(format!(
"The specified solarti-token account is not initialized: {input}",
)))
}
}
pub fn spinner_with_style() -> ProgressBar {
let pb = ProgressBar::new_spinner();
pb.enable_steady_tick(120);
pb.set_style(
ProgressStyle::default_spinner()
.tick_strings(&[
"▹▹▹▹▹",
"▸▹▹▹▹",
"▹▸▹▹▹",
"▹▹▸▹▹",
"▹▹▹▸▹",
"▹▹▹▹▸",
"▪▪▪▪▪",
])
.template("{spinner:.dim} {msg}"),
);
pb
}
pub fn wait_with_spinner_and_countdown(seconds: u64) {
let pb = spinner_with_style();
pb.enable_steady_tick(120);
for i in 0..seconds {
pb.set_message(
style(format!("Waiting {} seconds...", seconds - i))
.dim()
.to_string(),
);
sleep(Duration::from_secs(1));
}
pb.finish_and_clear();
}
pub fn progress_bar_with_style(len: u64) -> ProgressBar {
let pb = ProgressBar::new(len);
pb.tick();
pb.enable_steady_tick(1000);
pb.set_style(
ProgressStyle::default_bar().template("[{elapsed_precise}] {msg}{wide_bar} {pos}/{len}"),
);
pb
}
pub fn get_dialoguer_theme() -> ColorfulTheme {
ColorfulTheme {
prompt_style: Style::new(),
checked_item_prefix: style("✔".to_string()).green().force_styling(true),
unchecked_item_prefix: style("✔".to_string()).black().force_styling(true),
..Default::default()
}
}
pub fn assert_correct_authority(user_keypair: &Pubkey, update_authority: &Pubkey) -> Result<()> {
if user_keypair != update_authority {
return Err(anyhow!(
"Update authority does not match that of the candy machine."
));
}
Ok(())
}
pub fn f64_to_u64_safe(f: f64) -> Result<u64, FloatConversionError> {
if f.fract() != 0.0 {
return Err(FloatConversionError::Fractional);
}
if f <= u64::MIN as f64 || f >= u64::MAX as f64 {
return Err(FloatConversionError::Overflow);
}
Ok(f.trunc() as u64)
}
pub fn get_cm_creator_metadata_accounts(
client: &RpcClient,
creator: &str,
position: usize,
) -> Result<Vec<Pubkey>> {
let accounts = get_cm_creator_accounts(client, creator, position)?
.into_iter()
.map(|(pubkey, _account)| pubkey)
.collect::<Vec<Pubkey>>();
Ok(accounts)
}
pub fn get_cm_creator_mint_accounts(
client: &RpcClient,
creator: &str,
position: usize,
) -> Result<Vec<Pubkey>> {
let accounts = get_cm_creator_accounts(client, creator, position)?
.into_iter()
.map(|(_, account)| account.data[33..65].to_vec())
.map(|data| Pubkey::try_from(data).expect("Slice must be the same length as a Pubkey"))
.collect::<Vec<Pubkey>>();
Ok(accounts)
}
fn get_cm_creator_accounts(
client: &RpcClient,
creator: &str,
position: usize,
) -> Result<Vec<(Pubkey, Account)>> {
if position > 4 {
error!("CM Creator position cannot be greater than 4");
std::process::exit(1);
}
let offset = 1 + 32 + 32 + 4 + MAX_NAME_LENGTH + 4 + MAX_URI_LENGTH + 4 + MAX_SYMBOL_LENGTH + 2 + 1 + 4 + position * (
32 + 1 + 1 );
let bytes = MemcmpEncodedBytes::Base58(creator.to_string());
let config = RpcProgramAccountsConfig {
filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new(offset, bytes))]),
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
data_slice: None,
commitment: Some(CommitmentConfig {
commitment: CommitmentLevel::Confirmed,
}),
min_context_slot: None,
},
with_context: None,
};
let results = client.get_program_accounts_with_config(&TOKEN_METADATA_PROGRAM_ID, config)?;
Ok(results)
}
pub fn get_mint_decimals(program: &Program, config: &ConfigData) -> Result<u8> {
if let Some(mint_pubkey) = config.spl_token {
let mint_account = program.rpc().get_account(&mint_pubkey)?;
let mint = spl_token::state::Mint::unpack(&mint_account.data)?;
Ok(mint.decimals)
} else {
Ok(9)
}
}