From ea7e6255375ea83b5654cd8f5c4c3aa8232548e2 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Mon, 8 Dec 2025 18:32:04 -0600 Subject: [PATCH] Make cli output json Previously we would output the debug format of each of our return times which worked, but was not very usable. You could not easily pipe results with cli tools or anything like that. This changes the results to be output to json to make it easier to use and more similiar to what people expect. This had one annoyance because since our structs are generated using prost, we cannot just simply add the serde traits to the structs, so instead we recreate the types we are going to output in the cli and covert them. --- Cargo.lock | 4 +- ldk-server-cli/Cargo.toml | 4 +- ldk-server-cli/src/main.rs | 57 ++- ldk-server-cli/src/types.rs | 736 ++++++++++++++++++++++++++++++++++++ 4 files changed, 783 insertions(+), 18 deletions(-) create mode 100644 ldk-server-cli/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 60a3b4d..d96df13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1736,8 +1736,10 @@ name = "ldk-server-cli" version = "0.1.0" dependencies = [ "clap", + "hex-conservative 0.2.1", "ldk-server-client", - "prost", + "serde", + "serde_json", "tokio", ] diff --git a/ldk-server-cli/Cargo.toml b/ldk-server-cli/Cargo.toml index a38ca83..8490345 100644 --- a/ldk-server-cli/Cargo.toml +++ b/ldk-server-cli/Cargo.toml @@ -7,4 +7,6 @@ edition = "2021" ldk-server-client = { path = "../ldk-server-client" } clap = { version = "4.0.5", default-features = false, features = ["derive", "std", "error-context", "suggestions", "help"] } tokio = { version = "1.38.0", default-features = false, features = ["rt-multi-thread", "macros"] } -prost = { version = "0.11.6", default-features = false} +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +hex-conservative = "0.2.1" diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index a45ec6e..7a50bd3 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -1,3 +1,5 @@ +mod types; + use clap::{Parser, Subcommand}; use ldk_server_client::client::LdkServerClient; use ldk_server_client::error::LdkServerError; @@ -13,7 +15,7 @@ use ldk_server_client::ldk_server_protos::api::{ use ldk_server_client::ldk_server_protos::types::{ bolt11_invoice_description, Bolt11InvoiceDescription, PageToken, Payment, }; -use std::fmt::Debug; +use serde::Serialize; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -119,16 +121,22 @@ async fn main() { match cli.command { Commands::GetNodeInfo => { - handle_response_result(client.get_node_info(GetNodeInfoRequest {}).await); + handle_response_result::<_, types::GetNodeInfoResponse>( + client.get_node_info(GetNodeInfoRequest {}).await, + ); }, Commands::GetBalances => { - handle_response_result(client.get_balances(GetBalancesRequest {}).await); + handle_response_result::<_, types::GetBalancesResponse>( + client.get_balances(GetBalancesRequest {}).await, + ); }, Commands::OnchainReceive => { - handle_response_result(client.onchain_receive(OnchainReceiveRequest {}).await); + handle_response_result::<_, types::OnchainReceiveResponse>( + client.onchain_receive(OnchainReceiveRequest {}).await, + ); }, Commands::OnchainSend { address, amount_sats, send_all, fee_rate_sat_per_vb } => { - handle_response_result( + handle_response_result::<_, types::OnchainSendResponse>( client .onchain_send(OnchainSendRequest { address, @@ -159,15 +167,17 @@ async fn main() { let request = Bolt11ReceiveRequest { description: invoice_description, expiry_secs, amount_msat }; - handle_response_result(client.bolt11_receive(request).await); + handle_response_result::<_, types::Bolt11ReceiveResponse>( + client.bolt11_receive(request).await, + ); }, Commands::Bolt11Send { invoice, amount_msat } => { - handle_response_result( + handle_response_result::<_, types::Bolt11SendResponse>( client.bolt11_send(Bolt11SendRequest { invoice, amount_msat }).await, ); }, Commands::Bolt12Receive { description, amount_msat, expiry_secs, quantity } => { - handle_response_result( + handle_response_result::<_, types::Bolt12ReceiveResponse>( client .bolt12_receive(Bolt12ReceiveRequest { description, @@ -179,14 +189,14 @@ async fn main() { ); }, Commands::Bolt12Send { offer, amount_msat, quantity, payer_note } => { - handle_response_result( + handle_response_result::<_, types::Bolt12SendResponse>( client .bolt12_send(Bolt12SendRequest { offer, amount_msat, quantity, payer_note }) .await, ); }, Commands::CloseChannel { user_channel_id, counterparty_node_id } => { - handle_response_result( + handle_response_result::<_, types::CloseChannelResponse>( client .close_channel(CloseChannelRequest { user_channel_id, counterparty_node_id }) .await, @@ -197,7 +207,7 @@ async fn main() { counterparty_node_id, force_close_reason, } => { - handle_response_result( + handle_response_result::<_, types::ForceCloseChannelResponse>( client .force_close_channel(ForceCloseChannelRequest { user_channel_id, @@ -214,7 +224,7 @@ async fn main() { push_to_counterparty_msat, announce_channel, } => { - handle_response_result( + handle_response_result::<_, types::OpenChannelResponse>( client .open_channel(OpenChannelRequest { node_pubkey, @@ -228,10 +238,14 @@ async fn main() { ); }, Commands::ListChannels => { - handle_response_result(client.list_channels(ListChannelsRequest {}).await); + handle_response_result::<_, types::ListChannelsResponse>( + client.list_channels(ListChannelsRequest {}).await, + ); }, Commands::ListPayments { number_of_payments } => { - handle_response_result(list_n_payments(client, number_of_payments).await); + handle_response_result::<_, types::ListPaymentsResponse>( + list_n_payments(client, number_of_payments).await, + ); }, } } @@ -256,10 +270,21 @@ async fn list_n_payments( Ok(payments) } -fn handle_response_result(response: Result) { +fn handle_response_result(response: Result) +where + Rs: Into, + Js: Serialize + std::fmt::Debug, +{ match response { Ok(response) => { - println!("Success: {:?}", response); + let json_response: Js = response.into(); + match serde_json::to_string_pretty(&json_response) { + Ok(json) => println!("{json}"), + Err(e) => { + eprintln!("Error serializing response ({json_response:?}) to JSON: {e}"); + std::process::exit(1); + }, + } }, Err(e) => { handle_error(e); diff --git a/ldk-server-cli/src/types.rs b/ldk-server-cli/src/types.rs new file mode 100644 index 0000000..7386c40 --- /dev/null +++ b/ldk-server-cli/src/types.rs @@ -0,0 +1,736 @@ +use hex_conservative::DisplayHex; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct GetNodeInfoResponse { + pub node_id: String, + pub current_best_block: BestBlock, + #[serde(skip_serializing_if = "Option::is_none")] + pub latest_lightning_wallet_sync_timestamp: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub latest_onchain_wallet_sync_timestamp: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub latest_fee_rate_cache_update_timestamp: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub latest_rgs_snapshot_timestamp: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub latest_node_announcement_broadcast_timestamp: Option, +} + +impl From for GetNodeInfoResponse { + fn from(proto: ldk_server_client::ldk_server_protos::api::GetNodeInfoResponse) -> Self { + Self { + node_id: proto.node_id, + current_best_block: proto + .current_best_block + .map(Into::into) + .unwrap_or(BestBlock { block_hash: String::new(), height: 0 }), + latest_lightning_wallet_sync_timestamp: proto.latest_lightning_wallet_sync_timestamp, + latest_onchain_wallet_sync_timestamp: proto.latest_onchain_wallet_sync_timestamp, + latest_fee_rate_cache_update_timestamp: proto.latest_fee_rate_cache_update_timestamp, + latest_rgs_snapshot_timestamp: proto.latest_rgs_snapshot_timestamp, + latest_node_announcement_broadcast_timestamp: proto + .latest_node_announcement_broadcast_timestamp, + } + } +} + +#[derive(Debug, Serialize)] +pub struct BestBlock { + pub block_hash: String, + pub height: u32, +} + +impl From for BestBlock { + fn from(proto: ldk_server_client::ldk_server_protos::types::BestBlock) -> Self { + Self { block_hash: proto.block_hash, height: proto.height } + } +} + +#[derive(Debug, Serialize)] +pub struct OnchainReceiveResponse { + pub address: String, +} + +impl From + for OnchainReceiveResponse +{ + fn from(proto: ldk_server_client::ldk_server_protos::api::OnchainReceiveResponse) -> Self { + Self { address: proto.address } + } +} + +#[derive(Debug, Serialize)] +pub struct OnchainSendResponse { + pub txid: String, +} + +impl From for OnchainSendResponse { + fn from(proto: ldk_server_client::ldk_server_protos::api::OnchainSendResponse) -> Self { + Self { txid: proto.txid } + } +} + +#[derive(Debug, Serialize)] +pub struct Bolt11ReceiveResponse { + pub invoice: String, +} + +impl From + for Bolt11ReceiveResponse +{ + fn from(proto: ldk_server_client::ldk_server_protos::api::Bolt11ReceiveResponse) -> Self { + Self { invoice: proto.invoice } + } +} + +#[derive(Debug, Serialize)] +pub struct Bolt11SendResponse { + pub payment_id: String, +} + +impl From for Bolt11SendResponse { + fn from(proto: ldk_server_client::ldk_server_protos::api::Bolt11SendResponse) -> Self { + Self { payment_id: proto.payment_id } + } +} + +#[derive(Debug, Serialize)] +pub struct Bolt12ReceiveResponse { + pub offer: String, +} + +impl From + for Bolt12ReceiveResponse +{ + fn from(proto: ldk_server_client::ldk_server_protos::api::Bolt12ReceiveResponse) -> Self { + Self { offer: proto.offer } + } +} + +#[derive(Debug, Serialize)] +pub struct Bolt12SendResponse { + pub payment_id: String, +} + +impl From for Bolt12SendResponse { + fn from(proto: ldk_server_client::ldk_server_protos::api::Bolt12SendResponse) -> Self { + Self { payment_id: proto.payment_id } + } +} + +#[derive(Debug, Serialize)] +pub struct OpenChannelResponse { + pub user_channel_id: String, +} + +impl From for OpenChannelResponse { + fn from(proto: ldk_server_client::ldk_server_protos::api::OpenChannelResponse) -> Self { + Self { user_channel_id: proto.user_channel_id } + } +} + +#[derive(Debug, Serialize)] +pub struct CloseChannelResponse {} + +impl From + for CloseChannelResponse +{ + fn from(_proto: ldk_server_client::ldk_server_protos::api::CloseChannelResponse) -> Self { + Self {} + } +} + +#[derive(Debug, Serialize)] +pub struct ForceCloseChannelResponse {} + +impl From + for ForceCloseChannelResponse +{ + fn from(_proto: ldk_server_client::ldk_server_protos::api::ForceCloseChannelResponse) -> Self { + Self {} + } +} + +#[derive(Debug, Serialize)] +pub struct ListChannelsResponse { + pub channels: Vec, +} + +impl From + for ListChannelsResponse +{ + fn from(proto: ldk_server_client::ldk_server_protos::api::ListChannelsResponse) -> Self { + Self { channels: proto.channels.into_iter().map(Into::into).collect() } + } +} + +#[derive(Debug, Serialize)] +pub struct Channel { + pub channel_id: String, + pub counterparty_node_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub funding_txo: Option, + pub user_channel_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub unspendable_punishment_reserve: Option, + pub channel_value_sats: u64, + pub feerate_sat_per_1000_weight: u32, + pub outbound_capacity_msat: u64, + pub inbound_capacity_msat: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub confirmations_required: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub confirmations: Option, + pub is_outbound: bool, + pub is_channel_ready: bool, + pub is_usable: bool, + pub is_announced: bool, + pub channel_config: ChannelConfig, + pub next_outbound_htlc_limit_msat: u64, + pub next_outbound_htlc_minimum_msat: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub force_close_spend_delay: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub counterparty_outbound_htlc_minimum_msat: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub counterparty_outbound_htlc_maximum_msat: Option, + pub counterparty_unspendable_punishment_reserve: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub counterparty_forwarding_info_fee_base_msat: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub counterparty_forwarding_info_fee_proportional_millionths: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub counterparty_forwarding_info_cltv_expiry_delta: Option, +} + +impl From for Channel { + fn from(proto: ldk_server_client::ldk_server_protos::types::Channel) -> Self { + Self { + channel_id: proto.channel_id, + counterparty_node_id: proto.counterparty_node_id, + funding_txo: proto.funding_txo.map(Into::into), + user_channel_id: proto.user_channel_id, + unspendable_punishment_reserve: proto.unspendable_punishment_reserve, + channel_value_sats: proto.channel_value_sats, + feerate_sat_per_1000_weight: proto.feerate_sat_per_1000_weight, + outbound_capacity_msat: proto.outbound_capacity_msat, + inbound_capacity_msat: proto.inbound_capacity_msat, + confirmations_required: proto.confirmations_required, + confirmations: proto.confirmations, + is_outbound: proto.is_outbound, + is_channel_ready: proto.is_channel_ready, + is_usable: proto.is_usable, + is_announced: proto.is_announced, + channel_config: proto.channel_config.map(Into::into).unwrap_or(ChannelConfig { + forwarding_fee_proportional_millionths: None, + forwarding_fee_base_msat: None, + cltv_expiry_delta: None, + force_close_avoidance_max_fee_satoshis: None, + accept_underpaying_htlcs: None, + max_dust_htlc_exposure: None, + }), + next_outbound_htlc_limit_msat: proto.next_outbound_htlc_limit_msat, + next_outbound_htlc_minimum_msat: proto.next_outbound_htlc_minimum_msat, + force_close_spend_delay: proto.force_close_spend_delay, + counterparty_outbound_htlc_minimum_msat: proto.counterparty_outbound_htlc_minimum_msat, + counterparty_outbound_htlc_maximum_msat: proto.counterparty_outbound_htlc_maximum_msat, + counterparty_unspendable_punishment_reserve: proto + .counterparty_unspendable_punishment_reserve, + counterparty_forwarding_info_fee_base_msat: proto + .counterparty_forwarding_info_fee_base_msat, + counterparty_forwarding_info_fee_proportional_millionths: proto + .counterparty_forwarding_info_fee_proportional_millionths, + counterparty_forwarding_info_cltv_expiry_delta: proto + .counterparty_forwarding_info_cltv_expiry_delta, + } + } +} + +#[derive(Debug, Serialize)] +pub struct OutPoint { + pub txid: String, + pub vout: u32, +} + +impl From for OutPoint { + fn from(proto: ldk_server_client::ldk_server_protos::types::OutPoint) -> Self { + Self { txid: proto.txid, vout: proto.vout } + } +} + +#[derive(Debug, Serialize)] +pub struct ChannelConfig { + #[serde(skip_serializing_if = "Option::is_none")] + pub forwarding_fee_proportional_millionths: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub forwarding_fee_base_msat: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cltv_expiry_delta: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub force_close_avoidance_max_fee_satoshis: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub accept_underpaying_htlcs: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub max_dust_htlc_exposure: Option, +} + +impl From for ChannelConfig { + fn from(proto: ldk_server_client::ldk_server_protos::types::ChannelConfig) -> Self { + use ldk_server_client::ldk_server_protos::types::channel_config::MaxDustHtlcExposure as ProtoMaxDust; + + let max_dust_htlc_exposure = proto.max_dust_htlc_exposure.map(|exposure| match exposure { + ProtoMaxDust::FixedLimitMsat(val) => MaxDustHtlcExposure::FixedLimitMsat(val), + ProtoMaxDust::FeeRateMultiplier(val) => MaxDustHtlcExposure::FeeRateMultiplier(val), + }); + + Self { + forwarding_fee_proportional_millionths: proto.forwarding_fee_proportional_millionths, + forwarding_fee_base_msat: proto.forwarding_fee_base_msat, + cltv_expiry_delta: proto.cltv_expiry_delta, + force_close_avoidance_max_fee_satoshis: proto.force_close_avoidance_max_fee_satoshis, + accept_underpaying_htlcs: proto.accept_underpaying_htlcs, + max_dust_htlc_exposure, + } + } +} + +#[derive(Debug, Serialize)] +#[serde(tag = "type", content = "value")] +pub enum MaxDustHtlcExposure { + #[serde(rename = "fixed_limit_msat")] + FixedLimitMsat(u64), + #[serde(rename = "fee_rate_multiplier")] + FeeRateMultiplier(u64), +} + +#[derive(Debug, Serialize)] +pub struct ListPaymentsResponse { + pub payments: Vec, +} + +impl From> for ListPaymentsResponse { + fn from(payments: Vec) -> Self { + Self { payments: payments.into_iter().map(Into::into).collect() } + } +} + +#[derive(Debug, Serialize)] +pub struct Payment { + pub id: String, + pub kind: PaymentKind, + #[serde(skip_serializing_if = "Option::is_none")] + pub amount_msat: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub fee_paid_msat: Option, + pub direction: PaymentDirection, + pub status: PaymentStatus, + pub latest_update_timestamp: u64, +} + +impl From for Payment { + fn from(proto: ldk_server_client::ldk_server_protos::types::Payment) -> Self { + Self { + id: proto.id, + kind: proto + .kind + .map(Into::into) + .unwrap_or(PaymentKind::Spontaneous { hash: String::new(), preimage: None }), + amount_msat: proto.amount_msat, + fee_paid_msat: proto.fee_paid_msat, + direction: match proto.direction { + 0 => PaymentDirection::Inbound, + 1 => PaymentDirection::Outbound, + _ => PaymentDirection::Inbound, + }, + status: match proto.status { + 0 => PaymentStatus::Pending, + 1 => PaymentStatus::Succeeded, + 2 => PaymentStatus::Failed, + _ => PaymentStatus::Pending, + }, + latest_update_timestamp: proto.latest_update_timestamp, + } + } +} + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +pub enum PaymentKind { + #[serde(rename = "onchain")] + Onchain { txid: String, status: ConfirmationStatus }, + #[serde(rename = "bolt11")] + Bolt11 { + hash: String, + #[serde(skip_serializing_if = "Option::is_none")] + preimage: Option, + #[serde(skip_serializing_if = "Option::is_none")] + secret: Option, + }, + #[serde(rename = "bolt11_jit")] + Bolt11Jit { + hash: String, + #[serde(skip_serializing_if = "Option::is_none")] + preimage: Option, + #[serde(skip_serializing_if = "Option::is_none")] + secret: Option, + lsp_fee_limits: LspFeeLimits, + #[serde(skip_serializing_if = "Option::is_none")] + counterparty_skimmed_fee_msat: Option, + }, + #[serde(rename = "bolt12_offer")] + Bolt12Offer { + #[serde(skip_serializing_if = "Option::is_none")] + hash: Option, + #[serde(skip_serializing_if = "Option::is_none")] + preimage: Option, + #[serde(skip_serializing_if = "Option::is_none")] + secret: Option, + offer_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + payer_note: Option, + #[serde(skip_serializing_if = "Option::is_none")] + quantity: Option, + }, + #[serde(rename = "bolt12_refund")] + Bolt12Refund { + #[serde(skip_serializing_if = "Option::is_none")] + hash: Option, + #[serde(skip_serializing_if = "Option::is_none")] + preimage: Option, + #[serde(skip_serializing_if = "Option::is_none")] + secret: Option, + #[serde(skip_serializing_if = "Option::is_none")] + payer_note: Option, + #[serde(skip_serializing_if = "Option::is_none")] + quantity: Option, + }, + #[serde(rename = "spontaneous")] + Spontaneous { + hash: String, + #[serde(skip_serializing_if = "Option::is_none")] + preimage: Option, + }, +} + +impl From for PaymentKind { + fn from(proto: ldk_server_client::ldk_server_protos::types::PaymentKind) -> Self { + use ldk_server_client::ldk_server_protos::types::payment_kind::Kind; + + match proto.kind { + Some(Kind::Onchain(onchain)) => PaymentKind::Onchain { + txid: onchain.txid, + status: onchain + .status + .map(Into::into) + .unwrap_or(ConfirmationStatus::Unconfirmed {}), + }, + Some(Kind::Bolt11(bolt11)) => PaymentKind::Bolt11 { + hash: bolt11.hash, + preimage: bolt11.preimage, + secret: bolt11.secret.map(|s| s.to_lower_hex_string()), + }, + Some(Kind::Bolt11Jit(bolt11_jit)) => PaymentKind::Bolt11Jit { + hash: bolt11_jit.hash, + preimage: bolt11_jit.preimage, + secret: bolt11_jit.secret.map(|s| s.to_lower_hex_string()), + lsp_fee_limits: bolt11_jit.lsp_fee_limits.map(Into::into).unwrap_or(LspFeeLimits { + max_total_opening_fee_msat: None, + max_proportional_opening_fee_ppm_msat: None, + }), + counterparty_skimmed_fee_msat: bolt11_jit.counterparty_skimmed_fee_msat, + }, + Some(Kind::Bolt12Offer(bolt12_offer)) => PaymentKind::Bolt12Offer { + hash: bolt12_offer.hash, + preimage: bolt12_offer.preimage, + secret: bolt12_offer.secret.map(|s| s.to_lower_hex_string()), + offer_id: bolt12_offer.offer_id, + payer_note: bolt12_offer.payer_note, + quantity: bolt12_offer.quantity, + }, + Some(Kind::Bolt12Refund(bolt12_refund)) => PaymentKind::Bolt12Refund { + hash: bolt12_refund.hash, + preimage: bolt12_refund.preimage, + secret: bolt12_refund.secret.map(|s| s.to_lower_hex_string()), + payer_note: bolt12_refund.payer_note, + quantity: bolt12_refund.quantity, + }, + Some(Kind::Spontaneous(spontaneous)) => { + PaymentKind::Spontaneous { hash: spontaneous.hash, preimage: spontaneous.preimage } + }, + None => PaymentKind::Spontaneous { hash: String::new(), preimage: None }, + } + } +} + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +pub enum ConfirmationStatus { + #[serde(rename = "confirmed")] + Confirmed { block_hash: String, height: u32, timestamp: u64 }, + #[serde(rename = "unconfirmed")] + Unconfirmed {}, +} + +impl From for ConfirmationStatus { + fn from(proto: ldk_server_client::ldk_server_protos::types::ConfirmationStatus) -> Self { + use ldk_server_client::ldk_server_protos::types::confirmation_status::Status; + + match proto.status { + Some(Status::Confirmed(confirmed)) => ConfirmationStatus::Confirmed { + block_hash: confirmed.block_hash, + height: confirmed.height, + timestamp: confirmed.timestamp, + }, + Some(Status::Unconfirmed(_)) | None => ConfirmationStatus::Unconfirmed {}, + } + } +} + +#[derive(Debug, Serialize)] +pub struct LspFeeLimits { + #[serde(skip_serializing_if = "Option::is_none")] + pub max_total_opening_fee_msat: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub max_proportional_opening_fee_ppm_msat: Option, +} + +impl From for LspFeeLimits { + fn from(proto: ldk_server_client::ldk_server_protos::types::LspFeeLimits) -> Self { + Self { + max_total_opening_fee_msat: proto.max_total_opening_fee_msat, + max_proportional_opening_fee_ppm_msat: proto.max_proportional_opening_fee_ppm_msat, + } + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum PaymentDirection { + Inbound, + Outbound, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum PaymentStatus { + Pending, + Succeeded, + Failed, +} + +#[derive(Debug, Serialize)] +pub struct GetBalancesResponse { + pub total_onchain_balance_sats: u64, + pub spendable_onchain_balance_sats: u64, + pub total_anchor_channels_reserve_sats: u64, + pub total_lightning_balance_sats: u64, + pub lightning_balances: Vec, + pub pending_balances_from_channel_closures: Vec, +} + +impl From for GetBalancesResponse { + fn from(proto: ldk_server_client::ldk_server_protos::api::GetBalancesResponse) -> Self { + Self { + total_onchain_balance_sats: proto.total_onchain_balance_sats, + spendable_onchain_balance_sats: proto.spendable_onchain_balance_sats, + total_anchor_channels_reserve_sats: proto.total_anchor_channels_reserve_sats, + total_lightning_balance_sats: proto.total_lightning_balance_sats, + lightning_balances: proto.lightning_balances.into_iter().map(Into::into).collect(), + pending_balances_from_channel_closures: proto + .pending_balances_from_channel_closures + .into_iter() + .map(Into::into) + .collect(), + } + } +} + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +pub enum LightningBalance { + #[serde(rename = "claimable_on_channel_close")] + ClaimableOnChannelClose { + channel_id: String, + counterparty_node_id: String, + amount_satoshis: u64, + transaction_fee_satoshis: u64, + outbound_payment_htlc_rounded_msat: u64, + outbound_forwarded_htlc_rounded_msat: u64, + inbound_claiming_htlc_rounded_msat: u64, + inbound_htlc_rounded_msat: u64, + }, + #[serde(rename = "claimable_awaiting_confirmations")] + ClaimableAwaitingConfirmations { + channel_id: String, + counterparty_node_id: String, + amount_satoshis: u64, + confirmation_height: u32, + }, + #[serde(rename = "contentious_claimable")] + ContentiousClaimable { + channel_id: String, + counterparty_node_id: String, + amount_satoshis: u64, + timeout_height: u32, + payment_hash: String, + payment_preimage: String, + }, + #[serde(rename = "maybe_timeout_claimable_htlc")] + MaybeTimeoutClaimableHtlc { + channel_id: String, + counterparty_node_id: String, + amount_satoshis: u64, + claimable_height: u32, + payment_hash: String, + outbound_payment: bool, + }, + #[serde(rename = "maybe_preimage_claimable_htlc")] + MaybePreimageClaimableHtlc { + channel_id: String, + counterparty_node_id: String, + amount_satoshis: u64, + expiry_height: u32, + payment_hash: String, + }, + #[serde(rename = "counterparty_revoked_output_claimable")] + CounterpartyRevokedOutputClaimable { + channel_id: String, + counterparty_node_id: String, + amount_satoshis: u64, + }, +} + +impl From for LightningBalance { + fn from(proto: ldk_server_client::ldk_server_protos::types::LightningBalance) -> Self { + use ldk_server_client::ldk_server_protos::types::lightning_balance::BalanceType; + + match proto.balance_type { + Some(BalanceType::ClaimableOnChannelClose(b)) => { + LightningBalance::ClaimableOnChannelClose { + channel_id: b.channel_id, + counterparty_node_id: b.counterparty_node_id, + amount_satoshis: b.amount_satoshis, + transaction_fee_satoshis: b.transaction_fee_satoshis, + outbound_payment_htlc_rounded_msat: b.outbound_payment_htlc_rounded_msat, + outbound_forwarded_htlc_rounded_msat: b.outbound_forwarded_htlc_rounded_msat, + inbound_claiming_htlc_rounded_msat: b.inbound_claiming_htlc_rounded_msat, + inbound_htlc_rounded_msat: b.inbound_htlc_rounded_msat, + } + }, + Some(BalanceType::ClaimableAwaitingConfirmations(b)) => { + LightningBalance::ClaimableAwaitingConfirmations { + channel_id: b.channel_id, + counterparty_node_id: b.counterparty_node_id, + amount_satoshis: b.amount_satoshis, + confirmation_height: b.confirmation_height, + } + }, + Some(BalanceType::ContentiousClaimable(b)) => LightningBalance::ContentiousClaimable { + channel_id: b.channel_id, + counterparty_node_id: b.counterparty_node_id, + amount_satoshis: b.amount_satoshis, + timeout_height: b.timeout_height, + payment_hash: b.payment_hash, + payment_preimage: b.payment_preimage, + }, + Some(BalanceType::MaybeTimeoutClaimableHtlc(b)) => { + LightningBalance::MaybeTimeoutClaimableHtlc { + channel_id: b.channel_id, + counterparty_node_id: b.counterparty_node_id, + amount_satoshis: b.amount_satoshis, + claimable_height: b.claimable_height, + payment_hash: b.payment_hash, + outbound_payment: b.outbound_payment, + } + }, + Some(BalanceType::MaybePreimageClaimableHtlc(b)) => { + LightningBalance::MaybePreimageClaimableHtlc { + channel_id: b.channel_id, + counterparty_node_id: b.counterparty_node_id, + amount_satoshis: b.amount_satoshis, + expiry_height: b.expiry_height, + payment_hash: b.payment_hash, + } + }, + Some(BalanceType::CounterpartyRevokedOutputClaimable(b)) => { + LightningBalance::CounterpartyRevokedOutputClaimable { + channel_id: b.channel_id, + counterparty_node_id: b.counterparty_node_id, + amount_satoshis: b.amount_satoshis, + } + }, + None => LightningBalance::ClaimableOnChannelClose { + channel_id: String::new(), + counterparty_node_id: String::new(), + amount_satoshis: 0, + transaction_fee_satoshis: 0, + outbound_payment_htlc_rounded_msat: 0, + outbound_forwarded_htlc_rounded_msat: 0, + inbound_claiming_htlc_rounded_msat: 0, + inbound_htlc_rounded_msat: 0, + }, + } + } +} + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +pub enum PendingSweepBalance { + #[serde(rename = "pending_broadcast")] + PendingBroadcast { + #[serde(skip_serializing_if = "Option::is_none")] + channel_id: Option, + amount_satoshis: u64, + }, + #[serde(rename = "broadcast_awaiting_confirmation")] + BroadcastAwaitingConfirmation { + #[serde(skip_serializing_if = "Option::is_none")] + channel_id: Option, + latest_broadcast_height: u32, + latest_spending_txid: String, + amount_satoshis: u64, + }, + #[serde(rename = "awaiting_threshold_confirmations")] + AwaitingThresholdConfirmations { + #[serde(skip_serializing_if = "Option::is_none")] + channel_id: Option, + latest_spending_txid: String, + confirmation_hash: String, + confirmation_height: u32, + amount_satoshis: u64, + }, +} + +impl From + for PendingSweepBalance +{ + fn from(proto: ldk_server_client::ldk_server_protos::types::PendingSweepBalance) -> Self { + use ldk_server_client::ldk_server_protos::types::pending_sweep_balance::BalanceType; + + match proto.balance_type { + Some(BalanceType::PendingBroadcast(b)) => PendingSweepBalance::PendingBroadcast { + channel_id: b.channel_id, + amount_satoshis: b.amount_satoshis, + }, + Some(BalanceType::BroadcastAwaitingConfirmation(b)) => { + PendingSweepBalance::BroadcastAwaitingConfirmation { + channel_id: b.channel_id, + latest_broadcast_height: b.latest_broadcast_height, + latest_spending_txid: b.latest_spending_txid, + amount_satoshis: b.amount_satoshis, + } + }, + Some(BalanceType::AwaitingThresholdConfirmations(b)) => { + PendingSweepBalance::AwaitingThresholdConfirmations { + channel_id: b.channel_id, + latest_spending_txid: b.latest_spending_txid, + confirmation_hash: b.confirmation_hash, + confirmation_height: b.confirmation_height, + amount_satoshis: b.amount_satoshis, + } + }, + None => PendingSweepBalance::PendingBroadcast { channel_id: None, amount_satoshis: 0 }, + } + } +}