use super::*;
use crate::cli::query::{LeoBlock, LeoProgram};
use leo_ast::NetworkName;
use leo_package::{ProgramData, create_http_agent};
use leo_span::Symbol;
use snarkvm::prelude::{Program, ProgramID};
use indexmap::IndexSet;
use std::collections::HashMap;
pub fn get_public_balance<N: Network>(
private_key: &PrivateKey<N>,
endpoint: &str,
network: NetworkName,
context: &Context,
) -> Result<u64> {
let address = Address::<N>::try_from(ViewKey::try_from(private_key)?)?;
let mut public_balance = LeoQuery {
env_override: EnvOptions { endpoint: Some(endpoint.to_string()), network: Some(network), ..Default::default() },
command: QueryCommands::Program {
command: LeoProgram {
name: "credits".to_string(),
edition: None,
mappings: false,
mapping_value: Some(vec!["account".to_string(), address.to_string()]),
},
},
}
.execute(Context::new(context.path.clone(), context.home.clone(), true, None)?)?;
public_balance.truncate(public_balance.len() - 3);
public_balance.parse::<u64>().map_err(|_| crate::errors::invalid_balance(address).into())
}
#[allow(dead_code)]
pub fn get_latest_block_height(
endpoint: &str,
network: NetworkName,
context: &Context,
network_retries: u32,
) -> Result<u32> {
let height = LeoQuery {
env_override: EnvOptions { endpoint: Some(endpoint.to_string()), network: Some(network), network_retries },
command: QueryCommands::Block {
command: LeoBlock {
id: None,
latest: false,
latest_hash: false,
latest_height: true,
range: None,
transactions: false,
to_height: false,
},
},
}
.execute(Context::new(context.path.clone(), context.home.clone(), true, None)?)?;
let height = height.parse::<u32>().map_err(crate::errors::string_parse_error)?;
Ok(height)
}
pub fn handle_broadcast<N: Network>(
endpoint: &str,
transaction: &Transaction<N>,
operation: &str,
) -> Result<(String, u16)> {
let mut response = create_http_agent()
.post(endpoint)
.query("check_transaction", "true")
.header("X-Leo-Version", env!("CARGO_PKG_VERSION"))
.send_json(transaction)
.map_err(|err| crate::errors::broadcast_error(err.to_string()))?;
match response.status().as_u16() {
200..=299 => {
println!(
"✉️ Broadcasted transaction with:\n - transaction ID: '{}'",
transaction.id().to_string().bold().yellow(),
);
if let Some(fee) = transaction.fee_transition() {
println!(" - fee ID: '{}'", fee.id().to_string().bold().yellow());
println!(" - fee transaction ID: '{}'", Transaction::from_fee(fee)?.id().to_string().bold().yellow());
println!(" (use this to check for rejected transactions)\n");
}
Ok((response.body_mut().read_to_string().unwrap(), response.status().as_u16()))
}
301 => {
let msg = format!(
"⚠️ The endpoint `{endpoint}` has been permanently moved. Try using `https://api.explorer.provable.com/v1` in your `.env` file or via the `--endpoint` flag."
);
Err(crate::errors::broadcast_error(msg).into())
}
_ => {
let code = response.status();
let response_body = response.body_mut().read_to_string().unwrap_or_default();
let action = match transaction {
Transaction::Deploy(..) => format!("deploy '{}'", operation.bold()),
Transaction::Execute(..) => format!("broadcast execution '{}'", operation.bold()),
Transaction::Fee(..) => format!("broadcast fee '{}'", operation.bold()),
};
let msg = format!(
" Failed to {action}\n Endpoint: {endpoint}\n Status: {code}\n Response: {response_body}"
);
Err(crate::errors::broadcast_error(msg).into())
}
}
}
pub fn load_latest_programs_from_network<N: Network>(
context: &Context,
program_id: ProgramID<N>,
network: NetworkName,
endpoint: &str,
network_retries: u32,
) -> Result<Vec<(Program<N>, Option<u16>)>> {
use snarkvm::prelude::Program;
use std::collections::HashSet;
let mut programs = HashMap::new();
let mut ordered_programs = IndexSet::new();
let mut stack = vec![(program_id, false)];
while let Some((current_id, seen)) = stack.pop() {
if seen {
ordered_programs.insert(current_id);
}
else {
if programs.contains_key(¤t_id) {
continue;
}
let program = leo_package::CompilationUnit::fetch(
Symbol::intern(¤t_id.name().to_string()),
None,
&context.home()?,
network,
endpoint,
true,
network_retries,
)
.map_err(|_| crate::errors::custom(format!("Failed to fetch program source for ID: {current_id}")))?;
let ProgramData::Bytecode(program_src) = program.data else {
panic!("Expected bytecode when fetching a remote program");
};
let bytecode = Program::<N>::from_str(&program_src)
.map_err(|_| crate::errors::custom(format!("Failed to parse program source for ID: {current_id}")))?;
let imports = bytecode.imports().keys().cloned().collect::<HashSet<_>>();
programs.insert(current_id, (bytecode, program.edition));
stack.push((current_id, true));
for import_id in imports {
stack.push((import_id, false));
}
}
}
Ok(ordered_programs
.iter()
.map(|program_id| programs.remove(program_id).expect("Program not found in cache"))
.collect())
}