Skip to content

Conversation

grod220
Copy link
Member

@grod220 grod220 commented Aug 22, 2025

New SyncMetadataToSplToken Instruction: Allows syncing metadata from unwrapped tokens to a wrapped SPL Token's Metaplex account, performing a CPI "upsert" (create or update).

Also refactors and consolidates metadata reading into a universal extract_token_metadata function.

@grod220 grod220 force-pushed the spl-token-metadata-sync branch from 0815e03 to 1643f48 Compare August 22, 2025 18:28
Comment on lines +148 to +152
/// This instruction will create the `Metaplex` metadata account if it
/// doesn't exist, or update it if it does. The `wrapped_mint_authority`
/// PDA must be pre-funded with enough lamports to cover the rent for
/// the `Metaplex` metadata account's creation or updates, as it will
/// act as the payer for the `Metaplex` program CPI.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, the metaplex metadata program requires a payer for the create instruction. Pre-funding the PDA is not an option, so this instruction uses the wrapped_mint_authority PDA as the signer and requires the user to transfer to that account.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yuck, that really stinks. I don't love the solution, but I can't come up with anything better. It's slick since the wrapped mint authority is required to sign create / update metadata

Comment on lines +92 to +122
/// Extracts the token metadata from the unwrapped mint
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() {
// Source is Token-2022: resolve metadata pointer
resolve_token_2022_source_metadata(
unwrapped_mint_info,
source_metadata_info,
owner_program_info,
)?
} else if *unwrapped_mint_info.owner == spl_token::id() {
// Source is spl-token: read from Metaplex PDA
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)
}
Copy link
Member Author

@grod220 grod220 Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used for both sync instructions so abstracting

@@ -0,0 +1,445 @@
use {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the new extract_token_metadata, this felt right extracting out to its own so that redundant tests are not needed for both sync code paths.

This is quite close to the same as the former program/tests/test_pointer_sync.rs (deleted now) but does not use mollusk except for the CPI case for third party programs.

.execute();
}

#[test]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deleted tests here are covered in program/tests/test_extract_token_metadata.rs now

}

pub struct SyncMetadataBuilder<'a> {
pub struct SyncToToken2022Builder<'a> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming to distinguish the two syncing builders

@grod220 grod220 marked this pull request as ready for review August 22, 2025 19:02
Copy link
Contributor

@joncinque joncinque left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Just a nit which can be done in a future PR

Comment on lines +148 to +152
/// This instruction will create the `Metaplex` metadata account if it
/// doesn't exist, or update it if it does. The `wrapped_mint_authority`
/// PDA must be pre-funded with enough lamports to cover the rent for
/// the `Metaplex` metadata account's creation or updates, as it will
/// act as the payer for the `Metaplex` program CPI.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yuck, that really stinks. I don't love the solution, but I can't come up with anything better. It's slick since the wrapped mint authority is required to sign create / update metadata

@grod220 grod220 force-pushed the spl-token-metadata-sync branch from 1643f48 to acb98e7 Compare August 27, 2025 05:38
@grod220 grod220 merged commit 278ed6e into main Aug 27, 2025
10 checks passed
@grod220 grod220 deleted the spl-token-metadata-sync branch August 27, 2025 06:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants