spl_token_collection/
processor.rs1use {
4 solana_program::{
5 account_info::{next_account_info, AccountInfo},
6 entrypoint::ProgramResult,
7 msg,
8 program_error::ProgramError,
9 program_option::COption,
10 pubkey::Pubkey,
11 },
12 spl_pod::optional_keys::OptionalNonZeroPubkey,
13 spl_token_2022::{
14 extension::{
15 metadata_pointer::MetadataPointer, BaseStateWithExtensions, StateWithExtensions,
16 },
17 state::Mint,
18 },
19 spl_token_group_interface::{
20 error::TokenGroupError,
21 instruction::{InitializeGroup, TokenGroupInstruction},
22 state::{TokenGroup, TokenGroupMember},
23 },
24 spl_token_metadata_interface::state::TokenMetadata,
25 spl_type_length_value::state::TlvStateMut,
26};
27
28fn check_update_authority(
29 update_authority_info: &AccountInfo,
30 expected_update_authority: &OptionalNonZeroPubkey,
31) -> ProgramResult {
32 if !update_authority_info.is_signer {
33 return Err(ProgramError::MissingRequiredSignature);
34 }
35 let update_authority = Option::<Pubkey>::from(*expected_update_authority)
36 .ok_or(TokenGroupError::ImmutableGroup)?;
37 if update_authority != *update_authority_info.key {
38 return Err(TokenGroupError::IncorrectUpdateAuthority.into());
39 }
40 Ok(())
41}
42
43fn check_mint_and_metadata(
45 mint_info: &AccountInfo,
46 mint_authority_info: &AccountInfo,
47) -> ProgramResult {
48 let mint_data = mint_info.try_borrow_data()?;
49 let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
50
51 if !mint_authority_info.is_signer {
52 return Err(ProgramError::MissingRequiredSignature);
53 }
54 if mint.base.mint_authority.as_ref() != COption::Some(mint_authority_info.key) {
55 return Err(TokenGroupError::IncorrectMintAuthority.into());
56 }
57
58 let metadata_pointer = mint.get_extension::<MetadataPointer>()?;
59 let metadata_pointer_address = Option::<Pubkey>::from(metadata_pointer.metadata_address);
60
61 if metadata_pointer_address == Some(*mint_info.key) {
64 mint.get_variable_len_extension::<TokenMetadata>()?;
65 }
66
67 Ok(())
68}
69
70pub fn process_initialize_collection(
73 _program_id: &Pubkey,
74 accounts: &[AccountInfo],
75 data: InitializeGroup,
76) -> ProgramResult {
77 let account_info_iter = &mut accounts.iter();
78
79 let collection_info = next_account_info(account_info_iter)?;
80 let mint_info = next_account_info(account_info_iter)?;
81 let mint_authority_info = next_account_info(account_info_iter)?;
82
83 check_mint_and_metadata(mint_info, mint_authority_info)?;
84
85 let mut buffer = collection_info.try_borrow_mut_data()?;
87 let mut state = TlvStateMut::unpack(&mut buffer)?;
88 let (collection, _) = state.init_value::<TokenGroup>(false)?;
89 *collection = TokenGroup::new(mint_info.key, data.update_authority, data.max_size.into());
90
91 Ok(())
92}
93
94pub fn process_initialize_collection_member(
97 _program_id: &Pubkey,
98 accounts: &[AccountInfo],
99) -> ProgramResult {
100 let account_info_iter = &mut accounts.iter();
101
102 let member_info = next_account_info(account_info_iter)?;
103 let mint_info = next_account_info(account_info_iter)?;
104 let mint_authority_info = next_account_info(account_info_iter)?;
105 let collection_info = next_account_info(account_info_iter)?;
106 let collection_update_authority_info = next_account_info(account_info_iter)?;
107
108 check_mint_and_metadata(mint_info, mint_authority_info)?;
109
110 if member_info.key == collection_info.key {
111 return Err(TokenGroupError::MemberAccountIsGroupAccount.into());
112 }
113
114 let mut buffer = collection_info.try_borrow_mut_data()?;
115 let mut state = TlvStateMut::unpack(&mut buffer)?;
116 let collection = state.get_first_value_mut::<TokenGroup>()?;
117
118 check_update_authority(
119 collection_update_authority_info,
120 &collection.update_authority,
121 )?;
122 let member_number = collection.increment_size()?;
123
124 let mut buffer = member_info.try_borrow_mut_data()?;
125 let mut state = TlvStateMut::unpack(&mut buffer)?;
126
127 let (member, _) = state.init_value::<TokenGroupMember>(true)?;
130 *member = TokenGroupMember::new(mint_info.key, collection_info.key, member_number);
131
132 Ok(())
133}
134
135pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
137 let instruction = TokenGroupInstruction::unpack(input)?;
138 match instruction {
139 TokenGroupInstruction::InitializeGroup(data) => {
140 msg!("Instruction: InitializeCollection");
141 process_initialize_collection(program_id, accounts, data)
142 }
143 TokenGroupInstruction::UpdateGroupMaxSize(data) => {
144 msg!("Instruction: UpdateCollectionMaxSize");
145 spl_token_group_example::processor::process_update_group_max_size(
147 program_id, accounts, data,
148 )
149 }
150 TokenGroupInstruction::UpdateGroupAuthority(data) => {
151 msg!("Instruction: UpdateCollectionAuthority");
152 spl_token_group_example::processor::process_update_group_authority(
154 program_id, accounts, data,
155 )
156 }
157 TokenGroupInstruction::InitializeMember(_) => {
158 msg!("Instruction: InitializeCollectionMember");
159 process_initialize_collection_member(program_id, accounts)
160 }
161 }
162}