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