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 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 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 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}