fedimint_ln_common/
client.rs1use std::collections::BTreeSet;
2use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
3use std::str::FromStr;
4
5use anyhow::Context;
6use fedimint_core::util::SafeUrl;
7use iroh::NodeAddr;
8use reqwest::{Method, StatusCode};
9use serde::Serialize;
10use serde::de::DeserializeOwned;
11use thiserror::Error;
12
13use crate::iroh::GatewayIrohConnector;
14
15pub struct GatewayRpcClient {
16 base_url: SafeUrl,
17 iroh_connector: Option<GatewayIrohConnector>,
18 client: reqwest::Client,
19 password: Option<String>,
20}
21
22impl GatewayRpcClient {
23 pub async fn new(
24 api: SafeUrl,
25 password: Option<String>,
26 iroh_dns: Option<SafeUrl>,
27 connection_override: Option<SafeUrl>,
28 ) -> anyhow::Result<Self> {
29 let iroh_connector = if api.scheme() == "iroh" {
30 let host = api.host_str().context("Url is missing host")?;
31 let iroh_pk = iroh::PublicKey::from_str(host).context(format!(
32 "Could not parse Iroh Public key: Invalid public key: {host}"
33 ))?;
34 let mut iroh_connector =
35 GatewayIrohConnector::new(iroh_pk, password.clone(), iroh_dns).await?;
36
37 if let Some(connection_override) = connection_override {
38 let node_addr = NodeAddr {
39 node_id: iroh_pk,
40 relay_url: None,
41 direct_addresses: BTreeSet::from([SocketAddr::V4(SocketAddrV4::new(
42 connection_override
43 .host_str()
44 .ok_or(anyhow::anyhow!("No connection override host"))?
45 .parse::<Ipv4Addr>()?,
46 connection_override.port().ok_or(anyhow::anyhow!(
47 "No iroh port supplied for connection override"
48 ))?,
49 ))]),
50 };
51
52 iroh_connector = iroh_connector.with_connection_override(iroh_pk, node_addr);
53 }
54 Some(iroh_connector)
55 } else {
56 None
57 };
58
59 Ok(Self {
60 base_url: api,
61 iroh_connector,
62 client: reqwest::Client::new(),
63 password,
64 })
65 }
66
67 async fn call<P: Serialize, T: DeserializeOwned>(
68 &self,
69 method: Method,
70 route: &str,
71 payload: Option<P>,
72 ) -> Result<T, GatewayRpcError> {
73 if let Some(iroh_connector) = &self.iroh_connector {
74 let payload = payload.map(|p| serde_json::to_value(p).expect("Could not serialize"));
75 let response = iroh_connector
76 .request(route, payload)
77 .await
78 .map_err(|e| GatewayRpcError::IrohError(e.to_string()))?;
79 let status_code = StatusCode::from_u16(response.status)
80 .map_err(|e| GatewayRpcError::IrohError(e.to_string()))?;
81 match status_code {
82 StatusCode::OK => {
83 let response = serde_json::from_value::<T>(response.body)
84 .map_err(|e| GatewayRpcError::IrohError(e.to_string()))?;
85 Ok(response)
86 }
87 status => Err(GatewayRpcError::BadStatus(status)),
88 }
89 } else {
90 let url = self.base_url.join(route).expect("Invalid base url");
91 let mut builder = self.client.request(method, url.clone().to_unsafe());
92 if let Some(password) = self.password.clone() {
93 builder = builder.bearer_auth(password);
94 }
95 if let Some(payload) = payload {
96 builder = builder.json(&payload);
97 }
98
99 let response = builder
100 .send()
101 .await
102 .map_err(|e| GatewayRpcError::RequestError(e.to_string()))?;
103
104 match response.status() {
105 StatusCode::OK => Ok(response
106 .json::<T>()
107 .await
108 .map_err(|e| GatewayRpcError::RequestError(e.to_string()))?),
109 status => Err(GatewayRpcError::BadStatus(status)),
110 }
111 }
112 }
113
114 pub async fn call_get<T: DeserializeOwned>(&self, route: &str) -> Result<T, GatewayRpcError> {
115 self.call(Method::GET, route, None::<()>).await
116 }
117
118 pub async fn call_post<P: Serialize, T: DeserializeOwned>(
119 &self,
120 route: &str,
121 payload: P,
122 ) -> Result<T, GatewayRpcError> {
123 self.call(Method::POST, route, Some(payload)).await
124 }
125}
126
127pub type GatewayRpcResult<T> = Result<T, GatewayRpcError>;
128
129#[derive(Error, Debug, Clone, Eq, PartialEq)]
130pub enum GatewayRpcError {
131 #[error("Bad status returned {0}")]
132 BadStatus(StatusCode),
133 #[error("Error connecting to the gateway {0}")]
134 RequestError(String),
135 #[error("Iroh error: {0}")]
136 IrohError(String),
137}