Browse Source

mintd: add utoipa swagger UI

ok300 5 months ago
parent
commit
dffc30233c

+ 2 - 0
.github/workflows/ci.yml

@@ -46,6 +46,7 @@ jobs:
             -p cdk --no-default-features,
             -p cdk --no-default-features --features wallet,
             -p cdk --no-default-features --features mint,
+            -p cdk --no-default-features --features "mint swagger",
             -p cdk-redb,
             -p cdk-sqlite,
             -p cdk-axum,
@@ -143,6 +144,7 @@ jobs:
             -p cdk --no-default-features,
             -p cdk --no-default-features --features wallet,
             -p cdk --no-default-features --features mint,
+            -p cdk --no-default-features --features "mint swagger",
             -p cdk-axum,
             -p cdk-strike,
             -p cdk-lnbits,

+ 4 - 0
crates/cdk-axum/Cargo.toml

@@ -15,7 +15,11 @@ axum = "0.6.20"
 cdk = { path = "../cdk", version = "0.4.0", default-features = false, features = ["mint"] }
 tokio = { version = "1", default-features = false }
 tracing = { version = "0.1", default-features = false, features = ["attributes", "log"] }
+utoipa = { version = "4", features = ["preserve_order", "preserve_path_order"], optional = true }
 futures = { version = "0.3.28", default-features = false }
 moka = { version = "0.11.1", features = ["future"] }
 serde_json = "1"
 paste = "1.0.15"
+
+[features]
+swagger = ["cdk/swagger", "dep:utoipa"]

+ 110 - 1
crates/cdk-axum/src/lib.rs

@@ -4,6 +4,7 @@
 #![warn(rustdoc::bare_urls)]
 
 use std::sync::Arc;
+use std::time::Duration;
 
 use anyhow::Result;
 use axum::routing::{get, post};
@@ -11,10 +12,42 @@ use axum::Router;
 use cdk::mint::Mint;
 use moka::future::Cache;
 use router_handlers::*;
-use std::time::Duration;
 
 mod router_handlers;
 
