sugar_cli/sign/
process.rs

1use 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    // (1) Setting up connection
42    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    // Send tx with retries.
189    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}