spl_token_wrap/
metadata.rs1use {
4 crate::{error::TokenWrapError, metaplex::metaplex_to_token_2022_metadata},
5 mpl_token_metadata::{accounts::Metadata as MetaplexMetadata, ID as MPL_TOKEN_METADATA_ID},
6 solana_account_info::AccountInfo,
7 solana_cpi::{get_return_data, invoke},
8 solana_program_error::ProgramError,
9 spl_token_2022::{
10 extension::{
11 metadata_pointer::MetadataPointer, BaseStateWithExtensions, PodStateWithExtensions,
12 },
13 id as token_2022_id,
14 pod::PodMint,
15 },
16 spl_token_metadata_interface::{instruction::emit, state::TokenMetadata},
17 spl_type_length_value::variable_len_pack::VariableLenPack,
18};
19
20pub fn cpi_emit_and_decode<'a>(
24 owner_program_info: &AccountInfo<'a>,
25 metadata_info: &AccountInfo<'a>,
26) -> Result<TokenMetadata, ProgramError> {
27 invoke(
28 &emit(owner_program_info.key, metadata_info.key, None, None),
29 &[metadata_info.clone()],
30 )?;
31
32 if let Some((program_key, data)) = get_return_data() {
33 if program_key == *owner_program_info.key {
35 return TokenMetadata::unpack_from_slice(&data);
36 }
37 }
38
39 Err(TokenWrapError::ExternalProgramReturnedNoData.into())
40}
41
42pub fn resolve_token_2022_source_metadata<'a>(
51 unwrapped_mint_info: &AccountInfo<'a>,
52 maybe_source_metadata_info: Option<&AccountInfo<'a>>,
53 maybe_owner_program_info: Option<&AccountInfo<'a>>,
54) -> Result<TokenMetadata, ProgramError> {
55 let data = unwrapped_mint_info.try_borrow_data()?;
56 let mint_state = PodStateWithExtensions::<PodMint>::unpack(&data)?;
57 let pointer = mint_state
58 .get_extension::<MetadataPointer>()
59 .map_err(|_| TokenWrapError::MetadataPointerMissing)?;
60 let metadata_addr =
61 Option::from(pointer.metadata_address).ok_or(TokenWrapError::MetadataPointerUnset)?;
62
63 if metadata_addr == *unwrapped_mint_info.key {
65 return mint_state.get_variable_len_extension::<TokenMetadata>();
66 }
67
68 let metadata_info = maybe_source_metadata_info.ok_or(ProgramError::NotEnoughAccountKeys)?;
70 if metadata_info.key != &metadata_addr {
71 return Err(TokenWrapError::MetadataPointerMismatch.into());
72 }
73
74 if metadata_info.owner == &token_2022_id() {
75 Err(ProgramError::InvalidAccountData)
78 } else if metadata_info.owner == &MPL_TOKEN_METADATA_ID {
79 metaplex_to_token_2022_metadata(unwrapped_mint_info, metadata_info)
81 } else {
82 let owner_program_info =
84 maybe_owner_program_info.ok_or(ProgramError::NotEnoughAccountKeys)?;
85 if owner_program_info.key != metadata_info.owner {
86 return Err(ProgramError::InvalidAccountOwner);
87 }
88 cpi_emit_and_decode(owner_program_info, metadata_info)
89 }
90}
91
92pub fn extract_token_metadata<'a>(
94 unwrapped_mint_info: &AccountInfo<'a>,
95 source_metadata_info: Option<&AccountInfo<'a>>,
96 owner_program_info: Option<&AccountInfo<'a>>,
97) -> Result<TokenMetadata, ProgramError> {
98 let unwrapped_metadata = if *unwrapped_mint_info.owner == spl_token_2022::id() {
99 resolve_token_2022_source_metadata(
101 unwrapped_mint_info,
102 source_metadata_info,
103 owner_program_info,
104 )?
105 } else if *unwrapped_mint_info.owner == spl_token::id() {
106 let metaplex_metadata_info =
108 source_metadata_info.ok_or(ProgramError::NotEnoughAccountKeys)?;
109 let (expected_metaplex_pda, _) = MetaplexMetadata::find_pda(unwrapped_mint_info.key);
110 if *metaplex_metadata_info.owner != mpl_token_metadata::ID {
111 return Err(ProgramError::InvalidAccountOwner);
112 }
113 if *metaplex_metadata_info.key != expected_metaplex_pda {
114 return Err(TokenWrapError::MetaplexMetadataMismatch.into());
115 }
116 metaplex_to_token_2022_metadata(unwrapped_mint_info, metaplex_metadata_info)?
117 } else {
118 return Err(ProgramError::IncorrectProgramId);
119 };
120
121 Ok(unwrapped_metadata)
122}