1use bitcoin::address::NetworkUnchecked;
2use bitcoin::{Address, Txid};
3use fedimint_core::util::SafeUrl;
4use fedimint_lightning::{
5 ChannelInfo, CloseChannelsWithPeerRequest, CloseChannelsWithPeerResponse, OpenChannelRequest,
6 SendOnchainRequest,
7};
8use lightning_invoice::Bolt11Invoice;
9use reqwest::{Method, StatusCode};
10use serde::de::DeserializeOwned;
11use serde::Serialize;
12use thiserror::Error;
13
14use super::{
15 BackupPayload, ConfigPayload, ConnectFedPayload, CreateInvoiceForOperatorPayload,
16 DepositAddressPayload, DepositAddressRecheckPayload, FederationInfo, GatewayBalances,
17 GatewayFedConfig, GatewayInfo, LeaveFedPayload, MnemonicResponse, PayInvoiceForOperatorPayload,
18 PaymentLogPayload, PaymentLogResponse, ReceiveEcashPayload, ReceiveEcashResponse,
19 SetFeesPayload, SpendEcashPayload, SpendEcashResponse, WithdrawPayload, WithdrawResponse,
20 ADDRESS_ENDPOINT, ADDRESS_RECHECK_ENDPOINT, BACKUP_ENDPOINT, CLOSE_CHANNELS_WITH_PEER_ENDPOINT,
21 CONFIGURATION_ENDPOINT, CONNECT_FED_ENDPOINT, CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT,
22 GATEWAY_INFO_ENDPOINT, GATEWAY_INFO_POST_ENDPOINT, GET_BALANCES_ENDPOINT,
23 GET_LN_ONCHAIN_ADDRESS_ENDPOINT, LEAVE_FED_ENDPOINT, LIST_ACTIVE_CHANNELS_ENDPOINT,
24 MNEMONIC_ENDPOINT, OPEN_CHANNEL_ENDPOINT, PAYMENT_LOG_ENDPOINT,
25 PAY_INVOICE_FOR_OPERATOR_ENDPOINT, RECEIVE_ECASH_ENDPOINT, SEND_ONCHAIN_ENDPOINT,
26 SET_FEES_ENDPOINT, SPEND_ECASH_ENDPOINT, STOP_ENDPOINT, WITHDRAW_ENDPOINT,
27};
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 async fn call<P: Serialize, T: DeserializeOwned>(
265 &self,
266 method: Method,
267 url: SafeUrl,
268 payload: Option<P>,
269 ) -> Result<T, GatewayRpcError> {
270 let mut builder = self.client.request(method, url.clone().to_unsafe());
271 if let Some(password) = self.password.clone() {
272 builder = builder.bearer_auth(password);
273 }
274 if let Some(payload) = payload {
275 builder = builder
276 .json(&payload)
277 .header(reqwest::header::CONTENT_TYPE, "application/json");
278 }
279
280 let response = builder.send().await?;
281
282 match response.status() {
283 StatusCode::OK => Ok(response.json::<T>().await?),
284 status => Err(GatewayRpcError::BadStatus(status)),
285 }
286 }
287
288 async fn call_get<T: DeserializeOwned>(&self, url: SafeUrl) -> Result<T, GatewayRpcError> {
289 self.call(Method::GET, url, None::<()>).await
290 }
291
292 async fn call_post<P: Serialize, T: DeserializeOwned>(
293 &self,
294 url: SafeUrl,
295 payload: P,
296 ) -> Result<T, GatewayRpcError> {
297 self.call(Method::POST, url, Some(payload)).await
298 }
299}
300
301pub type GatewayRpcResult<T> = Result<T, GatewayRpcError>;
302
303#[derive(Error, Debug)]
304pub enum GatewayRpcError {
305 #[error("Bad status returned {0}")]
306 BadStatus(StatusCode),
307 #[error(transparent)]
308 RequestError(#[from] reqwest::Error),
309}