+#[cfg(feature = "swagger")]
+mod swagger_imports {
+    pub use cdk::amount::Amount;
+    pub use cdk::error::{ErrorCode, ErrorResponse};
+    pub use cdk::nuts::nut00::{
+        BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod, Proof, Witness,
+    };
+    pub use cdk::nuts::nut01::{Keys, KeysResponse, PublicKey, SecretKey};
+    pub use cdk::nuts::nut02::{Id, KeySet, KeySetInfo, KeySetVersion, KeysetResponse};
+    pub use cdk::nuts::nut03::{SwapRequest, SwapResponse};
+    pub use cdk::nuts::nut04;
+    pub use cdk::nuts::nut04::{
+        MintBolt11Request, MintBolt11Response, MintMethodSettings, MintQuoteBolt11Request,
+        MintQuoteBolt11Response,
+    };
+    pub use cdk::nuts::nut05;
+    pub use cdk::nuts::nut05::{
+        MeltBolt11Request, MeltMethodSettings, MeltQuoteBolt11Request, MeltQuoteBolt11Response,
+    };
+    pub use cdk::nuts::nut06::{ContactInfo, MintInfo, MintVersion, Nuts, SupportedSettings};
+    pub use cdk::nuts::nut07::{CheckStateRequest, CheckStateResponse, ProofState, State};
+    pub use cdk::nuts::nut09::{RestoreRequest, RestoreResponse};
+    pub use cdk::nuts::nut11::P2PKWitness;
+    pub use cdk::nuts::nut12::{BlindSignatureDleq, ProofDleq};
+    pub use cdk::nuts::nut14::HTLCWitness;
+    pub use cdk::nuts::nut15;
+    pub use cdk::nuts::nut15::{Mpp, MppMethodSettings};
+    pub use cdk::nuts::{MeltQuoteState, MintQuoteState};
+}
+
+#[cfg(feature = "swagger")]
+use swagger_imports::*;
+
 /// CDK Mint State
 #[derive(Clone)]
 pub struct MintState {
@@ -22,6 +55,82 @@ pub struct MintState {
     cache: Cache<String, String>,
 }
 
+#[cfg(feature = "swagger")]
+#[derive(utoipa::OpenApi)]
+#[openapi(
+    components(schemas(
+        Amount,
+        BlindedMessage,
+        BlindSignature,
+        BlindSignatureDleq,
+        CheckStateRequest,
+        CheckStateResponse,
+        ContactInfo,
+        CurrencyUnit,
+        ErrorCode,
+        ErrorResponse,
+        HTLCWitness,
+        Id,
+        Keys,
+        KeysResponse,
+        KeysetResponse,
+        KeySet,
+        KeySetInfo,
+        KeySetVersion,
+        MeltBolt11Request,
+        MeltQuoteBolt11Request,
+        MeltQuoteBolt11Response,
+        MeltQuoteState,
+        MeltMethodSettings,
+        MintBolt11Request,
+        MintBolt11Response,
+        MintInfo,
+        MintQuoteBolt11Request,
+        MintQuoteBolt11Response,
+        MintQuoteState,
+        MintMethodSettings,
+        MintVersion,
+        Mpp,
+        MppMethodSettings,
+        Nuts,
+        P2PKWitness,
+        PaymentMethod,
+        Proof,
+        ProofDleq,
+        ProofState,
+        PublicKey,
+        RestoreRequest,
+        RestoreResponse,
+        SecretKey,
+        State,
+        SupportedSettings,
+        SwapRequest,
+        SwapResponse,
+        Witness,
+        nut04::Settings,
+        nut05::Settings,
+        nut15::Settings
+    )),
+    info(description = "Cashu CDK mint APIs", title = "cdk-mintd",),
+    paths(
+        get_keys,
+        get_keyset_pubkeys,
+        get_keysets,
+        get_mint_info,
+        get_mint_bolt11_quote,
+        get_check_mint_bolt11_quote,
+        post_mint_bolt11,
+        get_melt_bolt11_quote,
+        get_check_melt_bolt11_quote,
+        post_melt_bolt11,
+        post_swap,
+        post_check,
+        post_restore
+    )
+)]
+/// OpenAPI spec for the mint's v1 APIs
+pub struct ApiDocV1;
+
 /// Create mint [`Router`] with required endpoints for cashu mint
 pub async fn create_mint_router(mint: Arc<Mint>, cache_ttl: u64, cache_tti: u64) -> Result<Router> {
     let state = MintState {

+ 171 - 3
crates/cdk-axum/src/router_handlers.rs

@@ -49,6 +49,17 @@ post_cache_wrapper!(post_swap, SwapRequest, SwapResponse);
 post_cache_wrapper!(post_mint_bolt11, MintBolt11Request, MintBolt11Response);
 post_cache_wrapper!(post_melt_bolt11, MeltBolt11Request, MeltQuoteBolt11Response);
 
+#[cfg_attr(feature = "swagger", utoipa::path(
+    get,
+    context_path = "/v1",
+    path = "/keys",
+    responses(
+        (status = 200, description = "Successful response", body = KeysResponse, content_type = "application/json")
+    )
+))]
+/// Get the public keys of the newest mint keyset
+///
+/// This endpoint returns a dictionary of all supported token values of the mint and their associated public key.
 pub async fn get_keys(State(state): State<MintState>) -> Result<Json<KeysResponse>, Response> {
     let pubkeys = state.mint.pubkeys().await.map_err(|err| {
         tracing::error!("Could not get keys: {}", err);
@@ -58,6 +69,21 @@ pub async fn get_keys(State(state): State<MintState>) -> Result<Json<KeysRespons
     Ok(Json(pubkeys))
 }
 
+#[cfg_attr(feature = "swagger", utoipa::path(
+    get,
+    context_path = "/v1",
+    path = "/keys/{keyset_id}",
+    params(
+        ("keyset_id" = String, description = "The keyset ID"),
+    ),
+    responses(
+        (status = 200, description = "Successful response", body = KeysResponse, content_type = "application/json"),
+        (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
+    )
+))]
+/// Get the public keys of a specific keyset
+///
+/// Get the public keys of the mint from a specific keyset ID.
 pub async fn get_keyset_pubkeys(
     State(state): State<MintState>,
     Path(keyset_id): Path<Id>,
@@ -70,15 +96,40 @@ pub async fn get_keyset_pubkeys(
     Ok(Json(pubkeys))
 }
 
+#[cfg_attr(feature = "swagger", utoipa::path(
+    get,
+    context_path = "/v1",
+    path = "/keysets",
+    responses(
+        (status = 200, description = "Successful response", body = KeysetResponse, content_type = "application/json"),
+        (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
+    )
+))]
+/// Get all active keyset IDs of the mint
+///
+/// This endpoint returns a list of keysets that the mint currently supports and will accept tokens from.
 pub async fn get_keysets(State(state): State<MintState>) -> Result<Json<KeysetResponse>, Response> {
-    let mint = state.mint.keysets().await.map_err(|err| {
-        tracing::error!("Could not get keyset: {}", err);
+    let keysets = state.mint.keysets().await.map_err(|err| {
+        tracing::error!("Could not get keysets: {}", err);
         into_response(err)
     })?;
 
-    Ok(Json(mint))
+    Ok(Json(keysets))
 }
 
+#[cfg_attr(feature = "swagger", utoipa::path(
+    post,
+    context_path = "/v1",
+    path = "/mint/quote/bolt11",
+    request_body(content = MintQuoteBolt11Request, description = "Request params", content_type = "application/json"),
+    responses(
+        (status = 200, description = "Successful response", body = MintQuoteBolt11Response, content_type = "application/json"),
+        (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
+    )
+))]
+/// Request a quote for minting of new tokens
+///
+/// Request minting of new tokens. The mint responds with a Lightning invoice. This endpoint can be used for a Lightning invoice UX flow.
 pub async fn get_mint_bolt11_quote(
     State(state): State<MintState>,
     Json(payload): Json<MintQuoteBolt11Request>,
@@ -92,6 +143,21 @@ pub async fn get_mint_bolt11_quote(
     Ok(Json(quote))
 }
 
+#[cfg_attr(feature = "swagger", utoipa::path(
+    get,
+    context_path = "/v1",
+    path = "/mint/quote/bolt11/{quote_id}",
+    params(
+        ("quote_id" = String, description = "The quote ID"),
+    ),
+    responses(
+        (status = 200, description = "Successful response", body = MintQuoteBolt11Response, content_type = "application/json"),
+        (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
+    )
+))]
+/// Get mint quote by ID
+///
+/// Get mint quote state.
 pub async fn get_check_mint_bolt11_quote(
     State(state): State<MintState>,
     Path(quote_id): Path<String>,
@@ -108,6 +174,21 @@ pub async fn get_check_mint_bolt11_quote(
     Ok(Json(quote))
 }
 
+#[cfg_attr(feature = "swagger", utoipa::path(
+    post,
+    context_path = "/v1",
+    path = "/mint/bolt11",
+    request_body(content = MintBolt11Request, description = "Request params", content_type = "application/json"),
+    responses(
+        (status = 200, description = "Successful response", body = MintBolt11Response, content_type = "application/json"),
+        (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
+    )
+))]
+/// Mint tokens by paying a BOLT11 Lightning invoice.
+///
+/// Requests the minting of tokens belonging to a paid payment request.
+///
+/// Call this endpoint after `POST /v1/mint/quote`.
 pub async fn post_mint_bolt11(
     State(state): State<MintState>,
     Json(payload): Json<MintBolt11Request>,
@@ -124,6 +205,17 @@ pub async fn post_mint_bolt11(
     Ok(Json(res))
 }
 
+#[cfg_attr(feature = "swagger", utoipa::path(
+    post,
+    context_path = "/v1",
+    path = "/melt/quote/bolt11",
+    request_body(content = MeltQuoteBolt11Request, description = "Quote params", content_type = "application/json"),
+    responses(
+        (status = 200, description = "Successful response", body = MeltQuoteBolt11Response, content_type = "application/json"),
+        (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
+    )
+))]
+/// Request a quote for melting tokens
 pub async fn get_melt_bolt11_quote(
     State(state): State<MintState>,
     Json(payload): Json<MeltQuoteBolt11Request>,
@@ -137,6 +229,21 @@ pub async fn get_melt_bolt11_quote(
     Ok(Json(quote))
 }
 
+#[cfg_attr(feature = "swagger", utoipa::path(
+    get,
+    context_path = "/v1",
+    path = "/melt/quote/bolt11/{quote_id}",
+    params(
+        ("quote_id" = String, description = "The quote ID"),
+    ),
+    responses(
+        (status = 200, description = "Successful response", body = MeltQuoteBolt11Response, content_type = "application/json"),
+        (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
+    )
+))]
+/// Get melt quote by ID
+///
+/// Get melt quote state.
 pub async fn get_check_melt_bolt11_quote(
     State(state): State<MintState>,
     Path(quote_id): Path<String>,
@@ -153,6 +260,19 @@ pub async fn get_check_melt_bolt11_quote(
     Ok(Json(quote))
 }
 
+#[cfg_attr(feature = "swagger", utoipa::path(
+    post,
+    context_path = "/v1",
+    path = "/melt/bolt11",
+    request_body(content = MeltBolt11Request, description = "Melt params", content_type = "application/json"),
+    responses(
+        (status = 200, description = "Successful response", body = MeltQuoteBolt11Response, content_type = "application/json"),
+        (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
+    )
+))]
+/// Melt tokens for a Bitcoin payment that the mint will make for the user in exchange
+///
+/// Requests tokens to be destroyed and sent out via Lightning.
 pub async fn post_melt_bolt11(
     State(state): State<MintState>,
     Json(payload): Json<MeltBolt11Request>,
@@ -166,6 +286,19 @@ pub async fn post_melt_bolt11(
     Ok(Json(res))
 }
 
+#[cfg_attr(feature = "swagger", utoipa::path(
+    post,
+    context_path = "/v1",
+    path = "/checkstate",
+    request_body(content = CheckStateRequest, description = "State params", content_type = "application/json"),
+    responses(
+        (status = 200, description = "Successful response", body = CheckStateResponse, content_type = "application/json"),
+        (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
+    )
+))]
+/// Check whether a proof is spent already or is pending in a transaction
+///
+/// Check whether a secret has been spent already or not.
 pub async fn post_check(
     State(state): State<MintState>,
     Json(payload): Json<CheckStateRequest>,
@@ -178,10 +311,34 @@ pub async fn post_check(
     Ok(Json(state))
 }
 
+#[cfg_attr(feature = "swagger", utoipa::path(
+    get,
+    context_path = "/v1",
+    path = "/info",
+    responses(
+        (status = 200, description = "Successful response", body = MintInfo)
+    )
+))]
+/// Mint information, operator contact information, and other info
 pub async fn get_mint_info(State(state): State<MintState>) -> Result<Json<MintInfo>, Response> {
     Ok(Json(state.mint.mint_info().clone().time(unix_time())))
 }
 
