From 5c7b8bb033e55e4c3ffa4b71365837579a1c9095 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 19 Jan 2026 13:29:30 +0100 Subject: [PATCH 01/10] Upgrade `uniffi` dependency to v0.29.5 We here upgrade to `uniffi` v0.29.5. This upgrade requires a few changes and workarounds, in particular `UniffiCustomTypeConverter` are gone and replaced with corresponding macros. We also had to find workarounds for remote enum interface definitions, and discovered that `uniffi` lost the ability to use remote trait interfaces. The latter therefore requires us to add some duplicative code as we added a local copy of `VssHeaderProvider`/`VssHeaderProviderError` and used an adaptor to make it work for the bindings side of the builder. --- Cargo.toml | 5 +- bindings/ldk_node.udl | 66 +-- bindings/uniffi-bindgen/Cargo.toml | 2 +- scripts/uniffi_bindgen_generate_kotlin.sh | 2 +- .../uniffi_bindgen_generate_kotlin_android.sh | 2 +- scripts/uniffi_bindgen_generate_python.sh | 2 +- scripts/uniffi_bindgen_generate_swift.sh | 5 +- src/builder.rs | 10 +- src/event.rs | 1 + src/ffi/types.rs | 474 ++++++++++++------ 10 files changed, 344 insertions(+), 225 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2e224720d..8f5efc249 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,11 +70,12 @@ tokio = { version = "1.37", default-features = false, features = [ "rt-multi-thr esplora-client = { version = "0.12", default-features = false, features = ["tokio", "async-https-rustls"] } electrum-client = { version = "0.24.0", default-features = false, features = ["proxy", "use-rustls-ring"] } libc = "0.2" -uniffi = { version = "0.28.3", features = ["build"], optional = true } +uniffi = { version = "0.29.5", features = ["build"], optional = true } serde = { version = "1.0.210", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0.128", default-features = false, features = ["std"] } log = { version = "0.4.22", default-features = false, features = ["std"]} +async-trait = { version = "0.1", default-features = false } vss-client = { package = "vss-client-ng", version = "0.5" } prost = { version = "0.11.6", default-features = false} #bitcoin-payment-instructions = { version = "0.6" } @@ -106,7 +107,7 @@ lnd_grpc_rust = { version = "2.10.0", default-features = false } tokio = { version = "1.37", features = ["fs"] } [build-dependencies] -uniffi = { version = "0.28.3", features = ["build"], optional = true } +uniffi = { version = "0.29.5", features = ["build"], optional = true } [profile.release] panic = "abort" diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index d40f72f4a..e461187d7 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -79,6 +79,7 @@ enum WordCount { "Words24", }; +[Remote] enum LogLevel { "Gossip", "Trace", @@ -281,6 +282,7 @@ interface OnchainPayment { Txid bump_fee_rbf(PaymentId payment_id, FeeRate? fee_rate); }; +[Remote] interface FeeRate { [Name=from_sat_per_kwu] constructor(u64 sat_kwu); @@ -377,6 +379,7 @@ dictionary NodeStatus { u64? latest_node_announcement_broadcast_timestamp; }; +[Remote] dictionary BestBlock { BlockHash block_hash; u32 height; @@ -400,35 +403,21 @@ enum BuildError { "AsyncPaymentsConfigMismatch", }; -[Trait] +[Trait, WithForeign] interface VssHeaderProvider { [Async, Throws=VssHeaderProviderError] - record get_headers([ByRef]sequence request); + record get_headers(sequence request); }; [Error] -enum VssHeaderProviderError { - "InvalidData", - "RequestError", - "AuthorizationError", - "InternalError", -}; - -[Enum] -interface Event { - PaymentSuccessful(PaymentId? payment_id, PaymentHash payment_hash, PaymentPreimage? payment_preimage, u64? fee_paid_msat); - PaymentFailed(PaymentId? payment_id, PaymentHash? payment_hash, PaymentFailureReason? reason); - PaymentReceived(PaymentId? payment_id, PaymentHash payment_hash, u64 amount_msat, sequence custom_records); - PaymentClaimable(PaymentId payment_id, PaymentHash payment_hash, u64 claimable_amount_msat, u32? claim_deadline, sequence custom_records); - PaymentForwarded(ChannelId prev_channel_id, ChannelId next_channel_id, UserChannelId? - prev_user_channel_id, UserChannelId? next_user_channel_id, PublicKey? prev_node_id, PublicKey? next_node_id, u64? total_fee_earned_msat, u64? skimmed_fee_msat, boolean claim_from_onchain_tx, u64? outbound_amount_forwarded_msat); - ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo); - ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, OutPoint? funding_txo); - ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason); - SplicePending(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint new_funding_txo); - SpliceFailed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint? abandoned_funding_txo); +interface VssHeaderProviderError { + InvalidData(string error); + RequestError(string error); + AuthorizationError(string error); + InternalError(string error); }; +[Remote] enum PaymentFailureReason { "RecipientRejected", "UserAbandoned", @@ -442,25 +431,6 @@ enum PaymentFailureReason { "BlindedPathCreationFailed", }; -[Enum] -interface ClosureReason { - CounterpartyForceClosed(UntrustedString peer_msg); - HolderForceClosed(boolean? broadcasted_latest_txn, string message); - LegacyCooperativeClosure(); - CounterpartyInitiatedCooperativeClosure(); - LocallyInitiatedCooperativeClosure(); - CommitmentTxConfirmed(); - FundingTimedOut(); - ProcessingError(string err); - DisconnectedPeer(); - OutdatedChannelManager(); - CounterpartyCoopClosedUnfundedChannel(); - LocallyCoopClosedUnfundedChannel(); - FundingBatchClosure(); - HTLCsTimedOut( PaymentHash? payment_hash ); - PeerFeerateTooLow(u32 peer_feerate_sat_per_kw, u32 required_feerate_sat_per_kw); -}; - [Enum] interface PaymentKind { Onchain(Txid txid, ConfirmationStatus status); @@ -510,6 +480,7 @@ dictionary PaymentDetails { u64 latest_update_timestamp; }; +[Remote] dictionary RouteParametersConfig { u64? max_total_routing_fee_msat; u32 max_total_cltv_expiry_delta; @@ -522,6 +493,7 @@ dictionary CustomTlvRecord { sequence value; }; +[Remote] dictionary LSPS1OrderStatus { LSPS1OrderId order_id; LSPS1OrderParams order_params; @@ -529,6 +501,7 @@ dictionary LSPS1OrderStatus { LSPS1ChannelInfo? channel_state; }; +[Remote] dictionary LSPS1OrderParams { u64 lsp_balance_sat; u64 client_balance_sat; @@ -563,19 +536,21 @@ dictionary LSPS1OnchainPaymentInfo { Address? refund_onchain_address; }; +[Remote] dictionary LSPS1ChannelInfo { LSPSDateTime funded_at; OutPoint funding_outpoint; LSPSDateTime expires_at; }; +[Remote] enum LSPS1PaymentState { "ExpectPayment", "Paid", "Refunded", }; -[NonExhaustive] +[Remote, NonExhaustive] enum Network { "Bitcoin", "Testnet", @@ -583,6 +558,7 @@ enum Network { "Regtest", }; +[Remote] dictionary OutPoint { Txid txid; u32 vout; @@ -679,6 +655,7 @@ interface LightningBalance { ); }; +[Remote] enum BalanceSource { "HolderForceClosed", "CounterpartyForceClosed", @@ -741,6 +718,7 @@ dictionary ChannelUpdateInfo { RoutingFees fees; }; +[Remote] dictionary RoutingFees { u32 base_msat; u32 proportional_millionths; @@ -757,6 +735,7 @@ dictionary NodeAnnouncementInfo { sequence addresses; }; +[Remote] enum Currency { "Bitcoin", "BitcoinTestnet", @@ -929,3 +908,6 @@ typedef string LSPSDateTime; [Custom] typedef string ScriptBuf; + +typedef enum ClosureReason; +typedef enum Event; diff --git a/bindings/uniffi-bindgen/Cargo.toml b/bindings/uniffi-bindgen/Cargo.toml index a33c0f9ae..87792935c 100644 --- a/bindings/uniffi-bindgen/Cargo.toml +++ b/bindings/uniffi-bindgen/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -uniffi = { version = "0.28.3", features = ["cli"] } +uniffi = { version = "0.29.5", features = ["cli"] } diff --git a/scripts/uniffi_bindgen_generate_kotlin.sh b/scripts/uniffi_bindgen_generate_kotlin.sh index a1085600e..dc0237ba6 100755 --- a/scripts/uniffi_bindgen_generate_kotlin.sh +++ b/scripts/uniffi_bindgen_generate_kotlin.sh @@ -29,6 +29,6 @@ else fi mkdir -p "$BINDINGS_DIR"/"$PROJECT_DIR"/lib/src/main/kotlin/"$PACKAGE_DIR" || exit 1 -$UNIFFI_BINDGEN_BIN generate bindings/ldk_node.udl --language kotlin -o "$TARGET_DIR" || exit 1 +$UNIFFI_BINDGEN_BIN generate bindings/ldk_node.udl --lib-file "$DYNAMIC_LIB_PATH" --language kotlin -o "$TARGET_DIR" || exit 1 cp "$TARGET_DIR"/"$PACKAGE_DIR"/ldk_node.kt "$BINDINGS_DIR"/"$PROJECT_DIR"/lib/src/main/kotlin/"$PACKAGE_DIR"/ || exit 1 diff --git a/scripts/uniffi_bindgen_generate_kotlin_android.sh b/scripts/uniffi_bindgen_generate_kotlin_android.sh index c87db5924..161292857 100755 --- a/scripts/uniffi_bindgen_generate_kotlin_android.sh +++ b/scripts/uniffi_bindgen_generate_kotlin_android.sh @@ -38,7 +38,7 @@ rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-android RUSTFLAGS="-C link-args=-Wl,-z,max-page-size=16384,-z,common-page-size=16384" CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --profile release-smaller --features uniffi --target x86_64-linux-android || exit 1 RUSTFLAGS="-C link-args=-Wl,-z,max-page-size=16384,-z,common-page-size=16384" CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --profile release-smaller --features uniffi --target armv7-linux-androideabi || exit 1 RUSTFLAGS="-C link-args=-Wl,-z,max-page-size=16384,-z,common-page-size=16384" CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --profile release-smaller --features uniffi --target aarch64-linux-android || exit 1 -$UNIFFI_BINDGEN_BIN generate bindings/ldk_node.udl --language kotlin --config uniffi-android.toml -o "$BINDINGS_DIR"/"$PROJECT_DIR"/lib/src/main/kotlin || exit 1 +$UNIFFI_BINDGEN_BIN generate bindings/ldk_node.udl --lib-file "$TARGET_DIR"/x86_64-linux-android/release-smaller/libldk_node.so --language kotlin --config uniffi-android.toml -o "$BINDINGS_DIR"/"$PROJECT_DIR"/lib/src/main/kotlin || exit 1 JNI_LIB_DIR="$BINDINGS_DIR"/"$PROJECT_DIR"/lib/src/main/jniLibs/ mkdir -p $JNI_LIB_DIR/x86_64 || exit 1 diff --git a/scripts/uniffi_bindgen_generate_python.sh b/scripts/uniffi_bindgen_generate_python.sh index 14e7b4f86..50ba450b7 100755 --- a/scripts/uniffi_bindgen_generate_python.sh +++ b/scripts/uniffi_bindgen_generate_python.sh @@ -9,7 +9,7 @@ else fi cargo build --profile release-smaller --features uniffi || exit 1 -$UNIFFI_BINDGEN_BIN generate bindings/ldk_node.udl --language python -o "$BINDINGS_DIR" || exit 1 +$UNIFFI_BINDGEN_BIN generate bindings/ldk_node.udl --lib-file "$DYNAMIC_LIB_PATH" --language python -o "$BINDINGS_DIR" || exit 1 mkdir -p $BINDINGS_DIR cp "$DYNAMIC_LIB_PATH" "$BINDINGS_DIR" || exit 1 diff --git a/scripts/uniffi_bindgen_generate_swift.sh b/scripts/uniffi_bindgen_generate_swift.sh index ce1151a3b..d4c900e40 100755 --- a/scripts/uniffi_bindgen_generate_swift.sh +++ b/scripts/uniffi_bindgen_generate_swift.sh @@ -4,9 +4,6 @@ set -eox pipefail BINDINGS_DIR="./bindings/swift" UNIFFI_BINDGEN_BIN="cargo run --manifest-path bindings/uniffi-bindgen/Cargo.toml" -cargo build --release || exit 1 -$UNIFFI_BINDGEN_BIN generate bindings/ldk_node.udl --language swift -o "$BINDINGS_DIR" || exit 1 - mkdir -p $BINDINGS_DIR # Install rust target toolchains @@ -30,7 +27,7 @@ lipo target/aarch64-apple-ios-sim/release/libldk_node.a target/x86_64-apple-ios/ mkdir -p target/lipo-macos/release-smaller || exit 1 lipo target/aarch64-apple-darwin/release-smaller/libldk_node.a target/x86_64-apple-darwin/release-smaller/libldk_node.a -create -output target/lipo-macos/release-smaller/libldk_node.a || exit 1 -$UNIFFI_BINDGEN_BIN generate bindings/ldk_node.udl --language swift -o "$BINDINGS_DIR" || exit 1 +$UNIFFI_BINDGEN_BIN generate bindings/ldk_node.udl --lib-file target/release-smaller/libldk_node.a --language swift -o "$BINDINGS_DIR" || exit 1 swiftc -module-name LDKNode -emit-library -o "$BINDINGS_DIR"/libldk_node.dylib -emit-module -emit-module-path "$BINDINGS_DIR" -parse-as-library -L ./target/release-smaller -lldk_node -Xcc -fmodule-map-file="$BINDINGS_DIR"/LDKNodeFFI.modulemap "$BINDINGS_DIR"/LDKNode.swift -v || exit 1 diff --git a/src/builder.rs b/src/builder.rs index a2ea9aea7..2f021a82c 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1026,17 +1026,13 @@ impl ArcedNodeBuilder { /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md pub fn build_with_vss_store_and_header_provider( &self, node_entropy: Arc, vss_url: String, store_id: String, - header_provider: Arc, + header_provider: Arc, ) -> Result, BuildError> { + let adapter = Arc::new(crate::ffi::VssHeaderProviderAdapter::new(header_provider)); self.inner .read() .unwrap() - .build_with_vss_store_and_header_provider( - *node_entropy, - vss_url, - store_id, - header_provider, - ) + .build_with_vss_store_and_header_provider(*node_entropy, vss_url, store_id, adapter) .map(Arc::new) } diff --git a/src/event.rs b/src/event.rs index a4dcc8cf3..7633a4b01 100644 --- a/src/event.rs +++ b/src/event.rs @@ -60,6 +60,7 @@ use crate::{ /// /// [`Node`]: [`crate::Node`] #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] pub enum Event { /// A sent payment was successful. PaymentSuccessful { diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 2a349a967..e8f45b751 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -41,7 +41,101 @@ pub use lightning_liquidity::lsps1::msgs::{ }; pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; pub use lightning_types::string::UntrustedString; -pub use vss_client::headers::{VssHeaderProvider, VssHeaderProviderError}; +use std::collections::HashMap; + +use vss_client::headers::VssHeaderProvider as VssClientHeaderProvider; +use vss_client::headers::VssHeaderProviderError as VssClientHeaderProviderError; + +/// Errors around providing headers for each VSS request. +#[derive(Debug)] +pub enum VssHeaderProviderError { + /// Invalid data was encountered. + InvalidData { + /// The error message. + error: String, + }, + /// An external request failed. + RequestError { + /// The error message. + error: String, + }, + /// Authorization was refused. + AuthorizationError { + /// The error message. + error: String, + }, + /// An application-level error occurred specific to the header provider functionality. + InternalError { + /// The error message. + error: String, + }, +} + +impl std::fmt::Display for VssHeaderProviderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidData { error } => write!(f, "invalid data: {}", error), + Self::RequestError { error } => { + write!(f, "error performing external request: {}", error) + }, + Self::AuthorizationError { error } => { + write!(f, "authorization was refused: {}", error) + }, + Self::InternalError { error } => write!(f, "internal error: {}", error), + } + } +} + +impl std::error::Error for VssHeaderProviderError {} + +impl From for VssClientHeaderProviderError { + fn from(e: VssHeaderProviderError) -> Self { + match e { + VssHeaderProviderError::InvalidData { error } => { + VssClientHeaderProviderError::InvalidData { error } + }, + VssHeaderProviderError::RequestError { error } => { + VssClientHeaderProviderError::RequestError { error } + }, + VssHeaderProviderError::AuthorizationError { error } => { + VssClientHeaderProviderError::AuthorizationError { error } + }, + VssHeaderProviderError::InternalError { error } => { + VssClientHeaderProviderError::InternalError { error } + }, + } + } +} + +/// Defines a trait around how headers are provided for each VSS request. +#[async_trait::async_trait] +pub trait VssHeaderProvider: Send + Sync { + /// Returns the HTTP headers to be used for a VSS request. + async fn get_headers( + &self, request: Vec, + ) -> Result, VssHeaderProviderError>; +} + +/// An adapter that wraps the local [`VssHeaderProvider`] and implements the upstream +/// [`VssClientHeaderProvider`] trait. +pub(crate) struct VssHeaderProviderAdapter { + inner: Arc, +} + +impl VssHeaderProviderAdapter { + pub(crate) fn new(inner: Arc) -> Self { + Self { inner } + } +} + +#[async_trait::async_trait] +impl VssClientHeaderProvider for VssHeaderProviderAdapter { + async fn get_headers( + &self, request: &[u8], + ) -> Result, VssClientHeaderProviderError> { + self.inner.get_headers(request.to_vec()).await.map_err(Into::into) + } +} use crate::builder::sanitize_alias; pub use crate::config::{ @@ -57,71 +151,63 @@ pub use crate::payment::store::{ ConfirmationStatus, LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus, }; pub use crate::payment::UnifiedPaymentResult; -use crate::{hex_utils, SocketAddress, UniffiCustomTypeConverter, UserChannelId}; - -impl UniffiCustomTypeConverter for PublicKey { - type Builtin = String; +use crate::{hex_utils, SocketAddress, UserChannelId}; - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(PublicKey, String, { + remote, + try_lift: |val| { if let Ok(key) = PublicKey::from_str(&val) { return Ok(key); } Err(Error::InvalidPublicKey.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { obj.to_string() - } -} - -impl UniffiCustomTypeConverter for NodeId { - type Builtin = String; + }, +}); - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(NodeId, String, { + remote, + try_lift: |val| { if let Ok(key) = NodeId::from_str(&val) { return Ok(key); } Err(Error::InvalidNodeId.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { obj.to_string() - } -} - -impl UniffiCustomTypeConverter for Address { - type Builtin = String; + }, +}); - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(Address, String, { + remote, + try_lift: |val| { if let Ok(addr) = Address::from_str(&val) { return Ok(addr.assume_checked()); } Err(Error::InvalidAddress.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { obj.to_string() - } -} - -impl UniffiCustomTypeConverter for ScriptBuf { - type Builtin = String; + }, +}); - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(ScriptBuf, String, { + remote, + try_lift: |val| { if let Ok(key) = ScriptBuf::from_hex(&val) { return Ok(key); } Err(Error::InvalidScriptPubKey.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { obj.to_string() - } -} + }, +}); #[derive(Debug, Clone, PartialEq, Eq)] pub enum OfferAmount { @@ -686,10 +772,9 @@ impl AsRef for Bolt12Invoice { } } -impl UniffiCustomTypeConverter for OfferId { - type Builtin = String; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(OfferId, String, { + remote, + try_lift: |val| { if let Some(bytes_vec) = hex_utils::to_vec(&val) { let bytes_res = bytes_vec.try_into(); if let Ok(bytes) = bytes_res { @@ -697,17 +782,15 @@ impl UniffiCustomTypeConverter for OfferId { } } Err(Error::InvalidOfferId.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { hex_utils::to_string(&obj.0) - } -} - -impl UniffiCustomTypeConverter for PaymentId { - type Builtin = String; + }, +}); - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(PaymentId, String, { + remote, + try_lift: |val| { if let Some(bytes_vec) = hex_utils::to_vec(&val) { let bytes_res = bytes_vec.try_into(); if let Ok(bytes) = bytes_res { @@ -715,33 +798,29 @@ impl UniffiCustomTypeConverter for PaymentId { } } Err(Error::InvalidPaymentId.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { hex_utils::to_string(&obj.0) - } -} - -impl UniffiCustomTypeConverter for PaymentHash { - type Builtin = String; + }, +}); - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(PaymentHash, String, { + remote, + try_lift: |val| { if let Ok(hash) = Sha256::from_str(&val) { Ok(PaymentHash(hash.to_byte_array())) } else { Err(Error::InvalidPaymentHash.into()) } - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { Sha256::from_slice(&obj.0).unwrap().to_string() - } -} - -impl UniffiCustomTypeConverter for PaymentPreimage { - type Builtin = String; + }, +}); - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(PaymentPreimage, String, { + remote, + try_lift: |val| { if let Some(bytes_vec) = hex_utils::to_vec(&val) { let bytes_res = bytes_vec.try_into(); if let Ok(bytes) = bytes_res { @@ -749,17 +828,15 @@ impl UniffiCustomTypeConverter for PaymentPreimage { } } Err(Error::InvalidPaymentPreimage.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { hex_utils::to_string(&obj.0) - } -} - -impl UniffiCustomTypeConverter for PaymentSecret { - type Builtin = String; + }, +}); - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(PaymentSecret, String, { + remote, + try_lift: |val| { if let Some(bytes_vec) = hex_utils::to_vec(&val) { let bytes_res = bytes_vec.try_into(); if let Ok(bytes) = bytes_res { @@ -767,17 +844,15 @@ impl UniffiCustomTypeConverter for PaymentSecret { } } Err(Error::InvalidPaymentSecret.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { hex_utils::to_string(&obj.0) - } -} - -impl UniffiCustomTypeConverter for ChannelId { - type Builtin = String; + }, +}); - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(ChannelId, String, { + remote, + try_lift: |val| { if let Some(hex_vec) = hex_utils::to_vec(&val) { if hex_vec.len() == 32 { let mut channel_id = [0u8; 32]; @@ -786,91 +861,81 @@ impl UniffiCustomTypeConverter for ChannelId { } } Err(Error::InvalidChannelId.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { hex_utils::to_string(&obj.0) } -} +}); -impl UniffiCustomTypeConverter for UserChannelId { - type Builtin = String; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(UserChannelId, String, { + remote, + try_lift: |val| { Ok(UserChannelId(u128::from_str(&val).map_err(|_| Error::InvalidChannelId)?)) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { obj.0.to_string() - } -} + }, +}); -impl UniffiCustomTypeConverter for Txid { - type Builtin = String; - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(Txid, String, { + remote, + try_lift: |val| { Ok(Txid::from_str(&val)?) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { obj.to_string() - } -} + }, +}); -impl UniffiCustomTypeConverter for BlockHash { - type Builtin = String; - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(BlockHash, String, { + remote, + try_lift: |val| { Ok(BlockHash::from_str(&val)?) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { obj.to_string() - } -} + }, +}); -impl UniffiCustomTypeConverter for Mnemonic { - type Builtin = String; - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(Mnemonic, String, { + remote, + try_lift: |val| { Ok(Mnemonic::from_str(&val).map_err(|_| Error::InvalidSecretKey)?) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { obj.to_string() - } -} + }, +}); -impl UniffiCustomTypeConverter for SocketAddress { - type Builtin = String; - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(SocketAddress, String, { + remote, + try_lift: |val| { Ok(SocketAddress::from_str(&val).map_err(|_| Error::InvalidSocketAddress)?) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { obj.to_string() - } -} + }, +}); -impl UniffiCustomTypeConverter for UntrustedString { - type Builtin = String; - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(UntrustedString, String, { + remote, + try_lift: |val| { Ok(UntrustedString(val)) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { obj.to_string() - } -} - -impl UniffiCustomTypeConverter for NodeAlias { - type Builtin = String; + }, +}); - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(NodeAlias, String, { + remote, + try_lift: |val| { Ok(sanitize_alias(&val).map_err(|_| Error::InvalidNodeAlias)?) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { obj.to_string() - } -} + }, +}); /// Represents the description of an invoice which has to be either a directly included string or /// a hash of a description provided out of band. @@ -1224,28 +1289,105 @@ impl From for LSPS1Bol } } -impl UniffiCustomTypeConverter for LSPS1OrderId { - type Builtin = String; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(LSPS1OrderId, String, { + remote, + try_lift: |val| { Ok(Self(val)) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { obj.0 - } -} - -impl UniffiCustomTypeConverter for LSPSDateTime { - type Builtin = String; + }, +}); - fn into_custom(val: Self::Builtin) -> uniffi::Result { +uniffi::custom_type!(LSPSDateTime, String, { + remote, + try_lift: |val| { Ok(LSPSDateTime::from_str(&val).map_err(|_| Error::InvalidDateTime)?) - } - - fn from_custom(obj: Self) -> Self::Builtin { + }, + lower: |obj| { obj.to_rfc3339() - } + }, +}); + +/// The reason the channel was closed. See individual variants for more details. +#[uniffi::remote(Enum)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ClosureReason { + /// Closure generated from receiving a peer error message. + /// + /// Our counterparty may have broadcasted their latest commitment state, and we have + /// as well. + CounterpartyForceClosed { + /// The error which the peer sent us. + /// + /// Be careful about printing the peer_msg, a well-crafted message could exploit + /// a security vulnerability in the terminal emulator or the logging subsystem. + peer_msg: UntrustedString, + }, + /// Closure generated from a force close initiated by us. + HolderForceClosed { + /// Whether or not the latest transaction was broadcasted when the channel was force + /// closed. + /// + /// This will be set to `Some(true)` for any channels closed after their funding + /// transaction was (or might have been) broadcasted, and `Some(false)` for any channels + /// closed prior to their funding transaction being broadcasted. + broadcasted_latest_txn: Option, + /// The error message provided when initiating the force close. + message: String, + }, + /// The channel was closed after negotiating a cooperative close and we've now broadcasted + /// the cooperative close transaction. Note the shutdown may have been initiated by us. + LegacyCooperativeClosure, + /// The channel was closed after negotiating a cooperative close and we've now broadcasted + /// the cooperative close transaction. This indicates that the shutdown was initiated by our + /// counterparty. + /// + /// In rare cases where we initiated closure immediately prior to shutting down without + /// persisting, this value may be provided for channels we initiated closure for. + CounterpartyInitiatedCooperativeClosure, + /// The channel was closed after negotiating a cooperative close and we've now broadcasted + /// the cooperative close transaction. This indicates that the shutdown was initiated by us. + LocallyInitiatedCooperativeClosure, + /// A commitment transaction was confirmed on chain, closing the channel. Most likely this + /// commitment transaction came from our counterparty, but it may also have come from + /// a copy of our own channel monitor. + CommitmentTxConfirmed, + /// The funding transaction failed to confirm in a timely manner on an inbound channel or the + /// counterparty failed to fund the channel in a timely manner. + FundingTimedOut, + /// Closure generated from processing an event, likely a HTLC forward/relay/reception. + ProcessingError { + /// A developer-readable error message which we generated. + err: String, + }, + /// The peer disconnected prior to funding completing. In this case the spec mandates that we + /// forget the channel entirely - we can attempt again if the peer reconnects. + DisconnectedPeer, + /// Closure generated during deserialization if the channel monitor is newer than + /// the channel manager deserialized. + OutdatedChannelManager, + /// The counterparty requested a cooperative close of a channel that had not been funded yet. + /// The channel has been immediately closed. + CounterpartyCoopClosedUnfundedChannel, + /// We requested a cooperative close of a channel that had not been funded yet. + /// The channel has been immediately closed. + LocallyCoopClosedUnfundedChannel, + /// Another channel in the same funding batch closed before the funding transaction + /// was ready to be broadcast. + FundingBatchClosure, + /// One of our HTLCs timed out in a channel, causing us to force close the channel. + HTLCsTimedOut { + /// The payment hash of an HTLC that timed out. + payment_hash: Option, + }, + /// Our peer provided a feerate which violated our required minimum. + PeerFeerateTooLow { + /// The feerate on our channel set by our peer. + peer_feerate_sat_per_kw: u32, + /// The required feerate we enforce. + required_feerate_sat_per_kw: u32, + }, } #[cfg(test)] From 50e0d1aa5cc3d161766b04842d37d6331bfeb995 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 27 Feb 2026 12:41:57 +0100 Subject: [PATCH 02/10] Migrate simple flat enums to UniFFI proc-macro export Migrate `WordCount`, `PaymentDirection`, `PaymentStatus`, and `AsyncPaymentsRole` from UDL enum definitions to proc-macro `#[derive(uniffi::Enum)]` on their Rust source types, replacing the UDL definitions with `typedef enum` references. Generated with the help of AI (Claude Code). Co-Authored-By: HAL 9000 --- bindings/ldk_node.udl | 24 ++++-------------------- src/config.rs | 1 + src/entropy.rs | 1 + src/payment/store.rs | 2 ++ 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index e461187d7..714588696 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -71,13 +71,7 @@ enum EntropyError { "InvalidSeedFile", }; -enum WordCount { - "Words12", - "Words15", - "Words18", - "Words21", - "Words24", -}; +typedef enum WordCount; [Remote] enum LogLevel { @@ -448,16 +442,9 @@ interface UnifiedPaymentResult { Bolt12(PaymentId payment_id); }; -enum PaymentDirection { - "Inbound", - "Outbound", -}; +typedef enum PaymentDirection; -enum PaymentStatus { - "Pending", - "Succeeded", - "Failed", -}; +typedef enum PaymentStatus; dictionary LSPFeeLimits { u64? max_total_opening_fee_msat; @@ -744,10 +731,7 @@ enum Currency { "Signet", }; -enum AsyncPaymentsRole { - "Client", - "Server", -}; +typedef enum AsyncPaymentsRole; dictionary RouteHintHop { PublicKey src_node_id; diff --git a/src/config.rs b/src/config.rs index 1dfa66176..61e648f37 100644 --- a/src/config.rs +++ b/src/config.rs @@ -595,6 +595,7 @@ impl From for LdkMaxDustHTLCExposure { } #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] /// The role of the node in an asynchronous payments context. /// /// See for more information about the async payments protocol. diff --git a/src/entropy.rs b/src/entropy.rs index 8bd338622..78fd77493 100644 --- a/src/entropy.rs +++ b/src/entropy.rs @@ -125,6 +125,7 @@ pub fn generate_entropy_mnemonic(word_count: Option) -> Mnemonic { /// Supported BIP39 mnemonic word counts for entropy generation. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] pub enum WordCount { /// 12-word mnemonic (128-bit entropy) Words12, diff --git a/src/payment/store.rs b/src/payment/store.rs index e28d8fe6c..eee687cd8 100644 --- a/src/payment/store.rs +++ b/src/payment/store.rs @@ -317,6 +317,7 @@ impl StorableObject for PaymentDetails { /// Represents the direction of a payment. #[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] pub enum PaymentDirection { /// The payment is inbound. Inbound, @@ -331,6 +332,7 @@ impl_writeable_tlv_based_enum!(PaymentDirection, /// Represents the current status of a payment. #[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] pub enum PaymentStatus { /// The payment is still pending. Pending, From 0b180e95c158588312d648877a29e217719b6402 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 27 Feb 2026 12:45:14 +0100 Subject: [PATCH 03/10] Migrate `EntropyError` and `BuildError` to UniFFI proc-macro export Add `#[derive(uniffi::Error)]` to `EntropyError` and `BuildError`, replacing their UDL definitions with `typedef enum` references. Generated with the help of AI (Claude Code). Co-Authored-By: HAL 9000 --- bindings/ldk_node.udl | 23 ++--------------------- src/builder.rs | 1 + src/entropy.rs | 1 + 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 714588696..56ea09d2f 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -66,10 +66,7 @@ interface NodeEntropy { constructor(string seed_path); }; -enum EntropyError { - "InvalidSeedBytes", - "InvalidSeedFile", -}; +typedef enum EntropyError; typedef enum WordCount; @@ -379,23 +376,7 @@ dictionary BestBlock { u32 height; }; -[Error] -enum BuildError { - "InvalidSystemTime", - "InvalidChannelMonitor", - "InvalidListeningAddresses", - "InvalidAnnouncementAddresses", - "InvalidNodeAlias", - "RuntimeSetupFailed", - "ReadFailed", - "WriteFailed", - "StoragePathAccessFailed", - "KVStoreSetupFailed", - "WalletSetupFailed", - "LoggerSetupFailed", - "NetworkMismatch", - "AsyncPaymentsConfigMismatch", -}; +typedef enum BuildError; [Trait, WithForeign] interface VssHeaderProvider { diff --git a/src/builder.rs b/src/builder.rs index 2f021a82c..2c46f6166 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -154,6 +154,7 @@ impl std::fmt::Debug for LogWriterConfig { /// /// [`Node`]: crate::Node #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Error))] pub enum BuildError { /// The current system time is invalid, clocks might have gone backwards. InvalidSystemTime, diff --git a/src/entropy.rs b/src/entropy.rs index 78fd77493..650849641 100644 --- a/src/entropy.rs +++ b/src/entropy.rs @@ -16,6 +16,7 @@ use crate::io; /// An error that could arise during [`NodeEntropy`] construction. #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Error))] pub enum EntropyError { /// The given seed bytes are invalid, e.g., have invalid length. InvalidSeedBytes, From 76f4310450eeed2d515bd1a1ba18e6b1544f04f5 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 27 Feb 2026 12:56:51 +0100 Subject: [PATCH 04/10] Migrate complex enums and `LSPFeeLimits` to UniFFI proc-macro export Migrate `MaxDustHTLCExposure`, `ConfirmationStatus`, `PaymentKind`, `LightningBalance`, and `PendingSweepBalance` enums, as well as the `LSPFeeLimits` record, from UDL definitions to proc-macro derives. Co-Authored-By: HAL 9000 --- bindings/ldk_node.udl | 83 ++++--------------------------------------- src/balance.rs | 2 ++ src/config.rs | 1 + src/ffi/types.rs | 4 +-- src/payment/store.rs | 3 ++ 5 files changed, 13 insertions(+), 80 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 56ea09d2f..c469420af 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -406,15 +406,7 @@ enum PaymentFailureReason { "BlindedPathCreationFailed", }; -[Enum] -interface PaymentKind { - Onchain(Txid txid, ConfirmationStatus status); - Bolt11(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret); - Bolt11Jit(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret, u64? counterparty_skimmed_fee_msat, LSPFeeLimits lsp_fee_limits); - Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId offer_id, UntrustedString? payer_note, u64? quantity); - Bolt12Refund(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, UntrustedString? payer_note, u64? quantity); - Spontaneous(PaymentHash hash, PaymentPreimage? preimage); -}; +typedef enum PaymentKind; [Enum] interface UnifiedPaymentResult { @@ -427,16 +419,9 @@ typedef enum PaymentDirection; typedef enum PaymentStatus; -dictionary LSPFeeLimits { - u64? max_total_opening_fee_msat; - u64? max_proportional_opening_fee_ppm_msat; -}; +typedef dictionary LSPFeeLimits; -[Enum] -interface ConfirmationStatus { - Confirmed (BlockHash block_hash, u32 height, u64 timestamp); - Unconfirmed (); -}; +typedef enum ConfirmationStatus; dictionary PaymentDetails { PaymentId id; @@ -574,54 +559,7 @@ dictionary PeerDetails { boolean is_connected; }; -[Enum] -interface LightningBalance { - ClaimableOnChannelClose ( - ChannelId channel_id, - PublicKey counterparty_node_id, - u64 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 - ); - ClaimableAwaitingConfirmations ( - ChannelId channel_id, - PublicKey counterparty_node_id, - u64 amount_satoshis, - u32 confirmation_height, - BalanceSource source - ); - ContentiousClaimable ( - ChannelId channel_id, - PublicKey counterparty_node_id, - u64 amount_satoshis, - u32 timeout_height, - PaymentHash payment_hash, - PaymentPreimage payment_preimage - ); - MaybeTimeoutClaimableHTLC ( - ChannelId channel_id, - PublicKey counterparty_node_id, - u64 amount_satoshis, - u32 claimable_height, - PaymentHash payment_hash, - boolean outbound_payment - ); - MaybePreimageClaimableHTLC ( - ChannelId channel_id, - PublicKey counterparty_node_id, - u64 amount_satoshis, - u32 expiry_height, - PaymentHash payment_hash - ); - CounterpartyRevokedOutputClaimable ( - ChannelId channel_id, - PublicKey counterparty_node_id, - u64 amount_satoshis - ); -}; +typedef enum LightningBalance; [Remote] enum BalanceSource { @@ -631,12 +569,7 @@ enum BalanceSource { "Htlc", }; -[Enum] -interface PendingSweepBalance { - PendingBroadcast ( ChannelId? channel_id, u64 amount_satoshis ); - BroadcastAwaitingConfirmation ( ChannelId? channel_id, u32 latest_broadcast_height, Txid latest_spending_txid, u64 amount_satoshis ); - AwaitingThresholdConfirmations ( ChannelId? channel_id, Txid latest_spending_txid, BlockHash confirmation_hash, u32 confirmation_height, u64 amount_satoshis); -}; +typedef enum PendingSweepBalance; dictionary BalanceDetails { u64 total_onchain_balance_sats; @@ -656,11 +589,7 @@ dictionary ChannelConfig { boolean accept_underpaying_htlcs; }; -[Enum] -interface MaxDustHTLCExposure { - FixedLimit ( u64 limit_msat ); - FeeRateMultiplier ( u64 multiplier ); -}; +typedef enum MaxDustHTLCExposure; interface NetworkGraph { sequence list_channels(); diff --git a/src/balance.rs b/src/balance.rs index d96278dae..abd3f0d82 100644 --- a/src/balance.rs +++ b/src/balance.rs @@ -67,6 +67,7 @@ pub struct BalanceDetails { /// Details about the status of a known Lightning balance. #[derive(Debug, Clone)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] pub enum LightningBalance { /// The channel is not yet closed (or the commitment or closing transaction has not yet /// appeared in a block). The given balance is claimable (less on-chain fees) if the channel is @@ -304,6 +305,7 @@ impl LightningBalance { /// Details about the status of a known balance currently being swept to our on-chain wallet. #[derive(Debug, Clone)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] pub enum PendingSweepBalance { /// The spendable output is about to be swept, but a spending transaction has yet to be generated and /// broadcast. diff --git a/src/config.rs b/src/config.rs index 61e648f37..6330f087a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -555,6 +555,7 @@ impl Default for ChannelConfig { /// /// See [`LdkChannelConfig::max_dust_htlc_exposure`] for details. #[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] pub enum MaxDustHTLCExposure { /// This sets a fixed limit on the total dust exposure in millisatoshis. /// diff --git a/src/ffi/types.rs b/src/ffi/types.rs index e8f45b751..cafb2ac1c 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -147,9 +147,7 @@ use crate::error::Error; pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo}; pub use crate::liquidity::{LSPS1OrderStatus, LSPS2ServiceConfig}; pub use crate::logger::{LogLevel, LogRecord, LogWriter}; -pub use crate::payment::store::{ - ConfirmationStatus, LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus, -}; +pub use crate::payment::store::{PaymentDirection, PaymentKind, PaymentStatus}; pub use crate::payment::UnifiedPaymentResult; use crate::{hex_utils, SocketAddress, UserChannelId}; diff --git a/src/payment/store.rs b/src/payment/store.rs index eee687cd8..1b2edb29a 100644 --- a/src/payment/store.rs +++ b/src/payment/store.rs @@ -350,6 +350,7 @@ impl_writeable_tlv_based_enum!(PaymentStatus, /// Represents the kind of a payment. #[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] pub enum PaymentKind { /// An on-chain payment. /// @@ -496,6 +497,7 @@ impl_writeable_tlv_based_enum!(PaymentKind, /// Represents the confirmation status of a transaction. #[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] pub enum ConfirmationStatus { /// The transaction is confirmed in the best chain. Confirmed { @@ -525,6 +527,7 @@ impl_writeable_tlv_based_enum!(ConfirmationStatus, /// /// [`LdkChannelConfig::accept_underpaying_htlcs`]: lightning::util::config::ChannelConfig::accept_underpaying_htlcs #[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct LSPFeeLimits { /// The maximal total amount we allow any configured LSP withhold from us when forwarding the /// payment. From 118284ad9c98ec0e637ecb87dbdd0ad720213d03 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 27 Feb 2026 12:59:49 +0100 Subject: [PATCH 05/10] Migrate records and config types to UniFFI proc-macro export Migrate `Config`, `AnchorChannelsConfig`, `BackgroundSyncConfig`, `SyncTimeoutsConfig`, `EsploraSyncConfig`, `ElectrumSyncConfig`, `ChannelConfig`, `CustomTlvRecord`, `PeerDetails`, `PaymentDetails`, `BalanceDetails`, `NodeStatus`, `LSPS2ServiceConfig`, and `ChannelDetails` from UDL dictionary definitions to `#[derive(uniffi::Record)]` proc-macro exports. Co-Authored-By: HAL 9000 --- bindings/ldk_node.udl | 142 +++++------------------------------------- src/balance.rs | 1 + src/config.rs | 7 +++ src/ffi/types.rs | 8 +-- src/lib.rs | 1 + src/liquidity.rs | 1 + src/payment/store.rs | 1 + src/types.rs | 3 + 8 files changed, 31 insertions(+), 133 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index c469420af..53f289efb 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -3,59 +3,19 @@ namespace ldk_node { Config default_config(); }; -dictionary Config { - string storage_dir_path; - Network network; - sequence? listening_addresses; - sequence? announcement_addresses; - NodeAlias? node_alias; - sequence trusted_peers_0conf; - u64 probing_liquidity_limit_multiplier; - AnchorChannelsConfig? anchor_channels_config; - RouteParametersConfig? route_parameters; -}; +typedef dictionary Config; -dictionary AnchorChannelsConfig { - sequence trusted_peers_no_reserve; - u64 per_channel_reserve_sats; -}; +typedef dictionary AnchorChannelsConfig; -dictionary BackgroundSyncConfig { - u64 onchain_wallet_sync_interval_secs; - u64 lightning_wallet_sync_interval_secs; - u64 fee_rate_cache_update_interval_secs; -}; +typedef dictionary BackgroundSyncConfig; -dictionary SyncTimeoutsConfig { - u64 onchain_wallet_sync_timeout_secs; - u64 lightning_wallet_sync_timeout_secs; - u64 fee_rate_cache_update_timeout_secs; - u64 tx_broadcast_timeout_secs; - u8 per_request_timeout_secs; -}; +typedef dictionary SyncTimeoutsConfig; -dictionary EsploraSyncConfig { - BackgroundSyncConfig? background_sync_config; - SyncTimeoutsConfig timeouts_config; -}; +typedef dictionary EsploraSyncConfig; -dictionary ElectrumSyncConfig { - BackgroundSyncConfig? background_sync_config; - SyncTimeoutsConfig timeouts_config; -}; +typedef dictionary ElectrumSyncConfig; -dictionary LSPS2ServiceConfig { - string? require_token; - boolean advertise_service; - u32 channel_opening_fee_ppm; - u32 channel_over_provisioning_ppm; - u64 min_channel_opening_fee_msat; - u32 min_channel_lifetime; - u32 max_client_to_self_delay; - u64 min_payment_size_msat; - u64 max_payment_size_msat; - boolean client_trusts_lsp; -}; +typedef dictionary LSPS2ServiceConfig; interface NodeEntropy { [Name=from_bip39_mnemonic] @@ -359,16 +319,7 @@ enum NodeError { "HrnParsingFailed", }; -dictionary NodeStatus { - boolean is_running; - BestBlock current_best_block; - u64? latest_lightning_wallet_sync_timestamp; - u64? latest_onchain_wallet_sync_timestamp; - u64? latest_fee_rate_cache_update_timestamp; - u64? latest_rgs_snapshot_timestamp; - u64? latest_pathfinding_scores_sync_timestamp; - u64? latest_node_announcement_broadcast_timestamp; -}; +typedef dictionary NodeStatus; [Remote] dictionary BestBlock { @@ -423,15 +374,7 @@ typedef dictionary LSPFeeLimits; typedef enum ConfirmationStatus; -dictionary PaymentDetails { - PaymentId id; - PaymentKind kind; - u64? amount_msat; - u64? fee_paid_msat; - PaymentDirection direction; - PaymentStatus status; - u64 latest_update_timestamp; -}; +typedef dictionary PaymentDetails; [Remote] dictionary RouteParametersConfig { @@ -441,10 +384,7 @@ dictionary RouteParametersConfig { u8 max_channel_saturation_power_of_half; }; -dictionary CustomTlvRecord { - u64 type_num; - sequence value; -}; +typedef dictionary CustomTlvRecord; [Remote] dictionary LSPS1OrderStatus { @@ -517,47 +457,9 @@ dictionary OutPoint { u32 vout; }; -dictionary ChannelDetails { - ChannelId channel_id; - PublicKey counterparty_node_id; - OutPoint? funding_txo; - ScriptBuf? funding_redeem_script; - u64? short_channel_id; - u64? outbound_scid_alias; - u64? inbound_scid_alias; - u64 channel_value_sats; - u64? unspendable_punishment_reserve; - UserChannelId user_channel_id; - u32 feerate_sat_per_1000_weight; - u64 outbound_capacity_msat; - u64 inbound_capacity_msat; - u32? confirmations_required; - u32? confirmations; - boolean is_outbound; - boolean is_channel_ready; - boolean is_usable; - boolean is_announced; - u16? cltv_expiry_delta; - u64 counterparty_unspendable_punishment_reserve; - u64? counterparty_outbound_htlc_minimum_msat; - u64? counterparty_outbound_htlc_maximum_msat; - u32? counterparty_forwarding_info_fee_base_msat; - u32? counterparty_forwarding_info_fee_proportional_millionths; - u16? counterparty_forwarding_info_cltv_expiry_delta; - u64 next_outbound_htlc_limit_msat; - u64 next_outbound_htlc_minimum_msat; - u16? force_close_spend_delay; - u64 inbound_htlc_minimum_msat; - u64? inbound_htlc_maximum_msat; - ChannelConfig config; -}; - -dictionary PeerDetails { - PublicKey node_id; - SocketAddress address; - boolean is_persisted; - boolean is_connected; -}; +typedef dictionary ChannelDetails; + +typedef dictionary PeerDetails; typedef enum LightningBalance; @@ -571,23 +473,9 @@ enum BalanceSource { typedef enum PendingSweepBalance; -dictionary BalanceDetails { - u64 total_onchain_balance_sats; - u64 spendable_onchain_balance_sats; - u64 total_anchor_channels_reserve_sats; - u64 total_lightning_balance_sats; - sequence lightning_balances; - sequence pending_balances_from_channel_closures; -}; +typedef dictionary BalanceDetails; -dictionary ChannelConfig { - u32 forwarding_fee_proportional_millionths; - u32 forwarding_fee_base_msat; - u16 cltv_expiry_delta; - MaxDustHTLCExposure max_dust_htlc_exposure; - u64 force_close_avoidance_max_fee_satoshis; - boolean accept_underpaying_htlcs; -}; +typedef dictionary ChannelConfig; typedef enum MaxDustHTLCExposure; diff --git a/src/balance.rs b/src/balance.rs index abd3f0d82..6c6ad946d 100644 --- a/src/balance.rs +++ b/src/balance.rs @@ -17,6 +17,7 @@ use lightning_types::payment::{PaymentHash, PaymentPreimage}; /// /// [`Node::list_balances`]: crate::Node::list_balances #[derive(Debug, Clone)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct BalanceDetails { /// The total balance of our on-chain wallet. pub total_onchain_balance_sats: u64, diff --git a/src/config.rs b/src/config.rs index 6330f087a..37ed170a1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -108,6 +108,7 @@ pub(crate) const EXTERNAL_PATHFINDING_SCORES_SYNC_TIMEOUT_SECS: u64 = 5; pub(crate) const HRN_RESOLUTION_TIMEOUT_SECS: u64 = 5; #[derive(Debug, Clone)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] /// Represents the configuration of an [`Node`] instance. /// /// ### Defaults @@ -238,6 +239,7 @@ impl Default for Config { /// /// [BOLT 3]: https://github.com/lightning/bolts/blob/master/03-transactions.md#htlc-timeout-and-htlc-success-transactions #[derive(Debug, Clone)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct AnchorChannelsConfig { /// A list of peers that we trust to get the required channel closing transactions confirmed /// on-chain. @@ -353,6 +355,7 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig { /// | `lightning_wallet_sync_interval_secs` | 30 | /// | `fee_rate_cache_update_interval_secs` | 600 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct BackgroundSyncConfig { /// The time in-between background sync attempts of the onchain wallet, in seconds. /// @@ -392,6 +395,7 @@ impl Default for BackgroundSyncConfig { /// | `tx_broadcast_timeout_secs` | 10 | /// | `per_request_timeout_secs` | 10 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct SyncTimeoutsConfig { /// The timeout after which we abort syncing the onchain wallet. pub onchain_wallet_sync_timeout_secs: u64, @@ -422,6 +426,7 @@ impl Default for SyncTimeoutsConfig { /// Background syncing is enabled by default, using the default values specified in /// [`BackgroundSyncConfig`]. #[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct EsploraSyncConfig { /// Background sync configuration. /// @@ -448,6 +453,7 @@ impl Default for EsploraSyncConfig { /// Background syncing is enabled by default, using the default values specified in /// [`BackgroundSyncConfig`]. #[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct ElectrumSyncConfig { /// Background sync configuration. /// @@ -481,6 +487,7 @@ pub struct BitcoindRestClientConfig { /// Options which apply on a per-channel basis and may change at runtime or based on negotiation /// with our counterparty. #[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct ChannelConfig { /// Amount (in millionths of a satoshi) charged per satoshi for payments forwarded outbound /// over the channel. diff --git a/src/ffi/types.rs b/src/ffi/types.rs index cafb2ac1c..ee8e5c00e 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -138,16 +138,12 @@ impl VssClientHeaderProvider for VssHeaderProviderAdapter { } use crate::builder::sanitize_alias; -pub use crate::config::{ - default_config, AnchorChannelsConfig, BackgroundSyncConfig, ElectrumSyncConfig, - EsploraSyncConfig, MaxDustHTLCExposure, SyncTimeoutsConfig, -}; +pub use crate::config::{default_config, ElectrumSyncConfig, EsploraSyncConfig}; pub use crate::entropy::{generate_entropy_mnemonic, EntropyError, NodeEntropy, WordCount}; use crate::error::Error; pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo}; -pub use crate::liquidity::{LSPS1OrderStatus, LSPS2ServiceConfig}; +pub use crate::liquidity::LSPS1OrderStatus; pub use crate::logger::{LogLevel, LogRecord, LogWriter}; -pub use crate::payment::store::{PaymentDirection, PaymentKind, PaymentStatus}; pub use crate::payment::UnifiedPaymentResult; use crate::{hex_utils, SocketAddress, UserChannelId}; diff --git a/src/lib.rs b/src/lib.rs index 1b93cb6e9..3c214026a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1735,6 +1735,7 @@ impl Drop for Node { /// Represents the status of the [`Node`]. #[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct NodeStatus { /// Indicates whether the [`Node`] is running. pub is_running: bool, diff --git a/src/liquidity.rs b/src/liquidity.rs index c22df0898..4eac44672 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -99,6 +99,7 @@ struct LSPS2Service { /// /// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md #[derive(Debug, Clone)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct LSPS2ServiceConfig { /// A token we may require to be sent by the clients. /// diff --git a/src/payment/store.rs b/src/payment/store.rs index 1b2edb29a..0e2de9815 100644 --- a/src/payment/store.rs +++ b/src/payment/store.rs @@ -24,6 +24,7 @@ use crate::hex_utils; /// Represents a payment. #[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct PaymentDetails { /// The identifier of this payment. pub id: PaymentId, diff --git a/src/types.rs b/src/types.rs index c5ff07756..381bfbd21 100644 --- a/src/types.rs +++ b/src/types.rs @@ -355,6 +355,7 @@ impl fmt::Display for UserChannelId { /// /// [`Node::list_channels`]: crate::Node::list_channels #[derive(Debug, Clone)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct ChannelDetails { /// The channel's ID (prior to initial channel setup this is a random 32 bytes, thereafter it /// is derived from channel funding or key material). @@ -591,6 +592,7 @@ impl From for ChannelDetails { /// /// [`Node::list_peers`]: crate::Node::list_peers #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct PeerDetails { /// The node ID of the peer. pub node_id: PublicKey, @@ -604,6 +606,7 @@ pub struct PeerDetails { /// Custom TLV entry. #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct CustomTlvRecord { /// Type number. pub type_num: u64, From cff77d9e0a42da972959bbf075e19d4a0b5e88a8 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 27 Feb 2026 13:03:16 +0100 Subject: [PATCH 06/10] Migrate FFI-only types and remaining types to UniFFI proc-macro export Migrate `Bolt11InvoiceDescription`, `OfferAmount`, `UnifiedPaymentResult`, `RouteHintHop`, `LogRecord`, `LSPS1PaymentInfo`, `LSPS1Bolt11PaymentInfo`, `LSPS1OnchainPaymentInfo`, `ChannelInfo`, `ChannelUpdateInfo`, `NodeInfo`, `NodeAnnouncementInfo`, and `VssHeaderProviderError` from UDL definitions to proc-macro derives. Co-Authored-By: HAL 9000 --- bindings/ldk_node.udl | 98 ++++++------------------------------------ src/ffi/types.rs | 16 +++---- src/graph.rs | 8 ++-- src/logger.rs | 1 + src/payment/unified.rs | 1 + 5 files changed, 27 insertions(+), 97 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 53f289efb..68f683e81 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -40,15 +40,7 @@ enum LogLevel { "Error", }; -dictionary LogRecord { - LogLevel level; - string args; - string module_path; - u32 line; - PublicKey? peer_id; - ChannelId? channel_id; - PaymentHash? payment_hash; -}; +typedef dictionary LogRecord; [Trait, WithForeign] interface LogWriter { @@ -151,11 +143,7 @@ interface Node { bytes export_pathfinding_scores(); }; -[Enum] -interface Bolt11InvoiceDescription { - Hash(string hash); - Direct(string description); -}; +typedef enum Bolt11InvoiceDescription; interface Bolt11Payment { [Throws=NodeError] @@ -335,13 +323,7 @@ interface VssHeaderProvider { record get_headers(sequence request); }; -[Error] -interface VssHeaderProviderError { - InvalidData(string error); - RequestError(string error); - AuthorizationError(string error); - InternalError(string error); -}; +typedef enum VssHeaderProviderError; [Remote] enum PaymentFailureReason { @@ -359,12 +341,7 @@ enum PaymentFailureReason { typedef enum PaymentKind; -[Enum] -interface UnifiedPaymentResult { - Onchain(Txid txid); - Bolt11(PaymentId payment_id); - Bolt12(PaymentId payment_id); -}; +typedef enum UnifiedPaymentResult; typedef enum PaymentDirection; @@ -405,29 +382,11 @@ dictionary LSPS1OrderParams { boolean announce_channel; }; -dictionary LSPS1PaymentInfo { - LSPS1Bolt11PaymentInfo? bolt11; - LSPS1OnchainPaymentInfo? onchain; -}; +typedef dictionary LSPS1PaymentInfo; -dictionary LSPS1Bolt11PaymentInfo { - LSPS1PaymentState state; - LSPSDateTime expires_at; - u64 fee_total_sat; - u64 order_total_sat; - Bolt11Invoice invoice; -}; +typedef dictionary LSPS1Bolt11PaymentInfo; -dictionary LSPS1OnchainPaymentInfo { - LSPS1PaymentState state; - LSPSDateTime expires_at; - u64 fee_total_sat; - u64 order_total_sat; - Address address; - u16? min_onchain_payment_confirmations; - FeeRate min_fee_for_0conf; - Address? refund_onchain_address; -}; +typedef dictionary LSPS1OnchainPaymentInfo; [Remote] dictionary LSPS1ChannelInfo { @@ -486,22 +445,9 @@ interface NetworkGraph { NodeInfo? node([ByRef]NodeId node_id); }; -dictionary ChannelInfo { - NodeId node_one; - ChannelUpdateInfo? one_to_two; - NodeId node_two; - ChannelUpdateInfo? two_to_one; - u64? capacity_sats; -}; +typedef dictionary ChannelInfo; -dictionary ChannelUpdateInfo { - u32 last_update; - boolean enabled; - u16 cltv_expiry_delta; - u64 htlc_minimum_msat; - u64 htlc_maximum_msat; - RoutingFees fees; -}; +typedef dictionary ChannelUpdateInfo; [Remote] dictionary RoutingFees { @@ -509,16 +455,9 @@ dictionary RoutingFees { u32 proportional_millionths; }; -dictionary NodeInfo { - sequence channels; - NodeAnnouncementInfo? announcement_info; -}; +typedef dictionary NodeInfo; -dictionary NodeAnnouncementInfo { - u32 last_update; - string alias; - sequence addresses; -}; +typedef dictionary NodeAnnouncementInfo; [Remote] enum Currency { @@ -531,14 +470,7 @@ enum Currency { typedef enum AsyncPaymentsRole; -dictionary RouteHintHop { - PublicKey src_node_id; - u64 short_channel_id; - u16 cltv_expiry_delta; - u64? htlc_minimum_msat; - u64? htlc_maximum_msat; - RoutingFees fees; -}; +typedef dictionary RouteHintHop; [Traits=(Debug, Display, Eq)] interface Bolt11Invoice { @@ -562,11 +494,7 @@ interface Bolt11Invoice { PublicKey recover_payee_pub_key(); }; -[Enum] -interface OfferAmount { - Bitcoin(u64 amount_msats); - Currency(string iso4217_code, u64 amount); -}; +typedef enum OfferAmount; [Traits=(Debug, Display, Eq)] interface Offer { diff --git a/src/ffi/types.rs b/src/ffi/types.rs index ee8e5c00e..7a55227f4 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -47,7 +47,7 @@ use vss_client::headers::VssHeaderProvider as VssClientHeaderProvider; use vss_client::headers::VssHeaderProviderError as VssClientHeaderProviderError; /// Errors around providing headers for each VSS request. -#[derive(Debug)] +#[derive(Debug, uniffi::Error)] pub enum VssHeaderProviderError { /// Invalid data was encountered. InvalidData { @@ -141,7 +141,7 @@ use crate::builder::sanitize_alias; pub use crate::config::{default_config, ElectrumSyncConfig, EsploraSyncConfig}; pub use crate::entropy::{generate_entropy_mnemonic, EntropyError, NodeEntropy, WordCount}; use crate::error::Error; -pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo}; +pub use crate::graph::{ChannelInfo, NodeInfo}; pub use crate::liquidity::LSPS1OrderStatus; pub use crate::logger::{LogLevel, LogRecord, LogWriter}; pub use crate::payment::UnifiedPaymentResult; @@ -203,7 +203,7 @@ uniffi::custom_type!(ScriptBuf, String, { }, }); -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, uniffi::Enum)] pub enum OfferAmount { Bitcoin { amount_msats: u64 }, Currency { iso4217_code: String, amount: u64 }, @@ -933,7 +933,7 @@ uniffi::custom_type!(NodeAlias, String, { /// Represents the description of an invoice which has to be either a directly included string or /// a hash of a description provided out of band. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, uniffi::Enum)] pub enum Bolt11InvoiceDescription { /// Contains a full description. Direct { @@ -1026,7 +1026,7 @@ impl From for Currency { /// /// While this generally comes from BOLT 11's `r` field, this struct includes more fields than are /// available in BOLT 11. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, uniffi::Record)] pub struct RouteHintHop { /// The node_id of the non-target end of the route pub src_node_id: PublicKey, @@ -1198,7 +1198,7 @@ impl std::fmt::Display for Bolt11Invoice { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)] pub struct LSPS1PaymentInfo { /// A Lightning payment using BOLT 11. pub bolt11: Option, @@ -1218,7 +1218,7 @@ impl From for LSPS1PaymentIn /// An onchain payment. #[cfg(feature = "uniffi")] -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)] pub struct LSPS1OnchainPaymentInfo { /// Indicates the current state of the payment. pub state: lightning_liquidity::lsps1::msgs::LSPS1PaymentState, @@ -1257,7 +1257,7 @@ impl From for LSPS1On } } /// A Lightning payment using BOLT 11. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)] pub struct LSPS1Bolt11PaymentInfo { /// Indicates the current state of the payment. pub state: LSPS1PaymentState, diff --git a/src/graph.rs b/src/graph.rs index f2daebb9f..87e0de1cc 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -56,7 +56,7 @@ impl NetworkGraph { /// /// This is a simplified version of LDK's `ChannelInfo` for bindings. #[cfg(feature = "uniffi")] -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)] pub struct ChannelInfo { /// Source node of the first direction of a channel pub node_one: NodeId, @@ -87,7 +87,7 @@ impl From for ChannelInfo { /// /// This is a simplified version of LDK's `ChannelUpdateInfo` for bindings. #[cfg(feature = "uniffi")] -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)] pub struct ChannelUpdateInfo { /// When the last update to the channel direction was issued. /// Value is opaque, as set in the announcement. @@ -122,7 +122,7 @@ impl From for ChannelUpdateInfo { /// /// This is a simplified version of LDK's `NodeInfo` for bindings. #[cfg(feature = "uniffi")] -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)] pub struct NodeInfo { /// All valid channels a node has announced pub channels: Vec, @@ -146,7 +146,7 @@ impl From for NodeInfo { /// /// This is a simplified version of LDK's `NodeAnnouncementInfo` for bindings. #[cfg(feature = "uniffi")] -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)] pub struct NodeAnnouncementInfo { /// When the last known update to the node state was issued. /// Value is opaque, as set in the announcement. diff --git a/src/logger.rs b/src/logger.rs index f2b53a1dc..fed64d7a5 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -101,6 +101,7 @@ impl fmt::Display for LogContext<'_> { /// It is similar to the non-`uniffi` version, but it omits the lifetime parameter /// for the `LogRecord`, as the Uniffi-exposed interface cannot handle lifetimes. #[cfg(feature = "uniffi")] +#[derive(uniffi::Record)] pub struct LogRecord { /// The verbosity level of the message. pub level: LogLevel, diff --git a/src/payment/unified.rs b/src/payment/unified.rs index 671af14ff..06ab8bf01 100644 --- a/src/payment/unified.rs +++ b/src/payment/unified.rs @@ -296,6 +296,7 @@ impl UnifiedPayment { /// [`PaymentId`]: lightning::ln::channelmanager::PaymentId /// [`Txid`]: bitcoin::hash_types::Txid #[derive(Debug)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] pub enum UnifiedPaymentResult { /// An on-chain payment. Onchain { From e530a03548347d48fbe35ffd767d3e2c29334867 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 27 Feb 2026 13:43:17 +0100 Subject: [PATCH 07/10] Migrate `NetworkGraph`, `NodeEntropy`, and `LSPS1Liquidity` interfaces to UniFFI proc-macro export Move these three simple Object types from UDL interface definitions to `#[derive(uniffi::Object)]` + `#[uniffi::export]` proc-macro annotations, replacing their UDL interface blocks with `typedef interface` references. Generated with the assistance of AI (Claude). Co-Authored-By: HAL 9000 --- bindings/ldk_node.udl | 23 +++-------------------- src/entropy.rs | 33 ++++++++++++++++++++------------- src/ffi/types.rs | 3 +-- src/graph.rs | 4 ++++ src/liquidity.rs | 4 ++++ 5 files changed, 32 insertions(+), 35 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 68f683e81..22ed1bffe 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -17,14 +17,7 @@ typedef dictionary ElectrumSyncConfig; typedef dictionary LSPS2ServiceConfig; -interface NodeEntropy { - [Name=from_bip39_mnemonic] - constructor(Mnemonic mnemonic, string? passphrase); - [Throws=EntropyError, Name=from_seed_bytes] - constructor(bytes seed_bytes); - [Throws=EntropyError, Name=from_seed_path] - constructor(string seed_path); -}; +typedef interface NodeEntropy; typedef enum EntropyError; @@ -239,12 +232,7 @@ interface UnifiedPayment { UnifiedPaymentResult send([ByRef]string uri_str, u64? amount_msat, RouteParametersConfig? route_parameters); }; -interface LSPS1Liquidity { - [Throws=NodeError] - LSPS1OrderStatus request_channel(u64 lsp_balance_sat, u64 client_balance_sat, u32 channel_expiry_blocks, boolean announce_channel); - [Throws=NodeError] - LSPS1OrderStatus check_order_status(LSPS1OrderId order_id); -}; +typedef interface LSPS1Liquidity; [Error] enum NodeError { @@ -438,12 +426,7 @@ typedef dictionary ChannelConfig; typedef enum MaxDustHTLCExposure; -interface NetworkGraph { - sequence list_channels(); - ChannelInfo? channel(u64 short_channel_id); - sequence list_nodes(); - NodeInfo? node([ByRef]NodeId node_id); -}; +typedef interface NetworkGraph; typedef dictionary ChannelInfo; diff --git a/src/entropy.rs b/src/entropy.rs index 650849641..beed1e9ea 100644 --- a/src/entropy.rs +++ b/src/entropy.rs @@ -40,13 +40,31 @@ impl std::error::Error for EntropyError {} /// /// [`Node`]: crate::Node #[derive(Copy, Clone)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] pub struct NodeEntropy([u8; WALLET_KEYS_SEED_LEN]); +impl NodeEntropy { + /// Configures the [`Node`] instance to source its wallet entropy from the given + /// [`WALLET_KEYS_SEED_LEN`] seed bytes. + /// + /// [`Node`]: crate::Node + #[cfg(not(feature = "uniffi"))] + pub fn from_seed_bytes(seed_bytes: [u8; WALLET_KEYS_SEED_LEN]) -> Self { + Self(seed_bytes) + } + + pub(crate) fn to_seed_bytes(&self) -> [u8; WALLET_KEYS_SEED_LEN] { + self.0 + } +} + +#[cfg_attr(feature = "uniffi", uniffi::export)] impl NodeEntropy { /// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic. /// /// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki /// [`Node`]: crate::Node + #[cfg_attr(feature = "uniffi", uniffi::constructor)] pub fn from_bip39_mnemonic(mnemonic: Mnemonic, passphrase: Option) -> Self { match passphrase { Some(passphrase) => Self(mnemonic.to_seed(passphrase)), @@ -54,15 +72,6 @@ impl NodeEntropy { } } - /// Configures the [`Node`] instance to source its wallet entropy from the given - /// [`WALLET_KEYS_SEED_LEN`] seed bytes. - /// - /// [`Node`]: crate::Node - #[cfg(not(feature = "uniffi"))] - pub fn from_seed_bytes(seed_bytes: [u8; WALLET_KEYS_SEED_LEN]) -> Self { - Self(seed_bytes) - } - /// Configures the [`Node`] instance to source its wallet entropy from the given /// [`WALLET_KEYS_SEED_LEN`] seed bytes. /// @@ -71,6 +80,7 @@ impl NodeEntropy { /// /// [`Node`]: crate::Node #[cfg(feature = "uniffi")] + #[uniffi::constructor] pub fn from_seed_bytes(seed_bytes: Vec) -> Result { if seed_bytes.len() != WALLET_KEYS_SEED_LEN { return Err(EntropyError::InvalidSeedBytes); @@ -86,16 +96,13 @@ impl NodeEntropy { /// stored at the given location. /// /// [`Node`]: crate::Node + #[cfg_attr(feature = "uniffi", uniffi::constructor)] pub fn from_seed_path(seed_path: String) -> Result { Ok(Self( io::utils::read_or_generate_seed_file(&seed_path) .map_err(|_| EntropyError::InvalidSeedFile)?, )) } - - pub(crate) fn to_seed_bytes(&self) -> [u8; WALLET_KEYS_SEED_LEN] { - self.0 - } } impl fmt::Display for NodeEntropy { diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 7a55227f4..9c56b5ebd 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -139,9 +139,8 @@ impl VssClientHeaderProvider for VssHeaderProviderAdapter { use crate::builder::sanitize_alias; pub use crate::config::{default_config, ElectrumSyncConfig, EsploraSyncConfig}; -pub use crate::entropy::{generate_entropy_mnemonic, EntropyError, NodeEntropy, WordCount}; +pub use crate::entropy::{generate_entropy_mnemonic, NodeEntropy, WordCount}; use crate::error::Error; -pub use crate::graph::{ChannelInfo, NodeInfo}; pub use crate::liquidity::LSPS1OrderStatus; pub use crate::logger::{LogLevel, LogRecord, LogWriter}; pub use crate::payment::UnifiedPaymentResult; diff --git a/src/graph.rs b/src/graph.rs index 87e0de1cc..a83318609 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -20,6 +20,7 @@ use lightning::routing::gossip::{ChannelInfo, NodeInfo}; use crate::types::Graph; /// Represents the network as nodes and channels between them. +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] pub struct NetworkGraph { inner: Arc, } @@ -28,7 +29,10 @@ impl NetworkGraph { pub(crate) fn new(inner: Arc) -> Self { Self { inner } } +} +#[cfg_attr(feature = "uniffi", uniffi::export)] +impl NetworkGraph { /// Returns the list of channels in the graph pub fn list_channels(&self) -> Vec { self.inner.read_only().channels().unordered_keys().map(|c| *c).collect() diff --git a/src/liquidity.rs b/src/liquidity.rs index 4eac44672..ee9863eb6 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -1451,6 +1451,7 @@ pub(crate) struct LSPS2BuyResponse { /// [`Node::lsps1_liquidity`]: crate::Node::lsps1_liquidity /// [`Bolt11Payment::receive_via_jit_channel`]: crate::payment::Bolt11Payment::receive_via_jit_channel #[derive(Clone)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] pub struct LSPS1Liquidity { runtime: Arc, wallet: Arc, @@ -1467,7 +1468,10 @@ impl LSPS1Liquidity { ) -> Self { Self { runtime, wallet, connection_manager, liquidity_source, logger } } +} +#[cfg_attr(feature = "uniffi", uniffi::export)] +impl LSPS1Liquidity { /// Connects to the configured LSP and places an order for an inbound channel. /// /// The channel will be opened after one of the returned payment options has successfully been From d02245931b595cc1c1ec3301aa73a730e51f4da2 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 27 Feb 2026 13:53:45 +0100 Subject: [PATCH 08/10] Migrate FFI wrapper objects to UniFFI proc-macro export Migrate `Bolt11Invoice`, `Offer`, `HumanReadableName`, `Refund`, and `Bolt12Invoice` from UDL interface definitions to proc-macro derives (`uniffi::Object`) with `#[uniffi::export]` on impl blocks and `#[uniffi::constructor]` on constructors. For types with `[Traits=(Debug, Display, Eq)]`, use `#[uniffi::export(Debug, Display, Eq)]` on the struct. Replace UDL interface definitions with `typedef interface` references. Co-Authored-By: HAL 9000 --- bindings/ldk_node.udl | 88 +++---------------------------------------- src/ffi/types.rs | 22 +++++++++-- 2 files changed, 23 insertions(+), 87 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 22ed1bffe..34afff113 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -455,95 +455,17 @@ typedef enum AsyncPaymentsRole; typedef dictionary RouteHintHop; -[Traits=(Debug, Display, Eq)] -interface Bolt11Invoice { - [Throws=NodeError, Name=from_str] - constructor([ByRef] string invoice_str); - sequence signable_hash(); - PaymentHash payment_hash(); - PaymentSecret payment_secret(); - u64? amount_milli_satoshis(); - u64 expiry_time_seconds(); - u64 seconds_since_epoch(); - u64 seconds_until_expiry(); - boolean is_expired(); - boolean would_expire(u64 at_time_seconds); - Bolt11InvoiceDescription invoice_description(); - u64 min_final_cltv_expiry_delta(); - Network network(); - Currency currency(); - sequence
fallback_addresses(); - sequence> route_hints(); - PublicKey recover_payee_pub_key(); -}; +typedef interface Bolt11Invoice; typedef enum OfferAmount; -[Traits=(Debug, Display, Eq)] -interface Offer { - [Throws=NodeError, Name=from_str] - constructor([ByRef] string offer_str); - OfferId id(); - boolean is_expired(); - string? offer_description(); - string? issuer(); - OfferAmount? amount(); - boolean is_valid_quantity(u64 quantity); - boolean expects_quantity(); - boolean supports_chain(Network chain); - sequence chains(); - sequence? metadata(); - u64? absolute_expiry_seconds(); - PublicKey? issuer_signing_pubkey(); -}; +typedef interface Offer; -interface HumanReadableName { - [Throws=NodeError, Name=from_encoded] - constructor([ByRef] string encoded); - string user(); - string domain(); -}; +typedef interface HumanReadableName; -[Traits=(Debug, Display, Eq)] -interface Refund { - [Throws=NodeError, Name=from_str] - constructor([ByRef] string refund_str); - string refund_description(); - u64? absolute_expiry_seconds(); - boolean is_expired(); - string? issuer(); - sequence payer_metadata(); - Network? chain(); - u64 amount_msats(); - u64? quantity(); - PublicKey payer_signing_pubkey(); - string? payer_note(); -}; +typedef interface Refund; -interface Bolt12Invoice { - [Throws=NodeError, Name=from_str] - constructor([ByRef] string invoice_str); - PaymentHash payment_hash(); - u64 amount_msats(); - OfferAmount? amount(); - PublicKey signing_pubkey(); - u64 created_at(); - u64? absolute_expiry_seconds(); - u64 relative_expiry(); - boolean is_expired(); - string? invoice_description(); - string? issuer(); - string? payer_note(); - sequence? metadata(); - u64? quantity(); - sequence signable_hash(); - PublicKey payer_signing_pubkey(); - PublicKey? issuer_signing_pubkey(); - sequence chain(); - sequence>? offer_chains(); - sequence
fallback_addresses(); - sequence encode(); -}; +typedef interface Bolt12Invoice; [Custom] typedef string Txid; diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 9c56b5ebd..650ef88ed 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -233,12 +233,15 @@ impl From for OfferAmount { /// [`InvoiceRequest`]: lightning::offers::invoice_request::InvoiceRequest /// [`Bolt12Invoice`]: lightning::offers::invoice::Bolt12Invoice /// [`Offer`]: lightning::offers::Offer:amount -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, uniffi::Object)] +#[uniffi::export(Debug, Display, Eq)] pub struct Offer { pub(crate) inner: LdkOffer, } +#[uniffi::export] impl Offer { + #[uniffi::constructor] pub fn from_str(offer_str: &str) -> Result { offer_str.parse() } @@ -376,15 +379,18 @@ impl std::fmt::Display for Offer { /// This struct can also be used for LN-Address recipients. /// /// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack +#[derive(uniffi::Object)] pub struct HumanReadableName { pub(crate) inner: LdkHumanReadableName, } +#[uniffi::export] impl HumanReadableName { /// Constructs a new [`HumanReadableName`] from the standard encoding - `user`@`domain`. /// /// If `user` includes the standard BIP 353 ₿ prefix it is automatically removed as required by /// BIP 353. + #[uniffi::constructor] pub fn from_encoded(encoded: &str) -> Result { let hrn = match LdkHumanReadableName::from_encoded(encoded) { Ok(hrn) => Ok(hrn), @@ -438,12 +444,15 @@ impl AsRef for HumanReadableName { /// /// [`Bolt12Invoice`]: lightning::offers::invoice::Bolt12Invoice /// [`Offer`]: lightning::offers::offer::Offer -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, uniffi::Object)] +#[uniffi::export(Debug, Display, Eq)] pub struct Refund { pub(crate) inner: LdkRefund, } +#[uniffi::export] impl Refund { + #[uniffi::constructor] pub fn from_str(refund_str: &str) -> Result { refund_str.parse() } @@ -550,12 +559,14 @@ impl std::fmt::Display for Refund { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, uniffi::Object)] pub struct Bolt12Invoice { pub(crate) inner: LdkBolt12Invoice, } +#[uniffi::export] impl Bolt12Invoice { + #[uniffi::constructor] pub fn from_str(invoice_str: &str) -> Result { invoice_str.parse() } @@ -1055,12 +1066,15 @@ impl From for RouteHintHop { } /// Represents a syntactically and semantically correct lightning BOLT11 invoice. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, uniffi::Object)] +#[uniffi::export(Debug, Display, Eq)] pub struct Bolt11Invoice { pub(crate) inner: LdkBolt11Invoice, } +#[uniffi::export] impl Bolt11Invoice { + #[uniffi::constructor] pub fn from_str(invoice_str: &str) -> Result { invoice_str.parse() } From 18699d36394f554202142f44f6a9dfad6602a449 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 27 Feb 2026 14:48:19 +0100 Subject: [PATCH 09/10] Migrate payment interfaces to UniFFI proc-macro export Migrate `Bolt11Payment`, `Bolt12Payment`, `SpontaneousPayment`, `OnchainPayment`, and `UnifiedPayment` from UDL interface definitions to UniFFI proc-macro export. For each payment struct, split the impl block so that `pub(crate)` and private helper methods remain in a plain impl block while all `pub fn` methods are placed in a `#[cfg_attr(feature = "uniffi", uniffi::export)]` impl block. For `Bolt12Payment`, the cfg-gated `set_paths_to_static_invoice_server` and `blinded_paths_for_async_recipient` methods are placed in separate `#[cfg(not(feature = "uniffi"))]` and `#[cfg(feature = "uniffi")] #[uniffi::export]` impl blocks to avoid proc-macro conflicts. Replace the full interface definitions in the UDL with `typedef interface` one-liners and remove the now-unused `UnifiedPaymentResult` re-export from `ffi/types.rs`. This commit was made with assistance from an AI tool (Claude Code). Co-Authored-By: HAL 9000 --- bindings/ldk_node.udl | 83 +--------- src/ffi/types.rs | 1 - src/payment/bolt11.rs | 290 +++++++++++++++++------------------ src/payment/bolt12.rs | 301 ++++++++++++++++++------------------- src/payment/onchain.rs | 4 + src/payment/spontaneous.rs | 74 ++++----- src/payment/unified.rs | 4 + 7 files changed, 345 insertions(+), 412 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 34afff113..1df0c7b38 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -138,81 +138,13 @@ interface Node { typedef enum Bolt11InvoiceDescription; -interface Bolt11Payment { - [Throws=NodeError] - PaymentId send([ByRef]Bolt11Invoice invoice, RouteParametersConfig? route_parameters); - [Throws=NodeError] - PaymentId send_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat, RouteParametersConfig? route_parameters); - [Throws=NodeError] - void send_probes([ByRef]Bolt11Invoice invoice, RouteParametersConfig? route_parameters); - [Throws=NodeError] - void send_probes_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat, RouteParametersConfig? route_parameters); - [Throws=NodeError] - void claim_for_hash(PaymentHash payment_hash, u64 claimable_amount_msat, PaymentPreimage preimage); - [Throws=NodeError] - void fail_for_hash(PaymentHash payment_hash); - [Throws=NodeError] - Bolt11Invoice receive(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs); - [Throws=NodeError] - Bolt11Invoice receive_for_hash(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, PaymentHash payment_hash); - [Throws=NodeError] - Bolt11Invoice receive_variable_amount([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs); - [Throws=NodeError] - Bolt11Invoice receive_variable_amount_for_hash([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, PaymentHash payment_hash); - [Throws=NodeError] - Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_lsp_fee_limit_msat); - [Throws=NodeError] - Bolt11Invoice receive_via_jit_channel_for_hash(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_lsp_fee_limit_msat, PaymentHash payment_hash); - [Throws=NodeError] - Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat); - [Throws=NodeError] - Bolt11Invoice receive_variable_amount_via_jit_channel_for_hash([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat, PaymentHash payment_hash); -}; +typedef interface Bolt11Payment; -interface Bolt12Payment { - [Throws=NodeError] - PaymentId send([ByRef]Offer offer, u64? quantity, string? payer_note, RouteParametersConfig? route_parameters); - [Throws=NodeError] - PaymentId send_using_amount([ByRef]Offer offer, u64 amount_msat, u64? quantity, string? payer_note, RouteParametersConfig? route_parameters); - [Throws=NodeError] - Offer receive(u64 amount_msat, [ByRef]string description, u32? expiry_secs, u64? quantity); - [Throws=NodeError] - Offer receive_variable_amount([ByRef]string description, u32? expiry_secs); - [Throws=NodeError] - Bolt12Invoice request_refund_payment([ByRef]Refund refund); - [Throws=NodeError] - Refund initiate_refund(u64 amount_msat, u32 expiry_secs, u64? quantity, string? payer_note, RouteParametersConfig? route_parameters); - [Throws=NodeError] - Offer receive_async(); - [Throws=NodeError] - void set_paths_to_static_invoice_server(bytes paths); - [Throws=NodeError] - bytes blinded_paths_for_async_recipient(bytes recipient_id); -}; +typedef interface Bolt12Payment; -interface SpontaneousPayment { - [Throws=NodeError] - PaymentId send(u64 amount_msat, PublicKey node_id, RouteParametersConfig? route_parameters); - [Throws=NodeError] - PaymentId send_with_custom_tlvs(u64 amount_msat, PublicKey node_id, RouteParametersConfig? route_parameters, sequence custom_tlvs); - [Throws=NodeError] - PaymentId send_with_preimage(u64 amount_msat, PublicKey node_id, PaymentPreimage preimage, RouteParametersConfig? route_parameters); - [Throws=NodeError] - PaymentId send_with_preimage_and_custom_tlvs(u64 amount_msat, PublicKey node_id, sequence custom_tlvs, PaymentPreimage preimage, RouteParametersConfig? route_parameters); - [Throws=NodeError] - void send_probes(u64 amount_msat, PublicKey node_id); -}; +typedef interface SpontaneousPayment; -interface OnchainPayment { - [Throws=NodeError] - Address new_address(); - [Throws=NodeError] - Txid send_to_address([ByRef]Address address, u64 amount_sats, FeeRate? fee_rate); - [Throws=NodeError] - Txid send_all_to_address([ByRef]Address address, boolean retain_reserve, FeeRate? fee_rate); - [Throws=NodeError] - Txid bump_fee_rbf(PaymentId payment_id, FeeRate? fee_rate); -}; +typedef interface OnchainPayment; [Remote] interface FeeRate { @@ -225,12 +157,7 @@ interface FeeRate { u64 to_sat_per_vb_ceil(); }; -interface UnifiedPayment { - [Throws=NodeError] - string receive(u64 amount_sats, [ByRef]string message, u32 expiry_sec); - [Throws=NodeError, Async] - UnifiedPaymentResult send([ByRef]string uri_str, u64? amount_msat, RouteParametersConfig? route_parameters); -}; +typedef interface UnifiedPayment; typedef interface LSPS1Liquidity; diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 650ef88ed..8fe387078 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -143,7 +143,6 @@ pub use crate::entropy::{generate_entropy_mnemonic, NodeEntropy, WordCount}; use crate::error::Error; pub use crate::liquidity::LSPS1OrderStatus; pub use crate::logger::{LogLevel, LogRecord, LogWriter}; -pub use crate::payment::UnifiedPaymentResult; use crate::{hex_utils, SocketAddress, UserChannelId}; uniffi::custom_type!(PublicKey, String, { diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index 56eb2f20b..f2857e814 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -54,6 +54,7 @@ type Bolt11InvoiceDescription = crate::ffi::Bolt11InvoiceDescription; /// /// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md /// [`Node::bolt11_payment`]: crate::Node::bolt11_payment +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] pub struct Bolt11Payment { runtime: Arc, channel_manager: Arc, @@ -87,6 +88,152 @@ impl Bolt11Payment { } } + pub(crate) fn receive_inner( + &self, amount_msat: Option, invoice_description: &LdkBolt11InvoiceDescription, + expiry_secs: u32, manual_claim_payment_hash: Option, + ) -> Result { + let invoice = { + let invoice_params = Bolt11InvoiceParameters { + amount_msats: amount_msat, + description: invoice_description.clone(), + invoice_expiry_delta_secs: Some(expiry_secs), + payment_hash: manual_claim_payment_hash, + ..Default::default() + }; + + match self.channel_manager.create_bolt11_invoice(invoice_params) { + Ok(inv) => { + log_info!(self.logger, "Invoice created: {}", inv); + inv + }, + Err(e) => { + log_error!(self.logger, "Failed to create invoice: {}", e); + return Err(Error::InvoiceCreationFailed); + }, + } + }; + + let payment_hash = invoice.payment_hash(); + let payment_secret = invoice.payment_secret(); + let id = PaymentId(payment_hash.0); + let preimage = if manual_claim_payment_hash.is_none() { + // If the user hasn't registered a custom payment hash, we're positive ChannelManager + // will know the preimage at this point. + let res = self + .channel_manager + .get_payment_preimage(payment_hash, payment_secret.clone()) + .ok(); + debug_assert!(res.is_some(), "We just let ChannelManager create an inbound payment, it can't have forgotten the preimage by now."); + res + } else { + None + }; + let kind = PaymentKind::Bolt11 { + hash: payment_hash, + preimage, + secret: Some(payment_secret.clone()), + }; + let payment = PaymentDetails::new( + id, + kind, + amount_msat, + None, + PaymentDirection::Inbound, + PaymentStatus::Pending, + ); + self.payment_store.insert(payment)?; + + Ok(invoice) + } + + fn receive_via_jit_channel_inner( + &self, amount_msat: Option, description: &LdkBolt11InvoiceDescription, + expiry_secs: u32, max_total_lsp_fee_limit_msat: Option, + max_proportional_lsp_fee_limit_ppm_msat: Option, payment_hash: Option, + ) -> Result { + let liquidity_source = + self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?; + + let (node_id, address) = + liquidity_source.get_lsps2_lsp_details().ok_or(Error::LiquiditySourceUnavailable)?; + + let peer_info = PeerInfo { node_id, address }; + + let con_node_id = peer_info.node_id; + let con_addr = peer_info.address.clone(); + let con_cm = Arc::clone(&self.connection_manager); + + // We need to use our main runtime here as a local runtime might not be around to poll + // connection futures going forward. + self.runtime.block_on(async move { + con_cm.connect_peer_if_necessary(con_node_id, con_addr).await + })?; + + log_info!(self.logger, "Connected to LSP {}@{}. ", peer_info.node_id, peer_info.address); + + let liquidity_source = Arc::clone(&liquidity_source); + let (invoice, lsp_total_opening_fee, lsp_prop_opening_fee) = + self.runtime.block_on(async move { + if let Some(amount_msat) = amount_msat { + liquidity_source + .lsps2_receive_to_jit_channel( + amount_msat, + description, + expiry_secs, + max_total_lsp_fee_limit_msat, + payment_hash, + ) + .await + .map(|(invoice, total_fee)| (invoice, Some(total_fee), None)) + } else { + liquidity_source + .lsps2_receive_variable_amount_to_jit_channel( + description, + expiry_secs, + max_proportional_lsp_fee_limit_ppm_msat, + payment_hash, + ) + .await + .map(|(invoice, prop_fee)| (invoice, None, Some(prop_fee))) + } + })?; + + // Register payment in payment store. + let payment_hash = invoice.payment_hash(); + let payment_secret = invoice.payment_secret(); + let lsp_fee_limits = LSPFeeLimits { + max_total_opening_fee_msat: lsp_total_opening_fee, + max_proportional_opening_fee_ppm_msat: lsp_prop_opening_fee, + }; + let id = PaymentId(payment_hash.0); + let preimage = + self.channel_manager.get_payment_preimage(payment_hash, payment_secret.clone()).ok(); + let kind = PaymentKind::Bolt11Jit { + hash: payment_hash, + preimage, + secret: Some(payment_secret.clone()), + counterparty_skimmed_fee_msat: None, + lsp_fee_limits, + }; + let payment = PaymentDetails::new( + id, + kind, + amount_msat, + None, + PaymentDirection::Inbound, + PaymentStatus::Pending, + ); + self.payment_store.insert(payment)?; + + // Persist LSP peer to make sure we reconnect on restart. + self.peer_store.add_peer(peer_info)?; + + Ok(invoice) + } +} + +#[cfg_attr(feature = "uniffi", uniffi::export)] +impl Bolt11Payment { /// Send a payment given an invoice. /// /// If `route_parameters` are provided they will override the default as well as the @@ -478,64 +625,6 @@ impl Bolt11Payment { Ok(maybe_wrap(invoice)) } - pub(crate) fn receive_inner( - &self, amount_msat: Option, invoice_description: &LdkBolt11InvoiceDescription, - expiry_secs: u32, manual_claim_payment_hash: Option, - ) -> Result { - let invoice = { - let invoice_params = Bolt11InvoiceParameters { - amount_msats: amount_msat, - description: invoice_description.clone(), - invoice_expiry_delta_secs: Some(expiry_secs), - payment_hash: manual_claim_payment_hash, - ..Default::default() - }; - - match self.channel_manager.create_bolt11_invoice(invoice_params) { - Ok(inv) => { - log_info!(self.logger, "Invoice created: {}", inv); - inv - }, - Err(e) => { - log_error!(self.logger, "Failed to create invoice: {}", e); - return Err(Error::InvoiceCreationFailed); - }, - } - }; - - let payment_hash = invoice.payment_hash(); - let payment_secret = invoice.payment_secret(); - let id = PaymentId(payment_hash.0); - let preimage = if manual_claim_payment_hash.is_none() { - // If the user hasn't registered a custom payment hash, we're positive ChannelManager - // will know the preimage at this point. - let res = self - .channel_manager - .get_payment_preimage(payment_hash, payment_secret.clone()) - .ok(); - debug_assert!(res.is_some(), "We just let ChannelManager create an inbound payment, it can't have forgotten the preimage by now."); - res - } else { - None - }; - let kind = PaymentKind::Bolt11 { - hash: payment_hash, - preimage, - secret: Some(payment_secret.clone()), - }; - let payment = PaymentDetails::new( - id, - kind, - amount_msat, - None, - PaymentDirection::Inbound, - PaymentStatus::Pending, - ); - self.payment_store.insert(payment)?; - - Ok(invoice) - } - /// Returns a payable invoice that can be used to request a payment of the amount given and /// receive it via a newly created just-in-time (JIT) channel. /// @@ -668,91 +757,6 @@ impl Bolt11Payment { Ok(maybe_wrap(invoice)) } - fn receive_via_jit_channel_inner( - &self, amount_msat: Option, description: &LdkBolt11InvoiceDescription, - expiry_secs: u32, max_total_lsp_fee_limit_msat: Option, - max_proportional_lsp_fee_limit_ppm_msat: Option, payment_hash: Option, - ) -> Result { - let liquidity_source = - self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?; - - let (node_id, address) = - liquidity_source.get_lsps2_lsp_details().ok_or(Error::LiquiditySourceUnavailable)?; - - let peer_info = PeerInfo { node_id, address }; - - let con_node_id = peer_info.node_id; - let con_addr = peer_info.address.clone(); - let con_cm = Arc::clone(&self.connection_manager); - - // We need to use our main runtime here as a local runtime might not be around to poll - // connection futures going forward. - self.runtime.block_on(async move { - con_cm.connect_peer_if_necessary(con_node_id, con_addr).await - })?; - - log_info!(self.logger, "Connected to LSP {}@{}. ", peer_info.node_id, peer_info.address); - - let liquidity_source = Arc::clone(&liquidity_source); - let (invoice, lsp_total_opening_fee, lsp_prop_opening_fee) = - self.runtime.block_on(async move { - if let Some(amount_msat) = amount_msat { - liquidity_source - .lsps2_receive_to_jit_channel( - amount_msat, - description, - expiry_secs, - max_total_lsp_fee_limit_msat, - payment_hash, - ) - .await - .map(|(invoice, total_fee)| (invoice, Some(total_fee), None)) - } else { - liquidity_source - .lsps2_receive_variable_amount_to_jit_channel( - description, - expiry_secs, - max_proportional_lsp_fee_limit_ppm_msat, - payment_hash, - ) - .await - .map(|(invoice, prop_fee)| (invoice, None, Some(prop_fee))) - } - })?; - - // Register payment in payment store. - let payment_hash = invoice.payment_hash(); - let payment_secret = invoice.payment_secret(); - let lsp_fee_limits = LSPFeeLimits { - max_total_opening_fee_msat: lsp_total_opening_fee, - max_proportional_opening_fee_ppm_msat: lsp_prop_opening_fee, - }; - let id = PaymentId(payment_hash.0); - let preimage = - self.channel_manager.get_payment_preimage(payment_hash, payment_secret.clone()).ok(); - let kind = PaymentKind::Bolt11Jit { - hash: payment_hash, - preimage, - secret: Some(payment_secret.clone()), - counterparty_skimmed_fee_msat: None, - lsp_fee_limits, - }; - let payment = PaymentDetails::new( - id, - kind, - amount_msat, - None, - PaymentDirection::Inbound, - PaymentStatus::Pending, - ); - self.payment_store.insert(payment)?; - - // Persist LSP peer to make sure we reconnect on restart. - self.peer_store.add_peer(peer_info)?; - - Ok(invoice) - } - /// Sends payment probes over all paths of a route that would be used to pay the given invoice. /// /// This may be used to send "pre-flight" probes, i.e., to train our scorer before conducting diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index d5ee30ba3..980e20696 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -57,6 +57,7 @@ type HumanReadableName = Arc; /// /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md /// [`Node::bolt12_payment`]: crate::Node::bolt12_payment +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] pub struct Bolt12Payment { channel_manager: Arc, keys_manager: Arc, @@ -84,18 +85,9 @@ impl Bolt12Payment { } } - /// Send a payment given an offer. - /// - /// If `payer_note` is `Some` it will be seen by the recipient and reflected back in the invoice - /// response. - /// - /// If `quantity` is `Some` it represents the number of items requested. - /// - /// If `route_parameters` are provided they will override the default as well as the - /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis. - pub fn send( - &self, offer: &Offer, quantity: Option, payer_note: Option, - route_parameters: Option, + pub(crate) fn send_using_amount_inner( + &self, offer: &Offer, amount_msat: u64, quantity: Option, payer_note: Option, + route_parameters: Option, hrn: Option, ) -> Result { if !*self.is_running.read().unwrap() { return Err(Error::NotRunning); @@ -114,22 +106,35 @@ impl Bolt12Payment { log_error!(self.logger, "Failed to send payment as the provided offer was denominated in an unsupported currency."); return Err(Error::UnsupportedCurrency); }, - None => { - log_error!(self.logger, "Failed to send payment due to the given offer being \"zero-amount\". Please use send_using_amount instead."); - return Err(Error::InvalidOffer); - }, + None => amount_msat, }; + if amount_msat < offer_amount_msat { + log_error!( + self.logger, + "Failed to pay as the given amount needs to be at least the offer amount: required {}msat, gave {}msat.", offer_amount_msat, amount_msat); + return Err(Error::InvalidAmount); + } + let params = OptionalOfferPaymentParams { payer_note: payer_note.clone(), retry_strategy, route_params_config: route_parameters, }; - let res = if let Some(quantity) = quantity { - self.channel_manager - .pay_for_offer_with_quantity(&offer, None, payment_id, params, quantity) + let res = if let Some(hrn) = hrn { + let hrn = maybe_deref(&hrn); + let offer = OfferFromHrn { offer: offer.clone(), hrn: *hrn }; + self.channel_manager.pay_for_offer_from_hrn(&offer, amount_msat, payment_id, params) + } else if let Some(quantity) = quantity { + self.channel_manager.pay_for_offer_with_quantity( + &offer, + Some(amount_msat), + payment_id, + params, + quantity, + ) } else { - self.channel_manager.pay_for_offer(&offer, None, payment_id, params) + self.channel_manager.pay_for_offer(&offer, Some(amount_msat), payment_id, params) }; match res { @@ -138,7 +143,7 @@ impl Bolt12Payment { log_info!( self.logger, "Initiated sending {}msat to {:?}", - offer_amount_msat, + amount_msat, payee_pubkey ); @@ -153,7 +158,7 @@ impl Bolt12Payment { let payment = PaymentDetails::new( payment_id, kind, - Some(offer_amount_msat), + Some(amount_msat), None, PaymentDirection::Outbound, PaymentStatus::Pending, @@ -163,7 +168,7 @@ impl Bolt12Payment { Ok(payment_id) }, Err(e) => { - log_error!(self.logger, "Failed to send invoice request: {:?}", e); + log_error!(self.logger, "Failed to send payment: {:?}", e); match e { Bolt12SemanticError::DuplicatePaymentId => Err(Error::DuplicatePayment), _ => { @@ -178,65 +183,84 @@ impl Bolt12Payment { let payment = PaymentDetails::new( payment_id, kind, - Some(offer_amount_msat), + Some(amount_msat), None, PaymentDirection::Outbound, PaymentStatus::Failed, ); self.payment_store.insert(payment)?; - Err(Error::InvoiceRequestCreationFailed) + Err(Error::PaymentSendingFailed) }, } }, } } - /// Send a payment given an offer and an amount in millisatoshi. - /// - /// This will fail if the amount given is less than the value required by the given offer. - /// - /// This can be used to pay a so-called "zero-amount" offers, i.e., an offer that leaves the - /// amount paid to be determined by the user. + pub(crate) fn receive_inner( + &self, amount_msat: u64, description: &str, expiry_secs: Option, quantity: Option, + ) -> Result { + let mut offer_builder = self.channel_manager.create_offer_builder().map_err(|e| { + log_error!(self.logger, "Failed to create offer builder: {:?}", e); + Error::OfferCreationFailed + })?; + + if let Some(expiry_secs) = expiry_secs { + let absolute_expiry = (SystemTime::now() + Duration::from_secs(expiry_secs as u64)) + .duration_since(UNIX_EPOCH) + .unwrap(); + offer_builder = offer_builder.absolute_expiry(absolute_expiry); + } + + let mut offer = + offer_builder.amount_msats(amount_msat).description(description.to_string()); + + if let Some(qty) = quantity { + if qty == 0 { + log_error!(self.logger, "Failed to create offer: quantity can't be zero."); + return Err(Error::InvalidQuantity); + } else { + offer = offer.supported_quantity(Quantity::Bounded(NonZeroU64::new(qty).unwrap())) + }; + }; + + let finalized_offer = offer.build().map_err(|e| { + log_error!(self.logger, "Failed to create offer: {:?}", e); + Error::OfferCreationFailed + })?; + + Ok(finalized_offer) + } + + fn blinded_paths_for_async_recipient_internal( + &self, recipient_id: Vec, + ) -> Result, Error> { + match self.async_payments_role { + Some(AsyncPaymentsRole::Server) => {}, + _ => { + return Err(Error::AsyncPaymentServicesDisabled); + }, + } + + self.channel_manager + .blinded_paths_for_async_recipient(recipient_id, None) + .or(Err(Error::InvalidBlindedPaths)) + } +} + +#[cfg_attr(feature = "uniffi", uniffi::export)] +impl Bolt12Payment { + /// Send a payment given an offer. /// /// If `payer_note` is `Some` it will be seen by the recipient and reflected back in the invoice /// response. /// + /// If `quantity` is `Some` it represents the number of items requested. + /// /// If `route_parameters` are provided they will override the default as well as the /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis. - pub fn send_using_amount( - &self, offer: &Offer, amount_msat: u64, quantity: Option, payer_note: Option, + pub fn send( + &self, offer: &Offer, quantity: Option, payer_note: Option, route_parameters: Option, - ) -> Result { - let payment_id = self.send_using_amount_inner( - offer, - amount_msat, - quantity, - payer_note, - route_parameters, - None, - )?; - Ok(payment_id) - } - - /// Internal helper to send a BOLT12 offer payment given an offer - /// and an amount in millisatoshi. - /// - /// This function contains the core payment logic and is called by - /// [`Self::send_using_amount`] and other internal logic that resolves - /// payment parameters (e.g. [`crate::UnifiedPayment::send`]). - /// - /// It wraps the core LDK `pay_for_offer` logic and handles necessary pre-checks, - /// payment ID generation, and payment details storage. - /// - /// The amount validation logic ensures the provided `amount_msat` is sufficient - /// based on the offer's required amount. - /// - /// If `hrn` is `Some`, the payment is initiated using [`ChannelManager::pay_for_offer_from_hrn`] - /// for offers resolved from a Human-Readable Name ([`HumanReadableName`]). - /// Otherwise, it falls back to the standard offer payment methods. - pub(crate) fn send_using_amount_inner( - &self, offer: &Offer, amount_msat: u64, quantity: Option, payer_note: Option, - route_parameters: Option, hrn: Option, ) -> Result { if !*self.is_running.read().unwrap() { return Err(Error::NotRunning); @@ -255,35 +279,22 @@ impl Bolt12Payment { log_error!(self.logger, "Failed to send payment as the provided offer was denominated in an unsupported currency."); return Err(Error::UnsupportedCurrency); }, - None => amount_msat, + None => { + log_error!(self.logger, "Failed to send payment due to the given offer being \"zero-amount\". Please use send_using_amount instead."); + return Err(Error::InvalidOffer); + }, }; - if amount_msat < offer_amount_msat { - log_error!( - self.logger, - "Failed to pay as the given amount needs to be at least the offer amount: required {}msat, gave {}msat.", offer_amount_msat, amount_msat); - return Err(Error::InvalidAmount); - } - let params = OptionalOfferPaymentParams { payer_note: payer_note.clone(), retry_strategy, route_params_config: route_parameters, }; - let res = if let Some(hrn) = hrn { - let hrn = maybe_deref(&hrn); - let offer = OfferFromHrn { offer: offer.clone(), hrn: *hrn }; - self.channel_manager.pay_for_offer_from_hrn(&offer, amount_msat, payment_id, params) - } else if let Some(quantity) = quantity { - self.channel_manager.pay_for_offer_with_quantity( - &offer, - Some(amount_msat), - payment_id, - params, - quantity, - ) + let res = if let Some(quantity) = quantity { + self.channel_manager + .pay_for_offer_with_quantity(&offer, None, payment_id, params, quantity) } else { - self.channel_manager.pay_for_offer(&offer, Some(amount_msat), payment_id, params) + self.channel_manager.pay_for_offer(&offer, None, payment_id, params) }; match res { @@ -292,7 +303,7 @@ impl Bolt12Payment { log_info!( self.logger, "Initiated sending {}msat to {:?}", - amount_msat, + offer_amount_msat, payee_pubkey ); @@ -307,7 +318,7 @@ impl Bolt12Payment { let payment = PaymentDetails::new( payment_id, kind, - Some(amount_msat), + Some(offer_amount_msat), None, PaymentDirection::Outbound, PaymentStatus::Pending, @@ -317,7 +328,7 @@ impl Bolt12Payment { Ok(payment_id) }, Err(e) => { - log_error!(self.logger, "Failed to send payment: {:?}", e); + log_error!(self.logger, "Failed to send invoice request: {:?}", e); match e { Bolt12SemanticError::DuplicatePaymentId => Err(Error::DuplicatePayment), _ => { @@ -332,52 +343,44 @@ impl Bolt12Payment { let payment = PaymentDetails::new( payment_id, kind, - Some(amount_msat), + Some(offer_amount_msat), None, PaymentDirection::Outbound, PaymentStatus::Failed, ); self.payment_store.insert(payment)?; - Err(Error::PaymentSendingFailed) + Err(Error::InvoiceRequestCreationFailed) }, } }, } } - pub(crate) fn receive_inner( - &self, amount_msat: u64, description: &str, expiry_secs: Option, quantity: Option, - ) -> Result { - let mut offer_builder = self.channel_manager.create_offer_builder().map_err(|e| { - log_error!(self.logger, "Failed to create offer builder: {:?}", e); - Error::OfferCreationFailed - })?; - - if let Some(expiry_secs) = expiry_secs { - let absolute_expiry = (SystemTime::now() + Duration::from_secs(expiry_secs as u64)) - .duration_since(UNIX_EPOCH) - .unwrap(); - offer_builder = offer_builder.absolute_expiry(absolute_expiry); - } - - let mut offer = - offer_builder.amount_msats(amount_msat).description(description.to_string()); - - if let Some(qty) = quantity { - if qty == 0 { - log_error!(self.logger, "Failed to create offer: quantity can't be zero."); - return Err(Error::InvalidQuantity); - } else { - offer = offer.supported_quantity(Quantity::Bounded(NonZeroU64::new(qty).unwrap())) - }; - }; - - let finalized_offer = offer.build().map_err(|e| { - log_error!(self.logger, "Failed to create offer: {:?}", e); - Error::OfferCreationFailed - })?; - - Ok(finalized_offer) + /// Send a payment given an offer and an amount in millisatoshi. + /// + /// This will fail if the amount given is less than the value required by the given offer. + /// + /// This can be used to pay a so-called "zero-amount" offers, i.e., an offer that leaves the + /// amount paid to be determined by the user. + /// + /// If `payer_note` is `Some` it will be seen by the recipient and reflected back in the invoice + /// response. + /// + /// If `route_parameters` are provided they will override the default as well as the + /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis. + pub fn send_using_amount( + &self, offer: &Offer, amount_msat: u64, quantity: Option, payer_note: Option, + route_parameters: Option, + ) -> Result { + let payment_id = self.send_using_amount_inner( + offer, + amount_msat, + quantity, + payer_note, + route_parameters, + None, + )?; + Ok(payment_id) } /// Returns a payable offer that can be used to request and receive a payment of the amount @@ -543,7 +546,10 @@ impl Bolt12Payment { .map(maybe_wrap) .or(Err(Error::OfferCreationFailed)) } +} +#[cfg(not(feature = "uniffi"))] +impl Bolt12Payment { /// Sets the [`BlindedMessagePath`]s that we will use as an async recipient to interactively build [`Offer`]s with a /// static invoice server, so the server can serve [`StaticInvoice`]s to payers on our behalf when we're offline. /// @@ -551,7 +557,6 @@ impl Bolt12Payment { /// /// [`Offer`]: lightning::offers::offer::Offer /// [`StaticInvoice`]: lightning::offers::static_invoice::StaticInvoice - #[cfg(not(feature = "uniffi"))] pub fn set_paths_to_static_invoice_server( &self, paths: Vec, ) -> Result<(), Error> { @@ -560,6 +565,23 @@ impl Bolt12Payment { .or(Err(Error::InvalidBlindedPaths)) } + /// [`BlindedMessagePath`]s for an async recipient to communicate with this node and interactively + /// build [`Offer`]s and [`StaticInvoice`]s for receiving async payments. + /// + /// **Caution**: Async payments support is considered experimental. + /// + /// [`Offer`]: lightning::offers::offer::Offer + /// [`StaticInvoice`]: lightning::offers::static_invoice::StaticInvoice + pub fn blinded_paths_for_async_recipient( + &self, recipient_id: Vec, + ) -> Result, Error> { + self.blinded_paths_for_async_recipient_internal(recipient_id) + } +} + +#[cfg(feature = "uniffi")] +#[uniffi::export] +impl Bolt12Payment { /// Sets the [`BlindedMessagePath`]s that we will use as an async recipient to interactively build [`Offer`]s with a /// static invoice server, so the server can serve [`StaticInvoice`]s to payers on our behalf when we're offline. /// @@ -567,7 +589,6 @@ impl Bolt12Payment { /// /// [`Offer`]: lightning::offers::offer::Offer /// [`StaticInvoice`]: lightning::offers::static_invoice::StaticInvoice - #[cfg(feature = "uniffi")] pub fn set_paths_to_static_invoice_server(&self, paths: Vec) -> Result<(), Error> { let decoded_paths = as Readable>::read(&mut &paths[..]) .or(Err(Error::InvalidBlindedPaths))?; @@ -584,21 +605,6 @@ impl Bolt12Payment { /// /// [`Offer`]: lightning::offers::offer::Offer /// [`StaticInvoice`]: lightning::offers::static_invoice::StaticInvoice - #[cfg(not(feature = "uniffi"))] - pub fn blinded_paths_for_async_recipient( - &self, recipient_id: Vec, - ) -> Result, Error> { - self.blinded_paths_for_async_recipient_internal(recipient_id) - } - - /// [`BlindedMessagePath`]s for an async recipient to communicate with this node and interactively - /// build [`Offer`]s and [`StaticInvoice`]s for receiving async payments. - /// - /// **Caution**: Async payments support is considered experimental. - /// - /// [`Offer`]: lightning::offers::offer::Offer - /// [`StaticInvoice`]: lightning::offers::static_invoice::StaticInvoice - #[cfg(feature = "uniffi")] pub fn blinded_paths_for_async_recipient( &self, recipient_id: Vec, ) -> Result, Error> { @@ -608,19 +614,4 @@ impl Bolt12Payment { paths.write(&mut bytes).or(Err(Error::InvalidBlindedPaths))?; Ok(bytes) } - - fn blinded_paths_for_async_recipient_internal( - &self, recipient_id: Vec, - ) -> Result, Error> { - match self.async_payments_role { - Some(AsyncPaymentsRole::Server) => {}, - _ => { - return Err(Error::AsyncPaymentServicesDisabled); - }, - } - - self.channel_manager - .blinded_paths_for_async_recipient(recipient_id, None) - .or(Err(Error::InvalidBlindedPaths)) - } } diff --git a/src/payment/onchain.rs b/src/payment/onchain.rs index 92de090ab..cc16690e2 100644 --- a/src/payment/onchain.rs +++ b/src/payment/onchain.rs @@ -41,6 +41,7 @@ macro_rules! maybe_map_fee_rate_opt { /// Should be retrieved by calling [`Node::onchain_payment`]. /// /// [`Node::onchain_payment`]: crate::Node::onchain_payment +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] pub struct OnchainPayment { wallet: Arc, channel_manager: Arc, @@ -56,7 +57,10 @@ impl OnchainPayment { ) -> Self { Self { wallet, channel_manager, config, is_running, logger } } +} +#[cfg_attr(feature = "uniffi", uniffi::export)] +impl OnchainPayment { /// Retrieve a new on-chain/funding address. pub fn new_address(&self) -> Result { let funding_address = self.wallet.get_new_address()?; diff --git a/src/payment/spontaneous.rs b/src/payment/spontaneous.rs index 536501e75..74fa84c0e 100644 --- a/src/payment/spontaneous.rs +++ b/src/payment/spontaneous.rs @@ -32,6 +32,7 @@ const LDK_DEFAULT_FINAL_CLTV_EXPIRY_DELTA: u32 = 144; /// Should be retrieved by calling [`Node::spontaneous_payment`]. /// /// [`Node::spontaneous_payment`]: crate::Node::spontaneous_payment +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] pub struct SpontaneousPayment { channel_manager: Arc, keys_manager: Arc, @@ -50,41 +51,6 @@ impl SpontaneousPayment { Self { channel_manager, keys_manager, payment_store, config, is_running, logger } } - /// Send a spontaneous aka. "keysend", payment. - /// - /// If `route_parameters` are provided they will override the default as well as the - /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis. - pub fn send( - &self, amount_msat: u64, node_id: PublicKey, - route_parameters: Option, - ) -> Result { - self.send_inner(amount_msat, node_id, route_parameters, None, None) - } - - /// Send a spontaneous payment including a list of custom TLVs. - pub fn send_with_custom_tlvs( - &self, amount_msat: u64, node_id: PublicKey, - route_parameters: Option, custom_tlvs: Vec, - ) -> Result { - self.send_inner(amount_msat, node_id, route_parameters, Some(custom_tlvs), None) - } - - /// Send a spontaneous payment with custom preimage - pub fn send_with_preimage( - &self, amount_msat: u64, node_id: PublicKey, preimage: PaymentPreimage, - route_parameters: Option, - ) -> Result { - self.send_inner(amount_msat, node_id, route_parameters, None, Some(preimage)) - } - - /// Send a spontaneous payment with custom preimage including a list of custom TLVs. - pub fn send_with_preimage_and_custom_tlvs( - &self, amount_msat: u64, node_id: PublicKey, custom_tlvs: Vec, - preimage: PaymentPreimage, route_parameters: Option, - ) -> Result { - self.send_inner(amount_msat, node_id, route_parameters, Some(custom_tlvs), Some(preimage)) - } - fn send_inner( &self, amount_msat: u64, node_id: PublicKey, route_parameters: Option, custom_tlvs: Option>, @@ -194,6 +160,44 @@ impl SpontaneousPayment { }, } } +} + +#[cfg_attr(feature = "uniffi", uniffi::export)] +impl SpontaneousPayment { + /// Send a spontaneous aka. "keysend", payment. + /// + /// If `route_parameters` are provided they will override the default as well as the + /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis. + pub fn send( + &self, amount_msat: u64, node_id: PublicKey, + route_parameters: Option, + ) -> Result { + self.send_inner(amount_msat, node_id, route_parameters, None, None) + } + + /// Send a spontaneous payment including a list of custom TLVs. + pub fn send_with_custom_tlvs( + &self, amount_msat: u64, node_id: PublicKey, + route_parameters: Option, custom_tlvs: Vec, + ) -> Result { + self.send_inner(amount_msat, node_id, route_parameters, Some(custom_tlvs), None) + } + + /// Send a spontaneous payment with custom preimage + pub fn send_with_preimage( + &self, amount_msat: u64, node_id: PublicKey, preimage: PaymentPreimage, + route_parameters: Option, + ) -> Result { + self.send_inner(amount_msat, node_id, route_parameters, None, Some(preimage)) + } + + /// Send a spontaneous payment with custom preimage including a list of custom TLVs. + pub fn send_with_preimage_and_custom_tlvs( + &self, amount_msat: u64, node_id: PublicKey, custom_tlvs: Vec, + preimage: PaymentPreimage, route_parameters: Option, + ) -> Result { + self.send_inner(amount_msat, node_id, route_parameters, Some(custom_tlvs), Some(preimage)) + } /// Sends payment probes over all paths of a route that would be used to pay the given /// amount to the given `node_id`. diff --git a/src/payment/unified.rs b/src/payment/unified.rs index 06ab8bf01..8681dbf6e 100644 --- a/src/payment/unified.rs +++ b/src/payment/unified.rs @@ -58,6 +58,7 @@ struct Extras { /// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md /// [`Node::unified_payment`]: crate::Node::unified_payment +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] pub struct UnifiedPayment { onchain_payment: Arc, bolt11_invoice: Arc, @@ -75,7 +76,10 @@ impl UnifiedPayment { ) -> Self { Self { onchain_payment, bolt11_invoice, bolt12_payment, config, logger, hrn_resolver } } +} +#[cfg_attr(feature = "uniffi", uniffi::export)] +impl UnifiedPayment { /// Generates a URI with an on-chain address, [BOLT 11] invoice and [BOLT 12] offer. /// /// The URI allows users to send the payment request allowing the wallet to decide From d00b135d7e61b223f6aae1e4f9a5d36271d336da Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 27 Feb 2026 15:45:29 +0100 Subject: [PATCH 10/10] Remove unnecessary typedefs from UDL file With the migration to UniFFI proc-macro exports, many typedefs in the UDL file are no longer needed as their types are only referenced by other proc-macro-defined types. Remove 30 non-custom typedefs that are not directly referenced by any fully UDL-defined type (interfaces, traits, remote types, or namespace functions). Note: `[Custom]` typedefs must be retained as the binding generator needs them regardless of whether the type appears in UDL signatures. Generated with the assistance of an AI coding tool. Co-Authored-By: HAL 9000 --- bindings/ldk_node.udl | 59 ------------------------------------------- 1 file changed, 59 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 1df0c7b38..c2b5117a8 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -5,22 +5,12 @@ namespace ldk_node { typedef dictionary Config; -typedef dictionary AnchorChannelsConfig; - -typedef dictionary BackgroundSyncConfig; - -typedef dictionary SyncTimeoutsConfig; - typedef dictionary EsploraSyncConfig; typedef dictionary ElectrumSyncConfig; -typedef dictionary LSPS2ServiceConfig; - typedef interface NodeEntropy; -typedef enum EntropyError; - typedef enum WordCount; [Remote] @@ -136,8 +126,6 @@ interface Node { bytes export_pathfinding_scores(); }; -typedef enum Bolt11InvoiceDescription; - typedef interface Bolt11Payment; typedef interface Bolt12Payment; @@ -254,18 +242,6 @@ enum PaymentFailureReason { "BlindedPathCreationFailed", }; -typedef enum PaymentKind; - -typedef enum UnifiedPaymentResult; - -typedef enum PaymentDirection; - -typedef enum PaymentStatus; - -typedef dictionary LSPFeeLimits; - -typedef enum ConfirmationStatus; - typedef dictionary PaymentDetails; [Remote] @@ -276,8 +252,6 @@ dictionary RouteParametersConfig { u8 max_channel_saturation_power_of_half; }; -typedef dictionary CustomTlvRecord; - [Remote] dictionary LSPS1OrderStatus { LSPS1OrderId order_id; @@ -299,10 +273,6 @@ dictionary LSPS1OrderParams { typedef dictionary LSPS1PaymentInfo; -typedef dictionary LSPS1Bolt11PaymentInfo; - -typedef dictionary LSPS1OnchainPaymentInfo; - [Remote] dictionary LSPS1ChannelInfo { LSPSDateTime funded_at; @@ -335,8 +305,6 @@ typedef dictionary ChannelDetails; typedef dictionary PeerDetails; -typedef enum LightningBalance; - [Remote] enum BalanceSource { "HolderForceClosed", @@ -345,30 +313,18 @@ enum BalanceSource { "Htlc", }; -typedef enum PendingSweepBalance; - typedef dictionary BalanceDetails; typedef dictionary ChannelConfig; -typedef enum MaxDustHTLCExposure; - typedef interface NetworkGraph; -typedef dictionary ChannelInfo; - -typedef dictionary ChannelUpdateInfo; - [Remote] dictionary RoutingFees { u32 base_msat; u32 proportional_millionths; }; -typedef dictionary NodeInfo; - -typedef dictionary NodeAnnouncementInfo; - [Remote] enum Currency { "Bitcoin", @@ -380,20 +336,6 @@ enum Currency { typedef enum AsyncPaymentsRole; -typedef dictionary RouteHintHop; - -typedef interface Bolt11Invoice; - -typedef enum OfferAmount; - -typedef interface Offer; - -typedef interface HumanReadableName; - -typedef interface Refund; - -typedef interface Bolt12Invoice; - [Custom] typedef string Txid; @@ -451,5 +393,4 @@ typedef string LSPSDateTime; [Custom] typedef string ScriptBuf; -typedef enum ClosureReason; typedef enum Event;