use crate::constants::SYSTEM_PROGRAM_ID;
use anyhow::{bail, Result};
use mpl_token_metadata::{
instructions::{
CreateMasterEditionV3Builder, CreateMetadataAccountV3Builder, CreateV1Builder,
MintV1Builder, UpdateMetadataAccountV2Builder,
},
types::{
AuthorizationData, Collection, CollectionDetails, Creator, PrintSupply, TokenStandard, Uses,
},
ID,
};
use serde::{Deserialize, Serialize};
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
compute_budget::ComputeBudgetInstruction,
pubkey::Pubkey,
signature::Signature,
signer::{keypair::Keypair, Signer},
system_instruction::create_account,
};
use spl_associated_token_account::{
get_associated_token_address, instruction::create_associated_token_account,
};
use spl_token::{
instruction::{initialize_mint, mint_to},
ID as TOKEN_PROGRAM_ID,
};
use std::str::FromStr;
use crate::{
constants::MINT_LAYOUT_SIZE, data::Priority, decode::ToPubkey, transaction::get_compute_units,
};
use crate::{convert::convert_local_to_remote_data, transaction::send_and_confirm_tx};
use crate::{
data::{Asset, NftData},
derive::derive_token_record_pda,
};
#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]
pub struct AssetData {
pub name: String,
pub symbol: String,
pub uri: String,
pub seller_fee_basis_points: u16,
pub creators: Option<Vec<Creator>>,
pub primary_sale_happened: bool,
pub is_mutable: bool,
pub token_standard: TokenStandard,
pub collection: Option<Collection>,
pub uses: Option<Uses>,
pub collection_details: Option<CollectionDetails>,
pub rule_set: Option<String>,
}
pub enum MintAssetArgs<'a, P: ToPubkey> {
V1 {
payer: Option<&'a Keypair>,
authority: &'a Keypair,
receiver: P,
mint: Option<Keypair>,
asset_data: AssetData,
print_supply: Option<PrintSupply>,
mint_decimals: Option<u8>,
amount: u64,
authorization_data: Option<AuthorizationData>,
priority: Priority,
},
}
pub struct MintResult {
pub signature: Signature,
pub mint: Pubkey,
}
pub fn mint_asset<P: ToPubkey>(client: &RpcClient, args: MintAssetArgs<P>) -> Result<MintResult> {
match args {
MintAssetArgs::V1 { .. } => mint_asset_v1(client, args),
}
}
fn mint_asset_v1<P: ToPubkey>(client: &RpcClient, args: MintAssetArgs<P>) -> Result<MintResult> {
let MintAssetArgs::V1 {
payer,
authority,
receiver,
mint,
asset_data,
print_supply,
mint_decimals,
amount,
authorization_data,
priority,
} = args;
let mint_signer = if let Some(mint) = mint {
mint
} else {
Keypair::new()
};
let mut asset = Asset::new(mint_signer.pubkey());
let receiver = receiver.to_pubkey()?;
let payer = payer.unwrap_or(authority);
let token_standard = asset_data.token_standard;
if let Some(decimals) = mint_decimals {
if decimals > 9 {
bail!("Decimals must be less than or equal to 9");
}
}
let mut create_builder = CreateV1Builder::new();
create_builder
.mint(asset.mint, true)
.metadata(asset.metadata)
.authority(authority.pubkey())
.payer(payer.pubkey())
.update_authority(authority.pubkey(), true)
.name(asset_data.name)
.symbol(asset_data.symbol)
.uri(asset_data.uri)
.seller_fee_basis_points(asset_data.seller_fee_basis_points)
.primary_sale_happened(asset_data.primary_sale_happened)
.is_mutable(asset_data.is_mutable)
.token_standard(token_standard)
.system_program(SYSTEM_PROGRAM_ID)
.spl_token_program(Some(spl_token::ID));
if let Some(creators) = asset_data.creators {
create_builder.creators(creators);
}
if let Some(collection) = asset_data.collection {
create_builder.collection(collection);
}
if let Some(uses) = asset_data.uses {
create_builder.uses(uses);
}
if let Some(details) = asset_data.collection_details {
create_builder.collection_details(details);
}
if let Some(rule_set) = asset_data.rule_set {
create_builder.rule_set(Pubkey::from_str(&rule_set)?);
}
if let Some(decimals) = mint_decimals {
create_builder.decimals(decimals);
}
if let Some(print_supply) = print_supply {
create_builder.print_supply(print_supply);
}
let token_ata = get_associated_token_address(&receiver, &asset.mint);
let token_record = derive_token_record_pda(&asset.mint, &token_ata);
let mut mint_builder = MintV1Builder::new();
mint_builder
.metadata(asset.metadata)
.token(token_ata)
.token_owner(Some(receiver))
.token_record(Some(token_record))
.mint(asset.mint)
.authority(authority.pubkey())
.payer(payer.pubkey())
.system_program(SYSTEM_PROGRAM_ID);
if matches!(
token_standard,
TokenStandard::NonFungible | TokenStandard::ProgrammableNonFungible
) {
if amount != 1 {
bail!("Non-fungible assets must have an amount of 1");
}
asset.add_edition();
create_builder.master_edition(asset.edition);
mint_builder.master_edition(asset.edition);
mint_builder.amount(amount);
}
let create_ix = create_builder.instruction();
mint_builder.amount(amount);
if let Some(data) = authorization_data {
mint_builder.authorization_data(data);
}
let mint_ix = mint_builder.instruction();
let instructions = vec![create_ix, mint_ix];
let micro_lamports = match priority {
Priority::None => 20, Priority::Low => 20_000, Priority::Medium => 200_000, Priority::High => 1_000_000, Priority::Max => 2_000_000, };
let signers = vec![payer, authority, &mint_signer];
let units = get_compute_units(client, &instructions, &signers)?.unwrap_or(200_000);
let mut final_instructions = vec![
ComputeBudgetInstruction::set_compute_unit_limit(units as u32),
ComputeBudgetInstruction::set_compute_unit_price(micro_lamports),
];
final_instructions.extend(instructions);
let sig = send_and_confirm_tx(client, &signers, &final_instructions)?;
Ok(MintResult {
signature: sig,
mint: asset.mint,
})
}
pub fn mint(
client: &RpcClient,
funder: Keypair,
receiver: Pubkey,
nft_data: NftData,
immutable: bool,
primary_sale_happened: bool,
) -> Result<(Signature, Pubkey)> {
let metaplex_program_id = ID;
let mint = Keypair::new();
let data = convert_local_to_remote_data(nft_data)?;
let min_rent = client.get_minimum_balance_for_rent_exemption(MINT_LAYOUT_SIZE as usize)?;
let create_mint_account_ix = create_account(
&funder.pubkey(),
&mint.pubkey(),
min_rent,
MINT_LAYOUT_SIZE,
&TOKEN_PROGRAM_ID,
);
let init_mint_ix = initialize_mint(
&TOKEN_PROGRAM_ID,
&mint.pubkey(),
&funder.pubkey(),
Some(&funder.pubkey()),
0,
)?;
let assoc = get_associated_token_address(&receiver, &mint.pubkey());
let create_assoc_account_ix = create_associated_token_account(
&funder.pubkey(),
&receiver,
&mint.pubkey(),
&spl_token::ID,
);
let mint_to_ix = mint_to(
&TOKEN_PROGRAM_ID,
&mint.pubkey(),
&assoc,
&funder.pubkey(),
&[],
1,
)?;
let metadata_seeds = &[
"metadata".as_bytes(),
&metaplex_program_id.to_bytes(),
&mint.pubkey().to_bytes(),
];
let (metadata_account, _pda) =
Pubkey::find_program_address(metadata_seeds, &metaplex_program_id);
let master_edition_seeds = &[
"metadata".as_bytes(),
&metaplex_program_id.to_bytes(),
&mint.pubkey().to_bytes(),
"edition".as_bytes(),
];
let (master_edition_account, _pda) =
Pubkey::find_program_address(master_edition_seeds, &metaplex_program_id);
let create_metadata_account_ix = CreateMetadataAccountV3Builder::new()
.metadata(metadata_account)
.mint(mint.pubkey())
.mint_authority(funder.pubkey())
.payer(funder.pubkey())
.update_authority(funder.pubkey(), true)
.data(data)
.is_mutable(!immutable)
.system_program(SYSTEM_PROGRAM_ID)
.instruction();
let create_master_edition_account_ix = CreateMasterEditionV3Builder::new()
.edition(master_edition_account)
.mint(mint.pubkey())
.update_authority(funder.pubkey())
.mint_authority(funder.pubkey())
.payer(funder.pubkey())
.metadata(metadata_account)
.token_program(spl_token::ID)
.system_program(SYSTEM_PROGRAM_ID)
.max_supply(0)
.instruction();
let mut instructions = vec![
create_mint_account_ix,
init_mint_ix,
create_assoc_account_ix,
mint_to_ix,
create_metadata_account_ix,
create_master_edition_account_ix,
];
if primary_sale_happened {
let ix = UpdateMetadataAccountV2Builder::new()
.metadata(metadata_account)
.update_authority(funder.pubkey())
.primary_sale_happened(true)
.instruction();
instructions.push(ix);
}
let sig = send_and_confirm_tx(client, &[&funder, &mint], &instructions)?;
Ok((sig, mint.pubkey()))
}