leo_lang/cli/commands/common/
query.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use crate::cli::query::{LeoBlock, LeoProgram};
18use indexmap::IndexMap;
19use snarkvm::prelude::{Program, ProgramID};
20
21use super::*;
22
23use leo_package::{NetworkName, ProgramData};
24use leo_span::Symbol;
25use ureq::Response;
26
27/// A helper function to query the public balance of an address.
28pub fn get_public_balance<N: Network>(
29    private_key: &PrivateKey<N>,
30    endpoint: &str,
31    network: &str,
32    context: &Context,
33) -> Result<u64> {
34    // Derive the account address.
35    let address = Address::<N>::try_from(ViewKey::try_from(private_key)?)?;
36    // Query the public balance of the address on the `account` mapping from `credits.aleo`.
37    let mut public_balance = LeoQuery {
38        endpoint: Some(endpoint.to_string()),
39        network: Some(network.to_string()),
40        command: QueryCommands::Program {
41            command: LeoProgram {
42                name: "credits".to_string(),
43                mappings: false,
44                mapping_value: Some(vec!["account".to_string(), address.to_string()]),
45            },
46        },
47    }
48    .execute(Context::new(context.path.clone(), context.home.clone(), true)?)?;
49    // Remove the last 3 characters since they represent the `u64` suffix.
50    public_balance.truncate(public_balance.len() - 3);
51    // Make sure the balance is valid.
52    public_balance.parse::<u64>().map_err(|_| CliError::invalid_balance(address).into())
53}
54
55// A helper function to query for the latest block height.
56#[allow(dead_code)]
57pub fn get_latest_block_height(endpoint: &str, network: NetworkName, context: &Context) -> Result<u32> {
58    // Query the latest block height.
59    let height = LeoQuery {
60        endpoint: Some(endpoint.to_string()),
61        network: Some(network.to_string()),
62        command: QueryCommands::Block {
63            command: LeoBlock {
64                id: None,
65                latest: false,
66                latest_hash: false,
67                latest_height: true,
68                range: None,
69                transactions: false,
70                to_height: false,
71            },
72        },
73    }
74    .execute(Context::new(context.path.clone(), context.home.clone(), true)?)?;
75    // Parse the height.
76    let height = height.parse::<u32>().map_err(CliError::string_parse_error)?;
77    Ok(height)
78}
79
80/// Determine if the transaction should be broadcast or displayed to user.
81pub fn handle_broadcast<N: Network>(endpoint: &str, transaction: &Transaction<N>, operation: &str) -> Result<Response> {
82    // Send the deployment request to the endpoint.
83    let response = ureq::AgentBuilder::new()
84        .redirects(0)
85        .build()
86        .post(endpoint)
87        .set("X-Leo-Version", env!("CARGO_PKG_VERSION"))
88        .send_json(transaction)
89        .map_err(|err| CliError::broadcast_error(err.to_string()))?;
90    match response.status() {
91        200..=299 => {
92            println!(
93                "✉️ Broadcasted transaction with:\n  - transaction ID: '{}'\n  - fee ID: '{}'",
94                transaction.id().to_string().bold().yellow(),
95                transaction.fee_transition().expect("Expected a fee in transactions").id().to_string().bold().yellow()
96            );
97            Ok(response)
98        }
99        301 => {
100            let msg = format!(
101                "⚠️ The endpoint `{endpoint}` has been permanently moved. Try using `https://api.explorer.provable.com/v1` in your `.env` file or via the `--endpoint` flag."
102            );
103            Err(CliError::broadcast_error(msg).into())
104        }
105        _ => {
106            let code = response.status();
107            let error_message = match response.into_string() {
108                Ok(response) => format!("(status code {code}: {:?})", response),
109                Err(err) => format!("({err})"),
110            };
111
112            let msg = match transaction {
113                Transaction::Deploy(..) => {
114                    format!("❌ Failed to deploy '{}' to {}: {}", operation.bold(), &endpoint, error_message)
115                }
116                Transaction::Execute(..) => {
117                    format!(
118                        "❌ Failed to broadcast execution '{}' to {}: {}",
119                        operation.bold(),
120                        &endpoint,
121                        error_message
122                    )
123                }
124                Transaction::Fee(..) => {
125                    format!("❌ Failed to broadcast fee '{}' to {}: {}", operation.bold(), &endpoint, error_message)
126                }
127            };
128
129            Err(CliError::broadcast_error(msg).into())
130        }
131    }
132}
133
134/// Loads a program and all its imports from the network, using an iterative DFS.
135pub fn load_programs_from_network<N: Network>(
136    context: &Context,
137    program_id: ProgramID<N>,
138    network: NetworkName,
139    endpoint: &str,
140) -> Result<Vec<(ProgramID<N>, Program<N>)>> {
141    use snarkvm::prelude::Program;
142    use std::collections::HashSet;
143
144    // Set of already loaded program IDs to prevent redundant fetches.
145    let mut visited = HashSet::new();
146    // Maintains the insertion order of loaded programs.
147    let mut programs = IndexMap::new();
148    // Stack of program IDs to process (DFS traversal).
149    let mut stack = vec![program_id];
150
151    // Loop until all programs and their dependencies are visited.
152    while let Some(current_id) = stack.pop() {
153        // Skip if we've already loaded this program.
154        if !visited.insert(current_id) {
155            continue;
156        }
157
158        // Fetch the program source from the network.
159        let ProgramData::Bytecode(program_src) = leo_package::Program::fetch(
160            Symbol::intern(&current_id.name().to_string()),
161            &context.home()?,
162            network,
163            endpoint,
164            true,
165        )
166        .map_err(|_| CliError::custom(format!("Failed to fetch program source for ID: {current_id}")))?
167        .data
168        else {
169            panic!("Expected bytecode when fetching a remote program");
170        };
171
172        // Parse the program source into a Program object.
173        let program = Program::<N>::from_str(&program_src)
174            .map_err(|_| CliError::custom(format!("Failed to parse program source for ID: {current_id}")))?;
175
176        // Queue all imported programs for future processing.
177        for import_id in program.imports().keys() {
178            stack.push(*import_id);
179        }
180
181        // Add the program to our ordered set.
182        programs.insert(current_id, program);
183    }
184
185    // Return all loaded programs in insertion order.
186    Ok(programs.into_iter().rev().collect())
187}