Skip to content

Commit

Permalink
feat: bolt12
Browse files Browse the repository at this point in the history
feat: mint builder
  • Loading branch information
thesimplekid committed Oct 25, 2024
1 parent 58e7226 commit 3f494bc
Show file tree
Hide file tree
Showing 62 changed files with 2,746 additions and 708 deletions.
34 changes: 32 additions & 2 deletions crates/cdk-axum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,12 @@ pub struct MintState {
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> {
pub async fn create_mint_router(
mint: Arc<Mint>,
cache_ttl: u64,
cache_tti: u64,
include_bolt12: bool,
) -> Result<Router> {
let state = MintState {
mint,
cache: Cache::builder()
Expand All @@ -142,7 +147,7 @@ pub async fn create_mint_router(mint: Arc<Mint>, cache_ttl: u64, cache_tti: u64)
.build(),
};

let v1_router = Router::new()
let mut v1_router = Router::new()
.route("/keys", get(get_keys))
.route("/keysets", get(get_keysets))
.route("/keys/:keyset_id", get(get_keyset_pubkeys))
Expand All @@ -163,7 +168,32 @@ pub async fn create_mint_router(mint: Arc<Mint>, cache_ttl: u64, cache_tti: u64)
.route("/info", get(get_mint_info))
.route("/restore", post(post_restore));

// Conditionally create and merge bolt12_router
if include_bolt12 {
let bolt12_router = create_bolt12_router(state.clone());
//v1_router = bolt12_router.merge(v1_router);
v1_router = v1_router.merge(bolt12_router);
}

// Nest the combined router under "/v1"
let mint_router = Router::new().nest("/v1", v1_router).with_state(state);

Ok(mint_router)
}

fn create_bolt12_router(state: MintState) -> Router<MintState> {
Router::new()
.route("/melt/quote/bolt12", post(get_melt_bolt12_quote))
.route(
"/melt/quote/bolt12/:quote_id",
get(get_check_melt_bolt11_quote),
)
.route("/melt/bolt12", post(post_melt_bolt12))
.route("/mint/quote/bolt12", post(get_mint_bolt12_quote))
.route(
"/mint/quote/bolt12/:quote_id",
get(get_check_mint_bolt11_quote),
)
.route("/mint/bolt12", post(post_mint_bolt12))
.with_state(state)
}
63 changes: 55 additions & 8 deletions crates/cdk-axum/src/router_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ use axum::extract::{Json, Path, State};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use cdk::error::ErrorResponse;
use cdk::nuts::nut19::{MintQuoteBolt12Request, MintQuoteBolt12Response};
use cdk::nuts::{
CheckStateRequest, CheckStateResponse, Id, KeysResponse, KeysetResponse, MeltBolt11Request,
MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintBolt11Request, MintBolt11Response,
MintInfo, MintQuoteBolt11Request, MintQuoteBolt11Response, RestoreRequest, RestoreResponse,
SwapRequest, SwapResponse,
MeltBolt12Request, MeltQuoteBolt11Request, MeltQuoteBolt11Response, MeltQuoteBolt12Request,
MintBolt11Request, MintBolt11Response, MintInfo, MintQuoteBolt11Request,
MintQuoteBolt11Response, RestoreRequest, RestoreResponse, SwapRequest, SwapResponse,
};
use cdk::util::unix_time;
use cdk::Error;
Expand Down Expand Up @@ -143,6 +144,20 @@ pub async fn get_mint_bolt11_quote(
Ok(Json(quote))
}

/// Get mint bolt12 quote
pub async fn get_mint_bolt12_quote(
State(state): State<MintState>,
Json(payload): Json<MintQuoteBolt12Request>,
) -> Result<Json<MintQuoteBolt12Response>, Response> {
let quote = state
.mint
.get_mint_bolt12_quote(payload)
.await
.map_err(into_response)?;

Ok(Json(quote))
}

#[cfg_attr(feature = "swagger", utoipa::path(
get,
context_path = "/v1",
Expand All @@ -155,8 +170,6 @@ pub async fn get_mint_bolt11_quote(
(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>,
Expand Down Expand Up @@ -205,6 +218,23 @@ pub async fn post_mint_bolt11(
Ok(Json(res))
}

/// Request a quote for melting tokens
pub async fn post_mint_bolt12(
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",
Expand All @@ -215,7 +245,6 @@ pub async fn post_mint_bolt11(
(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>,
Expand Down Expand Up @@ -277,12 +306,30 @@ pub async fn post_melt_bolt11(
State(state): State<MintState>,
Json(payload): Json<MeltBolt11Request>,
) -> Result<Json<MeltQuoteBolt11Response>, Response> {
let res = state
let res = state.mint.melt(&payload).await.map_err(into_response)?;

Ok(Json(res))
}

pub async fn get_melt_bolt12_quote(
State(state): State<MintState>,
Json(payload): Json<MeltQuoteBolt12Request>,
) -> Result<Json<MeltQuoteBolt11Response>, Response> {
let quote = state
.mint
.melt_bolt11(&payload)
.get_melt_bolt12_quote(&payload)
.await
.map_err(into_response)?;

Ok(Json(quote))
}

pub async fn post_melt_bolt12(
State(state): State<MintState>,
Json(payload): Json<MeltBolt12Request>,
) -> Result<Json<MeltQuoteBolt11Response>, Response> {
let res = state.mint.melt(&payload).await.map_err(into_response)?;

Ok(Json(res))
}

Expand Down
13 changes: 12 additions & 1 deletion crates/cdk-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ enum Commands {
MintInfo(sub_commands::mint_info::MintInfoSubcommand),
/// Mint proofs via bolt11
Mint(sub_commands::mint::MintSubCommand),
/// Remint
ReMint(sub_commands::remint_bolt12::ReMintSubCommand),
/// Burn Spent tokens
Burn(sub_commands::burn::BurnSubCommand),
/// Restore proofs from seed
Expand All @@ -83,7 +85,7 @@ enum Commands {
#[tokio::main]
async fn main() -> Result<()> {
let args: Cli = Cli::parse();
let default_filter = args.log_level;
let default_filter = "warn";

let sqlx_filter = "sqlx=warn";

Expand Down Expand Up @@ -219,5 +221,14 @@ async fn main() -> Result<()> {
Commands::CreateRequest(sub_command_args) => {
sub_commands::create_request::create_request(&multi_mint_wallet, sub_command_args).await
}
Commands::ReMint(sub_command_args) => {
sub_commands::remint_bolt12::remint(
&multi_mint_wallet,
&mnemonic.to_seed_normalized(""),
localstore,
sub_command_args,
)
.await
}
}
}
49 changes: 35 additions & 14 deletions crates/cdk-cli/src/sub_commands/melt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ use std::io::Write;
use std::str::FromStr;

use anyhow::{bail, Result};
use cdk::nuts::CurrencyUnit;
use cdk::amount::Amount;
use cdk::nuts::{CurrencyUnit, PaymentMethod};
use cdk::wallet::multi_mint_wallet::{MultiMintWallet, WalletKey};
use cdk::Bolt11Invoice;
// use cdk::Bolt11Invoice;
use clap::Args;

use crate::sub_commands::balance::mint_balances;
Expand All @@ -15,13 +16,19 @@ pub struct MeltSubCommand {
/// Currency unit e.g. sat
#[arg(default_value = "sat")]
unit: String,
/// Payment method
#[arg(short, long, default_value = "bolt11")]
method: String,
/// Amount
#[arg(short, long)]
amount: Option<u64>,
}

pub async fn pay(
multi_mint_wallet: &MultiMintWallet,
sub_command_args: &MeltSubCommand,
) -> Result<()> {
let unit = CurrencyUnit::from_str(&sub_command_args.unit)?;
let unit = CurrencyUnit::from_str(&sub_command_args.unit).unwrap();
let mints_amounts = mint_balances(multi_mint_wallet, &unit).await?;

println!("Enter mint number to melt from");
Expand All @@ -44,22 +51,36 @@ pub async fn pay(
.await
.expect("Known wallet");

println!("Enter bolt11 invoice request");
let method = PaymentMethod::from_str(&sub_command_args.method)?;
match method {
PaymentMethod::Bolt11 => {
println!("Enter bolt11 invoice request");
}
PaymentMethod::Bolt12 => {
println!("Enter bolt12 invoice request");
}
_ => panic!("Unknown payment method"),
}

let mut user_input = String::new();
let stdin = io::stdin();
io::stdout().flush().unwrap();
stdin.read_line(&mut user_input)?;
let bolt11 = Bolt11Invoice::from_str(user_input.trim())?;

if bolt11
.amount_milli_satoshis()
.unwrap()
.gt(&(<cdk::Amount as Into<u64>>::into(mints_amounts[mint_number].1) * 1000_u64))
{
bail!("Not enough funds");
}
let quote = wallet.melt_quote(bolt11.to_string(), None).await?;

let quote = match method {
PaymentMethod::Bolt11 => {
wallet
.melt_quote(user_input.trim().to_string(), None)
.await?
}
PaymentMethod::Bolt12 => {
let amount = sub_command_args.amount.map(Amount::from);
wallet
.melt_bolt12_quote(user_input.trim().to_string(), amount)
.await?
}
_ => panic!("Unsupported payment methof"),
};

println!("{:?}", quote);

Expand Down
26 changes: 21 additions & 5 deletions crates/cdk-cli/src/sub_commands/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use anyhow::Result;
use cdk::amount::SplitTarget;
use cdk::cdk_database::{Error, WalletDatabase};
use cdk::mint_url::MintUrl;
use cdk::nuts::{CurrencyUnit, MintQuoteState};
use cdk::nuts::{CurrencyUnit, MintQuoteState, PaymentMethod};
use cdk::wallet::multi_mint_wallet::WalletKey;
use cdk::wallet::{MultiMintWallet, Wallet};
use cdk::Amount;
Expand All @@ -21,8 +21,11 @@ pub struct MintSubCommand {
/// Amount
amount: u64,
/// Currency unit e.g. sat
#[arg(default_value = "sat")]
#[arg(short, long, default_value = "sat")]
unit: String,
/// Payment method
#[arg(long, default_value = "bolt11")]
method: String,
/// Quote description
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
Expand Down Expand Up @@ -51,9 +54,22 @@ pub async fn mint(
}
};

let quote = wallet
.mint_quote(Amount::from(sub_command_args.amount), description)
.await?;
let method = PaymentMethod::from_str(&sub_command_args.method)?;

let quote = match method {
PaymentMethod::Bolt11 => {
println!("Bolt11");
wallet
.mint_quote(Amount::from(sub_command_args.amount), description)
.await?
}
PaymentMethod::Bolt12 => {
wallet
.mint_bolt12_quote(Amount::from(sub_command_args.amount), description)
.await?
}
_ => panic!("Unsupported unit"),
};

println!("Quote: {:#?}", quote);

Expand Down
1 change: 1 addition & 0 deletions crates/cdk-cli/src/sub_commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod mint_info;
pub mod pay_request;
pub mod pending_mints;
pub mod receive;
pub mod remint_bolt12;
pub mod restore;
pub mod send;
pub mod update_mint_url;
54 changes: 54 additions & 0 deletions crates/cdk-cli/src/sub_commands/remint_bolt12.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use std::sync::Arc;

use anyhow::Result;
use cdk::amount::SplitTarget;
use cdk::cdk_database::{Error, WalletDatabase};
use cdk::mint_url::MintUrl;
use cdk::nuts::CurrencyUnit;
use cdk::wallet::multi_mint_wallet::WalletKey;
use cdk::wallet::{MultiMintWallet, Wallet};
use clap::Args;
use serde::{Deserialize, Serialize};

#[derive(Args, Serialize, Deserialize)]
pub struct ReMintSubCommand {
/// Mint url
mint_url: MintUrl,
#[arg(long)]
quote_id: String,
}

pub async fn remint(
multi_mint_wallet: &MultiMintWallet,
seed: &[u8],
localstore: Arc<dyn WalletDatabase<Err = Error> + Sync + Send>,
sub_command_args: &ReMintSubCommand,
) -> Result<()> {
let mint_url = sub_command_args.mint_url.clone();
let quote_id = sub_command_args.quote_id.clone();

let wallet = match multi_mint_wallet
.get_wallet(&WalletKey::new(mint_url.clone(), CurrencyUnit::Sat))
.await
{
Some(wallet) => wallet.clone(),
None => {
let wallet = Wallet::new(
&mint_url.to_string(),
CurrencyUnit::Sat,
localstore,
seed,
None,
)?;

multi_mint_wallet.add_wallet(wallet.clone()).await;
wallet
}
};

let receive_amount = wallet.mint(&quote_id, SplitTarget::default(), None).await?;

println!("Received {receive_amount} from mint {mint_url}");

Ok(())
}
Loading

0 comments on commit 3f494bc

Please sign in to comment.