mpl_token_metadata/processor/
mod.rs1mod bubblegum;
2mod burn;
3mod collection;
4mod delegate;
5mod edition;
6pub(crate) mod escrow;
7mod freeze;
8mod metadata;
9mod state;
10mod uses;
11mod verification;
12
13use borsh::{BorshDeserialize, BorshSerialize};
14pub use bubblegum::*;
15pub use burn::*;
16pub use collection::*;
17pub use delegate::*;
18pub use edition::*;
19pub use escrow::*;
20pub use freeze::*;
21pub use metadata::*;
22use mpl_token_auth_rules::payload::Payload;
23use mpl_utils::cmp_pubkeys;
24#[cfg(feature = "serde-feature")]
25use serde::{Deserialize, Serialize};
26use solana_program::{
27 account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError,
28 pubkey::Pubkey,
29};
30pub use state::*;
31pub use uses::*;
32pub use verification::*;
33
34use crate::{
35 error::MetadataError,
36 instruction::{
37 MetadataInstruction, CREATE_METADATA_ACCOUNT, CREATE_METADATA_ACCOUNT_V2,
38 DEPRECATED_CREATE_MASTER_EDITION, DEPRECATED_CREATE_RESERVATION_LIST,
39 DEPRECATED_MINT_NEW_EDITION_FROM_MASTER_EDITION_VIA_PRINTING_TOKEN,
40 DEPRECATED_MINT_PRINTING_TOKENS, DEPRECATED_MINT_PRINTING_TOKENS_VIA_TOKEN,
41 DEPRECATED_SET_RESERVATION_LIST, UPDATE_METADATA_ACCOUNT,
42 },
43 processor::{
44 edition::{
45 process_convert_master_edition_v1_to_v2, process_create_master_edition,
46 process_mint_new_edition_from_master_edition_via_token,
47 },
48 escrow::process_transfer_out_of_escrow,
49 },
50 state::{
51 Key, Metadata, TokenMetadataAccount, TokenStandard, TokenState, DISCRIMINATOR_INDEX,
52 TOKEN_STATE_INDEX,
53 },
54};
55
56#[repr(C)]
57#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
58#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
59pub struct AuthorizationData {
60 pub payload: Payload,
61}
62
63impl AuthorizationData {
64 pub fn new(payload: Payload) -> Self {
65 Self { payload }
66 }
67 pub fn new_empty() -> Self {
68 Self {
69 payload: Payload::new(),
70 }
71 }
72}
73
74pub fn process_instruction<'a>(
82 program_id: &'a Pubkey,
83 accounts: &'a [AccountInfo<'a>],
84 input: &[u8],
85) -> ProgramResult {
86 let (variant, _args) = input
87 .split_first()
88 .ok_or(MetadataError::InvalidInstruction)?;
89
90 let instruction = match MetadataInstruction::try_from_slice(input) {
91 Ok(instruction) => Ok(instruction),
92 Err(_) => match *variant {
94 CREATE_METADATA_ACCOUNT
95 | UPDATE_METADATA_ACCOUNT
96 | DEPRECATED_CREATE_MASTER_EDITION
97 | DEPRECATED_MINT_NEW_EDITION_FROM_MASTER_EDITION_VIA_PRINTING_TOKEN
98 | DEPRECATED_SET_RESERVATION_LIST
99 | DEPRECATED_CREATE_RESERVATION_LIST
100 | DEPRECATED_MINT_PRINTING_TOKENS_VIA_TOKEN
101 | DEPRECATED_MINT_PRINTING_TOKENS
102 | CREATE_METADATA_ACCOUNT_V2 => Err(MetadataError::Removed.into()),
103 _ => Err(ProgramError::InvalidInstructionData),
104 },
105 }?;
106
107 if is_locked(program_id, accounts) && !matches!(instruction, MetadataInstruction::Unlock(_)) {
112 return Err(MetadataError::LockedToken.into());
113 }
114
115 match instruction {
117 MetadataInstruction::Burn(args) => {
118 msg!("IX: Burn");
119 burn::burn(program_id, accounts, args)
120 }
121 MetadataInstruction::Create(args) => {
122 msg!("IX: Create");
123 metadata::create(program_id, accounts, args)
124 }
125 MetadataInstruction::Mint(args) => {
126 msg!("IX: Mint");
127 metadata::mint(program_id, accounts, args)
128 }
129 MetadataInstruction::Delegate(args) => {
130 msg!("IX: Delegate");
131 delegate::delegate(program_id, accounts, args)
132 }
133 MetadataInstruction::Revoke(args) => {
134 msg!("IX: Revoke");
135 delegate::revoke(program_id, accounts, args)
136 }
137 MetadataInstruction::Lock(args) => {
138 msg!("IX: Lock");
139 state::lock(program_id, accounts, args)
140 }
141 MetadataInstruction::Unlock(args) => {
142 msg!("IX: Unlock");
143 state::unlock(program_id, accounts, args)
144 }
145 MetadataInstruction::Migrate(args) => {
146 msg!("IX: Migrate");
147 metadata::migrate(program_id, accounts, args)
148 }
149 MetadataInstruction::Transfer(args) => {
150 msg!("IX: Transfer");
151 metadata::transfer(program_id, accounts, args)
152 }
153 MetadataInstruction::Update(args) => {
154 msg!("IX: Update");
155 metadata::update(program_id, accounts, args)
156 }
157 MetadataInstruction::Verify(args) => {
158 msg!("IX: Verify");
159 verification::verify(program_id, accounts, args)
160 }
161 MetadataInstruction::Unverify(args) => {
162 msg!("IX: Unverify");
163 verification::unverify(program_id, accounts, args)
164 }
165 _ => {
166 if !has_programmable_metadata(program_id, accounts)? {
170 process_legacy_instruction(program_id, accounts, instruction)
171 } else {
172 Err(MetadataError::InstructionNotSupported.into())
173 }
174 }
175 }
176}
177
178fn process_legacy_instruction<'a>(
180 program_id: &'a Pubkey,
181 accounts: &'a [AccountInfo<'a>],
182 instruction: MetadataInstruction,
183) -> ProgramResult {
184 match instruction {
185 MetadataInstruction::CreateMetadataAccount => Err(MetadataError::Removed.into()),
186 MetadataInstruction::UpdateMetadataAccount => Err(MetadataError::Removed.into()),
187 MetadataInstruction::CreateMetadataAccountV2 => Err(MetadataError::Removed.into()),
188 MetadataInstruction::CreateMetadataAccountV3(args) => {
189 msg!("IX: Create Metadata Accounts v3");
190 process_create_metadata_accounts_v3(
191 program_id,
192 accounts,
193 args.data,
194 args.is_mutable,
195 args.collection_details,
196 )
197 }
198 MetadataInstruction::UpdateMetadataAccountV2(args) => {
199 msg!("IX: Update Metadata Accounts v2");
200 process_update_metadata_accounts_v2(
201 program_id,
202 accounts,
203 args.data,
204 args.update_authority,
205 args.primary_sale_happened,
206 args.is_mutable,
207 )
208 }
209 MetadataInstruction::DeprecatedCreateMasterEdition => Err(MetadataError::Removed.into()),
210 MetadataInstruction::DeprecatedMintNewEditionFromMasterEditionViaPrintingToken => {
211 Err(MetadataError::Removed.into())
212 }
213 MetadataInstruction::UpdatePrimarySaleHappenedViaToken => {
214 msg!("IX: Update primary sale via token");
215 process_update_primary_sale_happened_via_token(program_id, accounts)
216 }
217 MetadataInstruction::DeprecatedSetReservationList => Err(MetadataError::Removed.into()),
218 MetadataInstruction::DeprecatedCreateReservationList => Err(MetadataError::Removed.into()),
219 MetadataInstruction::SignMetadata => {
220 msg!("IX: Sign Metadata");
221 process_sign_metadata(program_id, accounts)
222 }
223 MetadataInstruction::RemoveCreatorVerification => {
224 msg!("IX: Remove Creator Verification");
225 process_remove_creator_verification(program_id, accounts)
226 }
227 MetadataInstruction::DeprecatedMintPrintingTokensViaToken => {
228 Err(MetadataError::Removed.into())
229 }
230 MetadataInstruction::DeprecatedMintPrintingTokens => Err(MetadataError::Removed.into()),
231 MetadataInstruction::CreateMasterEdition => Err(MetadataError::Removed.into()),
232 MetadataInstruction::CreateMasterEditionV3(args) => {
233 msg!("V3 Create Master Edition");
234 process_create_master_edition(program_id, accounts, args.max_supply)
235 }
236 MetadataInstruction::MintNewEditionFromMasterEditionViaToken(args) => {
237 msg!("IX: Mint New Edition from Master Edition Via Token");
238 process_mint_new_edition_from_master_edition_via_token(
239 program_id,
240 accounts,
241 args.edition,
242 false,
243 )
244 }
245 MetadataInstruction::ConvertMasterEditionV1ToV2 => {
246 msg!("IX: Convert Master Edition V1 to V2");
247 process_convert_master_edition_v1_to_v2(program_id, accounts)
248 }
249 MetadataInstruction::MintNewEditionFromMasterEditionViaVaultProxy(_args) => {
250 Err(MetadataError::Removed.into())
251 }
252 MetadataInstruction::PuffMetadata => {
253 msg!("IX: Puff Metadata");
254 process_puff_metadata_account(program_id, accounts)
255 }
256 MetadataInstruction::VerifyCollection => {
257 msg!("IX: Verify Collection");
258 verify_collection(program_id, accounts)
259 }
260 MetadataInstruction::SetAndVerifyCollection => {
261 msg!("IX: Set and Verify Collection");
262 set_and_verify_collection(program_id, accounts)
263 }
264 MetadataInstruction::UnverifyCollection => {
265 msg!("IX: Unverify Collection");
266 unverify_collection(program_id, accounts)
267 }
268 MetadataInstruction::Utilize(args) => {
269 msg!("IX: Use/Utilize Token");
270 process_utilize(program_id, accounts, args.number_of_uses)
271 }
272 MetadataInstruction::ApproveUseAuthority(args) => {
273 msg!("IX: Approve Use Authority");
274 process_approve_use_authority(program_id, accounts, args.number_of_uses)
275 }
276 MetadataInstruction::RevokeUseAuthority => {
277 msg!("IX: Revoke Use Authority");
278 process_revoke_use_authority(program_id, accounts)
279 }
280 MetadataInstruction::ApproveCollectionAuthority => {
281 msg!("IX: Approve Collection Authority");
282 process_approve_collection_authority(program_id, accounts)
283 }
284 MetadataInstruction::RevokeCollectionAuthority => {
285 msg!("IX: Revoke Collection Authority");
286 process_revoke_collection_authority(program_id, accounts)
287 }
288 MetadataInstruction::FreezeDelegatedAccount => {
289 msg!("IX: Freeze Delegated Account");
290 process_freeze_delegated_account(program_id, accounts)
291 }
292 MetadataInstruction::ThawDelegatedAccount => {
293 msg!("IX: Thaw Delegated Account");
294 process_thaw_delegated_account(program_id, accounts)
295 }
296 MetadataInstruction::BurnNft => {
297 msg!("IX: Burn NFT");
298 process_burn_nft(program_id, accounts)
299 }
300 MetadataInstruction::BurnEditionNft => {
301 msg!("IX: Burn Edition NFT");
302 process_burn_edition_nft(program_id, accounts)
303 }
304 MetadataInstruction::VerifySizedCollectionItem => {
305 msg!("IX: Verify Collection V2");
306 verify_sized_collection_item(program_id, accounts)
307 }
308 MetadataInstruction::SetAndVerifySizedCollectionItem => {
309 msg!("IX: Set and Verify Collection");
310 set_and_verify_sized_collection_item(program_id, accounts)
311 }
312 MetadataInstruction::UnverifySizedCollectionItem => {
313 msg!("IX: Unverify Sized Collection");
314 unverify_sized_collection_item(program_id, accounts)
315 }
316 MetadataInstruction::SetCollectionSize(args) => {
317 msg!("IX: Set Collection Size");
318 set_collection_size(program_id, accounts, args)
319 }
320 MetadataInstruction::SetTokenStandard => {
321 msg!("IX: Set Token Standard");
322 process_set_token_standard(program_id, accounts)
323 }
324 MetadataInstruction::BubblegumSetCollectionSize(args) => {
325 msg!("IX: Bubblegum Program Set Collection Size");
326 bubblegum_set_collection_size(program_id, accounts, args)
327 }
328 MetadataInstruction::CreateEscrowAccount => {
329 msg!("IX: Create Escrow Account");
330 process_create_escrow_account(program_id, accounts)
331 }
332 MetadataInstruction::CloseEscrowAccount => {
333 msg!("IX: Close Escrow Account");
334 process_close_escrow_account(program_id, accounts)
335 }
336 MetadataInstruction::TransferOutOfEscrow(args) => {
337 msg!("IX: Transfer Out Of Escrow");
338 process_transfer_out_of_escrow(program_id, accounts, args)
339 }
340 _ => Err(ProgramError::InvalidInstructionData),
341 }
342}
343
344pub fn next_optional_account_info<'a, 'b, I: Iterator<Item = &'a AccountInfo<'b>>>(
351 iter: &mut I,
352) -> Result<Option<I::Item>, ProgramError> {
353 let account_info = iter.next().ok_or(ProgramError::NotEnoughAccountKeys)?;
354
355 Ok(if cmp_pubkeys(account_info.key, &crate::ID) {
356 None
357 } else {
358 Some(account_info)
359 })
360}
361
362pub fn try_get_account_info<'a>(
369 accounts: &'a [AccountInfo<'a>],
370 index: usize,
371) -> Result<&'a AccountInfo<'a>, ProgramError> {
372 let account_info = try_get_optional_account_info(accounts, index)?;
373 if let Some(account_info) = account_info {
375 Ok(account_info)
376 } else {
377 Err(ProgramError::NotEnoughAccountKeys)
378 }
379}
380
381pub fn try_get_optional_account_info<'a>(
388 accounts: &'a [AccountInfo<'a>],
389 index: usize,
390) -> Result<Option<&'a AccountInfo<'a>>, ProgramError> {
391 if index < accounts.len() {
392 Ok(if cmp_pubkeys(accounts[index].key, &crate::ID) {
393 None
394 } else {
395 Some(&accounts[index])
396 })
397 } else {
398 Err(ProgramError::NotEnoughAccountKeys)
399 }
400}
401
402fn has_programmable_metadata(
407 program_id: &Pubkey,
408 accounts: &[AccountInfo],
409) -> Result<bool, ProgramError> {
410 for account_info in accounts {
411 if account_info.owner == program_id && !account_info.data_is_empty() {
413 let discriminator = account_info.data.borrow()[DISCRIMINATOR_INDEX];
414 if discriminator == Key::MetadataV1 as u8 {
416 let metadata = Metadata::from_account_info(account_info)?;
417
418 if matches!(
419 metadata.token_standard,
420 Some(TokenStandard::ProgrammableNonFungible)
421 ) {
422 return Ok(true);
423 }
424 }
425 }
426 }
427
428 Ok(false)
429}
430
431fn is_locked(program_id: &Pubkey, accounts: &[AccountInfo]) -> bool {
433 for account_info in accounts {
434 if account_info.owner == program_id && !account_info.data_is_empty() {
436 let data = account_info.data.borrow();
437 if (data[DISCRIMINATOR_INDEX] == Key::TokenRecord as u8)
439 && (data[TOKEN_STATE_INDEX] == TokenState::Locked as u8)
440 {
441 return true;
442 }
443 }
444 }
445
446 false
447}