33 common:: { parse_pubkey, process_transaction} ,
44 config:: Config ,
55 output:: { format_output, println_display} ,
6- CommandResult ,
6+ CommandResult , Error ,
77 } ,
88 clap:: { ArgMatches , Args } ,
99 mpl_token_metadata:: accounts:: Metadata as MetaplexMetadata ,
1010 serde_derive:: { Deserialize , Serialize } ,
1111 serde_with:: { serde_as, DisplayFromStr } ,
1212 solana_cli_output:: { display:: writeln_name_value, QuietDisplay , VerboseDisplay } ,
13+ solana_client:: nonblocking:: rpc_client:: RpcClient ,
1314 solana_pubkey:: Pubkey ,
1415 solana_remote_wallet:: remote_wallet:: RemoteWalletManager ,
1516 solana_signature:: Signature ,
1617 solana_signer:: Signer ,
1718 solana_transaction:: Transaction ,
19+ spl_token_2022:: {
20+ extension:: {
21+ metadata_pointer:: MetadataPointer , BaseStateWithExtensions , PodStateWithExtensions ,
22+ } ,
23+ pod:: PodMint ,
24+ } ,
1825 spl_token_wrap:: {
1926 get_wrapped_mint_address, get_wrapped_mint_authority,
2027 instruction:: sync_metadata_to_token_2022,
@@ -31,20 +38,18 @@ pub struct SyncMetadataToToken2022Args {
3138 #[ clap( value_parser = parse_pubkey) ]
3239 pub unwrapped_mint : Pubkey ,
3340
34- /// Specify that the source metadata is from a `Metaplex` Token Metadata
35- /// account. The CLI will derive the PDA automatically.
36- #[ clap( long) ]
37- pub metaplex : bool ,
38-
39- /// Optional source metadata account when the unwrapped mint's metadata
40- /// pointer points to an external account or third-party program
41- #[ clap( long, value_parser = parse_pubkey, conflicts_with = "metaplex" , requires = "program-id" ) ]
41+ /// Optional source metadata account. If not provided, it will be derived
42+ /// automatically. For SPL Token mints, this will be the `Metaplex`
43+ /// Metadata PDA. For Token-2022 mints, the metadata pointer extension
44+ /// is checked first, falling back to the `Metaplex` PDA if the pointer is
45+ /// not set.
46+ #[ clap( long, value_parser = parse_pubkey) ]
4247 pub metadata_account : Option < Pubkey > ,
4348
4449 /// Optional owner program for the source metadata account, when owned by a
4550 /// third-party program
46- #[ clap( long, value_parser = parse_pubkey) ]
47- pub program_id : Option < Pubkey > ,
51+ #[ clap( long, value_parser = parse_pubkey, requires = "metadata-account" ) ]
52+ pub metadata_program_id : Option < Pubkey > ,
4853}
4954
5055#[ serde_as]
@@ -64,7 +69,7 @@ pub struct SyncMetadataToToken2022Output {
6469 pub source_metadata : Option < Pubkey > ,
6570
6671 #[ serde_as( as = "Option<DisplayFromStr>" ) ]
67- pub owner_program : Option < Pubkey > ,
72+ pub metadata_program_id : Option < Pubkey > ,
6873
6974 pub signatures : Vec < Signature > ,
7075}
@@ -81,8 +86,8 @@ impl Display for SyncMetadataToToken2022Output {
8186 if let Some ( src) = self . source_metadata {
8287 writeln_name_value ( f, "Source metadata:" , & src. to_string ( ) ) ?;
8388 }
84- if let Some ( owner ) = self . owner_program {
85- writeln_name_value ( f, "Owner program:" , & owner . to_string ( ) ) ?;
89+ if let Some ( id ) = self . metadata_program_id {
90+ writeln_name_value ( f, "Metadata program id :" , & id . to_string ( ) ) ?;
8691 }
8792
8893 writeln ! ( f, "Signers:" ) ?;
@@ -112,11 +117,10 @@ pub async fn command_sync_metadata_to_token2022(
112117 let wrapped_mint = get_wrapped_mint_address ( & args. unwrapped_mint , & spl_token_2022:: id ( ) ) ;
113118 let wrapped_mint_authority = get_wrapped_mint_authority ( & wrapped_mint) ;
114119
115- let source_metadata = if args. metaplex {
116- let ( metaplex_pda, _) = MetaplexMetadata :: find_pda ( & args. unwrapped_mint ) ;
117- Some ( metaplex_pda)
120+ let source_metadata = if let Some ( metadata_account) = args. metadata_account {
121+ Some ( metadata_account)
118122 } else {
119- args. metadata_account
123+ resolve_source_metadata_account ( & config . rpc_client , & args. unwrapped_mint ) . await ?
120124 } ;
121125
122126 println_display (
@@ -133,7 +137,7 @@ pub async fn command_sync_metadata_to_token2022(
133137 & wrapped_mint_authority,
134138 & args. unwrapped_mint ,
135139 source_metadata. as_ref ( ) ,
136- args. program_id . as_ref ( ) ,
140+ args. metadata_program_id . as_ref ( ) ,
137141 ) ;
138142
139143 let blockhash = config. rpc_client . get_latest_blockhash ( ) . await ?;
@@ -148,9 +152,44 @@ pub async fn command_sync_metadata_to_token2022(
148152 wrapped_mint,
149153 wrapped_mint_authority,
150154 source_metadata,
151- owner_program : args. program_id ,
155+ metadata_program_id : args. metadata_program_id ,
152156 signatures : transaction. signatures ,
153157 } ;
154158
155159 Ok ( format_output ( config, output) )
156160}
161+
162+ pub async fn resolve_source_metadata_account (
163+ rpc_client : & RpcClient ,
164+ unwrapped_mint : & Pubkey ,
165+ ) -> Result < Option < Pubkey > , Error > {
166+ let acct = rpc_client. get_account ( unwrapped_mint) . await ?;
167+ let owner = acct. owner ;
168+
169+ let metaplex_pda = Some ( MetaplexMetadata :: find_pda ( unwrapped_mint) . 0 ) ;
170+
171+ if owner == spl_token:: id ( ) {
172+ return Ok ( metaplex_pda) ;
173+ }
174+
175+ if owner == spl_token_2022:: id ( ) {
176+ let mint_state = PodStateWithExtensions :: < PodMint > :: unpack ( & acct. data ) ?;
177+
178+ let resolved = match mint_state. get_extension :: < MetadataPointer > ( ) {
179+ Ok ( pointer) => match Option :: from ( pointer. metadata_address ) {
180+ Some ( addr) if addr == * unwrapped_mint => None ,
181+ Some ( addr) => Some ( addr) ,
182+ None => metaplex_pda, // unset pointer → fallback
183+ } ,
184+ Err ( _) => metaplex_pda, // no extension → fallback
185+ } ;
186+
187+ return Ok ( resolved) ;
188+ }
189+
190+ Err ( format ! (
191+ "Unwrapped mint {} is not an SPL Token or SPL Token-2022 mint" ,
192+ unwrapped_mint
193+ )
194+ . into ( ) )
195+ }
0 commit comments