From 32619ede99b04178d8eeba6c7156d3eb66a33aaa Mon Sep 17 00:00:00 2001 From: benthecarman Date: Mon, 6 Jul 2026 18:23:53 -0500 Subject: [PATCH] Add BOLT11 underpaying send RPC Expose Bolt11SendUnderpaying through the proto API, server handler, client, CLI, MCP tool registry, tests, and docs. AI-assisted-by: OpenAI Codex --- docs/api-guide.md | 9 ++-- ldk-server-cli/src/main.rs | 67 +++++++++++++++++++++++++--- ldk-server-client/src/client.rs | 55 +++++++++++++---------- ldk-server-grpc/src/api.rs | 29 ++++++++++++ ldk-server-grpc/src/endpoints.rs | 1 + ldk-server-grpc/src/proto/api.proto | 24 ++++++++++ ldk-server-mcp/src/tools/handlers.rs | 22 ++++++--- ldk-server-mcp/src/tools/mod.rs | 6 +++ ldk-server-mcp/src/tools/schema.rs | 18 ++++++++ ldk-server-mcp/tests/integration.rs | 11 ++++- ldk-server/src/api/bolt11_send.rs | 23 +++++++++- ldk-server/src/service.rs | 25 ++++++----- 12 files changed, 240 insertions(+), 50 deletions(-) diff --git a/docs/api-guide.md b/docs/api-guide.md index a3c17efe..a6dab08a 100644 --- a/docs/api-guide.md +++ b/docs/api-guide.md @@ -93,10 +93,11 @@ All RPCs are unary (single request, single response) unless noted otherwise. ### BOLT11 Payments -| RPC | Description | -|-----------------|-------------------------------------------------------------------| -| `Bolt11Receive` | Create an invoice (fixed or variable amount) with automatic claim | -| `Bolt11Send` | Pay a BOLT11 invoice (with optional routing config) | +| RPC | Description | +|---------------------------|------------------------------------------------------------------------------------| +| `Bolt11Receive` | Create an invoice (fixed or variable amount) with automatic claim | +| `Bolt11Send` | Pay a BOLT11 invoice (with optional routing config) | +| `Bolt11SendUnderpaying` | Pay less than the invoice amount when the receiver accepts underpaying HTLCs | ### BOLT11 Hodl Invoices diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index 918f57a3..b3b00502 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -28,12 +28,13 @@ use ldk_server_client::ldk_server_grpc::api::{ Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11ReceiveVariableAmountViaJitChannelRequest, Bolt11ReceiveVariableAmountViaJitChannelResponse, Bolt11ReceiveViaJitChannelRequest, Bolt11ReceiveViaJitChannelResponse, Bolt11SendRequest, Bolt11SendResponse, - Bolt12ReceiveRequest, Bolt12ReceiveResponse, Bolt12SendRequest, Bolt12SendResponse, - CloseChannelRequest, CloseChannelResponse, ConnectPeerRequest, ConnectPeerResponse, - DecodeInvoiceRequest, DecodeInvoiceResponse, DecodeOfferRequest, DecodeOfferResponse, - DisconnectPeerRequest, DisconnectPeerResponse, ExportPathfindingScoresRequest, - ForceCloseChannelRequest, ForceCloseChannelResponse, GetBalancesRequest, GetBalancesResponse, - GetNodeInfoRequest, GetNodeInfoResponse, GetPaymentDetailsRequest, GetPaymentDetailsResponse, + Bolt11SendUnderpayingRequest, Bolt11SendUnderpayingResponse, Bolt12ReceiveRequest, + Bolt12ReceiveResponse, Bolt12SendRequest, Bolt12SendResponse, CloseChannelRequest, + CloseChannelResponse, ConnectPeerRequest, ConnectPeerResponse, DecodeInvoiceRequest, + DecodeInvoiceResponse, DecodeOfferRequest, DecodeOfferResponse, DisconnectPeerRequest, + DisconnectPeerResponse, ExportPathfindingScoresRequest, ForceCloseChannelRequest, + ForceCloseChannelResponse, GetBalancesRequest, GetBalancesResponse, GetNodeInfoRequest, + GetNodeInfoResponse, GetPaymentDetailsRequest, GetPaymentDetailsResponse, GraphGetChannelRequest, GraphGetChannelResponse, GraphGetNodeRequest, GraphGetNodeResponse, GraphListChannelsRequest, GraphListChannelsResponse, GraphListNodesRequest, GraphListNodesResponse, ListChannelsRequest, ListChannelsResponse, @@ -246,6 +247,32 @@ enum Commands { )] max_channel_saturation_power_of_half: Option, }, + #[command(about = "Underpay a BOLT11 invoice")] + Bolt11SendUnderpaying { + #[arg(help = "A BOLT11 invoice for a payment within the Lightning Network")] + invoice: String, + #[arg( + help = "Amount to send, e.g. 50sat or 50000msat. Must be less than the invoice amount" + )] + amount: Amount, + #[arg( + long, + help = "Maximum total routing fee, e.g. 50sat or 50000msat. Defaults to 1% of payment + 50 sats" + )] + max_total_routing_fee: Option, + #[arg(long, help = "Maximum total CLTV delta we accept for the route (default: 1008)")] + max_total_cltv_expiry_delta: Option, + #[arg( + long, + help = "Maximum number of paths that may be used by MPP payments (default: 10)" + )] + max_path_count: Option, + #[arg( + long, + help = "Maximum share of a channel's total capacity to send over a channel, as a power of 1/2 (default: 2)" + )] + max_channel_saturation_power_of_half: Option, + }, #[command(about = "Return a BOLT12 offer for receiving payments")] Bolt12Receive { #[arg(help = "Description to attach along with the offer")] @@ -765,6 +792,34 @@ async fn main() { .await, ); }, + Commands::Bolt11SendUnderpaying { + invoice, + amount, + max_total_routing_fee, + max_total_cltv_expiry_delta, + max_path_count, + max_channel_saturation_power_of_half, + } => { + let amount_msat = amount.to_msat(); + let max_total_routing_fee_msat = max_total_routing_fee.map(|a| a.to_msat()); + let route_parameters = RouteParametersConfig { + max_total_routing_fee_msat, + max_total_cltv_expiry_delta: max_total_cltv_expiry_delta + .unwrap_or(DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA), + max_path_count: max_path_count.unwrap_or(DEFAULT_MAX_PATH_COUNT), + max_channel_saturation_power_of_half: max_channel_saturation_power_of_half + .unwrap_or(DEFAULT_MAX_CHANNEL_SATURATION_POWER_OF_HALF), + }; + handle_response_result::<_, Bolt11SendUnderpayingResponse>( + client + .bolt11_send_underpaying(Bolt11SendUnderpayingRequest { + invoice, + amount_msat, + route_parameters: Some(route_parameters), + }) + .await, + ); + }, Commands::Bolt12Receive { description, amount, expiry_secs, quantity } => { let amount_msat = amount.map(|a| a.to_msat()); handle_response_result::<_, Bolt12ReceiveResponse>( diff --git a/ldk-server-client/src/client.rs b/ldk-server-client/src/client.rs index b672ad0f..32653080 100644 --- a/ldk-server-client/src/client.rs +++ b/ldk-server-client/src/client.rs @@ -21,20 +21,21 @@ use ldk_server_grpc::api::{ Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11ReceiveVariableAmountViaJitChannelRequest, Bolt11ReceiveVariableAmountViaJitChannelResponse, Bolt11ReceiveViaJitChannelRequest, Bolt11ReceiveViaJitChannelResponse, Bolt11SendRequest, Bolt11SendResponse, - Bolt12ReceiveRequest, Bolt12ReceiveResponse, Bolt12SendRequest, Bolt12SendResponse, - CloseChannelRequest, CloseChannelResponse, ConnectPeerRequest, ConnectPeerResponse, - DecodeInvoiceRequest, DecodeInvoiceResponse, DecodeOfferRequest, DecodeOfferResponse, - DisconnectPeerRequest, DisconnectPeerResponse, ExportPathfindingScoresRequest, - ExportPathfindingScoresResponse, ForceCloseChannelRequest, ForceCloseChannelResponse, - GetBalancesRequest, GetBalancesResponse, GetNodeInfoRequest, GetNodeInfoResponse, - GetPaymentDetailsRequest, GetPaymentDetailsResponse, GraphGetChannelRequest, - GraphGetChannelResponse, GraphGetNodeRequest, GraphGetNodeResponse, GraphListChannelsRequest, - GraphListChannelsResponse, GraphListNodesRequest, GraphListNodesResponse, ListChannelsRequest, - ListChannelsResponse, ListForwardedPaymentsRequest, ListForwardedPaymentsResponse, - ListPaymentsRequest, ListPaymentsResponse, ListPeersRequest, ListPeersResponse, - OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, - OpenChannelRequest, OpenChannelResponse, SignMessageRequest, SignMessageResponse, - SpliceInRequest, SpliceInResponse, SpliceOutRequest, SpliceOutResponse, SpontaneousSendRequest, + Bolt11SendUnderpayingRequest, Bolt11SendUnderpayingResponse, Bolt12ReceiveRequest, + Bolt12ReceiveResponse, Bolt12SendRequest, Bolt12SendResponse, CloseChannelRequest, + CloseChannelResponse, ConnectPeerRequest, ConnectPeerResponse, DecodeInvoiceRequest, + DecodeInvoiceResponse, DecodeOfferRequest, DecodeOfferResponse, DisconnectPeerRequest, + DisconnectPeerResponse, ExportPathfindingScoresRequest, ExportPathfindingScoresResponse, + ForceCloseChannelRequest, ForceCloseChannelResponse, GetBalancesRequest, GetBalancesResponse, + GetNodeInfoRequest, GetNodeInfoResponse, GetPaymentDetailsRequest, GetPaymentDetailsResponse, + GraphGetChannelRequest, GraphGetChannelResponse, GraphGetNodeRequest, GraphGetNodeResponse, + GraphListChannelsRequest, GraphListChannelsResponse, GraphListNodesRequest, + GraphListNodesResponse, ListChannelsRequest, ListChannelsResponse, + ListForwardedPaymentsRequest, ListForwardedPaymentsResponse, ListPaymentsRequest, + ListPaymentsResponse, ListPeersRequest, ListPeersResponse, OnchainReceiveRequest, + OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, OpenChannelRequest, + OpenChannelResponse, SignMessageRequest, SignMessageResponse, SpliceInRequest, + SpliceInResponse, SpliceOutRequest, SpliceOutResponse, SpontaneousSendRequest, SpontaneousSendResponse, SubscribeEventsRequest, UnifiedSendRequest, UnifiedSendResponse, UpdateChannelConfigRequest, UpdateChannelConfigResponse, VerifySignatureRequest, VerifySignatureResponse, @@ -42,15 +43,16 @@ use ldk_server_grpc::api::{ use ldk_server_grpc::endpoints::{ BOLT11_CLAIM_FOR_HASH_PATH, BOLT11_FAIL_FOR_HASH_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, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH, - CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, DECODE_INVOICE_PATH, DECODE_OFFER_PATH, - DISCONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH, FORCE_CLOSE_CHANNEL_PATH, - GET_BALANCES_PATH, GET_METRICS_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, - GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH, GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, - GRPC_SERVICE_PREFIX, LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH, - LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_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_VIA_JIT_CHANNEL_PATH, BOLT11_SEND_PATH, BOLT11_SEND_UNDERPAYING_PATH, + BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH, CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, + DECODE_INVOICE_PATH, DECODE_OFFER_PATH, DISCONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH, + FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_METRICS_PATH, GET_NODE_INFO_PATH, + GET_PAYMENT_DETAILS_PATH, GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH, + GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, GRPC_SERVICE_PREFIX, LIST_CHANNELS_PATH, + LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH, LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH, + ONCHAIN_SEND_PATH, OPEN_CHANNEL_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::{ @@ -242,6 +244,13 @@ impl LdkServerClient { self.grpc_unary(&request, BOLT11_SEND_PATH).await } + /// Send an underpaying payment for a BOLT11 invoice. + pub async fn bolt11_send_underpaying( + &self, request: Bolt11SendUnderpayingRequest, + ) -> Result { + self.grpc_unary(&request, BOLT11_SEND_UNDERPAYING_PATH).await + } + /// Retrieve a new BOLT12 offer. pub async fn bolt12_receive( &self, request: Bolt12ReceiveRequest, diff --git a/ldk-server-grpc/src/api.rs b/ldk-server-grpc/src/api.rs index 26016bf4..832c147a 100644 --- a/ldk-server-grpc/src/api.rs +++ b/ldk-server-grpc/src/api.rs @@ -376,6 +376,35 @@ pub struct Bolt11SendResponse { #[prost(string, tag = "1")] pub payment_id: ::prost::alloc::string::String, } +/// Send an underpaying payment for a BOLT11 invoice. +/// 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 Bolt11SendUnderpayingRequest { + /// An invoice for a payment within the Lightning Network. + #[prost(string, tag = "1")] + pub invoice: ::prost::alloc::string::String, + /// Amount in millisatoshis to send. Must be less than the amount required by the invoice. + #[prost(uint64, tag = "2")] + pub amount_msat: u64, + /// Configuration options for payment routing and pathfinding. + #[prost(message, optional, tag = "3")] + pub route_parameters: ::core::option::Option, +} +/// The response for the `Bolt11SendUnderpaying` 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 Bolt11SendUnderpayingResponse { + /// An identifier used to uniquely identify a payment in hex-encoded form. + #[prost(string, tag = "1")] + pub payment_id: ::prost::alloc::string::String, +} /// Returns a BOLT12 offer for the given amount, if specified. /// /// See more: diff --git a/ldk-server-grpc/src/endpoints.rs b/ldk-server-grpc/src/endpoints.rs index 9b20263f..cc4492be 100644 --- a/ldk-server-grpc/src/endpoints.rs +++ b/ldk-server-grpc/src/endpoints.rs @@ -22,6 +22,7 @@ pub const BOLT11_RECEIVE_VIA_JIT_CHANNEL_PATH: &str = "Bolt11ReceiveViaJitChanne pub const BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH: &str = "Bolt11ReceiveVariableAmountViaJitChannel"; pub const BOLT11_SEND_PATH: &str = "Bolt11Send"; +pub const BOLT11_SEND_UNDERPAYING_PATH: &str = "Bolt11SendUnderpaying"; pub const BOLT12_RECEIVE_PATH: &str = "Bolt12Receive"; pub const BOLT12_SEND_PATH: &str = "Bolt12Send"; pub const OPEN_CHANNEL_PATH: &str = "OpenChannel"; diff --git a/ldk-server-grpc/src/proto/api.proto b/ldk-server-grpc/src/proto/api.proto index 244af5d9..501e0837 100644 --- a/ldk-server-grpc/src/proto/api.proto +++ b/ldk-server-grpc/src/proto/api.proto @@ -296,6 +296,28 @@ message Bolt11SendResponse { string payment_id = 1; } +// Send an underpaying payment for a BOLT11 invoice. +// See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.send_using_amount_underpaying +message Bolt11SendUnderpayingRequest { + + // An invoice for a payment within the Lightning Network. + string invoice = 1; + + // Amount in millisatoshis to send. Must be less than the amount required by the invoice. + uint64 amount_msat = 2; + + // Configuration options for payment routing and pathfinding. + optional types.RouteParametersConfig route_parameters = 3; + +} + +// The response for the `Bolt11SendUnderpaying` RPC. On failure, a gRPC error status is returned. +message Bolt11SendUnderpayingResponse { + + // An identifier used to uniquely identify a payment in hex-encoded form. + string payment_id = 1; +} + // Returns a BOLT12 offer for the given amount, if specified. // // See more: @@ -928,6 +950,8 @@ service LightningNode { rpc Bolt11ReceiveVariableAmountViaJitChannel(Bolt11ReceiveVariableAmountViaJitChannelRequest) returns (Bolt11ReceiveVariableAmountViaJitChannelResponse); // Send a payment for a BOLT11 invoice. rpc Bolt11Send(Bolt11SendRequest) returns (Bolt11SendResponse); + // Send an underpaying payment for a BOLT11 invoice. + rpc Bolt11SendUnderpaying(Bolt11SendUnderpayingRequest) returns (Bolt11SendUnderpayingResponse); // Return a BOLT12 offer. rpc Bolt12Receive(Bolt12ReceiveRequest) returns (Bolt12ReceiveResponse); // Send a payment for a BOLT12 offer. diff --git a/ldk-server-mcp/src/tools/handlers.rs b/ldk-server-mcp/src/tools/handlers.rs index 814fbc20..6557fbdf 100644 --- a/ldk-server-mcp/src/tools/handlers.rs +++ b/ldk-server-mcp/src/tools/handlers.rs @@ -12,11 +12,12 @@ use ldk_server_client::client::LdkServerClient; use ldk_server_client::ldk_server_grpc::api::{ Bolt11ClaimForHashRequest, Bolt11FailForHashRequest, Bolt11ReceiveForHashRequest, Bolt11ReceiveRequest, Bolt11ReceiveVariableAmountViaJitChannelRequest, - Bolt11ReceiveViaJitChannelRequest, Bolt11SendRequest, Bolt12ReceiveRequest, Bolt12SendRequest, - CloseChannelRequest, ConnectPeerRequest, DecodeInvoiceRequest, DecodeOfferRequest, - DisconnectPeerRequest, ExportPathfindingScoresRequest, ForceCloseChannelRequest, - GetBalancesRequest, GetNodeInfoRequest, GetPaymentDetailsRequest, GraphGetChannelRequest, - GraphGetNodeRequest, GraphListChannelsRequest, GraphListNodesRequest, ListChannelsRequest, + Bolt11ReceiveViaJitChannelRequest, Bolt11SendRequest, Bolt11SendUnderpayingRequest, + Bolt12ReceiveRequest, Bolt12SendRequest, CloseChannelRequest, ConnectPeerRequest, + DecodeInvoiceRequest, DecodeOfferRequest, DisconnectPeerRequest, + ExportPathfindingScoresRequest, ForceCloseChannelRequest, GetBalancesRequest, + GetNodeInfoRequest, GetPaymentDetailsRequest, GraphGetChannelRequest, GraphGetNodeRequest, + GraphListChannelsRequest, GraphListNodesRequest, ListChannelsRequest, ListForwardedPaymentsRequest, ListPaymentsRequest, ListPeersRequest, OnchainReceiveRequest, OnchainSendRequest, OpenChannelRequest, SignMessageRequest, SpliceInRequest, SpliceOutRequest, SpontaneousSendRequest, UnifiedSendRequest, UpdateChannelConfigRequest, VerifySignatureRequest, @@ -191,6 +192,17 @@ pub async fn handle_bolt11_send(client: &LdkServerClient, args: Value) -> Result serialize_response(response) } +pub async fn handle_bolt11_send_underpaying( + client: &LdkServerClient, args: Value, +) -> Result { + let request: Bolt11SendUnderpayingRequest = + parse_request_with_route_parameters(args, |request: &mut Bolt11SendUnderpayingRequest| { + &mut request.route_parameters + })?; + let response = client.bolt11_send_underpaying(request).await.map_err(McpError::from)?; + serialize_response(response) +} + pub async fn handle_bolt12_receive( client: &LdkServerClient, args: Value, ) -> Result { diff --git a/ldk-server-mcp/src/tools/mod.rs b/ldk-server-mcp/src/tools/mod.rs index f689d3d9..02c2a352 100644 --- a/ldk-server-mcp/src/tools/mod.rs +++ b/ldk-server-mcp/src/tools/mod.rs @@ -139,6 +139,12 @@ pub fn build_tool_registry() -> ToolRegistry { schema::bolt11_send_schema, |client, args| Box::pin(handlers::handle_bolt11_send(client, args)), ), + tool_spec( + "bolt11_send_underpaying", + "Underpay a BOLT11 Lightning invoice", + schema::bolt11_send_underpaying_schema, + |client, args| Box::pin(handlers::handle_bolt11_send_underpaying(client, args)), + ), tool_spec( "bolt12_receive", "Create a BOLT12 offer for receiving Lightning payments", diff --git a/ldk-server-mcp/src/tools/schema.rs b/ldk-server-mcp/src/tools/schema.rs index b9c45144..c7cde012 100644 --- a/ldk-server-mcp/src/tools/schema.rs +++ b/ldk-server-mcp/src/tools/schema.rs @@ -311,6 +311,24 @@ pub fn bolt11_send_schema() -> Value { }) } +pub fn bolt11_send_underpaying_schema() -> Value { + json!({ + "type": "object", + "properties": { + "invoice": { + "type": "string", + "description": "A BOLT11 invoice string to underpay" + }, + "amount_msat": { + "type": "integer", + "description": "Amount in millisatoshis to send. Must be less than the invoice amount" + }, + "route_parameters": route_parameters_config_schema() + }, + "required": ["invoice", "amount_msat"] + }) +} + pub fn bolt12_receive_schema() -> Value { json!({ "type": "object", diff --git a/ldk-server-mcp/tests/integration.rs b/ldk-server-mcp/tests/integration.rs index 891956c3..5b636285 100644 --- a/ldk-server-mcp/tests/integration.rs +++ b/ldk-server-mcp/tests/integration.rs @@ -11,7 +11,7 @@ use std::io::{BufRead, BufReader, Write}; use serde_json::{json, Value}; -const NUM_TOOLS: usize = 37; +const NUM_TOOLS: usize = 38; const EXPECTED_TOOLS: [&str; NUM_TOOLS] = [ "bolt11_claim_for_hash", "bolt11_fail_for_hash", @@ -20,6 +20,7 @@ const EXPECTED_TOOLS: [&str; NUM_TOOLS] = [ "bolt11_receive_variable_amount_via_jit_channel", "bolt11_receive_via_jit_channel", "bolt11_send", + "bolt11_send_underpaying", "bolt12_receive", "bolt12_send", "close_channel", @@ -323,6 +324,14 @@ fn test_decode_invoice_unreachable() { assert_unreachable_tool("decode_invoice", json!({ "invoice": "lnbc1example" })); } +#[test] +fn test_bolt11_send_underpaying_unreachable() { + assert_unreachable_tool( + "bolt11_send_underpaying", + json!({ "invoice": "lnbc1example", "amount_msat": 1000 }), + ); +} + #[test] fn test_decode_offer_unreachable() { assert_unreachable_tool("decode_offer", json!({ "offer": "lno1example" })); diff --git a/ldk-server/src/api/bolt11_send.rs b/ldk-server/src/api/bolt11_send.rs index 2ee55c33..7376be64 100644 --- a/ldk-server/src/api/bolt11_send.rs +++ b/ldk-server/src/api/bolt11_send.rs @@ -11,7 +11,10 @@ use std::str::FromStr; use std::sync::Arc; use ldk_node::lightning_invoice::Bolt11Invoice; -use ldk_server_grpc::api::{Bolt11SendRequest, Bolt11SendResponse}; +use ldk_server_grpc::api::{ + Bolt11SendRequest, Bolt11SendResponse, Bolt11SendUnderpayingRequest, + Bolt11SendUnderpayingResponse, +}; use crate::api::build_route_parameters_config_from_proto; use crate::api::error::LdkServerError; @@ -35,3 +38,21 @@ pub(crate) async fn handle_bolt11_send_request( let response = Bolt11SendResponse { payment_id: payment_id.to_string() }; Ok(response) } + +pub(crate) async fn handle_bolt11_send_underpaying_request( + context: Arc, request: Bolt11SendUnderpayingRequest, +) -> Result { + let invoice = Bolt11Invoice::from_str(request.invoice.as_str()) + .map_err(|_| ldk_node::NodeError::InvalidInvoice)?; + + let route_parameters = build_route_parameters_config_from_proto(request.route_parameters)?; + + let payment_id = context.node.bolt11_payment().send_using_amount_underpaying( + &invoice, + request.amount_msat, + route_parameters, + )?; + + let response = Bolt11SendUnderpayingResponse { payment_id: payment_id.to_string() }; + Ok(response) +} diff --git a/ldk-server/src/service.rs b/ldk-server/src/service.rs index 8706b379..83c6fb7e 100644 --- a/ldk-server/src/service.rs +++ b/ldk-server/src/service.rs @@ -21,15 +21,16 @@ use ldk_node::Node; use ldk_server_grpc::endpoints::{ BOLT11_CLAIM_FOR_HASH_PATH, BOLT11_FAIL_FOR_HASH_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, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH, - CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, DECODE_INVOICE_PATH, DECODE_OFFER_PATH, - DISCONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH, FORCE_CLOSE_CHANNEL_PATH, - GET_BALANCES_PATH, GET_METRICS_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, - GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH, GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, - LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH, LIST_PEERS_PATH, - ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_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_VIA_JIT_CHANNEL_PATH, BOLT11_SEND_PATH, BOLT11_SEND_UNDERPAYING_PATH, + BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH, CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, + DECODE_INVOICE_PATH, DECODE_OFFER_PATH, DISCONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH, + FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_METRICS_PATH, GET_NODE_INFO_PATH, + GET_PAYMENT_DETAILS_PATH, GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH, + GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, LIST_CHANNELS_PATH, + LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH, LIST_PEERS_PATH, ONCHAIN_RECEIVE_PATH, + ONCHAIN_SEND_PATH, OPEN_CHANNEL_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::{ @@ -49,7 +50,7 @@ use crate::api::bolt11_receive_via_jit_channel::{ handle_bolt11_receive_variable_amount_via_jit_channel_request, handle_bolt11_receive_via_jit_channel_request, }; -use crate::api::bolt11_send::handle_bolt11_send_request; +use crate::api::bolt11_send::{handle_bolt11_send_request, handle_bolt11_send_underpaying_request}; use crate::api::bolt12_receive::handle_bolt12_receive_request; use crate::api::bolt12_send::handle_bolt12_send_request; use crate::api::close_channel::{handle_close_channel_request, handle_force_close_channel_request}; @@ -318,6 +319,10 @@ impl Service> for NodeService { BOLT11_SEND_PATH => { handle_grpc_unary(context, body_bytes, handle_bolt11_send_request).await }, + BOLT11_SEND_UNDERPAYING_PATH => { + handle_grpc_unary(context, body_bytes, handle_bolt11_send_underpaying_request) + .await + }, BOLT12_RECEIVE_PATH => { handle_grpc_unary(context, body_bytes, handle_bolt12_receive_request).await },