sugar_cli/deploy/
initialize.rs

1use anchor_client::solana_sdk::{
2    pubkey::Pubkey,
3    signature::{Keypair, Signature, Signer},
4    system_instruction, system_program,
5};
6use anyhow::Result;
7use mpl_candy_machine_core::{
8    accounts as nft_accounts, instruction as nft_instruction, CandyMachineData, ConfigLineSettings,
9    Creator as CandyCreator,
10};
11pub use mpl_token_metadata::state::{
12    MAX_CREATOR_LIMIT, MAX_NAME_LENGTH, MAX_SYMBOL_LENGTH, MAX_URI_LENGTH,
13};
14use mpl_token_metadata::{
15    instruction::MetadataDelegateRole, pda::find_metadata_delegate_record_account,
16    state::TokenStandard,
17};
18use solana_program::native_token::LAMPORTS_PER_MLN;
19
20use crate::{
21    common::*,
22    config::data::*,
23    deploy::errors::*,
24    pdas::{find_candy_machine_creator_pda, find_master_edition_pda, find_metadata_pda},
25};
26
27/// Create the candy machine data struct.
28pub fn create_candy_machine_data(
29    _client: &Client,
30    config: &ConfigData,
31    cache: &Cache,
32) -> Result<CandyMachineData> {
33    let mut creators: Vec<CandyCreator> = Vec::new();
34    let mut share = 0u32;
35
36    for creator in &config.creators {
37        let c = creator.to_candy_format()?;
38        share += c.percentage_share as u32;
39
40        creators.push(c);
41    }
42
43    if creators.is_empty() || creators.len() > (MAX_CREATOR_LIMIT - 1) {
44        return Err(anyhow!(
45            "The number of creators must be between 1 and {}.",
46            MAX_CREATOR_LIMIT - 1,
47        ));
48    }
49
50    if share != 100 {
51        return Err(anyhow!(
52            "Creator(s) share must add up to 100, current total {}.",
53            share,
54        ));
55    }
56
57    let config_line_settings = if config.hidden_settings.is_some() {
58        None
59    } else {
60        // CMv3 allows the specification of a common prefix for both name and uri
61        // therefore we need to determine the largest common prefix and the len of
62        // the remaining parts of the name and uri
63
64        // (shortest, largest, len largest) name
65        let mut name_pair = [String::new(), String::new(), String::new()];
66        // (shortest, largest, len largest) uri
67        let mut uri_pair = [String::new(), String::new(), String::new()];
68        // compares a value against a pair of (shorter, larger)
69        let compare_pair = |value: &String, pair: &mut [String; 3]| {
70            // lexicographic smaller
71            if pair[0].is_empty() || value < &pair[0] {
72                pair[0] = value.to_string();
73            }
74            // lexicographic larger
75            if value > &pair[1] {
76                pair[1] = value.to_string();
77            }
78            // lengthwise larger
79            if value.len() > pair[2].len() {
80                pair[2] = value.to_string();
81            }
82        };
83        let common_prefix = |value1: &str, value2: &str| {
84            let bytes1 = value1.as_bytes();
85            let bytes2 = value2.as_bytes();
86            let mut index = 0;
87
88            while (index < bytes1.len() && index < bytes2.len()) && bytes1[index] == bytes2[index] {
89                index += 1;
90            }
91
92            value1[..index].to_string()
93        };
94
95        for (index, item) in cache.items.iter() {
96            if i64::from_str(index)? > -1 {
97                compare_pair(&item.name, &mut name_pair);
98                compare_pair(&item.metadata_link, &mut uri_pair);
99            }
100        }
101
102        let name_prefix = common_prefix(&name_pair[0], &name_pair[1]);
103        let uri_prefix = common_prefix(&uri_pair[0], &uri_pair[1]);
104
105        Some(ConfigLineSettings {
106            name_length: (name_pair[2].len() - name_prefix.len()) as u32,
107            prefix_name: name_prefix,
108            uri_length: (uri_pair[2].len() - uri_prefix.len()) as u32,
109            prefix_uri: uri_prefix,
110            is_sequential: config.is_sequential,
111        })
112    };
113
114    let hidden_settings = config.hidden_settings.as_ref().map(|s| s.to_candy_format());
115
116    let data = CandyMachineData {
117        items_available: config.number,
118        symbol: config.symbol.clone(),
119        seller_fee_basis_points: config.seller_fee_basis_points,
120        max_supply: 0,
121        is_mutable: config.is_mutable,
122        creators,
123        config_line_settings,
124        hidden_settings,
125    };
126
127    Ok(data)
128}
129
130/// Send the `initialize_candy_machine` instruction to the candy machine program.
131pub fn initialize_candy_machine(
132    config_data: &ConfigData,
133
134    candy_account: &Keypair,
135    candy_machine_data: CandyMachineData,
136    collection_mint: Pubkey,
137    collection_update_authority: Pubkey,
138    program: Program,
139) -> Result<Signature> {
140    let payer = program.payer();
141    let candy_account_size = candy_machine_data.get_space_for_candy()?;
142
143    info!(
144        "Initializing candy machine with account size of: {} and address of: {}",
145        candy_account_size,
146        candy_account.pubkey().to_string()
147    );
148
149    let lamports = program
150        .rpc()
151        .get_minimum_balance_for_rent_exemption(candy_account_size)?;
152
153    let balance = program.rpc().get_account(&payer)?.lamports;
154
155    if lamports > balance {
156        return Err(DeployError::BalanceTooLow(
157            format!("{:.3}", (balance as f64 / LAMPORTS_PER_MLN as f64)),
158            format!("{:.3}", (lamports as f64 / LAMPORTS_PER_MLN as f64)),
159        )
160        .into());
161    }
162
163    // required PDAs
164
165    let (authority_pda, _) = find_candy_machine_creator_pda(&candy_account.pubkey());
166
167    let collection_metadata = find_metadata_pda(&collection_mint);
168    let collection_master_edition = find_master_edition_pda(&collection_mint);
169    let (collection_delegate_record, _) = find_metadata_delegate_record_account(
170        &collection_mint,
171        MetadataDelegateRole::Collection,
172        &collection_update_authority,
173        &authority_pda,
174    );
175
176    let tx = program
177        .request()
178        .instruction(system_instruction::create_account(
179            &payer,
180            &candy_account.pubkey(),
181            lamports,
182            candy_account_size as u64,
183            &program.id(),
184        ))
185        .signer(candy_account)
186        .accounts(nft_accounts::InitializeV2 {
187            candy_machine: candy_account.pubkey(),
188            authority: payer,
189            authority_pda,
190            payer,
191            collection_metadata,
192            collection_mint,
193            collection_master_edition,
194            collection_update_authority,
195            collection_delegate_record,
196            rule_set: config_data.rule_set,
197            token_metadata_program: mpl_token_metadata::ID,
198            system_program: system_program::id(),
199            sysvar_instructions: sysvar::instructions::ID,
200            authorization_rules_program: None,
201            authorization_rules: None,
202        })
203        .args(nft_instruction::InitializeV2 {
204            data: candy_machine_data,
205            token_standard: <crate::config::data::TokenStandard as std::convert::Into<
206                TokenStandard,
207            >>::into(config_data.token_standard) as u8,
208        });
209
210    let sig = tx.send()?;
211
212    Ok(sig)
213}