Skip to content

Commit

Permalink
feat: bolt12 on fake wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
thesimplekid committed Nov 18, 2024
1 parent 1a75215 commit cd67f63
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 3 deletions.
2 changes: 2 additions & 0 deletions crates/cdk-fake-wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ description = "CDK fake ln backend"

[dependencies]
async-trait = "0.1.74"
anyhow = "1"
bitcoin = { version = "0.32.2", default-features = false }
cdk = { path = "../cdk", version = "0.4.0", default-features = false, features = ["mint"] }
futures = { version = "0.3.28", default-features = false }
Expand All @@ -22,5 +23,6 @@ serde = "1"
serde_json = "1"
uuid = { version = "1", features = ["v4"] }
lightning-invoice = { version = "0.32.0", features = ["serde", "std"] }
lightning = { version = "0.0.125", default-features = false, features = ["std"]}
tokio-stream = "0.1.15"
rand = "0.8.5"
204 changes: 204 additions & 0 deletions crates/cdk-fake-wallet/src/bolt12.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
use std::pin::Pin;
use std::str::FromStr;
use std::sync::atomic::Ordering;
use std::time::Duration;

use anyhow::anyhow;
use async_trait::async_trait;
use bitcoin::key::Secp256k1;
use cdk::amount::{to_unit, Amount};
use cdk::cdk_lightning::bolt12::{Bolt12Settings, MintBolt12Lightning};
use cdk::cdk_lightning::{
self, Bolt12PaymentQuoteResponse, CreateOfferResponse, PayInvoiceResponse, WaitInvoiceResponse,
};
use cdk::mint;
use cdk::mint::types::PaymentRequest;
use cdk::nuts::{CurrencyUnit, MeltQuoteBolt12Request};
use futures::stream::StreamExt;
use futures::Stream;
use lightning::offers::offer::{Amount as LDKAmount, Offer, OfferBuilder};
use tokio::time;
use tokio_stream::wrappers::ReceiverStream;
use uuid::Uuid;

use crate::FakeWallet;

