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 bitcoin::{self as arch_bitcoin, hashes::Hash},
18 entrypoint::ProgramResult,
19 input_to_sign::InputToSign,
20 msg,
21 program::{get_transaction_to_sign, invoke_signed, set_input_to_sign},
22 program_error::ProgramError,
23 program_option::COption,
24 program_pack::{IsInitialized, Pack},
25 pubkey::Pubkey,
26 rent::minimum_rent,
27 system_instruction::create_account,
28 },
29};
30
31pub struct Processor {}
33
34impl Processor {
35 pub fn process(
37 program_id: &Pubkey,
38 accounts: &[AccountInfo],
39 instruction_data: &[u8],
40 ) -> ProgramResult {
41 let instruction = MetadataInstruction::unpack(instruction_data)?;
42
43 match instruction {
44 MetadataInstruction::CreateMetadata {
45 name,
46 symbol,
47 image,
48 description,
49 immutable,
50 } => Self::process_create_metadata(
51 program_id,
52 accounts,
53 name,
54 symbol,
55 image,
56 description,
57 immutable,
58 ),
59 MetadataInstruction::UpdateMetadata {
60 name,
61 symbol,
62 image,
63 description,
64 } => Self::process_update_metadata(accounts, name, symbol, image, description),
65 MetadataInstruction::CreateAttributes { data } => {
66 Self::process_create_attributes(program_id, accounts, data)
67 }
68 MetadataInstruction::ReplaceAttributes { data } => {
69 Self::process_replace_attributes(program_id, accounts, data)
70 }
71
72 MetadataInstruction::TransferAuthority { new_authority } => {
73 Self::process_transfer_authority(accounts, new_authority)
74 }
75
76 MetadataInstruction::MakeImmutable => Self::process_make_immutable(accounts),
77 MetadataInstruction::Anchor {
78 input_index,
79 input_signer,
80 } => {
81 msg!("Instruction: Anchor");
82 Self::process_anchor(program_id, accounts, input_index, input_signer)
83 }
84 }
85 }
86
87 fn process_create_metadata(
88 program_id: &Pubkey,
89 accounts: &[AccountInfo],
90 name: String,
91 symbol: String,
92 image: String,
93 description: String,
94 immutable: bool,
95 ) -> ProgramResult {
96 let account_info_iter = &mut accounts.iter();
97 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() {
105 msg!(
106 "Mint is not owned by the token program expected {:?}, got {:?}",
107 apl_token::id(),
108 mint_info.owner
109 );
110 return Err(ProgramError::IncorrectProgramId);
111 }
112
113 let mint = Mint::unpack(&mint_info.data.borrow()) .map_err(|_| ProgramError::InvalidAccountData)?;
116
117 if !mint.is_initialized() {
118 msg!("Mint is not initialized");
119 return Err(ProgramError::UninitializedAccount);
120 }
121
122 let matched_signer: Option<Pubkey> = match mint.mint_authority {
124 COption::Some(mint_auth) => {
125 if cmp_pubkeys(&mint_auth, mint_authority_info.key) {
126 Some(mint_auth)
127 } else {
128 msg!("Mint authority does not match expected authority");
129 return Err(MetadataError::InvalidAuthority.into());
130 }
131 }
132 COption::None => match mint.freeze_authority {
133 COption::Some(freeze_auth) => {
134 if cmp_pubkeys(&freeze_auth, mint_authority_info.key) {
135 Some(freeze_auth)
136 } else {
137 msg!("Freeze authority does not match expected authority");
138 return Err(MetadataError::InvalidAuthority.into());
139 }
140 }
141 COption::None => {
142 msg!("Mint has no mint or freeze authority");
143 return Err(MetadataError::InvalidAuthority.into());
144 }
145 },
146 };
147
148 if !mint_authority_info.is_signer {
150 msg!("Mint authority is not a signer");
151 return Err(MetadataError::InvalidAuthority.into());
152 }
153
154 let (expected_md_pda, md_bump) = find_metadata_pda_with_program(program_id, mint_info.key);
156 if !cmp_pubkeys(&expected_md_pda, metadata_info.key) {
157 msg!("Metadata PDA does not match expected PDA");
158 return Err(ProgramError::InvalidSeeds);
159 }
160
161 if name.len() > NAME_MAX_LEN
163 || symbol.len() > SYMBOL_MAX_LEN
164 || image.len() > IMAGE_MAX_LEN
165 || description.len() > DESCRIPTION_MAX_LEN
166 {
167 msg!(
168 "Metadata field size is too long: name={}/{}, symbol={}/{}, image={}/{}, description={}/{}",
169 name.len(),
170 NAME_MAX_LEN,
171 symbol.len(),
172 SYMBOL_MAX_LEN,
173 image.len(),
174 IMAGE_MAX_LEN,
175 description.len(),
176 DESCRIPTION_MAX_LEN,
177 );
178 return Err(MetadataError::StringTooLong.into());
179 }
180
181 if metadata_info.owner != program_id {
183 if *system_program_info.key != Pubkey::system_program() {
185 msg!("System program id does not match expected system program id");
186 return Err(ProgramError::IncorrectProgramId);
187 }
188
189 if !payer_info.is_signer {
190 msg!("Payer is not a signer");
191 return Err(ProgramError::MissingRequiredSignature);
192 }
193
194 let space = TokenMetadata::LEN as u64;
195 let lamports = minimum_rent(TokenMetadata::LEN);
196
197 invoke_signed(
198 &create_account(
199 payer_info.key,
200 metadata_info.key,
201 lamports,
202 space,
203 program_id,
204 ),
205 &[
206 payer_info.clone(),
207 metadata_info.clone(),
208 system_program_info.clone(),
209 ],
210 &[&[
211 METADATA_SEED, mint_info.key.as_ref(),
213 &[md_bump],
214 ]],
215 )?;
216 }
217
218 {
220 let data_ref = metadata_info.data.borrow();
221 if !data_ref.is_empty() && data_ref[0] != 0 {
222 return Err(MetadataError::MetadataAlreadyExists.into());
223 }
224 }
225
226 let metadata = TokenMetadata {
228 is_initialized: true,
229 mint: *mint_info.key,
230 name,
231 symbol,
232 image,
233 description,
234 update_authority: if immutable { None } else { matched_signer },
235 };
236
237 metadata.pack_into_slice(&mut metadata_info.data.borrow_mut());
238
239 Ok(())
240 }
241
242 fn process_update_metadata(
243 accounts: &[AccountInfo],
244 name: Option<String>,
245 symbol: Option<String>,
246 image: Option<String>,
247 description: Option<String>,
248 ) -> ProgramResult {
249 let account_info_iter = &mut accounts.iter();
250 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 {
254 msg!("Update authority is not a signer");
255 return Err(ProgramError::MissingRequiredSignature);
256 }
257
258 let mut metadata = TokenMetadata::unpack(&metadata_info.data.borrow())
260 .map_err(|_| ProgramError::InvalidAccountData)?;
261
262 if !metadata.is_initialized() {
263 msg!("Metadata not initialized");
264 return Err(ProgramError::UninitializedAccount);
265 }
266
267 match metadata.update_authority {
269 Some(current_auth) => {
270 if !cmp_pubkeys(¤t_auth, update_authority_info.key) {
271 msg!("Update authority does not match");
272 return Err(MetadataError::InvalidAuthority.into());
273 }
274 }
275 None => {
276 msg!("Metadata is immutable");
277 return Err(MetadataError::InvalidAuthority.into());
278 }
279 }
280
281 if let Some(ref n) = name {
283 if n.len() > NAME_MAX_LEN {
284 return Err(MetadataError::StringTooLong.into());
285 }
286 }
287 if let Some(ref s) = symbol {
288 if s.len() > SYMBOL_MAX_LEN {
289 return Err(MetadataError::StringTooLong.into());
290 }
291 }
292 if let Some(ref i) = image {
293 if i.len() > IMAGE_MAX_LEN {
294 return Err(MetadataError::StringTooLong.into());
295 }
296 }
297 if let Some(ref d) = description {
298 if d.len() > DESCRIPTION_MAX_LEN {
299 return Err(MetadataError::StringTooLong.into());
300 }
301 }
302
303 if let Some(n) = name {
304 metadata.name = n;
305 }
306 if let Some(s) = symbol {
307 metadata.symbol = s;
308 }
309 if let Some(i) = image {
310 metadata.image = i;
311 }
312 if let Some(d) = description {
313 metadata.description = d;
314 }
315
316 metadata.pack_into_slice(&mut metadata_info.data.borrow_mut());
317 Ok(())
318 }
319
320 fn process_create_attributes(
321 program_id: &Pubkey,
322 accounts: &[AccountInfo],
323 data: Vec<(String, String)>,
324 ) -> ProgramResult {
325 let account_info_iter = &mut accounts.iter();
326 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 {
334 return Err(ProgramError::MissingRequiredSignature);
335 }
336
337 let (expected_attrs_pda, attrs_bump) =
339 find_attributes_pda_with_program(program_id, mint_info.key);
340 if !cmp_pubkeys(&expected_attrs_pda, attributes_info.key) {
341 msg!("Attributes PDA does not match expected PDA");
342 return Err(ProgramError::InvalidSeeds);
343 }
344
345 let metadata = TokenMetadata::unpack(&metadata_info.data.borrow())
347 .map_err(|_| ProgramError::InvalidAccountData)?;
348 if !metadata.is_initialized() || !cmp_pubkeys(&metadata.mint, mint_info.key) {
349 msg!("Metadata not initialized or mint mismatch");
350 return Err(ProgramError::InvalidAccountData);
351 }
352
353 match metadata.update_authority {
354 Some(current_auth) => {
355 if !cmp_pubkeys(¤t_auth, update_authority_info.key) {
356 msg!("Update authority does not match");
357 return Err(MetadataError::InvalidAuthority.into());
358 }
359 }
360 None => {
361 msg!("Metadata is immutable");
362 return Err(MetadataError::InvalidAuthority.into());
363 }
364 }
365
366 if data.len() > MAX_ATTRIBUTES {
368 msg!("Too many attributes: {} > {}", data.len(), MAX_ATTRIBUTES);
369 return Err(MetadataError::TooManyAttributes.into());
370 }
371 for (k, v) in &data {
372 if k.is_empty() || v.is_empty() {
373 msg!("Attribute key and value must be non-empty");
374 return Err(MetadataError::InvalidInstructionData.into());
375 }
376 if k.len() > MAX_KEY_LENGTH || v.len() > MAX_VALUE_LENGTH {
377 msg!(
378 "Attribute key or value is too long: key={}/{}, value={}/{}",
379 k.len(),
380 MAX_KEY_LENGTH,
381 v.len(),
382 MAX_VALUE_LENGTH,
383 );
384 return Err(MetadataError::StringTooLong.into());
385 }
386 }
387
388 let required_space: u64 = TokenMetadataAttributes::LEN as u64;
390
391 if attributes_info.owner != program_id {
392 if *system_program_info.key != Pubkey::system_program() {
393 msg!("System program id does not match expected system program id");
394 return Err(ProgramError::IncorrectProgramId);
395 }
396 if !payer_info.is_signer {
397 msg!("Payer is not a signer");
398 return Err(ProgramError::MissingRequiredSignature);
399 }
400 let lamports = minimum_rent(TokenMetadataAttributes::LEN);
401
402 invoke_signed(
403 &create_account(
404 payer_info.key,
405 attributes_info.key,
406 lamports,
407 required_space,
408 program_id,
409 ),
410 &[
411 payer_info.clone(),
412 attributes_info.clone(),
413 system_program_info.clone(),
414 ],
415 &[&[
416 ATTRIBUTES_SEED, mint_info.key.as_ref(),
418 &[attrs_bump],
419 ]],
420 )?;
421 } else {
422 let curr_len = attributes_info.data.borrow().len() as u64;
423 if curr_len != required_space {
424 msg!(
425 "Attributes account size mismatch: curr={} required={}",
426 curr_len,
427 required_space
428 );
429 return Err(ProgramError::InvalidAccountData);
430 }
431 }
432
433 {
435 let data_ref = attributes_info.data.borrow();
436 if !data_ref.is_empty() && data_ref[0] != 0 {
437 return Err(MetadataError::MetadataAlreadyExists.into());
438 }
439 }
440
441 let attrs = TokenMetadataAttributes {
442 is_initialized: true,
443 mint: *mint_info.key,
444 data,
445 };
446 attrs.pack_into_slice(&mut attributes_info.data.borrow_mut());
447 Ok(())
448 }
449
450 fn process_replace_attributes(
451 program_id: &Pubkey,
452 accounts: &[AccountInfo],
453 data: Vec<(String, String)>,
454 ) -> ProgramResult {
455 let account_info_iter = &mut accounts.iter();
456 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 {
461 return Err(ProgramError::MissingRequiredSignature);
462 }
463
464 let metadata = TokenMetadata::unpack(&metadata_info.data.borrow())
466 .map_err(|_| ProgramError::InvalidAccountData)?;
467 if !metadata.is_initialized() {
468 return Err(ProgramError::UninitializedAccount);
469 }
470 match metadata.update_authority {
471 Some(current_auth) => {
472 if !cmp_pubkeys(¤t_auth, update_authority_info.key) {
473 return Err(MetadataError::InvalidAuthority.into());
474 }
475 }
476 None => return Err(MetadataError::InvalidAuthority.into()),
477 }
478
479 let (expected_attrs_pda, _bump) =
481 find_attributes_pda_with_program(program_id, &metadata.mint);
482 if !cmp_pubkeys(&expected_attrs_pda, attributes_info.key) {
483 return Err(ProgramError::InvalidSeeds);
484 }
485
486 let mut attrs = TokenMetadataAttributes::unpack(&attributes_info.data.borrow())
488 .map_err(|_| ProgramError::InvalidAccountData)?;
489 if !attrs.is_initialized() {
490 return Err(ProgramError::UninitializedAccount);
491 }
492
493 if data.len() > MAX_ATTRIBUTES {
495 return Err(MetadataError::TooManyAttributes.into());
496 }
497 for (k, v) in &data {
498 if k.is_empty() || v.is_empty() {
499 return Err(MetadataError::InvalidInstructionData.into());
500 }
501 if k.len() > MAX_KEY_LENGTH || v.len() > MAX_VALUE_LENGTH {
502 return Err(MetadataError::StringTooLong.into());
503 }
504 }
505
506 attrs.data = data;
508 attrs.pack_into_slice(&mut attributes_info.data.borrow_mut());
509 Ok(())
510 }
511
512 fn process_transfer_authority(
513 accounts: &[AccountInfo],
514 new_authority: Pubkey,
515 ) -> ProgramResult {
516 let account_info_iter = &mut accounts.iter();
517 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 {
521 msg!("Current authority is not a signer");
522 return Err(ProgramError::MissingRequiredSignature);
523 }
524
525 let mut metadata = TokenMetadata::unpack(&metadata_info.data.borrow())
526 .map_err(|_| ProgramError::InvalidAccountData)?;
527 if !metadata.is_initialized() {
528 msg!("Metadata not initialized");
529 return Err(ProgramError::UninitializedAccount);
530 }
531
532 match metadata.update_authority {
533 Some(current_auth) => {
534 if !cmp_pubkeys(¤t_auth, current_authority_info.key) {
535 msg!("Signer is not current update authority");
536 return Err(MetadataError::InvalidAuthority.into());
537 }
538 }
539 None => {
540 msg!("Metadata is immutable; cannot transfer authority");
541 return Err(MetadataError::InvalidAuthority.into());
542 }
543 }
544
545 metadata.update_authority = Some(new_authority);
546 metadata.pack_into_slice(&mut metadata_info.data.borrow_mut());
547 Ok(())
548 }
549
550 fn process_anchor(
553 program_id: &Pubkey,
554 accounts: &[AccountInfo],
555 input_index: u32,
556 input_signer: Pubkey,
557 ) -> ProgramResult {
558 let account_info_iter = &mut accounts.iter();
559 let account_info = next_account_info(account_info_iter)?;
560 let authority_info = next_account_info(account_info_iter)?;
561
562 if !cmp_pubkeys(program_id, account_info.owner) {
563 msg!("Anchor: account not owned by this program");
564 return Err(ProgramError::IncorrectProgramId);
565 }
566
567 let metadata = TokenMetadata::unpack(&account_info.data.borrow())
568 .map_err(|_| ProgramError::InvalidAccountData)?;
569
570 if !metadata.is_initialized() {
571 msg!("Anchor: metadata not initialized");
572 return Err(ProgramError::UninitializedAccount);
573 }
574
575 let expected_authority = metadata
576 .update_authority
577 .ok_or(MetadataError::InvalidAuthority)?;
578
579 if !cmp_pubkeys(&expected_authority, authority_info.key) {
580 msg!("Anchor: authority does not match");
581 return Err(MetadataError::InvalidAuthority.into());
582 }
583
584 if !authority_info.is_signer {
585 msg!("Anchor: authority must be a signer");
586 return Err(ProgramError::MissingRequiredSignature);
587 }
588
589 let buf = get_transaction_to_sign();
590 let arr: [u8; 4] = buf
591 .get(..4)
592 .and_then(|s| s.try_into().ok())
593 .ok_or(ProgramError::InvalidInstructionData)?;
594 let tx_len = u32::from_le_bytes(arr) as usize;
595 let tx_data = buf
596 .get(4..4 + tx_len)
597 .ok_or(ProgramError::InvalidInstructionData)?;
598 let tx: arch_bitcoin::Transaction = arch_bitcoin::consensus::deserialize(tx_data)
599 .map_err(|_| ProgramError::InvalidInstructionData)?;
600
601 let txid = tx.compute_txid();
602 let mut txid_bytes: [u8; 32] = txid.as_raw_hash().to_byte_array();
603 txid_bytes.reverse();
604
605 let input_to_sign = InputToSign {
606 index: input_index,
607 signer: input_signer,
608 };
609
610 set_input_to_sign(accounts, txid_bytes, &[input_to_sign])?;
611
612 Ok(())
613 }
614
615 fn process_make_immutable(accounts: &[AccountInfo]) -> ProgramResult {
616 let account_info_iter = &mut accounts.iter();
617 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 {
621 msg!("Current authority is not a signer");
622 return Err(ProgramError::MissingRequiredSignature);
623 }
624
625 let mut metadata = TokenMetadata::unpack(&metadata_info.data.borrow())
626 .map_err(|_| ProgramError::InvalidAccountData)?;
627 if !metadata.is_initialized() {
628 msg!("Metadata not initialized");
629 return Err(ProgramError::UninitializedAccount);
630 }
631
632 match metadata.update_authority {
633 Some(current_auth) => {
634 if !cmp_pubkeys(¤t_auth, current_authority_info.key) {
635 msg!("Signer is not current update authority");
636 return Err(MetadataError::InvalidAuthority.into());
637 }
638 }
639 None => {
640 msg!("Metadata already immutable");
641 return Err(MetadataError::InvalidAuthority.into());
642 }
643 }
644
645 metadata.update_authority = None;
646 metadata.pack_into_slice(&mut metadata_info.data.borrow_mut());
647 Ok(())
648 }
649}
650
651fn cmp_pubkeys(a: &Pubkey, b: &Pubkey) -> bool {
653 arch_program::program_memory::sol_memcmp(a.as_ref(), b.as_ref(), 32) == 0
654}