snarkos_cli/commands/developer/
mod.rs1mod 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#[derive(Debug, Parser)]
64pub enum Developer {
65 Decrypt(Decrypt),
67 Deploy(Deploy),
69 Execute(Execute),
71 Scan(Scan),
73 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 fn parse_package<N: Network>(program_id: ProgramID<N>, path: &Option<String>) -> Result<Package<N>> {
90 let directory = match path {
92 Some(path) => PathBuf::from_str(path)?,
93 None => std::env::current_dir()?,
94 };
95
96 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 Ok(package)
106 }
107
108 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 let ciphertext = Record::<N, Ciphertext<N>>::from_str(record)?;
114 let view_key = ViewKey::try_from(private_key)?;
116 ciphertext.decrypt(&view_key)
118 }
119 false => Record::<N, Plaintext<N>>::from_str(record),
120 }
121 }
122
123 fn fetch_program<N: Network>(program_id: &ProgramID<N>, endpoint: &str) -> Result<Program<N>> {
125 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 let response = ureq::get(&format!("{endpoint}/{network}/program/{program_id}")).call();
135
136 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 bail!("Failed to fetch program {program_id}: {response:?}")
143 }
144 err => bail!(err),
145 },
146 }
147 }
148
149 fn get_public_balance<N: Network>(address: &Address<N>, endpoint: &str) -> Result<u64> {
151 let credits = ProgramID::<N>::from_str("credits.aleo")?;
153 let account_mapping = Identifier::<N>::from_str("account")?;
154
155 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 let response =
165 ureq::get(&format!("{endpoint}/{network}/program/{credits}/mapping/{account_mapping}/{address}")).call();
166
167 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 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 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 let transaction_id = transaction.id();
198
199 ensure!(!transaction.is_fee(), "The transaction is a fee transaction and cannot be broadcast");
201
202 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 if let Some(endpoint) = broadcast {
231 match ureq::post(endpoint).send_json(&transaction) {
233 Ok(id) => {
234 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 Ok(transaction_id.to_string())
295 } else if dry_run {
296 Ok(transaction.to_string())
298 } else {
299 Ok("".to_string())
300 }
301 }
302}