use actix_web::middleware::Logger; use actix_web::web::{self, Query}; use actix_web::{get, middleware, post, App, HttpResponse, HttpServer, Responder, Result}; use cashu::keyset::{self, KeySet}; use cashu::mint::{self, Mint}; use cashu::{wallet, Amount}; use env_logger::Env; use serde::{Deserialize, Serialize}; use std::sync::Mutex; struct Tuppence { mint: Mutex, active_keyset: KeySet, } #[derive(Debug, Serialize)] struct SimpleError { error: String, } fn simple_error(e: impl std::fmt::Display) -> SimpleError { SimpleError { error: format!("{e}"), } } fn mint_error_resp(e: mint::Error) -> HttpResponse { HttpResponse::BadRequest().json(simple_error(e)) } #[get("/keys")] async fn keys(data: web::Data) -> Result { Ok(web::Json(data.active_keyset.keys.clone())) } #[get("/keys/{id}")] async fn keys_by_id(id: web::Path, data: web::Data) -> HttpResponse { let id = id.into_inner(); if id == data.active_keyset.id { return HttpResponse::Ok().json(data.active_keyset.keys.clone()); } else { match data.mint.lock() { Ok(mint) => match mint.keyset(&id) { Some(keyset) => HttpResponse::Ok().json(keyset.keys), None => HttpResponse::NotFound() .json(simple_error("Keyset Not Found")) .into(), }, Err(_) => HttpResponse::InternalServerError() .json(simple_error("Internal Server Error")) .into(), } } } #[get("/keysets")] async fn keysets(data: web::Data) -> HttpResponse { match data.mint.lock() { Ok(mint) => HttpResponse::Ok().json(mint.keysets()), Err(_) => HttpResponse::InternalServerError() .json(simple_error("Internal Server Error")) .into(), } } #[derive(Debug, Deserialize)] struct InvoiceRequestQuery { amount: Amount, } #[get("/mint")] async fn invoice_request( query: Query, data: web::Data, ) -> HttpResponse { let InvoiceRequestQuery { amount } = query.into_inner(); match data.mint.lock() { Ok(mut mint) => { let invoice = mint.process_invoice_request(amount); // TODO: This is for testing purposes only, maybe a feature flag later mint.pay_invoice(invoice.hash); HttpResponse::Ok().json(invoice) } Err(_) => HttpResponse::InternalServerError() .json(simple_error("Internal Server Error")) .into(), } } #[derive(Debug, Deserialize)] struct MintRequestQuery { hash: mint::Sha256, } #[post("/mint")] async fn mint_request( query: Query, payload: web::Json, data: web::Data, ) -> HttpResponse { let MintRequestQuery { hash } = query.into_inner(); let mint_req = payload.into_inner(); match data.mint.lock() { Ok(mut mint) => match mint.process_mint_request(hash, mint_req) { Ok(mint_response) => HttpResponse::Ok().json(mint_response), Err(e) => mint_error_resp(e), }, Err(_) => HttpResponse::InternalServerError() .json(simple_error("Internal Server Error")) .into(), } } #[post("/split")] async fn split_request( payload: web::Json, data: web::Data, ) -> HttpResponse { let split_req = payload.into_inner(); match data.mint.lock() { Ok(mut mint) => match mint.process_split_request(split_req) { Ok(split_response) => HttpResponse::Ok().json(split_response), Err(e) => mint_error_resp(e), }, Err(_) => HttpResponse::InternalServerError() .json(simple_error("Internal Server Error")) .into(), } } async fn fallback() -> HttpResponse { HttpResponse::NotFound() .json(simple_error("Not Found")) .into() } #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init_from_env(Env::default().default_filter_or("info")); let mut mint = Mint::new("muh secret", "0/0", 24); mint.rotate_keyset("muh secret", "0/0/1", 24); mint.rotate_keyset("muh secret", "0/0/2", 24); mint.rotate_keyset("muh secret", "0/0/3", 24); mint.rotate_keyset("muh secret", "0/0/4", 24); mint.rotate_keyset("muh secret", "0/0/5", 24); mint.rotate_keyset("muh secret", "0/0/6", 24); mint.rotate_keyset("muh secret", "0/0/7", 24); mint.rotate_keyset("muh secret", "0/0/8", 24); mint.rotate_keyset("muh secret", "0/0/9", 24); let active_keyset = mint.active_keyset_pubkeys(); let tuppence = web::Data::new(Tuppence { mint: Mutex::new(mint), active_keyset, }); HttpServer::new(move || { App::new() .wrap(Logger::new("%a \"%r\" %s %b %T")) .wrap(middleware::Compress::default()) .app_data(tuppence.clone()) .service(keys_by_id) .service(keys) .service(keysets) .service(invoice_request) .service(mint_request) .service(split_request) .default_service(web::route().to(fallback)) }) .bind(("127.0.0.1", 9090))? .run() .await }