Skip to content

Commit 816fa47

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 0c684a9 commit 816fa47

File tree

10 files changed

+668
-7
lines changed

10 files changed

+668
-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.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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 22 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;
@@ -95,7 +96,9 @@ struct LiquiditySourceConfig {
9596

9697
#[derive(Debug, Clone)]
9798
struct PayjoinConfig {
99+
payjoin_directory: payjoin::Url,
98100
payjoin_relay: payjoin::Url,
101+
ohttp_keys: Option<payjoin::OhttpKeys>,
99102
}
100103

101104
impl Default for LiquiditySourceConfig {
@@ -263,10 +266,18 @@ impl NodeBuilder {
263266

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

@@ -1002,13 +1013,21 @@ fn build_with_store_internal(
10021013
let (event_handling_stopped_sender, _) = tokio::sync::watch::channel(());
10031014

10041015
let mut payjoin_sender = None;
1016+
let mut payjoin_receiver = None;
10051017
if let Some(pj_config) = payjoin_config {
10061018
payjoin_sender = Some(Arc::new(PayjoinSender::new(
10071019
Arc::clone(&logger),
10081020
Arc::clone(&wallet),
10091021
Arc::clone(&tx_broadcaster),
10101022
pj_config.payjoin_relay.clone(),
10111023
)));
1024+
payjoin_receiver = Some(Arc::new(PayjoinReceiver::new(
1025+
Arc::clone(&logger),
1026+
Arc::clone(&wallet),
1027+
pj_config.payjoin_directory.clone(),
1028+
pj_config.payjoin_relay.clone(),
1029+
pj_config.ohttp_keys.clone(),
1030+
)));
10121031
}
10131032

10141033
let is_listening = Arc::new(AtomicBool::new(false));
@@ -1033,6 +1052,7 @@ fn build_with_store_internal(
10331052
chain_monitor,
10341053
output_sweeper,
10351054
payjoin_sender,
1055+
payjoin_receiver,
10361056
peer_manager,
10371057
connection_manager,
10381058
keys_manager,

src/error.rs

Lines changed: 15 additions & 0 deletions
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

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;
@@ -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
)
@@ -1094,9 +1123,11 @@ impl Node {
10941123
#[cfg(feature = "uniffi")]
10951124
pub fn payjoin_payment(&self) -> Arc<PayjoinPayment> {
10961125
let payjoin_sender = self.payjoin_sender.as_ref();
1126+
let payjoin_receiver = self.payjoin_receiver.as_ref();
10971127
Arc::new(PayjoinPayment::new(
10981128
Arc::clone(&self.runtime),
10991129
payjoin_sender.map(Arc::clone),
1130+
payjoin_receiver.map(Arc::clone),
11001131
Arc::clone(&self.config),
11011132
Arc::clone(&self.event_queue),
11021133
))

0 commit comments

Comments
 (0)