use mpl_token_metadata::{
accounts::{MasterEdition, Metadata},
instructions::{
CreateMetadataAccountV3Cpi, CreateMetadataAccountV3CpiAccounts,
CreateMetadataAccountV3InstructionArgs, SetAndVerifyCollectionCpi,
SetAndVerifyCollectionCpiAccounts, UnverifyCollectionCpi, UnverifyCollectionCpiAccounts,
UpdateMetadataAccountV2Cpi, UpdateMetadataAccountV2CpiAccounts,
UpdateMetadataAccountV2InstructionArgs,
},
};
use crate::{
cpi::Cpi,
state::{
NftRecord, Tag, COLLECTION_PREFIX, CREATOR_FEE, METADATA_SIGNER, META_SYMBOL, MINT_PREFIX,
SELLER_BASIS,
},
utils::check_name,
};
use {
bonfida_utils::{
checks::{check_account_key, check_account_owner, check_signer},
BorshSize, InstructionsAccount,
},
borsh::{BorshDeserialize, BorshSerialize},
mpl_token_metadata::types::{Creator, DataV2},
solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
msg,
program::{invoke, invoke_signed},
program_error::ProgramError,
program_pack::Pack,
pubkey::Pubkey,
system_program, sysvar,
},
spl_name_service::instruction::transfer,
spl_token::{instruction::mint_to, state::Mint},
};
#[derive(BorshDeserialize, BorshSerialize, BorshSize)]
pub struct Params {
pub name: String,
pub uri: String,
}
#[derive(InstructionsAccount)]
pub struct Accounts<'a, T> {
#[cons(writable)]
pub mint: &'a T,
#[cons(writable)]
pub nft_destination: &'a T,
#[cons(writable)]
pub name_account: &'a T,
#[cons(writable)]
pub nft_record: &'a T,
#[cons(writable, signer)]
pub name_owner: &'a T,
#[cons(writable)]
pub metadata_account: &'a T,
pub edition_account: &'a T,
pub collection_metadata: &'a T,
pub collection_mint: &'a T,
#[cons(writable)]
pub central_state: &'a T,
#[cons(writable, signer)]
pub fee_payer: &'a T,
pub spl_token_program: &'a T,
pub metadata_program: &'a T,
pub system_program: &'a T,
pub spl_name_service_program: &'a T,
pub rent_account: &'a T,
#[cons(signer)]
#[cfg(not(feature = "devnet"))]
pub metadata_signer: &'a T,
}
impl<'a, 'b: 'a> Accounts<'a, AccountInfo<'b>> {
pub fn parse(
accounts: &'a [AccountInfo<'b>],
program_id: &Pubkey,
) -> Result<Self, ProgramError> {
let accounts_iter = &mut accounts.iter();
let accounts = Accounts {
mint: next_account_info(accounts_iter)?,
nft_destination: next_account_info(accounts_iter)?,
name_account: next_account_info(accounts_iter)?,
nft_record: next_account_info(accounts_iter)?,
name_owner: next_account_info(accounts_iter)?,
metadata_account: next_account_info(accounts_iter)?,
edition_account: next_account_info(accounts_iter)?,
collection_metadata: next_account_info(accounts_iter)?,
collection_mint: next_account_info(accounts_iter)?,
central_state: next_account_info(accounts_iter)?,
fee_payer: next_account_info(accounts_iter)?,
spl_token_program: next_account_info(accounts_iter)?,
metadata_program: next_account_info(accounts_iter)?,
system_program: next_account_info(accounts_iter)?,
spl_name_service_program: next_account_info(accounts_iter)?,
rent_account: next_account_info(accounts_iter)?,
#[cfg(not(feature = "devnet"))]
metadata_signer: next_account_info(accounts_iter)?,
};
check_account_key(accounts.central_state, &crate::central_state::KEY)?;
check_account_key(accounts.spl_token_program, &spl_token::ID)?;
check_account_key(accounts.metadata_program, &mpl_token_metadata::ID)?;
check_account_key(accounts.system_program, &system_program::ID)?;
check_account_key(accounts.spl_name_service_program, &spl_name_service::ID)?;
check_account_key(accounts.rent_account, &sysvar::rent::ID)?;
#[cfg(not(feature = "devnet"))]
check_account_key(accounts.metadata_signer, &METADATA_SIGNER)?;
check_account_owner(accounts.mint, &spl_token::ID)?;
check_account_owner(accounts.nft_destination, &spl_token::ID)?;
check_account_owner(accounts.name_account, &spl_name_service::ID)?;
check_account_owner(accounts.nft_record, &system_program::ID)
.or_else(|_| check_account_owner(accounts.nft_record, program_id))?;
check_account_owner(accounts.metadata_account, &system_program::ID)
.or_else(|_| check_account_owner(accounts.metadata_account, &mpl_token_metadata::ID))?;
check_account_owner(accounts.edition_account, &mpl_token_metadata::ID)?;
check_account_owner(accounts.collection_metadata, &mpl_token_metadata::ID)?;
check_account_owner(accounts.collection_mint, &spl_token::ID)?;
check_signer(accounts.name_owner)?;
#[cfg(not(feature = "devnet"))]
check_signer(accounts.metadata_signer)?;
Ok(accounts)
}
}
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], params: Params) -> ProgramResult {
let accounts = Accounts::parse(accounts, program_id)?;
let Params { name, uri } = params;
let (mint, _) = Pubkey::find_program_address(
&[MINT_PREFIX, &accounts.name_account.key.to_bytes()],
program_id,
);
check_account_key(accounts.mint, &mint)?;
let (nft_record_key, nft_record_nonce) =
NftRecord::find_key(accounts.name_account.key, program_id);
check_account_key(accounts.nft_record, &nft_record_key)?;
check_name(&name, accounts.name_account)?;
let (metadata_key, _) = Metadata::find_pda(&mint);
check_account_key(accounts.metadata_account, &metadata_key)?;
let (collection_mint, _) =
Pubkey::find_program_address(&[COLLECTION_PREFIX, &program_id.to_bytes()], program_id);
check_account_key(accounts.collection_mint, &collection_mint)?;
let (edition_key, _) = MasterEdition::find_pda(&collection_mint);
check_account_key(accounts.edition_account, &edition_key)?;
let (collection_metadata, _) = Metadata::find_pda(&collection_mint);
check_account_key(accounts.collection_metadata, &collection_metadata)?;
let mint_info = Mint::unpack(&accounts.mint.data.borrow())?;
if mint_info.supply != 0 {
msg!("Expected supply == 0 and received {}", mint_info.supply);
return Err(ProgramError::InvalidAccountData);
}
if accounts.nft_record.data_is_empty() {
msg!("+ Creating NFT record");
let nft_record = NftRecord::new(
nft_record_nonce,
*accounts.name_owner.key,
*accounts.name_account.key,
mint,
);
let seeds: &[&[u8]] = &[
NftRecord::SEED,
&accounts.name_account.key.to_bytes(),
&[nft_record_nonce],
];
Cpi::create_account(
program_id,
accounts.system_program,
accounts.fee_payer,
accounts.nft_record,
seeds,
nft_record.borsh_len(),
)?;
nft_record.save(&mut accounts.nft_record.data.borrow_mut());
} else {
msg!("+ NFT record already exists");
let mut nft_record =
NftRecord::from_account_info(accounts.nft_record, Tag::InactiveRecord)?;
check_account_key(accounts.mint, &nft_record.nft_mint)?;
nft_record.tag = Tag::ActiveRecord;
nft_record.owner = *accounts.name_owner.key;
nft_record.save(&mut accounts.nft_record.data.borrow_mut());
}
let ix = mint_to(
&spl_token::ID,
&mint,
accounts.nft_destination.key,
&crate::central_state::KEY,
&[],
1,
)?;
let seeds: &[&[u8]] = &[&program_id.to_bytes(), &[crate::central_state::NONCE]];
invoke_signed(
&ix,
&[
accounts.spl_token_program.clone(),
accounts.mint.clone(),
accounts.nft_destination.clone(),
accounts.central_state.clone(),
],
&[seeds],
)?;
let central_creator = Creator {
address: crate::central_state::KEY,
verified: true,
share: 0,
};
if accounts.metadata_account.data_is_empty() {
msg!("+ Creating metadata");
CreateMetadataAccountV3Cpi::new(
accounts.metadata_program,
CreateMetadataAccountV3CpiAccounts {
metadata: accounts.metadata_account,
mint: accounts.mint,
mint_authority: accounts.central_state,
payer: accounts.fee_payer,
update_authority: (accounts.central_state, true),
system_program: accounts.system_program,
rent: Some(accounts.rent_account),
},
CreateMetadataAccountV3InstructionArgs {
data: DataV2 {
name,
symbol: META_SYMBOL.to_string(),
uri,
seller_fee_basis_points: SELLER_BASIS,
creators: Some(vec![central_creator, CREATOR_FEE]),
collection: None,
uses: None,
},
is_mutable: true,
collection_details: None,
},
)
.invoke_signed(&[seeds])?;
} else {
msg!("+ Metadata already exists");
UnverifyCollectionCpi::new(
accounts.metadata_program,
UnverifyCollectionCpiAccounts {
metadata: accounts.metadata_account,
collection_authority: accounts.central_state,
collection_mint: accounts.collection_mint,
collection: accounts.collection_metadata,
collection_master_edition_account: accounts.edition_account,
collection_authority_record: None,
},
)
.invoke_signed(&[seeds])?;
let data = DataV2 {
name,
symbol: META_SYMBOL.to_string(),
uri,
seller_fee_basis_points: SELLER_BASIS,
creators: Some(vec![central_creator, CREATOR_FEE]),
collection: None,
uses: None,
};
UpdateMetadataAccountV2Cpi::new(
accounts.metadata_program,
UpdateMetadataAccountV2CpiAccounts {
metadata: accounts.metadata_account,
update_authority: accounts.central_state,
},
UpdateMetadataAccountV2InstructionArgs {
data: Some(data),
new_update_authority: Some(crate::central_state::KEY),
primary_sale_happened: None,
is_mutable: None,
},
)
.invoke_signed(&[seeds])?;
}
msg!("+ Verifying collection");
SetAndVerifyCollectionCpi::new(
accounts.metadata_program,
SetAndVerifyCollectionCpiAccounts {
metadata: accounts.metadata_account,
update_authority: accounts.central_state,
collection_authority: accounts.central_state,
payer: accounts.fee_payer,
collection_mint: accounts.collection_mint,
collection: accounts.collection_metadata,
collection_master_edition_account: accounts.edition_account,
collection_authority_record: None,
},
)
.invoke_signed(&[seeds])?;
let ix = transfer(
spl_name_service::ID,
nft_record_key,
*accounts.name_account.key,
*accounts.name_owner.key,
None,
)?;
invoke(
&ix,
&[
accounts.spl_name_service_program.clone(),
accounts.nft_record.clone(),
accounts.name_account.clone(),
accounts.name_owner.clone(),
],
)?;
Ok(())
}