From b22f567f39348cadb3e6e53194a8f5d805f9a203 Mon Sep 17 00:00:00 2001 From: Fernando Ledesma Date: Thu, 24 Sep 2026 18:37:58 -0500 Subject: [PATCH] Add BOLT11 JIT hold-invoice RPCs. Wrap ldk-node receive_via_jit_channel_for_hash and receive_variable_amount_via_jit_channel_for_hash as two new RPCs. --- docs/api-guide.md | 4 +- docs/configuration.md | 4 +- e2e-tests/src/lib.rs | 17 ++ e2e-tests/tests/e2e.rs | 176 ++++++++++++++++++ ldk-server-cli/src/main.rs | 85 ++++++++- ldk-server-client/src/client.rs | 55 ++++-- ldk-server-grpc/src/api.rs | 84 ++++++++- ldk-server-grpc/src/endpoints.rs | 3 + ldk-server-grpc/src/events.rs | 2 +- ldk-server-grpc/src/proto/api.proto | 67 ++++++- ldk-server-grpc/src/proto/events.proto | 2 +- ldk-server-mcp/src/tools/handlers.rs | 29 ++- ldk-server-mcp/src/tools/mod.rs | 16 ++ ldk-server-mcp/src/tools/schema.rs | 48 +++++ ldk-server-mcp/tests/integration.rs | 27 ++- .../src/api/bolt11_receive_via_jit_channel.rs | 50 ++++- ldk-server/src/macaroons/authorization.rs | 35 ++-- ldk-server/src/service.rs | 39 +++- 18 files changed, 687 insertions(+), 56 deletions(-) diff --git a/docs/api-guide.md b/docs/api-guide.md index 5621f15e..3d9c2554 100644 --- a/docs/api-guide.md +++ b/docs/api-guide.md @@ -209,6 +209,8 @@ a channel just-in-time when the invoice is paid. |--------------------------------------------|-----------------------------------------------------------| | `Bolt11ReceiveViaJitChannel` | Create a fixed-amount invoice with JIT channel opening | | `Bolt11ReceiveVariableAmountViaJitChannel` | Create a variable-amount invoice with JIT channel opening | +| `Bolt11ReceiveViaJitChannelForHash` | Create a fixed-amount invoice with JIT channel opening for a given payment hash (manual claim required) | +| `Bolt11ReceiveVariableAmountViaJitChannelForHash` | Create a variable-amount invoice with JIT channel opening for a given payment hash (manual claim required) | ### BOLT12 Offers and Refunds @@ -363,7 +365,7 @@ Hodl invoices allow you to inspect and conditionally accept incoming payments: 1. **Subscribe:** Call `SubscribeEvents` before you create or share the invoice. Events are not replayed. -2. **Create the invoice:** Generate a new payment hash. Call `Bolt11ReceiveForHash` with this hash. +2. **Create the invoice:** Generate a new payment hash. Call `Bolt11ReceiveForHash`, `Bolt11ReceiveViaJitChannelForHash` or `Bolt11ReceiveVariableAmountViaJitChannelForHash` with this hash. Never reuse a payment hash. Reuse is unsafe and can cause loss of funds. 3. **Handle each payment:** Save the payment ID from each `PaymentClaimable` event. A payer can pay the same invoice more than once. Each payment has a separate event and payment ID. diff --git a/docs/configuration.md b/docs/configuration.md index c00afebf..898c9339 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -196,8 +196,8 @@ to register several LSPs. Each LSP's supported protocols are discovered on start [bLIP-50 / LSPS0](https://github.com/lightning/blips/blob/master/blip-0050.md). LDK Server currently supports LSPS2 only, so an LSP that does not advertise it is registered but unused. -When at least one LSPS2-capable LSP is configured, the `Bolt11ReceiveViaJitChannel` and -`Bolt11ReceiveVariableAmountViaJitChannel` RPCs become available, and the cheapest fee offer +When at least one LSPS2-capable LSP is configured, the `Bolt11ReceiveViaJitChannel`, +`Bolt11ReceiveVariableAmountViaJitChannel`, `Bolt11ReceiveViaJitChannelForHash` and `Bolt11ReceiveVariableAmountViaJitChannelForHash` RPCs become available, and the cheapest fee offer across all LSPS2-capable LSPs is selected per invoice. Requires each LSP's public key and address. Some LSPs also require an authentication token. diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index 47e86409..0d776812 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -194,6 +194,7 @@ pub struct TestConfigBuilder { log: Option<(Option, String)>, tls_hosts: Option>, forwarded_payment_tracking_mode: Option, + lsps_clients: Vec<(String, String, bool)>, } impl TestConfigBuilder { @@ -217,6 +218,7 @@ impl TestConfigBuilder { log: None, tls_hosts: None, forwarded_payment_tracking_mode: None, + lsps_clients: Vec::new(), } } @@ -273,6 +275,12 @@ impl TestConfigBuilder { self } + /// Add a `[[liquidity.lsps_client]]` section. + pub fn lsps_client(mut self, node_pubkey: &str, address: &str, trust_peer_0conf: bool) -> Self { + self.lsps_clients.push((node_pubkey.to_string(), address.to_string(), trust_peer_0conf)); + self + } + /// Build the config into a TOML string. pub fn build(&self) -> String { fn toml_string_array(values: &[String]) -> String { @@ -357,6 +365,15 @@ poll_metrics_interval = 1{metrics_auth} config.push_str(&format!("\n[tls]\nhosts = {}\n", toml_string_array(hosts))); } + for (node_pubkey, address, trust_peer_0conf) in &self.lsps_clients { + config.push_str(&format!( + "\n[[liquidity.lsps_client]]\n\ + node_pubkey = \"{node_pubkey}\"\n\ + address = \"{address}\"\n\ + trust_peer_0conf = {trust_peer_0conf}\n" + )); + } + config } } diff --git a/e2e-tests/tests/e2e.rs b/e2e-tests/tests/e2e.rs index 132b08c6..f6aff0d4 100644 --- a/e2e-tests/tests/e2e.rs +++ b/e2e-tests/tests/e2e.rs @@ -1832,6 +1832,182 @@ async fn test_hodl_invoice_fail() { assert_eq!(failed.reason, Some(PaymentFailureReason::RecipientRejected as i32)); } +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_jit_hodl_invoice_claim() { + let bitcoind = TestBitcoind::new(); + + // A: normal payer node + let server_a = LdkServerHandle::start(&bitcoind).await; + + // Subscribe to events on A before any payments + let mut events_a = server_a.client().subscribe_events().await.unwrap(); + + // B: LSP node (all e2e servers include LSPS2 service config) + let server_b = LdkServerHandle::start(&bitcoind).await; + + // Open channel A -> B (1M sats, larger for JIT forwarding) + setup_funded_channel(&bitcoind, &server_a, &server_b, 1_000_000).await; + + // Fund B additionally so it can open JIT channel to C + let addr_b = server_b.client().onchain_receive(OnchainReceiveRequest {}).await.unwrap().address; + bitcoind.fund_address(&addr_b, 1.0); + mine_and_sync(&bitcoind, &[&server_a, &server_b], 6).await; + + // C: JIT client + let lsp_pubkey = server_b.node_id().to_string(); + let lsp_addr = format!("127.0.0.1:{}", server_b.p2p_port); + let server_c = LdkServerHandle::start_with_config(&bitcoind, |params| { + TestConfigBuilder::new(params).lsps_client(&lsp_pubkey, &lsp_addr, true).build() + }) + .await; + let mut events_c = server_c.client().subscribe_events().await.unwrap(); + + let preimage_bytes_1 = [43u8; 32]; + let preimage_hex_1 = preimage_bytes_1.to_lower_hex_string(); + let payment_hash_1 = sha256::Hash::hash(&preimage_bytes_1); + let payment_hash_hex_1 = payment_hash_1.to_byte_array().to_lower_hex_string(); + + let preimage_bytes_2 = [44u8; 32]; + let payment_hash_2 = sha256::Hash::hash(&preimage_bytes_2); + let payment_hash_hex_2 = payment_hash_2.to_byte_array().to_lower_hex_string(); + + // Create fixed amount hodl invoice on c + let invoice_resp_1 = run_cli( + &server_c, + &[ + "bolt11-receive-via-jit-channel-for-hash", + &payment_hash_hex_1, + "100000000msat", + "-d", + "jit hodl test", + "-e", + "3600", + ], + ); + let invoice_1 = invoice_resp_1["invoice"].as_str().unwrap(); + assert!(!invoice_1.is_empty()); + + // Create variable amount hodl invoice on c + let invoice_resp_2 = run_cli( + &server_c, + &[ + "bolt11-receive-variable-amount-via-jit-channel-for-hash", + &payment_hash_hex_2, + "-d", + "jit variable hodl test", + "-e", + "3600", + ], + ); + let invoice_2 = invoice_resp_2["invoice"].as_str().unwrap(); + assert!(!invoice_2.is_empty()); + + // Pay the hodl invoice from A + run_cli(&server_a, &["bolt11-send", invoice_1]); + + // Wait for PaymentClaimable event on C (drain other events) + let claimable = + wait_for_event(&mut events_c, |e| matches!(e, Event::PaymentClaimable(_))).await; + let Some(Event::PaymentClaimable(claimable_event)) = &claimable.event else { + panic!("expected PaymentClaimable"); + }; + assert!(claimable_event.claim_deadline.is_some()); + assert!(!claimable_event.payment_id.is_empty()); + + // Claim the payment on C + let claimable_amount = format!("{}msat", claimable_event.claimable_amount_msat); + let args: Vec<&str> = vec![ + "bolt11-claim-for-id", + &claimable_event.payment_id, + &preimage_hex_1, + "-c", + &claimable_amount, + ]; + + run_cli(&server_c, &args); + + // Wait for PaymentSuccessful on A after claim (drain other events) + let successful = + wait_for_event(&mut events_a, |e| matches!(e, Event::PaymentSuccessful(_))).await; + let Some(Event::PaymentSuccessful(event)) = &successful.event else { + panic!("expected PaymentSuccessful"); + }; + assert!(!event.payment.as_ref().unwrap().payment_id.is_empty()); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_jit_hodl_invoice_fail() { + let bitcoind = TestBitcoind::new(); + + // A: normal payer node + let server_a = LdkServerHandle::start(&bitcoind).await; + + // Subscribe to events on A before any payments + let mut events_a = server_a.client().subscribe_events().await.unwrap(); + + // B: LSP node (all e2e servers include LSPS2 service config) + let server_b = LdkServerHandle::start(&bitcoind).await; + + // Open channel A -> B (1M sats, larger for JIT forwarding) + setup_funded_channel(&bitcoind, &server_a, &server_b, 1_000_000).await; + + // Fund B additionally so it can open JIT channel to C + let addr_b = server_b.client().onchain_receive(OnchainReceiveRequest {}).await.unwrap().address; + bitcoind.fund_address(&addr_b, 1.0); + mine_and_sync(&bitcoind, &[&server_a, &server_b], 6).await; + + // C: JIT client + let lsp_pubkey = server_b.node_id().to_string(); + let lsp_addr = format!("127.0.0.1:{}", server_b.p2p_port); + let server_c = LdkServerHandle::start_with_config(&bitcoind, |params| { + TestConfigBuilder::new(params).lsps_client(&lsp_pubkey, &lsp_addr, true).build() + }) + .await; + let mut events_c = server_c.client().subscribe_events().await.unwrap(); + + let preimage_bytes = [43u8; 32]; + let payment_hash = sha256::Hash::hash(&preimage_bytes); + let payment_hash_hex = payment_hash.to_byte_array().to_lower_hex_string(); + + // Create fixed amount hodl invoice on c + let invoice_resp = run_cli( + &server_c, + &[ + "bolt11-receive-via-jit-channel-for-hash", + &payment_hash_hex, + "100000000msat", + "-d", + "jit hodl test", + "-e", + "3600", + ], + ); + let invoice = invoice_resp["invoice"].as_str().unwrap(); + assert!(!invoice.is_empty()); + + // Pay the hodl invoice from A + run_cli(&server_a, &["bolt11-send", invoice]); + + // Wait for PaymentClaimable event on C (drain other events) + let claimable = + wait_for_event(&mut events_c, |e| matches!(e, Event::PaymentClaimable(_))).await; + let Some(Event::PaymentClaimable(claimable_event)) = &claimable.event else { + panic!("expected PaymentClaimable"); + }; + assert!(claimable_event.claim_deadline.is_some()); + assert!(!claimable_event.payment_id.is_empty()); + + run_cli(&server_c, &["bolt11-fail-for-id", &claimable_event.payment_id]); + + // Verify PaymentFailed on A and its failure reason. + let event_a = wait_for_event(&mut events_a, |e| matches!(e, Event::PaymentFailed(_))).await; + let Some(Event::PaymentFailed(failed)) = &event_a.event else { + panic!("expected PaymentFailed"); + }; + assert!(!failed.payment.as_ref().unwrap().payment_id.is_empty()); + assert_eq!(failed.reason, Some(PaymentFailureReason::RecipientRejected as i32)); +} + #[tokio::test] async fn test_metrics_endpoint() { let bitcoind = TestBitcoind::new(); diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index 990f3e46..69af8aa4 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -29,8 +29,12 @@ use ldk_server_client::ldk_server_grpc::api::{ onchain_send_request, open_channel_request, splice_in_request, AllFunds, Bolt11ClaimForIdRequest, Bolt11ClaimForIdResponse, Bolt11FailForIdRequest, Bolt11FailForIdResponse, Bolt11ReceiveForHashRequest, Bolt11ReceiveForHashResponse, - Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11ReceiveVariableAmountViaJitChannelRequest, - Bolt11ReceiveVariableAmountViaJitChannelResponse, Bolt11ReceiveViaJitChannelRequest, + Bolt11ReceiveRequest, Bolt11ReceiveResponse, + Bolt11ReceiveVariableAmountViaJitChannelForHashRequest, + Bolt11ReceiveVariableAmountViaJitChannelForHashResponse, + Bolt11ReceiveVariableAmountViaJitChannelRequest, + Bolt11ReceiveVariableAmountViaJitChannelResponse, Bolt11ReceiveViaJitChannelForHashRequest, + Bolt11ReceiveViaJitChannelForHashResponse, Bolt11ReceiveViaJitChannelRequest, Bolt11ReceiveViaJitChannelResponse, Bolt11SendRequest, Bolt11SendResponse, Bolt11SendUnderpayingRequest, Bolt11SendUnderpayingResponse, Bolt12CreatePayerProofRequest, Bolt12CreatePayerProofResponse, Bolt12ReceiveRefundRequest, Bolt12ReceiveRefundResponse, @@ -226,6 +230,47 @@ enum Commands { #[arg(long, help = "Maximum proportional fee the LSP may deduct in ppm-msat")] max_proportional_lsp_fee_limit_ppm_msat: Option, }, + #[command( + about = "Create a fixed-amount BOLT11 invoice to receive via an LSPS2 JIT channel for a given payment hash (manual claim required)" + )] + Bolt11ReceiveViaJitChannelForHash { + #[arg(help = "The hex-encoded 32-byte payment hash")] + payment_hash: String, + #[arg(help = "Amount to request, e.g. 50sat or 50000msat")] + amount: Amount, + #[arg(short, long, help = "Description to attach along with the invoice")] + description: Option, + #[arg( + long, + help = "SHA-256 hash of the description (hex). Use instead of description for longer text" + )] + description_hash: Option, + #[arg(short, long, help = "Invoice expiry time in seconds (default: 86400)")] + expiry_secs: Option, + #[arg( + long, + help = "Maximum total fee an LSP may deduct for opening the JIT channel, e.g. 50sat or 50000msat" + )] + max_total_lsp_fee_limit: Option, + }, + #[command( + about = "Create a variable-amount BOLT11 invoice to receive via an LSPS2 JIT channel for a given payment hash (manual claim required)" + )] + Bolt11ReceiveVariableAmountViaJitChannelForHash { + #[arg(help = "The hex-encoded 32-byte payment hash")] + payment_hash: String, + #[arg(short, long, help = "Description to attach along with the invoice")] + description: Option, + #[arg( + long, + help = "SHA-256 hash of the description (hex). Use instead of description for longer text" + )] + description_hash: Option, + #[arg(short, long, help = "Invoice expiry time in seconds (default: 86400)")] + expiry_secs: Option, + #[arg(long, help = "Maximum proportional fee the LSP may deduct in ppm-msat")] + max_proportional_lsp_fee_limit_ppm_msat: Option, + }, #[command(about = "Pay a BOLT11 invoice")] Bolt11Send { #[arg(help = "A BOLT11 invoice for a payment within the Lightning Network")] @@ -945,6 +990,42 @@ async fn main() { client.bolt11_receive_variable_amount_via_jit_channel(request).await, ); }, + Commands::Bolt11ReceiveViaJitChannelForHash { + amount, + description, + description_hash, + expiry_secs, + max_total_lsp_fee_limit, + payment_hash, + } => { + let request = Bolt11ReceiveViaJitChannelForHashRequest { + amount_msat: amount.to_msat(), + description: parse_bolt11_invoice_description(description, description_hash), + expiry_secs: expiry_secs.unwrap_or(DEFAULT_EXPIRY_SECS), + max_total_lsp_fee_limit_msat: max_total_lsp_fee_limit.map(|a| a.to_msat()), + payment_hash, + }; + handle_response_result::<_, Bolt11ReceiveViaJitChannelForHashResponse>( + client.bolt11_receive_via_jit_channel_for_hash(request).await, + ); + }, + Commands::Bolt11ReceiveVariableAmountViaJitChannelForHash { + description, + description_hash, + expiry_secs, + max_proportional_lsp_fee_limit_ppm_msat, + payment_hash, + } => { + let request = Bolt11ReceiveVariableAmountViaJitChannelForHashRequest { + description: parse_bolt11_invoice_description(description, description_hash), + expiry_secs: expiry_secs.unwrap_or(DEFAULT_EXPIRY_SECS), + max_proportional_lsp_fee_limit_ppm_msat, + payment_hash, + }; + handle_response_result::<_, Bolt11ReceiveVariableAmountViaJitChannelForHashResponse>( + client.bolt11_receive_variable_amount_via_jit_channel_for_hash(request).await, + ); + }, Commands::Bolt11Send { invoice, amount, diff --git a/ldk-server-client/src/client.rs b/ldk-server-client/src/client.rs index 87680c50..fcf7b0e6 100644 --- a/ldk-server-client/src/client.rs +++ b/ldk-server-client/src/client.rs @@ -15,8 +15,12 @@ use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; use ldk_server_grpc::api::{ Bolt11ClaimForIdRequest, Bolt11ClaimForIdResponse, Bolt11FailForIdRequest, Bolt11FailForIdResponse, Bolt11ReceiveForHashRequest, Bolt11ReceiveForHashResponse, - Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11ReceiveVariableAmountViaJitChannelRequest, - Bolt11ReceiveVariableAmountViaJitChannelResponse, Bolt11ReceiveViaJitChannelRequest, + Bolt11ReceiveRequest, Bolt11ReceiveResponse, + Bolt11ReceiveVariableAmountViaJitChannelForHashRequest, + Bolt11ReceiveVariableAmountViaJitChannelForHashResponse, + Bolt11ReceiveVariableAmountViaJitChannelRequest, + Bolt11ReceiveVariableAmountViaJitChannelResponse, Bolt11ReceiveViaJitChannelForHashRequest, + Bolt11ReceiveViaJitChannelForHashResponse, Bolt11ReceiveViaJitChannelRequest, Bolt11ReceiveViaJitChannelResponse, Bolt11SendRequest, Bolt11SendResponse, Bolt11SendUnderpayingRequest, Bolt11SendUnderpayingResponse, Bolt12CreatePayerProofRequest, Bolt12CreatePayerProofResponse, Bolt12ReceiveRefundRequest, Bolt12ReceiveRefundResponse, @@ -48,21 +52,23 @@ use ldk_server_grpc::api::{ }; use ldk_server_grpc::endpoints::{ BOLT11_CLAIM_FOR_ID_PATH, BOLT11_FAIL_FOR_ID_PATH, BOLT11_RECEIVE_FOR_HASH_PATH, - BOLT11_RECEIVE_PATH, BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH, - BOLT11_RECEIVE_VIA_JIT_CHANNEL_PATH, BOLT11_SEND_PATH, BOLT11_SEND_UNDERPAYING_PATH, - BOLT12_CREATE_PAYER_PROOF_PATH, BOLT12_RECEIVE_PATH, BOLT12_RECEIVE_REFUND_PATH, - BOLT12_SEND_PATH, BOLT12_SEND_REFUND_PATH, CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, - CREATE_MACAROON_PATH, DECODE_INVOICE_PATH, DECODE_OFFER_PATH, DISCONNECT_PEER_PATH, - EXPORT_PATHFINDING_SCORES_PATH, FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, - GET_CHANNEL_FORWARDING_STATS_PATH, GET_FORWARDED_PAYMENT_DETAILS_PATH, - GET_FORWARDED_PAYMENT_TRACKING_MODE_PATH, GET_METRICS_PATH, GET_NODE_INFO_PATH, - GET_PAYMENT_DETAILS_PATH, GET_PERMISSIONS_PATH, GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH, - GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, GRPC_SERVICE_PREFIX, LIST_CHANNELS_PATH, - LIST_CHANNEL_FORWARDING_STATS_PATH, LIST_CHANNEL_PAIR_FORWARDING_STATS_PATH, - LIST_FORWARDED_PAYMENTS_PATH, LIST_MACAROONS_PATH, LIST_PAYMENTS_PATH, LIST_PEERS_PATH, - ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, REVOKE_MACAROON_PATH, - SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH, SPONTANEOUS_SEND_PATH, - SUBSCRIBE_EVENTS_PATH, UNIFIED_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH, + BOLT11_RECEIVE_PATH, BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_FOR_HASH_PATH, + BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH, + BOLT11_RECEIVE_VIA_JIT_CHANNEL_FOR_HASH_PATH, BOLT11_RECEIVE_VIA_JIT_CHANNEL_PATH, + BOLT11_SEND_PATH, BOLT11_SEND_UNDERPAYING_PATH, BOLT12_CREATE_PAYER_PROOF_PATH, + BOLT12_RECEIVE_PATH, BOLT12_RECEIVE_REFUND_PATH, BOLT12_SEND_PATH, BOLT12_SEND_REFUND_PATH, + CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, CREATE_MACAROON_PATH, DECODE_INVOICE_PATH, + DECODE_OFFER_PATH, DISCONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH, + FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_CHANNEL_FORWARDING_STATS_PATH, + GET_FORWARDED_PAYMENT_DETAILS_PATH, GET_FORWARDED_PAYMENT_TRACKING_MODE_PATH, GET_METRICS_PATH, + GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, GET_PERMISSIONS_PATH, GRAPH_GET_CHANNEL_PATH, + GRAPH_GET_NODE_PATH, GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, GRPC_SERVICE_PREFIX, + LIST_CHANNELS_PATH, LIST_CHANNEL_FORWARDING_STATS_PATH, + LIST_CHANNEL_PAIR_FORWARDING_STATS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_MACAROONS_PATH, + LIST_PAYMENTS_PATH, LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, + OPEN_CHANNEL_PATH, REVOKE_MACAROON_PATH, SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH, + SPONTANEOUS_SEND_PATH, SUBSCRIBE_EVENTS_PATH, UNIFIED_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, + VERIFY_SIGNATURE_PATH, }; use ldk_server_grpc::events::EventEnvelope; use ldk_server_grpc::grpc::{ @@ -233,6 +239,21 @@ impl LdkServerClient { self.grpc_unary(&request, BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH).await } + /// Retrieve a new fixed-amount BOLT11 invoice for receiving via an LSPS2 JIT channel for a given payment hash. + pub async fn bolt11_receive_via_jit_channel_for_hash( + &self, request: Bolt11ReceiveViaJitChannelForHashRequest, + ) -> Result { + self.grpc_unary(&request, BOLT11_RECEIVE_VIA_JIT_CHANNEL_FOR_HASH_PATH).await + } + + /// Retrieve a new variable-amount BOLT11 invoice for receiving via an LSPS2 JIT channel for a given payment hash. + pub async fn bolt11_receive_variable_amount_via_jit_channel_for_hash( + &self, request: Bolt11ReceiveVariableAmountViaJitChannelForHashRequest, + ) -> Result { + self.grpc_unary(&request, BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_FOR_HASH_PATH) + .await + } + /// Send a payment for a BOLT11 invoice. pub async fn bolt11_send( &self, request: Bolt11SendRequest, diff --git a/ldk-server-grpc/src/api.rs b/ldk-server-grpc/src/api.rs index 070167e1..f2655e11 100644 --- a/ldk-server-grpc/src/api.rs +++ b/ldk-server-grpc/src/api.rs @@ -249,7 +249,7 @@ pub struct Bolt11ReceiveForHashResponse { pub invoice: ::prost::alloc::string::String, } /// Manually claim a payment for a given payment ID with the corresponding preimage. -/// This should be used to claim payments created via `Bolt11ReceiveForHash`. +/// This should be used to claim payments created via `Bolt11ReceiveForHash`, `Bolt11ReceiveViaJitChannelForHash` or `Bolt11ReceiveVariableAmountViaJitChannelForHash`. /// See more: #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] @@ -279,7 +279,7 @@ pub struct Bolt11ClaimForIdRequest { #[derive(Clone, PartialEq, ::prost::Message)] pub struct Bolt11ClaimForIdResponse {} /// Manually fail a payment for a given payment ID. -/// This should be used to reject payments created via `Bolt11ReceiveForHash`. +/// This should be used to reject payments created via `Bolt11ReceiveForHash`, `Bolt11ReceiveViaJitChannelForHash` or `Bolt11ReceiveVariableAmountViaJitChannelForHash`. /// See more: #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] @@ -364,6 +364,86 @@ pub struct Bolt11ReceiveVariableAmountViaJitChannelResponse { #[prost(string, tag = "1")] pub invoice: ::prost::alloc::string::String, } +/// Return a BOLT11 payable invoice for a given payment hash, received via an +/// LSPS2 just-in-time channel. +/// The inbound payment will NOT be automatically claimed upon arrival. +/// Instead, the payment will need to be manually claimed by calling `Bolt11ClaimForId` +/// or manually failed by calling `Bolt11FailForId`. +/// See more: +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr(feature = "serde", serde(default))] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Bolt11ReceiveViaJitChannelForHashRequest { + /// The amount in millisatoshi to request. + #[prost(uint64, tag = "1")] + pub amount_msat: u64, + /// An optional description to attach along with the invoice. + /// Will be set in the description field of the encoded payment request. + #[prost(message, optional, tag = "2")] + pub description: ::core::option::Option, + /// Invoice expiry time in seconds. + #[prost(uint32, tag = "3")] + pub expiry_secs: u32, + /// Optional upper bound for the total fee an LSP may deduct when opening the JIT channel. + #[prost(uint64, optional, tag = "4")] + pub max_total_lsp_fee_limit_msat: ::core::option::Option, + /// The hex-encoded 32-byte payment hash to use for the invoice. + /// Use a new payment hash for each invoice. Reuse is unsafe and can cause loss of funds. + #[prost(string, tag = "5")] + pub payment_hash: ::prost::alloc::string::String, +} +/// The response for the `Bolt11ReceiveViaJitChannelForHash` RPC. On failure, a gRPC error status is returned. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr(feature = "serde", serde(default))] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Bolt11ReceiveViaJitChannelForHashResponse { + /// An invoice for a payment within the Lightning Network. + #[prost(string, tag = "1")] + pub invoice: ::prost::alloc::string::String, +} +/// Return a variable-amount BOLT11 invoice for a given payment hash, received via an +/// LSPS2 just-in-time channel. +/// The inbound payment will NOT be automatically claimed upon arrival. +/// Instead, the payment will need to be manually claimed by calling `Bolt11ClaimForId` +/// or manually failed by calling `Bolt11FailForId`. +/// See more: +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr(feature = "serde", serde(default))] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Bolt11ReceiveVariableAmountViaJitChannelForHashRequest { + /// An optional description to attach along with the invoice. + /// Will be set in the description field of the encoded payment request. + #[prost(message, optional, tag = "1")] + pub description: ::core::option::Option, + /// Invoice expiry time in seconds. + #[prost(uint32, tag = "2")] + pub expiry_secs: u32, + /// Optional upper bound for the proportional fee, in parts-per-million millisatoshis, that an + /// LSP may deduct when opening the JIT channel. + #[prost(uint64, optional, tag = "3")] + pub max_proportional_lsp_fee_limit_ppm_msat: ::core::option::Option, + /// The hex-encoded 32-byte payment hash to use for the invoice. + /// Use a new payment hash for each invoice. Reuse is unsafe and can cause loss of funds. + #[prost(string, tag = "4")] + pub payment_hash: ::prost::alloc::string::String, +} +/// The response for the `Bolt11ReceiveVariableAmountViaJitChannelForHash` RPC. On failure, a gRPC error status is returned. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr(feature = "serde", serde(default))] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Bolt11ReceiveVariableAmountViaJitChannelForHashResponse { + /// An invoice for a payment within the Lightning Network. + #[prost(string, tag = "1")] + pub invoice: ::prost::alloc::string::String, +} /// Send a payment for a BOLT11 invoice. /// See more: #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/ldk-server-grpc/src/endpoints.rs b/ldk-server-grpc/src/endpoints.rs index 2bdb3ce0..349fc2cf 100644 --- a/ldk-server-grpc/src/endpoints.rs +++ b/ldk-server-grpc/src/endpoints.rs @@ -21,6 +21,9 @@ pub const BOLT11_FAIL_FOR_ID_PATH: &str = "Bolt11FailForId"; pub const BOLT11_RECEIVE_VIA_JIT_CHANNEL_PATH: &str = "Bolt11ReceiveViaJitChannel"; pub const BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH: &str = "Bolt11ReceiveVariableAmountViaJitChannel"; +pub const BOLT11_RECEIVE_VIA_JIT_CHANNEL_FOR_HASH_PATH: &str = "Bolt11ReceiveViaJitChannelForHash"; +pub const BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_FOR_HASH_PATH: &str = + "Bolt11ReceiveVariableAmountViaJitChannelForHash"; pub const BOLT11_SEND_PATH: &str = "Bolt11Send"; pub const BOLT11_SEND_UNDERPAYING_PATH: &str = "Bolt11SendUnderpaying"; pub const BOLT12_RECEIVE_PATH: &str = "Bolt12Receive"; diff --git a/ldk-server-grpc/src/events.rs b/ldk-server-grpc/src/events.rs index 092baf45..7a3822e2 100644 --- a/ldk-server-grpc/src/events.rs +++ b/ldk-server-grpc/src/events.rs @@ -241,7 +241,7 @@ pub struct PaymentFailed { pub reason: ::core::option::Option, } /// PaymentClaimable indicates a payment has arrived and is waiting to be manually claimed or failed. -/// This event is only emitted for payments created via `Bolt11ReceiveForHash`. +/// This event is only emitted for payments created via `Bolt11ReceiveForHash`, `Bolt11ReceiveViaJitChannelForHash` or `Bolt11ReceiveVariableAmountViaJitChannelForHash`. /// Handle every event by its payment ID before `claim_deadline`. /// The same invoice can produce more than one event. Fail unexpected duplicate or late payments. /// Delivery through SubscribeEvents is best-effort and is not replayed. If the event is missed and diff --git a/ldk-server-grpc/src/proto/api.proto b/ldk-server-grpc/src/proto/api.proto index a1b9ef7b..819acd6c 100644 --- a/ldk-server-grpc/src/proto/api.proto +++ b/ldk-server-grpc/src/proto/api.proto @@ -197,7 +197,7 @@ message Bolt11ReceiveForHashResponse { } // Manually claim a payment for a given payment ID with the corresponding preimage. -// This should be used to claim payments created via `Bolt11ReceiveForHash`. +// This should be used to claim payments created via `Bolt11ReceiveForHash`, `Bolt11ReceiveViaJitChannelForHash` or `Bolt11ReceiveVariableAmountViaJitChannelForHash`. // See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.claim_for_id message Bolt11ClaimForIdRequest { @@ -219,7 +219,7 @@ message Bolt11ClaimForIdRequest { message Bolt11ClaimForIdResponse {} // Manually fail a payment for a given payment ID. -// This should be used to reject payments created via `Bolt11ReceiveForHash`. +// This should be used to reject payments created via `Bolt11ReceiveForHash`, `Bolt11ReceiveViaJitChannelForHash` or `Bolt11ReceiveVariableAmountViaJitChannelForHash`. // See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.fail_for_id message Bolt11FailForIdRequest { @@ -280,6 +280,65 @@ message Bolt11ReceiveVariableAmountViaJitChannelResponse { string invoice = 1; } +// Return a BOLT11 payable invoice for a given payment hash, received via an +// LSPS2 just-in-time channel. +// The inbound payment will NOT be automatically claimed upon arrival. +// Instead, the payment will need to be manually claimed by calling `Bolt11ClaimForId` +// or manually failed by calling `Bolt11FailForId`. +// See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.receive_via_jit_channel_for_hash +message Bolt11ReceiveViaJitChannelForHashRequest { + // The amount in millisatoshi to request. + uint64 amount_msat = 1; + + // An optional description to attach along with the invoice. + // Will be set in the description field of the encoded payment request. + types.Bolt11InvoiceDescription description = 2; + + // Invoice expiry time in seconds. + uint32 expiry_secs = 3; + + // Optional upper bound for the total fee an LSP may deduct when opening the JIT channel. + optional uint64 max_total_lsp_fee_limit_msat = 4; + + // The hex-encoded 32-byte payment hash to use for the invoice. + // Use a new payment hash for each invoice. Reuse is unsafe and can cause loss of funds. + string payment_hash = 5; +} + +// The response for the `Bolt11ReceiveViaJitChannelForHash` RPC. On failure, a gRPC error status is returned. +message Bolt11ReceiveViaJitChannelForHashResponse { + // An invoice for a payment within the Lightning Network. + string invoice = 1; +} + +// Return a variable-amount BOLT11 invoice for a given payment hash, received via an +// LSPS2 just-in-time channel. +// The inbound payment will NOT be automatically claimed upon arrival. +// Instead, the payment will need to be manually claimed by calling `Bolt11ClaimForId` +// or manually failed by calling `Bolt11FailForId`. +// See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.receive_variable_amount_via_jit_channel_for_hash +message Bolt11ReceiveVariableAmountViaJitChannelForHashRequest { + // An optional description to attach along with the invoice. + // Will be set in the description field of the encoded payment request. + types.Bolt11InvoiceDescription description = 1; + + // Invoice expiry time in seconds. + uint32 expiry_secs = 2; + + // Optional upper bound for the proportional fee, in parts-per-million millisatoshis, that an + // LSP may deduct when opening the JIT channel. + optional uint64 max_proportional_lsp_fee_limit_ppm_msat = 3; + + // The hex-encoded 32-byte payment hash to use for the invoice. + // Use a new payment hash for each invoice. Reuse is unsafe and can cause loss of funds. + string payment_hash = 4; +} + +// The response for the `Bolt11ReceiveVariableAmountViaJitChannelForHash` RPC. On failure, a gRPC error status is returned. +message Bolt11ReceiveVariableAmountViaJitChannelForHashResponse { + // An invoice for a payment within the Lightning Network. + string invoice = 1; +} // Send a payment for a BOLT11 invoice. // See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.send @@ -1168,6 +1227,10 @@ service LightningNode { rpc Bolt11ReceiveViaJitChannel(Bolt11ReceiveViaJitChannelRequest) returns (Bolt11ReceiveViaJitChannelResponse); // Return a variable-amount BOLT11 invoice for receiving via a JIT channel. rpc Bolt11ReceiveVariableAmountViaJitChannel(Bolt11ReceiveVariableAmountViaJitChannelRequest) returns (Bolt11ReceiveVariableAmountViaJitChannelResponse); + // Return a BOLT11 invoice for a given payment hash, received via a JIT channel. + rpc Bolt11ReceiveViaJitChannelForHash(Bolt11ReceiveViaJitChannelForHashRequest) returns (Bolt11ReceiveViaJitChannelForHashResponse); + // Return a variable-amount BOLT11 invoice for a given payment hash, received via a JIT channel. + rpc Bolt11ReceiveVariableAmountViaJitChannelForHash(Bolt11ReceiveVariableAmountViaJitChannelForHashRequest) returns (Bolt11ReceiveVariableAmountViaJitChannelForHashResponse); // Send a payment for a BOLT11 invoice. rpc Bolt11Send(Bolt11SendRequest) returns (Bolt11SendResponse); // Send part of the amount for a fixed-amount BOLT11 invoice. diff --git a/ldk-server-grpc/src/proto/events.proto b/ldk-server-grpc/src/proto/events.proto index 8d77b53c..030b6f89 100644 --- a/ldk-server-grpc/src/proto/events.proto +++ b/ldk-server-grpc/src/proto/events.proto @@ -181,7 +181,7 @@ message PaymentFailed { } // PaymentClaimable indicates a payment has arrived and is waiting to be manually claimed or failed. -// This event is only emitted for payments created via `Bolt11ReceiveForHash`. +// This event is only emitted for payments created via `Bolt11ReceiveForHash`, `Bolt11ReceiveViaJitChannelForHash` or `Bolt11ReceiveVariableAmountViaJitChannelForHash`. // Handle every event by its payment ID before `claim_deadline`. // The same invoice can produce more than one event. Fail unexpected duplicate or late payments. // Delivery through SubscribeEvents is best-effort and is not replayed. If the event is missed and diff --git a/ldk-server-mcp/src/tools/handlers.rs b/ldk-server-mcp/src/tools/handlers.rs index 7ba00a80..5e1d36f1 100644 --- a/ldk-server-mcp/src/tools/handlers.rs +++ b/ldk-server-mcp/src/tools/handlers.rs @@ -11,7 +11,8 @@ use hex_conservative::DisplayHex; use ldk_server_client::client::LdkServerClient; use ldk_server_client::ldk_server_grpc::api::{ Bolt11ClaimForIdRequest, Bolt11FailForIdRequest, Bolt11ReceiveForHashRequest, - Bolt11ReceiveRequest, Bolt11ReceiveVariableAmountViaJitChannelRequest, + Bolt11ReceiveRequest, Bolt11ReceiveVariableAmountViaJitChannelForHashRequest, + Bolt11ReceiveVariableAmountViaJitChannelRequest, Bolt11ReceiveViaJitChannelForHashRequest, Bolt11ReceiveViaJitChannelRequest, Bolt11SendRequest, Bolt11SendUnderpayingRequest, Bolt12CreatePayerProofRequest, Bolt12ReceiveRefundRequest, Bolt12ReceiveRequest, Bolt12SendRefundRequest, Bolt12SendRequest, CloseChannelRequest, ConnectPeerRequest, @@ -247,6 +248,32 @@ pub async fn handle_bolt11_receive_variable_amount_via_jit_channel( serialize_response(response) } +pub async fn handle_bolt11_receive_via_jit_channel_for_hash( + client: &LdkServerClient, args: Value, +) -> Result { + let mut request: Bolt11ReceiveViaJitChannelForHashRequest = parse_request(args)?; + if request.expiry_secs == 0 { + request.expiry_secs = DEFAULT_EXPIRY_SECS; + } + let response = + client.bolt11_receive_via_jit_channel_for_hash(request).await.map_err(McpError::from)?; + serialize_response(response) +} + +pub async fn handle_bolt11_receive_variable_amount_via_jit_channel_for_hash( + client: &LdkServerClient, args: Value, +) -> Result { + let mut request: Bolt11ReceiveVariableAmountViaJitChannelForHashRequest = parse_request(args)?; + if request.expiry_secs == 0 { + request.expiry_secs = DEFAULT_EXPIRY_SECS; + } + let response = client + .bolt11_receive_variable_amount_via_jit_channel_for_hash(request) + .await + .map_err(McpError::from)?; + serialize_response(response) +} + pub async fn handle_bolt11_send(client: &LdkServerClient, args: Value) -> Result { let request: Bolt11SendRequest = parse_request_with_route_parameters(args, |request: &mut Bolt11SendRequest| { diff --git a/ldk-server-mcp/src/tools/mod.rs b/ldk-server-mcp/src/tools/mod.rs index 981521fe..f3f2d6c3 100644 --- a/ldk-server-mcp/src/tools/mod.rs +++ b/ldk-server-mcp/src/tools/mod.rs @@ -157,6 +157,22 @@ pub fn build_tool_registry() -> ToolRegistry { )) }, ), + tool_spec( + "bolt11_receive_via_jit_channel_for_hash", + "Create a BOLT11 Lightning invoice to receive via an LSPS2 JIT channel for a given payment hash", + schema::bolt11_receive_via_jit_channel_for_hash_schema, + |client, args| Box::pin(handlers::handle_bolt11_receive_via_jit_channel_for_hash(client, args)), + ), + tool_spec( + "bolt11_receive_variable_amount_via_jit_channel_for_hash", + "Create a variable-amount BOLT11 Lightning invoice to receive via an LSPS2 JIT channel for a given payment hash", + schema::bolt11_receive_variable_amount_via_jit_channel_for_hash_schema, + |client, args| { + Box::pin(handlers::handle_bolt11_receive_variable_amount_via_jit_channel_for_hash( + client, args, + )) + }, + ), tool_spec( "bolt11_send", "Pay a BOLT11 Lightning invoice", diff --git a/ldk-server-mcp/src/tools/schema.rs b/ldk-server-mcp/src/tools/schema.rs index 957c5e1d..79a4ca66 100644 --- a/ldk-server-mcp/src/tools/schema.rs +++ b/ldk-server-mcp/src/tools/schema.rs @@ -316,6 +316,54 @@ pub fn bolt11_receive_variable_amount_via_jit_channel_schema() -> Value { }) } +pub fn bolt11_receive_via_jit_channel_for_hash_schema() -> Value { + json!({ + "type": "object", + "properties": { + "amount_msat": { + "type": "integer", + "description": "The amount in millisatoshis to request" + }, + "description": bolt11_invoice_description_schema(), + "expiry_secs": { + "type": "integer", + "description": "Invoice expiry time in seconds (defaults to 86400 if omitted or 0)" + }, + "max_total_lsp_fee_limit_msat": { + "type": "integer", + "description": "Optional upper bound for the total fee an LSP may deduct when opening the JIT channel" + }, + "payment_hash": { + "type": "string", + "description": "The hex-encoded 32-byte payment hash to use for the invoice" + } + }, + "required": ["amount_msat", "payment_hash"] + }) +} + +pub fn bolt11_receive_variable_amount_via_jit_channel_for_hash_schema() -> Value { + json!({ + "type": "object", + "properties": { + "description": bolt11_invoice_description_schema(), + "expiry_secs": { + "type": "integer", + "description": "Invoice expiry time in seconds (defaults to 86400 if omitted or 0)" + }, + "max_proportional_lsp_fee_limit_ppm_msat": { + "type": "integer", + "description": "Optional upper bound for the proportional fee, in parts-per-million millisatoshis, that an LSP may deduct when opening the JIT channel" + }, + "payment_hash": { + "type": "string", + "description": "The hex-encoded 32-byte payment hash to use for the invoice" + } + }, + "required": ["payment_hash"] + }) +} + pub fn bolt11_send_schema() -> Value { json!({ "type": "object", diff --git a/ldk-server-mcp/tests/integration.rs b/ldk-server-mcp/tests/integration.rs index d15ffd94..76847930 100644 --- a/ldk-server-mcp/tests/integration.rs +++ b/ldk-server-mcp/tests/integration.rs @@ -11,14 +11,16 @@ use std::io::{BufRead, BufReader, Write}; use serde_json::{json, Value}; -const NUM_TOOLS: usize = 50; +const NUM_TOOLS: usize = 52; const EXPECTED_TOOLS: [&str; NUM_TOOLS] = [ "bolt11_claim_for_id", "bolt11_fail_for_id", "bolt11_receive", "bolt11_receive_for_hash", "bolt11_receive_variable_amount_via_jit_channel", + "bolt11_receive_variable_amount_via_jit_channel_for_hash", "bolt11_receive_via_jit_channel", + "bolt11_receive_via_jit_channel_for_hash", "bolt11_send", "bolt11_send_underpaying", "bolt12_create_payer_proof", @@ -325,6 +327,29 @@ fn test_bolt11_receive_for_hash_unreachable() { ); } +#[test] +fn test_bolt11_receive_variable_amount_via_jit_channel_for_hash_unreachable() { + assert_unreachable_tool( + "bolt11_receive_variable_amount_via_jit_channel_for_hash", + json!({ + "payment_hash": "00".repeat(32), + "description": "test hodl" + }), + ); +} + +#[test] +fn test_bolt11_receive_via_jit_channel_for_hash_unreachable() { + assert_unreachable_tool( + "bolt11_receive_variable_amount_via_jit_channel_for_hash", + json!({ + "payment_hash": "00".repeat(32), + "amount_msat": 1000, + "description": "test hodl" + }), + ); +} + #[test] fn test_bolt11_claim_for_id_unreachable() { assert_unreachable_tool( diff --git a/ldk-server/src/api/bolt11_receive_via_jit_channel.rs b/ldk-server/src/api/bolt11_receive_via_jit_channel.rs index 72314580..d96838dc 100644 --- a/ldk-server/src/api/bolt11_receive_via_jit_channel.rs +++ b/ldk-server/src/api/bolt11_receive_via_jit_channel.rs @@ -9,9 +9,15 @@ use std::sync::Arc; +use crate::api::error::LdkServerErrorCode::InvalidRequestError; +use hex::FromHex; +use ldk_node::lightning_types::payment::PaymentHash; use ldk_server_grpc::api::{ + Bolt11ReceiveVariableAmountViaJitChannelForHashRequest, + Bolt11ReceiveVariableAmountViaJitChannelForHashResponse, Bolt11ReceiveVariableAmountViaJitChannelRequest, - Bolt11ReceiveVariableAmountViaJitChannelResponse, Bolt11ReceiveViaJitChannelRequest, + Bolt11ReceiveVariableAmountViaJitChannelResponse, Bolt11ReceiveViaJitChannelForHashRequest, + Bolt11ReceiveViaJitChannelForHashResponse, Bolt11ReceiveViaJitChannelRequest, Bolt11ReceiveViaJitChannelResponse, }; @@ -45,3 +51,45 @@ pub(crate) async fn handle_bolt11_receive_variable_amount_via_jit_channel_reques Ok(Bolt11ReceiveVariableAmountViaJitChannelResponse { invoice: invoice.to_string() }) } + +pub(crate) async fn handle_bolt11_receive_via_jit_channel_for_hash_request( + context: Arc, request: Bolt11ReceiveViaJitChannelForHashRequest, +) -> Result { + let description = proto_to_bolt11_description(request.description)?; + let hash_bytes = <[u8; 32]>::from_hex(&request.payment_hash).map_err(|_| { + LdkServerError::new( + InvalidRequestError, + "Invalid payment_hash, must be a 32-byte hex string.".to_string(), + ) + })?; + let payment_hash = PaymentHash(hash_bytes); + let invoice = context.node.bolt11_payment().receive_via_jit_channel_for_hash( + request.amount_msat, + &description, + request.expiry_secs, + request.max_total_lsp_fee_limit_msat, + payment_hash, + )?; + + Ok(Bolt11ReceiveViaJitChannelForHashResponse { invoice: invoice.to_string() }) +} + +pub(crate) async fn handle_bolt11_receive_variable_amount_via_jit_channel_for_hash_request( + context: Arc, request: Bolt11ReceiveVariableAmountViaJitChannelForHashRequest, +) -> Result { + let description = proto_to_bolt11_description(request.description)?; + let hash_bytes = <[u8; 32]>::from_hex(&request.payment_hash).map_err(|_| { + LdkServerError::new( + InvalidRequestError, + "Invalid payment_hash, must be a 32-byte hex string.".to_string(), + ) + })?; + let payment_hash = PaymentHash(hash_bytes); + let invoice = context.node.bolt11_payment().receive_variable_amount_via_jit_channel_for_hash( + &description, + request.expiry_secs, + request.max_proportional_lsp_fee_limit_ppm_msat, + payment_hash, + )?; + Ok(Bolt11ReceiveVariableAmountViaJitChannelForHashResponse { invoice: invoice.to_string() }) +} diff --git a/ldk-server/src/macaroons/authorization.rs b/ldk-server/src/macaroons/authorization.rs index b74a07ec..a78a16e2 100644 --- a/ldk-server/src/macaroons/authorization.rs +++ b/ldk-server/src/macaroons/authorization.rs @@ -11,21 +11,22 @@ use ldk_server_grpc::endpoints::{ BOLT11_CLAIM_FOR_ID_PATH, BOLT11_FAIL_FOR_ID_PATH, BOLT11_RECEIVE_FOR_HASH_PATH, - BOLT11_RECEIVE_PATH, BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH, - BOLT11_RECEIVE_VIA_JIT_CHANNEL_PATH, BOLT11_SEND_PATH, BOLT11_SEND_UNDERPAYING_PATH, - BOLT12_CREATE_PAYER_PROOF_PATH, BOLT12_RECEIVE_PATH, BOLT12_RECEIVE_REFUND_PATH, - BOLT12_SEND_PATH, BOLT12_SEND_REFUND_PATH, CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, - CREATE_MACAROON_PATH, DECODE_INVOICE_PATH, DECODE_OFFER_PATH, DISCONNECT_PEER_PATH, - EXPORT_PATHFINDING_SCORES_PATH, FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, - GET_CHANNEL_FORWARDING_STATS_PATH, GET_FORWARDED_PAYMENT_DETAILS_PATH, - GET_FORWARDED_PAYMENT_TRACKING_MODE_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, - GET_PERMISSIONS_PATH, GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH, GRAPH_LIST_CHANNELS_PATH, - GRAPH_LIST_NODES_PATH, LIST_CHANNELS_PATH, LIST_CHANNEL_FORWARDING_STATS_PATH, - LIST_CHANNEL_PAIR_FORWARDING_STATS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_MACAROONS_PATH, - LIST_PAYMENTS_PATH, LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, - OPEN_CHANNEL_PATH, REVOKE_MACAROON_PATH, SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH, - SPONTANEOUS_SEND_PATH, SUBSCRIBE_EVENTS_PATH, UNIFIED_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, - VERIFY_SIGNATURE_PATH, + BOLT11_RECEIVE_PATH, BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_FOR_HASH_PATH, + BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH, + BOLT11_RECEIVE_VIA_JIT_CHANNEL_FOR_HASH_PATH, BOLT11_RECEIVE_VIA_JIT_CHANNEL_PATH, + BOLT11_SEND_PATH, BOLT11_SEND_UNDERPAYING_PATH, BOLT12_CREATE_PAYER_PROOF_PATH, + BOLT12_RECEIVE_PATH, BOLT12_RECEIVE_REFUND_PATH, BOLT12_SEND_PATH, BOLT12_SEND_REFUND_PATH, + CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, CREATE_MACAROON_PATH, DECODE_INVOICE_PATH, + DECODE_OFFER_PATH, DISCONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH, + FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_CHANNEL_FORWARDING_STATS_PATH, + GET_FORWARDED_PAYMENT_DETAILS_PATH, GET_FORWARDED_PAYMENT_TRACKING_MODE_PATH, + GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, GET_PERMISSIONS_PATH, GRAPH_GET_CHANNEL_PATH, + GRAPH_GET_NODE_PATH, GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, LIST_CHANNELS_PATH, + LIST_CHANNEL_FORWARDING_STATS_PATH, LIST_CHANNEL_PAIR_FORWARDING_STATS_PATH, + LIST_FORWARDED_PAYMENTS_PATH, LIST_MACAROONS_PATH, LIST_PAYMENTS_PATH, LIST_PEERS_PATH, + ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, REVOKE_MACAROON_PATH, + SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH, SPONTANEOUS_SEND_PATH, + SUBSCRIBE_EVENTS_PATH, UNIFIED_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH, }; use ldk_server_grpc::permissions::{ CHANNELS_FORCE_CLOSE_PERMISSION, CHANNELS_MANAGE_PERMISSION, CHANNELS_READ_PERMISSION, @@ -53,6 +54,8 @@ pub(crate) fn method_authorization(method: &str) -> MethodAuthorization { | BOLT11_RECEIVE_FOR_HASH_PATH | BOLT11_RECEIVE_VIA_JIT_CHANNEL_PATH | BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH + | BOLT11_RECEIVE_VIA_JIT_CHANNEL_FOR_HASH_PATH + | BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_FOR_HASH_PATH | BOLT12_RECEIVE_PATH | BOLT12_RECEIVE_REFUND_PATH => MethodAuthorization::Permission(INVOICES_CREATE_PERMISSION), BOLT11_CLAIM_FOR_ID_PATH | BOLT11_FAIL_FOR_ID_PATH => { @@ -129,6 +132,8 @@ mod tests { ("Bolt11FailForId", Some("payments:claim")), ("Bolt11ReceiveViaJitChannel", Some("invoices:create")), ("Bolt11ReceiveVariableAmountViaJitChannel", Some("invoices:create")), + ("Bolt11ReceiveViaJitChannelForHash", Some("invoices:create")), + ("Bolt11ReceiveVariableAmountViaJitChannelForHash", Some("invoices:create")), ("Bolt11Send", Some("payments:send")), ("Bolt11SendUnderpaying", Some("payments:send")), ("Bolt12Receive", Some("invoices:create")), diff --git a/ldk-server/src/service.rs b/ldk-server/src/service.rs index ec2af133..6acbeee8 100644 --- a/ldk-server/src/service.rs +++ b/ldk-server/src/service.rs @@ -18,16 +18,17 @@ use hyper::{HeaderMap, Request, Response}; use ldk_node::Node; use ldk_server_grpc::endpoints::{ BOLT11_CLAIM_FOR_ID_PATH, BOLT11_FAIL_FOR_ID_PATH, BOLT11_RECEIVE_FOR_HASH_PATH, - BOLT11_RECEIVE_PATH, BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH, - BOLT11_RECEIVE_VIA_JIT_CHANNEL_PATH, BOLT11_SEND_PATH, BOLT11_SEND_UNDERPAYING_PATH, - BOLT12_CREATE_PAYER_PROOF_PATH, BOLT12_RECEIVE_PATH, BOLT12_RECEIVE_REFUND_PATH, - BOLT12_SEND_PATH, BOLT12_SEND_REFUND_PATH, CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, - CREATE_MACAROON_PATH, DECODE_INVOICE_PATH, DECODE_OFFER_PATH, DISCONNECT_PEER_PATH, - EXPORT_PATHFINDING_SCORES_PATH, FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, - GET_CHANNEL_FORWARDING_STATS_PATH, GET_FORWARDED_PAYMENT_DETAILS_PATH, - GET_FORWARDED_PAYMENT_TRACKING_MODE_PATH, GET_METRICS_PATH, GET_NODE_INFO_PATH, - GET_PAYMENT_DETAILS_PATH, GET_PERMISSIONS_PATH, GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH, - GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, LIST_CHANNELS_PATH, + BOLT11_RECEIVE_PATH, BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_FOR_HASH_PATH, + BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH, + BOLT11_RECEIVE_VIA_JIT_CHANNEL_FOR_HASH_PATH, BOLT11_RECEIVE_VIA_JIT_CHANNEL_PATH, + BOLT11_SEND_PATH, BOLT11_SEND_UNDERPAYING_PATH, BOLT12_CREATE_PAYER_PROOF_PATH, + BOLT12_RECEIVE_PATH, BOLT12_RECEIVE_REFUND_PATH, BOLT12_SEND_PATH, BOLT12_SEND_REFUND_PATH, + CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, CREATE_MACAROON_PATH, DECODE_INVOICE_PATH, + DECODE_OFFER_PATH, DISCONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH, + FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_CHANNEL_FORWARDING_STATS_PATH, + GET_FORWARDED_PAYMENT_DETAILS_PATH, GET_FORWARDED_PAYMENT_TRACKING_MODE_PATH, GET_METRICS_PATH, + GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, GET_PERMISSIONS_PATH, GRAPH_GET_CHANNEL_PATH, + GRAPH_GET_NODE_PATH, GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, LIST_CHANNELS_PATH, LIST_CHANNEL_FORWARDING_STATS_PATH, LIST_CHANNEL_PAIR_FORWARDING_STATS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_MACAROONS_PATH, LIST_PAYMENTS_PATH, LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, REVOKE_MACAROON_PATH, @@ -50,7 +51,9 @@ use crate::api::bolt11_fail_for_id::handle_bolt11_fail_for_id_request; use crate::api::bolt11_receive::handle_bolt11_receive_request; use crate::api::bolt11_receive_for_hash::handle_bolt11_receive_for_hash_request; use crate::api::bolt11_receive_via_jit_channel::{ + handle_bolt11_receive_variable_amount_via_jit_channel_for_hash_request, handle_bolt11_receive_variable_amount_via_jit_channel_request, + handle_bolt11_receive_via_jit_channel_for_hash_request, handle_bolt11_receive_via_jit_channel_request, }; use crate::api::bolt11_send::{handle_bolt11_send_request, handle_bolt11_send_underpaying_request}; @@ -274,6 +277,22 @@ impl Service> for NodeService { ) .await }, + BOLT11_RECEIVE_VIA_JIT_CHANNEL_FOR_HASH_PATH => { + handle_grpc_unary( + context, + body_bytes, + handle_bolt11_receive_via_jit_channel_for_hash_request, + ) + .await + }, + BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_FOR_HASH_PATH => { + handle_grpc_unary( + context, + body_bytes, + handle_bolt11_receive_variable_amount_via_jit_channel_for_hash_request, + ) + .await + }, BOLT11_SEND_PATH => { handle_grpc_unary(context, body_bytes, handle_bolt11_send_request).await },