use crate::execution::ExecutionEngine;
use crate::network::{AleoHttpClient, ProvableQuery};
use crate::program::AleoProgram;
use anyhow::Result;
use snarkvm::console::program::{ProgramID, Value};
use snarkvm::prelude::{Network, PrivateKey, TestRng, TestnetV0};
#[derive(Clone)]
pub struct AleoClient {
pub network: AleoHttpClient,
account: Option<crate::account::AleoAccount>,
program: Option<AleoProgram>,
}
impl AleoClient {
pub fn new(node_url: &str) -> Result<Self> {
let network = AleoHttpClient::new(node_url)?;
Ok(Self {
network,
account: None,
program: None,
})
}
pub fn new_with_rpc(rest_url: &str, rpc_url: &str) -> Result<Self> {
let network = AleoHttpClient::new_with_rpc(rest_url, rpc_url)?;
Ok(Self {
network,
account: None,
program: None,
})
}
pub fn set_account_from_private_key_str(&mut self, pk_str: &str) -> Result<()> {
let account = crate::account::AleoAccount::from_private_key_str(pk_str)?;
self.account = Some(account);
Ok(())
}
pub fn require_account(&self) -> Result<&crate::account::AleoAccount> {
self.account.as_ref().ok_or_else(|| {
anyhow::anyhow!("No account set. Call set_account_from_private_key_str() first.")
})
}
pub fn load_program_from_source(&mut self, source: &str) -> Result<()> {
let program = AleoProgram::from_source(source)?;
self.program = Some(program);
Ok(())
}
pub fn set_program(&mut self, program: AleoProgram) {
self.program = Some(program);
}
pub fn require_program(&self) -> Result<&AleoProgram> {
self.program.as_ref().ok_or_else(|| {
anyhow::anyhow!("No program loaded. Call load_program_from_source() first.")
})
}
pub fn execute_local(
&self,
program_id: &str,
function_name: &str,
inputs: &[String],
) -> Result<String> {
use snarkvm::prelude::{FromStr, TestRng};
let account = self.require_account()?;
let mut rng = TestRng::default();
let pid = ProgramID::<TestnetV0>::from_str(program_id)?;
let engine = ExecutionEngine::new()?;
if let Some(prog) = &self.program {
engine.add_program(prog.inner())?;
}
let (response, _trace) = engine.authorize_and_execute(
&account.private_key,
&pid,
function_name,
inputs.iter().map(|s| s.as_str()).collect(),
&mut rng,
)?;
Ok(format!("{response:?}"))
}
#[allow(clippy::too_many_arguments)]
pub async fn prove_execution(
&self,
private_key: &PrivateKey<TestnetV0>,
program_id: &ProgramID<TestnetV0>,
function_name: &str,
inputs: Vec<&str>,
base_fee: u64,
priority_fee: u64,
) -> Result<String> {
let mut rng = TestRng::default();
let program = self.network.fetch_program(&program_id.to_string()).await?;
let engine = ExecutionEngine::new_with_v0_fee_keys()?;
engine.add_program(&program)?;
let (_response, trace) = engine.authorize_and_execute(
private_key,
program_id,
function_name,
inputs,
&mut rng,
)?;
let (state_root, block_height) = self.network.fetch_state_root().await?;
let query = ProvableQuery::new(
state_root,
block_height,
"https://api.provable.com/v2/testnet",
);
let tx = engine.prove_and_package(
trace,
private_key,
program_id,
function_name,
base_fee,
priority_fee,
&query,
&mut rng,
)?;
Ok(serde_json::to_string(&tx)?)
}
#[allow(clippy::too_many_arguments)]
pub async fn execute_and_broadcast(
&self,
private_key: &PrivateKey<TestnetV0>,
program_id: &ProgramID<TestnetV0>,
function_name: &str,
inputs: Vec<&str>,
base_fee: u64,
priority_fee: u64,
) -> Result<String> {
let mut rng = TestRng::default();
let program = self.network.fetch_program(&program_id.to_string()).await?;
let engine = ExecutionEngine::new_with_v0_fee_keys()?;
engine.add_program(&program)?;
let (_response, trace) = engine.authorize_and_execute(
private_key,
program_id,
function_name,
inputs,
&mut rng,
)?;
let (state_root, block_height) = self.network.fetch_state_root().await?;
let query = ProvableQuery::new(
state_root,
block_height,
"https://api.provable.com/v2/testnet",
);
let tx = engine.prove_and_package(
trace,
private_key,
program_id,
function_name,
base_fee,
priority_fee,
&query,
&mut rng,
)?;
let tx_json = serde_json::to_string(&tx)?;
let raw = self.network.broadcast_transaction(tx_json).await?;
Ok(raw.trim_matches('"').to_string())
}
#[allow(clippy::too_many_arguments)]
pub async fn execute_and_broadcast_with_values(
&self,
private_key: &PrivateKey<TestnetV0>,
program_id: &ProgramID<TestnetV0>,
function_name: &str,
values: Vec<Value<TestnetV0>>,
base_fee: u64,
priority_fee: u64,
) -> Result<String> {
use snarkvm::prelude::TestRng;
let mut rng = TestRng::default();
let program = self.network.fetch_program(&program_id.to_string()).await?;
let engine = ExecutionEngine::new_with_v0_fee_keys()?;
engine.add_program(&program)?;
let (_response, trace) = engine.authorize_and_execute_with_values(
private_key,
program_id,
function_name,
values,
&mut rng,
)?;
let (state_root, block_height) = self.network.fetch_state_root().await?;
let query = ProvableQuery::new(
state_root,
block_height,
"https://api.provable.com/v2/testnet",
);
let tx = engine.prove_and_package(
trace,
private_key,
program_id,
function_name,
base_fee,
priority_fee,
&query,
&mut rng,
)?;
let tx_json = serde_json::to_string(&tx)?;
let raw = self.network.broadcast_transaction(tx_json).await?;
Ok(raw.trim_matches('"').to_string())
}
pub async fn deploy_program(
&self,
program_source: &str,
priority_fee_in_microcredits: u64,
) -> Result<String> {
use snarkvm::prelude::{ConsensusVersion, Program};
use std::str::FromStr;
let account = self.require_account()?;
let mut rng = TestRng::default();
let program = Program::<TestnetV0>::from_str(program_source)
.map_err(|e| anyhow::anyhow!("Failed to parse program: {e}"))?;
let engine = ExecutionEngine::new_with_v0_fee_keys()?;
engine.add_program(&program)?;
let deployment = engine.deploy_program(&program, &mut rng)?;
let min_cost = engine.deployment_cost_minimum(&deployment, ConsensusVersion::V14)?;
let base_fee = min_cost.saturating_mul(105) / 100;
let (state_root, block_height) = self.network.fetch_state_root().await?;
let query = ProvableQuery::new(
state_root,
block_height,
"https://api.provable.com/v2/testnet",
);
let tx = engine.build_deployment_transaction(
&account.private_key,
&program,
&deployment,
base_fee,
priority_fee_in_microcredits,
ConsensusVersion::V14,
&query,
&mut rng,
)?;
let tx_json = serde_json::to_string(&tx)?;
let raw = self.network.broadcast_transaction(tx_json).await?;
Ok(raw.trim_matches('"').to_string())
}
pub async fn get_balance(&self) -> Result<Option<u64>> {
let addr = self.require_account()?.address_str();
if let Some(val) =
self.network.fetch_mapping_value_rest("credits.aleo", "account", &addr).await?
{
return Ok(Some(val));
}
let val = self.network.fetch_mapping_value("credits.aleo", "account", &addr).await?;
match val {
Some(s) => Ok(Some(s.trim().parse::<u64>()?)),
None => Ok(None),
}
}
pub async fn fetch_unspent_records(&self) -> Result<String> {
let account = self.require_account()?;
self.network.fetch_records(&account.view_key.to_string()).await
}
pub async fn find_private_credits_records(&self) -> Result<Vec<(String, u64)>> {
let account = self.require_account()?;
self.network.find_private_credits_records(&account.view_key.to_string()).await
}
pub async fn get_block_height(&self) -> Result<u32> {
self.network.fetch_block_height().await
}
pub async fn get_state_root(&self) -> Result<<TestnetV0 as Network>::StateRoot> {
self.network.fetch_state_root_only().await
}
pub async fn verify_execution(&self, tx_id: &str) -> Result<String> {
use snarkvm::ledger::block::Transaction;
let tx_json = self.network.fetch_transaction(tx_id).await?;
let tx: Transaction<TestnetV0> = serde_json::from_str(&tx_json)
.map_err(|e| anyhow::anyhow!("Failed to deserialize transaction: {e}"))?;
match tx {
Transaction::Execute(ref _id, ref _exec_id, ref execution, ref _fee) => {
let transition = execution.transitions().next()
.ok_or_else(|| anyhow::anyhow!("Execution has no transitions"))?;
let program_id = *transition.program_id();
let program = self.network.fetch_program(&program_id.to_string()).await?;
let engine = ExecutionEngine::new()?;
engine.add_program(&program)?;
tracing::info!("Verifying execution proof...");
engine.verify_execution_transaction(execution)?;
Ok(format!(
"✅ Execution proof VERIFIED\n Program: {}\n Function: {}\n Transitions: {}",
program_id,
transition.function_name(),
execution.len(),
))
}
Transaction::Deploy(ref _id, ref _deploy_id, ref _owner, ref deployment, ref _fee) => {
tracing::info!("Verifying deployment proof...");
let engine = ExecutionEngine::new()?;
engine.verify_deployment_transaction(deployment, &mut TestRng::default())?;
Ok(format!(
"✅ Deployment proof VERIFIED\n Program: {}",
deployment.program_id(),
))
}
_ => Ok("⚠️ Transaction type does not contain a proof to verify".to_string()),
}
}
}