1#![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#[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 pub struct ApiDoc;
107 };
108}
109
110#[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#[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
228pub 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 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 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
277pub 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 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}