arch_token_metadata/
state.rs1use {
4 arch_program::{
5 program_error::ProgramError,
6 program_pack::{IsInitialized, Pack, Sealed},
7 pubkey::Pubkey,
8 },
9 borsh::{BorshDeserialize, BorshSerialize},
10};
11
12pub const NAME_MAX_LEN: usize = 256;
14
15pub const SYMBOL_MAX_LEN: usize = 16;
17
18pub const IMAGE_MAX_LEN: usize = 512;
20
21pub const DESCRIPTION_MAX_LEN: usize = 512;
23
24pub const MAX_KEY_LENGTH: usize = 64;
26
27pub const MAX_VALUE_LENGTH: usize = 240;
29
30pub const MAX_ATTRIBUTES: usize = 32;
32
33pub const TOKEN_METADATA_MAX_LEN: usize = 1 + 32 + (4 + NAME_MAX_LEN) +
39 (4 + SYMBOL_MAX_LEN) +
40 (4 + IMAGE_MAX_LEN) +
41 (4 + DESCRIPTION_MAX_LEN) +
42 (1 + 32); pub const TOKEN_METADATA_ATTRIBUTES_MAX_LEN: usize = 1 + 32 + 4 + (MAX_ATTRIBUTES * ((4 + MAX_KEY_LENGTH) + (4 + MAX_VALUE_LENGTH)));
51
52#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)]
54pub struct TokenMetadata {
55 pub is_initialized: bool,
57 pub mint: Pubkey,
59 pub name: String,
61 pub symbol: String,
63 pub image: String,
65 pub description: String,
67 pub update_authority: Option<Pubkey>,
69}
70
71impl Sealed for TokenMetadata {}
72impl IsInitialized for TokenMetadata {
73 fn is_initialized(&self) -> bool {
74 self.is_initialized
75 }
76}
77
78impl Pack for TokenMetadata {
79 const LEN: usize = TOKEN_METADATA_MAX_LEN;
80
81 fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
82 let mut slice_ref: &[u8] = src;
84 BorshDeserialize::deserialize(&mut slice_ref).map_err(|_| ProgramError::InvalidAccountData)
85 }
86
87 fn pack_into_slice(&self, dst: &mut [u8]) {
88 let data = borsh::to_vec(self).unwrap();
89 dst[..data.len()].copy_from_slice(&data);
91 if data.len() < dst.len() {
93 for b in &mut dst[data.len()..] {
94 *b = 0;
95 }
96 }
97 }
98}
99
100#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)]
102pub struct TokenMetadataAttributes {
103 pub is_initialized: bool,
105 pub mint: Pubkey,
107 pub data: Vec<(String, String)>, }
110
111impl Sealed for TokenMetadataAttributes {}
112impl IsInitialized for TokenMetadataAttributes {
113 fn is_initialized(&self) -> bool {
114 self.is_initialized
115 }
116}
117
118impl Pack for TokenMetadataAttributes {
119 const LEN: usize = TOKEN_METADATA_ATTRIBUTES_MAX_LEN;
120
121 fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
122 let mut slice_ref: &[u8] = src;
123 BorshDeserialize::deserialize(&mut slice_ref).map_err(|_| ProgramError::InvalidAccountData)
124 }
125
126 fn pack_into_slice(&self, dst: &mut [u8]) {
127 let data = borsh::to_vec(self).unwrap();
128 dst[..data.len()].copy_from_slice(&data);
129 if data.len() < dst.len() {
130 for b in &mut dst[data.len()..] {
131 *b = 0;
132 }
133 }
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use arch_program::program_pack::Pack;
141
142 fn pk(byte: u8) -> Pubkey {
143 let bytes = [byte; 32];
144 Pubkey::from_slice(&bytes)
145 }
146
147 #[test]
148 fn token_metadata_pack_unpack_roundtrip() {
149 let md = TokenMetadata {
150 is_initialized: true,
151 mint: pk(1),
152 name: "Arch Pioneer Token".to_string(),
153 symbol: "APT".to_string(),
154 image: "https://arweave.net/abc123.png".to_string(),
155 description: "The first token launched on Arch Network".to_string(),
156 update_authority: Some(pk(2)),
157 };
158
159 let mut buf = vec![0u8; TokenMetadata::LEN];
160 TokenMetadata::pack_into_slice(&md, &mut buf);
161
162 assert_eq!(buf[0], 1);
164
165 let md2 = TokenMetadata::unpack_from_slice(&buf).expect("unpack md");
166 assert_eq!(md, md2);
167 }
168
169 #[test]
170 fn token_metadata_unpack_ignores_trailing_zeros() {
171 let md = TokenMetadata {
172 is_initialized: true,
173 mint: pk(9),
174 name: "Name".to_string(),
175 symbol: "SYM".to_string(),
176 image: "img".to_string(),
177 description: "desc".to_string(),
178 update_authority: None,
179 };
180
181 let mut packed = borsh::to_vec(&md).unwrap();
182 packed.extend_from_slice(&vec![0u8; 512]);
184
185 let unpacked = TokenMetadata::unpack_from_slice(&packed).expect("unpack md");
186 assert_eq!(md, unpacked);
187 }
188
189 #[test]
190 fn token_metadata_attributes_pack_unpack_roundtrip() {
191 let attrs = TokenMetadataAttributes {
192 is_initialized: true,
193 mint: pk(3),
194 data: vec![
195 ("key1".to_string(), "value1".to_string()),
196 ("k".to_string(), "v".to_string()),
197 ],
198 };
199
200 let mut buf = vec![0u8; TokenMetadataAttributes::LEN];
201 TokenMetadataAttributes::pack_into_slice(&attrs, &mut buf);
202 assert_eq!(buf[0], 1);
203
204 let attrs2 = TokenMetadataAttributes::unpack_from_slice(&buf).expect("unpack attrs");
205 assert_eq!(attrs, attrs2);
206 }
207
208 #[test]
209 fn token_metadata_attributes_unpack_ignores_trailing_zeros() {
210 let attrs = TokenMetadataAttributes {
211 is_initialized: true,
212 mint: pk(7),
213 data: vec![("alpha".into(), "beta".into())],
214 };
215
216 let mut packed = borsh::to_vec(&attrs).unwrap();
217 packed.extend_from_slice(&vec![0u8; 256]);
218
219 let unpacked = TokenMetadataAttributes::unpack_from_slice(&packed).expect("unpack attrs");
220 assert_eq!(attrs, unpacked);
221 }
222}