use crate::execution::ExecutionEngine;
use crate::network::{AleoHttpClient, FixedStateRootQuery};
use crate::program::AleoProgram;
use anyhow::Result;
use snarkvm::console::program::ProgramID;
use snarkvm::prelude::{Network, PrivateKey, TestRng, TestnetV0};
#[derive(Clone)]
pub struct AleoClient {
pub network: AleoHttpClient,
account: Option<crate::account::AleoAccount>,
program: Option<AleoProgram<TestnetV0>>,
}
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 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<TestnetV0>) {
self.program = Some(program);
}
pub fn require_program(&self) -> Result<&AleoProgram<TestnetV0>> {
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 program = self.require_program()?;
let mut rng = TestRng::default();
let pid = ProgramID::<TestnetV0>::from_str(program_id)?;
let engine = ExecutionEngine::new()?;
engine.add_program(program.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 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 = FixedStateRootQuery { state_root, block_height };
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 tx_id = self.network.broadcast_transaction(tx_json).await?;
Ok(tx_id)
}
pub async fn get_balance(&self) -> Result<Option<u64>> {
let addr = self.require_account()?.address_str();
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 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
}
}