Skip to content

Commit e8081ae

Browse files
committed
Add LSPS2 commands to ldk-server-client and ldk-server-cli
Expose the new LSPS2 JIT invoice endpoints through the Rust client and CLI so callers can request either fixed or variable-amount invoices without constructing protobuf messages manually. Generated with the assistance of AI. Co-Authored-By: HAL 9000
1 parent 0f448d0 commit e8081ae

2 files changed

Lines changed: 123 additions & 18 deletions

File tree

ldk-server-cli/src/main.rs

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ use ldk_server_client::error::LdkServerErrorCode::{
2222
AuthError, InternalError, InternalServerError, InvalidRequestError, LightningError,
2323
};
2424
use ldk_server_client::ldk_server_protos::api::{
25-
Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11SendRequest, Bolt11SendResponse,
25+
Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11ReceiveVariableAmountViaJitChannelRequest,
26+
Bolt11ReceiveVariableAmountViaJitChannelResponse, Bolt11ReceiveViaJitChannelRequest,
27+
Bolt11ReceiveViaJitChannelResponse, Bolt11SendRequest, Bolt11SendResponse,
2628
Bolt12ReceiveRequest, Bolt12ReceiveResponse, Bolt12SendRequest, Bolt12SendResponse,
2729
CloseChannelRequest, CloseChannelResponse, ConnectPeerRequest, ConnectPeerResponse,
2830
DisconnectPeerRequest, DisconnectPeerResponse, ExportPathfindingScoresRequest,
@@ -134,6 +136,41 @@ enum Commands {
134136
#[arg(short, long, help = "Invoice expiry time in seconds (default: 86400)")]
135137
expiry_secs: Option<u32>,
136138
},
139+
#[command(about = "Create a fixed-amount BOLT11 invoice to receive via an LSPS2 JIT channel")]
140+
Bolt11ReceiveViaJitChannel {
141+
#[arg(help = "Amount to request, e.g. 50sat or 50000msat")]
142+
amount: Amount,
143+
#[arg(short, long, help = "Description to attach along with the invoice")]
144+
description: Option<String>,
145+
#[arg(
146+
long,
147+
help = "SHA-256 hash of the description (hex). Use instead of description for longer text"
148+
)]
149+
description_hash: Option<String>,
150+
#[arg(short, long, help = "Invoice expiry time in seconds (default: 86400)")]
151+
expiry_secs: Option<u32>,
152+
#[arg(
153+
long,
154+
help = "Maximum total fee an LSP may deduct for opening the JIT channel, e.g. 50sat or 50000msat"
155+
)]
156+
max_total_lsp_fee_limit: Option<Amount>,
157+
},
158+
#[command(
159+
about = "Create a variable-amount BOLT11 invoice to receive via an LSPS2 JIT channel"
160+
)]
161+
Bolt11ReceiveVariableAmountViaJitChannel {
162+
#[arg(short, long, help = "Description to attach along with the invoice")]
163+
description: Option<String>,
164+
#[arg(
165+
long,
166+
help = "SHA-256 hash of the description (hex). Use instead of description for longer text"
167+
)]
168+
description_hash: Option<String>,
169+
#[arg(short, long, help = "Invoice expiry time in seconds (default: 86400)")]
170+
expiry_secs: Option<u32>,
171+
#[arg(long, help = "Maximum proportional fee the LSP may deduct in ppm-msat")]
172+
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
173+
},
137174
#[command(about = "Pay a BOLT11 invoice")]
138175
Bolt11Send {
139176
#[arg(help = "A BOLT11 invoice for a payment within the Lightning Network")]
@@ -527,21 +564,8 @@ async fn main() {
527564
},
528565
Commands::Bolt11Receive { description, description_hash, expiry_secs, amount } => {
529566
let amount_msat = amount.map(|a| a.to_msat());
530-
let invoice_description = match (description, description_hash) {
531-
(Some(desc), None) => Some(Bolt11InvoiceDescription {
532-
kind: Some(bolt11_invoice_description::Kind::Direct(desc)),
533-
}),
534-
(None, Some(hash)) => Some(Bolt11InvoiceDescription {
535-
kind: Some(bolt11_invoice_description::Kind::Hash(hash)),
536-
}),
537-
(Some(_), Some(_)) => {
538-
handle_error(LdkServerError::new(
539-
InternalError,
540-
"Only one of description or description_hash can be set.".to_string(),
541-
));
542-
},
543-
(None, None) => None,
544-
};
567+
let invoice_description =
568+
parse_bolt11_invoice_description(description, description_hash);
545569

546570
let expiry_secs = expiry_secs.unwrap_or(DEFAULT_EXPIRY_SECS);
547571
let request =
@@ -551,6 +575,40 @@ async fn main() {
551575
client.bolt11_receive(request).await,
552576
);
553577
},
578+
Commands::Bolt11ReceiveViaJitChannel {
579+
amount,
580+
description,
581+
description_hash,
582+
expiry_secs,
583+
max_total_lsp_fee_limit,
584+
} => {
585+
let request = Bolt11ReceiveViaJitChannelRequest {
586+
amount_msat: amount.to_msat(),
587+
description: parse_bolt11_invoice_description(description, description_hash),
588+
expiry_secs: expiry_secs.unwrap_or(DEFAULT_EXPIRY_SECS),
589+
max_total_lsp_fee_limit_msat: max_total_lsp_fee_limit.map(|a| a.to_msat()),
590+
};
591+
592+
handle_response_result::<_, Bolt11ReceiveViaJitChannelResponse>(
593+
client.bolt11_receive_via_jit_channel(request).await,
594+
);
595+
},
596+
Commands::Bolt11ReceiveVariableAmountViaJitChannel {
597+
description,
598+
description_hash,
599+
expiry_secs,
600+
max_proportional_lsp_fee_limit_ppm_msat,
601+
} => {
602+
let request = Bolt11ReceiveVariableAmountViaJitChannelRequest {
603+
description: parse_bolt11_invoice_description(description, description_hash),
604+
expiry_secs: expiry_secs.unwrap_or(DEFAULT_EXPIRY_SECS),
605+
max_proportional_lsp_fee_limit_ppm_msat,
606+
};
607+
608+
handle_response_result::<_, Bolt11ReceiveVariableAmountViaJitChannelResponse>(
609+
client.bolt11_receive_variable_amount_via_jit_channel(request).await,
610+
);
611+
},
554612
Commands::Bolt11Send {
555613
invoice,
556614
amount,
@@ -958,6 +1016,26 @@ where
9581016
}
9591017
}
9601018

