diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 33c9d98c83..75f67365b9 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -1,6 +1,8 @@ namespace ldk_node { Mnemonic generate_entropy_mnemonic(WordCount? word_count); Config default_config(); + // Alby: compute LSPS2 opening fees for an LSP-provided offer. + u64? lsps2_compute_opening_fee_msat(u64 payment_size_msat, LSPS2OpeningFeeParams opening_fee_params); }; dictionary Config { @@ -145,6 +147,8 @@ interface Node { OnchainPayment onchain_payment(); UnifiedQrPayment unified_qr_payment(); LSPS1Liquidity lsps1_liquidity(); + // Alby: expose the LSPS2 opening fee menu so Hub can surface JIT limits to users. + LSPS2Liquidity lsps2_liquidity(); [Throws=NodeError] void connect(PublicKey node_id, SocketAddress address, boolean persist); [Throws=NodeError] @@ -293,6 +297,28 @@ interface LSPS1Liquidity { LSPS1OrderStatus check_order_status(LSPS1OrderId order_id); }; +interface LSPS2Liquidity { + [Throws=NodeError] + // Alby: fetch the LSP's LSPS2 opening fee menu. + LSPS2GetInfoResponse request_opening_fee_params(); +}; + +dictionary LSPS2OpeningFeeParams { + u64 min_fee_msat; + u32 proportional; + LSPSDateTime valid_until; + u32 min_lifetime; + u32 max_client_to_self_delay; + u64 min_payment_size_msat; + u64 max_payment_size_msat; + string promise; +}; + +dictionary LSPS2GetInfoResponse { + // Alby: return the raw LSPS2 fee menu so clients can read min_payment_size_msat. + sequence opening_fee_params_menu; +}; + [Error] enum NodeError { "AlreadyRunning", diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 3c88a665fe..bc11833a1b 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -38,6 +38,8 @@ pub use lightning_liquidity::lsps0::ser::LSPSDateTime; pub use lightning_liquidity::lsps1::msgs::{ LSPS1ChannelInfo, LSPS1OrderId, LSPS1OrderParams, LSPS1PaymentState, }; +// Alby: expose the LSPS2 opening fee menu item type over UniFFI. +pub use lightning_liquidity::lsps2::msgs::LSPS2OpeningFeeParams; pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; pub use lightning_types::string::UntrustedString; pub use vss_client::headers::{VssHeaderProvider, VssHeaderProviderError}; @@ -49,7 +51,8 @@ pub use crate::config::{ }; use crate::error::Error; pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo}; -pub use crate::liquidity::{LSPS1OrderStatus, LSPS2ServiceConfig}; +// Alby: expose the LSPS2 get_info response over UniFFI. +pub use crate::liquidity::{LSPS1OrderStatus, LSPS2GetInfoResponse, LSPS2ServiceConfig}; pub use crate::logger::{LogLevel, LogRecord, LogWriter}; pub use crate::payment::store::{ ConfirmationStatus, LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus, diff --git a/src/lib.rs b/src/lib.rs index 254788aa8e..5f091ef27c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -132,6 +132,8 @@ use ffi::*; use gossip::GossipSource; use graph::NetworkGraph; pub use io::utils::generate_entropy_mnemonic; +// Alby: export LSPS2 opening fee computation for local fee estimation in Hub. +pub use liquidity::lsps2_compute_opening_fee_msat; use io::utils::write_node_metrics; use lightning::chain::BestBlock; use lightning::events::bump_transaction::{Input, Wallet as LdkWallet}; @@ -144,7 +146,7 @@ use lightning::ln::msgs::SocketAddress; use lightning::routing::gossip::NodeAlias; use lightning::util::persist::KVStoreSync; use lightning_background_processor::process_events_async; -use liquidity::{LSPS1Liquidity, LiquiditySource}; +use liquidity::{LSPS1Liquidity, LSPS2Liquidity, LiquiditySource}; use logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger}; use payment::asynchronous::om_mailbox::OnionMessageMailbox; use payment::asynchronous::static_invoice_store::StaticInvoiceStore; @@ -1045,6 +1047,34 @@ impl Node { )) } + /// Returns a liquidity handler allowing to query [bLIP-52 / LSPS2] JIT channel parameters. + /// + /// Alby: expose the LSPS2 opening fee menu so Hub can surface min/max payment sizes. + /// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md + #[cfg(not(feature = "uniffi"))] + pub fn lsps2_liquidity(&self) -> LSPS2Liquidity { + LSPS2Liquidity::new( + Arc::clone(&self.runtime), + Arc::clone(&self.connection_manager), + self.liquidity_source.clone(), + Arc::clone(&self.logger), + ) + } + + /// Returns a liquidity handler allowing to query [bLIP-52 / LSPS2] JIT channel parameters. + /// + /// Alby: expose the LSPS2 opening fee menu so Hub can surface min/max payment sizes. + /// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md + #[cfg(feature = "uniffi")] + pub fn lsps2_liquidity(&self) -> Arc { + Arc::new(LSPS2Liquidity::new( + Arc::clone(&self.runtime), + Arc::clone(&self.connection_manager), + self.liquidity_source.clone(), + Arc::clone(&self.logger), + )) + } + /// Retrieve a list of known channels. pub fn list_channels(&self) -> Vec { self.channel_manager.list_channels().into_iter().map(|c| c.into()).collect() diff --git a/src/liquidity.rs b/src/liquidity.rs index 74e6098ddc..b8992c5076 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -78,7 +78,7 @@ struct LSPS2Client { lsp_address: SocketAddress, token: Option, ldk_client_config: LdkLSPS2ClientConfig, - pending_fee_requests: Mutex>>, + pending_fee_requests: Mutex>>, pending_buy_requests: Mutex>>, } @@ -829,7 +829,7 @@ where if let Some(sender) = lsps2_client.pending_fee_requests.lock().unwrap().remove(&request_id) { - let response = LSPS2FeeResponse { opening_fee_params_menu }; + let response = LSPS2GetInfoResponse { opening_fee_params_menu }; match sender.send(response) { Ok(()) => (), @@ -1189,7 +1189,7 @@ where Ok((invoice, min_prop_fee_ppm_msat)) } - async fn lsps2_request_opening_fee_params(&self) -> Result { + async fn lsps2_request_opening_fee_params(&self) -> Result { let lsps2_client = self.lsps2_client.as_ref().ok_or(Error::LiquiditySourceUnavailable)?; let client_handler = self.liquidity_manager.lsps2_client_handler().ok_or_else(|| { @@ -1431,9 +1431,18 @@ type LSPS1PaymentInfo = lightning_liquidity::lsps1::msgs::LSPS1PaymentInfo; #[cfg(feature = "uniffi")] type LSPS1PaymentInfo = crate::ffi::LSPS1PaymentInfo; +/// Response to an LSPS2 `get_info` request. +/// +/// Alby: surfaced so Hub can read the LSP's min_payment_size_msat and other JIT limits. +/// +/// See [bLIP-52 / LSPS2] for more information. +/// +/// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md #[derive(Debug, Clone)] -pub(crate) struct LSPS2FeeResponse { - opening_fee_params_menu: Vec, +pub struct LSPS2GetInfoResponse { + /// Alby: the raw LSPS2 fee menu returned by the LSP. + /// The set of opening fee parameters offered by the LSP. + pub opening_fee_params_menu: Vec, } #[derive(Debug, Clone)] @@ -1446,13 +1455,13 @@ pub(crate) struct LSPS2BuyResponse { /// /// Should be retrieved by calling [`Node::lsps1_liquidity`]. /// -/// To open [bLIP-52 / LSPS2] JIT channels, please refer to -/// [`Bolt11Payment::receive_via_jit_channel`]. +/// To query [bLIP-52 / LSPS2] JIT channel fees and limits, please refer to +/// [`Node::lsps2_liquidity`]. /// /// [bLIP-51 / LSPS1]: https://github.com/lightning/blips/blob/master/blip-0051.md /// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md /// [`Node::lsps1_liquidity`]: crate::Node::lsps1_liquidity -/// [`Bolt11Payment::receive_via_jit_channel`]: crate::payment::Bolt11Payment::receive_via_jit_channel +/// [`Node::lsps2_liquidity`]: crate::Node::lsps2_liquidity #[derive(Clone)] pub struct LSPS1Liquidity { runtime: Arc, @@ -1540,3 +1549,82 @@ impl LSPS1Liquidity { Ok(response) } } + +/// A liquidity handler allowing to query [bLIP-52 / LSPS2] JIT channel parameters. +/// +/// Should be retrieved by calling [`Node::lsps2_liquidity`]. +/// +/// Alby: this provides direct access to the LSP's opening fee menu without forcing invoice +/// creation first. +/// +/// To open [bLIP-52 / LSPS2] JIT channels, please refer to +/// [`Bolt11Payment::receive_via_jit_channel`]. +/// +/// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md +/// [`Node::lsps2_liquidity`]: crate::Node::lsps2_liquidity +/// [`Bolt11Payment::receive_via_jit_channel`]: crate::payment::Bolt11Payment::receive_via_jit_channel +#[derive(Clone)] +pub struct LSPS2Liquidity { + runtime: Arc, + connection_manager: Arc>>, + liquidity_source: Option>>>, + logger: Arc, +} + +impl LSPS2Liquidity { + pub(crate) fn new( + runtime: Arc, + connection_manager: Arc>>, + liquidity_source: Option>>>, + logger: Arc, + ) -> Self { + Self { runtime, connection_manager, liquidity_source, logger } + } + + /// Connects to the configured LSP and requests its current JIT channel opening fee parameters. + /// + /// Alby: Hub uses this to display the effective LSPS2 payment-size limits to users. + /// + /// This issues an `lsps2.get_info` request and returns the LSP's `opening_fee_params_menu`. + pub fn request_opening_fee_params(&self) -> Result { + let liquidity_source = + self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?; + + let (lsp_node_id, lsp_address) = + liquidity_source.get_lsps2_lsp_details().ok_or(Error::LiquiditySourceUnavailable)?; + + let con_node_id = lsp_node_id; + let con_addr = lsp_address.clone(); + let con_cm = Arc::clone(&self.connection_manager); + + // We need to use our main runtime here as a local runtime might not be around to poll + // connection futures going forward. + self.runtime.block_on(async move { + con_cm.connect_peer_if_necessary(con_node_id, con_addr).await + })?; + + log_info!(self.logger, "Connected to LSPS2 LSP {}@{}. ", lsp_node_id, lsp_address); + + let liquidity_source = Arc::clone(&liquidity_source); + self.runtime.block_on(async move { + liquidity_source.lsps2_request_opening_fee_params().await + }) + } +} + +/// Computes the LSPS2 opening fee for a given payment size and fee parameters. +/// +/// Alby: exported for clients that want to estimate the fee for a selected LSPS2 offer. +/// +/// See [bLIP-52 / LSPS2] for more information. +/// +/// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md#computing-the-opening_fee +pub fn lsps2_compute_opening_fee_msat( + payment_size_msat: u64, opening_fee_params: LSPS2OpeningFeeParams, +) -> Option { + compute_opening_fee( + payment_size_msat, + opening_fee_params.min_fee_msat, + opening_fee_params.proportional as u64, + ) +}