Skip to content

Commit 0d31424

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 b4d283f commit 0d31424

File tree

10 files changed

+694
-7
lines changed

10 files changed

+694
-7
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
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", default-features = false, features = ["send", "v2"] }
71+
payjoin = { version = "0.16.0", default-features = false, features = ["send", "receive", "v2"] }
7272

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

bindings/ldk_node.udl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ enum NodeError {
199199
"PayjoinRequestCreationFailed",
200200
"PayjoinResponseProcessingFailed",
201201
"PayjoinRequestTimeout",
202+
"PayjoinReceiverUnavailable",
203+
"PayjoinReceiverRequestValidationFailed",
204+
"PayjoinReceiverEnrollementFailed"
202205
};
203206

204207
dictionary NodeStatus {

src/builder.rs

Lines changed: 19 additions & 2 deletions
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;
@@ -1001,7 +1002,8 @@ fn build_with_store_internal(
10011002
let (stop_sender, _) = tokio::sync::watch::channel(());
10021003

10031004
let mut payjoin_sender = None;
1004-
if let Some((_payjoin_directory, payjoin_relay, _ohttp_keys)) = payjoin_config
1005+
let mut payjoin_receiver = None;
1006+
if let Some((payjoin_directory, payjoin_relay, ohttp_keys)) = payjoin_config
10051007
.as_ref()
10061008
.map(|pc| (pc.payjoin_directory.clone(), pc.payjoin_relay.clone(), pc.ohttp_keys.clone()))
10071009
{
@@ -1017,7 +1019,21 @@ fn build_with_store_internal(
10171019
Err(_) => {
10181020
return Err(BuildError::InvalidPayjoinConfig);
10191021
},
1020-
}
1022+
};
1023+
match PayjoinReceiver::new(
1024+
Arc::clone(&logger),
1025+
Arc::clone(&wallet),
1026+
&payjoin_directory,
1027+
&payjoin_relay,
1028+
ohttp_keys,
1029+
) {
1030+
Ok(payjoin_receiver_) => {
1031+
payjoin_receiver = Some(Arc::new(payjoin_receiver_));
1032+
},
1033+
Err(_) => {
1034+
return Err(BuildError::InvalidPayjoinConfig);
1035+
},
1036+
};
10211037
}
10221038

10231039
let is_listening = Arc::new(AtomicBool::new(false));
@@ -1041,6 +1057,7 @@ fn build_with_store_internal(
10411057
chain_monitor,
10421058
output_sweeper,
10431059
payjoin_sender,
1060+
payjoin_receiver,
10441061
peer_manager,
10451062
connection_manager,
10461063
keys_manager,

src/error.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ pub enum Error {
101101
PayjoinResponseProcessingFailed,
102102
/// Payjoin request timed out.
103103
PayjoinRequestTimeout,
104+
/// Failed to access payjoin receiver object.
105+
PayjoinReceiverUnavailable,
106+
/// Failed to enroll payjoin receiver.
107+
PayjoinReceiverEnrollementFailed,
108+
/// Failed to validate an incoming payjoin request.
109+
PayjoinReceiverRequestValidationFailed,
104110
}
105111

106112
impl fmt::Display for Error {
@@ -186,6 +192,15 @@ impl fmt::Display for Error {
186192
"Payjoin receiver did not respond to our request within the timeout period."
187193
)
188194
},
195+
Self::PayjoinReceiverUnavailable => {
196+
write!(f, "Failed to access payjoin receiver object. Make sure you have enabled Payjoin receiving support.")
197+
},
198+
Self::PayjoinReceiverRequestValidationFailed => {
199+
write!(f, "Failed to validate an incoming payjoin request. Payjoin sender request didnt pass the payjoin validation steps.")
200+
},
201+
Self::PayjoinReceiverEnrollementFailed => {
202+
write!(f, "Failed to enroll payjoin receiver. Make sure the configured Payjoin directory & Payjoin relay are available.")
203+
},
189204
}
190205
}
191206
}

src/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ pub mod io;
8989
mod liquidity;
9090
mod logger;
9191
mod message_handler;
92+
mod payjoin_receiver;
9293
mod payjoin_sender;
9394
pub mod payment;
9495
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;
@@ -189,6 +191,7 @@ pub struct Node {
189191
peer_manager: Arc<PeerManager>,
190192
connection_manager: Arc<ConnectionManager<Arc<FilesystemLogger>>>,
191193
payjoin_sender: Option<Arc<PayjoinSender>>,
194+
payjoin_receiver: Option<Arc<PayjoinReceiver>>,
192195
keys_manager: Arc<KeysManager>,
193196
network_graph: Arc<Graph>,
194197
gossip_source: Arc<GossipSource>,
@@ -645,6 +648,30 @@ impl Node {
645648
Arc::clone(&self.logger),
646649
));
647650

651+
// Check every 5 seconds if we have received a payjoin transaction to our enrolled
652+
// subdirectory with the configured Payjoin directory.
653+
if let Some(payjoin_receiver) = &self.payjoin_receiver {
654+
let mut stop_payjoin_server = self.stop_sender.subscribe();
655+
let payjoin_receiver = Arc::clone(&payjoin_receiver);
656+
let payjoin_check_interval = 5;
657+
runtime.spawn(async move {
658+
let mut payjoin_interval =
659+
tokio::time::interval(Duration::from_secs(payjoin_check_interval));
660+
payjoin_interval.reset();
661+
payjoin_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
662+
loop {
663+
tokio::select! {
664+
_ = stop_payjoin_server.changed() => {
665+
return;
666+
}
667+
_ = payjoin_interval.tick() => {
668+
let _ = payjoin_receiver.process_payjoin_request().await;
669+
}
670+
}
671+
}
672+
});
673+
}
674+
648675
let event_handler = Arc::new(EventHandler::new(
649676
Arc::clone(&self.event_queue),
650677
Arc::clone(&self.wallet),
@@ -960,9 +987,11 @@ impl Node {
960987
#[cfg(not(feature = "uniffi"))]
961988
pub fn payjoin_payment(&self) -> PayjoinPayment {
962989
let payjoin_sender = self.payjoin_sender.as_ref();
990+
let payjoin_receiver = self.payjoin_receiver.as_ref();
963991
PayjoinPayment::new(
964992
Arc::clone(&self.runtime),
965993
payjoin_sender.map(Arc::clone),
994+
payjoin_receiver.map(Arc::clone),
966995
Arc::clone(&self.config),
967996
Arc::clone(&self.event_queue),
968997
)
@@ -977,9 +1006,11 @@ impl Node {
9771006
#[cfg(feature = "uniffi")]
9781007
pub fn payjoin_payment(&self) -> Arc<PayjoinPayment> {
9791008
let payjoin_sender = self.payjoin_sender.as_ref();
1009+
let payjoin_receiver = self.payjoin_receiver.as_ref();
9801010
Arc::new(PayjoinPayment::new(
9811011
Arc::clone(&self.runtime),
9821012
payjoin_sender.map(Arc::clone),
1013+
payjoin_receiver.map(Arc::clone),
9831014
Arc::clone(&self.config),
9841015
Arc::clone(&self.event_queue),
9851016
))

0 commit comments

Comments
 (0)