1use mpl_token_metadata::{
4 accounts::{MasterEdition, Metadata},
5 instructions::{
6 CreateMetadataAccountV3Cpi, CreateMetadataAccountV3CpiAccounts,
7 CreateMetadataAccountV3InstructionArgs, SetAndVerifyCollectionCpi,
8 SetAndVerifyCollectionCpiAccounts, UnverifyCollectionCpi, UnverifyCollectionCpiAccounts,
9 UpdateMetadataAccountV2Cpi, UpdateMetadataAccountV2CpiAccounts,
10 UpdateMetadataAccountV2InstructionArgs,
11 },
12};
13
14use crate::{
15 cpi::Cpi,
16 state::{
17 NftRecord, Tag, COLLECTION_PREFIX, CREATOR_FEE, METADATA_SIGNER, META_SYMBOL, MINT_PREFIX,
18 SELLER_BASIS,
19 },
20 utils::check_name,
21};
22
23use {
24 bonfida_utils::{
25 checks::{check_account_key, check_account_owner, check_signer},
26 BorshSize, InstructionsAccount,
27 },
28 borsh::{BorshDeserialize, BorshSerialize},
29 mpl_token_metadata::types::{Creator, DataV2},
30 solana_program::{
31 account_info::{next_account_info, AccountInfo},
32 entrypoint::ProgramResult,
33 msg,
34 program::{invoke, invoke_signed},
35 program_error::ProgramError,
36 program_pack::Pack,
37 pubkey::Pubkey,
38 system_program, sysvar,
39 },
40 spl_name_service::instruction::transfer,
41 spl_token::{instruction::mint_to, state::Mint},
42};
43
44#[derive(BorshDeserialize, BorshSerialize, BorshSize)]
45pub struct Params {
46 pub name: String,
48
49 pub uri: String,
51}
52
53#[derive(InstructionsAccount)]
54pub struct Accounts<'a, T> {
55 #[cons(writable)]
57 pub mint: &'a T,
58
59 #[cons(writable)]
61 pub nft_destination: &'a T,
62
63 #[cons(writable)]
65 pub name_account: &'a T,
66
67 #[cons(writable)]
69 pub nft_record: &'a T,
70
71 #[cons(writable, signer)]
73 pub name_owner: &'a T,
74
75 #[cons(writable)]
77 pub metadata_account: &'a T,
78
79 pub edition_account: &'a T,
81
82 pub collection_metadata: &'a T,
84
85 pub collection_mint: &'a T,
87
88 #[cons(writable)]
90 pub central_state: &'a T,
91
92 #[cons(writable, signer)]
94 pub fee_payer: &'a T,
95
96 pub spl_token_program: &'a T,
98
99 pub metadata_program: &'a T,
101
102 pub system_program: &'a T,
104
105 pub spl_name_service_program: &'a T,
107
108 pub rent_account: &'a T,
110
111 #[cons(signer)]
113 #[cfg(not(feature = "devnet"))]
114 pub metadata_signer: &'a T,
115}
116
117impl<'a, 'b: 'a> Accounts<'a, AccountInfo<'b>> {
118 pub fn parse(
119 accounts: &'a [AccountInfo<'b>],
120 program_id: &Pubkey,
121 ) -> Result<Self, ProgramError> {
122 let accounts_iter = &mut accounts.iter();
123 let accounts = Accounts {
124 mint: next_account_info(accounts_iter)?,
125 nft_destination: next_account_info(accounts_iter)?,
126 name_account: next_account_info(accounts_iter)?,
127 nft_record: next_account_info(accounts_iter)?,
128 name_owner: next_account_info(accounts_iter)?,
129 metadata_account: next_account_info(accounts_iter)?,
130 edition_account: next_account_info(accounts_iter)?,
131 collection_metadata: next_account_info(accounts_iter)?,
132 collection_mint: next_account_info(accounts_iter)?,
133 central_state: next_account_info(accounts_iter)?,
134 fee_payer: next_account_info(accounts_iter)?,
135 spl_token_program: next_account_info(accounts_iter)?,
136 metadata_program: next_account_info(accounts_iter)?,
137 system_program: next_account_info(accounts_iter)?,
138 spl_name_service_program: next_account_info(accounts_iter)?,
139 rent_account: next_account_info(accounts_iter)?,
140 #[cfg(not(feature = "devnet"))]
141 metadata_signer: next_account_info(accounts_iter)?,
142 };
143
144 check_account_key(accounts.central_state, &crate::central_state::KEY)?;
146 check_account_key(accounts.spl_token_program, &spl_token::ID)?;
147 check_account_key(accounts.metadata_program, &mpl_token_metadata::ID)?;
148 check_account_key(accounts.system_program, &system_program::ID)?;
149 check_account_key(accounts.spl_name_service_program, &spl_name_service::ID)?;
150 check_account_key(accounts.rent_account, &sysvar::rent::ID)?;
151 #[cfg(not(feature = "devnet"))]
152 check_account_key(accounts.metadata_signer, &METADATA_SIGNER)?;
153
154 check_account_owner(accounts.mint, &spl_token::ID)?;
156 check_account_owner(accounts.nft_destination, &spl_token::ID)?;
157 check_account_owner(accounts.name_account, &spl_name_service::ID)?;
158 check_account_owner(accounts.nft_record, &system_program::ID)
159 .or_else(|_| check_account_owner(accounts.nft_record, program_id))?;
160 check_account_owner(accounts.metadata_account, &system_program::ID)
161 .or_else(|_| check_account_owner(accounts.metadata_account, &mpl_token_metadata::ID))?;
162 check_account_owner(accounts.edition_account, &mpl_token_metadata::ID)?;
163 check_account_owner(accounts.collection_metadata, &mpl_token_metadata::ID)?;
164 check_account_owner(accounts.collection_mint, &spl_token::ID)?;
165
166 check_signer(accounts.name_owner)?;
168 #[cfg(not(feature = "devnet"))]
169 check_signer(accounts.metadata_signer)?;
170
171 Ok(accounts)
172 }
173}
174
175pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], params: Params) -> ProgramResult {
176 let accounts = Accounts::parse(accounts, program_id)?;
177 let Params { name, uri } = params;
178
179 let (mint, _) = Pubkey::find_program_address(
180 &[MINT_PREFIX, &accounts.name_account.key.to_bytes()],
181 program_id,
182 );
183 check_account_key(accounts.mint, &mint)?;
184
185 let (nft_record_key, nft_record_nonce) =
187 NftRecord::find_key(accounts.name_account.key, program_id);
188 check_account_key(accounts.nft_record, &nft_record_key)?;
189
190 check_name(&name, accounts.name_account)?;
192
193 let (metadata_key, _) = Metadata::find_pda(&mint);
195 check_account_key(accounts.metadata_account, &metadata_key)?;
196
197 let (collection_mint, _) =
199 Pubkey::find_program_address(&[COLLECTION_PREFIX, &program_id.to_bytes()], program_id);
200 check_account_key(accounts.collection_mint, &collection_mint)?;
201
202 let (edition_key, _) = MasterEdition::find_pda(&collection_mint);
203 check_account_key(accounts.edition_account, &edition_key)?;
204
205 let (collection_metadata, _) = Metadata::find_pda(&collection_mint);
207 check_account_key(accounts.collection_metadata, &collection_metadata)?;
208
209 let mint_info = Mint::unpack(&accounts.mint.data.borrow())?;
211 if mint_info.supply != 0 {
212 msg!("Expected supply == 0 and received {}", mint_info.supply);
213 return Err(ProgramError::InvalidAccountData);
214 }
215
216 if accounts.nft_record.data_is_empty() {
217 msg!("+ Creating NFT record");
218 let nft_record = NftRecord::new(
219 nft_record_nonce,
220 *accounts.name_owner.key,
221 *accounts.name_account.key,
222 mint,
223 );
224 let seeds: &[&[u8]] = &[
225 NftRecord::SEED,
226 &accounts.name_account.key.to_bytes(),
227 &[nft_record_nonce],
228 ];
229 Cpi::create_account(
230 program_id,
231 accounts.system_program,
232 accounts.fee_payer,
233 accounts.nft_record,
234 seeds,
235 nft_record.borsh_len(),
236 )?;
237
238 nft_record.save(&mut accounts.nft_record.data.borrow_mut());
239 } else {
240 msg!("+ NFT record already exists");
241 let mut nft_record =
242 NftRecord::from_account_info(accounts.nft_record, Tag::InactiveRecord)?;
243
244 check_account_key(accounts.mint, &nft_record.nft_mint)?;
245
246 nft_record.tag = Tag::ActiveRecord;
247 nft_record.owner = *accounts.name_owner.key;
248
249 nft_record.save(&mut accounts.nft_record.data.borrow_mut());
250 }
251
252 let ix = mint_to(
254 &spl_token::ID,
255 &mint,
256 accounts.nft_destination.key,
257 &crate::central_state::KEY,
258 &[],
259 1,
260 )?;
261 let seeds: &[&[u8]] = &[&program_id.to_bytes(), &[crate::central_state::NONCE]];
262
263 invoke_signed(
264 &ix,
265 &[
266 accounts.spl_token_program.clone(),
267 accounts.mint.clone(),
268 accounts.nft_destination.clone(),
269 accounts.central_state.clone(),
270 ],
271 &[seeds],
272 )?;
273
274 let central_creator = Creator {
276 address: crate::central_state::KEY,
277 verified: true,
278 share: 0,
279 };
280 if accounts.metadata_account.data_is_empty() {
281 msg!("+ Creating metadata");
282 CreateMetadataAccountV3Cpi::new(
283 accounts.metadata_program,
284 CreateMetadataAccountV3CpiAccounts {
285 metadata: accounts.metadata_account,
286 mint: accounts.mint,
287 mint_authority: accounts.central_state,
288 payer: accounts.fee_payer,
289 update_authority: (accounts.central_state, true),
290 system_program: accounts.system_program,
291 rent: Some(accounts.rent_account),
292 },
293 CreateMetadataAccountV3InstructionArgs {
294 data: DataV2 {
295 name,
296 symbol: META_SYMBOL.to_string(),
297 uri,
298 seller_fee_basis_points: SELLER_BASIS,
299 creators: Some(vec![central_creator, CREATOR_FEE]),
300 collection: None,
301 uses: None,
302 },
303 is_mutable: true,
304 collection_details: None,
305 },
306 )
307 .invoke_signed(&[seeds])?;
308 } else {
309 msg!("+ Metadata already exists");
310 UnverifyCollectionCpi::new(
312 accounts.metadata_program,
313 UnverifyCollectionCpiAccounts {
314 metadata: accounts.metadata_account,
315 collection_authority: accounts.central_state,
316 collection_mint: accounts.collection_mint,
317 collection: accounts.collection_metadata,
318 collection_master_edition_account: accounts.edition_account,
319 collection_authority_record: None,
320 },
321 )
322 .invoke_signed(&[seeds])?;
323
324 let data = DataV2 {
325 name,
326 symbol: META_SYMBOL.to_string(),
327 uri,
328 seller_fee_basis_points: SELLER_BASIS,
329 creators: Some(vec![central_creator, CREATOR_FEE]),
330 collection: None,
331 uses: None,
332 };
333 UpdateMetadataAccountV2Cpi::new(
334 accounts.metadata_program,
335 UpdateMetadataAccountV2CpiAccounts {
336 metadata: accounts.metadata_account,
337 update_authority: accounts.central_state,
338 },
339 UpdateMetadataAccountV2InstructionArgs {
340 data: Some(data),
341 new_update_authority: Some(crate::central_state::KEY),
342 primary_sale_happened: None,
343 is_mutable: None,
344 },
345 )
346 .invoke_signed(&[seeds])?;
347 }
348
349 msg!("+ Verifying collection");
350 SetAndVerifyCollectionCpi::new(
351 accounts.metadata_program,
352 SetAndVerifyCollectionCpiAccounts {
353 metadata: accounts.metadata_account,
354 update_authority: accounts.central_state,
355 collection_authority: accounts.central_state,
356 payer: accounts.fee_payer,
357 collection_mint: accounts.collection_mint,
358 collection: accounts.collection_metadata,
359 collection_master_edition_account: accounts.edition_account,
360 collection_authority_record: None,
361 },
362 )
363 .invoke_signed(&[seeds])?;
364
365 let ix = transfer(
367 spl_name_service::ID,
368 nft_record_key,
369 *accounts.name_account.key,
370 *accounts.name_owner.key,
371 None,
372 )?;
373 invoke(
374 &ix,
375 &[
376 accounts.spl_name_service_program.clone(),
377 accounts.nft_record.clone(),
378 accounts.name_account.clone(),
379 accounts.name_owner.clone(),
380 ],
381 )?;
382
383 Ok(())
384}