ln_gateway/rpc/
rpc_client.rs

1use bitcoin::address::NetworkUnchecked;
2use bitcoin::{Address, Txid};
3use fedimint_core::util::SafeUrl;
4use lightning_invoice::Bolt11Invoice;
5use reqwest::{Method, StatusCode};
6use serde::de::DeserializeOwned;
7use serde::Serialize;
8use thiserror::Error;
9
10use super::{
11    BackupPayload, CloseChannelsWithPeerPayload, ConfigPayload, ConnectFedPayload,
12    CreateInvoiceForOperatorPayload, DepositAddressPayload, FederationInfo, GatewayBalances,
13    GatewayFedConfig, GatewayInfo, LeaveFedPayload, MnemonicResponse, OpenChannelPayload,
14    PayInvoiceForOperatorPayload, ReceiveEcashPayload, ReceiveEcashResponse, SendOnchainPayload,
15    SetConfigurationPayload, SpendEcashPayload, SpendEcashResponse, WithdrawPayload,
16    WithdrawResponse, ADDRESS_ENDPOINT, BACKUP_ENDPOINT, CLOSE_CHANNELS_WITH_PEER_ENDPOINT,
17    CONFIGURATION_ENDPOINT, CONNECT_FED_ENDPOINT, CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT,
18    GATEWAY_INFO_ENDPOINT, GATEWAY_INFO_POST_ENDPOINT, GET_BALANCES_ENDPOINT,
19    GET_LN_ONCHAIN_ADDRESS_ENDPOINT, LEAVE_FED_ENDPOINT, LIST_ACTIVE_CHANNELS_ENDPOINT,
20    MNEMONIC_ENDPOINT, OPEN_CHANNEL_ENDPOINT, PAY_INVOICE_FOR_OPERATOR_ENDPOINT,
21    RECEIVE_ECASH_ENDPOINT, SEND_ONCHAIN_ENDPOINT, SET_CONFIGURATION_ENDPOINT,
22    SPEND_ECASH_ENDPOINT, STOP_ENDPOINT, WITHDRAW_ENDPOINT,
23};
24use crate::lightning::{ChannelInfo, CloseChannelsWithPeerResponse};
25
26pub struct GatewayRpcClient {
27    /// Base URL to gateway web server
28    /// This should include an applicable API version, e.g. http://localhost:8080/v1
29    base_url: SafeUrl,
30    /// A request client
31    client: reqwest::Client,
32    /// Optional gateway password
33    password: Option<String>,
34}
35
36impl GatewayRpcClient {
37    pub fn new(versioned_api: SafeUrl, password: Option<String>) -> Self {
38        Self {
39            base_url: versioned_api,
40            client: reqwest::Client::new(),
41            password,
42        }
43    }
44
45    pub fn with_password(&self, password: Option<String>) -> Self {
46        GatewayRpcClient::new(self.base_url.clone(), password)
47    }
48
49    pub async fn get_info(&self) -> GatewayRpcResult<GatewayInfo> {
50        let url = self
51            .base_url
52            .join(GATEWAY_INFO_ENDPOINT)
53            .expect("invalid base url");
54        self.call_get(url).await
55    }
56
57    // FIXME: deprecated >= 0.3.0
58    pub async fn get_info_legacy(&self) -> GatewayRpcResult<GatewayInfo> {
59        let url = self
60            .base_url
61            .join(GATEWAY_INFO_POST_ENDPOINT)
62            .expect("invalid base url");
63        self.call_post(url, ()).await
64    }
65
66    pub async fn get_config(&self, payload: ConfigPayload) -> GatewayRpcResult<GatewayFedConfig> {
67        let url = self
68            .base_url
69            .join(CONFIGURATION_ENDPOINT)
70            .expect("invalid base url");
71        self.call_post(url, payload).await
72    }
73
74    pub async fn get_deposit_address(
75        &self,
76        payload: DepositAddressPayload,
77    ) -> GatewayRpcResult<Address<NetworkUnchecked>> {
78        let url = self
79            .base_url
80            .join(ADDRESS_ENDPOINT)
81            .expect("invalid base url");
82        self.call_post(url, payload).await
83    }
84
85    pub async fn withdraw(&self, payload: WithdrawPayload) -> GatewayRpcResult<WithdrawResponse> {
86        let url = self
87            .base_url
88            .join(WITHDRAW_ENDPOINT)
89            .expect("invalid base url");
90        self.call_post(url, payload).await
91    }
92
93    pub async fn connect_federation(
94        &self,
95        payload: ConnectFedPayload,
96    ) -> GatewayRpcResult<FederationInfo> {
97        let url = self
98            .base_url
99            .join(CONNECT_FED_ENDPOINT)
100            .expect("invalid base url");
101        self.call_post(url, payload).await
102    }
103
104    pub async fn leave_federation(
105        &self,
106        payload: LeaveFedPayload,
107    ) -> GatewayRpcResult<FederationInfo> {
108        let url = self
109            .base_url
110            .join(LEAVE_FED_ENDPOINT)
111            .expect("invalid base url");
112        self.call_post(url, payload).await
113    }
114
115    pub async fn backup(&self, payload: BackupPayload) -> GatewayRpcResult<()> {
116        let url = self
117            .base_url
118            .join(BACKUP_ENDPOINT)
119            .expect("invalid base url");
120        self.call_post(url, payload).await
121    }
122
123    pub async fn set_configuration(
124        &self,
125        payload: SetConfigurationPayload,
126    ) -> GatewayRpcResult<()> {
127        let url = self
128            .base_url
129            .join(SET_CONFIGURATION_ENDPOINT)
130            .expect("invalid base url");
131        self.call_post(url, payload).await
132    }
133
134    pub async fn create_invoice_for_self(
135        &self,
136        payload: CreateInvoiceForOperatorPayload,
137    ) -> GatewayRpcResult<Bolt11Invoice> {
138        let url = self
139            .base_url
140            .join(CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT)
141            .expect("invalid base url");
142        self.call_post(url, payload).await
143    }
144
145    pub async fn pay_invoice(
146        &self,
147        payload: PayInvoiceForOperatorPayload,
148    ) -> GatewayRpcResult<String> {
149        let url = self
150            .base_url
151            .join(PAY_INVOICE_FOR_OPERATOR_ENDPOINT)
152            .expect("invalid base url");
153        self.call_post(url, payload).await
154    }
155
156    pub async fn get_ln_onchain_address(&self) -> GatewayRpcResult<Address<NetworkUnchecked>> {
157        let url = self
158            .base_url
159            .join(GET_LN_ONCHAIN_ADDRESS_ENDPOINT)
160            .expect("invalid base url");
161        self.call_get(url).await
162    }
163
164    pub async fn open_channel(&self, payload: OpenChannelPayload) -> GatewayRpcResult<Txid> {
165        let url = self
166            .base_url
167            .join(OPEN_CHANNEL_ENDPOINT)
168            .expect("invalid base url");
169        self.call_post(url, payload).await
170    }
171
172    pub async fn close_channels_with_peer(
173        &self,
174        payload: CloseChannelsWithPeerPayload,
175    ) -> GatewayRpcResult<CloseChannelsWithPeerResponse> {
176        let url = self
177            .base_url
178            .join(CLOSE_CHANNELS_WITH_PEER_ENDPOINT)
179            .expect("invalid base url");
180        self.call_post(url, payload).await
181    }
182
183    pub async fn list_active_channels(&self) -> GatewayRpcResult<Vec<ChannelInfo>> {
184        let url = self
185            .base_url
186            .join(LIST_ACTIVE_CHANNELS_ENDPOINT)
187            .expect("invalid base url");
188        self.call_get(url).await
189    }
190
191    pub async fn send_onchain(&self, payload: SendOnchainPayload) -> GatewayRpcResult<Txid> {
192        let url = self
193            .base_url
194            .join(SEND_ONCHAIN_ENDPOINT)
195            .expect("invalid base url");
196        self.call_post(url, payload).await
197    }
198
199    pub async fn spend_ecash(
200        &self,
201        payload: SpendEcashPayload,
202    ) -> GatewayRpcResult<SpendEcashResponse> {
203        let url = self
204            .base_url
205            .join(SPEND_ECASH_ENDPOINT)
206            .expect("invalid base url");
207        self.call_post(url, payload).await
208    }
209
210    pub async fn receive_ecash(
211        &self,
212        payload: ReceiveEcashPayload,
213    ) -> GatewayRpcResult<ReceiveEcashResponse> {
214        let url = self
215            .base_url
216            .join(RECEIVE_ECASH_ENDPOINT)
217            .expect("invalid base url");
218        self.call_post(url, payload).await
219    }
220
221    pub async fn get_balances(&self) -> GatewayRpcResult<GatewayBalances> {
222        let url = self
223            .base_url
224            .join(GET_BALANCES_ENDPOINT)
225            .expect("invalid base url");
226        self.call_get(url).await
227    }
228
229    pub async fn get_mnemonic(&self) -> GatewayRpcResult<MnemonicResponse> {
230        let url = self
231            .base_url
232            .join(MNEMONIC_ENDPOINT)
233            .expect("invalid base url");
234        self.call_get(url).await
235    }
236
237    pub async fn stop(&self) -> GatewayRpcResult<()> {
238        let url = self.base_url.join(STOP_ENDPOINT).expect("invalid base url");
239        self.call_get(url).await
240    }
241
242    async fn call<P: Serialize, T: DeserializeOwned>(
243        &self,
244        method: Method,
245        url: SafeUrl,
246        payload: Option<P>,
247    ) -> Result<T, GatewayRpcError> {
248        let mut builder = self.client.request(method, url.clone().to_unsafe());
249        if let Some(password) = self.password.clone() {
250            builder = builder.bearer_auth(password);
251        }
252        if let Some(payload) = payload {
253            builder = builder
254                .json(&payload)
255                .header(reqwest::header::CONTENT_TYPE, "application/json");
256        }
257
258        let response = builder.send().await?;
259
260        match response.status() {
261            StatusCode::OK => Ok(response.json::<T>().await?),
262            status => Err(GatewayRpcError::BadStatus(status)),
263        }
264    }
265
266    async fn call_get<T: DeserializeOwned>(&self, url: SafeUrl) -> Result<T, GatewayRpcError> {
267        self.call(Method::GET, url, None::<()>).await
268    }
269
270    async fn call_post<P: Serialize, T: DeserializeOwned>(
271        &self,
272        url: SafeUrl,
273        payload: P,
274    ) -> Result<T, GatewayRpcError> {
275        self.call(Method::POST, url, Some(payload)).await
276    }
277}
278
279pub type GatewayRpcResult<T> = Result<T, GatewayRpcError>;
280
281#[derive(Error, Debug)]
282pub enum GatewayRpcError {
283    #[error("Bad status returned {0}")]
284    BadStatus(StatusCode),
285    #[error(transparent)]
286    RequestError(#[from] reqwest::Error),
287}