mod bubblegum;
mod burn;
mod collection;
mod delegate;
mod edition;
pub(crate) mod escrow;
mod fee;
mod freeze;
mod metadata;
mod state;
mod uses;
mod verification;
use borsh::{BorshDeserialize, BorshSerialize};
pub use bubblegum::*;
pub use burn::*;
pub use collection::*;
pub use delegate::*;
pub use edition::*;
pub use escrow::*;
pub use freeze::*;
pub use metadata::*;
use mpl_token_auth_rules::payload::Payload;
#[cfg(feature = "serde-feature")]
use serde::{Deserialize, Serialize};
use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError,
pubkey::Pubkey,
};
pub use state::*;
pub use uses::*;
pub use verification::*;
use crate::{
error::MetadataError,
instruction::{
MetadataInstruction, CREATE_METADATA_ACCOUNT, CREATE_METADATA_ACCOUNT_V2,
DEPRECATED_CREATE_MASTER_EDITION, DEPRECATED_CREATE_RESERVATION_LIST,
DEPRECATED_MINT_NEW_EDITION_FROM_MASTER_EDITION_VIA_PRINTING_TOKEN,
DEPRECATED_MINT_PRINTING_TOKENS, DEPRECATED_MINT_PRINTING_TOKENS_VIA_TOKEN,
DEPRECATED_SET_RESERVATION_LIST, MIGRATE, UPDATE_METADATA_ACCOUNT,
},
processor::{
edition::{
process_convert_master_edition_v1_to_v2, process_create_master_edition,
process_mint_new_edition_from_master_edition_via_token,
},
escrow::process_transfer_out_of_escrow,
},
state::{
Key, Metadata, TokenMetadataAccount, TokenStandard, TokenState, DISCRIMINATOR_INDEX,
TOKEN_STATE_INDEX,
},
};
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
pub struct AuthorizationData {
pub payload: Payload,
}
impl AuthorizationData {
pub fn new(payload: Payload) -> Self {
Self { payload }
}
pub fn new_empty() -> Self {
Self {
payload: Payload::new(),
}
}
}
pub fn process_instruction<'a>(
program_id: &'a Pubkey,
accounts: &'a [AccountInfo<'a>],
input: &[u8],
) -> ProgramResult {
let (variant, _args) = input
.split_first()
.ok_or(MetadataError::InvalidInstruction)?;
let instruction = match MetadataInstruction::try_from_slice(input) {
Ok(instruction) => Ok(instruction),
Err(_) => match *variant {
CREATE_METADATA_ACCOUNT
| UPDATE_METADATA_ACCOUNT
| DEPRECATED_CREATE_MASTER_EDITION
| DEPRECATED_MINT_NEW_EDITION_FROM_MASTER_EDITION_VIA_PRINTING_TOKEN
| DEPRECATED_SET_RESERVATION_LIST
| DEPRECATED_CREATE_RESERVATION_LIST
| DEPRECATED_MINT_PRINTING_TOKENS_VIA_TOKEN
| DEPRECATED_MINT_PRINTING_TOKENS
| CREATE_METADATA_ACCOUNT_V2
| MIGRATE => Err(MetadataError::Removed.into()),
_ => Err(ProgramError::InvalidInstructionData),
},
}?;
if is_locked(program_id, accounts) && !matches!(instruction, MetadataInstruction::Unlock(_)) {
return Err(MetadataError::LockedToken.into());
}
match instruction {
MetadataInstruction::Burn(args) => {
msg!("IX: Burn");
burn::burn(program_id, accounts, args)
}
MetadataInstruction::Create(args) => {
msg!("IX: Create");
metadata::create(program_id, accounts, args)
}
MetadataInstruction::Mint(args) => {
msg!("IX: Mint");
metadata::mint(program_id, accounts, args)
}
MetadataInstruction::Delegate(args) => {
msg!("IX: Delegate");
delegate::delegate(program_id, accounts, args)
}
MetadataInstruction::Revoke(args) => {
msg!("IX: Revoke");
delegate::revoke(program_id, accounts, args)
}
MetadataInstruction::Lock(args) => {
msg!("IX: Lock");
state::lock(program_id, accounts, args)
}
MetadataInstruction::Unlock(args) => {
msg!("IX: Unlock");
state::unlock(program_id, accounts, args)
}
MetadataInstruction::Migrate => Err(MetadataError::Removed.into()),
MetadataInstruction::Transfer(args) => {
msg!("IX: Transfer");
metadata::transfer(program_id, accounts, args)
}
MetadataInstruction::Update(args) => {
msg!("IX: Update");
metadata::update(program_id, accounts, args)
}
MetadataInstruction::Verify(args) => {
msg!("IX: Verify");
verification::verify(program_id, accounts, args)
}
MetadataInstruction::Unverify(args) => {
msg!("IX: Unverify");
verification::unverify(program_id, accounts, args)
}
MetadataInstruction::Collect => fee::process_collect_fees(program_id, accounts),
MetadataInstruction::Print(args) => {
msg!("IX: Print");
metadata::print(program_id, accounts, args)
}
_ => {
if !has_programmable_metadata(program_id, accounts)? {
process_legacy_instruction(program_id, accounts, instruction)
} else {
Err(MetadataError::InstructionNotSupported.into())
}
}
}
}
fn process_legacy_instruction<'a>(
program_id: &'a Pubkey,
accounts: &'a [AccountInfo<'a>],
instruction: MetadataInstruction,
) -> ProgramResult {
match instruction {
MetadataInstruction::CreateMetadataAccount => Err(MetadataError::Removed.into()),
MetadataInstruction::UpdateMetadataAccount => Err(MetadataError::Removed.into()),
MetadataInstruction::CreateMetadataAccountV2 => Err(MetadataError::Removed.into()),
MetadataInstruction::CreateMetadataAccountV3(args) => {
msg!("IX: Create Metadata Accounts v3");
process_create_metadata_accounts_v3(
program_id,
accounts,
args.data,
args.is_mutable,
args.collection_details,
)
}
MetadataInstruction::UpdateMetadataAccountV2(args) => {
msg!("IX: Update Metadata Accounts v2");
process_update_metadata_accounts_v2(
program_id,
accounts,
args.data,
args.update_authority,
args.primary_sale_happened,
args.is_mutable,
)
}
MetadataInstruction::DeprecatedCreateMasterEdition => Err(MetadataError::Removed.into()),
MetadataInstruction::DeprecatedMintNewEditionFromMasterEditionViaPrintingToken => {
Err(MetadataError::Removed.into())
}
MetadataInstruction::UpdatePrimarySaleHappenedViaToken => {
msg!("IX: Update primary sale via token");
process_update_primary_sale_happened_via_token(program_id, accounts)
}
MetadataInstruction::DeprecatedSetReservationList => Err(MetadataError::Removed.into()),
MetadataInstruction::DeprecatedCreateReservationList => Err(MetadataError::Removed.into()),
MetadataInstruction::SignMetadata => {
msg!("IX: Sign Metadata");
process_sign_metadata(program_id, accounts)
}
MetadataInstruction::RemoveCreatorVerification => {
msg!("IX: Remove Creator Verification");
process_remove_creator_verification(program_id, accounts)
}
MetadataInstruction::DeprecatedMintPrintingTokensViaToken => {
Err(MetadataError::Removed.into())
}
MetadataInstruction::DeprecatedMintPrintingTokens => Err(MetadataError::Removed.into()),
MetadataInstruction::CreateMasterEdition => Err(MetadataError::Removed.into()),
MetadataInstruction::CreateMasterEditionV3(args) => {
msg!("V3 Create Master Edition");
process_create_master_edition(program_id, accounts, args.max_supply)
}
MetadataInstruction::MintNewEditionFromMasterEditionViaToken(args) => {
msg!("IX: Mint New Edition from Master Edition Via Token");
process_mint_new_edition_from_master_edition_via_token(
program_id,
accounts,
args.edition,
)
}
MetadataInstruction::ConvertMasterEditionV1ToV2 => {
msg!("IX: Convert Master Edition V1 to V2");
process_convert_master_edition_v1_to_v2(program_id, accounts)
}
MetadataInstruction::MintNewEditionFromMasterEditionViaVaultProxy(_args) => {
Err(MetadataError::Removed.into())
}
MetadataInstruction::PuffMetadata => {
msg!("IX: Puff Metadata");
process_puff_metadata_account(program_id, accounts)
}
MetadataInstruction::VerifyCollection => {
msg!("IX: Verify Collection");
verify_collection(program_id, accounts)
}
MetadataInstruction::SetAndVerifyCollection => {
msg!("IX: Set and Verify Collection");
set_and_verify_collection(program_id, accounts)
}
MetadataInstruction::UnverifyCollection => {
msg!("IX: Unverify Collection");
unverify_collection(program_id, accounts)
}
MetadataInstruction::Utilize(args) => {
msg!("IX: Use/Utilize Token");
process_utilize(program_id, accounts, args.number_of_uses)
}
MetadataInstruction::ApproveUseAuthority(args) => {
msg!("IX: Approve Use Authority");
process_approve_use_authority(program_id, accounts, args.number_of_uses)
}
MetadataInstruction::RevokeUseAuthority => {
msg!("IX: Revoke Use Authority");
process_revoke_use_authority(program_id, accounts)
}
MetadataInstruction::ApproveCollectionAuthority => {
msg!("IX: Approve Collection Authority");
process_approve_collection_authority(program_id, accounts)
}
MetadataInstruction::RevokeCollectionAuthority => {
msg!("IX: Revoke Collection Authority");
process_revoke_collection_authority(program_id, accounts)
}
MetadataInstruction::FreezeDelegatedAccount => {
msg!("IX: Freeze Delegated Account");
process_freeze_delegated_account(program_id, accounts)
}
MetadataInstruction::ThawDelegatedAccount => {
msg!("IX: Thaw Delegated Account");
process_thaw_delegated_account(program_id, accounts)
}
MetadataInstruction::BurnNft => {
msg!("IX: Burn NFT");
process_burn_nft(program_id, accounts)
}
MetadataInstruction::BurnEditionNft => {
msg!("IX: Burn Edition NFT");
process_burn_edition_nft(program_id, accounts)
}
MetadataInstruction::VerifySizedCollectionItem => {
msg!("IX: Verify Collection V2");
verify_sized_collection_item(program_id, accounts)
}
MetadataInstruction::SetAndVerifySizedCollectionItem => {
msg!("IX: Set and Verify Collection");
set_and_verify_sized_collection_item(program_id, accounts)
}
MetadataInstruction::UnverifySizedCollectionItem => {
msg!("IX: Unverify Sized Collection");
unverify_sized_collection_item(program_id, accounts)
}
MetadataInstruction::SetCollectionSize(args) => {
msg!("IX: Set Collection Size");
set_collection_size(program_id, accounts, args)
}
MetadataInstruction::SetTokenStandard => {
msg!("IX: Set Token Standard");
process_set_token_standard(program_id, accounts)
}
MetadataInstruction::BubblegumSetCollectionSize(args) => {
msg!("IX: Bubblegum Program Set Collection Size");
bubblegum_set_collection_size(program_id, accounts, args)
}
MetadataInstruction::CreateEscrowAccount => {
msg!("IX: Create Escrow Account");
process_create_escrow_account(program_id, accounts)
}
MetadataInstruction::CloseEscrowAccount => {
msg!("IX: Close Escrow Account");
process_close_escrow_account(program_id, accounts)
}
MetadataInstruction::TransferOutOfEscrow(args) => {
msg!("IX: Transfer Out Of Escrow");
process_transfer_out_of_escrow(program_id, accounts, args)
}
_ => Err(ProgramError::InvalidInstructionData),
}
}
fn has_programmable_metadata(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> Result<bool, ProgramError> {
for account_info in accounts {
if account_info.owner == program_id && !account_info.data_is_empty() {
let discriminator = account_info.data.borrow()[DISCRIMINATOR_INDEX];
if discriminator == Key::MetadataV1 as u8 {
let metadata = Metadata::from_account_info(account_info)?;
if matches!(
metadata.token_standard,
Some(TokenStandard::ProgrammableNonFungible)
) {
return Ok(true);
}
}
}
}
Ok(false)
}
fn is_locked(program_id: &Pubkey, accounts: &[AccountInfo]) -> bool {
for account_info in accounts {
if account_info.owner == program_id && !account_info.data_is_empty() {
let data = account_info.data.borrow();
if (data[DISCRIMINATOR_INDEX] == Key::TokenRecord as u8)
&& (data[TOKEN_STATE_INDEX] == TokenState::Locked as u8)
{
return true;
}
}
}
false
}
macro_rules! all_account_infos {
($accounts:expr, $($account:ident),*) => {
let [$($account),*] = match $accounts {
[$($account),*, ..] => [$($account),*],
_ => return Err(solana_program::program_error::ProgramError::NotEnoughAccountKeys),
};
};
}
pub(crate) use all_account_infos;