nifty_cli/commands/
mint_batch.rs1use std::{sync::Arc, time::Duration};
2
3use crate::transaction::pack_instructions;
4
5use super::*;
6
7use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
8use tokio::sync::Mutex;
9
10pub struct MintBatchArgs {
11 pub keypair_path: Option<PathBuf>,
12 pub rpc_url: Option<String>,
13 pub asset_files_dir: PathBuf,
14 pub delay: u64,
15}
16
17pub struct AssetStruct {
18 pub asset: AssetFile,
19 pub asset_sk: Keypair,
20 pub owner: Pubkey,
21}
22
23pub struct AssetResult {
24 pub asset_pubkey: Pubkey,
25 pub asset_name: String,
26 pub tx_result: TxResult,
27}
28
29pub enum TxResult {
30 Success,
31 Failure(String),
32}
33
34impl TxResult {
35 pub fn is_success(&self) -> bool {
36 matches!(self, TxResult::Success)
37 }
38
39 pub fn is_failure(&self) -> bool {
40 matches!(self, TxResult::Failure(_))
41 }
42
43 pub fn get_error(&self) -> Option<&str> {
44 match self {
45 TxResult::Success => None,
46 TxResult::Failure(err) => Some(err),
47 }
48 }
49}
50
51pub async fn handle_mint_batch(args: MintBatchArgs) -> Result<()> {
52 let config = CliConfig::new(args.keypair_path, args.rpc_url)?;
53
54 let asset_files = std::fs::read_dir(args.asset_files_dir)?
56 .filter_map(|entry| {
57 let entry = entry.ok()?;
58 let path = entry.path();
59 if path.extension()?.to_str()? == "json" {
60 Some(path)
61 } else {
62 None
63 }
64 })
65 .collect::<Vec<PathBuf>>();
66
67 let authority_sk = config.keypair;
68
69 let mut instructions = vec![];
70
71 let asset_keys = Arc::new(Mutex::new(vec![]));
72 let asset_results = Arc::new(Mutex::new(vec![]));
73
74 for asset_file_path in asset_files {
75 let asset_data: AssetFile = serde_json::from_reader(File::open(asset_file_path)?)?;
76
77 let asset_sk = if let Some(path) = &asset_data.asset_keypair_path {
78 read_keypair_file(path).expect("failed to read keypair file")
79 } else {
80 Keypair::new()
81 };
82
83 let asset = asset_sk.pubkey();
84
85 asset_keys.lock().await.push(asset_sk);
86
87 asset_results.lock().await.push(AssetResult {
88 asset_pubkey: asset,
89 asset_name: asset_data.name.clone(),
90 tx_result: TxResult::Success,
91 });
92
93 let owner = asset_data.owner;
94
95 let accounts = MintAccounts {
96 asset,
97 owner,
98 payer: Some(authority_sk.pubkey()),
99 };
100 let asset_args = AssetArgs {
101 name: asset_data.name,
102 standard: Standard::NonFungible,
103 mutable: asset_data.mutable,
104 };
105
106 let extension_args = asset_data
107 .extensions
108 .iter()
109 .map(|extension| ExtensionArgs {
110 extension_type: extension.extension_type.clone(),
111 data: extension.value.clone().into_data(),
112 })
113 .collect::<Vec<ExtensionArgs>>();
114
115 instructions.push(mint(MintIxArgs {
116 accounts,
117 asset_args,
118 extension_args,
119 })?);
120 }
121
122 let mut futures = vec![];
126
127 let client = Arc::new(config.client);
128 let authority_sk = Arc::new(authority_sk);
129
130 let mp: MultiProgress = MultiProgress::new();
131 let sty =
132 ProgressStyle::with_template("[{percent}%] {bar:40.blue/white} {pos:>7}/{len:7} {msg}")
133 .unwrap()
134 .progress_chars("=>-");
135
136 for (i, asset_instructions) in instructions.into_iter().enumerate() {
137 let client = client.clone();
138 let authority_sk = authority_sk.clone();
139 let asset_keys = asset_keys.clone();
140 let asset_results = asset_results.clone();
141
142 let sty_clone = sty.clone();
143 let mp_clone = mp.clone();
144
145 futures.push(tokio::spawn(async move {
148 let packed_transactions =
150 pack_instructions(2, &authority_sk.pubkey(), &asset_instructions);
151
152 let pb = mp_clone.add(ProgressBar::new(packed_transactions.len() as u64));
154 pb.set_style(sty_clone.clone());
155
156 let asset_sk = &asset_keys.lock().await[i];
157 let asset_address = &asset_sk.pubkey();
158 pb.set_message(format!("sending transactions for asset {asset_address}"));
159
160 for transaction in packed_transactions {
161 let res = send_and_confirm_tx(&client, &[&authority_sk, &asset_sk], &transaction);
162 pb.inc(1);
163
164 match res {
165 Ok(_) => continue,
166 Err(err) => {
167 let mut results = asset_results.lock().await;
168 results[i].tx_result = TxResult::Failure(err.to_string());
169 break;
170 }
171 }
172 }
173 pb.finish_with_message(format!("asset {asset_address} minted"));
174 }));
175
176 tokio::time::sleep(Duration::from_millis(args.delay)).await;
178 }
179
180 for future in futures {
181 future.await?;
182 }
183
184 let results = asset_results.lock().await;
185 for result in results.iter() {
186 if result.tx_result.is_failure() {
187 println!(
188 "Failed to mint asset {}: {}",
189 result.asset_name,
190 result.tx_result.get_error().unwrap()
191 );
192 }
193 }
194
195 mp.clear().unwrap();
196
197 Ok(())
198}