Skip to main content

shadow_nft_standard/instructions/
create.rs

1//! This module contains all code for the `create` instruction,
2//! which initializes a metadata account for an NFT.
3use crate::{
4    common::{
5        _get_creator_group_seed, check_multisig,
6        collection::Collection,
7        creator_group::CreatorGroup,
8        metadata::{calculate_metadata_space, Metadata},
9        url::Url,
10    },
11    error::ErrorCode,
12    verbose_msg,
13};
14use anchor_lang::{prelude::*, solana_program::program_option::COption};
15use shadow_nft_common::payer_pda::get_payer_pda;
16
17#[cfg(feature = "use-token-2022")]
18use crate::common::token_2022::{Mint, Token2022 as Token};
19#[cfg(not(feature = "use-token-2022"))]
20use anchor_spl::token::{Mint, Token};
21
22/// This handler must first check for appropriate token mint account properties:
23/// 1) Supply = 0
24/// 2) Decimals = 0
25/// 3) Mint authority = metadata account
26///
27/// Then, it must initialize all metadata fields
28pub fn handle_create_metadata(
29    ctx: Context<CreateMetadataAccount>,
30    args: CreateMetaArgs,
31) -> Result<()> {
32    // Check mint account for non-fungibility
33    let incorrect_mint_authority =
34        ctx.accounts.asset_mint.mint_authority != COption::Some(ctx.accounts.metadata.key());
35
36    if incorrect_mint_authority {
37        return Err(ErrorCode::InvalidMintAuthority.into());
38    };
39
40    let some_freeze_authority = ctx.accounts.asset_mint.freeze_authority.is_some();
41    if some_freeze_authority {
42        return Err(ErrorCode::FreezeAuthorityPresent.into());
43    }
44    let unitary = (ctx.accounts.asset_mint.supply == 0) & (ctx.accounts.asset_mint.decimals == 0);
45    if !unitary {
46        return Err(ErrorCode::DivisibleToken.into());
47    }
48    verbose_msg!("Verified Non-Fungibility");
49
50    // Check collection is initialized
51    if !ctx
52        .accounts
53        .collection
54        .initialized(&ctx.accounts.creator_group.creators)
55    {
56        return Err(ErrorCode::UnintializedCollection.into());
57    };
58
59    // Check multisig
60    if ctx.accounts.creator_group.creators.len() > 1 {
61        check_multisig(
62            &ctx.accounts.creator_group.creators,
63            ctx.accounts.payer_creator.key,
64            ctx.remaining_accounts,
65        )?;
66    }
67
68    // Initialize metadata
69    let metadata = &mut ctx.accounts.metadata;
70    metadata.mint = ctx.accounts.asset_mint.key();
71    metadata.update_authority = args.update_authority;
72    metadata.collection_key = args.collection_key;
73    metadata.post_primary = false;
74    metadata.mutable = args.mutable;
75    metadata.name = args.name;
76    metadata.uri = args.uri;
77    verbose_msg!("Initialized Metadata Account");
78
79    // Increment collection size
80    ctx.accounts.collection.size = ctx
81        .accounts
82        .collection
83        .size
84        .checked_add(1)
85        .ok_or(ErrorCode::IntegerOverflow)?;
86    verbose_msg!("Collection now has {} items", ctx.accounts.collection.size);
87
88    Ok(())
89}
90
91#[derive(Accounts)]
92#[instruction(
93    args: CreateMetaArgs
94)]
95pub struct CreateMetadataAccount<'info> {
96    /// The metadata account to be initialized.
97    #[account(
98        init,
99        payer = payer_creator,
100        seeds = [
101            asset_mint.key().as_ref()
102        ],
103        bump,
104        space = calculate_metadata_space(&args.name, &args.uri)
105    )]
106    pub metadata: Account<'info, Metadata>,
107
108    /// NOTE: If this mutability changes + if creator group code changes,
109    /// we must revisit group seed
110    #[account(
111        seeds = [
112            // This assumes creator group is already sorted,
113            // which is done at group account initialization
114            _get_creator_group_seed(&creator_group.creators).as_ref()
115        ],
116        bump,
117    )]
118    pub creator_group: Account<'info, CreatorGroup>,
119
120    /// The token program mint account of the NFT
121    #[account()]
122    pub asset_mint: Account<'info, Mint>,
123
124    /// The `Collection` account associated with this NFT
125    #[account(
126        mut,
127        address = args.collection_key
128    )]
129    pub collection: Account<'info, Collection>,
130
131    /// Either a creator or an ephemeral payer pda from the minter program.
132    #[account(
133        mut,
134        // This constraint checks whether the signer is an authorized creator, or a minter from the minter program.
135        constraint = {
136            let is_authorized_creator = creator_group.creators.contains(payer_creator.key) & !collection.for_minter;
137            // For now, The ONLY cpi from the minter program into this program is the mint
138            // instruction with a PayerPda account.
139            let is_correct_and_authorized_minter_program_payer_pda = 
140                (*payer_creator.key == get_payer_pda(&asset_mint.key())) & collection.for_minter;
141
142            is_authorized_creator || is_correct_and_authorized_minter_program_payer_pda
143        }
144    )]
145    pub payer_creator: Signer<'info>,
146
147    pub token_program: Program<'info, Token>,
148
149    pub system_program: Program<'info, System>,
150}
151
152#[derive(AnchorSerialize, AnchorDeserialize)]
153pub struct CreateMetaArgs {
154    pub update_authority: Pubkey,
155    pub name: String,
156    pub uri: Url,
157    pub mutable: bool,
158    pub collection_key: Pubkey,
159}