Skip to content

Commit

Permalink
feat: bdk wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
thesimplekid committed Jul 19, 2024
1 parent e92a2b1 commit b0debd0
Show file tree
Hide file tree
Showing 13 changed files with 708 additions and 16 deletions.
15 changes: 15 additions & 0 deletions crates/cdk-axum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use anyhow::Result;
use axum::routing::{get, post};
use axum::Router;
use cdk::cdk_lightning::{self, MintLightning};
use cdk::cdk_onchain::{self, MintOnChain};
use cdk::mint::Mint;
use cdk::nuts::{CurrencyUnit, PaymentMethod};
use router_handlers::*;
Expand All @@ -21,10 +22,12 @@ pub async fn create_mint_router(
mint_url: &str,
mint: Arc<Mint>,
ln: HashMap<LnKey, Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync>>,
onchain: HashMap<LnKey, Arc<dyn MintOnChain<Err = cdk_onchain::Error> + Send + Sync>>,
quote_ttl: u64,
) -> Result<Router> {
let state = MintState {
ln,
onchain,
mint,
mint_url: mint_url.to_string(),
quote_ttl,
Expand All @@ -36,16 +39,27 @@ pub async fn create_mint_router(
.route("/keys/:keyset_id", get(get_keyset_pubkeys))
.route("/swap", post(post_swap))
.route("/mint/quote/bolt11", post(get_mint_bolt11_quote))
.route("/mint/quote/onchain", post(get_mint_onchain_quote))
.route(
"/mint/quote/bolt11/:quote_id",
get(get_check_mint_bolt11_quote),
)
.route(
"/mint/quote/onchain/:quote_id",
get(get_check_mint_onchain_quote),
)
.route("/mint/bolt11", post(post_mint_bolt11))
.route("/mint/onchain", post(post_mint_onchain))
.route("/melt/quote/bolt11", post(get_melt_bolt11_quote))
.route("/melt/quote/onchain", post(get_melt_onchain_quote))
.route(
"/melt/quote/bolt11/:quote_id",
get(get_check_melt_bolt11_quote),
)
.route(
"/melt/quote/onchain/:quote_id",
get(get_check_melt_onchain_quote),
)
.route("/melt/bolt11", post(post_melt_bolt11))
.route("/checkstate", post(post_check))
.route("/info", get(get_mint_info))
Expand All @@ -59,6 +73,7 @@ pub async fn create_mint_router(
#[derive(Clone)]
struct MintState {
ln: HashMap<LnKey, Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync>>,
onchain: HashMap<LnKey, Arc<dyn MintOnChain<Err = cdk_onchain::Error> + Send + Sync>>,
mint: Arc<Mint>,
mint_url: String,
quote_ttl: u64,
Expand Down
155 changes: 155 additions & 0 deletions crates/cdk-axum/src/router_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ use axum::extract::{Json, Path, State};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use cdk::cdk_lightning::to_unit;
use cdk::cdk_onchain::AddressPaidResponse;
use cdk::error::{Error, ErrorResponse};
use cdk::nuts::nut05::MeltBolt11Response;
use cdk::nuts::nut17::{
MintBtcOnchainRequest, MintBtcOnchainResponse, MintQuoteBtcOnchainRequest,
MintQuoteBtcOnchainResponse,
};
use cdk::nuts::nut18::{MeltQuoteBtcOnchainRequest, MeltQuoteBtcOnchainResponse};
use cdk::nuts::{
CheckStateRequest, CheckStateResponse, CurrencyUnit, Id, KeysResponse, KeysetResponse,
MeltBolt11Request, MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintBolt11Request,
Expand Down Expand Up @@ -96,6 +102,45 @@ pub async fn get_mint_bolt11_quote(
Ok(Json(quote.into()))
}

pub async fn get_mint_onchain_quote(
State(state): State<MintState>,
Json(payload): Json<MintQuoteBtcOnchainRequest>,
) -> Result<Json<MintQuoteBtcOnchainResponse>, Response> {
let onchain = state
.onchain
.get(&LnKey::new(payload.unit, PaymentMethod::Bolt11))
.ok_or({
tracing::info!("Bolt11 mint request for unsupported unit");

into_response(Error::UnsupportedUnit)
})?;

let quote_expiry = unix_time() + state.quote_ttl;

let address = onchain.new_address().await.map_err(|err| {
tracing::error!("Could not create invoice: {}", err);
into_response(Error::InvalidPaymentRequest)
})?;

let quote = state
.mint
.new_mint_quote(
state.mint_url.into(),
address.clone(),
payload.unit,
payload.amount,
quote_expiry,
address,
)
.await
.map_err(|err| {
tracing::error!("Could not create new mint quote: {}", err);
into_response(err)
})?;

Ok(Json(quote.into()))
}

pub async fn get_check_mint_bolt11_quote(
State(state): State<MintState>,
Path(quote_id): Path<String>,
Expand All @@ -112,6 +157,52 @@ pub async fn get_check_mint_bolt11_quote(
Ok(Json(quote))
}

pub async fn get_check_mint_onchain_quote(
State(state): State<MintState>,
Path(quote_id): Path<String>,
) -> Result<Json<MintQuoteBtcOnchainResponse>, Response> {
let quote = state
.mint
.localstore
.get_mint_quote(&quote_id)
.await
.unwrap()
.unwrap();

let address = quote.request.clone();

let onchain = state
.onchain
.get(&LnKey::new(quote.unit, PaymentMethod::BtcOnChain))
.ok_or({
tracing::info!("Bolt11 mint request for unsupported unit");

into_response(Error::UnsupportedUnit)
})?;

let AddressPaidResponse {
amount,
max_block_height,
} = onchain.check_address_paid(&address).await.unwrap();

if amount > quote.amount && max_block_height.is_some() {
// TODO: Need to check quote not already pending
let mut quote = quote.clone();

quote.state = MintQuoteState::Paid;

state.mint.update_mint_quote(quote).await.unwrap();
}

let res = MintQuoteBtcOnchainResponse {
quote: quote.id,
address: quote.request,
state: quote.state.clone(),
};

Ok(Json(res))
}

pub async fn post_mint_bolt11(
State(state): State<MintState>,
Json(payload): Json<MintBolt11Request>,
Expand All @@ -128,6 +219,22 @@ pub async fn post_mint_bolt11(
Ok(Json(res))
}

pub async fn post_mint_onchain(
State(state): State<MintState>,
Json(payload): Json<MintBtcOnchainRequest>,
) -> Result<Json<MintBtcOnchainResponse>, Response> {
let res = state
.mint
.process_mint_onchain_request(payload)
.await
.map_err(|err| {
tracing::error!("Could not process mint: {}", err);
into_response(err)
})?;

Ok(Json(res))
}

pub async fn get_melt_bolt11_quote(
State(state): State<MintState>,
Json(payload): Json<MeltQuoteBolt11Request>,
Expand Down Expand Up @@ -183,6 +290,38 @@ pub async fn get_melt_bolt11_quote(
Ok(Json(quote.into()))
}

pub async fn get_melt_onchain_quote(
State(state): State<MintState>,
Json(payload): Json<MeltQuoteBtcOnchainRequest>,
) -> Result<Json<MeltQuoteBtcOnchainResponse>, Response> {
// Convert amount to quote unit
let amount = to_unit(payload.amount, &payload.unit, &payload.unit).map_err(|err| {
tracing::error!("Backed does not support unit: {}", err);
into_response(Error::UnsupportedUnit)
})?;

// TODO: Get real fee estimate
let fee = 3000;

let quote = state
.mint
.new_melt_quote(
payload.address.clone(),
payload.unit,
amount.into(),
fee.into(),
unix_time() + state.quote_ttl,
payload.address,
)
.await
.map_err(|err| {
tracing::error!("Could not create melt quote: {}", err);
into_response(err)
})?;

Ok(Json(quote.into()))
}

pub async fn get_check_melt_bolt11_quote(
State(state): State<MintState>,
Path(quote_id): Path<String>,
Expand All @@ -199,6 +338,22 @@ pub async fn get_check_melt_bolt11_quote(
Ok(Json(quote))
}

pub async fn get_check_melt_onchain_quote(
State(state): State<MintState>,
Path(quote_id): Path<String>,
) -> Result<Json<MeltQuoteBolt11Response>, Response> {
let quote = state
.mint
.check_melt_quote(&quote_id)
.await
.map_err(|err| {
tracing::error!("Could not check melt quote: {}", err);
into_response(err)
})?;

Ok(Json(quote))
}

pub async fn post_melt_bolt11(
State(state): State<MintState>,
Json(payload): Json<MeltBolt11Request>,
Expand Down
25 changes: 25 additions & 0 deletions crates/cdk-bdk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "cdk-bdk"
version = "0.1.0"
edition = "2021"
authors = ["CDK Developers"]
homepage.workspace = true
repository.workspace = true
rust-version.workspace = true # MSRV
license.workspace = true
description = "BDK on chain wallet for mint"

[dependencies]
anyhow.workspace = true
async-trait.workspace = true
bitcoin = "0.32.0"
bdk_chain = "0.16.0"
bdk_wallet = { version = "1.0.0-alpha.13", default-features = false, features = ["keys-bip39"] }
bdk_esplora = { version = "0.15.0", default-features = false, features = ["std", "async-https"] }
bdk_file_store = "0.13.0"
cdk = { workspace = true, default-features = false, features = ["mint"] }
futures.workspace = true
tokio.workspace = true
tracing.workspace = true
thiserror.workspace = true
uuid.workspace = true
25 changes: 25 additions & 0 deletions crates/cdk-bdk/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
/// Invoice amount not defined
#[error("Unknown invoice amount")]
UnknownInvoiceAmount,
/// Wrong CLN response
#[error("Wrong cln response")]
WrongClnResponse,
/// Unknown invoice
#[error("Unknown invoice")]
UnknownInvoice,
#[error(transparent)]
Anyhow(#[from] anyhow::Error),
/// Esplora client error
#[error(transparent)]
EsploraClient(#[from] bdk_esplora::esplora_client::Error),
}

impl From<Error> for cdk::cdk_onchain::Error {
fn from(e: Error) -> Self {
Self::Oncahin(Box::new(e))
}
}
Loading

0 comments on commit b0debd0

Please sign in to comment.