Skip to main content

devimint/
faucet.rs

1use std::str::FromStr;
2
3use axum::Router;
4use axum::extract::State;
5use axum::http::StatusCode;
6use axum::routing::{get, post};
7use fedimint_gateway_common::V1_API_ENDPOINT;
8use fedimint_ln_server::common::lightning_invoice::Bolt11Invoice;
9use tokio::net::TcpListener;
10use tower_http::cors::CorsLayer;
11
12use crate::DevFed;
13use crate::gatewayd::GatewayClient;
14
15#[derive(Clone)]
16pub struct Faucet {
17    gw_ldk: GatewayClient,
18    invite_code: String,
19}
20
21impl Faucet {
22    /// Captures what the faucet serves, so that it does not have to hold on
23    /// to the daemons themselves.
24    pub fn new(dev_fed: &DevFed) -> anyhow::Result<Self> {
25        Ok(Faucet {
26            gw_ldk: dev_fed.gw_ldk.client(),
27            invite_code: dev_fed.fed.invite_code()?,
28        })
29    }
30
31    async fn pay_invoice(&self, invoice: Bolt11Invoice) -> anyhow::Result<()> {
32        self.gw_ldk.pay_invoice(invoice).await?;
33        Ok(())
34    }
35
36    async fn generate_invoice(&self, amount: u64) -> anyhow::Result<String> {
37        Ok(self.gw_ldk.create_invoice(amount).await?.to_string())
38    }
39
40    fn invite_code(&self) -> String {
41        self.invite_code.clone()
42    }
43}
44
45/// Serves the faucet API on the already bound `listener` until the task is
46/// cancelled.
47pub async fn run(faucet: Faucet, listener: TcpListener, gw_lnd_port: u16) -> anyhow::Result<()> {
48    let router = Router::new()
49        .route(
50            "/connect-string",
51            get(|State(faucet): State<Faucet>| async move { faucet.invite_code() }),
52        )
53        .route(
54            "/pay",
55            post(|State(faucet): State<Faucet>, invoice: String| async move {
56                let invoice = Bolt11Invoice::from_str(&invoice)
57                    .map_err(|e| (StatusCode::BAD_REQUEST, format!("{e:?}")))?;
58                faucet
59                    .pay_invoice(invoice)
60                    .await
61                    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")))
62            }),
63        )
64        .route(
65            "/invoice",
66            post(|State(faucet): State<Faucet>, amt: String| async move {
67                let amt = amt
68                    .parse::<u64>()
69                    .map_err(|e| (StatusCode::BAD_REQUEST, format!("{e:?}")))?;
70                faucet
71                    .generate_invoice(amt)
72                    .await
73                    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")))
74            }),
75        )
76        .route(
77            "/gateway-api",
78            get(move || async move { format!("http://127.0.0.1:{gw_lnd_port}/{V1_API_ENDPOINT}") }),
79        )
80        .layer(CorsLayer::permissive())
81        .with_state(faucet);
82
83    axum::serve(listener, router.into_make_service()).await?;
84    Ok(())
85}