3
3
common:: { parse_pubkey, process_transaction} ,
4
4
config:: Config ,
5
5
output:: { format_output, println_display} ,
6
- CommandResult ,
6
+ CommandResult , Error ,
7
7
} ,
8
8
clap:: { ArgMatches , Args } ,
9
9
mpl_token_metadata:: accounts:: Metadata as MetaplexMetadata ,
10
10
serde_derive:: { Deserialize , Serialize } ,
11
11
serde_with:: { serde_as, DisplayFromStr } ,
12
12
solana_cli_output:: { display:: writeln_name_value, QuietDisplay , VerboseDisplay } ,
13
+ solana_client:: nonblocking:: rpc_client:: RpcClient ,
13
14
solana_pubkey:: Pubkey ,
14
15
solana_remote_wallet:: remote_wallet:: RemoteWalletManager ,
15
16
solana_signature:: Signature ,
16
17
solana_signer:: Signer ,
17
18
solana_transaction:: Transaction ,
19
+ spl_token_2022:: {
20
+ extension:: {
21
+ metadata_pointer:: MetadataPointer , BaseStateWithExtensions , PodStateWithExtensions ,
22
+ } ,
23
+ pod:: PodMint ,
24
+ } ,
18
25
spl_token_wrap:: {
19
26
get_wrapped_mint_address, get_wrapped_mint_authority,
20
27
instruction:: sync_metadata_to_token_2022,
@@ -31,20 +38,18 @@ pub struct SyncMetadataToToken2022Args {
31
38
#[ clap( value_parser = parse_pubkey) ]
32
39
pub unwrapped_mint : Pubkey ,
33
40
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) ]
42
47
pub metadata_account : Option < Pubkey > ,
43
48
44
49
/// Optional owner program for the source metadata account, when owned by a
45
50
/// 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 > ,
48
53
}
49
54
50
55
#[ serde_as]
@@ -64,7 +69,7 @@ pub struct SyncMetadataToToken2022Output {
64
69
pub source_metadata : Option < Pubkey > ,
65
70
66
71
#[ serde_as( as = "Option<DisplayFromStr>" ) ]
67
- pub owner_program : Option < Pubkey > ,
72
+ pub metadata_program_id : Option < Pubkey > ,
68
73
69
74
pub signatures : Vec < Signature > ,
70
75
}
@@ -81,8 +86,8 @@ impl Display for SyncMetadataToToken2022Output {
81
86
if let Some ( src) = self . source_metadata {
82
87
writeln_name_value ( f, "Source metadata:" , & src. to_string ( ) ) ?;
83
88
}
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 ( ) ) ?;
86
91
}
87
92
88
93
writeln ! ( f, "Signers:" ) ?;
@@ -112,11 +117,10 @@ pub async fn command_sync_metadata_to_token2022(
112
117
let wrapped_mint = get_wrapped_mint_address ( & args. unwrapped_mint , & spl_token_2022:: id ( ) ) ;
113
118
let wrapped_mint_authority = get_wrapped_mint_authority ( & wrapped_mint) ;
114
119
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)
118
122
} else {
119
- args. metadata_account
123
+ resolve_source_metadata_account ( & config . rpc_client , & args. unwrapped_mint ) . await ?
120
124
} ;
121
125
122
126
println_display (
@@ -133,7 +137,7 @@ pub async fn command_sync_metadata_to_token2022(
133
137
& wrapped_mint_authority,
134
138
& args. unwrapped_mint ,
135
139
source_metadata. as_ref ( ) ,
136
- args. program_id . as_ref ( ) ,
140
+ args. metadata_program_id . as_ref ( ) ,
137
141
) ;
138
142
139
143
let blockhash = config. rpc_client . get_latest_blockhash ( ) . await ?;
@@ -148,9 +152,44 @@ pub async fn command_sync_metadata_to_token2022(
148
152
wrapped_mint,
149
153
wrapped_mint_authority,
150
154
source_metadata,
151
- owner_program : args. program_id ,
155
+ metadata_program_id : args. metadata_program_id ,
152
156
signatures : transaction. signatures ,
153
157
} ;
154
158
155
159
Ok ( format_output ( config, output) )
156
160
}
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