Skip to content

Commit dd890bc

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 60fd9f4 commit dd890bc

File tree

10 files changed

+670
-9
lines changed

10 files changed

+670
-9
lines changed

Diff for: Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ tokio = { version = "1.37", default-features = false, features = [ "rt-multi-thr
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"

Diff for: bindings/ldk_node.udl

+3
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ enum NodeError {
209209
"PayjoinRequestMissingAmount",
210210
"PayjoinRequestCreationFailed",
211211
"PayjoinResponseProcessingFailed",
212+
"PayjoinReceiverUnavailable",
213+
"PayjoinReceiverRequestValidationFailed",
214+
"PayjoinReceiverEnrollementFailed"
212215
};
213216

214217
dictionary NodeStatus {

Diff for: src/builder.rs

+25-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::liquidity::LiquiditySource;
1212
use crate::logger::{log_error, log_info, FilesystemLogger, Logger};
1313
use crate::message_handler::NodeCustomMessageHandler;
1414
use crate::payment::payjoin::send::PayjoinSender;
15+
use crate::payjoin_receiver::PayjoinReceiver;
1516
use crate::payment::store::PaymentStore;
1617
use crate::peer_store::PeerStore;
1718
use crate::tx_broadcaster::TransactionBroadcaster;
@@ -96,7 +97,9 @@ struct LiquiditySourceConfig {
9697

9798
#[derive(Debug, Clone)]
9899
struct PayjoinConfig {
100+
payjoin_directory: payjoin::Url,
99101
payjoin_relay: payjoin::Url,
102+
ohttp_keys: Option<payjoin::OhttpKeys>,
100103
}
101104

102105
impl Default for LiquiditySourceConfig {
@@ -263,10 +266,19 @@ impl NodeBuilder {
263266
}
264267

265268
/// Configures the [`Node`] instance to enable payjoin transactions.
266-
pub fn set_payjoin_config(&mut self, payjoin_relay: String) -> Result<&mut Self, BuildError> {
267-
let payjoin_relay =
268-
payjoin::Url::parse(&payjoin_relay).map_err(|_| BuildError::InvalidPayjoinConfig)?;
269-
self.payjoin_config = Some(PayjoinConfig { payjoin_relay });
269+
pub fn set_payjoin_config(
270+
&mut self, payjoin_directory: String, payjoin_relay: String, ohttp_keys: Option<String>,
271+
) -> Result<&mut Self, BuildError> {
272+
let payjoin_relay = payjoin::Url::parse(&payjoin_relay).map_err(|_| BuildError::InvalidPayjoinConfig)?;
273+
let payjoin_directory = payjoin::Url::parse(&payjoin_directory).map_err(|_| BuildError::InvalidPayjoinConfig)?;
274+
let ohttp_keys = if let Some(ohttp_keys) = ohttp_keys {
275+
let keys = match payjoin::OhttpKeys::decode(ohttp_keys.as_bytes()) {
276+
Ok(keys) => keys,
277+
Err(_) => return Err(BuildError::InvalidPayjoinConfig),
278+
};
279+
Some(keys)
280+
} else { None };
281+
self.payjoin_config = Some(PayjoinConfig { payjoin_directory, payjoin_relay, ohttp_keys });
270282
Ok(self)
271283
}
272284

@@ -998,11 +1010,19 @@ fn build_with_store_internal(
9981010
let (event_handling_stopped_sender, _) = tokio::sync::watch::channel(());
9991011

10001012
let mut payjoin_sender = None;
1013+
let mut payjoin_receiver = None;
10011014
if let Some(pj_config) = payjoin_config {
10021015
payjoin_sender = Some(Arc::new(PayjoinSender::new(
10031016
Arc::clone(&logger),
10041017
pj_config.payjoin_relay.clone(),
10051018
)));
1019+
payjoin_receiver = Some(Arc::new(PayjoinReceiver::new(
1020+
Arc::clone(&logger),
1021+
Arc::clone(&wallet),
1022+
pj_config.payjoin_directory.clone(),
1023+
pj_config.payjoin_relay.clone(),
1024+
pj_config.ohttp_keys.clone(),
1025+
)));
10061026
}
10071027

10081028
let is_listening = Arc::new(AtomicBool::new(false));
@@ -1027,6 +1047,7 @@ fn build_with_store_internal(
10271047
chain_monitor,
10281048
output_sweeper,
10291049
payjoin_sender,
1050+
payjoin_receiver,
10301051
peer_manager,
10311052
connection_manager,
10321053
keys_manager,

Diff for: src/error.rs

+15
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ pub enum Error {
105105
PayjoinRequestCreationFailed,
106106
/// Payjoin response processing failed.
107107
PayjoinResponseProcessingFailed,
108+
/// Failed to access payjoin receiver object.
109+
PayjoinReceiverUnavailable,
110+
/// Failed to enroll payjoin receiver.
111+
PayjoinReceiverEnrollementFailed,
112+
/// Failed to validate an incoming payjoin request.
113+
PayjoinReceiverRequestValidationFailed,
108114
}
109115

110116
impl fmt::Display for Error {
@@ -187,6 +193,15 @@ impl fmt::Display for Error {
187193
Self::PayjoinResponseProcessingFailed => {
188194
write!(f, "Payjoin receiver responded to our request with an invalid response that was ignored")
189195
},
196+
Self::PayjoinReceiverUnavailable => {
197+
write!(f, "Failed to access payjoin receiver object. Make sure you have enabled Payjoin receiving support.")
198+
},
199+
Self::PayjoinReceiverRequestValidationFailed => {
200+
write!(f, "Failed to validate an incoming payjoin request. Payjoin sender request didnt pass the payjoin validation steps.")
201+
},
202+
Self::PayjoinReceiverEnrollementFailed => {
203+
write!(f, "Failed to enroll payjoin receiver. Make sure the configured Payjoin directory & Payjoin relay are available.")
204+
},
190205
}
191206
}
192207
}

Diff for: src/lib.rs

+31
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
pub mod payment;
9394
mod peer_store;
9495
mod sweep;
@@ -110,6 +111,7 @@ use error::Error;
110111

111112
pub use event::Event;
112113
use payment::payjoin::send::PayjoinSender;
114+
use payjoin_receiver::PayjoinReceiver;
113115
pub use types::ChannelConfig;
114116

115117
pub use io::utils::generate_entropy_mnemonic;
@@ -190,6 +192,7 @@ pub struct Node {
190192
peer_manager: Arc<PeerManager>,
191193
connection_manager: Arc<ConnectionManager<Arc<FilesystemLogger>>>,
192194
payjoin_sender: Option<Arc<PayjoinSender>>,
195+
payjoin_receiver: Option<Arc<PayjoinReceiver>>,
193196
keys_manager: Arc<KeysManager>,
194197
network_graph: Arc<Graph>,
195198
gossip_source: Arc<GossipSource>,
@@ -690,6 +693,30 @@ impl Node {
690693
Arc::clone(&self.logger),
691694
));
692695

696+
// Check every 5 seconds if we have received a payjoin transaction to our enrolled
697+
// subdirectory with the configured Payjoin directory.
698+
if let Some(payjoin_receiver) = &self.payjoin_receiver {
699+
let mut stop_payjoin_server = self.stop_sender.subscribe();
700+
let payjoin_receiver = Arc::clone(&payjoin_receiver);
701+
let payjoin_check_interval = 5;
702+
runtime.spawn(async move {
703+
let mut payjoin_interval =
704+
tokio::time::interval(Duration::from_secs(payjoin_check_interval));
705+
payjoin_interval.reset();
706+
payjoin_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
707+
loop {
708+
tokio::select! {
709+
_ = stop_payjoin_server.changed() => {
710+
return;
711+
}
712+
_ = payjoin_interval.tick() => {
713+
let _ = payjoin_receiver.process_payjoin_request().await;
714+
}
715+
}
716+
}
717+
});
718+
}
719+
693720
let event_handler = Arc::new(EventHandler::new(
694721
Arc::clone(&self.event_queue),
695722
Arc::clone(&self.wallet),
@@ -1077,9 +1104,11 @@ impl Node {
10771104
#[cfg(not(feature = "uniffi"))]
10781105
pub fn payjoin_payment(&self) -> PayjoinPayment {
10791106
let payjoin_sender = self.payjoin_sender.as_ref();
1107+
let payjoin_receiver = self.payjoin_receiver.as_ref();
10801108
PayjoinPayment::new(
10811109
Arc::clone(&self.runtime),
10821110
payjoin_sender.map(Arc::clone),
1111+
payjoin_receiver.map(Arc::clone),
10831112
Arc::clone(&self.config),
10841113
Arc::clone(&self.event_queue),
10851114
Arc::clone(&self.logger),
@@ -1097,9 +1126,11 @@ impl Node {
10971126
#[cfg(feature = "uniffi")]
10981127
pub fn payjoin_payment(&self) -> Arc<PayjoinPayment> {
10991128
let payjoin_sender = self.payjoin_sender.as_ref();
1129+
let payjoin_receiver = self.payjoin_receiver.as_ref();
11001130
Arc::new(PayjoinPayment::new(
11011131
Arc::clone(&self.runtime),
11021132
payjoin_sender.map(Arc::clone),
1133+
payjoin_receiver.map(Arc::clone),
11031134
Arc::clone(&self.config),
11041135
Arc::clone(&self.event_queue),
11051136
Arc::clone(&self.logger),

0 commit comments

Comments
 (0)