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 }, + } + } +}