1019+
fn parse_bolt11_invoice_description(
1020+
description: Option<String>, description_hash: Option<String>,
1021+
) -> Option<Bolt11InvoiceDescription> {
1022+
match (description, description_hash) {
1023+
(Some(desc), None) => Some(Bolt11InvoiceDescription {
1024+
kind: Some(bolt11_invoice_description::Kind::Direct(desc)),
1025+
}),
1026+
(None, Some(hash)) => Some(Bolt11InvoiceDescription {
1027+
kind: Some(bolt11_invoice_description::Kind::Hash(hash)),
1028+
}),
1029+
(Some(_), Some(_)) => {
1030+
handle_error(LdkServerError::new(
1031+
InternalError,
1032+
"Only one of description or description_hash can be set.".to_string(),
1033+
));
1034+
},
1035+
(None, None) => None,
1036+
}
1037+
}
1038+
9611039
fn parse_page_token(token_str: &str) -> Result<PageToken, LdkServerError> {
9621040
let parts: Vec<&str> = token_str.split(':').collect();
9631041
if parts.len() != 2 {

ldk-server-client/src/client.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ use std::time::{SystemTime, UNIX_EPOCH};
1212
use bitcoin_hashes::hmac::{Hmac, HmacEngine};
1313
use bitcoin_hashes::{sha256, Hash, HashEngine};
1414
use ldk_server_protos::api::{
15-
Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11SendRequest, Bolt11SendResponse,
15+
Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11ReceiveVariableAmountViaJitChannelRequest,
16+
Bolt11ReceiveVariableAmountViaJitChannelResponse, Bolt11ReceiveViaJitChannelRequest,
17+
Bolt11ReceiveViaJitChannelResponse, Bolt11SendRequest, Bolt11SendResponse,
1618
Bolt12ReceiveRequest, Bolt12ReceiveResponse, Bolt12SendRequest, Bolt12SendResponse,
1719
CloseChannelRequest, CloseChannelResponse, ConnectPeerRequest, ConnectPeerResponse,
1820
DisconnectPeerRequest, DisconnectPeerResponse, ExportPathfindingScoresRequest,
@@ -30,7 +32,8 @@ use ldk_server_protos::api::{
3032
VerifySignatureRequest, VerifySignatureResponse,
3133
};
3234
use ldk_server_protos::endpoints::{
33-
BOLT11_RECEIVE_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH,
35+
BOLT11_RECEIVE_PATH, BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH,
36+
BOLT11_RECEIVE_VIA_JIT_CHANNEL_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH,
3437
CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, DISCONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH,
3538
FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH,
3639
GRAPH_GET_CHANNEL_PATH, GRAPH_GET_NODE_PATH, GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH,
@@ -143,6 +146,30 @@ impl LdkServerClient {
143146
self.post_request(&request, &url).await
144147
}
145148

149+
/// Retrieve a new fixed-amount BOLT11 invoice for receiving via an LSPS2 JIT channel.
150+
/// For API contract/usage, refer to docs for [`Bolt11ReceiveViaJitChannelRequest`] and
151+
/// [`Bolt11ReceiveViaJitChannelResponse`].
152+
pub async fn bolt11_receive_via_jit_channel(
153+
&self, request: Bolt11ReceiveViaJitChannelRequest,
154+
) -> Result<Bolt11ReceiveViaJitChannelResponse, LdkServerError> {
155+
let url = format!("https://{}/{BOLT11_RECEIVE_VIA_JIT_CHANNEL_PATH}", self.base_url);
156+
self.post_request(&request, &url).await
157+
}
158+
159+
/// Retrieve a new variable-amount BOLT11 invoice for receiving via an LSPS2 JIT channel.
160+
/// For API contract/usage, refer to docs for
161+
/// [`Bolt11ReceiveVariableAmountViaJitChannelRequest`] and
162+
/// [`Bolt11ReceiveVariableAmountViaJitChannelResponse`].
163+
pub async fn bolt11_receive_variable_amount_via_jit_channel(
164+
&self, request: Bolt11ReceiveVariableAmountViaJitChannelRequest,
165+
) -> Result<Bolt11ReceiveVariableAmountViaJitChannelResponse, LdkServerError> {
166+
let url = format!(
167+
"https://{}/{BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH}",
168+
self.base_url,
169+
);
170+
self.post_request(&request, &url).await
171+
}
172+
146173
/// Send a payment for a BOLT11 invoice.
147174
/// For API contract/usage, refer to docs for [`Bolt11SendRequest`] and [`Bolt11SendResponse`].
148175
pub async fn bolt11_send(

0 commit comments

Comments
 (0)