use crate::constants::{MAX_NAME_LEN, MAX_SYMBOL_LEN, MAX_URI_LEN, MPL_TOKEN_METADATA_PROGRAM_ID};
use crate::encoding::BorshTape;
use hopper_runtime::account::AccountView;
use hopper_runtime::address::Address;
use hopper_runtime::error::ProgramError;
use hopper_runtime::instruction::{InstructionAccount, InstructionView, Signer};
use hopper_runtime::ProgramResult;
const DISC_UPDATE_METADATA_ACCOUNT_V2: u8 = 15;
const DISC_CREATE_MASTER_EDITION_V3: u8 = 17;
const DISC_CREATE_METADATA_ACCOUNT_V3: u8 = 33;
#[derive(Clone, Copy)]
pub struct DataV2<'a> {
pub name: &'a str,
pub symbol: &'a str,
pub uri: &'a str,
pub seller_fee_basis_points: u16,
pub has_creators: bool,
pub has_collection: bool,
pub has_uses: bool,
}
impl<'a> DataV2<'a> {
#[inline]
pub const fn simple(
name: &'a str,
symbol: &'a str,
uri: &'a str,
seller_fee_basis_points: u16,
) -> Self {
Self {
name,
symbol,
uri,
seller_fee_basis_points,
has_creators: false,
has_collection: false,
has_uses: false,
}
}
fn validate_lengths(&self) -> ProgramResult {
if self.name.len() > MAX_NAME_LEN
|| self.symbol.len() > MAX_SYMBOL_LEN
|| self.uri.len() > MAX_URI_LEN
{
return Err(ProgramError::InvalidInstructionData);
}
Ok(())
}
#[inline]
pub fn validate_for_context(&self) -> ProgramResult {
self.validate_lengths()
}
fn write_borsh(&self, tape: &mut BorshTape<'_>) -> ProgramResult {
tape.write_str(self.name)?;
tape.write_str(self.symbol)?;
tape.write_str(self.uri)?;
tape.write_u16_le(self.seller_fee_basis_points)?;
if self.has_creators || self.has_collection || self.has_uses {
return Err(ProgramError::InvalidInstructionData);
}
tape.write_option_none()?; tape.write_option_none()?; tape.write_option_none()?; Ok(())
}
}
pub trait IntoMasterEditionMaxSupply {
fn into_master_edition_max_supply(self) -> Option<u64>;
}
impl IntoMasterEditionMaxSupply for u64 {
#[inline]
fn into_master_edition_max_supply(self) -> Option<u64> {
Some(self)
}
}
impl IntoMasterEditionMaxSupply for Option<u64> {
#[inline]
fn into_master_edition_max_supply(self) -> Option<u64> {
self
}
}
pub struct CreateMetadataAccountV3<'a> {
pub metadata: &'a AccountView,
pub mint: &'a AccountView,
pub mint_authority: &'a AccountView,
pub payer: &'a AccountView,
pub update_authority: &'a AccountView,
pub system_program: &'a AccountView,
pub rent: Option<&'a AccountView>,
pub data: DataV2<'a>,
pub is_mutable: bool,
}
impl CreateMetadataAccountV3<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
self.invoke_signed(&[])
}
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
self.data.validate_lengths()?;
let mut buf = [0u8; 320];
let mut tape = BorshTape::new(&mut buf);
tape.write_disc(DISC_CREATE_METADATA_ACCOUNT_V3)?;
self.data.write_borsh_into(&mut tape)?;
tape.write_bool(self.is_mutable)?;
tape.write_option_none()?; let len = tape.len();
if let Some(rent) = self.rent {
let metas = [
InstructionAccount::writable(self.metadata.address()),
InstructionAccount::readonly(self.mint.address()),
InstructionAccount::readonly_signer(self.mint_authority.address()),
InstructionAccount::writable_signer(self.payer.address()),
InstructionAccount::readonly_signer(self.update_authority.address()),
InstructionAccount::readonly(self.system_program.address()),
InstructionAccount::readonly(rent.address()),
];
let views = [
self.metadata,
self.mint,
self.mint_authority,
self.payer,
self.update_authority,
self.system_program,
rent,
];
let instruction = InstructionView {
program_id: &MPL_TOKEN_METADATA_PROGRAM_ID,
data: &buf[..len],
accounts: &metas,
};
hopper_runtime::cpi::invoke_signed(&instruction, &views, signers)
} else {
let metas = [
InstructionAccount::writable(self.metadata.address()),
InstructionAccount::readonly(self.mint.address()),
InstructionAccount::readonly_signer(self.mint_authority.address()),
InstructionAccount::writable_signer(self.payer.address()),
InstructionAccount::readonly_signer(self.update_authority.address()),
InstructionAccount::readonly(self.system_program.address()),
];
let views = [
self.metadata,
self.mint,
self.mint_authority,
self.payer,
self.update_authority,
self.system_program,
];
let instruction = InstructionView {
program_id: &MPL_TOKEN_METADATA_PROGRAM_ID,
data: &buf[..len],
accounts: &metas,
};
hopper_runtime::cpi::invoke_signed(&instruction, &views, signers)
}
}
}
pub struct CreateMasterEditionV3<'a> {
pub edition: &'a AccountView,
pub mint: &'a AccountView,
pub update_authority: &'a AccountView,
pub mint_authority: &'a AccountView,
pub payer: &'a AccountView,
pub metadata: &'a AccountView,
pub token_program: &'a AccountView,
pub system_program: &'a AccountView,
pub rent: Option<&'a AccountView>,
pub max_supply: Option<u64>,
}
impl CreateMasterEditionV3<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
self.invoke_signed(&[])
}
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
let mut buf = [0u8; 16];
let mut tape = BorshTape::new(&mut buf);
tape.write_disc(DISC_CREATE_MASTER_EDITION_V3)?;
tape.write_option_u64_le(self.max_supply)?;
let len = tape.len();
if let Some(rent) = self.rent {
let metas = [
InstructionAccount::writable(self.edition.address()),
InstructionAccount::writable(self.mint.address()),
InstructionAccount::readonly_signer(self.update_authority.address()),
InstructionAccount::readonly_signer(self.mint_authority.address()),
InstructionAccount::writable_signer(self.payer.address()),
InstructionAccount::readonly(self.metadata.address()),
InstructionAccount::readonly(self.token_program.address()),
InstructionAccount::readonly(self.system_program.address()),
InstructionAccount::readonly(rent.address()),
];
let views = [
self.edition,
self.mint,
self.update_authority,
self.mint_authority,
self.payer,
self.metadata,
self.token_program,
self.system_program,
rent,
];
let instruction = InstructionView {
program_id: &MPL_TOKEN_METADATA_PROGRAM_ID,
data: &buf[..len],
accounts: &metas,
};
hopper_runtime::cpi::invoke_signed(&instruction, &views, signers)
} else {
let metas = [
InstructionAccount::writable(self.edition.address()),
InstructionAccount::writable(self.mint.address()),
InstructionAccount::readonly_signer(self.update_authority.address()),
InstructionAccount::readonly_signer(self.mint_authority.address()),
InstructionAccount::writable_signer(self.payer.address()),
InstructionAccount::readonly(self.metadata.address()),
InstructionAccount::readonly(self.token_program.address()),
InstructionAccount::readonly(self.system_program.address()),
];
let views = [
self.edition,
self.mint,
self.update_authority,
self.mint_authority,
self.payer,
self.metadata,
self.token_program,
self.system_program,
];
let instruction = InstructionView {
program_id: &MPL_TOKEN_METADATA_PROGRAM_ID,
data: &buf[..len],
accounts: &metas,
};
hopper_runtime::cpi::invoke_signed(&instruction, &views, signers)
}
}
}
pub struct UpdateMetadataAccountV2<'a> {
pub metadata: &'a AccountView,
pub update_authority: &'a AccountView,
pub new_data: Option<DataV2<'a>>,
pub new_update_authority: Option<&'a Address>,
pub new_primary_sale_happened: Option<bool>,
pub new_is_mutable: Option<bool>,
}
impl UpdateMetadataAccountV2<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
self.invoke_signed(&[])
}
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
if let Some(data) = self.new_data {
data.validate_lengths()?;
}
let mut buf = [0u8; 384];
let mut tape = BorshTape::new(&mut buf);
tape.write_disc(DISC_UPDATE_METADATA_ACCOUNT_V2)?;
match self.new_data {
None => tape.write_option_none()?,
Some(data) => {
tape.write_option_some_tag()?;
data.write_borsh_into(&mut tape)?;
}
}
match self.new_update_authority {
None => tape.write_option_none()?,
Some(addr) => {
tape.write_option_some_tag()?;
tape.reserve_and_write_bytes(addr.as_array())?;
}
}
match self.new_primary_sale_happened {
None => tape.write_option_none()?,
Some(v) => {
tape.write_option_some_tag()?;
tape.write_bool(v)?;
}
}
match self.new_is_mutable {
None => tape.write_option_none()?,
Some(v) => {
tape.write_option_some_tag()?;
tape.write_bool(v)?;
}
}
let len = tape.len();
let metas = [
InstructionAccount::writable(self.metadata.address()),
InstructionAccount::readonly_signer(self.update_authority.address()),
];
let views = [self.metadata, self.update_authority];
let instruction = InstructionView {
program_id: &MPL_TOKEN_METADATA_PROGRAM_ID,
data: &buf[..len],
accounts: &metas,
};
hopper_runtime::cpi::invoke_signed(&instruction, &views, signers)
}
}
impl<'a> DataV2<'a> {
fn write_borsh_into(&self, tape: &mut BorshTape<'_>) -> ProgramResult {
self.write_borsh(tape)
}
}
trait BorshTapeBytes {
fn reserve_and_write_bytes(&mut self, bytes: &[u8]) -> ProgramResult;
}
impl<'a> BorshTapeBytes for BorshTape<'a> {
fn reserve_and_write_bytes(&mut self, bytes: &[u8]) -> ProgramResult {
if self.remaining() < bytes.len() {
return Err(ProgramError::InvalidInstructionData);
}
for &b in bytes {
self.write_u8(b)?;
}
Ok(())
}
}