Skip to content

Commit dea8065

Browse files
authored
Sync to spl-token CLI helper (#279)
1 parent 0abe9a4 commit dea8065

File tree

7 files changed

+489
-6
lines changed

7 files changed

+489
-6
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clients/cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ edition = { workspace = true }
1010
[dependencies]
1111
anyhow = { workspace = true }
1212
clap = { workspace = true }
13+
mpl-token-metadata = { workspace = true }
1314
serde = { workspace = true }
1415
serde_derive = { workspace = true }
1516
serde_with = { workspace = true }

clients/cli/src/cli.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use {
66
create_mint::{command_create_mint, CreateMintArgs},
77
find_pdas::{command_get_pdas, FindPdasArgs},
88
output::parse_output_format,
9+
sync_metadata_to_spl_token::{
10+
command_sync_metadata_to_spl_token, SyncMetadataToSplTokenArgs,
11+
},
912
unwrap::{command_unwrap, UnwrapArgs},
1013
wrap::{command_wrap, WrapArgs},
1114
CommandResult,
@@ -101,6 +104,9 @@ pub enum Command {
101104
/// Close a stuck escrow account when a mint has closed and re-created with
102105
/// different mint extensions
103106
CloseStuckEscrow(CloseStuckEscrowArgs),
107+
/// Sync metadata from unwrapped mint to wrapped SPL Token mint's `Metaplex`
108+
/// metadata account
109+
SyncMetadataToSplToken(SyncMetadataToSplTokenArgs),
104110
}
105111

106112
impl Command {
@@ -117,6 +123,9 @@ impl Command {
117123
Command::Unwrap(args) => command_unwrap(config, args, matches, wallet_manager).await,
118124
Command::CreateEscrowAccount(args) => command_create_escrow_account(config, args).await,
119125
Command::CloseStuckEscrow(args) => command_close_stuck_escrow(config, args).await,
126+
Command::SyncMetadataToSplToken(args) => {
127+
command_sync_metadata_to_spl_token(config, args, matches, wallet_manager).await
128+
}
120129
}
121130
}
122131
}

clients/cli/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod create_escrow_account;
66
mod create_mint;
77
mod find_pdas;
88
mod output;
9+
mod sync_metadata_to_spl_token;
910
mod unwrap;
1011
mod wrap;
1112

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use {
2+
crate::{
3+
common::{parse_pubkey, process_transaction},
4+
config::Config,
5+
output::{format_output, println_display},
6+
CommandResult,
7+
},
8+
clap::{ArgMatches, Args},
9+
mpl_token_metadata::accounts::Metadata as MetaplexMetadata,
10+
serde_derive::{Deserialize, Serialize},
11+
serde_with::{serde_as, DisplayFromStr},
12+
solana_cli_output::{display::writeln_name_value, QuietDisplay, VerboseDisplay},
13+
solana_pubkey::Pubkey,
14+
solana_remote_wallet::remote_wallet::RemoteWalletManager,
15+
solana_signature::Signature,
16+
solana_signer::Signer,
17+
solana_transaction::Transaction,
18+
spl_token_wrap::{
19+
get_wrapped_mint_address, get_wrapped_mint_authority,
20+
instruction::sync_metadata_to_spl_token,
21+
},
22+
std::{
23+
fmt::{Display, Formatter},
24+
rc::Rc,
25+
},
26+
};
27+
28+
#[derive(Clone, Debug, Args)]
29+
pub struct SyncMetadataToSplTokenArgs {
30+
/// The address of the unwrapped mint whose metadata will be synced from
31+
#[clap(value_parser = parse_pubkey)]
32+
pub unwrapped_mint: Pubkey,
33+
34+
/// Optional source metadata account when the unwrapped mint's metadata
35+
/// pointer points to an external account or third-party program
36+
#[clap(long, value_parser = parse_pubkey)]
37+
pub source_metadata: Option<Pubkey>,
38+
39+
/// Optional owner program for the source metadata account, when owned by a
40+
/// third-party program
41+
#[clap(long, value_parser = parse_pubkey)]
42+
pub owner_program: Option<Pubkey>,
43+
}
44+
45+
#[serde_as]
46+
#[derive(Debug, Serialize, Deserialize)]
47+
#[serde(rename_all = "camelCase")]
48+
pub struct SyncMetadataToSplTokenOutput {
49+
#[serde_as(as = "DisplayFromStr")]
50+
pub unwrapped_mint: Pubkey,
51+
52+
#[serde_as(as = "DisplayFromStr")]
53+
pub wrapped_mint: Pubkey,
54+
55+
#[serde_as(as = "DisplayFromStr")]
56+
pub wrapped_mint_authority: Pubkey,
57+
58+
#[serde_as(as = "DisplayFromStr")]
59+
pub metaplex_metadata: Pubkey,
60+
61+
#[serde_as(as = "Option<DisplayFromStr>")]
62+
pub source_metadata: Option<Pubkey>,
63+
64+
#[serde_as(as = "Option<DisplayFromStr>")]
65+
pub owner_program: Option<Pubkey>,
66+
67+
#[serde_as(as = "Option<DisplayFromStr>")]
68+
pub signature: Option<Signature>,
69+
}
70+
71+
impl Display for SyncMetadataToSplTokenOutput {
72+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
73+
writeln_name_value(f, "Unwrapped mint:", &self.unwrapped_mint.to_string())?;
74+
writeln_name_value(f, "Wrapped mint:", &self.wrapped_mint.to_string())?;
75+
writeln_name_value(
76+
f,
77+
"Wrapped mint authority:",
78+
&self.wrapped_mint_authority.to_string(),
79+
)?;
80+
writeln_name_value(
81+
f,
82+
"Metaplex metadata account:",
83+
&self.metaplex_metadata.to_string(),
84+
)?;
85+
if let Some(src) = self.source_metadata {
86+
writeln_name_value(f, "Source metadata:", &src.to_string())?;
87+
}
88+
if let Some(owner) = self.owner_program {
89+
writeln_name_value(f, "Owner program:", &owner.to_string())?;
90+
}
91+
if let Some(signature) = self.signature {
92+
writeln_name_value(f, "Signature:", &signature.to_string())?;
93+
}
94+
Ok(())
95+
}
96+
}
97+
98+
impl QuietDisplay for SyncMetadataToSplTokenOutput {
99+
fn write_str(&self, _: &mut dyn std::fmt::Write) -> std::fmt::Result {
100+
Ok(())
101+
}
102+
}
103+
impl VerboseDisplay for SyncMetadataToSplTokenOutput {}
104+
105+
pub async fn command_sync_metadata_to_spl_token(
106+
config: &Config,
107+
args: SyncMetadataToSplTokenArgs,
108+
_matches: &ArgMatches,
109+
_wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
110+
) -> CommandResult {
111+
let payer = config.fee_payer()?;
112+
let wrapped_token_program = spl_token::id();
113+
114+
let wrapped_mint = get_wrapped_mint_address(&args.unwrapped_mint, &wrapped_token_program);
115+
let wrapped_mint_authority = get_wrapped_mint_authority(&wrapped_mint);
116+
let (metaplex_metadata, _) = MetaplexMetadata::find_pda(&wrapped_mint);
117+
118+
println_display(
119+
config,
120+
format!(
121+
"Syncing metadata to SPL Token mint {} from {}",
122+
wrapped_mint, args.unwrapped_mint
123+
),
124+
);
125+
126+
let instruction = sync_metadata_to_spl_token(
127+
&spl_token_wrap::id(),
128+
&metaplex_metadata,
129+
&wrapped_mint_authority,
130+
&wrapped_mint,
131+
&args.unwrapped_mint,
132+
args.source_metadata.as_ref(),
133+
args.owner_program.as_ref(),
134+
);
135+
136+
let blockhash = config.rpc_client.get_latest_blockhash().await?;
137+
138+
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
139+
transaction.partial_sign(&[payer.clone()], blockhash);
140+
141+
let signature = process_transaction(config, transaction).await?;
142+
143+
let output = SyncMetadataToSplTokenOutput {
144+
unwrapped_mint: args.unwrapped_mint,
145+
wrapped_mint,
146+
wrapped_mint_authority,
147+
metaplex_metadata,
148+
source_metadata: args.source_metadata,
149+
owner_program: args.owner_program,
150+
signature,
151+
};
152+
153+
Ok(format_output(config, output))
154+
}

