use {
crate::{error::TokenWrapError, metaplex::metaplex_to_token_2022_metadata},
mpl_token_metadata::{accounts::Metadata as MetaplexMetadata, ID as MPL_TOKEN_METADATA_ID},
solana_account_info::AccountInfo,
solana_cpi::{get_return_data, invoke},
solana_program_error::ProgramError,
spl_token_2022::{
extension::{
metadata_pointer::MetadataPointer, BaseStateWithExtensions, PodStateWithExtensions,
},
id as token_2022_id,
pod::PodMint,
},
spl_token_metadata_interface::{instruction::emit, state::TokenMetadata},
spl_type_length_value::variable_len_pack::VariableLenPack,
};
pub fn cpi_emit_and_decode<'a>(
owner_program_info: &AccountInfo<'a>,
metadata_info: &AccountInfo<'a>,
) -> Result<TokenMetadata, ProgramError> {
invoke(
&emit(owner_program_info.key, metadata_info.key, None, None),
&[metadata_info.clone()],
)?;
if let Some((program_key, data)) = get_return_data() {
if program_key == *owner_program_info.key {
return TokenMetadata::unpack_from_slice(&data);
}
}
Err(TokenWrapError::ExternalProgramReturnedNoData.into())
}
pub fn resolve_token_2022_source_metadata<'a>(
unwrapped_mint_info: &AccountInfo<'a>,
maybe_source_metadata_info: Option<&AccountInfo<'a>>,
maybe_owner_program_info: Option<&AccountInfo<'a>>,
) -> Result<TokenMetadata, ProgramError> {
let data = unwrapped_mint_info.try_borrow_data()?;
let mint_state = PodStateWithExtensions::<PodMint>::unpack(&data)?;
let pointer = mint_state
.get_extension::<MetadataPointer>()
.map_err(|_| TokenWrapError::MetadataPointerMissing)?;
let metadata_addr =
Option::from(pointer.metadata_address).ok_or(TokenWrapError::MetadataPointerUnset)?;
if metadata_addr == *unwrapped_mint_info.key {
return mint_state.get_variable_len_extension::<TokenMetadata>();
}
let metadata_info = maybe_source_metadata_info.ok_or(ProgramError::NotEnoughAccountKeys)?;
if metadata_info.key != &metadata_addr {
return Err(TokenWrapError::MetadataPointerMismatch.into());
}
if metadata_info.owner == &token_2022_id() {
Err(ProgramError::InvalidAccountData)
} else if metadata_info.owner == &MPL_TOKEN_METADATA_ID {
metaplex_to_token_2022_metadata(unwrapped_mint_info, metadata_info)
} else {
let owner_program_info =
maybe_owner_program_info.ok_or(ProgramError::NotEnoughAccountKeys)?;
if owner_program_info.key != metadata_info.owner {
return Err(ProgramError::InvalidAccountOwner);
}
cpi_emit_and_decode(owner_program_info, metadata_info)
}
}
pub fn extract_token_metadata<'a>(
unwrapped_mint_info: &AccountInfo<'a>,
source_metadata_info: Option<&AccountInfo<'a>>,
owner_program_info: Option<&AccountInfo<'a>>,
) -> Result<TokenMetadata, ProgramError> {
let unwrapped_metadata = if *unwrapped_mint_info.owner == spl_token_2022::id() {
resolve_token_2022_source_metadata(
unwrapped_mint_info,
source_metadata_info,
owner_program_info,
)?
} else if *unwrapped_mint_info.owner == spl_token::id() {
let metaplex_metadata_info =
source_metadata_info.ok_or(ProgramError::NotEnoughAccountKeys)?;
let (expected_metaplex_pda, _) = MetaplexMetadata::find_pda(unwrapped_mint_info.key);
if *metaplex_metadata_info.owner != mpl_token_metadata::ID {
return Err(ProgramError::InvalidAccountOwner);
}
if *metaplex_metadata_info.key != expected_metaplex_pda {
return Err(TokenWrapError::MetaplexMetadataMismatch.into());
}
metaplex_to_token_2022_metadata(unwrapped_mint_info, metaplex_metadata_info)?
} else {
return Err(ProgramError::IncorrectProgramId);
};
Ok(unwrapped_metadata)
}