#[async_trait]
impl MintBolt12Lightning for FakeWallet {
type Err = cdk_lightning::Error;

fn get_settings(&self) -> Bolt12Settings {
Bolt12Settings {
mint: true,
melt: true,
unit: CurrencyUnit::Sat,
offer_description: true,
}
}

fn is_wait_invoice_active(&self) -> bool {
self.wait_invoice_is_active.load(Ordering::SeqCst)
}

fn cancel_wait_invoice(&self) {
self.wait_invoice_cancel_token.cancel()
}

async fn wait_any_offer(
&self,
) -> Result<Pin<Box<dyn Stream<Item = WaitInvoiceResponse> + Send>>, Self::Err> {
let receiver = self
.bolt12_receiver
.lock()
.await
.take()
.ok_or(super::Error::NoReceiver)?;
let receiver_stream = ReceiverStream::new(receiver);
self.wait_invoice_is_active.store(true, Ordering::SeqCst);

Ok(Box::pin(receiver_stream.map(|label| WaitInvoiceResponse {
request_lookup_id: label.clone(),
payment_amount: Amount::ZERO,
unit: CurrencyUnit::Sat,
payment_id: label,
})))
}

async fn get_bolt12_payment_quote(
&self,
melt_quote_request: &MeltQuoteBolt12Request,
) -> Result<Bolt12PaymentQuoteResponse, Self::Err> {
let amount = match melt_quote_request.amount {
Some(amount) => amount,
None => {
let offer = Offer::from_str(&melt_quote_request.request)
.map_err(|_| anyhow!("Invalid offer in request"))?;

match offer.amount() {
Some(LDKAmount::Bitcoin { amount_msats }) => amount_msats.into(),
None => {
return Err(cdk_lightning::Error::Anyhow(anyhow!(
"Amount not defined in offer or request"
)))
}
_ => return Err(cdk_lightning::Error::Anyhow(anyhow!("Unsupported unit"))),
}
}
};

let relative_fee_reserve =
(self.fee_reserve.percent_fee_reserve * u64::from(amount) as f32) as u64;

let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();

let fee = match relative_fee_reserve > absolute_fee_reserve {
true => relative_fee_reserve,
false => absolute_fee_reserve,
};

Ok(Bolt12PaymentQuoteResponse {
request_lookup_id: Uuid::new_v4().to_string(),
amount,
fee: fee.into(),
state: cdk::nuts::MeltQuoteState::Unpaid,
invoice: Some("".to_string()),
})
}

async fn pay_bolt12_offer(
&self,
melt_quote: mint::MeltQuote,
_amount: Option<Amount>,
_max_fee_amount: Option<Amount>,
) -> Result<PayInvoiceResponse, Self::Err> {
let bolt12 = &match melt_quote.request {
PaymentRequest::Bolt11 { .. } => return Err(super::Error::WrongRequestType.into()),
PaymentRequest::Bolt12 { offer, invoice: _ } => offer,
};

// let description = bolt12.description().to_string();

// let status: Option<FakeInvoiceDescription> = serde_json::from_str(&description).ok();

// let mut payment_states = self.payment_states.lock().await;
// let payment_status = status
// .clone()
// .map(|s| s.pay_invoice_state)
// .unwrap_or(MeltQuoteState::Paid);

// let checkout_going_status = status
// .clone()
// .map(|s| s.check_payment_state)
// .unwrap_or(MeltQuoteState::Paid);

// payment_states.insert(payment_hash.clone(), checkout_going_status);

// if let Some(description) = status {
// if description.check_err {
// let mut fail = self.failed_payment_check.lock().await;
// fail.insert(payment_hash.clone());
// }

// if description.pay_err {
// return Err(Error::UnknownInvoice.into());
// }
// }

Ok(PayInvoiceResponse {
payment_preimage: Some("".to_string()),
payment_lookup_id: bolt12.to_string(),
status: super::MeltQuoteState::Paid,
total_spent: melt_quote.amount,
unit: melt_quote.unit,
})
}

async fn create_bolt12_offer(
&self,
amount: Option<Amount>,
unit: &CurrencyUnit,
description: String,
unix_expiry: u64,
_single_use: bool,
) -> Result<CreateOfferResponse, Self::Err> {
let secret_key = bitcoin::secp256k1::SecretKey::new(&mut rand::thread_rng());

let secp_ctx = Secp256k1::new();

let offer_builder = OfferBuilder::new(secret_key.public_key(&secp_ctx))
.description(description)
.absolute_expiry(Duration::from_secs(unix_expiry));

let offer_builder = match amount {
Some(amount) => {
let amount = to_unit(amount, unit, &CurrencyUnit::Msat)?;
offer_builder.amount_msats(amount.into())
}
None => offer_builder,
};

let offer = offer_builder.build().unwrap();

let offer_string = offer.to_string();

let sender = self.bolt12_sender.clone();

let duration = time::Duration::from_secs(self.payment_delay);

tokio::spawn(async move {
// Wait for the random delay to elapse
time::sleep(duration).await;

// Send the message after waiting for the specified duration
if sender.send(offer_string.clone()).await.is_err() {
tracing::error!("Failed to send label: {}", offer_string);
}
});

Ok(CreateOfferResponse {
request_lookup_id: offer.to_string(),
request: offer,
expiry: Some(unix_expiry),
})
}
}
6 changes: 6 additions & 0 deletions crates/cdk-fake-wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use tokio::time;
use tokio_stream::wrappers::ReceiverStream;
use tokio_util::sync::CancellationToken;

mod bolt12;
pub mod error;

/// Fake Wallet
Expand All @@ -42,6 +43,8 @@ pub struct FakeWallet {
fee_reserve: FeeReserve,
sender: tokio::sync::mpsc::Sender<String>,
receiver: Arc<Mutex<Option<tokio::sync::mpsc::Receiver<String>>>>,
bolt12_sender: tokio::sync::mpsc::Sender<String>,
bolt12_receiver: Arc<Mutex<Option<tokio::sync::mpsc::Receiver<String>>>>,
payment_states: Arc<Mutex<HashMap<String, MeltQuoteState>>>,
failed_payment_check: Arc<Mutex<HashSet<String>>>,
payment_delay: u64,
Expand All @@ -58,11 +61,14 @@ impl FakeWallet {
payment_delay: u64,
) -> Self {
let (sender, receiver) = tokio::sync::mpsc::channel(8);
let (bolt12_sender, bolt12_receiver) = tokio::sync::mpsc::channel(8);

Self {
fee_reserve,
sender,
receiver: Arc::new(Mutex::new(Some(receiver))),
bolt12_sender,
bolt12_receiver: Arc::new(Mutex::new(Some(bolt12_receiver))),
payment_states: Arc::new(Mutex::new(payment_states)),
failed_payment_check: Arc::new(Mutex::new(fail_payment_check)),
payment_delay,
Expand Down
5 changes: 2 additions & 3 deletions crates/cdk-mintd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,8 @@ async fn main() -> anyhow::Result<()> {
mint_builder =
mint_builder.add_ln_backend(unit.clone(), mint_melt_limits, fake.clone());

// TODO: Bolt12 for fake
// mint_builder =
// mint_builder.add_bolt12_ln_backend(unit, mint_melt_limits, fake.clone());
mint_builder =
mint_builder.add_bolt12_ln_backend(unit, mint_melt_limits, fake.clone());
}
}
};
Expand Down

0 comments on commit cd67f63

Please sign in to comment.