use anchor_client::solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signature, Signer},
system_instruction, system_program,
};
use anyhow::Result;
use mpl_candy_machine_core::{
accounts as nft_accounts, instruction as nft_instruction, CandyMachineData, ConfigLineSettings,
Creator as CandyCreator,
};
pub use mpl_token_metadata::state::{
MAX_CREATOR_LIMIT, MAX_NAME_LENGTH, MAX_SYMBOL_LENGTH, MAX_URI_LENGTH,
};
use mpl_token_metadata::{
instruction::MetadataDelegateRole, pda::find_metadata_delegate_record_account,
state::TokenStandard,
};
use solana_program::native_token::LAMPORTS_PER_SOL;
use crate::{
common::*,
config::data::*,
deploy::errors::*,
pdas::{find_candy_machine_creator_pda, find_master_edition_pda, find_metadata_pda},
};
pub fn create_candy_machine_data(
_client: &Client,
config: &ConfigData,
cache: &Cache,
) -> Result<CandyMachineData> {
let mut creators: Vec<CandyCreator> = Vec::new();
let mut share = 0u32;
for creator in &config.creators {
let c = creator.to_candy_format()?;
share += c.percentage_share as u32;
creators.push(c);
}
if creators.is_empty() || creators.len() > (MAX_CREATOR_LIMIT - 1) {
return Err(anyhow!(
"The number of creators must be between 1 and {}.",
MAX_CREATOR_LIMIT - 1,
));
}
if share != 100 {
return Err(anyhow!(
"Creator(s) share must add up to 100, current total {}.",
share,
));
}
let config_line_settings = if config.hidden_settings.is_some() {
None
} else {
let mut name_pair = [String::new(), String::new(), String::new()];
let mut uri_pair = [String::new(), String::new(), String::new()];
let compare_pair = |value: &String, pair: &mut [String; 3]| {
if pair[0].is_empty() || value < &pair[0] {
pair[0] = value.to_string();
}
if value > &pair[1] {
pair[1] = value.to_string();
}
if value.len() > pair[2].len() {
pair[2] = value.to_string();
}
};
let common_prefix = |value1: &str, value2: &str| {
let bytes1 = value1.as_bytes();
let bytes2 = value2.as_bytes();
let mut index = 0;
while (index < bytes1.len() && index < bytes2.len()) && bytes1[index] == bytes2[index] {
index += 1;
}
value1[..index].to_string()
};
for (index, item) in cache.items.iter() {
if i64::from_str(index)? > -1 {
compare_pair(&item.name, &mut name_pair);
compare_pair(&item.metadata_link, &mut uri_pair);
}
}
let name_prefix = common_prefix(&name_pair[0], &name_pair[1]);
let uri_prefix = common_prefix(&uri_pair[0], &uri_pair[1]);
Some(ConfigLineSettings {
name_length: (name_pair[2].len() - name_prefix.len()) as u32,
prefix_name: name_prefix,
uri_length: (uri_pair[2].len() - uri_prefix.len()) as u32,
prefix_uri: uri_prefix,
is_sequential: config.is_sequential,
})
};
let hidden_settings = config.hidden_settings.as_ref().map(|s| s.to_candy_format());
let data = CandyMachineData {
items_available: config.number,
symbol: config.symbol.clone(),
seller_fee_basis_points: config.seller_fee_basis_points,
max_supply: 0,
is_mutable: config.is_mutable,
creators,
config_line_settings,
hidden_settings,
};
Ok(data)
}
pub fn initialize_candy_machine(
config_data: &ConfigData,
candy_account: &Keypair,
candy_machine_data: CandyMachineData,
collection_mint: Pubkey,
collection_update_authority: Pubkey,
program: Program,
) -> Result<Signature> {
let payer = program.payer();
let candy_account_size = candy_machine_data.get_space_for_candy()?;
info!(
"Initializing candy machine with account size of: {} and address of: {}",
candy_account_size,
candy_account.pubkey().to_string()
);
let lamports = program
.rpc()
.get_minimum_balance_for_rent_exemption(candy_account_size)?;
let balance = program.rpc().get_account(&payer)?.lamports;
if lamports > balance {
return Err(DeployError::BalanceTooLow(
format!("{:.3}", (balance as f64 / LAMPORTS_PER_SOL as f64)),
format!("{:.3}", (lamports as f64 / LAMPORTS_PER_SOL as f64)),
)
.into());
}
let (authority_pda, _) = find_candy_machine_creator_pda(&candy_account.pubkey());
let collection_metadata = find_metadata_pda(&collection_mint);
let collection_master_edition = find_master_edition_pda(&collection_mint);
let (collection_delegate_record, _) = find_metadata_delegate_record_account(
&collection_mint,
MetadataDelegateRole::Collection,
&collection_update_authority,
&authority_pda,
);
let tx = program
.request()
.instruction(system_instruction::create_account(
&payer,
&candy_account.pubkey(),
lamports,
candy_account_size as u64,
&program.id(),
))
.signer(candy_account)
.accounts(nft_accounts::InitializeV2 {
candy_machine: candy_account.pubkey(),
authority: payer,
authority_pda,
payer,
collection_metadata,
collection_mint,
collection_master_edition,
collection_update_authority,
collection_delegate_record,
rule_set: config_data.rule_set,
token_metadata_program: mpl_token_metadata::ID,
system_program: system_program::id(),
sysvar_instructions: sysvar::instructions::ID,
authorization_rules_program: None,
authorization_rules: None,
})
.args(nft_instruction::InitializeV2 {
data: candy_machine_data,
token_standard: <crate::config::data::TokenStandard as std::convert::Into<
TokenStandard,
>>::into(config_data.token_standard) as u8,
});
let sig = tx.send()?;
Ok(sig)
}