Skip to content

Commit ec59160

Browse files
committed
Add ability to receive Payjoin tx
Implements the payjoin receiver part as describe in BIP77. This would allow the on chain wallet linked to LDK node to receive payjoin transactions. Receiving a payjoin transaction requires first to enroll with the configured Payjoin directory and listening to our enrolled subdirectory for upcoming request. When a request received, we validate it as specified in BIP78, prepare our Payjoin proposal and send it back to the payjoin sender via the subdirectory.
1 parent 8ed8af8 commit ec59160

File tree

10 files changed

+763
-7
lines changed

10 files changed

+763
-7
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +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.16.0", features = ["send", "v2"] }
71+
payjoin = { version = "0.16.0", features = ["send", "receive", "v2"] }
7272

7373
[target.'cfg(vss)'.dependencies]
7474
vss-client = "0.2"

bindings/ldk_node.udl

+3
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ enum NodeError {
180180
"PayjoinRequestCreationFailed",
181181
"PayjoinResponseProcessingFailed",
182182
"PayjoinRequestTimeout",
183+
"PayjoinReceiverUnavailable",
184+
"PayjoinReceiverRequestValidationFailed",
185+
"PayjoinReceiverEnrollementFailed"
183186
};
184187

185188
dictionary NodeStatus {

src/builder.rs

+82-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::io::sqlite_store::SqliteStore;
1111
use crate::liquidity::LiquiditySource;
1212
use crate::logger::{log_error, log_info, FilesystemLogger, Logger};
1313
use crate::message_handler::NodeCustomMessageHandler;
14+
use crate::payjoin_receiver::PayjoinReceiver;
1415
use crate::payment::store::PaymentStore;
1516
use crate::peer_store::PeerStore;
1617
use crate::tx_broadcaster::TransactionBroadcaster;
@@ -99,6 +100,13 @@ struct PayjoinSenderConfig {
99100
payjoin_relay: String,
100101
}
101102

103+
#[derive(Debug, Clone)]
104+
struct PayjoinReceiverConfig {
105+
payjoin_directory: String,
106+
payjoin_relay: String,
107+
ohttp_keys: Option<String>,
108+
}
109+
102110
impl Default for LiquiditySourceConfig {
103111
fn default() -> Self {
104112
Self { lsps2_service: None }
@@ -179,6 +187,7 @@ pub struct NodeBuilder {
179187
gossip_source_config: Option<GossipSourceConfig>,
180188
liquidity_source_config: Option<LiquiditySourceConfig>,
181189
payjoin_sender_config: Option<PayjoinSenderConfig>,
190+
payjoin_receiver_config: Option<PayjoinReceiverConfig>,
182191
}
183192

184193
impl NodeBuilder {
@@ -195,13 +204,15 @@ impl NodeBuilder {
195204
let gossip_source_config = None;
196205
let liquidity_source_config = None;
197206
let payjoin_sender_config = None;
207+
let payjoin_receiver_config = None;
198208
Self {
199209
config,
200210
entropy_source_config,
201211
chain_data_source_config,
202212
gossip_source_config,
203213
liquidity_source_config,
204214
payjoin_sender_config,
215+
payjoin_receiver_config,
205216
}
206217
}
207218

@@ -262,6 +273,15 @@ impl NodeBuilder {
262273
self
263274
}
264275

276+
/// Configures the [`Node`] instance to enable receiving payjoin transactions.
277+
pub fn set_payjoin_receiver_config(
278+
&mut self, payjoin_directory: String, payjoin_relay: String, ohttp_keys: Option<String>,
279+
) -> &mut Self {
280+
self.payjoin_receiver_config =
281+
Some(PayjoinReceiverConfig { payjoin_directory, payjoin_relay, ohttp_keys });
282+
self
283+
}
284+
265285
/// Configures the [`Node`] instance to source its inbound liquidity from the given
266286
/// [LSPS2](https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md)
267287
/// service.
@@ -381,6 +401,7 @@ impl NodeBuilder {
381401
self.gossip_source_config.as_ref(),
382402
self.liquidity_source_config.as_ref(),
383403
self.payjoin_sender_config.as_ref(),
404+
self.payjoin_receiver_config.as_ref(),
384405
seed_bytes,
385406
logger,
386407
vss_store,
@@ -403,6 +424,7 @@ impl NodeBuilder {
403424
self.gossip_source_config.as_ref(),
404425
self.liquidity_source_config.as_ref(),
405426
self.payjoin_sender_config.as_ref(),
427+
self.payjoin_receiver_config.as_ref(),
406428
seed_bytes,
407429
logger,
408430
kv_store,
@@ -475,6 +497,17 @@ impl ArcedNodeBuilder {
475497
self.inner.write().unwrap().set_payjoin_sender_config(payjoin_relay);
476498
}
477499

500+
/// Configures the [`Node`] instance to enable receiving payjoin transactions.
501+
pub fn set_payjoin_receiver_config(
502+
&mut self, payjoin_directory: String, payjoin_relay: String, ohttp_keys: Option<String>,
503+
) {
504+
self.inner.write().unwrap().set_payjoin_receiver_config(
505+
payjoin_directory,
506+
payjoin_relay,
507+
ohttp_keys,
508+
);
509+
}
510+
478511
/// Configures the [`Node`] instance to source its gossip data from the given RapidGossipSync
479512
/// server.
480513
pub fn set_gossip_source_rgs(&self, rgs_server_url: String) {
@@ -544,7 +577,8 @@ fn build_with_store_internal(
544577
config: Arc<Config>, chain_data_source_config: Option<&ChainDataSourceConfig>,
545578
gossip_source_config: Option<&GossipSourceConfig>,
546579
liquidity_source_config: Option<&LiquiditySourceConfig>,
547-
payjoin_sender_config: Option<&PayjoinSenderConfig>, seed_bytes: [u8; 64],
580+
payjoin_sender_config: Option<&PayjoinSenderConfig>,
581+
payjoin_receiver_config: Option<&PayjoinReceiverConfig>, seed_bytes: [u8; 64],
548582
logger: Arc<FilesystemLogger>, kv_store: Arc<DynStore>,
549583
) -> Result<Node, BuildError> {
550584
// Initialize the on-chain wallet and chain access
@@ -1010,6 +1044,52 @@ fn build_with_store_internal(
10101044
}
10111045
});
10121046

1047+
let payjoin_receiver = payjoin_receiver_config.as_ref().and_then(|prc| {
1048+
match (payjoin::Url::parse(&prc.payjoin_directory), payjoin::Url::parse(&prc.payjoin_relay))
1049+
{
1050+
(Ok(directory), Ok(relay)) => {
1051+
let ohttp_keys = match prc.ohttp_keys.clone() {
1052+
Some(keys) => {
1053+
let keys = match bitcoin::base64::decode(keys) {
1054+
Ok(keys) => keys,
1055+
Err(e) => {
1056+
log_info!(logger, "Failed to decode ohttp keys: the provided key is not a valid Base64 string {}", e);
1057+
return None;
1058+
},
1059+
};
1060+
match payjoin::OhttpKeys::decode(&keys) {
1061+
Ok(ohttp_keys) => Some(ohttp_keys),
1062+
Err(e) => {
1063+
log_info!(logger, "Failed to decode ohttp keys, make sure you provided a valid Ohttp Key as provided by the payjoin directory: {}", e);
1064+
return None;
1065+
},
1066+
}
1067+
},
1068+
None => None,
1069+
};
1070+
Some(Arc::new(PayjoinReceiver::new(
1071+
Arc::clone(&logger),
1072+
Arc::clone(&wallet),
1073+
directory,
1074+
relay,
1075+
ohttp_keys,
1076+
)))
1077+
},
1078+
(Ok(_), Err(e)) => {
1079+
log_info!(logger, "The provided payjoin relay url is invalid: {}", e);
1080+
None
1081+
},
1082+
(Err(e), Ok(_)) => {
1083+
log_info!(logger, "The provided payjoin directory url is invalid: {}", e);
1084+
None
1085+
},
1086+
_ => {
1087+
log_info!(logger, "The provided payjoin relay and payjoin directory urls are invalid.");
1088+
None
1089+
},
1090+
}
1091+
});
1092+
10131093
let is_listening = Arc::new(AtomicBool::new(false));
10141094
let latest_wallet_sync_timestamp = Arc::new(RwLock::new(None));
10151095
let latest_onchain_wallet_sync_timestamp = Arc::new(RwLock::new(None));
@@ -1030,6 +1110,7 @@ fn build_with_store_internal(
10301110
chain_monitor,
10311111
output_sweeper,
10321112
payjoin_sender,
1113+
payjoin_receiver,
10331114
peer_manager,
10341115
connection_manager,
10351116
keys_manager,

src/error.rs

+15
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ pub enum Error {
9797
PayjoinResponseProcessingFailed,
9898
/// Payjoin request timed out.
9999
PayjoinRequestTimeout,
100+
/// Failed to access payjoin receiver object.
101+
PayjoinReceiverUnavailable,
102+
/// Failed to enroll payjoin receiver.
103+
PayjoinReceiverEnrollementFailed,
104+
/// Failed to validate an incoming payjoin request.
105+
PayjoinReceiverRequestValidationFailed,
100106
}
101107

102108
impl fmt::Display for Error {
@@ -175,6 +181,15 @@ impl fmt::Display for Error {
175181
Self::PayjoinRequestTimeout => {
176182
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")
177183
},
184+
Self::PayjoinReceiverUnavailable => {
185+
write!(f, "Failed to access payjoin receiver object. Make sure you have enabled Payjoin receiving support.")
186+
},
187+
Self::PayjoinReceiverRequestValidationFailed => {
188+
write!(f, "Failed to validate an incoming payjoin request. Payjoin sender request didnt pass the payjoin validation steps.")
189+
},
190+
Self::PayjoinReceiverEnrollementFailed => {
191+
write!(f, "Failed to enroll payjoin receiver. Make sure the configured Payjoin directory & Payjoin relay are available.")
192+
},
178193
}
179194
}
180195
}

src/lib.rs

+45-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_receiver;
9192
mod payjoin_sender;
9293
pub mod payment;
9394
mod peer_store;
@@ -110,6 +111,7 @@ pub use error::Error as NodeError;
110111
use error::Error;
111112

112113
pub use event::Event;
114+
use payjoin_receiver::PayjoinReceiver;
113115
pub use types::ChannelConfig;
114116

115117
pub use io::utils::generate_entropy_mnemonic;
@@ -187,6 +189,7 @@ pub struct Node {
187189
peer_manager: Arc<PeerManager>,
188190
connection_manager: Arc<ConnectionManager<Arc<FilesystemLogger>>>,
189191
payjoin_sender: Option<Arc<PayjoinSender>>,
192+
payjoin_receiver: Option<Arc<PayjoinReceiver>>,
190193
keys_manager: Arc<KeysManager>,
191194
network_graph: Arc<NetworkGraph>,
192195
gossip_source: Arc<GossipSource>,
@@ -626,6 +629,30 @@ impl Node {
626629
}
627630
});
628631

632+
// Check every 5 seconds if we have received a payjoin transaction to our enrolled
633+
// subdirectory with the configured Payjoin directory.
634+
if let Some(payjoin_receiver) = &self.payjoin_receiver {
635+
let mut stop_payjoin_server = self.stop_sender.subscribe();
636+
let payjoin_receiver = Arc::clone(&payjoin_receiver);
637+
let payjoin_check_interval = 5;
638+
runtime.spawn(async move {
639+
let mut payjoin_interval =
640+
tokio::time::interval(Duration::from_secs(payjoin_check_interval));
641+
payjoin_interval.reset();
642+
payjoin_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
643+
loop {
644+
tokio::select! {
645+
_ = stop_payjoin_server.changed() => {
646+
return;
647+
}
648+
_ = payjoin_interval.tick() => {
649+
let _ = payjoin_receiver.process_payjoin_request().await;
650+
}
651+
}
652+
}
653+
});
654+
}
655+
629656
let event_handler = Arc::new(EventHandler::new(
630657
Arc::clone(&self.event_queue),
631658
Arc::clone(&self.wallet),
@@ -929,36 +956,52 @@ impl Node {
929956

930957
/// Returns a payment handler allowing to send payjoin payments.
931958
///
932-
/// In order to utilize the Payjoin functionality, it's necessary
959+
/// In order to utilize the Payjoin sending functionality, it's necessary
933960
/// to configure your node using [`set_payjoin_sender_config`] to
934961
/// set the [`PayjoinSenderConfig`].
935962
///
963+
/// In order to utilize the Payjoin receiving functionality, it's necessary
964+
/// to configure your node using [`set_payjoin_receiver_config`] to
965+
/// set the [`PayjoinReceiverConfig`].
966+
///
936967
/// [`PayjoinSenderConfig`]: [`crate::builder::PayjoinSenderConfig`]
937968
/// [`set_payjoin_sender_config`]: [`crate::builder::NodeBuilder::set_payjoin_sender_config`]
969+
/// [`PayjoinReceiverConfig`]: [`crate::builder::PayjoinReceiverConfig`]
970+
/// [`set_payjoin_receiver_config`]: [`crate::builder::NodeBuilder::set_payjoin_receiver_config`]
938971
#[cfg(not(feature = "uniffi"))]
939972
pub fn payjoin_payment(&self) -> PayjoinPayment {
940973
let payjoin_sender = self.payjoin_sender.as_ref();
974+
let payjoin_receiver = self.payjoin_receiver.as_ref();
941975
PayjoinPayment::new(
942976
Arc::clone(&self.runtime),
943977
payjoin_sender.map(Arc::clone),
978+
payjoin_receiver.map(Arc::clone),
944979
Arc::clone(&self.config),
945980
)
946981
}
947982

948983
/// Returns a payment handler allowing to send payjoin payments.
949984
///
950-
/// In order to utilize the Payjoin functionality, it's necessary
985+
/// In order to utilize the Payjoin sending functionality, it's necessary
951986
/// to configure your node using [`set_payjoin_sender_config`] to
952987
/// set the [`PayjoinSenderConfig`].
953988
///
989+
/// In order to utilize the Payjoin receiving functionality, it's necessary
990+
/// to configure your node using [`set_payjoin_receiver_config`] to
991+
/// set the [`PayjoinReceiverConfig`].
992+
///
954993
/// [`PayjoinSenderConfig`]: [`crate::builder::PayjoinSenderConfig`]
955994
/// [`set_payjoin_sender_config`]: [`crate::builder::NodeBuilder::set_payjoin_sender_config`]
995+
/// [`PayjoinReceiverConfig`]: [`crate::builder::PayjoinReceiverConfig`]
996+
/// [`set_payjoin_receiver_config`]: [`crate::builder::NodeBuilder::set_payjoin_receiver_config`]
956997
#[cfg(feature = "uniffi")]
957998
pub fn payjoin_payment(&self) -> PayjoinPayment {
958999
let payjoin_sender = self.payjoin_sender.as_ref();
1000+
let payjoin_receiver = self.payjoin_receiver.as_ref();
9591001
PayjoinPayment::new(
9601002
Arc::clone(&self.runtime),
9611003
payjoin_sender.map(Arc::clone),
1004+
payjoin_receiver.map(Arc::clone),
9621005
Arc::clone(&self.config),
9631006
)
9641007
}

0 commit comments

Comments
 (0)