Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 84 additions & 36 deletions ldk-server-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -293,7 +294,22 @@ enum Commands {
#[arg(help = "Page token to continue from a previous page (format: token:index)")]
page_token: Option<String>,
},
#[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<u64>,
#[arg(long, help = "Page token to continue from a previous page (format: token:index)")]
page_token: Option<String>,
},
UpdateChannelConfig {
#[arg(short, long, help = "The local user_channel_id of this channel")]
user_channel_id: String,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -638,37 +683,40 @@ fn build_open_channel_config(
})
}

async fn handle_list_payments(
client: LdkServerClient, number_of_payments: Option<u64>, initial_page_token: Option<PageToken>,
) -> Result<ListPaymentsResponse, LdkServerError> {
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<PageToken>,
) -> Result<ListPaymentsResponse, LdkServerError> {
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<T, R, Fut>(
target_count: Option<u64>, initial_page_token: Option<PageToken>,
fetch_page: impl Fn(Option<PageToken>) -> Fut,
extract: impl Fn(R) -> (Vec<T>, Option<PageToken>),
) -> Result<CliPaginatedResponse<T>, LdkServerError>
where
Fut: std::future::Future<Output = Result<R, LdkServerError>>,
{
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<Rs, Js>(response: Result<Rs, LdkServerError>)
Expand Down
22 changes: 11 additions & 11 deletions ldk-server-cli/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Payment>,
pub struct CliPaginatedResponse<T> {
/// List of items.
pub list: Vec<T>,
/// 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<String>,
}

impl From<ListPaymentsResponse> 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<T> CliPaginatedResponse<T> {
pub fn new(list: Vec<T>, next_page_token: Option<PageToken>) -> Self {
Self { list, next_page_token: next_page_token.map(format_page_token) }
}
}

pub type CliListPaymentsResponse = CliPaginatedResponse<Payment>;
pub type CliListForwardedPaymentsResponse = CliPaginatedResponse<ForwardedPayment>;

fn format_page_token(token: PageToken) -> String {
format!("{}:{}", token.token, token.index)
}
33 changes: 27 additions & 6 deletions ldk-server-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<GetPaymentDetailsResponse, LdkServerError> {
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<ListForwardedPaymentsResponse, LdkServerError> {
let url = format!("https://{}/{LIST_FORWARDED_PAYMENTS_PATH}", self.base_url);
self.post_request(&request, &url).await
}

async fn post_request<Rq: Message, Rs: Message + Default>(
&self, request: &Rq, url: &str,
) -> Result<Rs, LdkServerError> {
Expand Down