mpl_token_metadata/processor/metadata/
create.rs

1use mpl_utils::assert_initialized;
2use solana_program::{
3    account_info::AccountInfo, entrypoint::ProgramResult, msg, program::invoke, program_pack::Pack,
4    pubkey::Pubkey, rent::Rent, system_instruction, sysvar::Sysvar,
5};
6use spl_token::{native_mint::DECIMALS, state::Mint};
7
8use crate::{
9    error::MetadataError,
10    instruction::{Context, Create, CreateArgs},
11    state::{
12        Metadata, ProgrammableConfig, TokenMetadataAccount, TokenStandard, MAX_MASTER_EDITION_LEN,
13        TOKEN_STANDARD_INDEX,
14    },
15    utils::{
16        create_master_edition, process_create_metadata_accounts_logic,
17        CreateMetadataAccountsLogicArgs,
18    },
19};
20
21/// Create the associated metadata accounts for a mint.
22///
23/// The instruction will also initialize the mint if the account does not
24/// exist. For `NonFungible` assets, a `master_edition` account is required.
25pub fn create<'a>(
26    program_id: &Pubkey,
27    accounts: &'a [AccountInfo<'a>],
28    args: CreateArgs,
29) -> ProgramResult {
30    let context = Create::to_context(accounts)?;
31
32    match args {
33        CreateArgs::V1 { .. } => create_v1(program_id, context, args),
34    }
35}
36
37/// V1 implementation of the create instruction.
38fn create_v1(program_id: &Pubkey, ctx: Context<Create>, args: CreateArgs) -> ProgramResult {
39    // get the args for the instruction
40    let CreateArgs::V1 {
41        ref asset_data,
42        decimals,
43        print_supply,
44    } = args;
45
46    // cannot create non-fungible editions on this instruction
47    if matches!(asset_data.token_standard, TokenStandard::NonFungibleEdition) {
48        return Err(MetadataError::InvalidTokenStandard.into());
49    }
50
51    // if the account does not exist, we will allocate a new mint
52
53    if ctx.accounts.mint_info.data_is_empty() {
54        // mint account must be a signer in the transaction
55        if !ctx.accounts.mint_info.is_signer {
56            return Err(MetadataError::MintIsNotSigner.into());
57        }
58
59        msg!("Init mint");
60
61        invoke(
62            &system_instruction::create_account(
63                ctx.accounts.payer_info.key,
64                ctx.accounts.mint_info.key,
65                Rent::get()?.minimum_balance(spl_token::state::Mint::LEN),
66                spl_token::state::Mint::LEN as u64,
67                &spl_token::ID,
68            ),
69            &[
70                ctx.accounts.payer_info.clone(),
71                ctx.accounts.mint_info.clone(),
72            ],
73        )?;
74
75        let decimals = match asset_data.token_standard {
76            // for NonFungible variants, we ignore the argument and
77            // always use 0 decimals
78            TokenStandard::NonFungible | TokenStandard::ProgrammableNonFungible => 0,
79            // for Fungile variants, we either use the specified decimals or the default
80            // DECIMALS from spl-token
81            TokenStandard::FungibleAsset | TokenStandard::Fungible => match decimals {
82                Some(decimals) => decimals,
83                // if decimals not provided, use the default
84                None => DECIMALS,
85            },
86            _ => {
87                return Err(MetadataError::InvalidTokenStandard.into());
88            }
89        };
90
91        // initializing the mint account
92        invoke(
93            &spl_token::instruction::initialize_mint2(
94                ctx.accounts.spl_token_program_info.key,
95                ctx.accounts.mint_info.key,
96                ctx.accounts.authority_info.key,
97                Some(ctx.accounts.authority_info.key),
98                decimals,
99            )?,
100            &[
101                ctx.accounts.mint_info.clone(),
102                ctx.accounts.authority_info.clone(),
103            ],
104        )?;
105    } else {
106        // validates the existing mint account
107
108        let mint: Mint = assert_initialized(ctx.accounts.mint_info, MetadataError::Uninitialized)?;
109        // NonFungible assets must have decimals == 0 and supply no greater than 1
110        if matches!(
111            asset_data.token_standard,
112            TokenStandard::NonFungible | TokenStandard::ProgrammableNonFungible
113        ) && (mint.decimals > 0 || mint.supply > 1)
114        {
115            return Err(MetadataError::InvalidMintForTokenStandard.into());
116        }
117        // Programmable assets must have supply == 0
118        if matches!(
119            asset_data.token_standard,
120            TokenStandard::ProgrammableNonFungible
121        ) && (mint.supply > 0)
122        {
123            return Err(MetadataError::MintSupplyMustBeZero.into());
124        }
125    }
126
127    // creates the metadata account
128
129    process_create_metadata_accounts_logic(
130        program_id,
131        CreateMetadataAccountsLogicArgs {
132            metadata_account_info: ctx.accounts.metadata_info,
133            mint_info: ctx.accounts.mint_info,
134            mint_authority_info: ctx.accounts.authority_info,
135            payer_account_info: ctx.accounts.payer_info,
136            update_authority_info: ctx.accounts.update_authority_info,
137            system_account_info: ctx.accounts.system_program_info,
138        },
139        asset_data.as_data_v2(),
140        false,
141        asset_data.is_mutable,
142        false,
143        true,
144        asset_data.collection_details.clone(),
145    )?;
146
147    // creates the master edition account (only for NonFungible assets)
148
149    if matches!(
150        asset_data.token_standard,
151        TokenStandard::NonFungible | TokenStandard::ProgrammableNonFungible
152    ) {
153        let print_supply = print_supply.ok_or(MetadataError::MissingPrintSupply)?;
154
155        if let Some(master_edition) = ctx.accounts.master_edition_info {
156            create_master_edition(
157                program_id,
158                master_edition,
159                ctx.accounts.mint_info,
160                ctx.accounts.update_authority_info,
161                ctx.accounts.authority_info,
162                ctx.accounts.payer_info,
163                ctx.accounts.metadata_info,
164                ctx.accounts.spl_token_program_info,
165                ctx.accounts.system_program_info,
166                print_supply.to_option(),
167            )?;
168
169            // for pNFTs, we store the token standard value at the end of the
170            // master edition account
171            if matches!(
172                asset_data.token_standard,
173                TokenStandard::ProgrammableNonFungible
174            ) {
175                let mut data = master_edition.data.borrow_mut();
176
177                if data.len() < MAX_MASTER_EDITION_LEN {
178                    return Err(MetadataError::InvalidMasterEditionAccountLength.into());
179                }
180
181                data[TOKEN_STANDARD_INDEX] = TokenStandard::ProgrammableNonFungible as u8;
182            }
183        } else {
184            return Err(MetadataError::MissingMasterEditionAccount.into());
185        }
186    } else if print_supply.is_some() {
187        msg!("Ignoring print supply for selected token standard");
188    }
189
190    let mut metadata = Metadata::from_account_info(ctx.accounts.metadata_info)?;
191    metadata.token_standard = Some(asset_data.token_standard);
192    metadata.primary_sale_happened = asset_data.primary_sale_happened;
193
194    // sets the programmable config for programmable assets
195
196    if matches!(
197        asset_data.token_standard,
198        TokenStandard::ProgrammableNonFungible
199    ) {
200        metadata.programmable_config = Some(ProgrammableConfig::V1 {
201            rule_set: asset_data.rule_set,
202        });
203    }
204
205    // saves the metadata state
206    metadata.save(&mut ctx.accounts.metadata_info.try_borrow_mut_data()?)?;
207
208    Ok(())
209}