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 public_routes = public_routes.merge(lnv1_routes());
173 public_routes = public_routes.merge(lnv2_routes());
174
175 let authenticated_routes = Router::new()
177 .route(ADDRESS_ENDPOINT, post(address))
178 .route(WITHDRAW_ENDPOINT, post(withdraw))
179 .route(CONNECT_FED_ENDPOINT, post(connect_fed))
180 .route(LEAVE_FED_ENDPOINT, post(leave_fed))
181 .route(BACKUP_ENDPOINT, post(backup))
182 .route(
183 CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT,
184 post(create_invoice_for_operator),
185 )
186 .route(
187 CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT,
188 post(create_offer_for_operator),
189 )
190 .route(
191 PAY_INVOICE_FOR_OPERATOR_ENDPOINT,
192 post(pay_invoice_operator),
193 )
194 .route(PAY_OFFER_FOR_OPERATOR_ENDPOINT, post(pay_offer_operator))
195 .route(GET_INVOICE_ENDPOINT, post(get_invoice))
196 .route(GET_LN_ONCHAIN_ADDRESS_ENDPOINT, get(get_ln_onchain_address))
197 .route(OPEN_CHANNEL_ENDPOINT, post(open_channel))
198 .route(
199 CLOSE_CHANNELS_WITH_PEER_ENDPOINT,
200 post(close_channels_with_peer),
201 )
202 .route(LIST_CHANNELS_ENDPOINT, get(list_channels))
203 .route(LIST_TRANSACTIONS_ENDPOINT, post(list_transactions))
204 .route(SEND_ONCHAIN_ENDPOINT, post(send_onchain))
205 .route(ADDRESS_RECHECK_ENDPOINT, post(recheck_address))
206 .route(GET_BALANCES_ENDPOINT, get(get_balances))
207 .route(SPEND_ECASH_ENDPOINT, post(spend_ecash))
208 .route(MNEMONIC_ENDPOINT, get(mnemonic))
209 .route(STOP_ENDPOINT, get(stop))
210 .route(PAYMENT_LOG_ENDPOINT, post(payment_log))
211 .route(PAYMENT_SUMMARY_ENDPOINT, post(payment_summary))
212 .route(SET_FEES_ENDPOINT, post(set_fees))
213 .route(CONFIGURATION_ENDPOINT, post(configuration))
214 .route(GATEWAY_INFO_POST_ENDPOINT, post(handle_post_info))
216 .route(GATEWAY_INFO_ENDPOINT, get(info))
217 .layer(middleware::from_fn(auth_middleware));
218
219 Router::new()
220 .merge(public_routes)
221 .merge(authenticated_routes)
222 .layer(Extension(gateway))
223 .layer(Extension(task_group))
224 .layer(CorsLayer::permissive())
225}
226
227#[instrument(target = LOG_GATEWAY, skip_all, err)]
231async fn handle_post_info(
232 Extension(gateway): Extension<Arc<Gateway>>,
233 Json(_payload): Json<InfoPayload>,
234) -> Result<impl IntoResponse, AdminGatewayError> {
235 let info = gateway.handle_get_info().await?;
236 Ok(Json(json!(info)))
237}
238
239#[instrument(target = LOG_GATEWAY, skip_all, err)]
241async fn info(
242 Extension(gateway): Extension<Arc<Gateway>>,
243) -> Result<impl IntoResponse, AdminGatewayError> {
244 let info = gateway.handle_get_info().await?;
245 Ok(Json(json!(info)))
246}
247
248#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
250async fn configuration(
251 Extension(gateway): Extension<Arc<Gateway>>,
252 Json(payload): Json<ConfigPayload>,
253) -> Result<impl IntoResponse, AdminGatewayError> {
254 let gateway_fed_config = gateway
255 .handle_get_federation_config(payload.federation_id)
256 .await?;
257 Ok(Json(json!(gateway_fed_config)))
258}
259
260#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
262async fn address(
263 Extension(gateway): Extension<Arc<Gateway>>,
264 Json(payload): Json<DepositAddressPayload>,
265) -> Result<impl IntoResponse, AdminGatewayError> {
266 let address = gateway.handle_address_msg(payload).await?;
267 Ok(Json(json!(address)))
268}
269
270#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
272async fn withdraw(
273 Extension(gateway): Extension<Arc<Gateway>>,
274 Json(payload): Json<WithdrawPayload>,
275) -> Result<impl IntoResponse, AdminGatewayError> {
276 let txid = gateway.handle_withdraw_msg(payload).await?;
277 Ok(Json(json!(txid)))
278}
279
280#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
281async fn create_invoice_for_operator(
282 Extension(gateway): Extension<Arc<Gateway>>,
283 Json(payload): Json<CreateInvoiceForOperatorPayload>,
284) -> Result<impl IntoResponse, AdminGatewayError> {
285 let invoice = gateway
286 .handle_create_invoice_for_operator_msg(payload)
287 .await?;
288 Ok(Json(json!(invoice)))
289}
290
291#[instrument(target = LOG_GATEWAY, skip_all, err)]
292async fn pay_invoice_operator(
293 Extension(gateway): Extension<Arc<Gateway>>,
294 Json(payload): Json<PayInvoiceForOperatorPayload>,
295) -> Result<impl IntoResponse, AdminGatewayError> {
296 let preimage = gateway.handle_pay_invoice_for_operator_msg(payload).await?;
297 Ok(Json(json!(preimage.0.encode_hex::<String>())))
298}
299
300#[instrument(target = LOG_GATEWAY, skip_all, err)]
301async fn pay_invoice(
302 Extension(gateway): Extension<Arc<Gateway>>,
303 Json(payload): Json<fedimint_ln_client::pay::PayInvoicePayload>,
304) -> Result<impl IntoResponse, PublicGatewayError> {
305 let preimage = gateway.handle_pay_invoice_msg(payload).await?;
306 Ok(Json(json!(preimage.0.encode_hex::<String>())))
307}
308
309#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
311async fn connect_fed(
312 Extension(gateway): Extension<Arc<Gateway>>,
313 Json(payload): Json<ConnectFedPayload>,
314) -> Result<impl IntoResponse, AdminGatewayError> {
315 let fed = gateway.handle_connect_federation(payload).await?;
316 Ok(Json(json!(fed)))
317}
318
319#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
321async fn leave_fed(
322 Extension(gateway): Extension<Arc<Gateway>>,
323 Json(payload): Json<LeaveFedPayload>,
324) -> Result<impl IntoResponse, AdminGatewayError> {
325 let fed = gateway.handle_leave_federation(payload).await?;
326 Ok(Json(json!(fed)))
327}
328
329#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
331async fn backup(
332 Extension(gateway): Extension<Arc<Gateway>>,
333 Json(payload): Json<BackupPayload>,
334) -> Result<impl IntoResponse, AdminGatewayError> {
335 gateway.handle_backup_msg(payload).await?;
336 Ok(Json(json!(())))
337}
338
339#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
340async fn set_fees(
341 Extension(gateway): Extension<Arc<Gateway>>,
342 Json(payload): Json<SetFeesPayload>,
343) -> Result<impl IntoResponse, AdminGatewayError> {
344 gateway.handle_set_fees_msg(payload).await?;
345 Ok(Json(json!(())))
346}
347
348#[instrument(target = LOG_GATEWAY, skip_all, err)]
349async fn get_ln_onchain_address(
350 Extension(gateway): Extension<Arc<Gateway>>,
351) -> Result<impl IntoResponse, AdminGatewayError> {
352 let address = gateway.handle_get_ln_onchain_address_msg().await?;
353 Ok(Json(json!(address.to_string())))
354}
355
356#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
357async fn open_channel(
358 Extension(gateway): Extension<Arc<Gateway>>,
359 Json(payload): Json<OpenChannelRequest>,
360) -> Result<impl IntoResponse, AdminGatewayError> {
361 let funding_txid = gateway.handle_open_channel_msg(payload).await?;
362 Ok(Json(json!(funding_txid)))
363}
364
365#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
366async fn close_channels_with_peer(
367 Extension(gateway): Extension<Arc<Gateway>>,
368 Json(payload): Json<CloseChannelsWithPeerRequest>,
369) -> Result<impl IntoResponse, AdminGatewayError> {
370 let response = gateway.handle_close_channels_with_peer_msg(payload).await?;
371 Ok(Json(json!(response)))
372}
373
374#[instrument(target = LOG_GATEWAY, skip_all, err)]
375async fn list_channels(
376 Extension(gateway): Extension<Arc<Gateway>>,
377) -> Result<impl IntoResponse, AdminGatewayError> {
378 let channels = gateway.handle_list_channels_msg().await?;
379 Ok(Json(json!(channels)))
380}
381
382#[instrument(target = LOG_GATEWAY, skip_all, err)]
383async fn send_onchain(
384 Extension(gateway): Extension<Arc<Gateway>>,
385 Json(payload): Json<SendOnchainRequest>,
386) -> Result<impl IntoResponse, AdminGatewayError> {
387 let txid = gateway.handle_send_onchain_msg(payload).await?;
388 Ok(Json(json!(txid)))
389}
390
391#[instrument(target = LOG_GATEWAY, skip_all, err)]
392async fn recheck_address(
393 Extension(gateway): Extension<Arc<Gateway>>,
394 Json(payload): Json<DepositAddressRecheckPayload>,
395) -> Result<impl IntoResponse, AdminGatewayError> {
396 gateway.handle_recheck_address_msg(payload).await?;
397 Ok(Json(json!({})))
398}
399
400#[instrument(target = LOG_GATEWAY, skip_all, err)]
401async fn get_balances(
402 Extension(gateway): Extension<Arc<Gateway>>,
403) -> Result<impl IntoResponse, AdminGatewayError> {
404 let balances = gateway.handle_get_balances_msg().await?;
405 Ok(Json(json!(balances)))
406}
407
408#[instrument(target = LOG_GATEWAY, skip_all, err)]
409async fn get_gateway_id(
410 Extension(gateway): Extension<Arc<Gateway>>,
411) -> Result<impl IntoResponse, PublicGatewayError> {
412 Ok(Json(json!(gateway.gateway_id)))
413}
414
415#[instrument(target = LOG_GATEWAY, skip_all, err)]
416async fn routing_info_v2(
417 Extension(gateway): Extension<Arc<Gateway>>,
418 Json(federation_id): Json<FederationId>,
419) -> Result<impl IntoResponse, PublicGatewayError> {
420 let routing_info = gateway.routing_info_v2(&federation_id).await?;
421 Ok(Json(json!(routing_info)))
422}
423
424#[instrument(target = LOG_GATEWAY, skip_all, err)]
425async fn pay_bolt11_invoice_v2(
426 Extension(gateway): Extension<Arc<Gateway>>,
427 Json(payload): Json<SendPaymentPayload>,
428) -> Result<impl IntoResponse, PublicGatewayError> {
429 let payment_result = gateway.send_payment_v2(payload).await?;
430 Ok(Json(json!(payment_result)))
431}
432
433#[instrument(target = LOG_GATEWAY, skip_all, err)]
434async fn create_bolt11_invoice_v2(
435 Extension(gateway): Extension<Arc<Gateway>>,
436 Json(payload): Json<CreateBolt11InvoicePayload>,
437) -> Result<impl IntoResponse, PublicGatewayError> {
438 let invoice = gateway.create_bolt11_invoice_v2(payload).await?;
439 Ok(Json(json!(invoice)))
440}
441
442async fn verify_bolt11_preimage_v2_get(
443 Extension(gateway): Extension<Arc<Gateway>>,
444 Path(payment_hash): Path<sha256::Hash>,
445 Query(query): Query<HashMap<String, String>>,
446) -> Result<Json<VerifyResponse>, LnurlError> {
447 let response = gateway
448 .verify_bolt11_preimage_v2(payment_hash, query.contains_key("wait"))
449 .await
450 .map_err(|e| LnurlError::internal(anyhow!(e)))?;
451
452 Ok(Json(response))
453}
454
455#[instrument(target = LOG_GATEWAY, skip_all, err)]
456async fn spend_ecash(
457 Extension(gateway): Extension<Arc<Gateway>>,
458 Json(payload): Json<SpendEcashPayload>,
459) -> Result<impl IntoResponse, AdminGatewayError> {
460 Ok(Json(json!(gateway.handle_spend_ecash_msg(payload).await?)))
461}
462
463#[instrument(target = LOG_GATEWAY, skip_all, err)]
464async fn receive_ecash(
465 Extension(gateway): Extension<Arc<Gateway>>,
466 Json(payload): Json<ReceiveEcashPayload>,
467) -> Result<impl IntoResponse, PublicGatewayError> {
468 Ok(Json(json!(
469 gateway.handle_receive_ecash_msg(payload).await?
470 )))
471}
472
473#[instrument(target = LOG_GATEWAY, skip_all, err)]
474async fn mnemonic(
475 Extension(gateway): Extension<Arc<Gateway>>,
476) -> Result<impl IntoResponse, AdminGatewayError> {
477 let words = gateway.handle_mnemonic_msg().await?;
478 Ok(Json(json!(words)))
479}
480
481#[instrument(target = LOG_GATEWAY, skip_all, err)]
482async fn stop(
483 Extension(task_group): Extension<TaskGroup>,
484 Extension(gateway): Extension<Arc<Gateway>>,
485) -> Result<impl IntoResponse, AdminGatewayError> {
486 gateway.handle_shutdown_msg(task_group).await?;
487 Ok(Json(json!(())))
488}
489
490#[instrument(target = LOG_GATEWAY, skip_all, err)]
491async fn payment_log(
492 Extension(gateway): Extension<Arc<Gateway>>,
493 Json(payload): Json<PaymentLogPayload>,
494) -> Result<impl IntoResponse, AdminGatewayError> {
495 let payment_log = gateway.handle_payment_log_msg(payload).await?;
496 Ok(Json(json!(payment_log)))
497}
498
499#[instrument(target = LOG_GATEWAY, skip_all, err)]
500async fn payment_summary(
501 Extension(gateway): Extension<Arc<Gateway>>,
502 Json(payload): Json<PaymentSummaryPayload>,
503) -> Result<impl IntoResponse, AdminGatewayError> {
504 let payment_summary = gateway.handle_payment_summary_msg(payload).await?;
505 Ok(Json(json!(payment_summary)))
506}
507
508#[instrument(target = LOG_GATEWAY, skip_all, err)]
509async fn get_invoice(
510 Extension(gateway): Extension<Arc<Gateway>>,
511 Json(payload): Json<GetInvoiceRequest>,
512) -> Result<impl IntoResponse, AdminGatewayError> {
513 let invoice = gateway.handle_get_invoice_msg(payload).await?;
514 Ok(Json(json!(invoice)))
515}
516
517#[instrument(target = LOG_GATEWAY, skip_all, err)]
518async fn list_transactions(
519 Extension(gateway): Extension<Arc<Gateway>>,
520 Json(payload): Json<ListTransactionsPayload>,
521) -> Result<impl IntoResponse, AdminGatewayError> {
522 let transactions = gateway.handle_list_transactions_msg(payload).await?;
523 Ok(Json(json!(transactions)))
524}
525
526#[instrument(target = LOG_GATEWAY, skip_all, err)]
527async fn create_offer_for_operator(
528 Extension(gateway): Extension<Arc<Gateway>>,
529 Json(payload): Json<CreateOfferPayload>,
530) -> Result<impl IntoResponse, AdminGatewayError> {
531 let offer = gateway
532 .handle_create_offer_for_operator_msg(payload)
533 .await?;
534 Ok(Json(json!(offer)))
535}
536
537#[instrument(target = LOG_GATEWAY, skip_all, err)]
538async fn pay_offer_operator(
539 Extension(gateway): Extension<Arc<Gateway>>,
540 Json(payload): Json<PayOfferPayload>,
541) -> Result<impl IntoResponse, AdminGatewayError> {
542 let response = gateway.handle_pay_offer_for_operator_msg(payload).await?;
543 Ok(Json(json!(response)))
544}