Skip to main content

name_tokenizer/processor/
create_collection.rs

1//! Create a verified collection
2
3use mpl_token_metadata::{
4    accounts::{MasterEdition, Metadata},
5    instructions::{
6        CreateMasterEditionV3Cpi, CreateMasterEditionV3CpiAccounts,
7        CreateMasterEditionV3InstructionArgs, CreateMetadataAccountV3Cpi,
8        CreateMetadataAccountV3CpiAccounts, CreateMetadataAccountV3InstructionArgs,
9    },
10    types::DataV2,
11};
12
13use crate::{
14    cpi::Cpi,
15    state::{COLLECTION_NAME, COLLECTION_PREFIX, COLLECTION_URI, META_SYMBOL},
16};
17
18use {
19    bonfida_utils::{
20        checks::{check_account_key, check_account_owner, check_signer},
21        BorshSize, InstructionsAccount,
22    },
23    borsh::{BorshDeserialize, BorshSerialize},
24    mpl_token_metadata::types::Creator,
25    solana_program::{
26        account_info::{next_account_info, AccountInfo},
27        entrypoint::ProgramResult,
28        msg,
29        program::{invoke, invoke_signed},
30        program_error::ProgramError,
31        program_pack::Pack,
32        pubkey::Pubkey,
33        system_program, sysvar,
34    },
35    spl_associated_token_account::instruction::create_associated_token_account,
36    spl_token::{
37        instruction::{initialize_mint, mint_to},
38        state::Mint,
39    },
40};
41
42#[derive(BorshDeserialize, BorshSerialize, BorshSize)]
43pub struct Params {}
44
45#[derive(InstructionsAccount)]
46pub struct Accounts<'a, T> {
47    /// The mint of the collection
48    #[cons(writable)]
49    pub collection_mint: &'a T,
50
51    #[cons(writable)]
52    pub edition: &'a T,
53
54    /// The metadata account
55    #[cons(writable)]
56    pub metadata_account: &'a T,
57
58    /// The central state account
59    pub central_state: &'a T,
60
61    #[cons(writable)]
62    /// Token account of the central state to hold the master edition
63    pub central_state_nft_ata: &'a T,
64
65    /// The fee payer account
66    pub fee_payer: &'a T,
67
68    /// The SPL token program account
69    pub spl_token_program: &'a T,
70
71    /// The metadata program account
72    pub metadata_program: &'a T,
73
74    /// The system program account
75    pub system_program: &'a T,
76
77    /// The SPL name service program account
78    pub spl_name_service_program: &'a T,
79
80    pub ata_program: &'a T,
81
82    /// Rent sysvar account
83    pub rent_account: &'a T,
84}
85
86impl<'a, 'b: 'a> Accounts<'a, AccountInfo<'b>> {
87    pub fn parse(
88        accounts: &'a [AccountInfo<'b>],
89        _program_id: &Pubkey,
90    ) -> Result<Self, ProgramError> {
91        let accounts_iter = &mut accounts.iter();
92        let accounts = Accounts {
93            collection_mint: next_account_info(accounts_iter)?,
94            edition: next_account_info(accounts_iter)?,
95            metadata_account: next_account_info(accounts_iter)?,
96            central_state: next_account_info(accounts_iter)?,
97            central_state_nft_ata: next_account_info(accounts_iter)?,
98            fee_payer: next_account_info(accounts_iter)?,
99            spl_token_program: next_account_info(accounts_iter)?,
100            metadata_program: next_account_info(accounts_iter)?,
101            system_program: next_account_info(accounts_iter)?,
102            spl_name_service_program: next_account_info(accounts_iter)?,
103            ata_program: next_account_info(accounts_iter)?,
104            rent_account: next_account_info(accounts_iter)?,
105        };
106
107        // Check keys
108        check_account_key(accounts.central_state, &crate::central_state::KEY)?;
109        check_account_key(accounts.spl_token_program, &spl_token::ID)?;
110        check_account_key(accounts.metadata_program, &mpl_token_metadata::ID)?;
111        check_account_key(accounts.system_program, &system_program::ID)?;
112        check_account_key(accounts.spl_name_service_program, &spl_name_service::ID)?;
113        check_account_key(accounts.ata_program, &spl_associated_token_account::ID)?;
114        check_account_key(accounts.rent_account, &sysvar::rent::ID)?;
115
116        // Check owners
117        check_account_owner(accounts.collection_mint, &system_program::ID)?;
118        check_account_owner(accounts.edition, &system_program::ID)?;
119        check_account_owner(accounts.metadata_account, &system_program::ID)?;
120        check_account_owner(accounts.central_state_nft_ata, &system_program::ID)?;
121
122        // Check signer
123        check_signer(accounts.fee_payer)?;
124
125        Ok(accounts)
126    }
127}
128
129pub fn process(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
130    let accounts = Accounts::parse(accounts, program_id)?;
131
132    let (collection_mint, collection_mint_nonce) =
133        Pubkey::find_program_address(&[COLLECTION_PREFIX, &program_id.to_bytes()], program_id);
134    check_account_key(accounts.collection_mint, &collection_mint)?;
135
136    let (metadata_key, _) = Metadata::find_pda(&collection_mint);
137    check_account_key(accounts.metadata_account, &metadata_key)?;
138
139    let (edition_key, _) = MasterEdition::find_pda(&collection_mint);
140    check_account_key(accounts.edition, &edition_key)?;
141
142    // Create mint account
143    msg!("+ Creating mint");
144    let seeds: &[&[u8]] = &[
145        COLLECTION_PREFIX,
146        &program_id.to_bytes(),
147        &[collection_mint_nonce],
148    ];
149    Cpi::create_account(
150        &spl_token::ID,
151        accounts.system_program,
152        accounts.fee_payer,
153        &accounts.collection_mint.clone(),
154        seeds,
155        Mint::LEN,
156    )?;
157    msg!("+ Initialize mint");
158    // Initialize mint
159    let ix = initialize_mint(
160        &spl_token::ID,
161        &collection_mint,
162        &crate::central_state::KEY,
163        Some(&crate::central_state::KEY),
164        0,
165    )?;
166    invoke_signed(
167        &ix,
168        &[
169            accounts.spl_token_program.clone(),
170            accounts.collection_mint.clone(),
171            accounts.rent_account.clone(),
172        ],
173        &[seeds],
174    )?;
175
176    // Create central state ATA
177    msg!("+ Creating central state ATA");
178    let ix = create_associated_token_account(
179        accounts.fee_payer.key,
180        &crate::central_state::KEY,
181        &collection_mint,
182        &spl_token::ID,
183    );
184    invoke(
185        &ix,
186        &[
187            accounts.ata_program.clone(),
188            accounts.fee_payer.clone(),
189            accounts.central_state_nft_ata.clone(),
190            accounts.central_state.clone(),
191            accounts.collection_mint.clone(),
192            accounts.system_program.clone(),
193            accounts.spl_token_program.clone(),
194            accounts.rent_account.clone(),
195        ],
196    )?;
197
198    // Mint NFT
199    // (because the master edition ix requires mint supply === 1)
200    msg!("+ Minting NFT");
201    let seeds: &[&[u8]] = &[&program_id.to_bytes(), &[crate::central_state::NONCE]];
202    let ix = mint_to(
203        &spl_token::ID,
204        &collection_mint,
205        accounts.central_state_nft_ata.key,
206        &crate::central_state::KEY,
207        &[],
208        1,
209    )?;
210
211    invoke_signed(
212        &ix,
213        &[
214            accounts.spl_token_program.clone(),
215            accounts.collection_mint.clone(),
216            accounts.central_state_nft_ata.clone(),
217            accounts.central_state.clone(),
218        ],
219        &[seeds],
220    )?;
221
222    // Create collection
223    msg!("+ Creating collection");
224    let central_creator = Creator {
225        address: crate::central_state::KEY,
226        verified: true,
227        share: 100,
228    };
229    CreateMetadataAccountV3Cpi::new(
230        accounts.metadata_program,
231        CreateMetadataAccountV3CpiAccounts {
232            metadata: accounts.metadata_account,
233            mint: accounts.collection_mint,
234            mint_authority: accounts.central_state,
235            payer: accounts.fee_payer,
236            update_authority: (accounts.central_state, true),
237            system_program: accounts.system_program,
238            rent: Some(accounts.rent_account),
239        },
240        CreateMetadataAccountV3InstructionArgs {
241            data: DataV2 {
242                name: COLLECTION_NAME.to_string(),
243                uri: COLLECTION_URI.to_string(),
244                symbol: META_SYMBOL.to_string(),
245                seller_fee_basis_points: 0,
246                creators: Some(vec![central_creator]),
247                uses: None,
248                collection: None,
249            },
250            is_mutable: true,
251            collection_details: None,
252        },
253    )
254    .invoke_signed(&[seeds])?;
255
256    // Create master edition
257    msg!("+ Creating master edition");
258    CreateMasterEditionV3Cpi::new(
259        accounts.metadata_program,
260        CreateMasterEditionV3CpiAccounts {
261            edition: accounts.edition,
262            mint: accounts.collection_mint,
263            update_authority: accounts.central_state,
264            token_program: accounts.spl_token_program,
265            system_program: accounts.system_program,
266            rent: Some(accounts.rent_account),
267            mint_authority: accounts.central_state,
268            metadata: accounts.metadata_account,
269            payer: accounts.fee_payer,
270        },
271        CreateMasterEditionV3InstructionArgs {
272            max_supply: Some(0),
273        },
274    )
275    .invoke_signed(&[seeds])?;
276
277    Ok(())
278}