mpl_candy_machine_nano/
lib.rs

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";
37// here just in case solana removes the var
38const 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        // Note this is the wallet of the Candy machine
57        let wallet = &ctx.accounts.wallet;
58        let payer = &ctx.accounts.payer;
59        let token_program = &ctx.accounts.token_program;
60        //Account name the same for IDL compatability
61        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        // Restrict Who can call Candy Machine via CPI
70        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                // We fail here. Its much cheaper to fail here than to allow a malicious user to add an ix at the end and then fail.
90                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            // stores the expire_time before the verification, since the verification
186            // will update the expire_time of the token and we won't be able to
187            // calculate the creation time
188            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            // verifies that the gatway token was not created before the candy
220            // machine go_live_date (avoids pre-solving the captcha)
221            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                            // when dealing with whitelist, the expire_time can be
226                            // before the go_live_date only if presale enabled
227                            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            // If the user has not actually made this account,
251            // this explodes and we just check normal dates.
252            // If they have, we check amount, if it's > 0 we let them use the logic
253            // if 0, check normal dates.
254            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                            // A non-presale whitelist with no discount price is a forced whitelist
324                            // If a pre-sale has no discount, its no issue, because the "discount"
325                            // is minting first - a presale whitelist always has an open post sale.
326                            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                        // A non-presale whitelist with no discount price is a forced whitelist
354                        // If a pre-sale has no discount, its no issue, because the "discount"
355                        // is minting first - a presale whitelist always has an open post sale.
356                        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            // no whitelist means normal datecheck
383            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        /// Check if the metadata acount has data if not bot fee
575        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        // No risk overflow because you literally cant store this many in an account
697        // going beyond u32 only happens with the hidden store candies, which dont use this.
698        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        // remove unneeded u32 because we're just gonna edit the u32 at the front
721        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        // plug in new count.
771        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        // - 1 because we are going to be a creator
820        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        // god forgive me couldnt think of better way to deal with this
828        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/// Create a new candy machine.
1019#[derive(Accounts)]
1020#[instruction(data: CandyMachineData)]
1021pub struct InitializeCandyMachine<'info> {
1022    /// CHECK: account constraints checked in account trait
1023    #[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    /// CHECK: wallet can be any account and is not written to or read
1026    wallet: UncheckedAccount<'info>,
1027    /// CHECK: authority can be any account and is not written to or read
1028    authority: UncheckedAccount<'info>,
1029    payer: Signer<'info>,
1030    system_program: Program<'info, System>,
1031    rent: Sysvar<'info, Rent>,
1032}
1033
1034/// Sets and verifies the collection during a candy machine mint
1035#[derive(Accounts)]
1036pub struct SetCollectionDuringMint<'info> {
1037    #[account(has_one = authority)]
1038    candy_machine: Account<'info, CandyMachine>,
1039    /// CHECK: account checked in CPI/instruction sysvar
1040    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    /// CHECK: account constraints checked in account trait
1045    #[account(address = mpl_token_metadata::id())]
1046    token_metadata_program: UncheckedAccount<'info>,
1047    /// CHECK: account constraints checked in account trait
1048    #[account(address = sysvar::instructions::id())]
1049    instructions: UncheckedAccount<'info>,
1050    /// CHECK: account checked in CPI
1051    collection_mint: UncheckedAccount<'info>,
1052    /// CHECK: account checked in CPI
1053    collection_metadata: UncheckedAccount<'info>,
1054    /// CHECK: account checked in CPI
1055    collection_master_edition: UncheckedAccount<'info>,
1056    /// CHECK: authority can be any account and is checked in CPI
1057    authority: UncheckedAccount<'info>,
1058    /// CHECK: account checked in CPI
1059    collection_authority_record: UncheckedAccount<'info>,
1060}
1061
1062/// Set the collection PDA for the candy machine
1063#[derive(Accounts)]
1064pub struct SetCollection<'info> {
1065    #[account(has_one = authority)]
1066    candy_machine: Account<'info, CandyMachine>,
1067    authority: Signer<'info>,
1068    /// CHECK: account constraints checked in account trait
1069    #[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    /// CHECK: account checked in CPI
1076    metadata: UncheckedAccount<'info>,
1077    /// CHECK: account checked in CPI
1078    mint: UncheckedAccount<'info>,
1079    /// CHECK: account checked in CPI
1080    edition: UncheckedAccount<'info>,
1081    /// CHECK: account checked in CPI
1082    #[account(mut)]
1083    collection_authority_record: UncheckedAccount<'info>,
1084    /// CHECK: account checked in CPI
1085    #[account(address = mpl_token_metadata::id())]
1086    token_metadata_program: UncheckedAccount<'info>,
1087}
1088
1089/// Set the collection PDA for the candy machine
1090#[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    /// CHECK: account checked in CPI
1098    metadata: UncheckedAccount<'info>,
1099    /// CHECK: account checked in CPI
1100    mint: UncheckedAccount<'info>,
1101    /// CHECK: account checked in CPI
1102    #[account(mut)]
1103    collection_authority_record: UncheckedAccount<'info>,
1104    /// CHECK: account checked in CPI
1105    #[account(address = mpl_token_metadata::id())]
1106    token_metadata_program: UncheckedAccount<'info>,
1107}
1108
1109/// Add multiple config lines to the candy machine.
1110#[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/// Withdraw SOL from candy machine account.
1118#[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    // > Only if collection
1125    // CollectionPDA account
1126}
1127
1128/// Mint a new NFT pseudo-randomly from the config array.
1129#[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    /// CHECK: account constraints checked in account trait
1138    #[account(seeds = [PREFIX.as_bytes(), candy_machine.key().as_ref()], bump = creator_bump)]
1139    candy_machine_creator: UncheckedAccount<'info>,
1140    payer: Signer<'info>,
1141    /// CHECK: wallet can be any account and is not written to or read
1142    #[account(mut)]
1143    wallet: UncheckedAccount<'info>,
1144    // With the following accounts we aren't using anchor macros because they are CPI'd
1145    // through to token-metadata which will do all the validations we need on them.
1146    /// CHECK: account checked in CPI
1147    #[account(mut)]
1148    metadata: UncheckedAccount<'info>,
1149    /// CHECK: account checked in CPI
1150    #[account(mut)]
1151    mint: UncheckedAccount<'info>,
1152    mint_authority: Signer<'info>,
1153    update_authority: Signer<'info>,
1154    /// CHECK: account checked in CPI
1155    #[account(mut)]
1156    master_edition: UncheckedAccount<'info>,
1157    /// CHECK: account checked in CPI
1158    #[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    // Leaving the name the same for IDL backward compatability
1165    /// CHECK: account checked in CPI
1166    recent_blockhashes: UncheckedAccount<'info>,
1167    /// CHECK: account constraints checked in account trait
1168    #[account(address = sysvar::instructions::id())]
1169    instruction_sysvar_account: UncheckedAccount<'info>,
1170    // > Only needed if candy machine has a gatekeeper
1171    // gateway_token
1172    // > Only needed if candy machine has a gatekeeper and it has expire_on_use set to true:
1173    // gateway program
1174    // network_expire_feature
1175    // > Only needed if candy machine has whitelist_mint_settings
1176    // whitelist_token_account
1177    // > Only needed if candy machine has whitelist_mint_settings and mode is BurnEveryTime
1178    // whitelist_token_mint
1179    // whitelist_burn_authority
1180    // > Only needed if candy machine has token mint
1181    // token_account_info
1182    // transfer_authority_info
1183}
1184
1185/// Update the candy machine state.
1186#[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    /// CHECK: wallet can be any account and is not written to or read
1195    wallet: UncheckedAccount<'info>,
1196}
1197
1198/// Candy machine state and config data.
1199#[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    // there's a borsh vec u32 denoting how many actual lines of data there are currently (eventually equals items available)
1208    // There is actually lines and lines of data after this but we explicitly never want them deserialized.
1209    // here there is a borsh vec u32 indicating number of bytes in bitmask array.
1210    // here there is a number of bytes equal to ceil(max_number_of_lines/8) and it is a bit mask used to figure out when to increment borsh vec u32
1211}
1212
1213const COLLECTION_PDA_SIZE: usize = 8 + 64;
1214
1215/// Collection PDA account
1216#[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    // Only captcha uses the bytes, the others just need to have same length
1234    // for front end borsh to not crap itself
1235    // Holds the validation window
1236    BurnEveryTime,
1237    NeverBurn,
1238}
1239
1240/// Candy machine settings data.
1241#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
1242pub struct CandyMachineData {
1243    pub uuid: String,
1244    pub price: u64,
1245    /// The symbol for the asset
1246    pub symbol: String,
1247    /// Royalty basis points that goes to creators in secondary sales (0-10000)
1248    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    /// If [`Some`] requires gateway tokens on mint
1259    pub gatekeeper: Option<GatekeeperConfig>,
1260}
1261
1262/// Configurations options for the gatekeeper.
1263#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
1264pub struct GatekeeperConfig {
1265    /// The network for the gateway token required
1266    pub gatekeeper_network: Pubkey,
1267    /// Whether or not the token should expire after minting.
1268    /// The gatekeeper network must support this if true.
1269    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 + // key
1285    32 + // authority
1286    32 + //wallet
1287    33 + // token mint
1288    4 + 6 + // uuid
1289    8 + // price
1290    8 + // items available
1291    9 + // go live
1292    10 + // end settings
1293    4 + MAX_SYMBOL_LENGTH + // u32 len + symbol
1294    2 + // seller fee basis points
1295    4 + MAX_CREATOR_LIMIT * MAX_CREATOR_LEN + // optional + u32 len + actual vec
1296    8 + //max supply
1297    1 + // is mutable
1298    1 + // retain authority
1299    1 + // option for hidden setting
1300    4 + MAX_NAME_LENGTH + // name length,
1301    4 + MAX_URI_LENGTH + // uri length,
1302    32 + // hash
1303    4 +  // max number of lines;
1304    8 + // items redeemed
1305    1 + // whitelist option
1306    1 + // whitelist mint mode
1307    1 + // allow presale
1308    9 + // discount price
1309    32 + // mint key for whitelist
1310    1 + 32 + 1 // gatekeeper
1311;
1312
1313/// Hidden Settings for large mints used with offline data.
1314#[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        /*msg!(
1349            "My position is {} and value there is {}",
1350            my_position_in_vec,
1351            arr[my_position_in_vec]
1352        );*/
1353        if arr[my_position_in_vec] == 255 {
1354            //msg!("We are screwed here, move on");
1355            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                //msg!("Moving by {}", eight_remainder);
1361                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                //msg!("Moving by 8");
1371                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                //msg!("Index to use {} is taken", index_to_use);
1386                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                //msg!("Index to use {} is not taken, exiting", index_to_use);
1396                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
1470/// Individual config line for storing NFT data pre-mint.
1471pub 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    /// URI pointing to JSON representing the asset
1477    pub uri: String,
1478}
1479
1480// Unfortunate duplication of token metadata so that IDL picks it up.
1481
1482#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
1483pub struct Creator {
1484    pub address: Pubkey,
1485    pub verified: bool,
1486    // In percentages, NOT basis points ;) Watch out!
1487    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}