1use {
4 crate::{
5 error::MetadataError,
6 find_attributes_pda_with_program, find_metadata_pda_with_program,
7 instruction::MetadataInstruction,
8 state::{
9 TokenMetadata, TokenMetadataAttributes, DESCRIPTION_MAX_LEN, IMAGE_MAX_LEN,
10 MAX_ATTRIBUTES, MAX_KEY_LENGTH, MAX_VALUE_LENGTH, NAME_MAX_LEN, SYMBOL_MAX_LEN,
11 },
12 ATTRIBUTES_SEED, METADATA_SEED,
13 },
14 apl_token::{self, state::Mint},
15 arch_program::{
16 account::{next_account_info, AccountInfo},
17 entrypoint::ProgramResult,
18 msg,
19 program::invoke_signed,
20 program_error::ProgramError,
21 program_option::COption,
22 program_pack::{IsInitialized, Pack},
23 pubkey::Pubkey,
24 rent::minimum_rent,
25 system_instruction::create_account,
26 },
27};
28
29pub struct Processor {}
31
32impl Processor {
33 pub fn process(
35 program_id: &Pubkey,
36 accounts: &[AccountInfo],
37 instruction_data: &[u8],
38 ) -> ProgramResult {
39 let instruction = MetadataInstruction::unpack(instruction_data)?;
40
41 match instruction {
42 MetadataInstruction::CreateMetadata {
43 name,
44 symbol,
45 image,
46 description,
47 immutable,
48 } => Self::process_create_metadata(
49 program_id,
50 accounts,
51 name,
52 symbol,
53 image,
54 description,
55 immutable,
56 ),
57 MetadataInstruction::UpdateMetadata {
58 name,
59 symbol,
60 image,
61 description,
62 } => Self::process_update_metadata(accounts, name, symbol, image, description),
63 MetadataInstruction::CreateAttributes { data } => {
64 Self::process_create_attributes(program_id, accounts, data)
65 }
66 MetadataInstruction::ReplaceAttributes { data } => {
67 Self::process_replace_attributes(program_id, accounts, data)
68 }
69
70 MetadataInstruction::TransferAuthority { new_authority } => {
71 Self::process_transfer_authority(accounts, new_authority)
72 }
73
74 MetadataInstruction::MakeImmutable => Self::process_make_immutable(accounts),
75 }
76 }
77
78 fn process_create_metadata(
79 program_id: &Pubkey,
80 accounts: &[AccountInfo],
81 name: String,
82 symbol: String,
83 image: String,
84 description: String,
85 immutable: bool,
86 ) -> ProgramResult {
87 let account_info_iter = &mut accounts.iter();
88 let payer_info = next_account_info(account_info_iter)?; let system_program_info = next_account_info(account_info_iter)?; let mint_info = next_account_info(account_info_iter)?; let metadata_info = next_account_info(account_info_iter)?; let mint_authority_info = next_account_info(account_info_iter)?; if mint_info.owner != &apl_token::id() {
96 msg!(
97 "Mint is not owned by the token program expected {:?}, got {:?}",
98 apl_token::id(),
99 mint_info.owner
100 );
101 return Err(ProgramError::IncorrectProgramId);
102 }
103
104 let mint = Mint::unpack(&mint_info.data.borrow()) .map_err(|_| ProgramError::InvalidAccountData)?;
107
108 if !mint.is_initialized() {
109 msg!("Mint is not initialized");
110 return Err(ProgramError::UninitializedAccount);
111 }
112
113 let matched_signer: Option<Pubkey> = match mint.mint_authority {
115 COption::Some(mint_auth) => {
116 if cmp_pubkeys(&mint_auth, mint_authority_info.key) {
117 Some(mint_auth)
118 } else {
119 msg!("Mint authority does not match expected authority");
120 return Err(MetadataError::InvalidAuthority.into());
121 }
122 }
123 COption::None => match mint.freeze_authority {
124 COption::Some(freeze_auth) => {
125 if cmp_pubkeys(&freeze_auth, mint_authority_info.key) {
126 Some(freeze_auth)
127 } else {
128 msg!("Freeze authority does not match expected authority");
129 return Err(MetadataError::InvalidAuthority.into());
130 }
131 }
132 COption::None => {
133 msg!("Mint has no mint or freeze authority");
134 return Err(MetadataError::InvalidAuthority.into());
135 }
136 },
137 };
138
139 if !mint_authority_info.is_signer {
141 msg!("Mint authority is not a signer");
142 return Err(MetadataError::InvalidAuthority.into());
143 }
144
145 let (expected_md_pda, md_bump) = find_metadata_pda_with_program(program_id, mint_info.key);
147 if !cmp_pubkeys(&expected_md_pda, metadata_info.key) {
148 msg!("Metadata PDA does not match expected PDA");
149 return Err(ProgramError::InvalidSeeds);
150 }
151
152 if name.len() > NAME_MAX_LEN
154 || symbol.len() > SYMBOL_MAX_LEN
155 || image.len() > IMAGE_MAX_LEN
156 || description.len() > DESCRIPTION_MAX_LEN
157 {
158 msg!(
159 "Metadata field size is too long: name={}/{}, symbol={}/{}, image={}/{}, description={}/{}",
160 name.len(),
161 NAME_MAX_LEN,
162 symbol.len(),
163 SYMBOL_MAX_LEN,
164 image.len(),
165 IMAGE_MAX_LEN,
166 description.len(),
167 DESCRIPTION_MAX_LEN,
168 );
169 return Err(MetadataError::StringTooLong.into());
170 }
171
172 if metadata_info.owner != program_id {
174 if *system_program_info.key != Pubkey::system_program() {
176 msg!("System program id does not match expected system program id");
177 return Err(ProgramError::IncorrectProgramId);
178 }
179
180 if !payer_info.is_signer {
181 msg!("Payer is not a signer");
182 return Err(ProgramError::MissingRequiredSignature);
183 }
184
185 let space = TokenMetadata::LEN as u64;
186 let lamports = minimum_rent(TokenMetadata::LEN);
187
188 invoke_signed(
189 &create_account(
190 payer_info.key,
191 metadata_info.key,
192 lamports,
193 space,
194 program_id,
195 ),
196 &[
197 payer_info.clone(),
198 metadata_info.clone(),
199 system_program_info.clone(),
200 ],
201 &[&[
202 METADATA_SEED, mint_info.key.as_ref(),
204 &[md_bump],
205 ]],
206 )?;
207 }
208
209 {
211 let data_ref = metadata_info.data.borrow();
212 if !data_ref.is_empty() && data_ref[0] != 0 {
213 return Err(MetadataError::MetadataAlreadyExists.into());
214 }
215 }
216
217 let metadata = TokenMetadata {
219 is_initialized: true,
220 mint: *mint_info.key,
221 name,
222 symbol,
223 image,
224 description,
225 update_authority: if immutable { None } else { matched_signer },
226 };
227
228 metadata.pack_into_slice(&mut metadata_info.data.borrow_mut());
229
230 Ok(())
231 }
232
233 fn process_update_metadata(
234 accounts: &[AccountInfo],
235 name: Option<String>,
236 symbol: Option<String>,
237 image: Option<String>,
238 description: Option<String>,
239 ) -> ProgramResult {
240 let account_info_iter = &mut accounts.iter();
241 let metadata_info = next_account_info(account_info_iter)?; let update_authority_info = next_account_info(account_info_iter)?; if !update_authority_info.is_signer {
245 msg!("Update authority is not a signer");
246 return Err(ProgramError::MissingRequiredSignature);
247 }
248
249 let mut metadata = TokenMetadata::unpack(&metadata_info.data.borrow())
251 .map_err(|_| ProgramError::InvalidAccountData)?;
252
253 if !metadata.is_initialized() {
254 msg!("Metadata not initialized");
255 return Err(ProgramError::UninitializedAccount);
256 }
257
258 match metadata.update_authority {
260 Some(current_auth) => {
261 if !cmp_pubkeys(¤t_auth, update_authority_info.key) {
262 msg!("Update authority does not match");
263 return Err(MetadataError::InvalidAuthority.into());
264 }
265 }
266 None => {
267 msg!("Metadata is immutable");
268 return Err(MetadataError::InvalidAuthority.into());
269 }
270 }
271
272 if let Some(ref n) = name {
274 if n.len() > NAME_MAX_LEN {
275 return Err(MetadataError::StringTooLong.into());
276 }
277 }
278 if let Some(ref s) = symbol {
279 if s.len() > SYMBOL_MAX_LEN {
280 return Err(MetadataError::StringTooLong.into());
281 }
282 }
283 if let Some(ref i) = image {
284 if i.len() > IMAGE_MAX_LEN {
285 return Err(MetadataError::StringTooLong.into());
286 }
287 }
288 if let Some(ref d) = description {
289 if d.len() > DESCRIPTION_MAX_LEN {
290 return Err(MetadataError::StringTooLong.into());
291 }
292 }
293
294 if let Some(n) = name {
295 metadata.name = n;
296 }
297 if let Some(s) = symbol {
298 metadata.symbol = s;
299 }
300 if let Some(i) = image {
301 metadata.image = i;
302 }
303 if let Some(d) = description {
304 metadata.description = d;
305 }
306
307 metadata.pack_into_slice(&mut metadata_info.data.borrow_mut());
308 Ok(())
309 }
310
311 fn process_create_attributes(
312 program_id: &Pubkey,
313 accounts: &[AccountInfo],
314 data: Vec<(String, String)>,
315 ) -> ProgramResult {
316 let account_info_iter = &mut accounts.iter();
317 let payer_info = next_account_info(account_info_iter)?; let system_program_info = next_account_info(account_info_iter)?; let mint_info = next_account_info(account_info_iter)?; let attributes_info = next_account_info(account_info_iter)?; let update_authority_info = next_account_info(account_info_iter)?; let metadata_info = next_account_info(account_info_iter)?; if !payer_info.is_signer || !update_authority_info.is_signer {
325 return Err(ProgramError::MissingRequiredSignature);
326 }
327
328 let (expected_attrs_pda, attrs_bump) =
330 find_attributes_pda_with_program(program_id, mint_info.key);
331 if !cmp_pubkeys(&expected_attrs_pda, attributes_info.key) {
332 msg!("Attributes PDA does not match expected PDA");
333 return Err(ProgramError::InvalidSeeds);
334 }
335
336 let metadata = TokenMetadata::unpack(&metadata_info.data.borrow())
338 .map_err(|_| ProgramError::InvalidAccountData)?;
339 if !metadata.is_initialized() || !cmp_pubkeys(&metadata.mint, mint_info.key) {
340 msg!("Metadata not initialized or mint mismatch");
341 return Err(ProgramError::InvalidAccountData);
342 }
343
344 match metadata.update_authority {
345 Some(current_auth) => {
346 if !cmp_pubkeys(¤t_auth, update_authority_info.key) {
347 msg!("Update authority does not match");
348 return Err(MetadataError::InvalidAuthority.into());
349 }
350 }
351 None => {
352 msg!("Metadata is immutable");
353 return Err(MetadataError::InvalidAuthority.into());
354 }
355 }
356
357 if data.len() > MAX_ATTRIBUTES {
359 msg!("Too many attributes: {} > {}", data.len(), MAX_ATTRIBUTES);
360 return Err(MetadataError::TooManyAttributes.into());
361 }
362 for (k, v) in &data {
363 if k.is_empty() || v.is_empty() {
364 msg!("Attribute key and value must be non-empty");
365 return Err(MetadataError::InvalidInstructionData.into());
366 }
367 if k.len() > MAX_KEY_LENGTH || v.len() > MAX_VALUE_LENGTH {
368 msg!(
369 "Attribute key or value is too long: key={}/{}, value={}/{}",
370 k.len(),
371 MAX_KEY_LENGTH,
372 v.len(),
373 MAX_VALUE_LENGTH,
374 );
375 return Err(MetadataError::StringTooLong.into());
376 }
377 }
378
379 let required_space: u64 = TokenMetadataAttributes::LEN as u64;
381
382 if attributes_info.owner != program_id {
383 if *system_program_info.key != Pubkey::system_program() {
384 msg!("System program id does not match expected system program id");
385 return Err(ProgramError::IncorrectProgramId);
386 }
387 if !payer_info.is_signer {
388 msg!("Payer is not a signer");
389 return Err(ProgramError::MissingRequiredSignature);
390 }
391 let lamports = minimum_rent(TokenMetadataAttributes::LEN);
392
393 invoke_signed(
394 &create_account(
395 payer_info.key,
396 attributes_info.key,
397 lamports,
398 required_space,
399 program_id,
400 ),
401 &[
402 payer_info.clone(),
403 attributes_info.clone(),
404 system_program_info.clone(),
405 ],
406 &[&[
407 ATTRIBUTES_SEED, mint_info.key.as_ref(),
409 &[attrs_bump],
410 ]],
411 )?;
412 } else {
413 let curr_len = attributes_info.data.borrow().len() as u64;
414 if curr_len != required_space {
415 msg!(
416 "Attributes account size mismatch: curr={} required={}",
417 curr_len,
418 required_space
419 );
420 return Err(ProgramError::InvalidAccountData);
421 }
422 }
423
424 {
426 let data_ref = attributes_info.data.borrow();
427 if !data_ref.is_empty() && data_ref[0] != 0 {
428 return Err(MetadataError::MetadataAlreadyExists.into());
429 }
430 }
431
432 let attrs = TokenMetadataAttributes {
433 is_initialized: true,
434 mint: *mint_info.key,
435 data,
436 };
437 attrs.pack_into_slice(&mut attributes_info.data.borrow_mut());
438 Ok(())
439 }
440
441 fn process_replace_attributes(
442 program_id: &Pubkey,
443 accounts: &[AccountInfo],
444 data: Vec<(String, String)>,
445 ) -> ProgramResult {
446 let account_info_iter = &mut accounts.iter();
447 let attributes_info = next_account_info(account_info_iter)?; let update_authority_info = next_account_info(account_info_iter)?; let metadata_info = next_account_info(account_info_iter)?; if !update_authority_info.is_signer {
452 return Err(ProgramError::MissingRequiredSignature);
453 }
454
455 let metadata = TokenMetadata::unpack(&metadata_info.data.borrow())
457 .map_err(|_| ProgramError::InvalidAccountData)?;
458 if !metadata.is_initialized() {
459 return Err(ProgramError::UninitializedAccount);
460 }
461 match metadata.update_authority {
462 Some(current_auth) => {
463 if !cmp_pubkeys(¤t_auth, update_authority_info.key) {
464 return Err(MetadataError::InvalidAuthority.into());
465 }
466 }
467 None => return Err(MetadataError::InvalidAuthority.into()),
468 }
469
470 let (expected_attrs_pda, _bump) =
472 find_attributes_pda_with_program(program_id, &metadata.mint);
473 if !cmp_pubkeys(&expected_attrs_pda, attributes_info.key) {
474 return Err(ProgramError::InvalidSeeds);
475 }
476
477 let mut attrs = TokenMetadataAttributes::unpack(&attributes_info.data.borrow())
479 .map_err(|_| ProgramError::InvalidAccountData)?;
480 if !attrs.is_initialized() {
481 return Err(ProgramError::UninitializedAccount);
482 }
483
484 if data.len() > MAX_ATTRIBUTES {
486 return Err(MetadataError::TooManyAttributes.into());
487 }
488 for (k, v) in &data {
489 if k.is_empty() || v.is_empty() {
490 return Err(MetadataError::InvalidInstructionData.into());
491 }
492 if k.len() > MAX_KEY_LENGTH || v.len() > MAX_VALUE_LENGTH {
493 return Err(MetadataError::StringTooLong.into());
494 }
495 }
496
497 attrs.data = data;
499 attrs.pack_into_slice(&mut attributes_info.data.borrow_mut());
500 Ok(())
501 }
502
503 fn process_transfer_authority(
504 accounts: &[AccountInfo],
505 new_authority: Pubkey,
506 ) -> ProgramResult {
507 let account_info_iter = &mut accounts.iter();
508 let metadata_info = next_account_info(account_info_iter)?; let current_authority_info = next_account_info(account_info_iter)?; if !current_authority_info.is_signer {
512 msg!("Current authority is not a signer");
513 return Err(ProgramError::MissingRequiredSignature);
514 }
515
516 let mut metadata = TokenMetadata::unpack(&metadata_info.data.borrow())
517 .map_err(|_| ProgramError::InvalidAccountData)?;
518 if !metadata.is_initialized() {
519 msg!("Metadata not initialized");
520 return Err(ProgramError::UninitializedAccount);
521 }
522
523 match metadata.update_authority {
524 Some(current_auth) => {
525 if !cmp_pubkeys(¤t_auth, current_authority_info.key) {
526 msg!("Signer is not current update authority");
527 return Err(MetadataError::InvalidAuthority.into());
528 }
529 }
530 None => {
531 msg!("Metadata is immutable; cannot transfer authority");
532 return Err(MetadataError::InvalidAuthority.into());
533 }
534 }
535
536 metadata.update_authority = Some(new_authority);
537 metadata.pack_into_slice(&mut metadata_info.data.borrow_mut());
538 Ok(())
539 }
540
541 fn process_make_immutable(accounts: &[AccountInfo]) -> ProgramResult {
542 let account_info_iter = &mut accounts.iter();
543 let metadata_info = next_account_info(account_info_iter)?; let current_authority_info = next_account_info(account_info_iter)?; if !current_authority_info.is_signer {
547 msg!("Current authority is not a signer");
548 return Err(ProgramError::MissingRequiredSignature);
549 }
550
551 let mut metadata = TokenMetadata::unpack(&metadata_info.data.borrow())
552 .map_err(|_| ProgramError::InvalidAccountData)?;
553 if !metadata.is_initialized() {
554 msg!("Metadata not initialized");
555 return Err(ProgramError::UninitializedAccount);
556 }
557
558 match metadata.update_authority {
559 Some(current_auth) => {
560 if !cmp_pubkeys(¤t_auth, current_authority_info.key) {
561 msg!("Signer is not current update authority");
562 return Err(MetadataError::InvalidAuthority.into());
563 }
564 }
565 None => {
566 msg!("Metadata already immutable");
567 return Err(MetadataError::InvalidAuthority.into());
568 }
569 }
570
571 metadata.update_authority = None;
572 metadata.pack_into_slice(&mut metadata_info.data.borrow_mut());
573 Ok(())
574 }
575}
576
577fn cmp_pubkeys(a: &Pubkey, b: &Pubkey) -> bool {
579 arch_program::program_memory::sol_memcmp(a.as_ref(), b.as_ref(), 32) == 0
580}