use crate::error::{Error, Result};
use crate::pubkey::{ids, Pubkey};
use crate::rpc::Account;
const KEY_METADATA_V1: u8 = 4;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenMetadata {
pub update_authority: Option<Pubkey>,
pub mint: Pubkey,
pub name: String,
pub symbol: String,
pub uri: String,
pub seller_fee_basis_points: u16,
pub is_mutable: bool,
pub creators: Vec<Pubkey>,
}
pub fn metadata_address(mint: &Pubkey) -> Result<Pubkey> {
let program = ids::METAPLEX_METADATA_PROGRAM;
let (address, _bump) = Pubkey::find_program_address(
&[b"metadata", program.as_bytes(), mint.as_bytes()],
&program,
)?;
Ok(address)
}
impl TokenMetadata {
pub fn parse(account: &Account) -> Result<Self> {
if account.owner != ids::METAPLEX_METADATA_PROGRAM {
return Err(Error::InvalidAccountData(
"account is not owned by the Metaplex metadata program".into(),
));
}
let d = &account.data;
if d.is_empty() || d[0] != KEY_METADATA_V1 {
return Err(Error::InvalidAccountData(format!(
"metadata account key is {}, expected {KEY_METADATA_V1}",
d.first().copied().unwrap_or(0)
)));
}
let mut c = 1usize;
let update_authority = read_pubkey(d, &mut c)?;
let mint = read_pubkey(d, &mut c)?;
let name = read_padded_string(d, &mut c)?;
let symbol = read_padded_string(d, &mut c)?;
let uri = read_padded_string(d, &mut c)?;
let seller_fee_basis_points = read_u16(d, &mut c)?;
let mut creators = Vec::new();
let has_creators = read_u8(d, &mut c)? == 1;
if has_creators {
let count = read_u32(d, &mut c)?;
for _ in 0..count.min(16) {
let address = read_pubkey(d, &mut c)?;
let _verified = read_u8(d, &mut c)?;
let _share = read_u8(d, &mut c)?;
creators.push(address);
}
}
let _primary_sale_happened = read_u8(d, &mut c)?;
let is_mutable = read_u8(d, &mut c)? == 1;
Ok(TokenMetadata {
update_authority: if update_authority.is_zeroed() {
None
} else {
Some(update_authority)
},
mint,
name,
symbol,
uri,
seller_fee_basis_points,
is_mutable,
creators,
})
}
}
fn read_u8(d: &[u8], c: &mut usize) -> Result<u8> {
let v = *d.get(*c).ok_or_else(short)?;
*c += 1;
Ok(v)
}
fn read_u16(d: &[u8], c: &mut usize) -> Result<u16> {
let end = c.checked_add(2).ok_or_else(short)?;
let slice = d.get(*c..end).ok_or_else(short)?;
*c = end;
Ok(u16::from_le_bytes([slice[0], slice[1]]))
}
fn read_u32(d: &[u8], c: &mut usize) -> Result<u32> {
let end = c.checked_add(4).ok_or_else(short)?;
let slice = d.get(*c..end).ok_or_else(short)?;
*c = end;
Ok(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
}
fn read_pubkey(d: &[u8], c: &mut usize) -> Result<Pubkey> {
let end = c.checked_add(32).ok_or_else(short)?;
let slice = d.get(*c..end).ok_or_else(short)?;
*c = end;
let mut b = [0u8; 32];
b.copy_from_slice(slice);
Ok(Pubkey::new_from_array(b))
}
fn read_padded_string(d: &[u8], c: &mut usize) -> Result<String> {
let len = read_u32(d, c)? as usize;
if len > d.len() {
return Err(Error::InvalidAccountData(
"metadata string length exceeds the account".into(),
));
}
let end = c.checked_add(len).ok_or_else(short)?;
let slice = d.get(*c..end).ok_or_else(short)?;
*c = end;
Ok(String::from_utf8_lossy(slice)
.trim_end_matches('\0')
.to_string())
}
fn short() -> Error {
Error::InvalidAccountData("metadata account is truncated".into())
}