use std::{
cmp,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use anchor_client::solana_sdk::{pubkey::Pubkey, signature::Keypair};
use anyhow::Result;
use console::style;
use futures::future::select_all;
use mpl_candy_machine_core::{
accounts as nft_accounts, instruction as nft_instruction, CandyMachineData, ConfigLine,
};
pub use mpl_token_metadata::state::{
MAX_CREATOR_LIMIT, MAX_NAME_LENGTH, MAX_SYMBOL_LENGTH, MAX_URI_LENGTH,
};
use crate::{
cache::*, candy_machine::CANDY_MACHINE_ID, common::*, config::data::*, deploy::errors::*,
setup::setup_client, utils::*,
};
const MAX_TRANSACTION_BYTES: usize = 1000;
const MAX_TRANSACTION_LINES: usize = 17;
pub struct TxInfo {
candy_pubkey: Pubkey,
payer: Keypair,
chunk: Vec<(u32, ConfigLine)>,
}
pub fn generate_config_lines(
num_items: u64,
cache_items: &CacheItems,
data: &CandyMachineData,
) -> Result<Vec<Vec<(u32, ConfigLine)>>> {
let mut config_lines: Vec<Vec<(u32, ConfigLine)>> = Vec::new();
let mut current: Vec<(u32, ConfigLine)> = Vec::new();
let mut tx_size = 0;
let config_line_settings = if let Some(config_line_settings) = &data.config_line_settings {
config_line_settings
} else {
return Err(anyhow!("Missing config line settings"));
};
let name_offset = config_line_settings.prefix_name.len();
let uri_offset = config_line_settings.prefix_uri.len();
for i in 0..num_items {
let item = match cache_items.get(&i.to_string()) {
Some(item) => item,
None => {
return Err(
DeployError::AddConfigLineFailed(format!("Missing cache item {}", i)).into(),
);
}
};
if item.on_chain {
if !current.is_empty() {
config_lines.push(current);
current = Vec::new();
tx_size = 0;
}
} else {
let config_line = ConfigLine {
name: item.name[name_offset..].to_string(),
uri: item.metadata_link[uri_offset..].to_string(),
};
let size = (2 * STRING_LEN_SIZE) + data.get_config_line_size();
if (tx_size + size) > MAX_TRANSACTION_BYTES || current.len() == MAX_TRANSACTION_LINES {
config_lines.push(current);
current = Vec::new();
tx_size = 0;
}
tx_size += size;
current.push((i as u32, config_line));
}
}
if !current.is_empty() {
config_lines.push(current);
}
Ok(config_lines)
}
pub async fn upload_config_lines(
sugar_config: Arc<SugarConfig>,
candy_pubkey: Pubkey,
cache: &mut Cache,
config_lines: Vec<Vec<(u32, ConfigLine)>>,
interrupted: Arc<AtomicBool>,
) -> Result<Vec<DeployError>> {
println!(
"Sending config line(s) in {} transaction(s): (Ctrl+C to abort)",
config_lines.len()
);
let pb = progress_bar_with_style(config_lines.len() as u64);
debug!("Num of config line chunks: {:?}", config_lines.len());
info!("Uploading config lines in chunks...");
let mut transactions = Vec::new();
for chunk in config_lines {
let keypair = bs58::encode(sugar_config.keypair.to_bytes()).into_string();
let payer = Keypair::from_base58_string(&keypair);
transactions.push(TxInfo {
candy_pubkey,
payer,
chunk,
});
}
let mut handles = Vec::new();
for tx in transactions.drain(0..cmp::min(transactions.len(), PARALLEL_LIMIT)) {
let config = sugar_config.clone();
handles.push(tokio::spawn(
async move { add_config_lines(config, tx).await },
));
}
let mut errors = Vec::new();
while !interrupted.load(Ordering::SeqCst) && !handles.is_empty() {
match select_all(handles).await {
(Ok(res), _index, remaining) => {
handles = remaining;
if res.is_ok() {
let indices = res?;
for index in indices {
let item = cache.items.get_mut(&index.to_string()).unwrap();
item.on_chain = true;
}
pb.inc(1);
} else {
errors.push(DeployError::AddConfigLineFailed(format!(
"Transaction error: {:?}",
res.err().unwrap()
)));
}
}
(Err(err), _index, remaining) => {
errors.push(DeployError::AddConfigLineFailed(format!(
"Transaction error: {:?}",
err
)));
handles = remaining;
}
}
if !transactions.is_empty() {
if (PARALLEL_LIMIT - handles.len()) > (PARALLEL_LIMIT / 2) {
cache.sync_file()?;
for tx in transactions.drain(0..cmp::min(transactions.len(), PARALLEL_LIMIT / 2)) {
let config = sugar_config.clone();
handles.push(tokio::spawn(
async move { add_config_lines(config, tx).await },
));
}
}
}
}
if !errors.is_empty() {
pb.abandon_with_message(format!("{}", style("Deploy failed ").red().bold()));
} else if !transactions.is_empty() {
pb.abandon_with_message(format!("{}", style("Upload aborted ").red().bold()));
return Err(DeployError::AddConfigLineFailed(
"Not all config lines were deployed.".to_string(),
)
.into());
} else {
pb.finish_with_message(format!(
"{}",
style("Write config lines successful ").green().bold()
));
}
cache.sync_file()?;
Ok(errors)
}
pub async fn add_config_lines(config: Arc<SugarConfig>, tx_info: TxInfo) -> Result<Vec<u32>> {
let client = setup_client(&config)?;
let program = client.program(CANDY_MACHINE_ID);
let mut indices: Vec<u32> = Vec::new();
let mut config_lines: Vec<ConfigLine> = Vec::new();
let start_index = tx_info.chunk[0].0;
for (index, line) in tx_info.chunk {
indices.push(index);
config_lines.push(line);
}
let _sig = program
.request()
.accounts(nft_accounts::AddConfigLines {
candy_machine: tx_info.candy_pubkey,
authority: program.payer(),
})
.args(nft_instruction::AddConfigLines {
index: start_index,
config_lines,
})
.signer(&tx_info.payer)
.send()?;
Ok(indices)
}