diff --git a/webln-js/src/lib.rs b/webln-js/src/lib.rs index 24d8703..f47f8da 100644 --- a/webln-js/src/lib.rs +++ b/webln-js/src/lib.rs @@ -13,11 +13,13 @@ use webln::WebLN; pub mod error; pub mod get_info; pub mod keysend; +pub mod request_invoice; pub mod send_payment; use self::error::{into_err, Result}; use self::get_info::JsGetInfoResponse; use self::keysend::JsKeysendArgs; +use self::request_invoice::{JsRequestInvoiceArgs, JsRequestInvoiceResponse}; use self::send_payment::JsSendPaymentResponse; #[wasm_bindgen(start)] @@ -72,7 +74,19 @@ impl JsWebLN { .into()) } - // TODO: add `make_invoice` + /// Request that the user creates an invoice to be used by the web app + #[wasm_bindgen(js_name = makeInvoice)] + pub async fn make_invoice( + &self, + args: &JsRequestInvoiceArgs, + ) -> Result { + Ok(self + .inner + .make_invoice(args.deref()) + .await + .map_err(into_err)? + .into()) + } /// Request that the user sends a payment for an invoice. #[wasm_bindgen(js_name = sendPayment)] diff --git a/webln-js/src/request_invoice.rs b/webln-js/src/request_invoice.rs new file mode 100644 index 0000000..f9a62a8 --- /dev/null +++ b/webln-js/src/request_invoice.rs @@ -0,0 +1,79 @@ +// Copyright (c) 2024 Yuki Kishimoto +// Distributed under the MIT software license + +use std::ops::Deref; + +use wasm_bindgen::prelude::*; +use webln::{RequestInvoiceArgs, RequestInvoiceResponse}; + +#[wasm_bindgen(js_name = RequestInvoiceArgs)] +pub struct JsRequestInvoiceArgs { + inner: RequestInvoiceArgs, +} + +impl Deref for JsRequestInvoiceArgs { + type Target = RequestInvoiceArgs; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl From for JsRequestInvoiceArgs { + fn from(inner: RequestInvoiceArgs) -> Self { + Self { inner } + } +} + +#[wasm_bindgen(js_class = RequestInvoiceArgs)] +impl JsRequestInvoiceArgs { + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self { + inner: RequestInvoiceArgs::default(), + } + } + + pub fn amount(self, amount: u32) -> Self { + self.inner.amount(amount as u64).into() + } + + #[wasm_bindgen(js_name = defaultAmount)] + pub fn default_amount(self, default_amount: u32) -> Self { + self.inner.default_amount(default_amount as u64).into() + } + + #[wasm_bindgen(js_name = minimumAmount)] + pub fn minimum_amount(self, minimum_amount: u32) -> Self { + self.inner.minimum_amount(minimum_amount as u64).into() + } + + #[wasm_bindgen(js_name = maximumAmount)] + pub fn maximum_amount(self, maximum_amount: u32) -> Self { + self.inner.maximum_amount(maximum_amount as u64).into() + } + + #[wasm_bindgen(js_name = defaultMemo)] + pub fn default_memo(self, default_memo: String) -> Self { + self.inner.default_memo(default_memo).into() + } +} + +#[wasm_bindgen(js_name = RequestInvoiceResponse)] +pub struct JsRequestInvoiceResponse { + inner: RequestInvoiceResponse, +} + +impl From for JsRequestInvoiceResponse { + fn from(inner: RequestInvoiceResponse) -> Self { + Self { inner } + } +} + +#[wasm_bindgen(js_class = RequestInvoiceResponse)] +impl JsRequestInvoiceResponse { + #[wasm_bindgen(getter)] + pub fn invoice(&self) -> String { + self.inner.invoice.clone() + } +} diff --git a/webln/src/lib.rs b/webln/src/lib.rs index bfd8727..f1fd6e3 100644 --- a/webln/src/lib.rs +++ b/webln/src/lib.rs @@ -17,7 +17,7 @@ pub const IS_ENABLED: &str = "isEnabled"; pub const ENABLE: &str = "enable"; pub const GET_INFO: &str = "getInfo"; pub const KEYSEND: &str = "keysend"; -// pub const MAKE_INVOICE: &str = "makeInvoice"; +pub const MAKE_INVOICE: &str = "makeInvoice"; pub const SEND_PAYMENT: &str = "sendPayment"; pub const SEND_PAYMENT_ASYNC: &str = "sendPaymentAsync"; @@ -92,7 +92,7 @@ where ENABLE => Self::Enable, GET_INFO => Self::GetInfo, KEYSEND => Self::Keysend, - "makeInvoice" => Self::MakeInvoice, + MAKE_INVOICE => Self::MakeInvoice, SEND_PAYMENT => Self::SendPayment, SEND_PAYMENT_ASYNC => Self::SendPaymentAsync, "signMessage" => Self::SignMessage, @@ -114,7 +114,7 @@ impl fmt::Display for GetInfoMethod { Self::Enable => write!(f, "{ENABLE}"), Self::GetInfo => write!(f, "{GET_INFO}"), Self::Keysend => write!(f, "{KEYSEND}"), - Self::MakeInvoice => write!(f, "makeInvoice"), + Self::MakeInvoice => write!(f, "{MAKE_INVOICE}"), Self::SendPayment => write!(f, "{SEND_PAYMENT}"), Self::SendPaymentAsync => write!(f, "{SEND_PAYMENT_ASYNC}"), Self::SignMessage => write!(f, "signMessage"), @@ -152,6 +152,104 @@ pub struct SendPaymentResponse { pub preimage: String, } +/// Request invoice args +/// +/// **All amounts are denominated in SAT.** +#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RequestInvoiceArgs { + pub amount: Option, + pub default_amount: Option, + pub minimum_amount: Option, + pub maximum_amount: Option, + pub default_memo: Option, +} + +impl RequestInvoiceArgs { + pub fn new() -> Self { + Self::default() + } + + pub fn amount(mut self, amount: u64) -> Self { + self.amount = Some(amount); + self + } + + pub fn default_amount(mut self, default_amount: u64) -> Self { + self.default_amount = Some(default_amount); + self + } + + pub fn minimum_amount(mut self, minimum_amount: u64) -> Self { + self.minimum_amount = Some(minimum_amount); + self + } + + pub fn maximum_amount(mut self, maximum_amount: u64) -> Self { + self.maximum_amount = Some(maximum_amount); + self + } + + pub fn default_memo(mut self, default_memo: String) -> Self { + self.default_memo = Some(default_memo); + self + } +} + +impl TryFrom<&RequestInvoiceArgs> for Object { + type Error = Error; + + fn try_from(args: &RequestInvoiceArgs) -> Result { + let obj = Self::new(); + + if let Some(amount) = args.amount { + Reflect::set( + &obj, + &JsValue::from_str("amount"), + &amount.to_string().into(), + )?; + } + + if let Some(default_amount) = args.default_amount { + Reflect::set( + &obj, + &JsValue::from_str("defaultAmount"), + &default_amount.to_string().into(), + )?; + } + + if let Some(minimum_amount) = args.minimum_amount { + Reflect::set( + &obj, + &JsValue::from_str("minimumAmount"), + &minimum_amount.to_string().into(), + )?; + } + + if let Some(maximum_amount) = args.maximum_amount { + Reflect::set( + &obj, + &JsValue::from_str("maximumAmount"), + &maximum_amount.to_string().into(), + )?; + } + + if let Some(default_memo) = &args.default_memo { + Reflect::set( + &obj, + &JsValue::from_str("defaultMemo"), + &default_memo.into(), + )?; + } + + Ok(obj) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RequestInvoiceResponse { + pub invoice: String, +} + /// WebLN instance #[derive(Debug, Clone)] pub struct WebLN { @@ -268,7 +366,29 @@ impl WebLN { }) } - // TODO: add `make_invoice` + /// Request that the user creates an invoice to be used by the web app + pub async fn make_invoice( + &self, + args: &RequestInvoiceArgs, + ) -> Result { + let func: Function = self.get_func(&self.webln_obj, MAKE_INVOICE)?; + + let request_invoice_obj: Object = args.try_into()?; + + let promise: Promise = + Promise::resolve(&func.call1(&self.webln_obj, &request_invoice_obj.into())?); + let result: JsValue = JsFuture::from(promise).await?; + let request_invoice_response_obj: Object = result.dyn_into()?; + + Ok(RequestInvoiceResponse { + invoice: self + .get_value_by_key(&request_invoice_response_obj, "paymentRequest")? + .as_string() + .ok_or_else(|| { + Error::TypeMismatch(String::from("expected a string [paymentRequest]")) + })?, + }) + } /// Request that the user sends a payment for an invoice. pub async fn send_payment(&self, invoice: String) -> Result {