-
Notifications
You must be signed in to change notification settings - Fork 7
Sync metadata from spl-token to token-2022 #229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -114,7 +114,7 @@ pub enum TokenWrapInstruction { | |
/// | ||
/// Supports (unwrapped to wrapped): | ||
/// - Token-2022 to Token-2022 | ||
/// - SPL-token to Token-2022 (still `TODO`) | ||
/// - SPL-token to Token-2022 | ||
/// | ||
/// If the `TokenMetadata` extension on the wrapped mint if not present, it | ||
/// will initialize it. The client is responsible for funding the wrapped | ||
|
@@ -125,9 +125,11 @@ pub enum TokenWrapInstruction { | |
/// Accounts expected by this instruction: | ||
/// | ||
/// 0. `[w]` Wrapped mint | ||
/// 1. `[]` Wrapped mint authority (PDA) | ||
/// 1. `[]` Wrapped mint authority PDA | ||
/// 2. `[]` Unwrapped mint | ||
/// 3. `[]` Token-2022 program | ||
/// 4. `[]` (Optional) `Metaplex` Metadata PDA. Required if the unwrapped | ||
/// mint is an `spl-token` mint. | ||
Comment on lines
+131
to
+132
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shoot, this actually reminds me that As followup work, maybe here in this optional account slot, we can just say "Token Metadata account, if separate" or something like that, and also mention the Metaplex PDA? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh! Totally forgot about that case. That feels like independent conditional branch of work. Cool if it goes in the next PR? |
||
SyncMetadataToToken2022, | ||
} | ||
|
||
|
@@ -310,13 +312,19 @@ pub fn sync_metadata_to_token_2022( | |
wrapped_mint: &Pubkey, | ||
wrapped_mint_authority: &Pubkey, | ||
unwrapped_mint: &Pubkey, | ||
metaplex_metadata: Option<&Pubkey>, | ||
) -> Instruction { | ||
let accounts = vec![ | ||
let mut accounts = vec![ | ||
AccountMeta::new(*wrapped_mint, false), | ||
AccountMeta::new_readonly(*wrapped_mint_authority, false), | ||
AccountMeta::new_readonly(*unwrapped_mint, false), | ||
AccountMeta::new_readonly(spl_token_2022::id(), false), | ||
]; | ||
|
||
if let Some(pubkey) = metaplex_metadata { | ||
accounts.push(AccountMeta::new_readonly(*pubkey, false)); | ||
} | ||
|
||
let data = TokenWrapInstruction::SyncMetadataToToken2022.pack(); | ||
Instruction::new_with_bytes(*program_id, &data, accounts) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
//! `Metaplex` related helpers | ||
|
||
use { | ||
mpl_token_metadata::accounts::Metadata as MetaplexMetadata, solana_account_info::AccountInfo, | ||
solana_program_error::ProgramError, spl_pod::optional_keys::OptionalNonZeroPubkey, | ||
spl_token_metadata_interface::state::TokenMetadata, | ||
}; | ||
|
||
fn extract_additional_metadata( | ||
metaplex_metadata: &MetaplexMetadata, | ||
) -> Result<Vec<(String, String)>, ProgramError> { | ||
Comment on lines
+9
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea behind this is to convert the remaining metaplex metadata fields to token-2022-metadata-ext-compatible. And for that it has to be added to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah this works for me! Nice! |
||
let mut additional_metadata = vec![ | ||
( | ||
"key".to_string(), | ||
serde_json::to_string(&metaplex_metadata.key) | ||
.map_err(|_| ProgramError::InvalidAccountData)?, | ||
), | ||
( | ||
"seller_fee_basis_points".to_string(), | ||
metaplex_metadata.seller_fee_basis_points.to_string(), | ||
), | ||
( | ||
"primary_sale_happened".to_string(), | ||
metaplex_metadata.primary_sale_happened.to_string(), | ||
), | ||
( | ||
"is_mutable".to_string(), | ||
metaplex_metadata.is_mutable.to_string(), | ||
), | ||
]; | ||
|
||
if let Some(creators) = &metaplex_metadata.creators { | ||
if !creators.is_empty() { | ||
additional_metadata.push(( | ||
"creators".to_string(), | ||
serde_json::to_string(creators).map_err(|_| ProgramError::InvalidAccountData)?, | ||
)); | ||
} | ||
} | ||
if let Some(edition_nonce) = metaplex_metadata.edition_nonce { | ||
additional_metadata.push(("edition_nonce".to_string(), edition_nonce.to_string())); | ||
} | ||
if let Some(token_standard) = &metaplex_metadata.token_standard { | ||
additional_metadata.push(( | ||
"token_standard".to_string(), | ||
serde_json::to_string(token_standard).map_err(|_| ProgramError::InvalidAccountData)?, | ||
)); | ||
} | ||
if let Some(collection) = &metaplex_metadata.collection { | ||
additional_metadata.push(( | ||
"collection".to_string(), | ||
serde_json::to_string(collection).map_err(|_| ProgramError::InvalidAccountData)?, | ||
)); | ||
} | ||
if let Some(uses) = &metaplex_metadata.uses { | ||
additional_metadata.push(( | ||
"uses".to_string(), | ||
serde_json::to_string(uses).map_err(|_| ProgramError::InvalidAccountData)?, | ||
)); | ||
} | ||
if let Some(collection_details) = &metaplex_metadata.collection_details { | ||
additional_metadata.push(( | ||
"collection_details".to_string(), | ||
serde_json::to_string(collection_details) | ||
.map_err(|_| ProgramError::InvalidAccountData)?, | ||
)); | ||
} | ||
if let Some(programmable_config) = &metaplex_metadata.programmable_config { | ||
additional_metadata.push(( | ||
"programmable_config".to_string(), | ||
serde_json::to_string(programmable_config) | ||
.map_err(|_| ProgramError::InvalidAccountData)?, | ||
)); | ||
} | ||
|
||
Ok(additional_metadata) | ||
} | ||
|
||
/// Converts `Metaplex` metadata to the Token-2022 `TokenMetadata` format. | ||
pub fn metaplex_to_token_2022_metadata( | ||
unwrapped_mint_info: &AccountInfo, | ||
metaplex_metadata_info: &AccountInfo, | ||
) -> Result<TokenMetadata, ProgramError> { | ||
let metaplex_data = metaplex_metadata_info.try_borrow_data()?; | ||
let metaplex_metadata = MetaplexMetadata::safe_deserialize(&metaplex_data) | ||
.map_err(|_| ProgramError::InvalidAccountData)?; | ||
|
||
let additional_metadata = extract_additional_metadata(&metaplex_metadata)?; | ||
|
||
Ok(TokenMetadata { | ||
update_authority: OptionalNonZeroPubkey(metaplex_metadata.update_authority), | ||
mint: *unwrapped_mint_info.key, | ||
name: metaplex_metadata.name, | ||
symbol: metaplex_metadata.symbol, | ||
uri: metaplex_metadata.uri, | ||
additional_metadata, | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,11 +8,13 @@ use { | |
get_wrapped_mint_backpointer_address_signer_seeds, | ||
get_wrapped_mint_backpointer_address_with_seed, get_wrapped_mint_signer_seeds, | ||
instruction::TokenWrapInstruction, | ||
metaplex::metaplex_to_token_2022_metadata, | ||
mint_customizer::{ | ||
default_token_2022::DefaultToken2022Customizer, interface::MintCustomizer, | ||
}, | ||
state::Backpointer, | ||
}, | ||
mpl_token_metadata::accounts::Metadata as MetaplexMetadata, | ||
solana_account_info::{next_account_info, AccountInfo}, | ||
solana_cpi::{invoke, invoke_signed}, | ||
solana_msg::msg, | ||
|
@@ -536,11 +538,6 @@ pub fn process_sync_metadata_to_token_2022(accounts: &[AccountInfo]) -> ProgramR | |
return Err(ProgramError::IncorrectProgramId); | ||
} | ||
|
||
// TODO: Temp until spl-token branch is added | ||
if *unwrapped_mint_info.owner != spl_token_2022::id() { | ||
return Err(ProgramError::IncorrectProgramId); | ||
} | ||
|
||
if *wrapped_mint_info.owner != spl_token_2022::id() { | ||
return Err(ProgramError::IncorrectProgramId); | ||
} | ||
|
@@ -556,12 +553,27 @@ pub fn process_sync_metadata_to_token_2022(accounts: &[AccountInfo]) -> ProgramR | |
return Err(TokenWrapError::MintAuthorityMismatch.into()); | ||
} | ||
|
||
// Get metadata from the token-2022 unwrapped mint | ||
let unwrapped_mint_data = unwrapped_mint_info.try_borrow_data()?; | ||
let unwrapped_mint_state = PodStateWithExtensions::<PodMint>::unpack(&unwrapped_mint_data)?; | ||
let unwrapped_metadata = unwrapped_mint_state | ||
.get_variable_len_extension::<TokenMetadata>() | ||
.map_err(|_| TokenWrapError::UnwrappedMintHasNoMetadata)?; | ||
let unwrapped_metadata = if *unwrapped_mint_info.owner == spl_token_2022::id() { | ||
// Source is Token-2022: read from extension | ||
let unwrapped_mint_data = unwrapped_mint_info.try_borrow_data()?; | ||
let unwrapped_mint_state = PodStateWithExtensions::<PodMint>::unpack(&unwrapped_mint_data)?; | ||
unwrapped_mint_state | ||
.get_variable_len_extension::<TokenMetadata>() | ||
.map_err(|_| TokenWrapError::UnwrappedMintHasNoMetadata)? | ||
Comment on lines
+557
to
+562
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is where you'd have a check to see if the address in the |
||
} else if *unwrapped_mint_info.owner == spl_token::id() { | ||
// Source is spl-token: read from Metaplex PDA | ||
let metaplex_metadata_info = next_account_info(account_info_iter)?; | ||
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); | ||
}; | ||
|
||
let authority_bump_seed = [authority_bump]; | ||
let authority_signer_seeds = | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The version of borsh synced to the version used in
mpl-token-metadata
due to compatibility reasons