Skip to content

Commit b349200

Browse files
authored
Sync to t-22 CLI updates (#305)
* Sync to t-22 CLI updates * change test rpc client to commitment level
1 parent 7811a00 commit b349200

File tree

4 files changed

+555
-109
lines changed

4 files changed

+555
-109
lines changed

clients/cli/src/sync_metadata_to_token2022.rs

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,25 @@ use {
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+
}

clients/cli/tests/helpers.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
#![allow(dead_code)]
22

33
use {
4+
mpl_token_metadata::{
5+
accounts::Metadata as MetaplexMetadata, instructions::CreateV1Builder, types::TokenStandard,
6+
},
47
serde_json::Value,
58
solana_cli_config::Config as SolanaConfig,
69
solana_client::nonblocking::rpc_client::RpcClient,
10+
solana_commitment_config::CommitmentConfig,
711
solana_keypair::{write_keypair_file, Keypair},
812
solana_program_pack::Pack,
913
solana_pubkey::Pubkey,
@@ -55,7 +59,10 @@ pub async fn start_validator() -> (TestValidator, Keypair) {
5559

5660
pub async fn setup_test_env() -> TestEnv {
5761
let (validator, payer) = start_validator().await;
58-
let rpc_client = Arc::new(validator.get_async_rpc_client());
62+
let rpc_client = Arc::new(RpcClient::new_with_commitment(
63+
validator.rpc_url(),
64+
CommitmentConfig::confirmed(),
65+
));
5966

6067
// Write payer keypair to a temporary file
6168
let keypair_file = NamedTempFile::new().unwrap();
@@ -123,6 +130,49 @@ pub async fn create_unwrapped_mint(env: &TestEnv, token_program_addr: &Pubkey) -
123130
mint_account.pubkey()
124131
}
125132

133+
pub async fn create_metaplex_metadata(
134+
env: &TestEnv,
135+
mint: &Pubkey,
136+
token_program: Pubkey,
137+
name: String,
138+
symbol: String,
139+
uri: String,
140+
) -> Pubkey {
141+
let (metaplex_pda, _) = MetaplexMetadata::find_pda(mint);
142+
143+
let mut builder = CreateV1Builder::new();
144+
let create_ix = builder
145+
.metadata(metaplex_pda)
146+
.master_edition(None)
147+
.mint(*mint, false)
148+
.authority(env.payer.pubkey())
149+
.payer(env.payer.pubkey())
150+
.update_authority(env.payer.pubkey(), true)
151+
.spl_token_program(Some(token_program))
152+
.name(name)
153+
.symbol(symbol)
154+
.uri(uri)
155+
.seller_fee_basis_points(0)
156+
.primary_sale_happened(false)
157+
.is_mutable(true)
158+
.token_standard(TokenStandard::Fungible)
159+
.decimals(9)
160+
.instruction();
161+
162+
let meta_tx = Transaction::new_signed_with_payer(
163+
&[create_ix],
164+
Some(&env.payer.pubkey()),
165+
&[&env.payer],
166+
env.rpc_client.get_latest_blockhash().await.unwrap(),
167+
);
168+
env.rpc_client
169+
.send_and_confirm_transaction(&meta_tx)
170+
.await
171+
.unwrap();
172+
173+
metaplex_pda
174+
}
175+
126176
pub async fn execute_create_mint(
127177
env: &TestEnv,
128178
unwrapped_mint: &Pubkey,

clients/cli/tests/test_sync_metadata_to_spl_token.rs

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
use {
22
crate::helpers::{
3-
create_unwrapped_mint, execute_create_mint, setup_test_env, TOKEN_WRAP_CLI_BIN,
4-
},
5-
mpl_token_metadata::{
6-
accounts::Metadata as MetaplexMetadata,
7-
instructions::{CreateMetadataAccountV3, CreateMetadataAccountV3InstructionArgs},
8-
types::DataV2,
9-
utils::clean,
3+
create_metaplex_metadata, create_unwrapped_mint, execute_create_mint, setup_test_env,
4+
TOKEN_WRAP_CLI_BIN,
105
},
6+
mpl_token_metadata::{accounts::Metadata as MetaplexMetadata, utils::clean},
117
serde_json::Value,
128
serial_test::serial,
139
solana_keypair::Keypair,
14-
solana_sdk_ids::system_program,
1510
solana_signer::Signer,
1611
solana_system_interface::instruction::transfer,
1712
solana_transaction::Transaction,
@@ -215,44 +210,18 @@ async fn test_sync_metadata_from_spl_token_to_spl_token() {
215210
let unwrapped_mint = create_unwrapped_mint(&env, &spl_token::id()).await;
216211

217212
// 2. Create Metaplex metadata for the unwrapped mint
218-
let (metaplex_pda, _) = MetaplexMetadata::find_pda(&unwrapped_mint);
219213
let name = "Unwrapped SPL".to_string();
220214
let symbol = "USPL".to_string();
221215
let uri = "http://uspl.com".to_string();
222-
223-
let create_meta_ix = CreateMetadataAccountV3 {
224-
metadata: metaplex_pda,
225-
mint: unwrapped_mint,
226-
mint_authority: env.payer.pubkey(),
227-
payer: env.payer.pubkey(),
228-
update_authority: (env.payer.pubkey(), true),
229-
system_program: system_program::id(),
230-
rent: None,
231-
}
232-
.instruction(CreateMetadataAccountV3InstructionArgs {
233-
data: DataV2 {
234-
name: name.clone(),
235-
symbol: symbol.clone(),
236-
uri: uri.clone(),
237-
seller_fee_basis_points: 0,
238-
creators: None,
239-
collection: None,
240-
uses: None,
241-
},
242-
is_mutable: true,
243-
collection_details: None,
244-
});
245-
246-
let meta_tx = Transaction::new_signed_with_payer(
247-
&[create_meta_ix],
248-
Some(&env.payer.pubkey()),
249-
&[&env.payer],
250-
env.rpc_client.get_latest_blockhash().await.unwrap(),
251-
);
252-
env.rpc_client
253-
.send_and_confirm_transaction(&meta_tx)
254-
.await
255-
.unwrap();
216+
let metaplex_pda = create_metaplex_metadata(
217+
&env,
218+
&unwrapped_mint,
219+
spl_token::id(),
220+
name.clone(),
221+
symbol.clone(),
222+
uri.clone(),
223+
)
224+
.await;
256225

257226
// 3. Create the corresponding wrapped SPL Token mint
258227
execute_create_mint(&env, &unwrapped_mint, &spl_token::id()).await;

0 commit comments

Comments
 (0)