1pub mod utils;
2use crate::utils::{
3 assert_initialized, assert_is_ata, assert_keys_equal, assert_owned_by, assert_valid_go_live,
4 punish_bots, spl_token_burn, spl_token_transfer, TokenBurnParams, TokenTransferParams,
5};
6use anchor_lang::{
7 prelude::*,
8 solana_program::{
9 program::{invoke, invoke_signed},
10 serialize_utils::{read_pubkey, read_u16},
11 system_instruction, sysvar,
12 },
13 AnchorDeserialize, AnchorSerialize, Discriminator, Key,
14};
15use solana_program::entrypoint::ProgramResult;
16use anchor_spl::token::Token;
17use arrayref::array_ref;
18use mpl_token_metadata::{
19 assertions::collection::assert_master_edition,
20 error::MetadataError,
21 instruction::{
22 approve_collection_authority, create_master_edition_v3, create_metadata_accounts_v2,
23 revoke_collection_authority, set_and_verify_collection, update_metadata_accounts_v2,
24 },
25 state::{
26 Metadata, MAX_CREATOR_LEN, MAX_CREATOR_LIMIT, MAX_NAME_LENGTH, MAX_SYMBOL_LENGTH,
27 MAX_URI_LENGTH,
28 },
29 utils::{assert_derivation, create_or_allocate_account_raw},
30};
31use solana_program::sysvar::{instructions::get_instruction_relative, SysvarId};
32use spl_token::state::Mint;
33use std::{cell::RefMut, ops::Deref, str::FromStr};
34anchor_lang::declare_id!("cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ");
35const EXPIRE_OFFSET: i64 = 10 * 60;
36const PREFIX: &str = "candy_machine";
37const BLOCK_HASHES: &str = "SysvarRecentB1ockHashes11111111111111111111";
39const BOT_FEE: u64 = 10000000;
40
41const GUMDROP_ID: Pubkey = solana_program::pubkey!("gdrpGjVffourzkdDRrQmySw4aTHr8a3xmQzzxSwFD1a");
42const A_TOKEN: Pubkey = solana_program::pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
43
44#[program]
45pub mod candy_machine {
46 use super::*;
47
48 #[inline(never)]
49 pub fn mint_nft<'info>(
50 ctx: Context<'_, '_, '_, 'info, MintNFT<'info>>,
51 creator_bump: u8,
52 ) -> ProgramResult {
53 let candy_machine = &mut ctx.accounts.candy_machine;
54 let candy_machine_creator = &ctx.accounts.candy_machine_creator;
55 let clock = &ctx.accounts.clock;
56 let wallet = &ctx.accounts.wallet;
58 let payer = &ctx.accounts.payer;
59 let token_program = &ctx.accounts.token_program;
60 let recent_slothashes = &ctx.accounts.recent_blockhashes;
62 let instruction_sysvar_account = &ctx.accounts.instruction_sysvar_account;
63 let instruction_sysvar_account_info = instruction_sysvar_account.to_account_info();
64 let instruction_sysvar = instruction_sysvar_account_info.data.borrow();
65 let current_ix = get_instruction_relative(0, &instruction_sysvar_account_info).unwrap();
66 if !ctx.accounts.metadata.data_is_empty() {
67 return Err(ErrorCode::MetadataAccountMustBeEmpty.into());
68 }
69 if current_ix.program_id != candy_machine::id() && current_ix.program_id != GUMDROP_ID {
71 punish_bots(
72 ErrorCode::SuspiciousTransaction,
73 payer.to_account_info(),
74 ctx.accounts.candy_machine.to_account_info(),
75 ctx.accounts.system_program.to_account_info(),
76 BOT_FEE,
77 )?;
78 return Ok(());
79 }
80 let next_ix = get_instruction_relative(1, &instruction_sysvar_account_info);
81 if next_ix.is_ok() {
82 let ix = &next_ix.unwrap();
83 let discriminator = &ix.data[0..8];
84 let after_collection_ix = get_instruction_relative(2, &instruction_sysvar_account_info);
85 if ix.program_id != candy_machine::id()
86 || discriminator != [103, 17, 200, 25, 118, 95, 125, 61]
87 || after_collection_ix.is_ok()
88 {
89 msg!("Failing and Halting Here due to an extra unauthorized instruction");
91 return Err(ErrorCode::SuspiciousTransaction.into());
92 }
93 }
94 let mut idx = 0;
95 let num_instructions = read_u16(&mut idx, &instruction_sysvar)
96 .map_err(|_| ProgramError::InvalidAccountData)?;
97
98 let associated_token = A_TOKEN;
99
100 for index in 0..num_instructions {
101 let mut current = 2 + (index * 2) as usize;
102 let start = read_u16(&mut current, &instruction_sysvar).unwrap();
103
104 current = start as usize;
105 let num_accounts = read_u16(&mut current, &instruction_sysvar).unwrap();
106 current += (num_accounts as usize) * (1 + 32);
107 let program_id = read_pubkey(&mut current, &instruction_sysvar).unwrap();
108
109 if program_id != candy_machine::id()
110 && program_id != spl_token::id()
111 && program_id != anchor_lang::solana_program::system_program::ID
112 && program_id != associated_token
113 {
114 msg!("Transaction had ix with program id {}", program_id);
115 punish_bots(
116 ErrorCode::SuspiciousTransaction,
117 payer.to_account_info(),
118 ctx.accounts.candy_machine.to_account_info(),
119 ctx.accounts.system_program.to_account_info(),
120 BOT_FEE,
121 )?;
122 return Ok(());
123 }
124 }
125 if recent_slothashes.key().to_string() == BLOCK_HASHES {
126 msg!("recent_blockhashes is deprecated and will break soon");
127 }
128 if recent_slothashes.key() != sysvar::slot_hashes::SlotHashes::id()
129 && recent_slothashes.key().to_string() != BLOCK_HASHES
130 {
131 return Err(ErrorCode::IncorrectSlotHashesPubkey.into());
132 }
133
134 let mut price = candy_machine.data.price;
135 if let Some(es) = &candy_machine.data.end_settings {
136 match es.end_setting_type {
137 EndSettingType::Date => {
138 if clock.unix_timestamp > es.number as i64 {
139 if ctx.accounts.payer.key() != candy_machine.authority {
140 punish_bots(
141 ErrorCode::CandyMachineNotLive,
142 payer.to_account_info(),
143 ctx.accounts.candy_machine.to_account_info(),
144 ctx.accounts.system_program.to_account_info(),
145 BOT_FEE,
146 )?;
147 return Ok(());
148 }
149 }
150 }
151 EndSettingType::Amount => {
152 if candy_machine.items_redeemed >= es.number {
153 if ctx.accounts.payer.key() != candy_machine.authority {
154 punish_bots(
155 ErrorCode::CandyMachineEmpty,
156 payer.to_account_info(),
157 ctx.accounts.candy_machine.to_account_info(),
158 ctx.accounts.system_program.to_account_info(),
159 BOT_FEE,
160 )?;
161 return Ok(());
162 }
163 return Err(ErrorCode::CandyMachineEmpty.into());
164 }
165 }
166 }
167 }
168
169 let mut remaining_accounts_counter: usize = 0;
170 if let Some(gatekeeper) = &candy_machine.data.gatekeeper {
171 if ctx.remaining_accounts.len() <= remaining_accounts_counter {
172 punish_bots(
173 ErrorCode::GatewayTokenMissing,
174 payer.to_account_info(),
175 ctx.accounts.candy_machine.to_account_info(),
176 ctx.accounts.system_program.to_account_info(),
177 BOT_FEE,
178 )?;
179 return Ok(());
180 }
181 let gateway_token_info = &ctx.remaining_accounts[remaining_accounts_counter];
182 let gateway_token = ::solana_gateway::borsh::try_from_slice_incomplete::<
183 ::solana_gateway::state::GatewayToken,
184 >(*gateway_token_info.data.borrow())?;
185 let expire_time = gateway_token
189 .expire_time
190 .ok_or(ErrorCode::GatewayTokenExpireTimeInvalid)?
191 as i64;
192 remaining_accounts_counter += 1;
193 if gatekeeper.expire_on_use {
194 if ctx.remaining_accounts.len() <= remaining_accounts_counter {
195 return Err(ErrorCode::GatewayAppMissing.into());
196 }
197 let gateway_app = &ctx.remaining_accounts[remaining_accounts_counter];
198 remaining_accounts_counter += 1;
199 if ctx.remaining_accounts.len() <= remaining_accounts_counter {
200 return Err(ErrorCode::NetworkExpireFeatureMissing.into());
201 }
202 let network_expire_feature = &ctx.remaining_accounts[remaining_accounts_counter];
203 remaining_accounts_counter += 1;
204 ::solana_gateway::Gateway::verify_and_expire_token(
205 gateway_app.clone(),
206 gateway_token_info.clone(),
207 payer.deref().clone(),
208 &gatekeeper.gatekeeper_network,
209 network_expire_feature.clone(),
210 )?;
211 } else {
212 ::solana_gateway::Gateway::verify_gateway_token_account_info(
213 gateway_token_info,
214 &payer.key(),
215 &gatekeeper.gatekeeper_network,
216 None,
217 )?;
218 }
219 match candy_machine.data.go_live_date {
222 Some(val) => {
223 if (expire_time - EXPIRE_OFFSET) < val {
224 if let Some(ws) = &candy_machine.data.whitelist_mint_settings {
225 if !ws.presale {
228 msg!(
229 "Invalid gateway token: calculated creation time {} and go_live_date {}",
230 expire_time - EXPIRE_OFFSET,
231 val);
232 return Err(ErrorCode::GatewayTokenExpireTimeInvalid.into());
233 }
234 } else {
235 msg!(
236 "Invalid gateway token: calculated creation time {} and go_live_date {}",
237 expire_time - EXPIRE_OFFSET,
238 val);
239 return Err(ErrorCode::GatewayTokenExpireTimeInvalid.into());
240 }
241 }
242 }
243 None => {}
244 }
245 }
246
247 if let Some(ws) = &candy_machine.data.whitelist_mint_settings {
248 let whitelist_token_account = &ctx.remaining_accounts[remaining_accounts_counter];
249 remaining_accounts_counter += 1;
250 match assert_is_ata(whitelist_token_account, &payer.key(), &ws.mint) {
255 Ok(wta) => {
256 if wta.amount > 0 {
257 match candy_machine.data.go_live_date {
258 None => {
259 if ctx.accounts.payer.key() != candy_machine.authority
260 && !ws.presale
261 {
262 punish_bots(
263 ErrorCode::CandyMachineNotLive,
264 payer.to_account_info(),
265 ctx.accounts.candy_machine.to_account_info(),
266 ctx.accounts.system_program.to_account_info(),
267 BOT_FEE,
268 )?;
269 return Ok(());
270 }
271 }
272 Some(val) => {
273 if clock.unix_timestamp < val
274 && ctx.accounts.payer.key() != candy_machine.authority
275 && !ws.presale
276 {
277 punish_bots(
278 ErrorCode::CandyMachineNotLive,
279 payer.to_account_info(),
280 ctx.accounts.candy_machine.to_account_info(),
281 ctx.accounts.system_program.to_account_info(),
282 BOT_FEE,
283 )?;
284 return Ok(());
285 }
286 }
287 }
288
289 if ws.mode == WhitelistMintMode::BurnEveryTime {
290 let whitelist_token_mint =
291 &ctx.remaining_accounts[remaining_accounts_counter];
292 remaining_accounts_counter += 1;
293
294 let whitelist_burn_authority =
295 &ctx.remaining_accounts[remaining_accounts_counter];
296 remaining_accounts_counter += 1;
297
298 if assert_keys_equal(whitelist_token_mint.key(), ws.mint).is_err() {
299 punish_bots(
300 ErrorCode::CandyMachineNotLive,
301 payer.to_account_info(),
302 ctx.accounts.candy_machine.to_account_info(),
303 ctx.accounts.system_program.to_account_info(),
304 BOT_FEE,
305 )?;
306 return Ok(());
307 }
308 spl_token_burn(TokenBurnParams {
309 mint: whitelist_token_mint.clone(),
310 source: whitelist_token_account.clone(),
311 amount: 1,
312 authority: whitelist_burn_authority.clone(),
313 authority_signer_seeds: None,
314 token_program: token_program.to_account_info(),
315 })?;
316 }
317
318 if let Some(dp) = ws.discount_price {
319 price = dp;
320 }
321 } else {
322 if wta.amount == 0 && ws.discount_price.is_none() && !ws.presale {
323 punish_bots(
327 ErrorCode::NoWhitelistToken,
328 payer.to_account_info(),
329 ctx.accounts.candy_machine.to_account_info(),
330 ctx.accounts.system_program.to_account_info(),
331 BOT_FEE,
332 )?;
333 return Ok(());
334 }
335 let go_live = assert_valid_go_live(payer, clock, candy_machine);
336 if go_live.is_err() {
337 punish_bots(
338 ErrorCode::CandyMachineNotLive,
339 payer.to_account_info(),
340 ctx.accounts.candy_machine.to_account_info(),
341 ctx.accounts.system_program.to_account_info(),
342 BOT_FEE,
343 )?;
344 return Ok(());
345 }
346 if ws.mode == WhitelistMintMode::BurnEveryTime {
347 remaining_accounts_counter += 2;
348 }
349 }
350 }
351 Err(_) => {
352 if ws.discount_price.is_none() && !ws.presale {
353 punish_bots(
357 ErrorCode::NoWhitelistToken,
358 payer.to_account_info(),
359 ctx.accounts.candy_machine.to_account_info(),
360 ctx.accounts.system_program.to_account_info(),
361 BOT_FEE,
362 )?;
363 return Ok(());
364 }
365 if ws.mode == WhitelistMintMode::BurnEveryTime {
366 remaining_accounts_counter += 2;
367 }
368 let go_live = assert_valid_go_live(payer, clock, candy_machine);
369 if go_live.is_err() {
370 punish_bots(
371 ErrorCode::CandyMachineNotLive,
372 payer.to_account_info(),
373 ctx.accounts.candy_machine.to_account_info(),
374 ctx.accounts.system_program.to_account_info(),
375 BOT_FEE,
376 )?;
377 return Ok(());
378 }
379 }
380 }
381 } else {
382 let go_live = assert_valid_go_live(payer, clock, candy_machine);
384 if go_live.is_err() {
385 punish_bots(
386 ErrorCode::CandyMachineNotLive,
387 payer.to_account_info(),
388 ctx.accounts.candy_machine.to_account_info(),
389 ctx.accounts.system_program.to_account_info(),
390 BOT_FEE,
391 )?;
392 return Ok(());
393 }
394 }
395
396 if candy_machine.items_redeemed >= candy_machine.data.items_available {
397 punish_bots(
398 ErrorCode::CandyMachineEmpty,
399 payer.to_account_info(),
400 ctx.accounts.candy_machine.to_account_info(),
401 ctx.accounts.system_program.to_account_info(),
402 BOT_FEE,
403 );
404 return Ok(());
405 }
406
407 if let Some(mint) = candy_machine.token_mint {
408 let token_account_info = &ctx.remaining_accounts[remaining_accounts_counter];
409 remaining_accounts_counter += 1;
410 let transfer_authority_info = &ctx.remaining_accounts[remaining_accounts_counter];
411 remaining_accounts_counter += 1;
412 let token_account = assert_is_ata(token_account_info, &payer.key(), &mint)?;
413
414 if token_account.amount < price {
415 return Err(ErrorCode::NotEnoughTokens.into());
416 }
417
418 spl_token_transfer(TokenTransferParams {
419 source: token_account_info.clone(),
420 destination: wallet.to_account_info(),
421 authority: transfer_authority_info.clone(),
422 authority_signer_seeds: &[],
423 token_program: token_program.to_account_info(),
424 amount: price,
425 })?;
426 } else {
427 if ctx.accounts.payer.lamports() < price {
428 return Err(ErrorCode::NotEnoughSOL.into());
429 }
430
431 invoke(
432 &system_instruction::transfer(&ctx.accounts.payer.key(), &wallet.key(), price),
433 &[
434 ctx.accounts.payer.to_account_info(),
435 wallet.to_account_info(),
436 ctx.accounts.system_program.to_account_info(),
437 ],
438 )?;
439 }
440
441 let data = recent_slothashes.data.borrow();
442 let most_recent = array_ref![data, 12, 8];
443
444 let index = u64::from_le_bytes(*most_recent);
445 let modded: usize = index
446 .checked_rem(candy_machine.data.items_available)
447 .ok_or(ErrorCode::NumericalOverflowError)? as usize;
448
449 let config_line = get_config_line(&candy_machine, modded, candy_machine.items_redeemed)?;
450
451 candy_machine.items_redeemed = candy_machine
452 .items_redeemed
453 .checked_add(1)
454 .ok_or(ErrorCode::NumericalOverflowError)?;
455
456 let cm_key = candy_machine.key();
457 let authority_seeds = [PREFIX.as_bytes(), cm_key.as_ref(), &[creator_bump]];
458
459 let mut creators: Vec<mpl_token_metadata::state::Creator> =
460 vec![mpl_token_metadata::state::Creator {
461 address: candy_machine_creator.key(),
462 verified: true,
463 share: 0,
464 }];
465
466 for c in &candy_machine.data.creators {
467 creators.push(mpl_token_metadata::state::Creator {
468 address: c.address,
469 verified: false,
470 share: c.share,
471 });
472 }
473
474 let metadata_infos = vec![
475 ctx.accounts.metadata.to_account_info(),
476 ctx.accounts.mint.to_account_info(),
477 ctx.accounts.mint_authority.to_account_info(),
478 ctx.accounts.payer.to_account_info(),
479 ctx.accounts.token_metadata_program.to_account_info(),
480 ctx.accounts.token_program.to_account_info(),
481 ctx.accounts.system_program.to_account_info(),
482 ctx.accounts.rent.to_account_info(),
483 candy_machine_creator.to_account_info(),
484 ];
485
486 let master_edition_infos = vec![
487 ctx.accounts.master_edition.to_account_info(),
488 ctx.accounts.mint.to_account_info(),
489 ctx.accounts.mint_authority.to_account_info(),
490 ctx.accounts.payer.to_account_info(),
491 ctx.accounts.metadata.to_account_info(),
492 ctx.accounts.token_metadata_program.to_account_info(),
493 ctx.accounts.token_program.to_account_info(),
494 ctx.accounts.system_program.to_account_info(),
495 ctx.accounts.rent.to_account_info(),
496 candy_machine_creator.to_account_info(),
497 ];
498 invoke_signed(
499 &create_metadata_accounts_v2(
500 ctx.accounts.token_metadata_program.key(),
501 ctx.accounts.metadata.key(),
502 ctx.accounts.mint.key(),
503 ctx.accounts.mint_authority.key(),
504 ctx.accounts.payer.key(),
505 candy_machine_creator.key(),
506 config_line.name,
507 candy_machine.data.symbol.clone(),
508 config_line.uri,
509 Some(creators),
510 candy_machine.data.seller_fee_basis_points,
511 true,
512 candy_machine.data.is_mutable,
513 None,
514 None,
515 ),
516 metadata_infos.as_slice(),
517 &[&authority_seeds],
518 )?;
519 invoke_signed(
520 &create_master_edition_v3(
521 ctx.accounts.token_metadata_program.key(),
522 ctx.accounts.master_edition.key(),
523 ctx.accounts.mint.key(),
524 candy_machine_creator.key(),
525 ctx.accounts.mint_authority.key(),
526 ctx.accounts.metadata.key(),
527 ctx.accounts.payer.key(),
528 Some(candy_machine.data.max_supply),
529 ),
530 master_edition_infos.as_slice(),
531 &[&authority_seeds],
532 )?;
533
534 let mut new_update_authority = Some(candy_machine.authority);
535
536 if !candy_machine.data.retain_authority {
537 new_update_authority = Some(ctx.accounts.update_authority.key());
538 }
539 invoke_signed(
540 &update_metadata_accounts_v2(
541 ctx.accounts.token_metadata_program.key(),
542 ctx.accounts.metadata.key(),
543 candy_machine_creator.key(),
544 new_update_authority,
545 None,
546 Some(true),
547 if !candy_machine.data.is_mutable {
548 Some(false)
549 } else {
550 None
551 },
552 ),
553 &[
554 ctx.accounts.token_metadata_program.to_account_info(),
555 ctx.accounts.metadata.to_account_info(),
556 candy_machine_creator.to_account_info(),
557 ],
558 &[&authority_seeds],
559 )?;
560
561 Ok(())
562 }
563
564 pub fn set_collection_during_mint(ctx: Context<SetCollectionDuringMint>) -> ProgramResult {
565 let ixs = &ctx.accounts.instructions;
566 let previous_instruction = get_instruction_relative(-1, ixs)?;
567 if &previous_instruction.program_id != &candy_machine::id() {
568 msg!(
569 "Transaction had ix with program id {}",
570 &previous_instruction.program_id
571 );
572 return Ok(());
573 }
574 if ctx.accounts.metadata.owner != &mpl_token_metadata::id()
576 || ctx.accounts.token_metadata_program.data_len() == 0
577 {
578 return Ok(());
579 }
580
581 let discriminator = &previous_instruction.data[0..8];
582 if discriminator != [211, 57, 6, 167, 15, 219, 35, 251] {
583 msg!("Transaction had ix with data {:?}", discriminator);
584 return Ok(());
585 }
586
587 let mint_ix_accounts = previous_instruction.accounts;
588 let mint_ix_cm = mint_ix_accounts[0].pubkey;
589 let mint_ix_metadata = mint_ix_accounts[4].pubkey;
590 let signer = mint_ix_accounts[6].pubkey;
591 let candy_key = ctx.accounts.candy_machine.key();
592 let metadata = ctx.accounts.metadata.key();
593 let payer = ctx.accounts.payer.key();
594
595 if &signer != &payer {
596 msg!(
597 "Signer with pubkey {} does not match the mint ix Signer with pubkey {}",
598 mint_ix_cm,
599 candy_key
600 );
601 return Ok(());
602 }
603 if &mint_ix_cm != &candy_key {
604 msg!("Candy Machine with pubkey {} does not match the mint ix Candy Machine with pubkey {}", mint_ix_cm, candy_key);
605 return Ok(());
606 }
607 if mint_ix_metadata != metadata {
608 msg!(
609 "Metadata with pubkey {} does not match the mint ix metadata with pubkey {}",
610 mint_ix_metadata,
611 metadata
612 );
613 return Ok(());
614 }
615
616 let collection_pda = &ctx.accounts.collection_pda;
617 let collection_mint = ctx.accounts.collection_mint.to_account_info();
618 if &collection_pda.mint != &collection_mint.key() {
619 return Ok(());
620 }
621 let seeds = [b"collection".as_ref(), candy_key.as_ref()];
622 let bump = assert_derivation(
623 &candy_machine::id(),
624 &collection_pda.to_account_info(),
625 &seeds,
626 )?;
627 let signer_seeds = [b"collection".as_ref(), candy_key.as_ref(), &[bump]];
628 let set_collection_infos = vec![
629 ctx.accounts.metadata.to_account_info(),
630 collection_pda.to_account_info(),
631 ctx.accounts.payer.to_account_info(),
632 ctx.accounts.authority.to_account_info(),
633 collection_mint.to_account_info(),
634 ctx.accounts.collection_metadata.to_account_info(),
635 ctx.accounts.collection_master_edition.to_account_info(),
636 ctx.accounts.collection_authority_record.to_account_info(),
637 ];
638 invoke_signed(
639 &set_and_verify_collection(
640 ctx.accounts.token_metadata_program.key(),
641 ctx.accounts.metadata.key(),
642 collection_pda.key(),
643 ctx.accounts.payer.key(),
644 ctx.accounts.authority.key(),
645 collection_mint.key(),
646 ctx.accounts.collection_metadata.key(),
647 ctx.accounts.collection_master_edition.key(),
648 Some(ctx.accounts.collection_authority_record.key()),
649 ),
650 set_collection_infos.as_slice(),
651 &[&signer_seeds],
652 )?;
653 Ok(())
654 }
655
656 pub fn update_candy_machine(
657 ctx: Context<UpdateCandyMachine>,
658 data: CandyMachineData,
659 ) -> ProgramResult {
660 let candy_machine = &mut ctx.accounts.candy_machine;
661
662 if data.items_available != candy_machine.data.items_available
663 && data.hidden_settings.is_none()
664 {
665 return Err(ErrorCode::CannotChangeNumberOfLines.into());
666 }
667
668 if candy_machine.data.items_available > 0
669 && candy_machine.data.hidden_settings.is_none()
670 && data.hidden_settings.is_some()
671 {
672 return Err(ErrorCode::CannotSwitchToHiddenSettings.into());
673 }
674
675 candy_machine.wallet = ctx.accounts.wallet.key();
676 candy_machine.data = data;
677
678 if ctx.remaining_accounts.len() > 0 {
679 candy_machine.token_mint = Some(ctx.remaining_accounts[0].key())
680 } else {
681 candy_machine.token_mint = None;
682 }
683 Ok(())
684 }
685
686 pub fn add_config_lines(
687 ctx: Context<AddConfigLines>,
688 index: u32,
689 config_lines: Vec<ConfigLine>,
690 ) -> ProgramResult {
691 let candy_machine = &mut ctx.accounts.candy_machine;
692 let account = candy_machine.to_account_info();
693 let current_count = get_config_count(&account.data.borrow_mut())?;
694 let mut data = account.data.borrow_mut();
695 let mut fixed_config_lines = vec![];
696 if index > (candy_machine.data.items_available as u32) - 1 {
699 return Err(ErrorCode::IndexGreaterThanLength.into());
700 }
701 if candy_machine.data.hidden_settings.is_some() {
702 return Err(ErrorCode::HiddenSettingsConfigsDoNotHaveConfigLines.into());
703 }
704 for line in &config_lines {
705 let mut array_of_zeroes = vec![];
706 while array_of_zeroes.len() < MAX_NAME_LENGTH - line.name.len() {
707 array_of_zeroes.push(0u8);
708 }
709 let name = line.name.clone() + std::str::from_utf8(&array_of_zeroes).unwrap();
710
711 let mut array_of_zeroes = vec![];
712 while array_of_zeroes.len() < MAX_URI_LENGTH - line.uri.len() {
713 array_of_zeroes.push(0u8);
714 }
715 let uri = line.uri.clone() + std::str::from_utf8(&array_of_zeroes).unwrap();
716 fixed_config_lines.push(ConfigLine { name, uri })
717 }
718
719 let as_vec = fixed_config_lines.try_to_vec()?;
720 let serialized: &[u8] = &as_vec.as_slice()[4..];
722
723 let position = CONFIG_ARRAY_START + 4 + (index as usize) * CONFIG_LINE_SIZE;
724
725 let array_slice: &mut [u8] =
726 &mut data[position..position + fixed_config_lines.len() * CONFIG_LINE_SIZE];
727
728 array_slice.copy_from_slice(serialized);
729
730 let bit_mask_vec_start = CONFIG_ARRAY_START
731 + 4
732 + (candy_machine.data.items_available as usize) * CONFIG_LINE_SIZE
733 + 4;
734
735 let mut new_count = current_count;
736 for i in 0..fixed_config_lines.len() {
737 let position = (index as usize)
738 .checked_add(i)
739 .ok_or(ErrorCode::NumericalOverflowError)?;
740 let my_position_in_vec = bit_mask_vec_start
741 + position
742 .checked_div(8)
743 .ok_or(ErrorCode::NumericalOverflowError)?;
744 let position_from_right = 7 - position
745 .checked_rem(8)
746 .ok_or(ErrorCode::NumericalOverflowError)?;
747 let mask = u8::pow(2, position_from_right as u32);
748
749 let old_value_in_vec = data[my_position_in_vec];
750 data[my_position_in_vec] = data[my_position_in_vec] | mask;
751 msg!(
752 "My position in vec is {} my mask is going to be {}, the old value is {}",
753 position,
754 mask,
755 old_value_in_vec
756 );
757 msg!(
758 "My new value is {} and my position from right is {}",
759 data[my_position_in_vec],
760 position_from_right
761 );
762 if old_value_in_vec != data[my_position_in_vec] {
763 msg!("Increasing count");
764 new_count = new_count
765 .checked_add(1)
766 .ok_or(ErrorCode::NumericalOverflowError)?;
767 }
768 }
769
770 data[CONFIG_ARRAY_START..CONFIG_ARRAY_START + 4]
772 .copy_from_slice(&(new_count as u32).to_le_bytes());
773
774 Ok(())
775 }
776
777 pub fn initialize_candy_machine(
778 ctx: Context<InitializeCandyMachine>,
779 data: CandyMachineData,
780 ) -> ProgramResult {
781 let candy_machine_account = &mut ctx.accounts.candy_machine;
782
783 if data.uuid.len() != 6 {
784 return Err(ErrorCode::UuidMustBeExactly6Length.into());
785 }
786
787 let mut candy_machine = CandyMachine {
788 data,
789 authority: ctx.accounts.authority.key(),
790 wallet: ctx.accounts.wallet.key(),
791 token_mint: None,
792 items_redeemed: 0,
793 };
794
795 if ctx.remaining_accounts.len() > 0 {
796 let token_mint_info = &ctx.remaining_accounts[0];
797 let _token_mint: Mint = assert_initialized(&token_mint_info)?;
798 let token_account: spl_token::state::Account =
799 assert_initialized(&ctx.accounts.wallet)?;
800
801 assert_owned_by(&token_mint_info, &spl_token::id())?;
802 assert_owned_by(&ctx.accounts.wallet, &spl_token::id())?;
803
804 if token_account.mint != token_mint_info.key() {
805 return Err(ErrorCode::MintMismatch.into());
806 }
807
808 candy_machine.token_mint = Some(*token_mint_info.key);
809 }
810
811 let mut array_of_zeroes = vec![];
812 while array_of_zeroes.len() < MAX_SYMBOL_LENGTH - candy_machine.data.symbol.len() {
813 array_of_zeroes.push(0u8);
814 }
815 let new_symbol =
816 candy_machine.data.symbol.clone() + std::str::from_utf8(&array_of_zeroes).unwrap();
817 candy_machine.data.symbol = new_symbol;
818
819 if candy_machine.data.creators.len() > MAX_CREATOR_LIMIT - 1 {
821 return Err(ErrorCode::TooManyCreators.into());
822 }
823
824 let mut new_data = CandyMachine::discriminator().try_to_vec().unwrap();
825 new_data.append(&mut candy_machine.try_to_vec().unwrap());
826 let mut data = candy_machine_account.data.borrow_mut();
827 for i in 0..new_data.len() {
829 data[i] = new_data[i];
830 }
831
832 let vec_start = CONFIG_ARRAY_START
833 + 4
834 + (candy_machine.data.items_available as usize) * CONFIG_LINE_SIZE;
835 let as_bytes = (candy_machine
836 .data
837 .items_available
838 .checked_div(8)
839 .ok_or(ErrorCode::NumericalOverflowError)? as u32)
840 .to_le_bytes();
841 for i in 0..4 {
842 data[vec_start + i] = as_bytes[i]
843 }
844
845 Ok(())
846 }
847
848 pub fn set_collection(ctx: Context<SetCollection>) -> ProgramResult {
849 let mint = ctx.accounts.mint.to_account_info();
850 let metadata: Metadata =
851 Metadata::from_account_info(&ctx.accounts.metadata.to_account_info())?;
852 if &metadata.update_authority != &ctx.accounts.authority.key() {
853 return Err(ErrorCode::IncorrectCollectionAuthority.into());
854 };
855 if &metadata.mint != &mint.key() {
856 return Err(MetadataError::MintMismatch.into());
857 }
858 let edition = ctx.accounts.edition.to_account_info();
859 let authority_record = ctx.accounts.collection_authority_record.to_account_info();
860 let candy_machine = &ctx.accounts.candy_machine;
861 assert_master_edition(&metadata, &edition)?;
862 if authority_record.data_is_empty() {
863 let approve_collection_infos = vec![
864 authority_record.clone(),
865 ctx.accounts.collection_pda.to_account_info(),
866 ctx.accounts.authority.to_account_info(),
867 ctx.accounts.payer.to_account_info(),
868 ctx.accounts.metadata.to_account_info(),
869 mint.clone(),
870 ctx.accounts.system_program.to_account_info(),
871 ctx.accounts.rent.to_account_info(),
872 ];
873 msg!(
874 "About to approve collection authority for {} with new authority {}.",
875 ctx.accounts.metadata.key(),
876 ctx.accounts.collection_pda.key
877 );
878 invoke(
879 &approve_collection_authority(
880 ctx.accounts.token_metadata_program.key(),
881 authority_record.key(),
882 ctx.accounts.collection_pda.to_account_info().key(),
883 ctx.accounts.authority.key(),
884 ctx.accounts.payer.key(),
885 ctx.accounts.metadata.key(),
886 mint.key.clone(),
887 ),
888 approve_collection_infos.as_slice(),
889 )?;
890 msg!(
891 "Successfully approved collection authority. Now setting PDA mint to {}.",
892 mint.key()
893 );
894 }
895 if ctx.accounts.collection_pda.data_is_empty() {
896 create_or_allocate_account_raw(
897 crate::id(),
898 &ctx.accounts.collection_pda.to_account_info(),
899 &ctx.accounts.rent.to_account_info(),
900 &ctx.accounts.system_program.to_account_info(),
901 &ctx.accounts.authority.to_account_info(),
902 COLLECTION_PDA_SIZE,
903 &[
904 b"collection".as_ref(),
905 &candy_machine.key().as_ref(),
906 &[*ctx.bumps.get("collection_pda").unwrap()],
907 ],
908 )?;
909 }
910 let mut data_ref: &mut [u8] = &mut ctx.accounts.collection_pda.try_borrow_mut_data()?;
911 let mut collection_pda_object: CollectionPDA =
912 AnchorDeserialize::deserialize(&mut &*data_ref)?;
913 collection_pda_object.mint = mint.key();
914 collection_pda_object.candy_machine = candy_machine.key();
915 collection_pda_object.try_serialize(&mut data_ref)?;
916 Ok(())
917 }
918
919 pub fn remove_collection(ctx: Context<RemoveCollection>) -> ProgramResult {
920 let mint = ctx.accounts.mint.to_account_info();
921 let metadata: Metadata =
922 Metadata::from_account_info(&ctx.accounts.metadata.to_account_info())?;
923 if &metadata.update_authority != &ctx.accounts.authority.key() {
924 return Err(ErrorCode::IncorrectCollectionAuthority.into());
925 };
926 if &metadata.mint != &mint.key() {
927 return Err(MetadataError::MintMismatch.into());
928 }
929
930 let authority_record = ctx.accounts.collection_authority_record.to_account_info();
931
932 let revoke_collection_infos = vec![
933 authority_record.clone(),
934 ctx.accounts.collection_pda.to_account_info(),
935 ctx.accounts.authority.to_account_info(),
936 ctx.accounts.metadata.to_account_info(),
937 mint.clone(),
938 ];
939 msg!(
940 "About to revoke collection authority for {}.",
941 ctx.accounts.metadata.key()
942 );
943 invoke(
944 &revoke_collection_authority(
945 ctx.accounts.token_metadata_program.key(),
946 authority_record.key(),
947 ctx.accounts.collection_pda.key(),
948 ctx.accounts.authority.key(),
949 ctx.accounts.metadata.key(),
950 mint.key(),
951 ),
952 revoke_collection_infos.as_slice(),
953 )?;
954 Ok(())
955 }
956
957 pub fn update_authority(
958 ctx: Context<UpdateCandyMachine>,
959 new_authority: Option<Pubkey>,
960 ) -> ProgramResult {
961 let candy_machine = &mut ctx.accounts.candy_machine;
962
963 if let Some(new_auth) = new_authority {
964 candy_machine.authority = new_auth;
965 }
966
967 Ok(())
968 }
969
970 pub fn withdraw_funds<'info>(ctx: Context<WithdrawFunds<'info>>) -> ProgramResult {
971 let authority = &ctx.accounts.authority;
972 let pay = &ctx.accounts.candy_machine.to_account_info();
973 let snapshot: u64 = pay.lamports();
974
975 **pay.lamports.borrow_mut() = 0;
976
977 **authority.lamports.borrow_mut() = authority
978 .lamports()
979 .checked_add(snapshot)
980 .ok_or(ErrorCode::NumericalOverflowError)?;
981
982 if ctx.remaining_accounts.len() > 0 {
983 let seeds = [b"collection".as_ref(), pay.key.as_ref()];
984 let pay = &ctx.remaining_accounts[0];
985 if &pay.key() != &Pubkey::find_program_address(&seeds, &candy_machine::id()).0 {
986 return Err(ErrorCode::MismatchedCollectionPDA.into());
987 }
988 let snapshot: u64 = pay.lamports();
989 **pay.lamports.borrow_mut() = 0;
990 **authority.lamports.borrow_mut() = authority
991 .lamports()
992 .checked_add(snapshot)
993 .ok_or(ErrorCode::NumericalOverflowError)?;
994 }
995
996 Ok(())
997 }
998}
999
1000fn get_space_for_candy(data: CandyMachineData) -> core::result::Result<usize, ProgramError> {
1001 let num = if data.hidden_settings.is_some() {
1002 CONFIG_ARRAY_START
1003 } else {
1004 CONFIG_ARRAY_START
1005 + 4
1006 + (data.items_available as usize) * CONFIG_LINE_SIZE
1007 + 8
1008 + 2 * ((data
1009 .items_available
1010 .checked_div(8)
1011 .ok_or(ErrorCode::NumericalOverflowError)?
1012 + 1) as usize)
1013 };
1014
1015 Ok(num)
1016}
1017
1018#[derive(Accounts)]
1020#[instruction(data: CandyMachineData)]
1021pub struct InitializeCandyMachine<'info> {
1022 #[account(zero, rent_exempt = skip, constraint = candy_machine.to_account_info().owner == program_id && candy_machine.to_account_info().data_len() >= get_space_for_candy(data) ?)]
1024 candy_machine: UncheckedAccount<'info>,
1025 wallet: UncheckedAccount<'info>,
1027 authority: UncheckedAccount<'info>,
1029 payer: Signer<'info>,
1030 system_program: Program<'info, System>,
1031 rent: Sysvar<'info, Rent>,
1032}
1033
1034#[derive(Accounts)]
1036pub struct SetCollectionDuringMint<'info> {
1037 #[account(has_one = authority)]
1038 candy_machine: Account<'info, CandyMachine>,
1039 metadata: UncheckedAccount<'info>,
1041 payer: Signer<'info>,
1042 #[account(mut, seeds = [b"collection".as_ref(), candy_machine.to_account_info().key.as_ref()], bump)]
1043 collection_pda: Account<'info, CollectionPDA>,
1044 #[account(address = mpl_token_metadata::id())]
1046 token_metadata_program: UncheckedAccount<'info>,
1047 #[account(address = sysvar::instructions::id())]
1049 instructions: UncheckedAccount<'info>,
1050 collection_mint: UncheckedAccount<'info>,
1052 collection_metadata: UncheckedAccount<'info>,
1054 collection_master_edition: UncheckedAccount<'info>,
1056 authority: UncheckedAccount<'info>,
1058 collection_authority_record: UncheckedAccount<'info>,
1060}
1061
1062#[derive(Accounts)]
1064pub struct SetCollection<'info> {
1065 #[account(has_one = authority)]
1066 candy_machine: Account<'info, CandyMachine>,
1067 authority: Signer<'info>,
1068 #[account(mut, seeds = [b"collection".as_ref(), candy_machine.to_account_info().key.as_ref()], bump)]
1070 collection_pda: UncheckedAccount<'info>,
1071 payer: Signer<'info>,
1072 system_program: Program<'info, System>,
1073 rent: Sysvar<'info, Rent>,
1074
1075 metadata: UncheckedAccount<'info>,
1077 mint: UncheckedAccount<'info>,
1079 edition: UncheckedAccount<'info>,
1081 #[account(mut)]
1083 collection_authority_record: UncheckedAccount<'info>,
1084 #[account(address = mpl_token_metadata::id())]
1086 token_metadata_program: UncheckedAccount<'info>,
1087}
1088
1089#[derive(Accounts)]
1091pub struct RemoveCollection<'info> {
1092 #[account(has_one = authority)]
1093 candy_machine: Account<'info, CandyMachine>,
1094 authority: Signer<'info>,
1095 #[account(mut, seeds = [b"collection".as_ref(), candy_machine.to_account_info().key.as_ref()], bump, close = authority)]
1096 collection_pda: Account<'info, CollectionPDA>,
1097 metadata: UncheckedAccount<'info>,
1099 mint: UncheckedAccount<'info>,
1101 #[account(mut)]
1103 collection_authority_record: UncheckedAccount<'info>,
1104 #[account(address = mpl_token_metadata::id())]
1106 token_metadata_program: UncheckedAccount<'info>,
1107}
1108
1109#[derive(Accounts)]
1111pub struct AddConfigLines<'info> {
1112 #[account(mut, has_one = authority)]
1113 candy_machine: Account<'info, CandyMachine>,
1114 authority: Signer<'info>,
1115}
1116
1117#[derive(Accounts)]
1119pub struct WithdrawFunds<'info> {
1120 #[account(mut, has_one = authority)]
1121 candy_machine: Account<'info, CandyMachine>,
1122 #[account(address = candy_machine.authority)]
1123 authority: Signer<'info>,
1124 }
1127
1128#[derive(Accounts)]
1130#[instruction(creator_bump: u8)]
1131pub struct MintNFT<'info> {
1132 #[account(
1133 mut,
1134 has_one = wallet
1135 )]
1136 candy_machine: Box<Account<'info, CandyMachine>>,
1137 #[account(seeds = [PREFIX.as_bytes(), candy_machine.key().as_ref()], bump = creator_bump)]
1139 candy_machine_creator: UncheckedAccount<'info>,
1140 payer: Signer<'info>,
1141 #[account(mut)]
1143 wallet: UncheckedAccount<'info>,
1144 #[account(mut)]
1148 metadata: UncheckedAccount<'info>,
1149 #[account(mut)]
1151 mint: UncheckedAccount<'info>,
1152 mint_authority: Signer<'info>,
1153 update_authority: Signer<'info>,
1154 #[account(mut)]
1156 master_edition: UncheckedAccount<'info>,
1157 #[account(address = mpl_token_metadata::id())]
1159 token_metadata_program: UncheckedAccount<'info>,
1160 token_program: Program<'info, Token>,
1161 system_program: Program<'info, System>,
1162 rent: Sysvar<'info, Rent>,
1163 clock: Sysvar<'info, Clock>,
1164 recent_blockhashes: UncheckedAccount<'info>,
1167 #[account(address = sysvar::instructions::id())]
1169 instruction_sysvar_account: UncheckedAccount<'info>,
1170 }
1184
1185#[derive(Accounts)]
1187pub struct UpdateCandyMachine<'info> {
1188 #[account(
1189 mut,
1190 has_one = authority
1191 )]
1192 candy_machine: Account<'info, CandyMachine>,
1193 authority: Signer<'info>,
1194 wallet: UncheckedAccount<'info>,
1196}
1197
1198#[account]
1200#[derive(Default)]
1201pub struct CandyMachine {
1202 pub authority: Pubkey,
1203 pub wallet: Pubkey,
1204 pub token_mint: Option<Pubkey>,
1205 pub items_redeemed: u64,
1206 pub data: CandyMachineData,
1207 }
1212
1213const COLLECTION_PDA_SIZE: usize = 8 + 64;
1214
1215#[account]
1217#[derive(Default, Debug)]
1218pub struct CollectionPDA {
1219 pub mint: Pubkey,
1220 pub candy_machine: Pubkey,
1221}
1222
1223#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
1224pub struct WhitelistMintSettings {
1225 pub mode: WhitelistMintMode,
1226 pub mint: Pubkey,
1227 pub presale: bool,
1228 pub discount_price: Option<u64>,
1229}
1230
1231#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
1232pub enum WhitelistMintMode {
1233 BurnEveryTime,
1237 NeverBurn,
1238}
1239
1240#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
1242pub struct CandyMachineData {
1243 pub uuid: String,
1244 pub price: u64,
1245 pub symbol: String,
1247 pub seller_fee_basis_points: u16,
1249 pub max_supply: u64,
1250 pub is_mutable: bool,
1251 pub retain_authority: bool,
1252 pub go_live_date: Option<i64>,
1253 pub end_settings: Option<EndSettings>,
1254 pub creators: Vec<Creator>,
1255 pub hidden_settings: Option<HiddenSettings>,
1256 pub whitelist_mint_settings: Option<WhitelistMintSettings>,
1257 pub items_available: u64,
1258 pub gatekeeper: Option<GatekeeperConfig>,
1260}
1261
1262#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
1264pub struct GatekeeperConfig {
1265 pub gatekeeper_network: Pubkey,
1267 pub expire_on_use: bool,
1270}
1271
1272#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
1273pub enum EndSettingType {
1274 Date,
1275 Amount,
1276}
1277
1278#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
1279pub struct EndSettings {
1280 pub end_setting_type: EndSettingType,
1281 pub number: u64,
1282}
1283
1284pub const CONFIG_ARRAY_START: usize = 8 + 32 + 32 + 33 + 4 + 6 + 8 + 8 + 9 + 10 + 4 + MAX_SYMBOL_LENGTH + 2 + 4 + MAX_CREATOR_LIMIT * MAX_CREATOR_LEN + 8 + 1 + 1 + 1 + 4 + MAX_NAME_LENGTH + 4 + MAX_URI_LENGTH + 32 + 4 + 8 + 1 + 1 + 1 + 9 + 32 + 1 + 32 + 1 ;
1312
1313#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
1315pub struct HiddenSettings {
1316 pub name: String,
1317 pub uri: String,
1318 pub hash: [u8; 32],
1319}
1320
1321pub fn get_config_count(data: &RefMut<&mut [u8]>) -> core::result::Result<usize, ProgramError> {
1322 return Ok(u32::from_le_bytes(*array_ref![data, CONFIG_ARRAY_START, 4]) as usize);
1323}
1324
1325pub fn get_good_index(
1326 arr: &mut RefMut<&mut [u8]>,
1327 items_available: usize,
1328 index: usize,
1329 pos: bool,
1330) -> core::result::Result<(usize, bool), ProgramError> {
1331 let mut index_to_use = index;
1332 let mut taken = 1;
1333 let mut found = false;
1334 let bit_mask_vec_start = CONFIG_ARRAY_START
1335 + 4
1336 + (items_available) * CONFIG_LINE_SIZE
1337 + 4
1338 + items_available
1339 .checked_div(8)
1340 .ok_or(ErrorCode::NumericalOverflowError)?
1341 + 4;
1342
1343 while taken > 0 && index_to_use < items_available {
1344 let my_position_in_vec = bit_mask_vec_start
1345 + index_to_use
1346 .checked_div(8)
1347 .ok_or(ErrorCode::NumericalOverflowError)?;
1348 if arr[my_position_in_vec] == 255 {
1354 let eight_remainder = 8 - index_to_use
1356 .checked_rem(8)
1357 .ok_or(ErrorCode::NumericalOverflowError)?;
1358 let reversed = 8 - eight_remainder + 1;
1359 if (eight_remainder != 0 && pos) || (reversed != 0 && !pos) {
1360 if pos {
1362 index_to_use += eight_remainder;
1363 } else {
1364 if index_to_use < 8 {
1365 break;
1366 }
1367 index_to_use -= reversed;
1368 }
1369 } else {
1370 if pos {
1372 index_to_use += 8;
1373 } else {
1374 index_to_use -= 8;
1375 }
1376 }
1377 } else {
1378 let position_from_right = 7 - index_to_use
1379 .checked_rem(8)
1380 .ok_or(ErrorCode::NumericalOverflowError)?;
1381 let mask = u8::pow(2, position_from_right as u32);
1382
1383 taken = mask & arr[my_position_in_vec];
1384 if taken > 0 {
1385 if pos {
1387 index_to_use += 1;
1388 } else {
1389 if index_to_use == 0 {
1390 break;
1391 }
1392 index_to_use -= 1;
1393 }
1394 } else if taken == 0 {
1395 found = true;
1397 arr[my_position_in_vec] = arr[my_position_in_vec] | mask;
1398 }
1399 }
1400 }
1401 Ok((index_to_use, found))
1402}
1403
1404pub fn get_config_line<'info>(
1405 a: &Account<'info, CandyMachine>,
1406 index: usize,
1407 mint_number: u64,
1408) -> core::result::Result<ConfigLine, ProgramError> {
1409 if let Some(hs) = &a.data.hidden_settings {
1410 return Ok(ConfigLine {
1411 name: hs.name.clone() + "#" + &(mint_number + 1).to_string(),
1412 uri: hs.uri.clone(),
1413 });
1414 }
1415 msg!("Index is set to {:?}", index);
1416 let a_info = a.to_account_info();
1417
1418 let mut arr = a_info.data.borrow_mut();
1419
1420 let (mut index_to_use, good) =
1421 get_good_index(&mut arr, a.data.items_available as usize, index, true)?;
1422 if !good {
1423 let (index_to_use_new, good_new) =
1424 get_good_index(&mut arr, a.data.items_available as usize, index, false)?;
1425 index_to_use = index_to_use_new;
1426 if !good_new {
1427 return Err(ErrorCode::CannotFindUsableConfigLine.into());
1428 }
1429 }
1430
1431 msg!(
1432 "Index actually ends up due to used bools {:?}",
1433 index_to_use
1434 );
1435 if arr[CONFIG_ARRAY_START + 4 + index_to_use * (CONFIG_LINE_SIZE)] == 1 {
1436 return Err(ErrorCode::CannotFindUsableConfigLine.into());
1437 }
1438
1439 let data_array = &mut arr[CONFIG_ARRAY_START + 4 + index_to_use * (CONFIG_LINE_SIZE)
1440 ..CONFIG_ARRAY_START + 4 + (index_to_use + 1) * (CONFIG_LINE_SIZE)];
1441
1442 let mut name_vec = vec![];
1443 let mut uri_vec = vec![];
1444 for i in 4..4 + MAX_NAME_LENGTH {
1445 if data_array[i] == 0 {
1446 break;
1447 }
1448 name_vec.push(data_array[i])
1449 }
1450 for i in 8 + MAX_NAME_LENGTH..8 + MAX_NAME_LENGTH + MAX_URI_LENGTH {
1451 if data_array[i] == 0 {
1452 break;
1453 }
1454 uri_vec.push(data_array[i])
1455 }
1456 let config_line: ConfigLine = ConfigLine {
1457 name: match String::from_utf8(name_vec) {
1458 Ok(val) => val,
1459 Err(_) => return Err(ErrorCode::InvalidString.into()),
1460 },
1461 uri: match String::from_utf8(uri_vec) {
1462 Ok(val) => val,
1463 Err(_) => return Err(ErrorCode::InvalidString.into()),
1464 },
1465 };
1466
1467 Ok(config_line)
1468}
1469
1470pub const CONFIG_LINE_SIZE: usize = 4 + MAX_NAME_LENGTH + 4 + MAX_URI_LENGTH;
1472
1473#[derive(AnchorSerialize, AnchorDeserialize, Debug)]
1474pub struct ConfigLine {
1475 pub name: String,
1476 pub uri: String,
1478}
1479
1480#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
1483pub struct Creator {
1484 pub address: Pubkey,
1485 pub verified: bool,
1486 pub share: u8,
1488}
1489
1490#[error_code]
1491pub enum ErrorCode {
1492 #[msg("Account does not have correct owner!")]
1493 IncorrectOwner,
1494 #[msg("Account is not initialized!")]
1495 Uninitialized,
1496 #[msg("Mint Mismatch!")]
1497 MintMismatch,
1498 #[msg("Index greater than length!")]
1499 IndexGreaterThanLength,
1500 #[msg("Numerical overflow error!")]
1501 NumericalOverflowError,
1502 #[msg("Can only provide up to 4 creators to candy machine (because candy machine is one)!")]
1503 TooManyCreators,
1504 #[msg("Uuid must be exactly of 6 length")]
1505 UuidMustBeExactly6Length,
1506 #[msg("Not enough tokens to pay for this minting")]
1507 NotEnoughTokens,
1508 #[msg("Not enough SOL to pay for this minting")]
1509 NotEnoughSOL,
1510 #[msg("Token transfer failed")]
1511 TokenTransferFailed,
1512 #[msg("Candy machine is empty!")]
1513 CandyMachineEmpty,
1514 #[msg("Candy machine is not live!")]
1515 CandyMachineNotLive,
1516 #[msg("Configs that are using hidden uris do not have config lines, they have a single hash representing hashed order")]
1517 HiddenSettingsConfigsDoNotHaveConfigLines,
1518 #[msg("Cannot change number of lines unless is a hidden config")]
1519 CannotChangeNumberOfLines,
1520 #[msg("Derived key invalid")]
1521 DerivedKeyInvalid,
1522 #[msg("Public key mismatch")]
1523 PublicKeyMismatch,
1524 #[msg("No whitelist token present")]
1525 NoWhitelistToken,
1526 #[msg("Token burn failed")]
1527 TokenBurnFailed,
1528 #[msg("Missing gateway app when required")]
1529 GatewayAppMissing,
1530 #[msg("Missing gateway token when required")]
1531 GatewayTokenMissing,
1532 #[msg("Invalid gateway token expire time")]
1533 GatewayTokenExpireTimeInvalid,
1534 #[msg("Missing gateway network expire feature when required")]
1535 NetworkExpireFeatureMissing,
1536 #[msg("Unable to find an unused config line near your random number index")]
1537 CannotFindUsableConfigLine,
1538 #[msg("Invalid string")]
1539 InvalidString,
1540 #[msg("Suspicious transaction detected")]
1541 SuspiciousTransaction,
1542 #[msg("Cannot Switch to Hidden Settings after items available is greater than 0")]
1543 CannotSwitchToHiddenSettings,
1544 #[msg("Incorrect SlotHashes PubKey")]
1545 IncorrectSlotHashesPubkey,
1546 #[msg("Incorrect collection NFT authority")]
1547 IncorrectCollectionAuthority,
1548 #[msg("Collection PDA address is invalid")]
1549 MismatchedCollectionPDA,
1550 #[msg("Provided mint account doesn't match collection PDA mint")]
1551 MismatchedCollectionMint,
1552 #[msg("The metadata account has data in it, and this must be empty to mint a new NFT")]
1553 MetadataAccountMustBeEmpty,
1554}
1555
1556impl From<ErrorCode> for ProgramError {
1557 fn from(_: ErrorCode) -> Self {
1558 todo!()
1559 }
1560}