cdk_axum/
lib.rs

1//! Axum server for Mint
2
3#![doc = include_str!("../README.md")]
4#![warn(missing_docs)]
5#![warn(rustdoc::bare_urls)]
6
7use std::sync::Arc;
8
9use anyhow::Result;
10#[cfg(feature = "auth")]
11use auth::create_auth_router;
12use axum::middleware::from_fn;
13use axum::response::Response;
14use axum::routing::{get, post};
15use axum::Router;
16use cache::HttpCache;
17use cdk::mint::Mint;
18use router_handlers::*;
19
20mod metrics;
21
22#[cfg(feature = "auth")]
23mod auth;
24mod bolt12_router;
25pub mod cache;
26mod router_handlers;
27mod ws;
28
29#[cfg(feature = "swagger")]
30mod swagger_imports {
31    pub use cdk::amount::Amount;
32    pub use cdk::error::{ErrorCode, ErrorResponse};
33    pub use cdk::nuts::nut00::{
34        BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod, Proof, Witness,
35    };
36    pub use cdk::nuts::nut01::{Keys, KeysResponse, PublicKey, SecretKey};
37    pub use cdk::nuts::nut02::{KeySet, KeySetInfo, KeysetResponse};
38    pub use cdk::nuts::nut03::{SwapRequest, SwapResponse};
39    pub use cdk::nuts::nut04::{MintMethodSettings, MintRequest, MintResponse};
40    pub use cdk::nuts::nut05::{MeltMethodSettings, MeltRequest};
41    pub use cdk::nuts::nut06::{ContactInfo, MintInfo, MintVersion, Nuts, SupportedSettings};
42    pub use cdk::nuts::nut07::{CheckStateRequest, CheckStateResponse, ProofState, State};
43    pub use cdk::nuts::nut09::{RestoreRequest, RestoreResponse};
44    pub use cdk::nuts::nut11::P2PKWitness;
45    pub use cdk::nuts::nut12::{BlindSignatureDleq, ProofDleq};
46    pub use cdk::nuts::nut14::HTLCWitness;
47    pub use cdk::nuts::nut15::{Mpp, MppMethodSettings};
48    pub use cdk::nuts::nut23::{
49        MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintQuoteBolt11Request,
50        MintQuoteBolt11Response,
51    };
52    #[cfg(feature = "auth")]
53    pub use cdk::nuts::MintAuthRequest;
54    pub use cdk::nuts::{nut04, nut05, nut15, MeltQuoteState, MintQuoteState};
55}
56
57#[cfg(feature = "swagger")]
58use swagger_imports::*;
59
60use crate::bolt12_router::{
61    cache_post_melt_bolt12, cache_post_mint_bolt12, get_check_mint_bolt12_quote,
62    post_melt_bolt12_quote, post_mint_bolt12_quote,
63};
64
65/// CDK Mint State
66#[derive(Clone)]
67pub struct MintState {
68    mint: Arc<Mint>,
69    cache: Arc<cache::HttpCache>,
70}
71
72#[cfg(feature = "swagger")]
73macro_rules! define_api_doc {
74    (
75        schemas: [$($schema:ty),* $(,)?]
76        $(, auth_schemas: [$($auth_schema:ty),* $(,)?])?
77        $(, paths: [$($path:path),* $(,)?])?
78        $(, auth_paths: [$($auth_path:path),* $(,)?])?
79    ) => {
80        #[derive(utoipa::OpenApi)]
81        #[openapi(
82            components(schemas(
83                $($schema,)*
84                $($($auth_schema,)*)?
85            )),
86            info(description = "Cashu CDK mint APIs", title = "cdk-mintd"),
87            paths(
88                get_keys,
89                get_keyset_pubkeys,
90                get_keysets,
91                get_mint_info,
92                post_mint_bolt11_quote,
93                get_check_mint_bolt11_quote,
94                post_mint_bolt11,
95                post_melt_bolt11_quote,
96                get_check_melt_bolt11_quote,
97                post_melt_bolt11,
98                post_swap,
99                post_check,
100                post_restore
101                $(,$($path,)*)?
102                $(,$($auth_path,)*)?
103            )
104        )]
105        /// Swagger api docs
106        pub struct ApiDoc;
107    };
108}
109
110// Configuration without auth feature
111#[cfg(all(feature = "swagger", not(feature = "auth")))]
112define_api_doc! {
113    schemas: [
114        Amount,
115        BlindedMessage,
116        BlindSignature,
117        BlindSignatureDleq,
118        CheckStateRequest,
119        CheckStateResponse,
120        ContactInfo,
121        CurrencyUnit,
122        ErrorCode,
123        ErrorResponse,
124        HTLCWitness,
125        Keys,
126        KeysResponse,
127        KeysetResponse,
128        KeySet,
129        KeySetInfo,
130        MeltRequest<String>,
131        MeltQuoteBolt11Request,
132        MeltQuoteBolt11Response<String>,
133        MeltQuoteState,
134        MeltMethodSettings,
135        MintRequest<String>,
136        MintResponse,
137        MintInfo,
138        MintQuoteBolt11Request,
139        MintQuoteBolt11Response<String>,
140        MintQuoteState,
141        MintMethodSettings,
142        MintVersion,
143        Mpp,
144        MppMethodSettings,
145        Nuts,
146        P2PKWitness,
147        PaymentMethod,
148        Proof,
149        ProofDleq,
150        ProofState,
151        PublicKey,
152        RestoreRequest,
153        RestoreResponse,
154        SecretKey,
155        State,
156        SupportedSettings,
157        SwapRequest,
158        SwapResponse,
159        Witness,
160        nut04::Settings,
161        nut05::Settings,
162        nut15::Settings
163    ]
164}
165
166// Configuration with auth feature
167#[cfg(all(feature = "swagger", feature = "auth"))]
168define_api_doc! {
169    schemas: [
170        Amount,
171        BlindedMessage,
172        BlindSignature,
173        BlindSignatureDleq,
174        CheckStateRequest,
175        CheckStateResponse,
176        ContactInfo,
177        CurrencyUnit,
178        ErrorCode,
179        ErrorResponse,
180        HTLCWitness,
181        Keys,
182        KeysResponse,
183        KeysetResponse,
184        KeySet,
185        KeySetInfo,
186        MeltRequest<String>,
187        MeltQuoteBolt11Request,
188        MeltQuoteBolt11Response<String>,
189        MeltQuoteState,
190        MeltMethodSettings,
191        MintRequest<String>,
192        MintResponse,
193        MintInfo,
194        MintQuoteBolt11Request,
195        MintQuoteBolt11Response<String>,
196        MintQuoteState,
197        MintMethodSettings,
198        MintVersion,
199        Mpp,
200        MppMethodSettings,
201        Nuts,
202        P2PKWitness,
203        PaymentMethod,
204        Proof,
205        ProofDleq,
206        ProofState,
207        PublicKey,
208        RestoreRequest,
209        RestoreResponse,
210        SecretKey,
211        State,
212        SupportedSettings,
213        SwapRequest,
214        SwapResponse,
215        Witness,
216        nut04::Settings,
217        nut05::Settings,
218        nut15::Settings
219    ],
220    auth_schemas: [MintAuthRequest],
221    auth_paths: [
222        crate::auth::get_auth_keysets,
223        crate::auth::get_blind_auth_keys,
224        crate::auth::post_mint_auth
225    ]
226}
227
228/// Create mint [`Router`] with required endpoints for cashu mint with the default cache
229pub async fn create_mint_router(mint: Arc<Mint>, include_bolt12: bool) -> Result<Router> {
230    create_mint_router_with_custom_cache(mint, Default::default(), include_bolt12).await
231}
232
233async fn cors_middleware(
234    req: axum::http::Request<axum::body::Body>,
235    next: axum::middleware::Next,
236) -> Response {
237    #[cfg(feature = "auth")]
238    let allowed_headers = "Content-Type, Clear-auth, Blind-auth";
239    #[cfg(not(feature = "auth"))]
240    let allowed_headers = "Content-Type";
241
242    // Handle preflight requests
243    if req.method() == axum::http::Method::OPTIONS {
244        let mut response = Response::new("".into());
245        response
246            .headers_mut()
247            .insert("Access-Control-Allow-Origin", "*".parse().unwrap());
248        response.headers_mut().insert(
249            "Access-Control-Allow-Methods",
250            "GET, POST, OPTIONS".parse().unwrap(),
251        );
252        response.headers_mut().insert(
253            "Access-Control-Allow-Headers",
254            allowed_headers.parse().unwrap(),
255        );
256        return response;
257    }
258
259    // Call the next handler
260    let mut response = next.run(req).await;
261
262    response
263        .headers_mut()
264        .insert("Access-Control-Allow-Origin", "*".parse().unwrap());
265    response.headers_mut().insert(
266        "Access-Control-Allow-Methods",
267        "GET, POST, OPTIONS".parse().unwrap(),
268    );
269    response.headers_mut().insert(
270        "Access-Control-Allow-Headers",
271        allowed_headers.parse().unwrap(),
272    );
273
274    response
275}
276
277/// Create mint [`Router`] with required endpoints for cashu mint with a custom
278/// backend for cache
279pub async fn create_mint_router_with_custom_cache(
280    mint: Arc<Mint>,
281    cache: HttpCache,
282    include_bolt12: bool,
283) -> Result<Router> {
284    let state = MintState {
285        mint,
286        cache: Arc::new(cache),
287    };
288
289    let v1_router = Router::new()
290        .route("/keys", get(get_keys))
291        .route("/keysets", get(get_keysets))
292        .route("/keys/{keyset_id}", get(get_keyset_pubkeys))
293        .route("/swap", post(cache_post_swap))
294        .route("/mint/quote/bolt11", post(post_mint_bolt11_quote))
295        .route(
296            "/mint/quote/bolt11/{quote_id}",
297            get(get_check_mint_bolt11_quote),
298        )
299        .route("/mint/bolt11", post(cache_post_mint_bolt11))
300        .route("/melt/quote/bolt11", post(post_melt_bolt11_quote))
301        .route("/ws", get(ws_handler))
302        .route(
303            "/melt/quote/bolt11/{quote_id}",
304            get(get_check_melt_bolt11_quote),
305        )
306        .route("/melt/bolt11", post(cache_post_melt_bolt11))
307        .route("/checkstate", post(post_check))
308        .route("/info", get(get_mint_info))
309        .route("/restore", post(post_restore));
310
311    let mint_router = Router::new().nest("/v1", v1_router);
312
313    #[cfg(feature = "auth")]
314    let mint_router = {
315        let auth_router = create_auth_router(state.clone());
316        mint_router.nest("/v1", auth_router)
317    };
318
319    // Conditionally create and merge bolt12_router
320    let mint_router = if include_bolt12 {
321        let bolt12_router = create_bolt12_router(state.clone());
322        mint_router.nest("/v1", bolt12_router)
323    } else {
324        mint_router
325    };
326
327    #[cfg(feature = "prometheus")]
328    let mint_router = mint_router.layer(axum::middleware::from_fn_with_state(
329        state.clone(),
330        metrics::global_metrics_middleware,
331    ));
332    let mint_router = mint_router
333        .layer(from_fn(cors_middleware))
334        .with_state(state);
335
336    Ok(mint_router)
337}
338
339fn create_bolt12_router(state: MintState) -> Router<MintState> {
340    Router::new()
341        .route("/melt/quote/bolt12", post(post_melt_bolt12_quote))
342        .route(
343            "/melt/quote/bolt12/{quote_id}",
344            get(get_check_melt_bolt11_quote),
345        )
346        .route("/melt/bolt12", post(cache_post_melt_bolt12))
347        .route("/mint/quote/bolt12", post(post_mint_bolt12_quote))
348        .route(
349            "/mint/quote/bolt12/{quote_id}",
350            get(get_check_mint_bolt12_quote),
351        )
352        .route("/mint/bolt12", post(cache_post_mint_bolt12))
353        .with_state(state)
354}