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: SafeUrl,
30 client: reqwest::Client,
32 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 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}