Skip to content

Commit ef8e586

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 a8efc7f commit ef8e586

File tree

10 files changed

+671
-14
lines changed

10 files changed

+671
-14
lines changed

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"

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 {

src/builder.rs

+27-2
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::payjoin::handler::PayjoinHandler;
1516
use crate::payment::store::PaymentStore;
1617
use crate::peer_store::PeerStore;
@@ -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,23 @@ 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> {
269+
pub fn set_payjoin_config(
270+
&mut self, payjoin_directory: String, payjoin_relay: String, ohttp_keys: Option<String>,
271+
) -> Result<&mut Self, BuildError> {
267272
let payjoin_relay =
268273
payjoin::Url::parse(&payjoin_relay).map_err(|_| BuildError::InvalidPayjoinConfig)?;
269-
self.payjoin_config = Some(PayjoinConfig { payjoin_relay });
274+
let payjoin_directory = payjoin::Url::parse(&payjoin_directory)
275+
.map_err(|_| BuildError::InvalidPayjoinConfig)?;
276+
let ohttp_keys = if let Some(ohttp_keys) = ohttp_keys {
277+
let keys = match payjoin::OhttpKeys::decode(ohttp_keys.as_bytes()) {
278+
Ok(keys) => keys,
279+
Err(_) => return Err(BuildError::InvalidPayjoinConfig),
280+
};
281+
Some(keys)
282+
} else {
283+
None
284+
};
285+
self.payjoin_config = Some(PayjoinConfig { payjoin_directory, payjoin_relay, ohttp_keys });
270286
Ok(self)
271287
}
272288

@@ -998,6 +1014,7 @@ fn build_with_store_internal(
9981014
let (event_handling_stopped_sender, _) = tokio::sync::watch::channel(());
9991015

10001016
let mut payjoin_handler = None;
1017+
let mut payjoin_receiver = None;
10011018
if let Some(pj_config) = payjoin_config {
10021019
payjoin_handler = Some(Arc::new(PayjoinHandler::new(
10031020
Arc::clone(&tx_broadcaster),
@@ -1008,6 +1025,13 @@ fn build_with_store_internal(
10081025
Arc::clone(&wallet),
10091026
Arc::clone(&payment_store),
10101027
)));
1028+
payjoin_receiver = Some(Arc::new(PayjoinReceiver::new(
1029+
Arc::clone(&logger),
1030+
Arc::clone(&wallet),
1031+
pj_config.payjoin_directory.clone(),
1032+
pj_config.payjoin_relay.clone(),
1033+
pj_config.ohttp_keys.clone(),
1034+
)));
10111035
}
10121036

10131037
let is_listening = Arc::new(AtomicBool::new(false));
@@ -1032,6 +1056,7 @@ fn build_with_store_internal(
10321056
chain_monitor,
10331057
output_sweeper,
10341058
payjoin_handler,
1059+
payjoin_receiver,
10351060
peer_manager,
10361061
connection_manager,
10371062
keys_manager,

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
}

src/lib.rs

+32-1
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;
@@ -109,6 +110,7 @@ pub use error::Error as NodeError;
109110
use error::Error;
110111

111112
pub use event::Event;
113+
use payjoin_receiver::PayjoinReceiver;
112114
use payment::payjoin::handler::PayjoinHandler;
113115
pub use types::ChannelConfig;
114116

@@ -190,6 +192,7 @@ pub struct Node {
190192
peer_manager: Arc<PeerManager>,
191193
connection_manager: Arc<ConnectionManager<Arc<FilesystemLogger>>>,
192194
payjoin_handler: Option<Arc<PayjoinHandler>>,
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_handler = self.payjoin_handler.as_ref();
1107+
let payjoin_receiver = self.payjoin_receiver.as_ref();
10801108
PayjoinPayment::new(
10811109
Arc::clone(&self.runtime),
10821110
payjoin_handler.map(Arc::clone),
1111+
payjoin_receiver.map(Arc::clone),
10831112
Arc::clone(&self.config),
10841113
Arc::clone(&self.logger),
10851114
Arc::clone(&self.wallet),
@@ -1095,9 +1124,11 @@ impl Node {
10951124
#[cfg(feature = "uniffi")]
10961125
pub fn payjoin_payment(&self) -> Arc<PayjoinPayment> {
10971126
let payjoin_handler = self.payjoin_handler.as_ref();
1127+
let payjoin_receiver = self.payjoin_receiver.as_ref();
10981128
Arc::new(PayjoinPayment::new(
10991129
Arc::clone(&self.runtime),
1100-
payjoin_handler.map(Arc::clone),
1130+
payjoin_sender.map(Arc::clone),
1131+
payjoin_receiver.map(Arc::clone),
11011132
Arc::clone(&self.config),
11021133
Arc::clone(&self.logger),
11031134
Arc::clone(&self.wallet),

0 commit comments

Comments
 (0)