1use std::collections::HashMap;
2use std::sync::Arc;
3
4use anyhow::anyhow;
5use axum::body::Body;
6use axum::extract::{Path, Query, Request};
7use axum::http::{Response, StatusCode, header};
8use axum::middleware::{self, Next};
9use axum::response::IntoResponse;
10use axum::routing::{get, post};
11use axum::{Extension, Json, Router};
12use bitcoin::hashes::sha256;
13use fedimint_core::config::FederationId;
14use fedimint_core::task::TaskGroup;
15use fedimint_core::util::FmtCompact;
16use fedimint_gateway_common::{
17 ADDRESS_ENDPOINT, ADDRESS_RECHECK_ENDPOINT, BACKUP_ENDPOINT, BackupPayload,
18 CLOSE_CHANNELS_WITH_PEER_ENDPOINT, CONFIGURATION_ENDPOINT, CONNECT_FED_ENDPOINT,
19 CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT, CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT,
20 CloseChannelsWithPeerRequest, ConfigPayload, ConnectFedPayload,
21 CreateInvoiceForOperatorPayload, CreateOfferPayload, DepositAddressPayload,
22 DepositAddressRecheckPayload, GATEWAY_INFO_ENDPOINT, GATEWAY_INFO_POST_ENDPOINT,
23 GET_BALANCES_ENDPOINT, GET_INVOICE_ENDPOINT, GET_LN_ONCHAIN_ADDRESS_ENDPOINT,
24 GetInvoiceRequest, InfoPayload, LEAVE_FED_ENDPOINT, LIST_CHANNELS_ENDPOINT,
25 LIST_TRANSACTIONS_ENDPOINT, LeaveFedPayload, ListTransactionsPayload, MNEMONIC_ENDPOINT,
26 OPEN_CHANNEL_ENDPOINT, OpenChannelRequest, PAY_INVOICE_FOR_OPERATOR_ENDPOINT,
27 PAY_OFFER_FOR_OPERATOR_ENDPOINT, PAYMENT_LOG_ENDPOINT, PAYMENT_SUMMARY_ENDPOINT,
28 PayInvoiceForOperatorPayload, PayOfferPayload, PaymentLogPayload, PaymentSummaryPayload,
29 RECEIVE_ECASH_ENDPOINT, ReceiveEcashPayload, SEND_ONCHAIN_ENDPOINT, SET_FEES_ENDPOINT,
30 SPEND_ECASH_ENDPOINT, STOP_ENDPOINT, SendOnchainRequest, SetFeesPayload, SpendEcashPayload,
31 V1_API_ENDPOINT, WITHDRAW_ENDPOINT, WithdrawPayload,
32};
33use fedimint_ln_common::gateway_endpoint_constants::{
34 GET_GATEWAY_ID_ENDPOINT, PAY_INVOICE_ENDPOINT,
35};
36use fedimint_lnv2_common::endpoint_constants::{
37 CREATE_BOLT11_INVOICE_ENDPOINT, ROUTING_INFO_ENDPOINT, SEND_PAYMENT_ENDPOINT,
38};
39use fedimint_lnv2_common::gateway_api::{CreateBolt11InvoicePayload, SendPaymentPayload};
40use fedimint_lnv2_common::lnurl::VerifyResponse;
41use fedimint_logging::LOG_GATEWAY;
42use hex::ToHex;
43use serde_json::json;
44use tokio::net::TcpListener;
45use tower_http::cors::CorsLayer;
46use tracing::{info, instrument, warn};
47
48use crate::Gateway;
49use crate::error::{AdminGatewayError, PublicGatewayError};
50
51struct LnurlError {
53 code: StatusCode,
54 reason: anyhow::Error,
55}
56
57impl LnurlError {
58 fn internal(reason: anyhow::Error) -> Self {
59 Self {
60 code: StatusCode::INTERNAL_SERVER_ERROR,
61 reason,
62 }
63 }
64}
65
66impl IntoResponse for LnurlError {
67 fn into_response(self) -> Response<Body> {
68 let json = Json(serde_json::json!({
69 "status": "ERROR",
70 "reason": self.reason.to_string(),
71 }));
72
73 (self.code, json).into_response()
74 }
75}
76
77pub async fn run_webserver(gateway: Arc<Gateway>) -> anyhow::Result<()> {
79 let task_group = gateway.task_group.clone();
80 let v1_routes = v1_routes(gateway.clone(), task_group.clone());
81 let api_v1 = Router::new()
82 .nest(&format!("/{V1_API_ENDPOINT}"), v1_routes.clone())
83 .merge(v1_routes);
85
86 let handle = task_group.make_handle();
87 let shutdown_rx = handle.make_shutdown_rx();
88 let listener = TcpListener::bind(&gateway.listen).await?;
89 let serve = axum::serve(listener, api_v1.into_make_service());
90 task_group.spawn("Gateway Webserver", |_| async {
91 let graceful = serve.with_graceful_shutdown(async {
92 shutdown_rx.await;
93 });
94
95 match graceful.await {
96 Err(err) => {
97 warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Error shutting down gatewayd webserver");
98 }
99 _ => {
100 info!(target: LOG_GATEWAY, "Successfully shutdown webserver");
101 }
102 }
103 });
104
105 info!(target: LOG_GATEWAY, listen = %gateway.listen, "Successfully started webserver");
106 Ok(())
107}
108
109fn extract_bearer_token(request: &Request) -> Result<String, StatusCode> {
111 let headers = request.headers();
112 let auth_header = headers.get(header::AUTHORIZATION);
113 if let Some(header_value) = auth_header {
114 let auth_str = header_value
115 .to_str()
116 .map_err(|_| StatusCode::UNAUTHORIZED)?;
117 let token = auth_str.trim_start_matches("Bearer ").to_string();
118 return Ok(token);
119 }
120
121 Err(StatusCode::UNAUTHORIZED)
122}
123
124async fn auth_middleware(
128 Extension(gateway): Extension<Arc<Gateway>>,
129 request: Request,
130 next: Next,
131) -> Result<impl IntoResponse, StatusCode> {
132 let token = extract_bearer_token(&request)?;
133 if bcrypt::verify(token, &gateway.bcrypt_password_hash.to_string())
134 .expect("Bcrypt hash is valid since we just stringified it")
135 {
136 return Ok(next.run(request).await);
137 }
138
139 Err(StatusCode::UNAUTHORIZED)
140}
141
142fn lnv1_routes() -> Router {
144 Router::new()
145 .route(PAY_INVOICE_ENDPOINT, post(pay_invoice))
146 .route(GET_GATEWAY_ID_ENDPOINT, get(get_gateway_id))
147}
148
149fn lnv2_routes() -> Router {
151 Router::new()
152 .route(ROUTING_INFO_ENDPOINT, post(routing_info_v2))
153 .route(SEND_PAYMENT_ENDPOINT, post(pay_bolt11_invoice_v2))
154 .route(
155 CREATE_BOLT11_INVOICE_ENDPOINT,
156 post(create_bolt11_invoice_v2),
157 )
158 .route("/verify/{payment_hash}", get(verify_bolt11_preimage_v2_get))
159}
160
161fn v1_routes(gateway: Arc<Gateway>, task_group: TaskGroup) -> Router {
170 let mut public_routes = Router::new().route(RECEIVE_ECASH_ENDPOINT, post(receive_ecash));
172
173 if gateway.is_running_lnv1() {
174 public_routes = public_routes.merge(lnv1_routes());
175 }
176
177 if gateway.is_running_lnv2() {
178 public_routes = public_routes.merge(lnv2_routes());
179 }
180
181 let authenticated_routes = Router::new()
183 .route(ADDRESS_ENDPOINT, post(address))
184 .route(WITHDRAW_ENDPOINT, post(withdraw))
185 .route(CONNECT_FED_ENDPOINT, post(connect_fed))
186 .route(LEAVE_FED_ENDPOINT, post(leave_fed))
187 .route(BACKUP_ENDPOINT, post(backup))
188 .route(
189 CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT,
190 post(create_invoice_for_operator),
191 )
192 .route(
193 CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT,
194 post(create_offer_for_operator),
195 )
196 .route(
197 PAY_INVOICE_FOR_OPERATOR_ENDPOINT,
198 post(pay_invoice_operator),
199 )
200 .route(PAY_OFFER_FOR_OPERATOR_ENDPOINT, post(pay_offer_operator))
201 .route(GET_INVOICE_ENDPOINT, post(get_invoice))
202 .route(GET_LN_ONCHAIN_ADDRESS_ENDPOINT, get(get_ln_onchain_address))
203 .route(OPEN_CHANNEL_ENDPOINT, post(open_channel))
204 .route(
205 CLOSE_CHANNELS_WITH_PEER_ENDPOINT,
206 post(close_channels_with_peer),
207 )
208 .route(LIST_CHANNELS_ENDPOINT, get(list_channels))
209 .route(LIST_TRANSACTIONS_ENDPOINT, post(list_transactions))
210 .route(SEND_ONCHAIN_ENDPOINT, post(send_onchain))
211 .route(ADDRESS_RECHECK_ENDPOINT, post(recheck_address))
212 .route(GET_BALANCES_ENDPOINT, get(get_balances))
213 .route(SPEND_ECASH_ENDPOINT, post(spend_ecash))
214 .route(MNEMONIC_ENDPOINT, get(mnemonic))
215 .route(STOP_ENDPOINT, get(stop))
216 .route(PAYMENT_LOG_ENDPOINT, post(payment_log))
217 .route(PAYMENT_SUMMARY_ENDPOINT, post(payment_summary))
218 .route(SET_FEES_ENDPOINT, post(set_fees))
219 .route(CONFIGURATION_ENDPOINT, post(configuration))
220 .route(GATEWAY_INFO_POST_ENDPOINT, post(handle_post_info))
222 .route(GATEWAY_INFO_ENDPOINT, get(info))
223 .layer(middleware::from_fn(auth_middleware));
224
225 Router::new()
226 .merge(public_routes)
227 .merge(authenticated_routes)
228 .layer(Extension(gateway))
229 .layer(Extension(task_group))
230 .layer(CorsLayer::permissive())
231}
232
233#[instrument(target = LOG_GATEWAY, skip_all, err)]
237async fn handle_post_info(
238 Extension(gateway): Extension<Arc<Gateway>>,
239 Json(_payload): Json<InfoPayload>,
240) -> Result<impl IntoResponse, AdminGatewayError> {
241 let info = gateway.handle_get_info().await?;
242 Ok(Json(json!(info)))
243}
244
245#[instrument(target = LOG_GATEWAY, skip_all, err)]
247async fn info(
248 Extension(gateway): Extension<Arc<Gateway>>,
249) -> Result<impl IntoResponse, AdminGatewayError> {
250 let info = gateway.handle_get_info().await?;
251 Ok(Json(json!(info)))
252}
253
254#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
256async fn configuration(
257 Extension(gateway): Extension<Arc<Gateway>>,
258 Json(payload): Json<ConfigPayload>,
259) -> Result<impl IntoResponse, AdminGatewayError> {
260 let gateway_fed_config = gateway
261 .handle_get_federation_config(payload.federation_id)
262 .await?;
263 Ok(Json(json!(gateway_fed_config)))
264}
265
266#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
268async fn address(
269 Extension(gateway): Extension<Arc<Gateway>>,
270 Json(payload): Json<DepositAddressPayload>,
271) -> Result<impl IntoResponse, AdminGatewayError> {
272 let address = gateway.handle_address_msg(payload).await?;
273 Ok(Json(json!(address)))
274}
275
276#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
278async fn withdraw(
279 Extension(gateway): Extension<Arc<Gateway>>,
280 Json(payload): Json<WithdrawPayload>,
281) -> Result<impl IntoResponse, AdminGatewayError> {
282 let txid = gateway.handle_withdraw_msg(payload).await?;
283 Ok(Json(json!(txid)))
284}
285
286#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
287async fn create_invoice_for_operator(
288 Extension(gateway): Extension<Arc<Gateway>>,
289 Json(payload): Json<CreateInvoiceForOperatorPayload>,
290) -> Result<impl IntoResponse, AdminGatewayError> {
291 let invoice = gateway
292 .handle_create_invoice_for_operator_msg(payload)
293 .await?;
294 Ok(Json(json!(invoice)))
295}
296
297#[instrument(target = LOG_GATEWAY, skip_all, err)]
298async fn pay_invoice_operator(
299 Extension(gateway): Extension<Arc<Gateway>>,
300 Json(payload): Json<PayInvoiceForOperatorPayload>,
301) -> Result<impl IntoResponse, AdminGatewayError> {
302 let preimage = gateway.handle_pay_invoice_for_operator_msg(payload).await?;
303 Ok(Json(json!(preimage.0.encode_hex::<String>())))
304}
305
306#[instrument(target = LOG_GATEWAY, skip_all, err)]
307async fn pay_invoice(
308 Extension(gateway): Extension<Arc<Gateway>>,
309 Json(payload): Json<fedimint_ln_client::pay::PayInvoicePayload>,
310) -> Result<impl IntoResponse, PublicGatewayError> {
311 let preimage = gateway.handle_pay_invoice_msg(payload).await?;
312 Ok(Json(json!(preimage.0.encode_hex::<String>())))
313}
314
315#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
317async fn connect_fed(
318 Extension(gateway): Extension<Arc<Gateway>>,
319 Json(payload): Json<ConnectFedPayload>,
320) -> Result<impl IntoResponse, AdminGatewayError> {
321 let fed = gateway.handle_connect_federation(payload).await?;
322 Ok(Json(json!(fed)))
323}
324
325#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
327async fn leave_fed(
328 Extension(gateway): Extension<Arc<Gateway>>,
329 Json(payload): Json<LeaveFedPayload>,
330) -> Result<impl IntoResponse, AdminGatewayError> {
331 let fed = gateway.handle_leave_federation(payload).await?;
332 Ok(Json(json!(fed)))
333}
334
335#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
337async fn backup(
338 Extension(gateway): Extension<Arc<Gateway>>,
339 Json(payload): Json<BackupPayload>,
340) -> Result<impl IntoResponse, AdminGatewayError> {
341 gateway.handle_backup_msg(payload).await?;
342 Ok(Json(json!(())))
343}
344
345#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
346async fn set_fees(
347 Extension(gateway): Extension<Arc<Gateway>>,
348 Json(payload): Json<SetFeesPayload>,
349) -> Result<impl IntoResponse, AdminGatewayError> {
350 gateway.handle_set_fees_msg(payload).await?;
351 Ok(Json(json!(())))
352}
353
354#[instrument(target = LOG_GATEWAY, skip_all, err)]
355async fn get_ln_onchain_address(
356 Extension(gateway): Extension<Arc<Gateway>>,
357) -> Result<impl IntoResponse, AdminGatewayError> {
358 let address = gateway.handle_get_ln_onchain_address_msg().await?;
359 Ok(Json(json!(address.to_string())))
360}
361
362#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
363async fn open_channel(
364 Extension(gateway): Extension<Arc<Gateway>>,
365 Json(payload): Json<OpenChannelRequest>,
366) -> Result<impl IntoResponse, AdminGatewayError> {
367 let funding_txid = gateway.handle_open_channel_msg(payload).await?;
368 Ok(Json(json!(funding_txid)))
369}
370
371#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
372async fn close_channels_with_peer(
373 Extension(gateway): Extension<Arc<Gateway>>,
374 Json(payload): Json<CloseChannelsWithPeerRequest>,
375) -> Result<impl IntoResponse, AdminGatewayError> {
376 let response = gateway.handle_close_channels_with_peer_msg(payload).await?;
377 Ok(Json(json!(response)))
378}
379
380#[instrument(target = LOG_GATEWAY, skip_all, err)]
381async fn list_channels(
382 Extension(gateway): Extension<Arc<Gateway>>,
383) -> Result<impl IntoResponse, AdminGatewayError> {
384 let channels = gateway.handle_list_channels_msg().await?;
385 Ok(Json(json!(channels)))
386}
387
388#[instrument(target = LOG_GATEWAY, skip_all, err)]
389async fn send_onchain(
390 Extension(gateway): Extension<Arc<Gateway>>,
391 Json(payload): Json<SendOnchainRequest>,
392) -> Result<impl IntoResponse, AdminGatewayError> {
393 let txid = gateway.handle_send_onchain_msg(payload).await?;
394 Ok(Json(json!(txid)))
395}
396
397#[instrument(target = LOG_GATEWAY, skip_all, err)]
398async fn recheck_address(
399 Extension(gateway): Extension<Arc<Gateway>>,
400 Json(payload): Json<DepositAddressRecheckPayload>,
401) -> Result<impl IntoResponse, AdminGatewayError> {
402 gateway.handle_recheck_address_msg(payload).await?;
403 Ok(Json(json!({})))
404}
405
406#[instrument(target = LOG_GATEWAY, skip_all, err)]
407async fn get_balances(
408 Extension(gateway): Extension<Arc<Gateway>>,
409) -> Result<impl IntoResponse, AdminGatewayError> {
410 let balances = gateway.handle_get_balances_msg().await?;
411 Ok(Json(json!(balances)))
412}
413
414#[instrument(target = LOG_GATEWAY, skip_all, err)]
415async fn get_gateway_id(
416 Extension(gateway): Extension<Arc<Gateway>>,
417) -> Result<impl IntoResponse, PublicGatewayError> {
418 Ok(Json(json!(gateway.gateway_id)))
419}
420
421#[instrument(target = LOG_GATEWAY, skip_all, err)]
422async fn routing_info_v2(
423 Extension(gateway): Extension<Arc<Gateway>>,
424 Json(federation_id): Json<FederationId>,
425) -> Result<impl IntoResponse, PublicGatewayError> {
426 let routing_info = gateway.routing_info_v2(&federation_id).await?;
427 Ok(Json(json!(routing_info)))
428}
429
430#[instrument(target = LOG_GATEWAY, skip_all, err)]
431async fn pay_bolt11_invoice_v2(
432 Extension(gateway): Extension<Arc<Gateway>>,
433 Json(payload): Json<SendPaymentPayload>,
434) -> Result<impl IntoResponse, PublicGatewayError> {
435 let payment_result = gateway.send_payment_v2(payload).await?;
436 Ok(Json(json!(payment_result)))
437}
438
439#[instrument(target = LOG_GATEWAY, skip_all, err)]
440async fn create_bolt11_invoice_v2(
441 Extension(gateway): Extension<Arc<Gateway>>,
442 Json(payload): Json<CreateBolt11InvoicePayload>,
443) -> Result<impl IntoResponse, PublicGatewayError> {
444 let invoice = gateway.create_bolt11_invoice_v2(payload).await?;
445 Ok(Json(json!(invoice)))
446}
447
448async fn verify_bolt11_preimage_v2_get(
449 Extension(gateway): Extension<Arc<Gateway>>,
450 Path(payment_hash): Path<sha256::Hash>,
451 Query(query): Query<HashMap<String, String>>,
452) -> Result<Json<VerifyResponse>, LnurlError> {
453 let response = gateway
454 .verify_bolt11_preimage_v2(payment_hash, query.contains_key("wait"))
455 .await
456 .map_err(|e| LnurlError::internal(anyhow!(e)))?;
457
458 Ok(Json(response))
459}
460
461#[instrument(target = LOG_GATEWAY, skip_all, err)]
462async fn spend_ecash(
463 Extension(gateway): Extension<Arc<Gateway>>,
464 Json(payload): Json<SpendEcashPayload>,
465) -> Result<impl IntoResponse, AdminGatewayError> {
466 Ok(Json(json!(gateway.handle_spend_ecash_msg(payload).await?)))
467}
468
469#[instrument(target = LOG_GATEWAY, skip_all, err)]
470async fn receive_ecash(
471 Extension(gateway): Extension<Arc<Gateway>>,
472 Json(payload): Json<ReceiveEcashPayload>,
473) -> Result<impl IntoResponse, PublicGatewayError> {
474 Ok(Json(json!(
475 gateway.handle_receive_ecash_msg(payload).await?
476 )))
477}
478
479#[instrument(target = LOG_GATEWAY, skip_all, err)]
480async fn mnemonic(
481 Extension(gateway): Extension<Arc<Gateway>>,
482) -> Result<impl IntoResponse, AdminGatewayError> {
483 let words = gateway.handle_mnemonic_msg().await?;
484 Ok(Json(json!(words)))
485}
486
487#[instrument(target = LOG_GATEWAY, skip_all, err)]
488async fn stop(
489 Extension(task_group): Extension<TaskGroup>,
490 Extension(gateway): Extension<Arc<Gateway>>,
491) -> Result<impl IntoResponse, AdminGatewayError> {
492 gateway.handle_shutdown_msg(task_group).await?;
493 Ok(Json(json!(())))
494}
495
496#[instrument(target = LOG_GATEWAY, skip_all, err)]
497async fn payment_log(
498 Extension(gateway): Extension<Arc<Gateway>>,
499 Json(payload): Json<PaymentLogPayload>,
500) -> Result<impl IntoResponse, AdminGatewayError> {
501 let payment_log = gateway.handle_payment_log_msg(payload).await?;
502 Ok(Json(json!(payment_log)))
503}
504
505#[instrument(target = LOG_GATEWAY, skip_all, err)]
506async fn payment_summary(
507 Extension(gateway): Extension<Arc<Gateway>>,
508 Json(payload): Json<PaymentSummaryPayload>,
509) -> Result<impl IntoResponse, AdminGatewayError> {
510 let payment_summary = gateway.handle_payment_summary_msg(payload).await?;
511 Ok(Json(json!(payment_summary)))
512}
513
514#[instrument(target = LOG_GATEWAY, skip_all, err)]
515async fn get_invoice(
516 Extension(gateway): Extension<Arc<Gateway>>,
517 Json(payload): Json<GetInvoiceRequest>,
518) -> Result<impl IntoResponse, AdminGatewayError> {
519 let invoice = gateway.handle_get_invoice_msg(payload).await?;
520 Ok(Json(json!(invoice)))
521}
522
523#[instrument(target = LOG_GATEWAY, skip_all, err)]
524async fn list_transactions(
525 Extension(gateway): Extension<Arc<Gateway>>,
526 Json(payload): Json<ListTransactionsPayload>,
527) -> Result<impl IntoResponse, AdminGatewayError> {
528 let transactions = gateway.handle_list_transactions_msg(payload).await?;
529 Ok(Json(json!(transactions)))
530}
531
532#[instrument(target = LOG_GATEWAY, skip_all, err)]
533async fn create_offer_for_operator(
534 Extension(gateway): Extension<Arc<Gateway>>,
535 Json(payload): Json<CreateOfferPayload>,
536) -> Result<impl IntoResponse, AdminGatewayError> {
537 let offer = gateway
538 .handle_create_offer_for_operator_msg(payload)
539 .await?;
540 Ok(Json(json!(offer)))
541}
542
543#[instrument(target = LOG_GATEWAY, skip_all, err)]
544async fn pay_offer_operator(
545 Extension(gateway): Extension<Arc<Gateway>>,
546 Json(payload): Json<PayOfferPayload>,
547) -> Result<impl IntoResponse, AdminGatewayError> {
548 let response = gateway.handle_pay_offer_for_operator_msg(payload).await?;
549 Ok(Json(json!(response)))
550}