sugar_cli/collections/
set.rs

1use std::str::FromStr;
2
3use anchor_client::solana_sdk::{pubkey::Pubkey, system_program};
4use anyhow::Result;
5use console::style;
6use mpl_candy_machine_core::{
7    accounts as nft_accounts, instruction as nft_instruction, AccountVersion,
8};
9use mpl_token_metadata::{
10    error::MetadataError,
11    instruction::MetadataDelegateRole,
12    pda::{find_collection_authority_account, find_metadata_delegate_record_account},
13    state::{MasterEditionV2, Metadata},
14};
15
16use crate::{
17    cache::load_cache,
18    candy_machine::{CANDY_MACHINE_ID, *},
19    common::*,
20    config::get_config_data,
21    hash::hash_and_update,
22    pdas::*,
23    update::{process_update, UpdateArgs},
24    utils::{assert_correct_authority, spinner_with_style},
25};
26
27pub struct SetCollectionArgs {
28    pub collection_mint: String,
29    pub keypair: Option<String>,
30    pub rpc_url: Option<String>,
31    pub cache: String,
32    pub config: String,
33    pub candy_machine: Option<String>,
34}
35
36pub fn process_set_collection(args: SetCollectionArgs) -> Result<()> {
37    let sugar_config = sugar_setup(args.keypair.clone(), args.rpc_url.clone())?;
38    let client = setup_client(&sugar_config)?;
39    let program = client.program(CANDY_MACHINE_ID);
40    let mut cache = Cache::new();
41
42    // The candy machine id specified takes precedence over the one from the cache.
43    let candy_machine_id = match args.candy_machine {
44        Some(ref candy_machine_id) => candy_machine_id.to_owned(),
45        None => {
46            cache = load_cache(&args.cache, false)?;
47            cache.program.candy_machine.clone()
48        }
49    };
50
51    let collection_mint_pubkey = match Pubkey::from_str(&args.collection_mint) {
52        Ok(candy_pubkey) => candy_pubkey,
53        Err(_) => {
54            let error = anyhow!(
55                "Failed to parse collection mint id: {}",
56                args.collection_mint
57            );
58            error!("{:?}", error);
59            return Err(error);
60        }
61    };
62
63    let candy_pubkey = match Pubkey::from_str(&candy_machine_id) {
64        Ok(candy_pubkey) => candy_pubkey,
65        Err(_) => {
66            let error = anyhow!("Failed to parse candy machine id: {}", candy_machine_id);
67            error!("{:?}", error);
68            return Err(error);
69        }
70    };
71
72    println!(
73        "{} {}Loading candy machine",
74        style("[1/2]").bold().dim(),
75        LOOKING_GLASS_EMOJI
76    );
77    println!("{} {}", style("Candy machine ID:").bold(), candy_machine_id);
78
79    let pb = spinner_with_style();
80    pb.set_message("Connecting...");
81
82    let candy_machine_state =
83        get_candy_machine_state(&sugar_config, &Pubkey::from_str(&candy_machine_id)?)?;
84
85    let collection_metadata_info = get_metadata_pda(&collection_mint_pubkey, &program)?;
86
87    let collection_edition_info = get_master_edition_pda(&collection_mint_pubkey, &program)?;
88
89    pb.finish_with_message("Done");
90
91    assert_correct_authority(
92        &sugar_config.keypair.pubkey(),
93        &candy_machine_state.authority,
94    )?;
95
96    println!(
97        "\n{} {}Setting collection mint for candy machine",
98        style("[2/2]").bold().dim(),
99        COLLECTION_EMOJI
100    );
101
102    let pb = spinner_with_style();
103    pb.set_message("Sending set collection transaction...");
104
105    let set_signature = set_collection(
106        &program,
107        &candy_pubkey,
108        &candy_machine_state,
109        &collection_mint_pubkey,
110        &collection_metadata_info,
111        &collection_edition_info,
112    )?;
113
114    pb.finish_with_message(format!(
115        "{} {}",
116        style("Set collection signature:").bold(),
117        set_signature
118    ));
119
120    // If a candy machine id wasn't manually specified we are operating on the candy machine in the cache
121    // and so need to update the cache file.
122    if args.candy_machine.is_none() {
123        cache.items.shift_remove("-1");
124        cache.program.collection_mint = collection_mint_pubkey.to_string();
125        cache.sync_file()?;
126
127        // If hidden settings are enabled, we update the hash value in the config file and update the candy machine on-chain.
128        if candy_machine_state.data.hidden_settings.is_some() {
129            let mut config_data = get_config_data(&args.config)?;
130            let hidden_settings = config_data.hidden_settings.as_ref().unwrap().clone();
131
132            println!(
133                "\n{} {}",
134                style("Hidden settings hash:").bold(),
135                hash_and_update(hidden_settings, &args.config, &mut config_data, &args.cache,)?
136            );
137
138            println!(
139                "\nCandy machine has hidden settings and cache file was updated. Updating hash value...\n"
140            );
141
142            let update_args = UpdateArgs {
143                keypair: args.keypair,
144                rpc_url: args.rpc_url,
145                cache: args.cache,
146                new_authority: None,
147                config: args.config,
148                candy_machine: Some(candy_machine_id),
149            };
150
151            process_update(update_args)?;
152        }
153    }
154
155    Ok(())
156}
157
158pub fn set_collection(
159    program: &Program,
160    candy_pubkey: &Pubkey,
161    candy_machine_state: &CandyMachine,
162    new_collection_mint_pubkey: &Pubkey,
163    new_collection_metadata_info: &PdaInfo<Metadata>,
164    new_collection_edition_info: &PdaInfo<MasterEditionV2>,
165) -> Result<Signature> {
166    let payer = program.payer();
167
168    let (authority_pda, _) = find_candy_machine_creator_pda(candy_pubkey);
169
170    let (new_collection_metadata_pubkey, new_collection_metadata) = new_collection_metadata_info;
171    let (new_collection_edition_pubkey, new_collection_edition) = new_collection_edition_info;
172
173    let new_collection_delegate_record = find_metadata_delegate_record_account(
174        new_collection_mint_pubkey,
175        MetadataDelegateRole::Collection,
176        &new_collection_metadata.update_authority,
177        &authority_pda,
178    )
179    .0;
180
181    if new_collection_metadata.update_authority != payer {
182        return Err(anyhow!(CustomCandyError::AuthorityMismatch(
183            new_collection_metadata.update_authority.to_string(),
184            payer.to_string()
185        )));
186    }
187
188    if new_collection_edition.max_supply != Some(0) {
189        return Err(anyhow!(MetadataError::CollectionMustBeAUniqueMasterEdition));
190    }
191
192    if candy_machine_state.items_redeemed > 0 {
193        return Err(anyhow!(
194            "You can't modify the Candy Machine collection after items have been minted."
195        ));
196    }
197
198    let collection_mint = candy_machine_state.collection_mint;
199    let (_, collection_metadata) = get_metadata_pda(&candy_machine_state.collection_mint, program)?;
200
201    let (authority_pda, _) = find_candy_machine_creator_pda(candy_pubkey);
202    let collection_delegate_record = if matches!(candy_machine_state.version, AccountVersion::V1) {
203        find_collection_authority_account(&collection_mint, &authority_pda).0
204    } else {
205        find_metadata_delegate_record_account(
206            &collection_mint,
207            MetadataDelegateRole::Collection,
208            &collection_metadata.update_authority,
209            &authority_pda,
210        )
211        .0
212    };
213
214    let collection_update_authority = collection_metadata.update_authority;
215    let collection_metadata = find_metadata_pda(&collection_mint);
216
217    let builder = program
218        .request()
219        .accounts(nft_accounts::SetCollectionV2 {
220            candy_machine: *candy_pubkey,
221            authority: payer,
222            authority_pda,
223            payer,
224            collection_mint,
225            collection_metadata,
226            collection_update_authority,
227            collection_delegate_record,
228            new_collection_metadata: *new_collection_metadata_pubkey,
229            new_collection_mint: *new_collection_mint_pubkey,
230            new_collection_master_edition: *new_collection_edition_pubkey,
231            new_collection_delegate_record,
232            new_collection_update_authority: new_collection_metadata.update_authority,
233            token_metadata_program: mpl_token_metadata::ID,
234            system_program: system_program::id(),
235            sysvar_instructions: sysvar::instructions::ID,
236            authorization_rules_program: None,
237            authorization_rules: None,
238        })
239        .args(nft_instruction::SetCollectionV2);
240
241    let sig = builder.send()?;
242
243    Ok(sig)
244}