use {
arch_program::{
program_error::ProgramError,
program_pack::{IsInitialized, Pack, Sealed},
pubkey::Pubkey,
},
borsh::{BorshDeserialize, BorshSerialize},
};
pub const NAME_MAX_LEN: usize = 256;
pub const SYMBOL_MAX_LEN: usize = 16;
pub const IMAGE_MAX_LEN: usize = 512;
pub const DESCRIPTION_MAX_LEN: usize = 512;
pub const MAX_KEY_LENGTH: usize = 64;
pub const MAX_VALUE_LENGTH: usize = 240;
pub const MAX_ATTRIBUTES: usize = 32;
pub const TOKEN_METADATA_MAX_LEN: usize = 1 + 32 + (4 + NAME_MAX_LEN) +
(4 + SYMBOL_MAX_LEN) +
(4 + IMAGE_MAX_LEN) +
(4 + DESCRIPTION_MAX_LEN) +
(1 + 32);
pub const TOKEN_METADATA_ATTRIBUTES_MAX_LEN: usize = 1 + 32 + 4 + (MAX_ATTRIBUTES * ((4 + MAX_KEY_LENGTH) + (4 + MAX_VALUE_LENGTH)));
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)]
pub struct TokenMetadata {
pub is_initialized: bool,
pub mint: Pubkey,
pub name: String,
pub symbol: String,
pub image: String,
pub description: String,
pub update_authority: Option<Pubkey>,
}
impl Sealed for TokenMetadata {}
impl IsInitialized for TokenMetadata {
fn is_initialized(&self) -> bool {
self.is_initialized
}
}
impl Pack for TokenMetadata {
const LEN: usize = TOKEN_METADATA_MAX_LEN;
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let mut slice_ref: &[u8] = src;
BorshDeserialize::deserialize(&mut slice_ref).map_err(|_| ProgramError::InvalidAccountData)
}
fn pack_into_slice(&self, dst: &mut [u8]) {
let data = borsh::to_vec(self).unwrap();
dst[..data.len()].copy_from_slice(&data);
if data.len() < dst.len() {
for b in &mut dst[data.len()..] {
*b = 0;
}
}
}
}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)]
pub struct TokenMetadataAttributes {
pub is_initialized: bool,
pub mint: Pubkey,
pub data: Vec<(String, String)>, }
impl Sealed for TokenMetadataAttributes {}
impl IsInitialized for TokenMetadataAttributes {
fn is_initialized(&self) -> bool {
self.is_initialized
}
}
impl Pack for TokenMetadataAttributes {
const LEN: usize = TOKEN_METADATA_ATTRIBUTES_MAX_LEN;
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let mut slice_ref: &[u8] = src;
BorshDeserialize::deserialize(&mut slice_ref).map_err(|_| ProgramError::InvalidAccountData)
}
fn pack_into_slice(&self, dst: &mut [u8]) {
let data = borsh::to_vec(self).unwrap();
dst[..data.len()].copy_from_slice(&data);
if data.len() < dst.len() {
for b in &mut dst[data.len()..] {
*b = 0;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use arch_program::program_pack::Pack;
fn pk(byte: u8) -> Pubkey {
let bytes = [byte; 32];
Pubkey::from_slice(&bytes)
}
#[test]
fn token_metadata_pack_unpack_roundtrip() {
let md = TokenMetadata {
is_initialized: true,
mint: pk(1),
name: "Arch Pioneer Token".to_string(),
symbol: "APT".to_string(),
image: "https://arweave.net/abc123.png".to_string(),
description: "The first token launched on Arch Network".to_string(),
update_authority: Some(pk(2)),
};
let mut buf = vec![0u8; TokenMetadata::LEN];
TokenMetadata::pack_into_slice(&md, &mut buf);
assert_eq!(buf[0], 1);
let md2 = TokenMetadata::unpack_from_slice(&buf).expect("unpack md");
assert_eq!(md, md2);
}
#[test]
fn token_metadata_unpack_ignores_trailing_zeros() {
let md = TokenMetadata {
is_initialized: true,
mint: pk(9),
name: "Name".to_string(),
symbol: "SYM".to_string(),
image: "img".to_string(),
description: "desc".to_string(),
update_authority: None,
};
let mut packed = borsh::to_vec(&md).unwrap();
packed.extend_from_slice(&vec![0u8; 512]);
let unpacked = TokenMetadata::unpack_from_slice(&packed).expect("unpack md");
assert_eq!(md, unpacked);
}
#[test]
fn token_metadata_attributes_pack_unpack_roundtrip() {
let attrs = TokenMetadataAttributes {
is_initialized: true,
mint: pk(3),
data: vec![
("key1".to_string(), "value1".to_string()),
("k".to_string(), "v".to_string()),
],
};
let mut buf = vec![0u8; TokenMetadataAttributes::LEN];
TokenMetadataAttributes::pack_into_slice(&attrs, &mut buf);
assert_eq!(buf[0], 1);
let attrs2 = TokenMetadataAttributes::unpack_from_slice(&buf).expect("unpack attrs");
assert_eq!(attrs, attrs2);
}
#[test]
fn token_metadata_attributes_unpack_ignores_trailing_zeros() {
let attrs = TokenMetadataAttributes {
is_initialized: true,
mint: pk(7),
data: vec![("alpha".into(), "beta".into())],
};
let mut packed = borsh::to_vec(&attrs).unwrap();
packed.extend_from_slice(&vec![0u8; 256]);
let unpacked = TokenMetadataAttributes::unpack_from_slice(&packed).expect("unpack attrs");
assert_eq!(attrs, unpacked);
}
}