1use bitcoin::address::NetworkUnchecked;
2use bitcoin::{Address, Txid};
3use fedimint_core::util::SafeUrl;
4use fedimint_gateway_common::{
5 ADDRESS_ENDPOINT, ADDRESS_RECHECK_ENDPOINT, BACKUP_ENDPOINT, BackupPayload,
6 CLOSE_CHANNELS_WITH_PEER_ENDPOINT, CONFIGURATION_ENDPOINT, CONNECT_FED_ENDPOINT,
7 CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT, CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT,
8 ChannelInfo, CloseChannelsWithPeerRequest, CloseChannelsWithPeerResponse, ConfigPayload,
9 ConnectFedPayload, CreateInvoiceForOperatorPayload, CreateOfferPayload, CreateOfferResponse,
10 DepositAddressPayload, DepositAddressRecheckPayload, FederationInfo, GATEWAY_INFO_ENDPOINT,
11 GATEWAY_INFO_POST_ENDPOINT, GET_BALANCES_ENDPOINT, GET_INVOICE_ENDPOINT,
12 GET_LN_ONCHAIN_ADDRESS_ENDPOINT, GatewayBalances, GatewayFedConfig, GatewayInfo,
13 GetInvoiceRequest, GetInvoiceResponse, LEAVE_FED_ENDPOINT, LIST_ACTIVE_CHANNELS_ENDPOINT,
14 LIST_TRANSACTIONS_ENDPOINT, LeaveFedPayload, ListTransactionsPayload, ListTransactionsResponse,
15 MNEMONIC_ENDPOINT, MnemonicResponse, OPEN_CHANNEL_ENDPOINT, OpenChannelRequest,
16 PAY_INVOICE_FOR_OPERATOR_ENDPOINT, PAY_OFFER_FOR_OPERATOR_ENDPOINT, PAYMENT_LOG_ENDPOINT,
17 PAYMENT_SUMMARY_ENDPOINT, PayInvoiceForOperatorPayload, PayOfferPayload, PayOfferResponse,
18 PaymentLogPayload, PaymentLogResponse, PaymentSummaryPayload, PaymentSummaryResponse,
19 RECEIVE_ECASH_ENDPOINT, ReceiveEcashPayload, ReceiveEcashResponse, SEND_ONCHAIN_ENDPOINT,
20 SET_FEES_ENDPOINT, SPEND_ECASH_ENDPOINT, STOP_ENDPOINT, SendOnchainRequest, SetFeesPayload,
21 SpendEcashPayload, SpendEcashResponse, WITHDRAW_ENDPOINT, WithdrawPayload, WithdrawResponse,
22};
23use lightning_invoice::Bolt11Invoice;
24use reqwest::{Method, StatusCode};
25use serde::Serialize;
26use serde::de::DeserializeOwned;
27use thiserror::Error;
28
29pub struct GatewayRpcClient {
30 base_url: SafeUrl,
33 client: reqwest::Client,
35 password: Option<String>,
37}
38
39impl GatewayRpcClient {
40 pub fn new(versioned_api: SafeUrl, password: Option<String>) -> Self {
41 Self {
42 base_url: versioned_api,
43 client: reqwest::Client::new(),
44 password,
45 }
46 }
47
48 pub fn with_password(&self, password: Option<String>) -> Self {
49 GatewayRpcClient::new(self.base_url.clone(), password)
50 }
51
52 pub async fn get_info(&self) -> GatewayRpcResult<GatewayInfo> {
53 let url = self
54 .base_url
55 .join(GATEWAY_INFO_ENDPOINT)
56 .expect("invalid base url");
57 self.call_get(url).await
58 }
59
60 pub async fn get_info_legacy(&self) -> GatewayRpcResult<GatewayInfo> {
62 let url = self
63 .base_url
64 .join(GATEWAY_INFO_POST_ENDPOINT)
65 .expect("invalid base url");
66 self.call_post(url, ()).await
67 }
68
69 pub async fn get_config(&self, payload: ConfigPayload) -> GatewayRpcResult<GatewayFedConfig> {
70 let url = self
71 .base_url
72 .join(CONFIGURATION_ENDPOINT)
73 .expect("invalid base url");
74 self.call_post(url, payload).await
75 }
76
77 pub async fn get_deposit_address(
78 &self,
79 payload: DepositAddressPayload,
80 ) -> GatewayRpcResult<Address<NetworkUnchecked>> {
81 let url = self
82 .base_url
83 .join(ADDRESS_ENDPOINT)
84 .expect("invalid base url");
85 self.call_post(url, payload).await
86 }
87
88 pub async fn withdraw(&self, payload: WithdrawPayload) -> GatewayRpcResult<WithdrawResponse> {
89 let url = self
90 .base_url
91 .join(WITHDRAW_ENDPOINT)
92 .expect("invalid base url");
93 self.call_post(url, payload).await
94 }
95
96 pub async fn connect_federation(
97 &self,
98 payload: ConnectFedPayload,
99 ) -> GatewayRpcResult<FederationInfo> {
100 let url = self
101 .base_url
102 .join(CONNECT_FED_ENDPOINT)
103 .expect("invalid base url");
104 self.call_post(url, payload).await
105 }
106
107 pub async fn leave_federation(
108 &self,
109 payload: LeaveFedPayload,
110 ) -> GatewayRpcResult<FederationInfo> {
111 let url = self
112 .base_url
113 .join(LEAVE_FED_ENDPOINT)
114 .expect("invalid base url");
115 self.call_post(url, payload).await
116 }
117
118 pub async fn backup(&self, payload: BackupPayload) -> GatewayRpcResult<()> {
119 let url = self
120 .base_url
121 .join(BACKUP_ENDPOINT)
122 .expect("invalid base url");
123 self.call_post(url, payload).await
124 }
125
126 pub async fn set_fees(&self, payload: SetFeesPayload) -> GatewayRpcResult<()> {
127 let url = self
128 .base_url
129 .join(SET_FEES_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: OpenChannelRequest) -> 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: CloseChannelsWithPeerRequest,
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: SendOnchainRequest) -> 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 recheck_address(
200 &self,
201 payload: DepositAddressRecheckPayload,
202 ) -> GatewayRpcResult<serde_json::Value> {
203 let url = self
204 .base_url
205 .join(ADDRESS_RECHECK_ENDPOINT)
206 .expect("invalid base url");
207 self.call_post(url, payload).await
208 }
209
210 pub async fn spend_ecash(
211 &self,
212 payload: SpendEcashPayload,
213 ) -> GatewayRpcResult<SpendEcashResponse> {
214 let url = self
215 .base_url
216 .join(SPEND_ECASH_ENDPOINT)
217 .expect("invalid base url");
218 self.call_post(url, payload).await
219 }
220
221 pub async fn receive_ecash(
222 &self,
223 payload: ReceiveEcashPayload,
224 ) -> GatewayRpcResult<ReceiveEcashResponse> {
225 let url = self
226 .base_url
227 .join(RECEIVE_ECASH_ENDPOINT)
228 .expect("invalid base url");
229 self.call_post(url, payload).await
230 }
231
232 pub async fn get_balances(&self) -> GatewayRpcResult<GatewayBalances> {
233 let url = self
234 .base_url
235 .join(GET_BALANCES_ENDPOINT)
236 .expect("invalid base url");
237 self.call_get(url).await
238 }
239
240 pub async fn get_mnemonic(&self) -> GatewayRpcResult<MnemonicResponse> {
241 let url = self
242 .base_url
243 .join(MNEMONIC_ENDPOINT)
244 .expect("invalid base url");
245 self.call_get(url).await
246 }
247
248 pub async fn stop(&self) -> GatewayRpcResult<()> {
249 let url = self.base_url.join(STOP_ENDPOINT).expect("invalid base url");
250 self.call_get(url).await
251 }
252
253 pub async fn payment_log(
254 &self,
255 payload: PaymentLogPayload,
256 ) -> GatewayRpcResult<PaymentLogResponse> {
257 let url = self
258 .base_url
259 .join(PAYMENT_LOG_ENDPOINT)
260 .expect("Invalid base url");
261 self.call_post(url, payload).await
262 }
263
264 pub async fn payment_summary(
265 &self,
266 payload: PaymentSummaryPayload,
267 ) -> GatewayRpcResult<PaymentSummaryResponse> {
268 let url = self
269 .base_url
270 .join(PAYMENT_SUMMARY_ENDPOINT)
271 .expect("invalid base url");
272 self.call_post(url, payload).await
273 }
274
275 pub async fn get_invoice(
276 &self,
277 payload: GetInvoiceRequest,
278 ) -> GatewayRpcResult<Option<GetInvoiceResponse>> {
279 let url = self
280 .base_url
281 .join(GET_INVOICE_ENDPOINT)
282 .expect("invalid base url");
283 self.call_post(url, payload).await
284 }
285
286 pub async fn list_transactions(
287 &self,
288 payload: ListTransactionsPayload,
289 ) -> GatewayRpcResult<ListTransactionsResponse> {
290 let url = self
291 .base_url
292 .join(LIST_TRANSACTIONS_ENDPOINT)
293 .expect("invalid base url");
294 self.call_post(url, payload).await
295 }
296
297 pub async fn create_offer(
298 &self,
299 payload: CreateOfferPayload,
300 ) -> GatewayRpcResult<CreateOfferResponse> {
301 let url = self
302 .base_url
303 .join(CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT)
304 .expect("invalid base url");
305 self.call_post(url, payload).await
306 }
307
308 pub async fn pay_offer(&self, payload: PayOfferPayload) -> GatewayRpcResult<PayOfferResponse> {
309 let url = self
310 .base_url
311 .join(PAY_OFFER_FOR_OPERATOR_ENDPOINT)
312 .expect("invalid base url");
313 self.call_post(url, payload).await
314 }
315
316 async fn call<P: Serialize, T: DeserializeOwned>(
317 &self,
318 method: Method,
319 url: SafeUrl,
320 payload: Option<P>,
321 ) -> Result<T, GatewayRpcError> {
322 let mut builder = self.client.request(method, url.clone().to_unsafe());
323 if let Some(password) = self.password.clone() {
324 builder = builder.bearer_auth(password);
325 }
326 if let Some(payload) = payload {
327 builder = builder
328 .json(&payload)
329 .header(reqwest::header::CONTENT_TYPE, "application/json");
330 }
331
332 let response = builder.send().await?;
333
334 match response.status() {
335 StatusCode::OK => Ok(response.json::<T>().await?),
336 status => Err(GatewayRpcError::BadStatus(status)),
337 }
338 }
339
340 async fn call_get<T: DeserializeOwned>(&self, url: SafeUrl) -> Result<T, GatewayRpcError> {
341 self.call(Method::GET, url, None::<()>).await
342 }
343
344 async fn call_post<P: Serialize, T: DeserializeOwned>(
345 &self,
346 url: SafeUrl,
347 payload: P,
348 ) -> Result<T, GatewayRpcError> {
349 self.call(Method::POST, url, Some(payload)).await
350 }
351}
352
353pub type GatewayRpcResult<T> = Result<T, GatewayRpcError>;
354
355#[derive(Error, Debug)]
356pub enum GatewayRpcError {
357 #[error("Bad status returned {0}")]
358 BadStatus(StatusCode),
359 #[error(transparent)]
360 RequestError(#[from] reqwest::Error),
361}