1use crate::{
2 assertions::{collection::assert_collection_update_is_valid, uses::assert_valid_use},
3 error::MetadataError,
4 state::{
5 get_reservation_list, Data, DataV2, EditionMarker, Key, MasterEditionV1, Metadata,
6 TokenStandard, Uses, EDITION, EDITION_MARKER_BIT_SIZE, MAX_CREATOR_LIMIT, MAX_EDITION_LEN,
7 MAX_EDITION_MARKER_SIZE, MAX_MASTER_EDITION_LEN, MAX_METADATA_LEN, MAX_NAME_LENGTH,
8 MAX_SYMBOL_LENGTH, MAX_URI_LENGTH, PREFIX,
9 },
10};
11use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
12use borsh::{BorshDeserialize, BorshSerialize};
13use solana_program::{
14 account_info::AccountInfo,
15 borsh::try_from_slice_unchecked,
16 entrypoint::ProgramResult,
17 msg,
18 program::{invoke, invoke_signed},
19 program_error::ProgramError,
20 program_option::COption,
21 program_pack::{IsInitialized, Pack},
22 pubkey::Pubkey,
23 system_instruction,
24 sysvar::{rent::Rent, Sysvar},
25};
26use spl_token::{
27 instruction::{set_authority, AuthorityType},
28 state::{Account, Mint},
29};
30use std::convert::TryInto;
31
32pub fn assert_data_valid(
33 data: &Data,
34 update_authority: &Pubkey,
35 existing_metadata: &Metadata,
36 allow_direct_creator_writes: bool,
37 update_authority_is_signer: bool,
38 is_updating: bool,
39) -> ProgramResult {
40 if data.name.len() > MAX_NAME_LENGTH {
41 return Err(MetadataError::NameTooLong.into());
42 }
43
44 if data.symbol.len() > MAX_SYMBOL_LENGTH {
45 return Err(MetadataError::SymbolTooLong.into());
46 }
47
48 if data.uri.len() > MAX_URI_LENGTH {
49 return Err(MetadataError::UriTooLong.into());
50 }
51
52 if data.seller_fee_basis_points > 10000 {
53 return Err(MetadataError::InvalidBasisPoints.into());
54 }
55
56 if data.creators.is_some() {
57 if let Some(creators) = &data.creators {
58 if creators.len() > MAX_CREATOR_LIMIT {
59 return Err(MetadataError::CreatorsTooLong.into());
60 }
61
62 if creators.is_empty() {
63 return Err(MetadataError::CreatorsMustBeAtleastOne.into());
64 } else {
65 let mut found = false;
66 let mut total: u8 = 0;
67 for i in 0..creators.len() {
68 let creator = &creators[i];
69 for j in (i + 1)..creators.len() {
70 if creators[j].address == creator.address {
71 return Err(MetadataError::DuplicateCreatorAddress.into());
72 }
73 }
74
75 total = total
76 .checked_add(creator.share)
77 .ok_or(MetadataError::NumericalOverflowError)?;
78
79 if creator.address == *update_authority {
80 found = true;
81 }
82
83 if (!update_authority_is_signer || creator.address != *update_authority)
88 && !allow_direct_creator_writes
89 {
90 if let Some(existing_creators) = &existing_metadata.data.creators {
91 match existing_creators
92 .iter()
93 .find(|c| c.address == creator.address)
94 {
95 Some(existing_creator) => {
96 if creator.verified && !existing_creator.verified {
97 return Err(
98 MetadataError::CannotVerifyAnotherCreator.into()
99 );
100 } else if !creator.verified && existing_creator.verified {
101 return Err(
102 MetadataError::CannotUnverifyAnotherCreator.into()
103 );
104 }
105 }
106 None => {
107 if creator.verified {
108 return Err(
109 MetadataError::CannotVerifyAnotherCreator.into()
110 );
111 }
112 }
113 }
114 } else {
115 if creator.verified {
116 return Err(MetadataError::CannotVerifyAnotherCreator.into());
117 }
118 }
119 }
120 }
121
122 if !found && !allow_direct_creator_writes && !is_updating {
123 return Err(MetadataError::MustBeOneOfCreators.into());
124 }
125 if total != 100 {
126 return Err(MetadataError::ShareTotalMustBe100.into());
127 }
128 }
129 }
130 }
131
132 Ok(())
133}
134
135pub fn assert_initialized<T: Pack + IsInitialized>(
137 account_info: &AccountInfo,
138) -> Result<T, ProgramError> {
139 let account: T = T::unpack_unchecked(&account_info.data.borrow())?;
140 if !account.is_initialized() {
141 Err(MetadataError::Uninitialized.into())
142 } else {
143 Ok(account)
144 }
145}
146
147#[inline(always)]
150pub fn create_or_allocate_account_raw<'a>(
151 program_id: Pubkey,
152 new_account_info: &AccountInfo<'a>,
153 rent_sysvar_info: &AccountInfo<'a>,
154 system_program_info: &AccountInfo<'a>,
155 payer_info: &AccountInfo<'a>,
156 size: usize,
157 signer_seeds: &[&[u8]],
158) -> ProgramResult {
159 let rent = &Rent::from_account_info(rent_sysvar_info)?;
160 let required_lamports = rent
161 .minimum_balance(size)
162 .max(1)
163 .saturating_sub(new_account_info.lamports());
164
165 if required_lamports > 0 {
166 msg!("Transfer {} lamports to the new account", required_lamports);
167 invoke(
168 &system_instruction::transfer(&payer_info.key, new_account_info.key, required_lamports),
169 &[
170 payer_info.clone(),
171 new_account_info.clone(),
172 system_program_info.clone(),
173 ],
174 )?;
175 }
176
177 let accounts = &[new_account_info.clone(), system_program_info.clone()];
178
179 msg!("Allocate space for the account");
180 invoke_signed(
181 &system_instruction::allocate(new_account_info.key, size.try_into().unwrap()),
182 accounts,
183 &[&signer_seeds],
184 )?;
185
186 msg!("Assign the account to the owning program");
187 invoke_signed(
188 &system_instruction::assign(new_account_info.key, &program_id),
189 accounts,
190 &[&signer_seeds],
191 )?;
192
193 Ok(())
194}
195
196pub fn assert_update_authority_is_correct(
197 metadata: &Metadata,
198 update_authority_info: &AccountInfo,
199) -> ProgramResult {
200 if metadata.update_authority != *update_authority_info.key {
201 return Err(MetadataError::UpdateAuthorityIncorrect.into());
202 }
203
204 if !update_authority_info.is_signer {
205 return Err(MetadataError::UpdateAuthorityIsNotSigner.into());
206 }
207
208 Ok(())
209}
210
211fn unpack_coption_key(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
213 let (tag, body) = array_refs![src, 4, 32];
214 match *tag {
215 [0, 0, 0, 0] => Ok(COption::None),
216 [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
217 _ => Err(ProgramError::InvalidAccountData),
218 }
219}
220
221pub fn get_owner_from_token_account(
223 token_account_info: &AccountInfo,
224) -> Result<Pubkey, ProgramError> {
225 let data = token_account_info.try_borrow_data()?;
227 let owner_data = array_ref![data, 32, 32];
228 Ok(Pubkey::new_from_array(*owner_data))
229}
230
231pub fn get_mint_authority(account_info: &AccountInfo) -> Result<COption<Pubkey>, ProgramError> {
232 let data = account_info.try_borrow_data().unwrap();
235 let authority_bytes = array_ref![data, 0, 36];
236
237 Ok(unpack_coption_key(&authority_bytes)?)
238}
239
240pub fn get_mint_freeze_authority(
241 account_info: &AccountInfo,
242) -> Result<COption<Pubkey>, ProgramError> {
243 let data = account_info.try_borrow_data().unwrap();
244 let authority_bytes = array_ref![data, 36 + 8 + 1 + 1, 36];
245
246 Ok(unpack_coption_key(&authority_bytes)?)
247}
248
249pub fn get_mint_supply(account_info: &AccountInfo) -> Result<u64, ProgramError> {
251 let data = account_info.try_borrow_data().unwrap();
254 let bytes = array_ref![data, 36, 8];
255
256 Ok(u64::from_le_bytes(*bytes))
257}
258
259pub fn get_mint_decimals(account_info: &AccountInfo) -> Result<u8, ProgramError> {
261 let data = account_info.try_borrow_data().unwrap();
264 Ok(data[44])
265}
266
267pub fn assert_mint_authority_matches_mint(
268 mint_authority: &COption<Pubkey>,
269 mint_authority_info: &AccountInfo,
270) -> ProgramResult {
271 match mint_authority {
272 COption::None => {
273 return Err(MetadataError::InvalidMintAuthority.into());
274 }
275 COption::Some(key) => {
276 if mint_authority_info.key != key {
277 return Err(MetadataError::InvalidMintAuthority.into());
278 }
279 }
280 }
281
282 if !mint_authority_info.is_signer {
283 return Err(MetadataError::NotMintAuthority.into());
284 }
285
286 Ok(())
287}
288
289pub fn assert_supply_invariance(
290 master_edition: &MasterEditionV1,
291 printing_mint: &Mint,
292 new_supply: u64,
293) -> ProgramResult {
294 if let Some(max_supply) = master_edition.max_supply {
297 let current_supply = printing_mint
298 .supply
299 .checked_add(master_edition.supply)
300 .ok_or(MetadataError::NumericalOverflowError)?;
301 let new_proposed_supply = current_supply
302 .checked_add(new_supply)
303 .ok_or(MetadataError::NumericalOverflowError)?;
304 if new_proposed_supply > max_supply {
305 return Err(MetadataError::PrintingWouldBreachMaximumSupply.into());
306 }
307 }
308
309 Ok(())
310}
311
312pub fn transfer_mint_authority<'a>(
313 edition_key: &Pubkey,
314 edition_account_info: &AccountInfo<'a>,
315 mint_info: &AccountInfo<'a>,
316 mint_authority_info: &AccountInfo<'a>,
317 token_program_info: &AccountInfo<'a>,
318) -> ProgramResult {
319 msg!("Setting mint authority");
320 let accounts = &[
321 mint_authority_info.clone(),
322 mint_info.clone(),
323 token_program_info.clone(),
324 edition_account_info.clone(),
325 ];
326 invoke_signed(
327 &set_authority(
328 token_program_info.key,
329 mint_info.key,
330 Some(edition_key),
331 AuthorityType::MintTokens,
332 mint_authority_info.key,
333 &[&mint_authority_info.key],
334 )
335 .unwrap(),
336 accounts,
337 &[],
338 )?;
339 msg!("Setting freeze authority");
340 let freeze_authority = get_mint_freeze_authority(mint_info)?;
341 if freeze_authority.is_some() {
342 invoke_signed(
343 &set_authority(
344 token_program_info.key,
345 mint_info.key,
346 Some(&edition_key),
347 AuthorityType::FreezeAccount,
348 mint_authority_info.key,
349 &[&mint_authority_info.key],
350 )
351 .unwrap(),
352 accounts,
353 &[],
354 )?;
355 msg!("Finished setting freeze authority");
356 } else {
357 msg!("Skipping freeze authority because this mint has none")
358 }
359
360 Ok(())
361}
362
363pub fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult {
364 if !rent.is_exempt(account_info.lamports(), account_info.data_len()) {
365 Err(MetadataError::NotRentExempt.into())
366 } else {
367 Ok(())
368 }
369}
370
371pub fn assert_edition_valid(
373 program_id: &Pubkey,
374 mint: &Pubkey,
375 edition_account_info: &AccountInfo,
376) -> ProgramResult {
377 let edition_seeds = &[
378 PREFIX.as_bytes(),
379 program_id.as_ref(),
380 &mint.as_ref(),
381 EDITION.as_bytes(),
382 ];
383 let (edition_key, _) = Pubkey::find_program_address(edition_seeds, program_id);
384 if edition_key != *edition_account_info.key {
385 return Err(MetadataError::InvalidEditionKey.into());
386 }
387
388 Ok(())
389}
390
391pub fn extract_edition_number_from_deprecated_reservation_list(
392 account: &AccountInfo,
393 mint_authority_info: &AccountInfo,
394) -> Result<u64, ProgramError> {
395 let mut reservation_list = get_reservation_list(account)?;
396
397 if let Some(supply_snapshot) = reservation_list.supply_snapshot() {
398 let mut prev_total_offsets: u64 = 0;
399 let mut offset: Option<u64> = None;
400 let mut reservations = reservation_list.reservations();
401 for i in 0..reservations.len() {
402 let mut reservation = &mut reservations[i];
403
404 if reservation.address == *mint_authority_info.key {
405 offset = Some(
406 prev_total_offsets
407 .checked_add(reservation.spots_remaining)
408 .ok_or(MetadataError::NumericalOverflowError)?,
409 );
410 reservation.spots_remaining = reservation
412 .spots_remaining
413 .checked_sub(1)
414 .ok_or(MetadataError::NumericalOverflowError)?;
415
416 reservation_list.set_reservations(reservations)?;
417 reservation_list.save(account)?;
418 break;
419 }
420
421 if reservation.address == solana_program::system_program::id() {
422 prev_total_offsets = reservation.total_spots;
426 } else {
427 prev_total_offsets = prev_total_offsets
428 .checked_add(reservation.total_spots)
429 .ok_or(MetadataError::NumericalOverflowError)?;
430 }
431 }
432
433 match offset {
434 Some(val) => Ok(supply_snapshot
435 .checked_add(val)
436 .ok_or(MetadataError::NumericalOverflowError)?),
437 None => {
438 return Err(MetadataError::AddressNotInReservation.into());
439 }
440 }
441 } else {
442 return Err(MetadataError::ReservationNotSet.into());
443 }
444}
445
446pub fn calculate_edition_number(
447 mint_authority_info: &AccountInfo,
448 reservation_list_info: Option<&AccountInfo>,
449 edition_override: Option<u64>,
450 me_supply: u64,
451) -> Result<u64, ProgramError> {
452 let edition = match reservation_list_info {
453 Some(account) => {
454 extract_edition_number_from_deprecated_reservation_list(account, mint_authority_info)?
455 }
456 None => {
457 if let Some(edit) = edition_override {
458 edit
459 } else {
460 me_supply
461 .checked_add(1)
462 .ok_or(MetadataError::NumericalOverflowError)?
463 }
464 }
465 };
466
467 Ok(edition)
468}
469
470fn get_max_supply_off_master_edition(
471 master_edition_account_info: &AccountInfo,
472) -> Result<Option<u64>, ProgramError> {
473 let data = master_edition_account_info.try_borrow_data()?;
474 if data[9] == 0 {
476 Ok(None)
477 } else {
478 let amount_data = array_ref![data, 10, 8];
479 Ok(Some(u64::from_le_bytes(*amount_data)))
480 }
481}
482
483pub fn get_supply_off_master_edition(
484 master_edition_account_info: &AccountInfo,
485) -> Result<u64, ProgramError> {
486 let data = master_edition_account_info.try_borrow_data()?;
487 let amount_data = array_ref![data, 1, 8];
490 Ok(u64::from_le_bytes(*amount_data))
491}
492
493pub fn calculate_supply_change<'a>(
494 master_edition_account_info: &AccountInfo<'a>,
495 reservation_list_info: Option<&AccountInfo<'a>>,
496 edition_override: Option<u64>,
497 me_supply: u64,
498) -> ProgramResult {
499 if reservation_list_info.is_none() {
500 let new_supply: u64;
501 if let Some(edition) = edition_override {
502 if edition > me_supply {
503 new_supply = edition;
504 } else {
505 new_supply = me_supply
506 }
507 } else {
508 new_supply = me_supply
509 .checked_add(1)
510 .ok_or(MetadataError::NumericalOverflowError)?;
511 }
512
513 if let Some(max) = get_max_supply_off_master_edition(master_edition_account_info)? {
514 if new_supply > max {
515 return Err(MetadataError::MaxEditionsMintedAlready.into());
516 }
517 }
518 let edition_data = &mut master_edition_account_info.data.borrow_mut();
520 let output = array_mut_ref![edition_data, 0, MAX_MASTER_EDITION_LEN];
521
522 let (_key, supply, _the_rest) =
523 mut_array_refs![output, 1, 8, MAX_MASTER_EDITION_LEN - 8 - 1];
524 *supply = new_supply.to_le_bytes();
525 }
526
527 Ok(())
528}
529
530#[allow(clippy::too_many_arguments)]
531pub fn mint_limited_edition<'a>(
532 program_id: &'a Pubkey,
533 master_metadata: Metadata,
534 new_metadata_account_info: &'a AccountInfo<'a>,
535 new_edition_account_info: &'a AccountInfo<'a>,
536 master_edition_account_info: &'a AccountInfo<'a>,
537 mint_info: &'a AccountInfo<'a>,
538 mint_authority_info: &'a AccountInfo<'a>,
539 payer_account_info: &'a AccountInfo<'a>,
540 update_authority_info: &'a AccountInfo<'a>,
541 token_program_account_info: &'a AccountInfo<'a>,
542 system_account_info: &'a AccountInfo<'a>,
543 rent_info: &'a AccountInfo<'a>,
544 reservation_list_info: Option<&'a AccountInfo<'a>>,
547 edition_override: Option<u64>,
550) -> ProgramResult {
551 let me_supply = get_supply_off_master_edition(master_edition_account_info)?;
552 let mint_authority = get_mint_authority(mint_info)?;
553 let mint_supply = get_mint_supply(mint_info)?;
554 assert_mint_authority_matches_mint(&mint_authority, mint_authority_info)?;
555
556 assert_edition_valid(
557 program_id,
558 &master_metadata.mint,
559 master_edition_account_info,
560 )?;
561
562 let edition_seeds = &[
563 PREFIX.as_bytes(),
564 program_id.as_ref(),
565 &mint_info.key.as_ref(),
566 EDITION.as_bytes(),
567 ];
568 let (edition_key, bump_seed) = Pubkey::find_program_address(edition_seeds, program_id);
569 if edition_key != *new_edition_account_info.key {
570 return Err(MetadataError::InvalidEditionKey.into());
571 }
572
573 if reservation_list_info.is_some() && edition_override.is_some() {
574 return Err(MetadataError::InvalidOperation.into());
575 }
576
577 calculate_supply_change(
578 master_edition_account_info,
579 reservation_list_info,
580 edition_override,
581 me_supply,
582 )?;
583
584 if mint_supply != 1 {
585 return Err(MetadataError::EditionsMustHaveExactlyOneToken.into());
586 }
587 let master_data = master_metadata.data;
588 let data_v2 = DataV2 {
590 name: master_data.name,
591 symbol: master_data.symbol,
592 uri: master_data.uri,
593 seller_fee_basis_points: master_data.seller_fee_basis_points,
594 creators: master_data.creators,
595 collection: master_metadata.collection,
596 uses: master_metadata.uses.map(|u| Uses {
597 use_method: u.use_method,
598 remaining: u.total, total: u.total,
600 }),
601 };
602 process_create_metadata_accounts_logic(
605 &program_id,
606 CreateMetadataAccountsLogicArgs {
607 metadata_account_info: new_metadata_account_info,
608 mint_info,
609 mint_authority_info,
610 payer_account_info,
611 update_authority_info,
612 system_account_info,
613 rent_info,
614 },
615 data_v2,
616 true,
617 false,
618 true,
619 true,
620 )?;
621 let edition_authority_seeds = &[
622 PREFIX.as_bytes(),
623 program_id.as_ref(),
624 &mint_info.key.as_ref(),
625 EDITION.as_bytes(),
626 &[bump_seed],
627 ];
628
629 create_or_allocate_account_raw(
630 *program_id,
631 new_edition_account_info,
632 rent_info,
633 system_account_info,
634 payer_account_info,
635 MAX_EDITION_LEN,
636 edition_authority_seeds,
637 )?;
638
639 let edition_data = &mut new_edition_account_info.data.borrow_mut();
641 let output = array_mut_ref![edition_data, 0, MAX_EDITION_LEN];
642
643 let (key, parent, edition, _padding) = mut_array_refs![output, 1, 32, 8, 200];
644
645 *key = [Key::EditionV1 as u8];
646 parent.copy_from_slice(master_edition_account_info.key.as_ref());
647
648 *edition = calculate_edition_number(
649 mint_authority_info,
650 reservation_list_info,
651 edition_override,
652 me_supply,
653 )?
654 .to_le_bytes();
655
656 transfer_mint_authority(
658 &edition_key,
659 new_edition_account_info,
660 mint_info,
661 mint_authority_info,
662 token_program_account_info,
663 )?;
664
665 Ok(())
666}
667
668pub fn spl_token_burn(params: TokenBurnParams<'_, '_>) -> ProgramResult {
669 let TokenBurnParams {
670 mint,
671 source,
672 authority,
673 token_program,
674 amount,
675 authority_signer_seeds,
676 } = params;
677 let mut seeds: Vec<&[&[u8]]> = vec![];
678 if let Some(seed) = authority_signer_seeds {
679 seeds.push(seed);
680 }
681 let result = invoke_signed(
682 &spl_token::instruction::burn(
683 token_program.key,
684 source.key,
685 mint.key,
686 authority.key,
687 &[],
688 amount,
689 )?,
690 &[source, mint, authority, token_program],
691 seeds.as_slice(),
692 );
693 result.map_err(|_| MetadataError::TokenBurnFailed.into())
694}
695
696pub struct TokenBurnParams<'a: 'b, 'b> {
698 pub mint: AccountInfo<'a>,
700 pub source: AccountInfo<'a>,
702 pub amount: u64,
704 pub authority: AccountInfo<'a>,
706 pub authority_signer_seeds: Option<&'b [&'b [u8]]>,
708 pub token_program: AccountInfo<'a>,
710}
711
712pub fn spl_token_mint_to(params: TokenMintToParams<'_, '_>) -> ProgramResult {
713 let TokenMintToParams {
714 mint,
715 destination,
716 authority,
717 token_program,
718 amount,
719 authority_signer_seeds,
720 } = params;
721 let mut seeds: Vec<&[&[u8]]> = vec![];
722 if let Some(seed) = authority_signer_seeds {
723 seeds.push(seed);
724 }
725 let result = invoke_signed(
726 &spl_token::instruction::mint_to(
727 token_program.key,
728 mint.key,
729 destination.key,
730 authority.key,
731 &[],
732 amount,
733 )?,
734 &[mint, destination, authority, token_program],
735 seeds.as_slice(),
736 );
737 result.map_err(|_| MetadataError::TokenMintToFailed.into())
738}
739
740pub struct TokenMintToParams<'a: 'b, 'b> {
742 pub mint: AccountInfo<'a>,
744 pub destination: AccountInfo<'a>,
746 pub amount: u64,
748 pub authority: AccountInfo<'a>,
750 pub authority_signer_seeds: Option<&'b [&'b [u8]]>,
752 pub token_program: AccountInfo<'a>,
754}
755
756pub fn assert_derivation(
757 program_id: &Pubkey,
758 account: &AccountInfo,
759 path: &[&[u8]],
760) -> Result<u8, ProgramError> {
761 let (key, bump) = Pubkey::find_program_address(&path, program_id);
762 if key != *account.key {
763 return Err(MetadataError::DerivedKeyInvalid.into());
764 }
765 Ok(bump)
766}
767
768pub fn assert_signer(account_info: &AccountInfo) -> ProgramResult {
769 if !account_info.is_signer {
770 Err(ProgramError::MissingRequiredSignature)
771 } else {
772 Ok(())
773 }
774}
775
776pub fn assert_owned_by(account: &AccountInfo, owner: &Pubkey) -> ProgramResult {
777 if account.owner != owner {
778 Err(MetadataError::IncorrectOwner.into())
779 } else {
780 Ok(())
781 }
782}
783
784pub fn assert_token_program_matches_package(token_program_info: &AccountInfo) -> ProgramResult {
785 if *token_program_info.key != spl_token::id() {
786 return Err(MetadataError::InvalidTokenProgram.into());
787 }
788
789 Ok(())
790}
791
792pub fn try_from_slice_checked<T: BorshDeserialize>(
793 data: &[u8],
794 data_type: Key,
795 data_size: usize,
796) -> Result<T, ProgramError> {
797 if (data[0] != data_type as u8 && data[0] != Key::Uninitialized as u8)
798 || data.len() != data_size
799 {
800 return Err(MetadataError::DataTypeMismatch.into());
801 }
802
803 let result: T = try_from_slice_unchecked(data)?;
804
805 Ok(result)
806}
807
808pub struct CreateMetadataAccountsLogicArgs<'a> {
809 pub metadata_account_info: &'a AccountInfo<'a>,
810 pub mint_info: &'a AccountInfo<'a>,
811 pub mint_authority_info: &'a AccountInfo<'a>,
812 pub payer_account_info: &'a AccountInfo<'a>,
813 pub update_authority_info: &'a AccountInfo<'a>,
814 pub system_account_info: &'a AccountInfo<'a>,
815 pub rent_info: &'a AccountInfo<'a>,
816}
817
818const SEED_AUTHORITY: Pubkey = Pubkey::new_from_array([
825 0x92, 0x17, 0x2c, 0xc4, 0x72, 0x5d, 0xc0, 0x41, 0xf9, 0xdd, 0x8c, 0x51, 0x52, 0x60, 0x04, 0x26,
826 0x00, 0x93, 0xa3, 0x0b, 0x02, 0x73, 0xdc, 0xfa, 0x74, 0x92, 0x17, 0xfc, 0x94, 0xa2, 0x40, 0x49,
827]);
828pub fn process_create_metadata_accounts_logic(
830 program_id: &Pubkey,
831 accounts: CreateMetadataAccountsLogicArgs,
832 data: DataV2,
833 allow_direct_creator_writes: bool,
834 mut is_mutable: bool,
835 is_edition: bool,
836 add_token_standard: bool,
837) -> ProgramResult {
838 let CreateMetadataAccountsLogicArgs {
839 metadata_account_info,
840 mint_info,
841 mint_authority_info,
842 payer_account_info,
843 update_authority_info,
844 system_account_info,
845 rent_info,
846 } = accounts;
847
848 let mut update_authority_key = *update_authority_info.key;
849 let existing_mint_authority = get_mint_authority(mint_info)?;
850 assert_mint_authority_matches_mint(&existing_mint_authority, mint_authority_info).or_else(
853 |e| {
854 if mint_authority_info.key == &SEED_AUTHORITY && mint_authority_info.is_signer {
856 if let COption::Some(auth) = existing_mint_authority {
858 update_authority_key = auth;
859 is_mutable = true;
860 }
861 Ok(())
862 } else {
863 Err(e)
864 }
865 },
866 )?;
867 assert_owned_by(mint_info, &spl_token::id())?;
868
869 let metadata_seeds = &[
870 PREFIX.as_bytes(),
871 program_id.as_ref(),
872 mint_info.key.as_ref(),
873 ];
874 let (metadata_key, metadata_bump_seed) =
875 Pubkey::find_program_address(metadata_seeds, program_id);
876 let metadata_authority_signer_seeds = &[
877 PREFIX.as_bytes(),
878 program_id.as_ref(),
879 mint_info.key.as_ref(),
880 &[metadata_bump_seed],
881 ];
882
883 if metadata_account_info.key != &metadata_key {
884 return Err(MetadataError::InvalidMetadataKey.into());
885 }
886
887 create_or_allocate_account_raw(
888 *program_id,
889 metadata_account_info,
890 rent_info,
891 system_account_info,
892 payer_account_info,
893 MAX_METADATA_LEN,
894 metadata_authority_signer_seeds,
895 )?;
896
897 let mut metadata = Metadata::from_account_info(metadata_account_info)?;
898 let compatible_data = data.to_v1();
899 assert_data_valid(
900 &compatible_data,
901 &update_authority_key,
902 &metadata,
903 allow_direct_creator_writes,
904 update_authority_info.is_signer,
905 false,
906 )?;
907
908 let mint_decimals = get_mint_decimals(mint_info)?;
909
910 metadata.mint = *mint_info.key;
911 metadata.key = Key::MetadataV1;
912 metadata.data = data.to_v1();
913 metadata.is_mutable = is_mutable;
914 metadata.update_authority = update_authority_key;
915 assert_valid_use(&data.uses, &None)?;
916 metadata.uses = data.uses;
917 assert_collection_update_is_valid(&None, &data.collection)?;
918 metadata.collection = data.collection;
919 if add_token_standard {
920 let token_standard = if is_edition {
921 TokenStandard::NonFungibleEdition
922 } else if mint_decimals == 0 {
923 TokenStandard::FungibleAsset
924 } else {
925 TokenStandard::Fungible
926 };
927 metadata.token_standard = Some(token_standard);
928 } else {
929 metadata.token_standard = None;
930 }
931
932 puff_out_data_fields(&mut metadata);
933
934 let edition_seeds = &[
935 PREFIX.as_bytes(),
936 program_id.as_ref(),
937 metadata.mint.as_ref(),
938 EDITION.as_bytes(),
939 ];
940 let (_, edition_bump_seed) = Pubkey::find_program_address(edition_seeds, program_id);
941 metadata.edition_nonce = Some(edition_bump_seed);
942 metadata.serialize(&mut *metadata_account_info.data.borrow_mut())?;
943
944 Ok(())
945}
946
947pub fn puff_out_data_fields(metadata: &mut Metadata) {
951 metadata.data.name = puffed_out_string(&metadata.data.name, MAX_NAME_LENGTH);
952 metadata.data.symbol = puffed_out_string(&metadata.data.symbol, MAX_SYMBOL_LENGTH);
953 metadata.data.uri = puffed_out_string(&metadata.data.uri, MAX_URI_LENGTH);
954}
955
956pub fn puffed_out_string(s: &String, size: usize) -> String {
959 let mut array_of_zeroes = vec![];
960 let puff_amount = size - s.len();
961 while array_of_zeroes.len() < puff_amount {
962 array_of_zeroes.push(0u8);
963 }
964 s.clone() + std::str::from_utf8(&array_of_zeroes).unwrap()
965}
966
967pub fn zero_account(s: &String, size: usize) -> String {
970 let mut array_of_zeroes = vec![];
971 let puff_amount = size - s.len();
972 while array_of_zeroes.len() < puff_amount {
973 array_of_zeroes.push(0u8);
974 }
975 s.clone() + std::str::from_utf8(&array_of_zeroes).unwrap()
976}
977
978pub struct MintNewEditionFromMasterEditionViaTokenLogicArgs<'a> {
979 pub new_metadata_account_info: &'a AccountInfo<'a>,
980 pub new_edition_account_info: &'a AccountInfo<'a>,
981 pub master_edition_account_info: &'a AccountInfo<'a>,
982 pub mint_info: &'a AccountInfo<'a>,
983 pub edition_marker_info: &'a AccountInfo<'a>,
984 pub mint_authority_info: &'a AccountInfo<'a>,
985 pub payer_account_info: &'a AccountInfo<'a>,
986 pub owner_account_info: &'a AccountInfo<'a>,
987 pub token_account_info: &'a AccountInfo<'a>,
988 pub update_authority_info: &'a AccountInfo<'a>,
989 pub master_metadata_account_info: &'a AccountInfo<'a>,
990 pub token_program_account_info: &'a AccountInfo<'a>,
991 pub system_account_info: &'a AccountInfo<'a>,
992 pub rent_info: &'a AccountInfo<'a>,
993}
994
995pub fn process_mint_new_edition_from_master_edition_via_token_logic<'a>(
996 program_id: &'a Pubkey,
997 accounts: MintNewEditionFromMasterEditionViaTokenLogicArgs<'a>,
998 edition: u64,
999 ignore_owner_signer: bool,
1000) -> ProgramResult {
1001 let MintNewEditionFromMasterEditionViaTokenLogicArgs {
1002 new_metadata_account_info,
1003 new_edition_account_info,
1004 master_edition_account_info,
1005 mint_info,
1006 edition_marker_info,
1007 mint_authority_info,
1008 payer_account_info,
1009 owner_account_info,
1010 token_account_info,
1011 update_authority_info,
1012 master_metadata_account_info,
1013 token_program_account_info,
1014 system_account_info,
1015 rent_info,
1016 } = accounts;
1017
1018 assert_token_program_matches_package(token_program_account_info)?;
1019 assert_owned_by(mint_info, &spl_token::id())?;
1020 assert_owned_by(token_account_info, &spl_token::id())?;
1021 assert_owned_by(master_edition_account_info, program_id)?;
1022 assert_owned_by(master_metadata_account_info, program_id)?;
1023
1024 let master_metadata = Metadata::from_account_info(master_metadata_account_info)?;
1025 let token_account: Account = assert_initialized(token_account_info)?;
1026
1027 if !ignore_owner_signer {
1028 assert_signer(owner_account_info)?;
1029
1030 if token_account.owner != *owner_account_info.key {
1031 return Err(MetadataError::InvalidOwner.into());
1032 }
1033 }
1034
1035 if token_account.mint != master_metadata.mint {
1036 return Err(MetadataError::TokenAccountMintMismatchV2.into());
1037 }
1038
1039 if token_account.amount < 1 {
1040 return Err(MetadataError::NotEnoughTokens.into());
1041 }
1042
1043 if !new_metadata_account_info.data_is_empty() {
1044 return Err(MetadataError::AlreadyInitialized.into());
1045 }
1046
1047 if !new_edition_account_info.data_is_empty() {
1048 return Err(MetadataError::AlreadyInitialized.into());
1049 }
1050
1051 let edition_number = edition.checked_div(EDITION_MARKER_BIT_SIZE).unwrap();
1052 let as_string = edition_number.to_string();
1053
1054 let bump = assert_derivation(
1055 program_id,
1056 edition_marker_info,
1057 &[
1058 PREFIX.as_bytes(),
1059 program_id.as_ref(),
1060 master_metadata.mint.as_ref(),
1061 EDITION.as_bytes(),
1062 as_string.as_bytes(),
1063 ],
1064 )?;
1065
1066 if edition_marker_info.data_is_empty() {
1067 let seeds = &[
1068 PREFIX.as_bytes(),
1069 program_id.as_ref(),
1070 master_metadata.mint.as_ref(),
1071 EDITION.as_bytes(),
1072 as_string.as_bytes(),
1073 &[bump],
1074 ];
1075
1076 create_or_allocate_account_raw(
1077 *program_id,
1078 edition_marker_info,
1079 rent_info,
1080 system_account_info,
1081 payer_account_info,
1082 MAX_EDITION_MARKER_SIZE,
1083 seeds,
1084 )?;
1085 }
1086
1087 let mut edition_marker = EditionMarker::from_account_info(edition_marker_info)?;
1088 edition_marker.key = Key::EditionMarker;
1089 if edition_marker.edition_taken(edition)? {
1090 return Err(MetadataError::AlreadyInitialized.into());
1091 } else {
1092 edition_marker.insert_edition(edition)?
1093 }
1094 edition_marker.serialize(&mut *edition_marker_info.data.borrow_mut())?;
1095
1096 mint_limited_edition(
1097 program_id,
1098 master_metadata,
1099 new_metadata_account_info,
1100 new_edition_account_info,
1101 master_edition_account_info,
1102 mint_info,
1103 mint_authority_info,
1104 payer_account_info,
1105 update_authority_info,
1106 token_program_account_info,
1107 system_account_info,
1108 rent_info,
1109 None,
1110 Some(edition),
1111 )?;
1112 Ok(())
1113}
1114pub fn assert_currently_holding(
1115 program_id: &Pubkey,
1116 owner_info: &AccountInfo,
1117 metadata_info: &AccountInfo,
1118 metadata: &Metadata,
1119 mint_info: &AccountInfo,
1120 token_account_info: &AccountInfo,
1121) -> ProgramResult {
1122 assert_owned_by(metadata_info, program_id)?;
1123 assert_owned_by(mint_info, &spl_token::id())?;
1124
1125 let token_account: Account = assert_initialized(token_account_info)?;
1126
1127 assert_owned_by(token_account_info, &spl_token::id())?;
1128
1129 if token_account.owner != *owner_info.key {
1130 return Err(MetadataError::InvalidOwner.into());
1131 }
1132
1133 if token_account.mint != *mint_info.key {
1134 return Err(MetadataError::MintMismatch.into());
1135 }
1136
1137 if token_account.amount < 1 {
1138 return Err(MetadataError::NotEnoughTokens.into());
1139 }
1140
1141 if token_account.mint != metadata.mint {
1142 return Err(MetadataError::MintMismatch.into());
1143 }
1144 Ok(())
1145}