diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index e77cf83..d755892 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -24,17 +24,18 @@ use ldk_server_client::ldk_server_protos::api::{ Bolt12ReceiveRequest, Bolt12ReceiveResponse, Bolt12SendRequest, Bolt12SendResponse, CloseChannelRequest, CloseChannelResponse, ForceCloseChannelRequest, ForceCloseChannelResponse, GetBalancesRequest, GetBalancesResponse, GetNodeInfoRequest, GetNodeInfoResponse, - ListChannelsRequest, ListChannelsResponse, ListPaymentsRequest, ListPaymentsResponse, - OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, - OpenChannelRequest, OpenChannelResponse, SpliceInRequest, SpliceInResponse, SpliceOutRequest, - SpliceOutResponse, UpdateChannelConfigRequest, UpdateChannelConfigResponse, + GetPaymentDetailsRequest, GetPaymentDetailsResponse, ListChannelsRequest, ListChannelsResponse, + ListForwardedPaymentsRequest, ListPaymentsRequest, OnchainReceiveRequest, + OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, OpenChannelRequest, + OpenChannelResponse, SpliceInRequest, SpliceInResponse, SpliceOutRequest, SpliceOutResponse, + UpdateChannelConfigRequest, UpdateChannelConfigResponse, }; use ldk_server_client::ldk_server_protos::types::{ bolt11_invoice_description, Bolt11InvoiceDescription, ChannelConfig, PageToken, RouteParametersConfig, }; use serde::Serialize; -use types::CliListPaymentsResponse; +use types::{CliListForwardedPaymentsResponse, CliListPaymentsResponse, CliPaginatedResponse}; mod config; mod types; @@ -293,7 +294,22 @@ enum Commands { #[arg(help = "Page token to continue from a previous page (format: token:index)")] page_token: Option, }, - #[command(about = "Update the config for a previously opened channel")] + #[command(about = "Get details of a specific payment by its payment ID")] + GetPaymentDetails { + #[arg(short, long, help = "The payment ID in hex-encoded form")] + payment_id: String, + }, + #[command(about = "Retrieves list of all forwarded payments")] + ListForwardedPayments { + #[arg( + short, + long, + help = "Fetch at least this many forwarded payments by iterating through multiple pages. Returns combined results with the last page token. If not provided, returns only a single page." + )] + number_of_payments: Option, + #[arg(long, help = "Page token to continue from a previous page (format: token:index)")] + page_token: Option, + }, UpdateChannelConfig { #[arg(short, long, help = "The local user_channel_id of this channel")] user_channel_id: String, @@ -584,7 +600,36 @@ async fn main() { .map(|token_str| parse_page_token(&token_str).unwrap_or_else(|e| handle_error(e))); handle_response_result::<_, CliListPaymentsResponse>( - handle_list_payments(client, number_of_payments, page_token).await, + fetch_paginated( + number_of_payments, + page_token, + |pt| client.list_payments(ListPaymentsRequest { page_token: pt }), + |r| (r.payments, r.next_page_token), + ) + .await, + ); + }, + Commands::GetPaymentDetails { payment_id } => { + handle_response_result::<_, GetPaymentDetailsResponse>( + client.get_payment_details(GetPaymentDetailsRequest { payment_id }).await, + ); + }, + Commands::ListForwardedPayments { number_of_payments, page_token } => { + let page_token = page_token + .map(|token_str| parse_page_token(&token_str).unwrap_or_else(|e| handle_error(e))); + + handle_response_result::<_, CliListForwardedPaymentsResponse>( + fetch_paginated( + number_of_payments, + page_token, + |pt| { + client.list_forwarded_payments(ListForwardedPaymentsRequest { + page_token: pt, + }) + }, + |r| (r.forwarded_payments, r.next_page_token), + ) + .await, ); }, Commands::UpdateChannelConfig { @@ -638,37 +683,40 @@ fn build_open_channel_config( }) } -async fn handle_list_payments( - client: LdkServerClient, number_of_payments: Option, initial_page_token: Option, -) -> Result { - if let Some(count) = number_of_payments { - list_n_payments(client, count, initial_page_token).await - } else { - // Fetch single page - client.list_payments(ListPaymentsRequest { page_token: initial_page_token }).await - } -} - -async fn list_n_payments( - client: LdkServerClient, target_count: u64, initial_page_token: Option, -) -> Result { - let mut payments = Vec::with_capacity(target_count as usize); - let mut page_token = initial_page_token; - let mut next_page_token; - - loop { - let response = client.list_payments(ListPaymentsRequest { page_token }).await?; - - payments.extend(response.payments); - next_page_token = response.next_page_token; +async fn fetch_paginated( + target_count: Option, initial_page_token: Option, + fetch_page: impl Fn(Option) -> Fut, + extract: impl Fn(R) -> (Vec, Option), +) -> Result, LdkServerError> +where + Fut: std::future::Future>, +{ + match target_count { + Some(count) => { + let mut items = Vec::with_capacity(count as usize); + let mut page_token = initial_page_token; + let mut next_page_token; + + loop { + let response = fetch_page(page_token).await?; + let (new_items, new_next_page_token) = extract(response); + items.extend(new_items); + next_page_token = new_next_page_token; + + if items.len() >= count as usize || next_page_token.is_none() { + break; + } + page_token = next_page_token; + } - if payments.len() >= target_count as usize || next_page_token.is_none() { - break; - } - page_token = next_page_token; + Ok(CliPaginatedResponse::new(items, next_page_token)) + }, + None => { + let response = fetch_page(initial_page_token).await?; + let (items, next_page_token) = extract(response); + Ok(CliPaginatedResponse::new(items, next_page_token)) + }, } - - Ok(ListPaymentsResponse { payments, next_page_token }) } fn handle_response_result(response: Result) diff --git a/ldk-server-cli/src/types.rs b/ldk-server-cli/src/types.rs index a482f79..e3c708d 100644 --- a/ldk-server-cli/src/types.rs +++ b/ldk-server-cli/src/types.rs @@ -13,29 +13,29 @@ //! of API responses for CLI output. These wrappers ensure that the CLI's output //! format matches what users expect and what the CLI can parse back as input. -use ldk_server_client::ldk_server_protos::api::ListPaymentsResponse; -use ldk_server_client::ldk_server_protos::types::{PageToken, Payment}; +use ldk_server_client::ldk_server_protos::types::{ForwardedPayment, PageToken, Payment}; use serde::Serialize; -/// CLI-specific wrapper for ListPaymentsResponse that formats the page token +/// CLI-specific wrapper for paginated responses that formats the page token /// as "token:idx" instead of a JSON object. #[derive(Debug, Clone, Serialize)] -pub struct CliListPaymentsResponse { - /// List of payments. - pub payments: Vec, +pub struct CliPaginatedResponse { + /// List of items. + pub list: Vec, /// Next page token formatted as "token:idx", or None if no more pages. #[serde(skip_serializing_if = "Option::is_none")] pub next_page_token: Option, } -impl From for CliListPaymentsResponse { - fn from(response: ListPaymentsResponse) -> Self { - let next_page_token = response.next_page_token.map(format_page_token); - - CliListPaymentsResponse { payments: response.payments, next_page_token } +impl CliPaginatedResponse { + pub fn new(list: Vec, next_page_token: Option) -> Self { + Self { list, next_page_token: next_page_token.map(format_page_token) } } } +pub type CliListPaymentsResponse = CliPaginatedResponse; +pub type CliListForwardedPaymentsResponse = CliPaginatedResponse; + fn format_page_token(token: PageToken) -> String { format!("{}:{}", token.token, token.index) } diff --git a/ldk-server-client/src/client.rs b/ldk-server-client/src/client.rs index f0509ab..ab3d2c2 100644 --- a/ldk-server-client/src/client.rs +++ b/ldk-server-client/src/client.rs @@ -16,16 +16,19 @@ use ldk_server_protos::api::{ Bolt12ReceiveRequest, Bolt12ReceiveResponse, Bolt12SendRequest, Bolt12SendResponse, CloseChannelRequest, CloseChannelResponse, ForceCloseChannelRequest, ForceCloseChannelResponse, GetBalancesRequest, GetBalancesResponse, GetNodeInfoRequest, GetNodeInfoResponse, - ListChannelsRequest, ListChannelsResponse, ListPaymentsRequest, ListPaymentsResponse, - OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, - OpenChannelRequest, OpenChannelResponse, SpliceInRequest, SpliceInResponse, SpliceOutRequest, - SpliceOutResponse, UpdateChannelConfigRequest, UpdateChannelConfigResponse, + GetPaymentDetailsRequest, GetPaymentDetailsResponse, ListChannelsRequest, ListChannelsResponse, + ListForwardedPaymentsRequest, ListForwardedPaymentsResponse, ListPaymentsRequest, + ListPaymentsResponse, OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, + OnchainSendResponse, OpenChannelRequest, OpenChannelResponse, SpliceInRequest, + SpliceInResponse, SpliceOutRequest, SpliceOutResponse, UpdateChannelConfigRequest, + UpdateChannelConfigResponse, }; use ldk_server_protos::endpoints::{ BOLT11_RECEIVE_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH, CLOSE_CHANNEL_PATH, FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH, - LIST_CHANNELS_PATH, LIST_PAYMENTS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, - OPEN_CHANNEL_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH, UPDATE_CHANNEL_CONFIG_PATH, + GET_PAYMENT_DETAILS_PATH, LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH, + ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH, + UPDATE_CHANNEL_CONFIG_PATH, }; use ldk_server_protos::error::{ErrorCode, ErrorResponse}; use prost::Message; @@ -231,6 +234,24 @@ impl LdkServerClient { self.post_request(&request, &url).await } + /// Retrieves payment details for a given payment id. + /// For API contract/usage, refer to docs for [`GetPaymentDetailsRequest`] and [`GetPaymentDetailsResponse`]. + pub async fn get_payment_details( + &self, request: GetPaymentDetailsRequest, + ) -> Result { + let url = format!("https://{}/{GET_PAYMENT_DETAILS_PATH}", self.base_url); + self.post_request(&request, &url).await + } + + /// Retrieves list of all forwarded payments. + /// For API contract/usage, refer to docs for [`ListForwardedPaymentsRequest`] and [`ListForwardedPaymentsResponse`]. + pub async fn list_forwarded_payments( + &self, request: ListForwardedPaymentsRequest, + ) -> Result { + let url = format!("https://{}/{LIST_FORWARDED_PAYMENTS_PATH}", self.base_url); + self.post_request(&request, &url).await + } + async fn post_request( &self, request: &Rq, url: &str, ) -> Result {