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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ log = { version = "0.4.22", default-features = false, features = ["std"]}

vss-client = "0.3"
prost = { version = "0.11.6", default-features = false}
#bitcoin-payment-instructions = { version = "0.5" }
bitcoin-payment-instructions = { git = "https://github.com/chuksys/bitcoin-payment-instructions", branch = "bump-lightning" }
Copy link
Author

@chuksys chuksys Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to point to my fork of bitcoin-payment-instructions because I had to bump the LDK dependencies there to resolve dependency conflicts here in the develop branch since we bumped the LDK deps here to commit hash 50391d3a3efa7a8f32d119d126a633e4b1981ee6.

Perhaps we could have a develop branch on https://github.com/rust-bitcoin/bitcoin-payment-instructions that allows us do the same?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, good point, thanks for bringing it up. I think for this approach here is fine (at least until this PR is getting ready to land), but going forward we might want to figure out a more coherent strategy.

Moving to a develop branch here does solve some of the issues, but ofc. it's not magic bullet :(


[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winbase"] }
Expand Down
20 changes: 14 additions & 6 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ interface Node {
Bolt12Payment bolt12_payment();
SpontaneousPayment spontaneous_payment();
OnchainPayment onchain_payment();
UnifiedQrPayment unified_qr_payment();
UnifiedPayment unified_payment();
LSPS1Liquidity lsps1_liquidity();
[Throws=NodeError]
void connect(PublicKey node_id, SocketAddress address, boolean persist);
Expand Down Expand Up @@ -201,7 +201,7 @@ interface Bolt12Payment {
[Throws=NodeError]
PaymentId send([ByRef]Offer offer, u64? quantity, string? payer_note);
[Throws=NodeError]
PaymentId send_using_amount([ByRef]Offer offer, u64 amount_msat, u64? quantity, string? payer_note);
PaymentId send_using_amount([ByRef]Offer offer, u64 amount_msat, u64? quantity, string? payer_note, HumanReadableName? hrn);
[Throws=NodeError]
Offer receive(u64 amount_msat, [ByRef]string description, u32? expiry_secs, u64? quantity);
[Throws=NodeError]
Expand Down Expand Up @@ -250,11 +250,11 @@ interface FeeRate {
u64 to_sat_per_vb_ceil();
};

interface UnifiedQrPayment {
interface UnifiedPayment {
[Throws=NodeError]
string receive(u64 amount_sats, [ByRef]string message, u32 expiry_sec);
[Throws=NodeError]
QrPaymentResult send([ByRef]string uri_str);
[Throws=NodeError, Async]
UnifiedPaymentResult send([ByRef]string uri_str, u64? amount_msat);
};

interface LSPS1Liquidity {
Expand Down Expand Up @@ -320,6 +320,7 @@ enum NodeError {
"LiquidityFeeTooHigh",
"InvalidBlindedPaths",
"AsyncPaymentServicesDisabled",
"HrnParsingFailed",
};

dictionary NodeStatus {
Expand Down Expand Up @@ -428,7 +429,7 @@ interface PaymentKind {
};

[Enum]
interface QrPaymentResult {
interface UnifiedPaymentResult {
Onchain(Txid txid);
Bolt11(PaymentId payment_id);
Bolt12(PaymentId payment_id);
Expand Down Expand Up @@ -775,6 +776,13 @@ interface Offer {
PublicKey? issuer_signing_pubkey();
};

interface HumanReadableName {
[Throws=NodeError, Name=from_encoded]
constructor([ByRef] string encoded);
string user();
string domain();
};

[Traits=(Debug, Display, Eq)]
interface Refund {
[Throws=NodeError, Name=from_str]
Expand Down
13 changes: 12 additions & 1 deletion src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ use std::sync::{Arc, Mutex, Once, RwLock};
use std::time::SystemTime;
use vss_client::headers::{FixedHeaders, LnurlAuthToJwtProvider, VssHeaderProvider};

use bitcoin_payment_instructions::onion_message_resolver::LDKOnionMessageDNSSECHrnResolver;

const VSS_HARDENED_CHILD_INDEX: u32 = 877;
const VSS_LNURL_AUTH_HARDENED_CHILD_INDEX: u32 = 138;
const LSPS_HARDENED_CHILD_INDEX: u32 = 577;
Expand Down Expand Up @@ -1451,6 +1453,8 @@ fn build_with_store_internal(
})?;
}

let hrn_resolver = Arc::new(LDKOnionMessageDNSSECHrnResolver::new(Arc::clone(&network_graph)));

// Initialize the PeerManager
let onion_messenger: Arc<OnionMessenger> = Arc::new(OnionMessenger::new(
Arc::clone(&keys_manager),
Expand All @@ -1460,7 +1464,7 @@ fn build_with_store_internal(
message_router,
Arc::clone(&channel_manager),
Arc::clone(&channel_manager),
IgnoringMessageHandler {},
Arc::clone(&hrn_resolver),
IgnoringMessageHandler {},
));
let ephemeral_bytes: [u8; 32] = keys_manager.get_secure_random_bytes();
Expand Down Expand Up @@ -1588,6 +1592,12 @@ fn build_with_store_internal(
Arc::clone(&keys_manager),
));

let peer_manager_clone = Arc::clone(&peer_manager);

hrn_resolver.register_post_queue_action(Box::new(move || {
peer_manager_clone.process_events();
}));

liquidity_source.as_ref().map(|l| l.set_peer_manager(Arc::clone(&peer_manager)));

gossip_source.set_gossip_verifier(
Expand Down Expand Up @@ -1681,6 +1691,7 @@ fn build_with_store_internal(
is_running,
is_listening,
node_metrics,
hrn_resolver,
})
}

Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ pub enum Error {
InvalidBlindedPaths,
/// Asynchronous payment services are disabled.
AsyncPaymentServicesDisabled,
/// Parsing a Human-Readable Name has failed.
HrnParsingFailed,
}

impl fmt::Display for Error {
Expand Down Expand Up @@ -201,6 +203,9 @@ impl fmt::Display for Error {
Self::AsyncPaymentServicesDisabled => {
write!(f, "Asynchronous payment services are disabled.")
},
Self::HrnParsingFailed => {
write!(f, "Failed to parse a human-readable name.")
},
}
}
}
Expand Down
55 changes: 54 additions & 1 deletion src/ffi/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ pub use crate::logger::{LogLevel, LogRecord, LogWriter};
pub use crate::payment::store::{
ConfirmationStatus, LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus,
};
pub use crate::payment::QrPaymentResult;
pub use crate::payment::UnifiedPaymentResult;

pub use lightning::chain::channelmonitor::BalanceSource;
pub use lightning::events::{ClosureReason, PaymentFailureReason};
pub use lightning::ln::types::ChannelId;
pub use lightning::offers::offer::OfferId;
pub use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
pub use lightning::routing::gossip::{NodeAlias, NodeId, RoutingFees};
pub use lightning::routing::router::RouteParametersConfig;
pub use lightning_types::string::UntrustedString;
Expand Down Expand Up @@ -278,6 +279,58 @@ impl std::fmt::Display for Offer {
}
}

pub struct HumanReadableName {
pub(crate) inner: LdkHumanReadableName,
}

impl HumanReadableName {
pub fn into_inner(&self) -> LdkHumanReadableName {
self.inner.clone()
}

pub fn from_encoded(encoded: &str) -> Result<Self, Error> {
let hrn = match LdkHumanReadableName::from_encoded(encoded) {
Ok(hrn) => Ok(hrn),
Err(_) => Err(Error::HrnParsingFailed),
}?;

Ok(Self { inner: hrn })
}

pub fn user(&self) -> String {
self.inner.user().to_string()
}

pub fn domain(&self) -> String {
self.inner.domain().to_string()
}
}

impl From<LdkHumanReadableName> for HumanReadableName {
fn from(ldk_hrn: LdkHumanReadableName) -> Self {
HumanReadableName { inner: ldk_hrn }
}
}

impl From<HumanReadableName> for LdkHumanReadableName {
fn from(wrapper: HumanReadableName) -> Self {
wrapper.into_inner()
}
}

impl Deref for HumanReadableName {
type Target = LdkHumanReadableName;
fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl AsRef<LdkHumanReadableName> for HumanReadableName {
fn as_ref(&self) -> &LdkHumanReadableName {
self.deref()
}
}

/// A `Refund` is a request to send an [`Bolt12Invoice`] without a preceding [`Offer`].
///
/// Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to
Expand Down
22 changes: 16 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,14 @@ use liquidity::{LSPS1Liquidity, LiquiditySource};
use payment::asynchronous::static_invoice_store::StaticInvoiceStore;
use payment::{
Bolt11Payment, Bolt12Payment, OnchainPayment, PaymentDetails, SpontaneousPayment,
UnifiedQrPayment,
UnifiedPayment,
};
use peer_store::{PeerInfo, PeerStore};
use runtime::Runtime;
use types::{
Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore, Graph,
KeysManager, OnionMessenger, PaymentStore, PeerManager, Router, Scorer, Sweeper, Wallet,
HRNResolver, KeysManager, OnionMessenger, PaymentStore, PeerManager, Router, Scorer, Sweeper,
Wallet,
};
pub use types::{ChannelDetails, CustomTlvRecord, PeerDetails, UserChannelId};

Expand Down Expand Up @@ -205,6 +206,7 @@ pub struct Node {
is_running: Arc<RwLock<bool>>,
is_listening: Arc<AtomicBool>,
node_metrics: Arc<RwLock<NodeMetrics>>,
hrn_resolver: Arc<HRNResolver>,
}

impl Node {
Expand Down Expand Up @@ -899,34 +901,42 @@ impl Node {
/// Returns a payment handler allowing to create [BIP 21] URIs with an on-chain, [BOLT 11],
/// and [BOLT 12] payment options.
///
/// This handler allows you to send payments to these URIs as well as [BIP 353] HRNs.
///
/// [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
/// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
/// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
#[cfg(not(feature = "uniffi"))]
pub fn unified_qr_payment(&self) -> UnifiedQrPayment {
UnifiedQrPayment::new(
pub fn unified_payment(&self) -> UnifiedPayment {
UnifiedPayment::new(
self.onchain_payment().into(),
self.bolt11_payment().into(),
self.bolt12_payment().into(),
Arc::clone(&self.config),
Arc::clone(&self.logger),
Arc::clone(&self.hrn_resolver),
)
}

/// Returns a payment handler allowing to create [BIP 21] URIs with an on-chain, [BOLT 11],
/// and [BOLT 12] payment options.
///
/// This handler allows you to send payments to these URIs as well as [BIP 353] HRNs.
///
/// [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
/// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
/// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
#[cfg(feature = "uniffi")]
pub fn unified_qr_payment(&self) -> Arc<UnifiedQrPayment> {
Arc::new(UnifiedQrPayment::new(
pub fn unified_payment(&self) -> Arc<UnifiedPayment> {
Arc::new(UnifiedPayment::new(
self.onchain_payment(),
self.bolt11_payment(),
self.bolt12_payment(),
Arc::clone(&self.config),
Arc::clone(&self.logger),
Arc::clone(&self.hrn_resolver),
))
}

Expand Down
14 changes: 12 additions & 2 deletions src/payment/bolt12.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::types::{ChannelManager, PaymentStore};

use lightning::blinded_path::message::BlindedMessagePath;
use lightning::ln::channelmanager::{OptionalOfferPaymentParams, PaymentId, Retry};
use lightning::offers::offer::{Amount, Offer as LdkOffer, Quantity};
use lightning::offers::offer::{Amount, Offer as LdkOffer, OfferFromHrn, Quantity};
use lightning::offers::parse::Bolt12SemanticError;
use lightning::routing::router::RouteParametersConfig;

Expand Down Expand Up @@ -47,6 +47,11 @@ type Refund = lightning::offers::refund::Refund;
#[cfg(feature = "uniffi")]
type Refund = Arc<crate::ffi::Refund>;

#[cfg(not(feature = "uniffi"))]
type HumanReadableName = lightning::onion_message::dns_resolution::HumanReadableName;
#[cfg(feature = "uniffi")]
type HumanReadableName = Arc<crate::ffi::HumanReadableName>;

/// A payment handler allowing to create and pay [BOLT 12] offers and refunds.
///
/// Should be retrieved by calling [`Node::bolt12_payment`].
Expand Down Expand Up @@ -184,6 +189,7 @@ impl Bolt12Payment {
/// response.
pub fn send_using_amount(
&self, offer: &Offer, amount_msat: u64, quantity: Option<u64>, payer_note: Option<String>,
hrn: Option<HumanReadableName>,
) -> Result<PaymentId, Error> {
if !*self.is_running.read().unwrap() {
return Err(Error::NotRunning);
Expand Down Expand Up @@ -218,7 +224,11 @@ impl Bolt12Payment {
retry_strategy,
route_params_config,
};
let res = if let Some(quantity) = 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),
Expand Down
4 changes: 2 additions & 2 deletions src/payment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ mod bolt12;
mod onchain;
mod spontaneous;
pub(crate) mod store;
mod unified_qr;
mod unified;

pub use bolt11::Bolt11Payment;
pub use bolt12::Bolt12Payment;
Expand All @@ -22,4 +22,4 @@ pub use spontaneous::SpontaneousPayment;
pub use store::{
ConfirmationStatus, LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus,
};
pub use unified_qr::{QrPaymentResult, UnifiedQrPayment};
pub use unified::{UnifiedPayment, UnifiedPaymentResult};
Loading
Loading