#![forbid(unsafe_code)]
#[macro_use]
mod util;
mod models;
pub use self::{models::*, util::*};
use jsonrpc_core::types::*;
use serde::Deserialize;
use serde_json::Value;
use std::{
fmt::Debug,
iter::{empty, once},
sync::Arc,
time::Duration,
};
enum RpcParams {
Map(Box<dyn Iterator<Item = (String, Value)> + Send + 'static>),
}
impl RpcParams {
fn map<M>(v: M) -> Self
where
M: Iterator<Item = (&'static str, Value)> + Send + 'static,
{
RpcParams::Map(Box::new(v.map(|(k, v)| (k.to_string(), v))))
}
}
impl From<RpcParams> for Params {
fn from(value: RpcParams) -> Self {
match value {
RpcParams::Map(v) => Params::Map(v.collect()),
}
}
}
#[derive(Clone, Debug)]
struct RemoteCaller {
http_client: reqwest::Client,
addr: String,
}
impl RemoteCaller {
async fn daemon_rpc_call<T>(&self, method: &'static str, params: RpcParams) -> anyhow::Result<T>
where
T: for<'de> Deserialize<'de> + Send + 'static + Debug,
{
let client = self.http_client.clone();
let uri = format!("{}/{}", self.addr, method);
let json_params: jsonrpc_core::types::params::Params = params.into();
let rsp = client.post(uri).json(&json_params).send().await?;
if rsp.status() != 200 {
rsp.error_for_status()?;
panic!("should never reach here");
}
let rsp = rsp.json::<T>().await?;
Ok(rsp)
}
}
#[derive(Clone, Debug)]
struct CallerWrapper(Arc<RemoteCaller>);
impl CallerWrapper {
async fn request<T>(&self, method: &'static str, params: RpcParams) -> anyhow::Result<T>
where
T: for<'de> Deserialize<'de> + Send + 'static + Debug,
{
let c = self.0.daemon_rpc_call(method, params).await?;
Ok(serde_json::from_value(c)?)
}
}
pub struct LwsRpcClientBuilder {
timeout: Option<Duration>,
}
impl LwsRpcClientBuilder {
pub fn new() -> Self {
Self { timeout: None }
}
pub fn timeout(mut self, timeout: u64) -> Self {
let timeout = Duration::from_secs(timeout);
self.timeout = Some(timeout);
self
}
pub fn build(self, addr: impl Into<String>) -> LwsRpcClient {
LwsRpcClient::new(addr.into(), self)
}
}
impl Default for LwsRpcClientBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct LwsRpcClient {
inner: CallerWrapper,
}
impl LwsRpcClient {
fn new(addr: String, config: LwsRpcClientBuilder) -> Self {
let mut client_builder = reqwest::ClientBuilder::new();
if let Some(timeout) = config.timeout {
client_builder = client_builder.timeout(timeout);
};
Self {
inner: CallerWrapper(Arc::new(RemoteCaller {
http_client: client_builder.build().unwrap(),
addr,
})),
}
}
pub async fn get_address_info(
&self,
address: monero::Address,
view_key: monero::PrivateKey,
) -> anyhow::Result<AddressInfo> {
let params = empty()
.chain(once(("address", address.to_string().into())))
.chain(once(("view_key", view_key.to_string().into())));
self.inner
.request("get_address_info", RpcParams::map(params))
.await
}
pub async fn get_address_txs(
&self,
address: monero::Address,
view_key: monero::PrivateKey,
) -> anyhow::Result<AddressTxs> {
let params = empty()
.chain(once(("address", address.to_string().into())))
.chain(once(("view_key", view_key.to_string().into())));
self.inner
.request("get_address_txs", RpcParams::map(params))
.await
}
pub async fn get_random_outs(
&self,
count: u32,
amounts: Vec<monero::Amount>,
) -> anyhow::Result<AmountOuts> {
let params = empty().chain(once(("count", count.into()))).chain(once((
"amounts",
amounts
.into_iter()
.map(|s| s.as_pico().to_string())
.collect::<Vec<_>>()
.into(),
)));
self.inner
.request("get_random_outs", RpcParams::map(params))
.await
}
pub async fn get_unspent_outs(
&self,
address: monero::Address,
view_key: monero::PrivateKey,
amount: monero::Amount,
mixin: u32,
use_dust: bool,
dust_threshold: monero::Amount,
) -> anyhow::Result<UnspentOuts> {
let params = empty()
.chain(once(("address", address.to_string().into())))
.chain(once(("view_key", view_key.to_string().into())))
.chain(once(("amount", amount.as_pico().to_string().into())))
.chain(once(("mixin", mixin.into())))
.chain(once(("use_dust", use_dust.into())))
.chain(once((
"dust_threshold",
dust_threshold.as_pico().to_string().into(),
)));
self.inner
.request("get_unspent_outs", RpcParams::map(params))
.await
}
pub async fn import_request(
&self,
address: monero::Address,
view_key: monero::PrivateKey,
from_height: Option<u64>,
) -> anyhow::Result<ImportResponse> {
let params = empty()
.chain(once(("address", address.to_string().into())))
.chain(once(("view_key", view_key.to_string().into())))
.chain(from_height.map(|v| ("from_height", v.into())));
self.inner
.request("import_wallet_request", RpcParams::map(params))
.await
}
pub async fn login(
&self,
address: monero::Address,
view_key: monero::PrivateKey,
create_account: bool,
generated_locally: bool,
) -> anyhow::Result<LoginResponse> {
let params = empty()
.chain(once(("address", address.to_string().into())))
.chain(once(("view_key", view_key.to_string().into())))
.chain(once(("create_account", create_account.into())))
.chain(once(("generated_locally", generated_locally.into())));
self.inner.request("login", RpcParams::map(params)).await
}
}