snarkos_cli/commands/developer/
mod.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkOS library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16mod decrypt;
17pub use decrypt::*;
18
19mod deploy;
20pub use deploy::*;
21
22mod execute;
23pub use execute::*;
24
25mod scan;
26pub use scan::*;
27
28mod transfer_private;
29pub use transfer_private::*;
30
31use snarkvm::{
32    console::network::Network,
33    package::Package,
34    prelude::{
35        Address,
36        Ciphertext,
37        Identifier,
38        Literal,
39        Plaintext,
40        PrivateKey,
41        Program,
42        ProgramID,
43        Record,
44        ToBytes,
45        Value,
46        ViewKey,
47        block::Transaction,
48    },
49};
50
51use anyhow::{Result, bail, ensure};
52use clap::{Parser, ValueEnum};
53use colored::Colorize;
54use std::{path::PathBuf, str::FromStr};
55
56#[derive(Copy, Clone, Debug, ValueEnum)]
57pub enum StoreFormat {
58    String,
59    Bytes,
60}
61
62/// Commands to deploy and execute transactions
63#[derive(Debug, Parser)]
64pub enum Developer {
65    /// Decrypt a ciphertext.
66    Decrypt(Decrypt),
67    /// Deploy a program.
68    Deploy(Deploy),
69    /// Execute a program function.
70    Execute(Execute),
71    /// Scan the node for records.
72    Scan(Scan),
73    /// Execute the `credits.aleo/transfer_private` function.
74    TransferPrivate(TransferPrivate),
75}
76
77impl Developer {
78    pub fn parse(self) -> Result<String> {
79        match self {
80            Self::Decrypt(decrypt) => decrypt.parse(),
81            Self::Deploy(deploy) => deploy.parse(),
82            Self::Execute(execute) => execute.parse(),
83            Self::Scan(scan) => scan.parse(),
84            Self::TransferPrivate(transfer_private) => transfer_private.parse(),
85        }
86    }
87
88    /// Parse the package from the directory.
89    fn parse_package<N: Network>(program_id: ProgramID<N>, path: &Option<String>) -> Result<Package<N>> {
90        // Instantiate a path to the directory containing the manifest file.
91        let directory = match path {
92            Some(path) => PathBuf::from_str(path)?,
93            None => std::env::current_dir()?,
94        };
95
96        // Load the package.
97        let package = Package::open(&directory)?;
98
99        ensure!(
100            package.program_id() == &program_id,
101            "The program name in the package does not match the specified program name"
102        );
103
104        // Return the package.
105        Ok(package)
106    }
107
108    /// Parses the record string. If the string is a ciphertext, then attempt to decrypt it.
109    fn parse_record<N: Network>(private_key: &PrivateKey<N>, record: &str) -> Result<Record<N, Plaintext<N>>> {
110        match record.starts_with("record1") {
111            true => {
112                // Parse the ciphertext.
113                let ciphertext = Record::<N, Ciphertext<N>>::from_str(record)?;
114                // Derive the view key.
115                let view_key = ViewKey::try_from(private_key)?;
116                // Decrypt the ciphertext.
117                ciphertext.decrypt(&view_key)
118            }
119            false => Record::<N, Plaintext<N>>::from_str(record),
120        }
121    }
122
123    /// Fetch the program from the given endpoint.
124    fn fetch_program<N: Network>(program_id: &ProgramID<N>, endpoint: &str) -> Result<Program<N>> {
125        // Get the network being used.
126        let network = match N::ID {
127            snarkvm::console::network::MainnetV0::ID => "mainnet",
128            snarkvm::console::network::TestnetV0::ID => "testnet",
129            snarkvm::console::network::CanaryV0::ID => "canary",
130            unknown_id => bail!("Unknown network ID ({unknown_id})"),
131        };
132
133        // Send a request to the query node.
134        let response = ureq::get(&format!("{endpoint}/{network}/program/{program_id}")).call();
135
136        // Deserialize the program.
137        match response {
138            Ok(response) => response.into_json().map_err(|err| err.into()),
139            Err(err) => match err {
140                ureq::Error::Status(_status, response) => {
141                    // Debug formatting displays more useful info, especially if the response body is empty.
142                    bail!("Failed to fetch program {program_id}: {response:?}")
143                }
144                err => bail!(err),
145            },
146        }
147    }
148
149    /// Fetch the public balance in microcredits associated with the address from the given endpoint.
150    fn get_public_balance<N: Network>(address: &Address<N>, endpoint: &str) -> Result<u64> {
151        // Initialize the program id and account identifier.
152        let credits = ProgramID::<N>::from_str("credits.aleo")?;
153        let account_mapping = Identifier::<N>::from_str("account")?;
154
155        // Get the network being used.
156        let network = match N::ID {
157            snarkvm::console::network::MainnetV0::ID => "mainnet",
158            snarkvm::console::network::TestnetV0::ID => "testnet",
159            snarkvm::console::network::CanaryV0::ID => "canary",
160            unknown_id => bail!("Unknown network ID ({unknown_id})"),
161        };
162
163        // Send a request to the query node.
164        let response =
165            ureq::get(&format!("{endpoint}/{network}/program/{credits}/mapping/{account_mapping}/{address}")).call();
166
167        // Deserialize the balance.
168        let balance: Result<Option<Value<N>>> = match response {
169            Ok(response) => response.into_json().map_err(|err| err.into()),
170            Err(err) => match err {
171                ureq::Error::Status(_status, response) => {
172                    bail!(response.into_string().unwrap_or("Response too large!".to_owned()))
173                }
174                err => bail!(err),
175            },
176        };
177
178        // Return the balance in microcredits.
179        match balance {
180            Ok(Some(Value::Plaintext(Plaintext::Literal(Literal::<N>::U64(amount), _)))) => Ok(*amount),
181            Ok(None) => Ok(0),
182            Ok(Some(..)) => bail!("Failed to deserialize balance for {address}"),
183            Err(err) => bail!("Failed to fetch balance for {address}: {err}"),
184        }
185    }
186
187    /// Determine if the transaction should be broadcast or displayed to user.
188    fn handle_transaction<N: Network>(
189        broadcast: &Option<String>,
190        dry_run: bool,
191        store: &Option<String>,
192        store_format: StoreFormat,
193        transaction: Transaction<N>,
194        operation: String,
195    ) -> Result<String> {
196        // Get the transaction id.
197        let transaction_id = transaction.id();
198
199        // Ensure the transaction is not a fee transaction.
200        ensure!(!transaction.is_fee(), "The transaction is a fee transaction and cannot be broadcast");
201
202        // Determine if the transaction should be stored.
203        if let Some(path) = store {
204            match PathBuf::from_str(path) {
205                Ok(file_path) => {
206                    match store_format {
207                        StoreFormat::Bytes => {
208                            let transaction_bytes = transaction.to_bytes_le()?;
209                            std::fs::write(&file_path, transaction_bytes)?;
210                        }
211                        StoreFormat::String => {
212                            let transaction_string = transaction.to_string();
213                            std::fs::write(&file_path, transaction_string)?;
214                        }
215                    }
216
217                    println!(
218                        "Transaction {transaction_id} was stored to {} as {:?}",
219                        file_path.display(),
220                        store_format
221                    );
222                }
223                Err(err) => {
224                    println!("The transaction was unable to be stored due to: {err}");
225                }
226            }
227        };
228
229        // Determine if the transaction should be broadcast to the network.
230        if let Some(endpoint) = broadcast {
231            // Send the deployment request to the local development node.
232            match ureq::post(endpoint).send_json(&transaction) {
233                Ok(id) => {
234                    // Remove the quotes from the response.
235                    let response_string = id.into_string()?.trim_matches('\"').to_string();
236                    ensure!(
237                        response_string == transaction_id.to_string(),
238                        "The response does not match the transaction id. ({response_string} != {transaction_id})"
239                    );
240
241                    match transaction {
242                        Transaction::Deploy(..) => {
243                            println!(
244                                "⌛ Deployment {transaction_id} ('{}') has been broadcast to {}.",
245                                operation.bold(),
246                                endpoint
247                            )
248                        }
249                        Transaction::Execute(..) => {
250                            println!(
251                                "⌛ Execution {transaction_id} ('{}') has been broadcast to {}.",
252                                operation.bold(),
253                                endpoint
254                            )
255                        }
256                        Transaction::Fee(..) => {
257                            println!("❌ Failed to broadcast fee '{}' to the {}.", operation.bold(), endpoint)
258                        }
259                    }
260                }
261                Err(error) => {
262                    let error_message = match error {
263                        ureq::Error::Status(code, response) => {
264                            format!("(status code {code}: {:?})", response.into_string()?)
265                        }
266                        ureq::Error::Transport(err) => format!("({err})"),
267                    };
268
269                    match transaction {
270                        Transaction::Deploy(..) => {
271                            bail!("❌ Failed to deploy '{}' to {}: {}", operation.bold(), &endpoint, error_message)
272                        }
273                        Transaction::Execute(..) => {
274                            bail!(
275                                "❌ Failed to broadcast execution '{}' to {}: {}",
276                                operation.bold(),
277                                &endpoint,
278                                error_message
279                            )
280                        }
281                        Transaction::Fee(..) => {
282                            bail!(
283                                "❌ Failed to broadcast fee '{}' to {}: {}",
284                                operation.bold(),
285                                &endpoint,
286                                error_message
287                            )
288                        }
289                    }
290                }
291            };
292
293            // Output the transaction id.
294            Ok(transaction_id.to_string())
295        } else if dry_run {
296            // Output the transaction string.
297            Ok(transaction.to_string())
298        } else {
299            Ok("".to_string())
300        }
301    }
302}