1use crate::execution::ExecutionEngine;
34use crate::network::{AleoHttpClient, ProvableQuery};
35use crate::program::AleoProgram;
36use anyhow::Result;
37use snarkvm::console::program::{ProgramID, Value};
38use snarkvm::prelude::{Network, PrivateKey, TestRng, TestnetV0};
39
40#[derive(Clone)]
42pub struct AleoClient {
43 pub network: AleoHttpClient,
44 account: Option<crate::account::AleoAccount>,
45 program: Option<AleoProgram>,
46}
47
48impl AleoClient {
49 pub fn new(node_url: &str) -> Result<Self> {
51 let network = AleoHttpClient::new(node_url)?;
52 Ok(Self {
53 network,
54 account: None,
55 program: None,
56 })
57 }
58
59 pub fn new_with_rpc(rest_url: &str, rpc_url: &str) -> Result<Self> {
61 let network = AleoHttpClient::new_with_rpc(rest_url, rpc_url)?;
62 Ok(Self {
63 network,
64 account: None,
65 program: None,
66 })
67 }
68
69 pub fn set_account_from_private_key_str(&mut self, pk_str: &str) -> Result<()> {
73 let account = crate::account::AleoAccount::from_private_key_str(pk_str)?;
74 self.account = Some(account);
75 Ok(())
76 }
77
78 pub fn require_account(&self) -> Result<&crate::account::AleoAccount> {
80 self.account.as_ref().ok_or_else(|| {
81 anyhow::anyhow!("No account set. Call set_account_from_private_key_str() first.")
82 })
83 }
84
85 pub fn load_program_from_source(&mut self, source: &str) -> Result<()> {
89 let program = AleoProgram::from_source(source)?;
90 self.program = Some(program);
91 Ok(())
92 }
93
94 pub fn set_program(&mut self, program: AleoProgram) {
96 self.program = Some(program);
97 }
98
99 pub fn require_program(&self) -> Result<&AleoProgram> {
101 self.program.as_ref().ok_or_else(|| {
102 anyhow::anyhow!("No program loaded. Call load_program_from_source() first.")
103 })
104 }
105
106 pub fn execute_local(
118 &self,
119 program_id: &str,
120 function_name: &str,
121 inputs: &[String],
122 ) -> Result<String> {
123 use snarkvm::prelude::{FromStr, TestRng};
124
125 let account = self.require_account()?;
126 let mut rng = TestRng::default();
127
128 let pid = ProgramID::<TestnetV0>::from_str(program_id)?;
129
130 let engine = ExecutionEngine::new()?;
132 if let Some(prog) = &self.program {
134 engine.add_program(prog.inner())?;
135 }
136
137 let (response, _trace) = engine.authorize_and_execute(
138 &account.private_key,
139 &pid,
140 function_name,
141 inputs.iter().map(|s| s.as_str()).collect(),
142 &mut rng,
143 )?;
144
145 Ok(format!("{response:?}"))
146 }
147
148 #[allow(clippy::too_many_arguments)]
155 pub async fn prove_execution(
156 &self,
157 private_key: &PrivateKey<TestnetV0>,
158 program_id: &ProgramID<TestnetV0>,
159 function_name: &str,
160 inputs: Vec<&str>,
161 base_fee: u64,
162 priority_fee: u64,
163 ) -> Result<String> {
164 let mut rng = TestRng::default();
165
166 let program = self.network.fetch_program(&program_id.to_string()).await?;
168
169 let engine = ExecutionEngine::new_with_v0_fee_keys()?;
171 engine.add_program(&program)?;
173
174 let (_response, trace) = engine.authorize_and_execute(
175 private_key,
176 program_id,
177 function_name,
178 inputs,
179 &mut rng,
180 )?;
181
182 let (state_root, block_height) = self.network.fetch_state_root().await?;
184 let query = ProvableQuery::new(
185 state_root,
186 block_height,
187 "https://api.provable.com/v2/testnet",
188 );
189
190 let tx = engine.prove_and_package(
191 trace,
192 private_key,
193 program_id,
194 function_name,
195 base_fee,
196 priority_fee,
197 &query,
198 &mut rng,
199 )?;
200
201 Ok(serde_json::to_string(&tx)?)
202 }
203
204 #[allow(clippy::too_many_arguments)]
208 pub async fn execute_and_broadcast(
209 &self,
210 private_key: &PrivateKey<TestnetV0>,
211 program_id: &ProgramID<TestnetV0>,
212 function_name: &str,
213 inputs: Vec<&str>,
214 base_fee: u64,
215 priority_fee: u64,
216 ) -> Result<String> {
217 let mut rng = TestRng::default();
218
219 let program = self.network.fetch_program(&program_id.to_string()).await?;
221
222 let engine = ExecutionEngine::new_with_v0_fee_keys()?;
224 engine.add_program(&program)?;
226
227 let (_response, trace) = engine.authorize_and_execute(
229 private_key,
230 program_id,
231 function_name,
232 inputs,
233 &mut rng,
234 )?;
235
236 let (state_root, block_height) = self.network.fetch_state_root().await?;
238 let query = ProvableQuery::new(
239 state_root,
240 block_height,
241 "https://api.provable.com/v2/testnet",
242 );
243
244 let tx = engine.prove_and_package(
246 trace,
247 private_key,
248 program_id,
249 function_name,
250 base_fee,
251 priority_fee,
252 &query,
253 &mut rng,
254 )?;
255
256 let tx_json = serde_json::to_string(&tx)?;
258 let raw = self.network.broadcast_transaction(tx_json).await?;
259 Ok(raw.trim_matches('"').to_string())
260 }
261
262 #[allow(clippy::too_many_arguments)]
268 pub async fn execute_and_broadcast_with_values(
269 &self,
270 private_key: &PrivateKey<TestnetV0>,
271 program_id: &ProgramID<TestnetV0>,
272 function_name: &str,
273 values: Vec<Value<TestnetV0>>,
274 base_fee: u64,
275 priority_fee: u64,
276 ) -> Result<String> {
277 use snarkvm::prelude::TestRng;
278
279 let mut rng = TestRng::default();
280
281 let program = self.network.fetch_program(&program_id.to_string()).await?;
283
284 let engine = ExecutionEngine::new_with_v0_fee_keys()?;
286 engine.add_program(&program)?;
288
289 let (_response, trace) = engine.authorize_and_execute_with_values(
291 private_key,
292 program_id,
293 function_name,
294 values,
295 &mut rng,
296 )?;
297
298 let (state_root, block_height) = self.network.fetch_state_root().await?;
300 let query = ProvableQuery::new(
301 state_root,
302 block_height,
303 "https://api.provable.com/v2/testnet",
304 );
305
306 let tx = engine.prove_and_package(
308 trace,
309 private_key,
310 program_id,
311 function_name,
312 base_fee,
313 priority_fee,
314 &query,
315 &mut rng,
316 )?;
317
318 let tx_json = serde_json::to_string(&tx)?;
320 let raw = self.network.broadcast_transaction(tx_json).await?;
321 Ok(raw.trim_matches('"').to_string())
322 }
323
324 pub async fn deploy_program(
335 &self,
336 program_source: &str,
337 priority_fee_in_microcredits: u64,
338 ) -> Result<String> {
339 use snarkvm::prelude::{ConsensusVersion, Program};
340 use std::str::FromStr;
341
342 let account = self.require_account()?;
343 let mut rng = TestRng::default();
344
345 let program = Program::<TestnetV0>::from_str(program_source)
347 .map_err(|e| anyhow::anyhow!("Failed to parse program: {e}"))?;
348
349 let engine = ExecutionEngine::new_with_v0_fee_keys()?;
351 engine.add_program(&program)?;
353
354 let deployment = engine.deploy_program(&program, &mut rng)?;
356
357 let min_cost = engine.deployment_cost_minimum(&deployment, ConsensusVersion::V14)?;
359 let base_fee = min_cost.saturating_mul(105) / 100;
362
363 let (state_root, block_height) = self.network.fetch_state_root().await?;
365 let query = ProvableQuery::new(
366 state_root,
367 block_height,
368 "https://api.provable.com/v2/testnet",
369 );
370
371 let tx = engine.build_deployment_transaction(
373 &account.private_key,
374 &program,
375 &deployment,
376 base_fee,
377 priority_fee_in_microcredits,
378 ConsensusVersion::V14,
379 &query,
380 &mut rng,
381 )?;
382
383 let tx_json = serde_json::to_string(&tx)?;
385 let raw = self.network.broadcast_transaction(tx_json).await?;
386 Ok(raw.trim_matches('"').to_string())
387 }
388
389 pub async fn get_balance(&self) -> Result<Option<u64>> {
395 let addr = self.require_account()?.address_str();
396 if let Some(val) =
398 self.network.fetch_mapping_value_rest("credits.aleo", "account", &addr).await?
399 {
400 return Ok(Some(val));
401 }
402 let val = self.network.fetch_mapping_value("credits.aleo", "account", &addr).await?;
404 match val {
405 Some(s) => Ok(Some(s.trim().parse::<u64>()?)),
406 None => Ok(None),
407 }
408 }
409
410 pub async fn fetch_unspent_records(&self) -> Result<String> {
412 let account = self.require_account()?;
413 self.network.fetch_records(&account.view_key.to_string()).await
414 }
415
416 pub async fn find_private_credits_records(&self) -> Result<Vec<(String, u64)>> {
418 let account = self.require_account()?;
419 self.network.find_private_credits_records(&account.view_key.to_string()).await
420 }
421
422 pub async fn get_block_height(&self) -> Result<u32> {
424 self.network.fetch_block_height().await
425 }
426
427 pub async fn get_state_root(&self) -> Result<<TestnetV0 as Network>::StateRoot> {
429 self.network.fetch_state_root_only().await
430 }
431
432 pub async fn verify_execution(&self, tx_id: &str) -> Result<String> {
442 use snarkvm::ledger::block::Transaction;
443
444 let tx_json = self.network.fetch_transaction(tx_id).await?;
446 let tx: Transaction<TestnetV0> = serde_json::from_str(&tx_json)
447 .map_err(|e| anyhow::anyhow!("Failed to deserialize transaction: {e}"))?;
448
449 match tx {
450 Transaction::Execute(ref _id, ref _exec_id, ref execution, ref _fee) => {
451 let transition = execution.transitions().next()
453 .ok_or_else(|| anyhow::anyhow!("Execution has no transitions"))?;
454 let program_id = *transition.program_id();
455
456 let program = self.network.fetch_program(&program_id.to_string()).await?;
458 let engine = ExecutionEngine::new()?;
459 engine.add_program(&program)?;
460
461 tracing::info!("Verifying execution proof...");
463 engine.verify_execution_transaction(execution)?;
464
465 Ok(format!(
466 "✅ Execution proof VERIFIED\n Program: {}\n Function: {}\n Transitions: {}",
467 program_id,
468 transition.function_name(),
469 execution.len(),
470 ))
471 }
472 Transaction::Deploy(ref _id, ref _deploy_id, ref _owner, ref deployment, ref _fee) => {
473 tracing::info!("Verifying deployment proof...");
475 let engine = ExecutionEngine::new()?;
476 engine.verify_deployment_transaction(deployment, &mut TestRng::default())?;
477
478 Ok(format!(
479 "✅ Deployment proof VERIFIED\n Program: {}",
480 deployment.program_id(),
481 ))
482 }
483 _ => Ok("⚠️ Transaction type does not contain a proof to verify".to_string()),
484 }
485 }
486}