| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 | use anyhow::Result;use axum::extract::{ws::WebSocketUpgrade, Json, Path, State};use axum::http::StatusCode;use axum::response::{IntoResponse, Response};use cdk::error::ErrorResponse;use cdk::nuts::{    CheckStateRequest, CheckStateResponse, Id, KeysResponse, KeysetResponse, MeltBolt11Request,    MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintBolt11Request, MintBolt11Response,    MintInfo, MintQuoteBolt11Request, MintQuoteBolt11Response, RestoreRequest, RestoreResponse,    SwapRequest, SwapResponse,};use cdk::util::unix_time;use cdk::Error;use paste::paste;use crate::{ws::main_websocket, MintState};macro_rules! post_cache_wrapper {    ($handler:ident, $request_type:ty, $response_type:ty) => {        paste! {            /// Cache wrapper function for $handler:            /// Wrap $handler into a function that caches responses using the request as key            pub async fn [<cache_ $handler>](                state: State<MintState>,                payload: Json<$request_type>            ) -> Result<Json<$response_type>, Response> {                let Json(json_extracted_payload) = payload.clone();                let State(mint_state) = state.clone();                let cache_key = serde_json::to_string(&json_extracted_payload).map_err(|err| {                    into_response(Error::from(err))                })?;                if let Some(cached_response) = mint_state.cache.get(&cache_key) {                    return Ok(Json(serde_json::from_str(&cached_response)                        .expect("Shouldn't panic: response is json-deserializable.")));                }                let Json(response) = $handler(state, payload).await?;                mint_state.cache.insert(cache_key, serde_json::to_string(&response)                    .expect("Shouldn't panic: response is json-serializable.")                ).await;                Ok(Json(response))            }        }    };}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);        into_response(err)    })?;    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>,) -> Result<Json<KeysResponse>, Response> {    let pubkeys = state.mint.keyset_pubkeys(&keyset_id).await.map_err(|err| {        tracing::error!("Could not get keyset pubkeys: {}", err);        into_response(err)    })?;    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 keysets = state.mint.keysets().await.map_err(|err| {        tracing::error!("Could not get keysets: {}", err);        into_response(err)    })?;    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 post_mint_bolt11_quote(    State(state): State<MintState>,    Json(payload): Json<MintQuoteBolt11Request>,) -> Result<Json<MintQuoteBolt11Response>, Response> {    let quote = state        .mint        .get_mint_bolt11_quote(payload)        .await        .map_err(into_response)?;    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>,) -> Result<Json<MintQuoteBolt11Response>, Response> {    let quote = state        .mint        .check_mint_quote("e_id)        .await        .map_err(|err| {            tracing::error!("Could not check mint quote {}: {}", quote_id, err);            into_response(err)        })?;    Ok(Json(quote))}pub async fn ws_handler(State(state): State<MintState>, ws: WebSocketUpgrade) -> impl IntoResponse {    ws.on_upgrade(|ws| main_websocket(ws, state))}/// 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`.#[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")    )))]pub async fn post_mint_bolt11(    State(state): State<MintState>,    Json(payload): Json<MintBolt11Request>,) -> Result<Json<MintBolt11Response>, Response> {    let res = state        .mint        .process_mint_request(payload)        .await        .map_err(|err| {            tracing::error!("Could not process mint: {}", err);            into_response(err)        })?;    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 tokenspub async fn post_melt_bolt11_quote(    State(state): State<MintState>,    Json(payload): Json<MeltQuoteBolt11Request>,) -> Result<Json<MeltQuoteBolt11Response>, Response> {    let quote = state        .mint        .get_melt_bolt11_quote(&payload)        .await        .map_err(into_response)?;    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>,) -> Result<Json<MeltQuoteBolt11Response>, Response> {    let quote = state        .mint        .check_melt_quote("e_id)        .await        .map_err(|err| {            tracing::error!("Could not check melt quote: {}", err);            into_response(err)        })?;    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>,) -> Result<Json<MeltQuoteBolt11Response>, Response> {    let res = state        .mint        .melt_bolt11(&payload)        .await        .map_err(into_response)?;    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>,) -> Result<Json<CheckStateResponse>, Response> {    let state = state.mint.check_state(&payload).await.map_err(|err| {        tracing::error!("Could not check state of proofs");        into_response(err)    })?;    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 infopub 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>,) -> Result<Json<SwapResponse>, Response> {    let swap_response = state        .mint        .process_swap_request(payload)        .await        .map_err(|err| {            tracing::error!("Could not process swap request: {}", err);            into_response(err)        })?;    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>,) -> Result<Json<RestoreResponse>, Response> {    let restore_response = state.mint.restore(payload).await.map_err(|err| {        tracing::error!("Could not process restore: {}", err);        into_response(err)    })?;    Ok(Json(restore_response))}pub fn into_response<T>(error: T) -> Responsewhere    T: Into<ErrorResponse>,{    (        StatusCode::INTERNAL_SERVER_ERROR,        Json::<ErrorResponse>(error.into()),    )        .into_response()}
 |