pub mod identity;
use std::path::{Path, PathBuf};
use std::time::Duration;
use candid::utils::ArgumentEncoder;
use candid::{CandidType, Decode, Principal, encode_args};
use ic_agent::identity::PemError;
use serde::Deserialize;
use serde::de::DeserializeOwned;
use thiserror::Error;
use crate::client::CanisterClient;
use crate::{CanisterClientError, CanisterClientResult};
#[derive(Error, Debug)]
pub enum AgentError {
#[error("configuration error: {0}")]
ConfigurationError(String),
#[error("agent error: {0}")]
Agent(#[from] ic_agent::AgentError),
#[error("failed to read PEM file {0}: {1}")]
PemError(PathBuf, PemError),
}
pub type Result<T> = std::result::Result<T, AgentError>;
#[derive(Clone)]
pub struct IcAgentClient {
pub canister_id: Principal,
agent: ic_agent::Agent,
}
impl IcAgentClient {
pub async fn with_identity(
canister: Principal,
identity_path: impl AsRef<Path>,
network: &str,
timeout: Option<Duration>,
) -> Result<Self> {
let agent = identity::init_agent(identity_path, network, timeout).await?;
Ok(Self {
canister_id: canister,
agent,
})
}
pub fn with_agent(canister: Principal, agent: ic_agent::Agent) -> Self {
Self {
canister_id: canister,
agent,
}
}
}
impl CanisterClient for IcAgentClient {
async fn query<T, R>(&self, method: &str, args: T) -> CanisterClientResult<R>
where
T: ArgumentEncoder + Send + Sync,
R: DeserializeOwned + CandidType + Send,
{
let args = encode_args(args)?;
self.agent
.query(&self.canister_id, method)
.with_arg(args)
.call()
.await
.map_err(CanisterClientError::IcAgentError)
.map(|r| decode(&r))
}
async fn update<T, R>(&self, method: &str, args: T) -> CanisterClientResult<R>
where
T: ArgumentEncoder + Send + Sync,
R: DeserializeOwned + CandidType + Send,
{
let args = encode_args(args)?;
self.agent
.update(&self.canister_id, method)
.with_arg(args)
.call_and_wait()
.await
.map_err(CanisterClientError::IcAgentError)
.map(|r| decode(&r))
}
}
#[inline]
fn decode<'a, T: CandidType + Deserialize<'a>>(bytes: &'a [u8]) -> T {
Decode!(bytes, T).expect("failed to decode item from candid")
}