+#[cfg_attr(feature = "swagger", utoipa::path(
+    post,
+    context_path = "/v1",
+    path = "/swap",
+    request_body(content = SwapRequest, description = "Swap params", content_type = "application/json"),
+    responses(
+        (status = 200, description = "Successful response", body = SwapResponse, content_type = "application/json"),
+        (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
+    )
+))]
+/// Swap inputs for outputs of the same value
+///
+/// Requests a set of Proofs to be swapped for another set of BlindSignatures.
+///
+/// This endpoint can be used by Alice to swap a set of proofs before making a payment to Carol. It can then used by Carol to redeem the tokens for new proofs.
 pub async fn post_swap(
     State(state): State<MintState>,
     Json(payload): Json<SwapRequest>,
@@ -197,6 +354,17 @@ pub async fn post_swap(
     Ok(Json(swap_response))
 }
 
+#[cfg_attr(feature = "swagger", utoipa::path(
+    post,
+    context_path = "/v1",
+    path = "/restore",
+    request_body(content = RestoreRequest, description = "Restore params", content_type = "application/json"),
+    responses(
+        (status = 200, description = "Successful response", body = RestoreResponse, content_type = "application/json"),
+        (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
+    )
+))]
+/// Restores blind signature for a set of outputs.
 pub async fn post_restore(
     State(state): State<MintState>,
     Json(payload): Json<RestoreRequest>,

+ 5 - 0
crates/cdk-mintd/Cargo.toml

@@ -34,3 +34,8 @@ tower-http = { version = "0.4.4", features = ["cors"] }
 lightning-invoice = { version = "0.32.0", features = ["serde", "std"] }
 home = "0.5.5"
 url = "2.3"
+utoipa = { version = "4", optional = true }
+utoipa-swagger-ui = { version = "4", features = ["axum"], optional = true }
+
+[features]
+swagger = ["cdk-axum/swagger", "dep:utoipa", "dep:utoipa-swagger-ui"]

+ 1 - 0
crates/cdk-mintd/example.config.toml

@@ -4,6 +4,7 @@ listen_host = "127.0.0.1"
 listen_port = 8085
 mnemonic = ""
 # input_fee_ppk = 0
+# enable_swagger_ui = false
 
 
 

+ 7 - 0
crates/cdk-mintd/src/config.rs

@@ -15,6 +15,12 @@ pub struct Info {
     pub seconds_to_cache_requests_for: Option<u64>,
     pub seconds_to_extend_cache_by: Option<u64>,
     pub input_fee_ppk: Option<u64>,
+
+    /// When this is set to true, the mint exposes a Swagger UI for it's API at
+    /// `[listen_host]:[listen_port]/swagger-ui`
+    ///
+    /// This requires `mintd` was built with the `swagger` feature flag.
+    pub enable_swagger_ui: Option<bool>,
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
@@ -94,6 +100,7 @@ pub struct Database {
     pub engine: DatabaseEngine,
 }
 
+/// CDK settings, derived from `config.toml`
 #[derive(Debug, Clone, Serialize, Deserialize, Default)]
 pub struct Settings {
     pub info: Info,

+ 13 - 1
crates/cdk-mintd/src/main.rs

@@ -36,6 +36,8 @@ use tokio::sync::{Mutex, Notify};
 use tower_http::cors::CorsLayer;
 use tracing_subscriber::EnvFilter;
 use url::Url;
+#[cfg(feature = "swagger")]
+use utoipa::OpenApi;
 
 mod cli;
 mod config;
@@ -451,7 +453,7 @@ async fn main() -> anyhow::Result<()> {
 
     // Checks the status of all pending melt quotes
     // Pending melt quotes where the payment has gone through inputs are burnt
-    // Pending melt quotes where the paynment has **failed** inputs are reset to unspent
+    // Pending melt quotes where the payment has **failed** inputs are reset to unspent
     check_pending_melt_quotes(Arc::clone(&mint), &ln_backends).await?;
 
     let listen_addr = settings.info.listen_host;
@@ -475,6 +477,16 @@ async fn main() -> anyhow::Result<()> {
         .merge(v1_service)
         .layer(CorsLayer::permissive());
 
+    #[cfg(feature = "swagger")]
+    {
+        if settings.info.enable_swagger_ui.unwrap_or(false) {
+            mint_service = mint_service.merge(
+                utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
+                    .url("/api-docs/openapi.json", cdk_axum::ApiDocV1::openapi()),
+            );
+        }
+    }
+
     for router in ln_routers {
         mint_service = mint_service.merge(router);
     }

+ 3 - 8
crates/cdk-redb/src/wallet/mod.rs

@@ -649,14 +649,9 @@ impl WalletDatabase for WalletRedbDatabase {
                 let mut proof = None;
 
                 if let Ok(proof_info) = serde_json::from_str::<ProofInfo>(v.value()) {
-                    match proof_info.matches_conditions(
-                        &mint_url,
-                        &unit,
-                        &state,
-                        &spending_conditions,
-                    ) {
-                        true => proof = Some(proof_info),
-                        false => (),
+                    if proof_info.matches_conditions(&mint_url, &unit, &state, &spending_conditions)
+                    {
+                        proof = Some(proof_info)
                     }
                 }
 

+ 2 - 0
crates/cdk/Cargo.toml

@@ -13,6 +13,7 @@ license = "MIT"
 [features]
 default = ["mint", "wallet"]
 mint = ["dep:futures"]
+swagger = ["mint", "dep:utoipa"]
 wallet = ["dep:reqwest"]
 bench = []
 
@@ -39,6 +40,7 @@ tracing = { version = "0.1", default-features = false, features = ["attributes",
 thiserror = "1"
 futures = { version = "0.3.28", default-features = false, optional = true }
 url = "2.3"
+utoipa = { version = "4", optional = true }
 uuid = { version = "1", features = ["v4"] }
 
 # -Z minimal-versions

+ 1 - 0
crates/cdk/src/amount.rs

@@ -27,6 +27,7 @@ pub enum Error {
 
 /// Amount can be any unit
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 #[serde(transparent)]
 pub struct Amount(u64);
 

+ 3 - 1
crates/cdk/src/error.rs

@@ -242,8 +242,9 @@ pub enum Error {
 
 /// CDK Error Response
 ///
-/// See NUT defenation in [00](https://github.com/cashubtc/nuts/blob/main/00.md)
+/// See NUT definition in [00](https://github.com/cashubtc/nuts/blob/main/00.md)
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct ErrorResponse {
     /// Error Code
     pub code: ErrorCode,
@@ -399,6 +400,7 @@ impl From<ErrorResponse> for Error {
 
 /// Possible Error Codes
 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub enum ErrorCode {
     /// Token is already spent
     TokenAlreadySpent,

+ 10 - 0
crates/cdk/src/nuts/nut00/mod.rs

@@ -103,6 +103,7 @@ pub enum Error {
 
 /// Blinded Message (also called `output`)
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct BlindedMessage {
     /// Amount
     ///
@@ -117,6 +118,7 @@ pub struct BlindedMessage {
     ///
     /// The blinded secret message generated by the sender.
     #[serde(rename = "B_")]
+    #[cfg_attr(feature = "swagger", schema(value_type = String))]
     pub blinded_secret: PublicKey,
     /// Witness
     ///
@@ -146,6 +148,7 @@ impl BlindedMessage {
 
 /// Blind Signature (also called `promise`)
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct BlindSignature {
     /// Amount
     ///
@@ -160,6 +163,7 @@ pub struct BlindSignature {
     ///
     /// The blinded signature on the secret message `B_` of [BlindedMessage].
     #[serde(rename = "C_")]
+    #[cfg_attr(feature = "swagger", schema(value_type = String))]
     pub c: PublicKey,
     /// DLEQ Proof
     ///
@@ -183,6 +187,7 @@ impl PartialOrd for BlindSignature {
 /// Witness
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 #[serde(untagged)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub enum Witness {
     /// P2PK Witness
     #[serde(with = "serde_p2pk_witness")]
@@ -226,6 +231,7 @@ impl Witness {
 
 /// Proofs
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct Proof {
     /// Amount
     pub amount: Amount,
@@ -233,9 +239,11 @@ pub struct Proof {
     #[serde(rename = "id")]
     pub keyset_id: Id,
     /// Secret message
+    #[cfg_attr(feature = "swagger", schema(value_type = String))]
     pub secret: Secret,
     /// Unblinded signature
     #[serde(rename = "C")]
+    #[cfg_attr(feature = "swagger", schema(value_type = String))]
     pub c: PublicKey,
     /// Witness
     #[serde(skip_serializing_if = "Option::is_none")]
@@ -360,6 +368,7 @@ where
 /// Currency Unit
 #[non_exhaustive]
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub enum CurrencyUnit {
     /// Sat
     #[default]
@@ -436,6 +445,7 @@ impl<'de> Deserialize<'de> for CurrencyUnit {
 /// Payment Method
 #[non_exhaustive]
 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub enum PaymentMethod {
     /// Bolt11 payment type
     #[default]

+ 2 - 0
crates/cdk/src/nuts/nut01/mod.rs

@@ -43,6 +43,7 @@ pub enum Error {
 ///
 /// See [NUT-01]
 #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct Keys(BTreeMap<AmountStr, PublicKey>);
 
 impl From<MintKeys> for Keys {
@@ -85,6 +86,7 @@ impl Keys {
 /// Mint Public Keys [NUT-01]
 #[serde_as]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct KeysResponse {
     /// Keysets
     #[serde_as(as = "VecSkipError<_>")]

+ 2 - 0
crates/cdk/src/nuts/nut01/public_key.rs

@@ -13,7 +13,9 @@ use crate::SECP256K1;
 
 /// PublicKey
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct PublicKey {
+    #[cfg_attr(feature = "swagger", schema(value_type = String))]
     inner: secp256k1::PublicKey,
 }
 

+ 2 - 0
crates/cdk/src/nuts/nut01/secret_key.rs

@@ -15,7 +15,9 @@ use crate::SECP256K1;
 
 /// SecretKey
 #[derive(Debug, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct SecretKey {
+    #[cfg_attr(feature = "swagger", schema(value_type = String))]
     inner: secp256k1::SecretKey,
 }
 

+ 5 - 0
crates/cdk/src/nuts/nut02.rs

@@ -53,6 +53,7 @@ pub enum Error {
 
 /// Keyset version
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub enum KeySetVersion {
     /// Current Version 00
     Version00,
@@ -88,6 +89,7 @@ impl fmt::Display for KeySetVersion {
 /// be stored in a Cashu token such that the token can be used to identify
 /// which mint or keyset it was generated from.
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct Id {
     version: KeySetVersion,
     id: [u8; Self::BYTELEN],
@@ -228,6 +230,7 @@ impl From<&Keys> for Id {
 /// Ids of mints keyset ids
 #[serde_as]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct KeysetResponse {
     /// set of public key ids that the mint generates
     #[serde_as(as = "VecSkipError<_>")]
@@ -236,6 +239,7 @@ pub struct KeysetResponse {
 
 /// Keyset
 #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct KeySet {
     /// Keyset [`Id`]
     pub id: Id,
@@ -271,6 +275,7 @@ impl From<MintKeySet> for KeySet {
 
 /// KeySetInfo
 #[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct KeySetInfo {
     /// Keyset [`Id`]
     pub id: Id,

+ 3 - 0
crates/cdk/src/nuts/nut03.rs

@@ -34,8 +34,10 @@ pub struct PreSwap {
 
 /// Split Request [NUT-06]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct SwapRequest {
     /// Proofs that are to be spent in `Split`
+    #[cfg_attr(feature = "swagger", schema(value_type = Vec<Proof>))]
     pub inputs: Proofs,
     /// Blinded Messages for Mint to sign
     pub outputs: Vec<BlindedMessage>,
@@ -64,6 +66,7 @@ impl SwapRequest {
 
 /// Split Response [NUT-06]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct SwapResponse {
     /// Promises
     pub signatures: Vec<BlindSignature>,

+ 9 - 0
crates/cdk/src/nuts/nut04.rs

@@ -26,6 +26,7 @@ pub enum Error {
 
 /// Mint quote request [NUT-04]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MintQuoteBolt11Request {
     /// Amount
     pub amount: Amount,
@@ -38,6 +39,7 @@ pub struct MintQuoteBolt11Request {
 /// Possible states of a quote
 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
 #[serde(rename_all = "UPPERCASE")]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = MintQuoteState))]
 pub enum QuoteState {
     /// Quote has not been paid
     #[default]
@@ -79,6 +81,7 @@ impl FromStr for QuoteState {
 
 /// Mint quote response [NUT-04]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MintQuoteBolt11Response {
     /// Quote Id
     pub quote: String,
@@ -175,10 +178,13 @@ impl From<crate::mint::MintQuote> for MintQuoteBolt11Response {
 
 /// Mint request [NUT-04]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MintBolt11Request {
     /// Quote id
+    #[cfg_attr(feature = "swagger", schema(max_length = 1_000))]
     pub quote: String,
     /// Outputs
+    #[cfg_attr(feature = "swagger", schema(max_items = 1_000))]
     pub outputs: Vec<BlindedMessage>,
 }
 
@@ -196,6 +202,7 @@ impl MintBolt11Request {
 
 /// Mint response [NUT-04]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MintBolt11Response {
     /// Blinded Signatures
     pub signatures: Vec<BlindSignature>,
@@ -203,6 +210,7 @@ pub struct MintBolt11Response {
 
 /// Mint Method Settings
 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MintMethodSettings {
     /// Payment Method e.g. bolt11
     pub method: PaymentMethod,
@@ -221,6 +229,7 @@ pub struct MintMethodSettings {
 
 /// Mint Settings
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = nut04::Settings))]
 pub struct Settings {
     /// Methods to mint
     pub methods: Vec<MintMethodSettings>,

+ 10 - 1
crates/cdk/src/nuts/nut05.rs

@@ -13,6 +13,7 @@ use super::nut00::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod,
 use super::nut15::Mpp;
 #[cfg(feature = "mint")]
 use crate::mint;
+use crate::nuts::MeltQuoteState;
 use crate::{Amount, Bolt11Invoice};
 
 /// NUT05 Error
@@ -28,8 +29,10 @@ pub enum Error {
 
 /// Melt quote request [NUT-05]
 #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MeltQuoteBolt11Request {
     /// Bolt11 invoice to be paid
+    #[cfg_attr(feature = "swagger", schema(value_type = String))]
     pub request: Bolt11Invoice,
     /// Unit wallet would like to pay with
     pub unit: CurrencyUnit,
@@ -40,6 +43,7 @@ pub struct MeltQuoteBolt11Request {
 /// Possible states of a quote
 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
 #[serde(rename_all = "UPPERCASE")]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = MeltQuoteState))]
 pub enum QuoteState {
     /// Quote has not been paid
     #[default]
@@ -83,6 +87,7 @@ impl FromStr for QuoteState {
 
 /// Melt quote response [NUT-05]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MeltQuoteBolt11Response {
     /// Quote Id
     pub quote: String,
@@ -95,7 +100,7 @@ pub struct MeltQuoteBolt11Response {
     /// Deprecated
     pub paid: Option<bool>,
     /// Quote State
-    pub state: QuoteState,
+    pub state: MeltQuoteState,
     /// Unix timestamp until the quote is valid
     pub expiry: u64,
     /// Payment preimage
@@ -209,10 +214,12 @@ impl From<mint::MeltQuote> for MeltQuoteBolt11Response {
 
 /// Melt Bolt11 Request [NUT-05]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MeltBolt11Request {
     /// Quote ID
     pub quote: String,
     /// Proofs
+    #[cfg_attr(feature = "swagger", schema(value_type = Vec<Proof>))]
     pub inputs: Proofs,
     /// Blinded Message that can be used to return change [NUT-08]
     /// Amount field of BlindedMessages `SHOULD` be set to zero
@@ -229,6 +236,7 @@ impl MeltBolt11Request {
 
 /// Melt Method Settings
 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MeltMethodSettings {
     /// Payment Method e.g. bolt11
     pub method: PaymentMethod,
@@ -266,6 +274,7 @@ impl Settings {
 
 /// Melt Settings
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = nut05::Settings))]
 pub struct Settings {
     /// Methods to melt
     pub methods: Vec<MeltMethodSettings>,

+ 5 - 0
crates/cdk/src/nuts/nut06.rs

@@ -9,6 +9,7 @@ use super::{nut04, nut05, nut15, MppMethodSettings};
 
 /// Mint Version
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MintVersion {
     /// Mint Software name
     pub name: String,
@@ -52,6 +53,7 @@ impl<'de> Deserialize<'de> for MintVersion {
 
 /// Mint Info [NIP-06]
 #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MintInfo {
     /// name of the mint and should be recognizable
     #[serde(skip_serializing_if = "Option::is_none")]
@@ -188,6 +190,7 @@ impl MintInfo {
 
 /// Supported nuts and settings
 #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct Nuts {
     /// NUT04 Settings
     #[serde(default)]
@@ -322,12 +325,14 @@ impl Nuts {
 
 /// Check state Settings
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct SupportedSettings {
     supported: bool,
 }
 
 /// Contact Info
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct ContactInfo {
     /// Contact Method i.e. nostr
     pub method: String,

+ 7 - 1
crates/cdk/src/nuts/nut07.rs

@@ -21,6 +21,7 @@ pub enum Error {
 /// State of Proof
 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
 #[serde(rename_all = "UPPERCASE")]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub enum State {
     /// Spent
     Spent,
@@ -63,19 +64,23 @@ impl FromStr for State {
     }
 }
 
-/// Check spendabale request [NUT-07]
+/// Check spendable request [NUT-07]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct CheckStateRequest {
     /// Y's of the proofs to check
     #[serde(rename = "Ys")]
+    #[cfg_attr(feature = "swagger", schema(value_type = Vec<String>, max_items = 1_000))]
     pub ys: Vec<PublicKey>,
 }
 
 /// Proof state [NUT-07]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct ProofState {
     /// Y of proof
     #[serde(rename = "Y")]
+    #[cfg_attr(feature = "swagger", schema(value_type = String))]
     pub y: PublicKey,
     /// State of proof
     pub state: State,
@@ -85,6 +90,7 @@ pub struct ProofState {
 
 /// Check Spendable Response [NUT-07]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct CheckStateResponse {
     /// Proof states
     pub states: Vec<ProofState>,

+ 2 - 0
crates/cdk/src/nuts/nut09.rs

@@ -8,6 +8,7 @@ use super::nut00::{BlindSignature, BlindedMessage};
 
 /// Restore Request [NUT-09]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct RestoreRequest {
     /// Outputs
     pub outputs: Vec<BlindedMessage>,
@@ -15,6 +16,7 @@ pub struct RestoreRequest {
 
 /// Restore Response [NUT-09]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct RestoreResponse {
     /// Outputs
     pub outputs: Vec<BlindedMessage>,

+ 1 - 0
crates/cdk/src/nuts/nut11/mod.rs

@@ -88,6 +88,7 @@ pub enum Error {
 
 /// P2Pk Witness
 #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct P2PKWitness {
     /// Signatures
     pub signatures: Vec<String>,

+ 7 - 0
crates/cdk/src/nuts/nut12.rs

@@ -41,10 +41,13 @@ pub enum Error {
 ///
 /// Defined in [NUT12](https://github.com/cashubtc/nuts/blob/main/12.md)
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct BlindSignatureDleq {
     /// e
+    #[cfg_attr(feature = "swagger", schema(value_type = String))]
     pub e: SecretKey,
     /// s
+    #[cfg_attr(feature = "swagger", schema(value_type = String))]
     pub s: SecretKey,
 }
 
@@ -52,12 +55,16 @@ pub struct BlindSignatureDleq {
 ///
 /// Defined in [NUT12](https://github.com/cashubtc/nuts/blob/main/12.md)
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct ProofDleq {
     /// e
+    #[cfg_attr(feature = "swagger", schema(value_type = String))]
     pub e: SecretKey,
     /// s
+    #[cfg_attr(feature = "swagger", schema(value_type = String))]
     pub s: SecretKey,
     /// Blinding factor
+    #[cfg_attr(feature = "swagger", schema(value_type = String))]
     pub r: SecretKey,
 }
 

+ 1 - 0
crates/cdk/src/nuts/nut14/mod.rs

@@ -52,6 +52,7 @@ pub enum Error {
 
 /// HTLC Witness
 #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct HTLCWitness {
     /// Primage
     pub preimage: String,

+ 3 - 0
crates/cdk/src/nuts/nut15.rs

@@ -10,6 +10,7 @@ use crate::Amount;
 /// Multi-part payment
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
 #[serde(rename = "lowercase")]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct Mpp {
     /// Amount
     pub amount: Amount,
@@ -17,6 +18,7 @@ pub struct Mpp {
 
 /// Mpp Method Settings
 #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
 pub struct MppMethodSettings {
     /// Payment Method e.g. bolt11
     pub method: PaymentMethod,
@@ -28,6 +30,7 @@ pub struct MppMethodSettings {
 
 /// Mpp Settings
 #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = nut15::Settings))]
 pub struct Settings {
     /// Method settings
     pub methods: Vec<MppMethodSettings>,

+ 1 - 1
flake.nix

@@ -135,7 +135,7 @@
               cargo update -p serde_with --precise 3.1.0
               cargo update -p regex --precise 1.9.6
               cargo update -p backtrace --precise 0.3.58
-              # For wasm32-unknown-unknown target 
+              # For wasm32-unknown-unknown target
               cargo update -p bumpalo --precise 3.12.0
               cargo update -p moka --precise 0.11.1
               cargo update -p triomphe --precise 0.1.11