diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 3c240b43c..ed9fbf5bc 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -803,6 +803,11 @@ interface Bolt12Invoice { sequence encode(); }; +interface PaymentPreimage { + [Throws=NodeError, Name=from_str] + constructor([ByRef] string preimage_str); +}; + [Custom] typedef string Txid; @@ -830,9 +835,6 @@ typedef string PaymentId; [Custom] typedef string PaymentHash; -[Custom] -typedef string PaymentPreimage; - [Custom] typedef string PaymentSecret; diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 32464d044..b10ee836c 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -19,7 +19,7 @@ where } #[cfg(feature = "uniffi")] -pub fn maybe_try_convert_enum(wrapped_type: &T) -> Result +pub fn maybe_try_from(wrapped_type: &T) -> Result where for<'a> R: TryFrom<&'a T, Error = crate::error::Error>, { @@ -37,7 +37,7 @@ pub fn maybe_deref(value: &T) -> &T { } #[cfg(not(feature = "uniffi"))] -pub fn maybe_try_convert_enum(value: &T) -> Result<&T, crate::error::Error> { +pub fn maybe_try_from(value: &T) -> Result<&T, crate::error::Error> { Ok(value) } diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 984e4da8f..3a3c51907 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -10,15 +10,18 @@ // // Make sure to add any re-exported items that need to be used in uniffi below. +pub use crate::balance::LightningBalance as NodeLightningBalance; pub use crate::config::{ default_config, AnchorChannelsConfig, BackgroundSyncConfig, ElectrumSyncConfig, EsploraSyncConfig, MaxDustHTLCExposure, }; +pub use crate::event::Event as NodeEvent; pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo}; pub use crate::liquidity::{LSPS1OrderStatus, LSPS2ServiceConfig, OnchainPaymentInfo, PaymentInfo}; pub use crate::logger::{LogLevel, LogRecord, LogWriter}; pub use crate::payment::store::{ - ConfirmationStatus, LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus, + ConfirmationStatus, LSPFeeLimits, PaymentDirection, PaymentKind as NodePaymentKind, + PaymentStatus, }; pub use crate::payment::{MaxTotalRoutingFeeLimit, QrPaymentResult, SendingParameters}; @@ -29,7 +32,9 @@ pub use lightning::offers::offer::OfferId; pub use lightning::routing::gossip::{NodeAlias, NodeId, RoutingFees}; pub use lightning::util::string::UntrustedString; -pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; +pub use lightning_types::payment::{ + PaymentHash, PaymentPreimage as LdkPaymentPreimage, PaymentSecret, +}; pub use lightning_invoice::{Description, SignedRawBolt11Invoice}; @@ -665,21 +670,650 @@ impl UniffiCustomTypeConverter for PaymentHash { } } -impl UniffiCustomTypeConverter for PaymentPreimage { - type Builtin = String; +/// The payment preimage is the "secret key" which is used to claim the funds of an HTLC on-chain +/// or in a lightning channel. +#[derive(Hash, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] +pub struct PaymentPreimage { + pub(crate) inner: LdkPaymentPreimage, +} - fn into_custom(val: Self::Builtin) -> uniffi::Result { - if let Some(bytes_vec) = hex_utils::to_vec(&val) { - let bytes_res = bytes_vec.try_into(); - if let Ok(bytes) = bytes_res { - return Ok(PaymentPreimage(bytes)); +impl PaymentPreimage { + pub fn from_str(preimage_str: &str) -> Result { + preimage_str.parse() + } +} + +impl FromStr for PaymentPreimage { + type Err = Error; + + fn from_str(preimage_str: &str) -> Result { + if let Some(bytes) = hex_utils::to_vec(preimage_str) { + if let Ok(array) = bytes.try_into() { + return Ok(Self::from(LdkPaymentPreimage(array))); } } - Err(Error::InvalidPaymentPreimage.into()) + + Err(Error::InvalidPaymentPreimage) } +} - fn from_custom(obj: Self) -> Self::Builtin { - hex_utils::to_string(&obj.0) +impl From for PaymentPreimage { + fn from(preimage: LdkPaymentPreimage) -> Self { + PaymentPreimage { inner: preimage } + } +} + +impl std::fmt::Display for PaymentPreimage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.inner) + } +} + +/// Represents the kind of a payment. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PaymentKind { + Onchain { + txid: Txid, + status: Arc, + }, + Bolt11 { + hash: PaymentHash, + preimage: Arc>, + secret: Option, + }, + Bolt11Jit { + hash: PaymentHash, + preimage: Arc>, + secret: Option, + counterparty_skimmed_fee_msat: Option, + lsp_fee_limits: LSPFeeLimits, + }, + Bolt12Offer { + hash: Option, + preimage: Arc>, + secret: Option, + offer_id: OfferId, + payer_note: Option, + quantity: Option, + }, + Bolt12Refund { + hash: Option, + preimage: Arc>, + secret: Option, + payer_note: Option, + quantity: Option, + }, + Spontaneous { + hash: PaymentHash, + preimage: Arc>, + }, +} + +impl From for PaymentKind { + fn from(payment_kind: NodePaymentKind) -> Self { + match payment_kind { + NodePaymentKind::Onchain { txid, status } => { + PaymentKind::Onchain { txid, status: Arc::new(status) } + }, + NodePaymentKind::Bolt11 { hash, preimage, secret } => { + PaymentKind::Bolt11 { hash, preimage: Arc::new(preimage), secret } + }, + NodePaymentKind::Bolt11Jit { + hash, + preimage, + secret, + counterparty_skimmed_fee_msat, + lsp_fee_limits, + } => PaymentKind::Bolt11Jit { + hash, + preimage: Arc::new(preimage), + secret, + counterparty_skimmed_fee_msat, + lsp_fee_limits, + }, + NodePaymentKind::Bolt12Offer { + hash, + preimage, + secret, + offer_id, + payer_note, + quantity, + } => PaymentKind::Bolt12Offer { + hash, + preimage: Arc::new(preimage), + secret, + offer_id, + payer_note, + quantity, + }, + NodePaymentKind::Bolt12Refund { hash, preimage, secret, payer_note, quantity } => { + PaymentKind::Bolt12Refund { + hash, + preimage: Arc::new(preimage), + secret, + payer_note, + quantity, + } + }, + NodePaymentKind::Spontaneous { hash, preimage } => { + PaymentKind::Spontaneous { hash, preimage: Arc::new(preimage) } + }, + } + } +} + +impl From for NodePaymentKind { + fn from(kind: PaymentKind) -> Self { + match kind { + PaymentKind::Onchain { txid, status } => { + NodePaymentKind::Onchain { txid, status: *status } + }, + PaymentKind::Bolt11 { hash, preimage, secret } => { + NodePaymentKind::Bolt11 { hash, preimage: *preimage, secret } + }, + PaymentKind::Bolt11Jit { + hash, + preimage, + secret, + counterparty_skimmed_fee_msat, + lsp_fee_limits, + } => NodePaymentKind::Bolt11Jit { + hash, + preimage: *preimage, + secret, + counterparty_skimmed_fee_msat, + lsp_fee_limits, + }, + PaymentKind::Bolt12Offer { hash, preimage, secret, offer_id, payer_note, quantity } => { + NodePaymentKind::Bolt12Offer { + hash, + preimage: *preimage, + secret, + offer_id, + payer_note, + quantity, + } + }, + PaymentKind::Bolt12Refund { hash, preimage, secret, payer_note, quantity } => { + NodePaymentKind::Bolt12Refund { + hash, + preimage: *preimage, + secret, + payer_note, + quantity, + } + }, + PaymentKind::Spontaneous { hash, preimage } => { + NodePaymentKind::Spontaneous { hash, preimage: *preimage } + }, + } + } +} + +/// Details about the status of a known Lightning balance. +#[derive(Debug, Clone)] +pub enum LightningBalance { + ClaimableOnChannelClose { + channel_id: ChannelId, + counterparty_node_id: PublicKey, + 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, + }, + ClaimableAwaitingConfirmations { + channel_id: ChannelId, + counterparty_node_id: PublicKey, + amount_satoshis: u64, + confirmation_height: u32, + source: BalanceSource, + }, + ContentiousClaimable { + channel_id: ChannelId, + counterparty_node_id: PublicKey, + amount_satoshis: u64, + timeout_height: u32, + payment_hash: PaymentHash, + payment_preimage: Arc, + }, + MaybeTimeoutClaimableHTLC { + channel_id: ChannelId, + counterparty_node_id: PublicKey, + amount_satoshis: u64, + claimable_height: u32, + payment_hash: PaymentHash, + outbound_payment: bool, + }, + MaybePreimageClaimableHTLC { + channel_id: ChannelId, + counterparty_node_id: PublicKey, + amount_satoshis: u64, + expiry_height: u32, + payment_hash: PaymentHash, + }, + CounterpartyRevokedOutputClaimable { + channel_id: ChannelId, + counterparty_node_id: PublicKey, + amount_satoshis: u64, + }, +} + +impl From for LightningBalance { + fn from(balance: NodeLightningBalance) -> Self { + match balance { + NodeLightningBalance::ClaimableOnChannelClose { + channel_id, + counterparty_node_id, + amount_satoshis, + transaction_fee_satoshis, + outbound_payment_htlc_rounded_msat, + outbound_forwarded_htlc_rounded_msat, + inbound_claiming_htlc_rounded_msat, + inbound_htlc_rounded_msat, + } => LightningBalance::ClaimableOnChannelClose { + channel_id, + counterparty_node_id, + amount_satoshis, + transaction_fee_satoshis, + outbound_payment_htlc_rounded_msat, + outbound_forwarded_htlc_rounded_msat, + inbound_claiming_htlc_rounded_msat, + inbound_htlc_rounded_msat, + }, + NodeLightningBalance::ClaimableAwaitingConfirmations { + channel_id, + counterparty_node_id, + amount_satoshis, + confirmation_height, + source, + } => LightningBalance::ClaimableAwaitingConfirmations { + channel_id, + counterparty_node_id, + amount_satoshis, + confirmation_height, + source, + }, + NodeLightningBalance::ContentiousClaimable { + channel_id, + counterparty_node_id, + amount_satoshis, + timeout_height, + payment_hash, + payment_preimage, + } => LightningBalance::ContentiousClaimable { + channel_id, + counterparty_node_id, + amount_satoshis, + timeout_height, + payment_hash, + payment_preimage: Arc::new(payment_preimage), + }, + NodeLightningBalance::MaybeTimeoutClaimableHTLC { + channel_id, + counterparty_node_id, + amount_satoshis, + claimable_height, + payment_hash, + outbound_payment, + } => LightningBalance::MaybeTimeoutClaimableHTLC { + channel_id, + counterparty_node_id, + amount_satoshis, + claimable_height, + payment_hash, + outbound_payment, + }, + NodeLightningBalance::MaybePreimageClaimableHTLC { + channel_id, + counterparty_node_id, + amount_satoshis, + expiry_height, + payment_hash, + } => LightningBalance::MaybePreimageClaimableHTLC { + channel_id, + counterparty_node_id, + amount_satoshis, + expiry_height, + payment_hash, + }, + NodeLightningBalance::CounterpartyRevokedOutputClaimable { + channel_id, + counterparty_node_id, + amount_satoshis, + } => LightningBalance::CounterpartyRevokedOutputClaimable { + channel_id, + counterparty_node_id, + amount_satoshis, + }, + } + } +} + +impl From for NodeLightningBalance { + fn from(balance: LightningBalance) -> Self { + match balance { + LightningBalance::ClaimableOnChannelClose { + channel_id, + counterparty_node_id, + amount_satoshis, + transaction_fee_satoshis, + outbound_payment_htlc_rounded_msat, + outbound_forwarded_htlc_rounded_msat, + inbound_claiming_htlc_rounded_msat, + inbound_htlc_rounded_msat, + } => NodeLightningBalance::ClaimableOnChannelClose { + channel_id, + counterparty_node_id, + amount_satoshis, + transaction_fee_satoshis, + outbound_payment_htlc_rounded_msat, + outbound_forwarded_htlc_rounded_msat, + inbound_claiming_htlc_rounded_msat, + inbound_htlc_rounded_msat, + }, + LightningBalance::ClaimableAwaitingConfirmations { + channel_id, + counterparty_node_id, + amount_satoshis, + confirmation_height, + source, + } => NodeLightningBalance::ClaimableAwaitingConfirmations { + channel_id, + counterparty_node_id, + amount_satoshis, + confirmation_height, + source, + }, + LightningBalance::ContentiousClaimable { + channel_id, + counterparty_node_id, + amount_satoshis, + timeout_height, + payment_hash, + payment_preimage, + } => NodeLightningBalance::ContentiousClaimable { + channel_id, + counterparty_node_id, + amount_satoshis, + timeout_height, + payment_hash, + payment_preimage: *payment_preimage, + }, + LightningBalance::MaybeTimeoutClaimableHTLC { + channel_id, + counterparty_node_id, + amount_satoshis, + claimable_height, + payment_hash, + outbound_payment, + } => NodeLightningBalance::MaybeTimeoutClaimableHTLC { + channel_id, + counterparty_node_id, + amount_satoshis, + claimable_height, + payment_hash, + outbound_payment, + }, + LightningBalance::MaybePreimageClaimableHTLC { + channel_id, + counterparty_node_id, + amount_satoshis, + expiry_height, + payment_hash, + } => NodeLightningBalance::MaybePreimageClaimableHTLC { + channel_id, + counterparty_node_id, + amount_satoshis, + expiry_height, + payment_hash, + }, + LightningBalance::CounterpartyRevokedOutputClaimable { + channel_id, + counterparty_node_id, + amount_satoshis, + } => NodeLightningBalance::CounterpartyRevokedOutputClaimable { + channel_id, + counterparty_node_id, + amount_satoshis, + }, + } + } +} + +/// An event emitted by [`Node`], which should be handled by the user. +/// +/// [`Node`]: [`crate::Node`] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Event { + PaymentSuccessful { + payment_id: Option, + payment_hash: PaymentHash, + payment_preimage: Arc>, + fee_paid_msat: Option, + }, + PaymentFailed { + payment_id: Option, + payment_hash: Option, + reason: Option, + }, + PaymentReceived { + payment_id: Option, + payment_hash: PaymentHash, + amount_msat: u64, + custom_records: Vec, + }, + PaymentForwarded { + prev_channel_id: ChannelId, + next_channel_id: ChannelId, + prev_user_channel_id: Option, + next_user_channel_id: Option, + prev_node_id: Option, + next_node_id: Option, + total_fee_earned_msat: Option, + skimmed_fee_msat: Option, + claim_from_onchain_tx: bool, + outbound_amount_forwarded_msat: Option, + }, + PaymentClaimable { + payment_id: PaymentId, + payment_hash: PaymentHash, + claimable_amount_msat: u64, + claim_deadline: Option, + custom_records: Vec, + }, + ChannelPending { + channel_id: ChannelId, + user_channel_id: UserChannelId, + former_temporary_channel_id: ChannelId, + counterparty_node_id: PublicKey, + funding_txo: OutPoint, + }, + ChannelReady { + channel_id: ChannelId, + user_channel_id: UserChannelId, + counterparty_node_id: Option, + }, + ChannelClosed { + channel_id: ChannelId, + user_channel_id: UserChannelId, + counterparty_node_id: Option, + reason: Arc>, + }, +} + +impl From for Event { + fn from(event: NodeEvent) -> Self { + match event { + NodeEvent::PaymentSuccessful { + payment_id, + payment_hash, + payment_preimage, + fee_paid_msat, + } => Event::PaymentSuccessful { + payment_id, + payment_hash, + payment_preimage: Arc::new(preimage), + fee_paid_msat, + }, + NodeEvent::PaymentFailed { payment_id, payment_hash, reason } => { + Event::PaymentFailed { payment_id, payment_hash, reason } + }, + NodeEvent::PaymentReceived { + payment_id, + payment_hash, + amount_msat, + custom_records, + } => Event::PaymentReceived { payment_id, payment_hash, amount_msat, custom_records }, + NodeEvent::PaymentForwarded { + prev_channel_id, + next_channel_id, + prev_user_channel_id, + next_user_channel_id, + prev_node_id, + next_node_id, + total_fee_earned_msat, + skimmed_fee_msat, + claim_from_onchain_tx, + outbound_amount_forwarded_msat, + } => Event::PaymentForwarded { + prev_channel_id, + next_channel_id, + prev_user_channel_id, + next_user_channel_id, + prev_node_id, + next_node_id, + total_fee_earned_msat, + skimmed_fee_msat, + claim_from_onchain_tx, + outbound_amount_forwarded_msat, + }, + NodeEvent::PaymentClaimable { + payment_id, + payment_hash, + claimable_amount_msat, + claim_deadline, + custom_records, + } => Event::PaymentClaimable { + payment_id, + payment_hash, + claimable_amount_msat, + claim_deadline, + custom_records, + }, + NodeEvent::ChannelPending { + channel_id, + user_channel_id, + former_temporary_channel_id, + counterparty_node_id, + funding_txo, + } => Event::ChannelPending { + channel_id, + user_channel_id, + former_temporary_channel_id, + counterparty_node_id, + funding_txo, + }, + NodeEvent::ChannelReady { channel_id, user_channel_id, counterparty_node_id } => { + Event::ChannelReady { channel_id, user_channel_id, counterparty_node_id } + }, + NodeEvent::ChannelClosed { + channel_id, + user_channel_id, + counterparty_node_id, + reason, + } => Event::ChannelClosed { + channel_id, + user_channel_id, + counterparty_node_id, + reason: Arc::new(reason), + }, + } + } +} + +impl From for NodeEvent { + fn from(event: Event) -> Self { + match event { + Event::PaymentSuccessful { + payment_id, + payment_hash, + payment_preimage, + fee_paid_msat, + } => NodeEvent::PaymentSuccessful { + payment_id, + payment_hash, + payment_preimage: *payment_preimage, + fee_paid_msat, + }, + Event::PaymentFailed { payment_id, payment_hash, reason } => { + NodeEvent::PaymentFailed { payment_id, payment_hash, reason } + }, + Event::PaymentReceived { payment_id, payment_hash, amount_msat, custom_records } => { + NodeEvent::PaymentReceived { payment_id, payment_hash, amount_msat, custom_records } + }, + Event::PaymentForwarded { + prev_channel_id, + next_channel_id, + prev_user_channel_id, + next_user_channel_id, + prev_node_id, + next_node_id, + total_fee_earned_msat, + skimmed_fee_msat, + claim_from_onchain_tx, + outbound_amount_forwarded_msat, + } => NodeEvent::PaymentForwarded { + prev_channel_id, + next_channel_id, + prev_user_channel_id, + next_user_channel_id, + prev_node_id, + next_node_id, + total_fee_earned_msat, + skimmed_fee_msat, + claim_from_onchain_tx, + outbound_amount_forwarded_msat, + }, + Event::PaymentClaimable { + payment_id, + payment_hash, + claimable_amount_msat, + claim_deadline, + custom_records, + } => NodeEvent::PaymentClaimable { + payment_id, + payment_hash, + claimable_amount_msat, + claim_deadline, + custom_records, + }, + Event::ChannelPending { + channel_id, + user_channel_id, + former_temporary_channel_id, + counterparty_node_id, + funding_txo, + } => NodeEvent::ChannelPending { + channel_id, + user_channel_id, + former_temporary_channel_id, + counterparty_node_id, + funding_txo, + }, + Event::ChannelReady { channel_id, user_channel_id, counterparty_node_id } => { + NodeEvent::ChannelReady { channel_id, user_channel_id, counterparty_node_id } + }, + Event::ChannelClosed { channel_id, user_channel_id, counterparty_node_id, reason } => { + NodeEvent::ChannelClosed { + channel_id, + user_channel_id, + counterparty_node_id, + reason, + } + }, + } } } diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index 817a428bd..97fcd6273 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -13,7 +13,7 @@ use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT}; use crate::connection::ConnectionManager; use crate::data_store::DataStoreUpdateResult; use crate::error::Error; -use crate::ffi::{maybe_deref, maybe_try_convert_enum, maybe_wrap}; +use crate::ffi::{maybe_deref, maybe_try_from, maybe_wrap}; use crate::liquidity::LiquiditySource; use crate::logger::{log_error, log_info, LdkLogger, Logger}; use crate::payment::store::{ @@ -436,7 +436,7 @@ impl Bolt11Payment { pub fn receive( &self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, ) -> Result { - let description = maybe_try_convert_enum(description)?; + let description = maybe_try_from(description)?; let invoice = self.receive_inner(Some(amount_msat), &description, expiry_secs, None)?; Ok(maybe_wrap(invoice)) } @@ -459,7 +459,7 @@ impl Bolt11Payment { &self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash, ) -> Result { - let description = maybe_try_convert_enum(description)?; + let description = maybe_try_from(description)?; let invoice = self.receive_inner(Some(amount_msat), &description, expiry_secs, Some(payment_hash))?; Ok(maybe_wrap(invoice)) @@ -472,7 +472,7 @@ impl Bolt11Payment { pub fn receive_variable_amount( &self, description: &Bolt11InvoiceDescription, expiry_secs: u32, ) -> Result { - let description = maybe_try_convert_enum(description)?; + let description = maybe_try_from(description)?; let invoice = self.receive_inner(None, &description, expiry_secs, None)?; Ok(maybe_wrap(invoice)) } @@ -494,7 +494,7 @@ impl Bolt11Payment { pub fn receive_variable_amount_for_hash( &self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash, ) -> Result { - let description = maybe_try_convert_enum(description)?; + let description = maybe_try_from(description)?; let invoice = self.receive_inner(None, &description, expiry_secs, Some(payment_hash))?; Ok(maybe_wrap(invoice)) } @@ -571,7 +571,7 @@ impl Bolt11Payment { &self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, max_total_lsp_fee_limit_msat: Option, ) -> Result { - let description = maybe_try_convert_enum(description)?; + let description = maybe_try_from(description)?; let invoice = self.receive_via_jit_channel_inner( Some(amount_msat), &description, @@ -597,7 +597,7 @@ impl Bolt11Payment { &self, description: &Bolt11InvoiceDescription, expiry_secs: u32, max_proportional_lsp_fee_limit_ppm_msat: Option, ) -> Result { - let description = maybe_try_convert_enum(description)?; + let description = maybe_try_from(description)?; let invoice = self.receive_via_jit_channel_inner( None, &description,