clients/cli/tests/helpers.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,20 @@ pub async fn start_validator() -> (TestValidator, Keypair) {
3535
solana_logger::setup();
3636
let mut test_validator_genesis = TestValidatorGenesis::default();
3737

38-
test_validator_genesis.add_upgradeable_programs_with_path(&[UpgradeableProgramInfo {
39-
program_id: spl_token_wrap::id(),
40-
loader: bpf_loader_upgradeable::id(),
41-
program_path: PathBuf::from("../../target/deploy/spl_token_wrap.so"),
42-
upgrade_authority: Pubkey::default(),
43-
}]);
38+
test_validator_genesis.add_upgradeable_programs_with_path(&[
39+
UpgradeableProgramInfo {
40+
program_id: spl_token_wrap::id(),
41+
loader: bpf_loader_upgradeable::id(),
42+
program_path: PathBuf::from("../../target/deploy/spl_token_wrap.so"),
43+
upgrade_authority: Pubkey::default(),
44+
},
45+
UpgradeableProgramInfo {
46+
program_id: mpl_token_metadata::ID,
47+
loader: bpf_loader_upgradeable::id(),
48+
program_path: PathBuf::from("../../program/tests/fixtures/mpl_token_metadata.so"),
49+
upgrade_authority: Pubkey::default(),
50+
},
51+
]);
4452

4553
test_validator_genesis.start_async().await
4654
}

0 commit comments

Comments
 (0)