sugar_cli/sign/
process.rs1use std::{str::FromStr, sync::Arc, time::Duration};
2
3pub use anchor_client::{
4 solana_sdk::{
5 account::Account,
6 commitment_config::{CommitmentConfig, CommitmentLevel},
7 native_token::LAMPORTS_PER_MLN,
8 pubkey::Pubkey,
9 signature::{Keypair, Signature, Signer},
10 system_instruction, system_program, sysvar,
11 transaction::Transaction,
12 },
13 Client, Program,
14};
15use anyhow::Error;
16use console::style;
17use miraland_client::rpc_client::RpcClient;
18use mpl_token_metadata::{instruction::sign_metadata, ID as METAPLEX_PROGRAM_ID};
19use retry::{delay::Exponential, retry};
20use tokio::sync::Semaphore;
21
22use crate::{
23 cache::load_cache,
24 candy_machine::CANDY_MACHINE_ID,
25 common::*,
26 config::{Cluster, SugarConfig},
27 pdas::{find_candy_machine_creator_pda, find_metadata_pda},
28 setup::{get_rpc_url, setup_client, sugar_setup},
29 utils::*,
30};
31
32pub struct SignArgs {
33 pub candy_machine_id: Option<String>,
34 pub keypair: Option<String>,
35 pub cache: String,
36 pub rpc_url: Option<String>,
37 pub mint: Option<String>,
38}
39
40pub async fn process_sign(args: SignArgs) -> Result<()> {
41 println!(
43 "{} {}Initializing connection",
44 if args.mint.is_some() {
45 style("[1/2]").bold().dim()
46 } else {
47 style("[1/3]").bold().dim()
48 },
49 COMPUTER_EMOJI
50 );
51
52 let pb = spinner_with_style();
53 pb.set_message("Connecting...");
54
55 let sugar_config = Arc::new(sugar_setup(args.keypair, args.rpc_url.clone())?);
56
57 let client = setup_client(&sugar_config)?;
58 let program = client.program(CANDY_MACHINE_ID);
59
60 pb.finish_with_message("Connected");
61
62 if let Some(mint_id) = args.mint {
63 println!(
64 "\n{} {}Signing one NFT",
65 style("[2/2]").bold().dim(),
66 SIGNING_EMOJI,
67 );
68 let pb = spinner_with_style();
69 pb.set_message(format!("Signing NFT with mint id {}.", mint_id));
70
71 let account_pubkey = Pubkey::from_str(&mint_id)?;
72 let metadata_pubkey = find_metadata_pda(&account_pubkey);
73 match sign(Arc::clone(&sugar_config.clone()), metadata_pubkey).await {
74 Ok(signature) => format!("{} {:?}", style("Signature:").bold(), signature),
75 Err(err) => {
76 pb.abandon_with_message(format!("{}", style("Signing failed ").red().bold()));
77 error!("{:?}", err);
78 return Err(err);
79 }
80 };
81
82 pb.finish();
83 } else {
84 println!(
85 "\n{} {}Fetching mint ids",
86 style("[2/3]").bold().dim(),
87 LOOKING_GLASS_EMOJI,
88 );
89
90 let mut errors = Vec::new();
91
92 let candy_machine_id = match args.candy_machine_id {
93 Some(candy_machine_id) => candy_machine_id,
94 None => {
95 let cache = load_cache(&args.cache, false)?;
96 cache.program.candy_machine
97 }
98 };
99
100 let candy_machine_id = Pubkey::from_str(&candy_machine_id)
101 .expect("Failed to parse pubkey from candy machine id.");
102
103 let miraland_cluster: Cluster = get_cluster(program.rpc())?;
104 let rpc_url = get_rpc_url(args.rpc_url);
105
106 let miraland_cluster = if rpc_url.ends_with("8899") {
107 Cluster::Localnet
108 } else {
109 miraland_cluster
110 };
111
112 let account_keys = match miraland_cluster {
113 Cluster::Devnet | Cluster::Localnet | Cluster::Mainnet => {
114 let client = RpcClient::new_with_timeout(&rpc_url, Duration::from_secs(300));
115 let (creator, _) = find_candy_machine_creator_pda(&candy_machine_id);
116 let creator = bs58::encode(creator).into_string();
117 get_cm_creator_metadata_accounts(&client, &creator, 0)?
118 }
119 _ => {
120 return Err(anyhow!(
121 "Cluster being used is unsupported for this command."
122 ))
123 }
124 };
125
126 if account_keys.is_empty() {
127 pb.finish_with_message(format!("{}", style("No NFTs found.").green().bold()));
128 return Err(anyhow!(format!(
129 "No NFTs found for candy machine id {candy_machine_id}.",
130 )));
131 } else {
132 pb.finish_with_message(format!("Found {:?} accounts", account_keys.len() as u64));
133 println!(
134 "\n{} {}Signing mint accounts",
135 style("[3/3]").bold().dim(),
136 SIGNING_EMOJI
137 );
138 }
139
140 let pb = progress_bar_with_style(account_keys.len() as u64);
141
142 let semaphore = Arc::new(Semaphore::new(100));
143 let mut join_handles = Vec::new();
144 for account in account_keys {
145 let permit = Arc::clone(&semaphore).acquire_owned().await.unwrap();
146 let config = sugar_config.clone();
147 let pb = pb.clone();
148
149 join_handles.push(tokio::spawn(async move {
150 let _permit = permit;
151 sign(Arc::clone(&config), account).await.ok();
152 pb.inc(1);
153 }));
154 }
155
156 for handle in join_handles {
157 handle.await.map_err(|err| errors.push(err)).ok();
158 }
159
160 if !errors.is_empty() {
161 pb.abandon_with_message(format!("{}", style("Signing command failed ").red().bold()));
162 return Err(anyhow!("Not all NFTs were signed.".to_string()));
163 } else {
164 pb.finish_with_message(format!(
165 "{}",
166 style("All NFTs signed successfully.").green().bold()
167 ));
168 }
169 }
170
171 Ok(())
172}
173
174async fn sign(config: Arc<SugarConfig>, metadata: Pubkey) -> Result<(), Error> {
175 let client = setup_client(&config)?;
176 let program = client.program(CANDY_MACHINE_ID);
177
178 let recent_blockhash = program.rpc().get_latest_blockhash()?;
179
180 let ix = sign_metadata(METAPLEX_PROGRAM_ID, metadata, config.keypair.pubkey());
181 let tx = Transaction::new_signed_with_payer(
182 &[ix],
183 Some(&config.keypair.pubkey()),
184 &[&config.keypair],
185 recent_blockhash,
186 );
187
188 retry(
190 Exponential::from_millis_with_factor(250, 2.0).take(3),
191 || program.rpc().send_and_confirm_transaction(&tx),
192 )?;
193
194 Ok(())
195}