Skip to content

Commit 21d1070

Browse files
committed
Add ability to send payjoin transactions.
Implements the payjoin sender part as describe in BIP77. This would allow the on chain wallet linked to LDK node to send payjoin transactions.
1 parent b7c4862 commit 21d1070

10 files changed

+461
-4
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ tokio = { version = "1", default-features = false, features = [ "rt-multi-thread
6868
esplora-client = { version = "0.6", default-features = false }
6969
libc = "0.2"
7070
uniffi = { version = "0.26.0", features = ["build"], optional = true }
71+
payjoin = { version = "0.15.0", features = ["send", "receive", "v2"] }
7172

7273
[target.'cfg(vss)'.dependencies]
7374
vss-client = "0.2"

bindings/ldk_node.udl

+6
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ enum NodeError {
151151
"InsufficientFunds",
152152
"LiquiditySourceUnavailable",
153153
"LiquidityFeeTooHigh",
154+
"PayjoinSenderUnavailable",
155+
"PayjoinUriNetworkMismatch",
156+
"PayjoinRequestMissingAmount",
157+
"PayjoinRequestCreationFailed",
158+
"PayjoinResponseProcessingFailed",
159+
"PayjoinRequestTimeout",
154160
};
155161

156162
dictionary NodeStatus {

src/builder.rs

+39-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::peer_store::PeerStore;
1616
use crate::tx_broadcaster::TransactionBroadcaster;
1717
use crate::types::{
1818
ChainMonitor, ChannelManager, DynStore, GossipSync, KeysManager, MessageRouter, NetworkGraph,
19-
OnionMessenger, PeerManager,
19+
OnionMessenger, PayjoinSender, PeerManager,
2020
};
2121
use crate::wallet::Wallet;
2222
use crate::{LogLevel, Node};
@@ -94,6 +94,11 @@ struct LiquiditySourceConfig {
9494
lsps2_service: Option<(SocketAddress, PublicKey, Option<String>)>,
9595
}
9696

97+
#[derive(Debug, Clone)]
98+
struct PayjoinSenderConfig {
99+
payjoin_relay: String,
100+
}
101+
97102
impl Default for LiquiditySourceConfig {
98103
fn default() -> Self {
99104
Self { lsps2_service: None }
@@ -173,6 +178,7 @@ pub struct NodeBuilder {
173178
chain_data_source_config: Option<ChainDataSourceConfig>,
174179
gossip_source_config: Option<GossipSourceConfig>,
175180
liquidity_source_config: Option<LiquiditySourceConfig>,
181+
payjoin_sender_config: Option<PayjoinSenderConfig>,
176182
}
177183

178184
impl NodeBuilder {
@@ -188,12 +194,14 @@ impl NodeBuilder {
188194
let chain_data_source_config = None;
189195
let gossip_source_config = None;
190196
let liquidity_source_config = None;
197+
let payjoin_sender_config = None;
191198
Self {
192199
config,
193200
entropy_source_config,
194201
chain_data_source_config,
195202
gossip_source_config,
196203
liquidity_source_config,
204+
payjoin_sender_config,
197205
}
198206
}
199207

@@ -248,6 +256,12 @@ impl NodeBuilder {
248256
self
249257
}
250258

259+
/// Configures the [`Node`] instance to enable sending payjoin transactions.
260+
pub fn set_payjoin_sender_config(&mut self, payjoin_relay: String) -> &mut Self {
261+
self.payjoin_sender_config = Some(PayjoinSenderConfig { payjoin_relay });
262+
self
263+
}
264+
251265
/// Configures the [`Node`] instance to source its inbound liquidity from the given
252266
/// [LSPS2](https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md)
253267
/// service.
@@ -366,6 +380,7 @@ impl NodeBuilder {
366380
self.chain_data_source_config.as_ref(),
367381
self.gossip_source_config.as_ref(),
368382
self.liquidity_source_config.as_ref(),
383+
self.payjoin_sender_config.as_ref(),
369384
seed_bytes,
370385
logger,
371386
vss_store,
@@ -387,6 +402,7 @@ impl NodeBuilder {
387402
self.chain_data_source_config.as_ref(),
388403
self.gossip_source_config.as_ref(),
389404
self.liquidity_source_config.as_ref(),
405+
self.payjoin_sender_config.as_ref(),
390406
seed_bytes,
391407
logger,
392408
kv_store,
@@ -454,6 +470,11 @@ impl ArcedNodeBuilder {
454470
self.inner.write().unwrap().set_gossip_source_p2p();
455471
}
456472

473+
/// Configures the [`Node`] instance to enable sending payjoin transactions.
474+
pub fn set_payjoin_sender_config(&self, payjoin_relay: String) {
475+
self.inner.write().unwrap().set_payjoin_sender_config(payjoin_relay);
476+
}
477+
457478
/// Configures the [`Node`] instance to source its gossip data from the given RapidGossipSync
458479
/// server.
459480
pub fn set_gossip_source_rgs(&self, rgs_server_url: String) {
@@ -522,7 +543,8 @@ impl ArcedNodeBuilder {
522543
fn build_with_store_internal(
523544
config: Arc<Config>, chain_data_source_config: Option<&ChainDataSourceConfig>,
524545
gossip_source_config: Option<&GossipSourceConfig>,
525-
liquidity_source_config: Option<&LiquiditySourceConfig>, seed_bytes: [u8; 64],
546+
liquidity_source_config: Option<&LiquiditySourceConfig>,
547+
payjoin_sender_config: Option<&PayjoinSenderConfig>, seed_bytes: [u8; 64],
526548
logger: Arc<FilesystemLogger>, kv_store: Arc<DynStore>,
527549
) -> Result<Node, BuildError> {
528550
// Initialize the on-chain wallet and chain access
@@ -974,6 +996,20 @@ fn build_with_store_internal(
974996

975997
let (stop_sender, _) = tokio::sync::watch::channel(());
976998

999+
let payjoin_sender = payjoin_sender_config.as_ref().and_then(|psc| {
1000+
if let Ok(payjoin_relay) = payjoin::Url::parse(&psc.payjoin_relay) {
1001+
Some(Arc::new(PayjoinSender::new(
1002+
Arc::clone(&logger),
1003+
Arc::clone(&wallet),
1004+
Arc::clone(&tx_broadcaster),
1005+
payjoin_relay,
1006+
)))
1007+
} else {
1008+
log_info!(logger, "The provided payjoin relay URL is invalid.");
1009+
None
1010+
}
1011+
});
1012+
9771013
let is_listening = Arc::new(AtomicBool::new(false));
9781014
let latest_wallet_sync_timestamp = Arc::new(RwLock::new(None));
9791015
let latest_onchain_wallet_sync_timestamp = Arc::new(RwLock::new(None));
@@ -993,6 +1029,7 @@ fn build_with_store_internal(
9931029
channel_manager,
9941030
chain_monitor,
9951031
output_sweeper,
1032+
payjoin_sender,
9961033
peer_manager,
9971034
connection_manager,
9981035
keys_manager,

src/error.rs

+30
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,18 @@ pub enum Error {
7171
LiquiditySourceUnavailable,
7272
/// The given operation failed due to the LSP's required opening fee being too high.
7373
LiquidityFeeTooHigh,
74+
/// Failed to access payjoin sender object.
75+
PayjoinSenderUnavailable,
76+
/// Payjoin URI network mismatch.
77+
PayjoinUriNetworkMismatch,
78+
/// Amount is neither user-provided nor defined in the URI.
79+
PayjoinRequestMissingAmount,
80+
/// Failed to build a payjoin request.
81+
PayjoinRequestCreationFailed,
82+
/// Payjoin response processing failed.
83+
PayjoinResponseProcessingFailed,
84+
/// Payjoin request timed out.
85+
PayjoinRequestTimeout,
7486
}
7587

7688
impl fmt::Display for Error {
@@ -122,6 +134,24 @@ impl fmt::Display for Error {
122134
Self::LiquidityFeeTooHigh => {
123135
write!(f, "The given operation failed due to the LSP's required opening fee being too high.")
124136
},
137+
Self::PayjoinSenderUnavailable => {
138+
write!(f, "Failed to access payjoin sender object. Make sure you have enabled Payjoin sending support.")
139+
},
140+
Self::PayjoinRequestMissingAmount => {
141+
write!(f, "Amount is neither user-provided nor defined in the URI.")
142+
},
143+
Self::PayjoinRequestCreationFailed => {
144+
write!(f, "Failed construct a payjoin request. Make sure the provided URI is valid and the configured Payjoin relay is available.")
145+
},
146+
Self::PayjoinUriNetworkMismatch => {
147+
write!(f, "The Provided Payjoin URI does not match the node network.")
148+
},
149+
Self::PayjoinResponseProcessingFailed => {
150+
write!(f, "Payjoin receiver responded to our request with an invalid response that was ignored. Notice they can still broadcast the original PSBT we shared with them")
151+
},
152+
Self::PayjoinRequestTimeout => {
153+
write!(f, "Payjoin receiver did not respond to our request within the timeout period. Notice they can still broadcast the original PSBT we shared with them")
154+
},
125155
}
126156
}
127157
}

src/lib.rs

+35-2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ pub mod io;
8888
mod liquidity;
8989
mod logger;
9090
mod message_handler;
91+
mod payjoin_sender;
9192
pub mod payment;
9293
mod peer_store;
9394
mod sweep;
@@ -101,6 +102,7 @@ pub use bip39;
101102
pub use bitcoin;
102103
pub use lightning;
103104
pub use lightning_invoice;
105+
pub use payjoin::Uri;
104106

105107
pub use balance::{BalanceDetails, LightningBalance, PendingSweepBalance};
106108
pub use config::{default_config, Config};
@@ -130,11 +132,11 @@ use event::{EventHandler, EventQueue};
130132
use gossip::GossipSource;
131133
use liquidity::LiquiditySource;
132134
use payment::store::PaymentStore;
133-
use payment::{Bolt11Payment, OnchainPayment, PaymentDetails, SpontaneousPayment};
135+
use payment::{Bolt11Payment, OnchainPayment, PayjoinPayment, PaymentDetails, SpontaneousPayment};
134136
use peer_store::{PeerInfo, PeerStore};
135137
use types::{
136138
Broadcaster, ChainMonitor, ChannelManager, DynStore, FeeEstimator, KeysManager, NetworkGraph,
137-
PeerManager, Router, Scorer, Sweeper, Wallet,
139+
PayjoinSender, PeerManager, Router, Scorer, Sweeper, Wallet,
138140
};
139141
pub use types::{ChannelDetails, PeerDetails, UserChannelId};
140142

@@ -181,6 +183,7 @@ pub struct Node {
181183
output_sweeper: Arc<Sweeper>,
182184
peer_manager: Arc<PeerManager>,
183185
connection_manager: Arc<ConnectionManager<Arc<FilesystemLogger>>>,
186+
payjoin_sender: Option<Arc<PayjoinSender>>,
184187
keys_manager: Arc<KeysManager>,
185188
network_graph: Arc<NetworkGraph>,
186189
gossip_source: Arc<GossipSource>,
@@ -891,6 +894,36 @@ impl Node {
891894
))
892895
}
893896

897+
/// Returns a payment handler allowing to send and receive on-chain payments.
898+
#[cfg(not(feature = "uniffi"))]
899+
pub fn payjoin_payment(&self) -> Result<PayjoinPayment, Error> {
900+
self.payjoin_sender.as_ref().map_or_else(
901+
|| Err(Error::PayjoinSenderUnavailable),
902+
|ps| {
903+
Ok(PayjoinPayment::new(
904+
Arc::clone(&self.runtime),
905+
ps.clone(),
906+
Arc::clone(&self.config),
907+
))
908+
},
909+
)
910+
}
911+
912+
/// Returns a payment handler allowing to send and receive on-chain payments.
913+
#[cfg(feature = "uniffi")]
914+
pub fn payjoin_payment(&self) -> Result<PayjoinPayment, Error> {
915+
self.payjoin_sender.as_ref().map_or_else(
916+
|| Err(Error::PayjoinSenderUnavailable),
917+
|ps| {
918+
Ok(PayjoinPayment::new(
919+
Arc::clone(&self.runtime),
920+
ps.clone(),
921+
Arc::clone(&self.config),
922+
))
923+
},
924+
)
925+
}
926+
894927
/// Retrieve a list of known channels.
895928
pub fn list_channels(&self) -> Vec<ChannelDetails> {
896929
self.channel_manager.list_channels().into_iter().map(|c| c.into()).collect()

0 commit comments

Comments
 (0)