Skip to content

Commit

Permalink
Add payjoin sender
Browse files Browse the repository at this point in the history
  • Loading branch information
jbesraa committed May 21, 2024
1 parent b7c4862 commit b444fab
Show file tree
Hide file tree
Showing 7 changed files with 442 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ tokio = { version = "1", default-features = false, features = [ "rt-multi-thread
esplora-client = { version = "0.6", default-features = false }
libc = "0.2"
uniffi = { version = "0.26.0", features = ["build"], optional = true }
payjoin = { version = "0.15.0", features = ["send", "receive", "v2"] }

[target.'cfg(vss)'.dependencies]
vss-client = "0.2"
Expand Down
2 changes: 2 additions & 0 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ enum NodeError {
"InsufficientFunds",
"LiquiditySourceUnavailable",
"LiquidityFeeTooHigh",
"PayjoinSender",
"AmountMissing"
};

dictionary NodeStatus {
Expand Down
34 changes: 34 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::io::sqlite_store::SqliteStore;
use crate::liquidity::LiquiditySource;
use crate::logger::{log_error, log_info, FilesystemLogger, Logger};
use crate::message_handler::NodeCustomMessageHandler;
use crate::payjoin_sender::PayjoinSender;
use crate::payment::store::PaymentStore;
use crate::peer_store::PeerStore;
use crate::tx_broadcaster::TransactionBroadcaster;
Expand Down Expand Up @@ -94,6 +95,11 @@ struct LiquiditySourceConfig {
lsps2_service: Option<(SocketAddress, PublicKey, Option<String>)>,
}

#[derive(Debug, Clone)]
struct PayjoinSenderConfig {
payjoin_relay: payjoin::Url,
}

impl Default for LiquiditySourceConfig {
fn default() -> Self {
Self { lsps2_service: None }
Expand Down Expand Up @@ -173,6 +179,7 @@ pub struct NodeBuilder {
chain_data_source_config: Option<ChainDataSourceConfig>,
gossip_source_config: Option<GossipSourceConfig>,
liquidity_source_config: Option<LiquiditySourceConfig>,
payjoin_sender_config: Option<PayjoinSenderConfig>,
}

impl NodeBuilder {
Expand All @@ -188,12 +195,14 @@ impl NodeBuilder {
let chain_data_source_config = None;
let gossip_source_config = None;
let liquidity_source_config = None;
let payjoin_sender_config = None;
Self {
config,
entropy_source_config,
chain_data_source_config,
gossip_source_config,
liquidity_source_config,
payjoin_sender_config,
}
}

Expand Down Expand Up @@ -248,6 +257,12 @@ impl NodeBuilder {
self
}

/// Configures the [`Node`] instance to enable sending payjoin transactions.
pub fn set_payjoin_sender_config(&mut self, payjoin_relay: payjoin::Url) -> &mut Self {
self.payjoin_sender_config = Some(PayjoinSenderConfig { payjoin_relay });
self
}

/// Configures the [`Node`] instance to source its inbound liquidity from the given
/// [LSPS2](https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md)
/// service.
Expand Down Expand Up @@ -369,6 +384,7 @@ impl NodeBuilder {
seed_bytes,
logger,
vss_store,
self.payjoin_sender_config.as_ref(),
)
}

Expand All @@ -390,6 +406,7 @@ impl NodeBuilder {
seed_bytes,
logger,
kv_store,
self.payjoin_sender_config.as_ref(),
)
}
}
Expand Down Expand Up @@ -454,6 +471,11 @@ impl ArcedNodeBuilder {
self.inner.write().unwrap().set_gossip_source_p2p();
}

/// Configures the [`Node`] instance to enable sending payjoin transactions.
pub fn set_payjoin_sender_config(&self, payjoin_relay: payjoin::Url) {
self.inner.write().unwrap().set_payjoin_sender_config(payjoin_relay);
}

/// Configures the [`Node`] instance to source its gossip data from the given RapidGossipSync
/// server.
pub fn set_gossip_source_rgs(&self, rgs_server_url: String) {
Expand Down Expand Up @@ -524,6 +546,7 @@ fn build_with_store_internal(
gossip_source_config: Option<&GossipSourceConfig>,
liquidity_source_config: Option<&LiquiditySourceConfig>, seed_bytes: [u8; 64],
logger: Arc<FilesystemLogger>, kv_store: Arc<DynStore>,
payjoin_sender_config: Option<&PayjoinSenderConfig>,
) -> Result<Node, BuildError> {
// Initialize the on-chain wallet and chain access
let xprv = bitcoin::bip32::ExtendedPrivKey::new_master(config.network.into(), &seed_bytes)
Expand Down Expand Up @@ -973,6 +996,16 @@ fn build_with_store_internal(
};

let (stop_sender, _) = tokio::sync::watch::channel(());
let payjoin_sender = if let Some(payjoin_sender_config) = payjoin_sender_config {
let payjoin_sender = PayjoinSender::new(
Arc::clone(&logger),
Arc::clone(&wallet),
&payjoin_sender_config.payjoin_relay,
);
Some(Arc::new(payjoin_sender))
} else {
None
};

let is_listening = Arc::new(AtomicBool::new(false));
let latest_wallet_sync_timestamp = Arc::new(RwLock::new(None));
Expand All @@ -993,6 +1026,7 @@ fn build_with_store_internal(
channel_manager,
chain_monitor,
output_sweeper,
payjoin_sender,
peer_manager,
connection_manager,
keys_manager,
Expand Down
8 changes: 8 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ pub enum Error {
LiquiditySourceUnavailable,
/// The given operation failed due to the LSP's required opening fee being too high.
LiquidityFeeTooHigh,
/// Amount is not prvoided and neither defined in the URI.
AmountMissing,
/// Payjoin errors
PayjoinSender,
}

impl fmt::Display for Error {
Expand Down Expand Up @@ -122,6 +126,10 @@ impl fmt::Display for Error {
Self::LiquidityFeeTooHigh => {
write!(f, "The given operation failed due to the LSP's required opening fee being too high.")
},
Self::PayjoinSender => write!(f, "Failed to send payjoin."),
Self::AmountMissing => {
write!(f, "Amount is not provided and neither defined in the URI.")
},
}
}
}
Expand Down
47 changes: 47 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ pub mod io;
mod liquidity;
mod logger;
mod message_handler;
mod payjoin_sender;
pub mod payment;
mod peer_store;
mod sweep;
Expand All @@ -99,6 +100,7 @@ mod wallet;

pub use bip39;
pub use bitcoin;
use bitcoin::address::NetworkChecked;
pub use lightning;
pub use lightning_invoice;

Expand All @@ -108,6 +110,7 @@ pub use error::Error as NodeError;
use error::Error;

pub use event::Event;
use payjoin_sender::PayjoinSender;
pub use types::ChannelConfig;

pub use io::utils::generate_entropy_mnemonic;
Expand Down Expand Up @@ -181,6 +184,7 @@ pub struct Node {
output_sweeper: Arc<Sweeper>,
peer_manager: Arc<PeerManager>,
connection_manager: Arc<ConnectionManager<Arc<FilesystemLogger>>>,
payjoin_sender: Option<Arc<PayjoinSender<Arc<FilesystemLogger>>>>,
keys_manager: Arc<KeysManager>,
network_graph: Arc<NetworkGraph>,
gossip_source: Arc<GossipSource>,
Expand Down Expand Up @@ -491,6 +495,28 @@ impl Node {
});
}

if let Some(payjoin_sender) = &self.payjoin_sender {
let mut stop_payjoin_server = self.stop_sender.subscribe();
let payjoin_sender = Arc::clone(&payjoin_sender);
let payjoin_check_interval = 2;
runtime.spawn(async move {
let mut payjoin_interval =
tokio::time::interval(Duration::from_secs(payjoin_check_interval));
payjoin_interval.reset();
payjoin_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
loop {
tokio::select! {
_ = stop_payjoin_server.changed() => {
return;
}
_ = payjoin_interval.tick() => {
let _ = payjoin_sender.process_payjoin_response().await;
}
}
}
});
}

// Regularly reconnect to persisted peers.
let connect_cm = Arc::clone(&self.connection_manager);
let connect_pm = Arc::clone(&self.peer_manager);
Expand Down Expand Up @@ -697,6 +723,27 @@ impl Node {
Ok(())
}

/// Send a payjoin transaction from the node on chain funds to the address as specified in the
/// payjoin URI.
pub async fn send_payjoin_transaction(
&self, payjoin_uri: payjoin::Uri<'static, NetworkChecked>, amount: Option<bitcoin::Amount>,
) -> Result<Option<bitcoin::Txid>, Error> {
let rt_lock = self.runtime.read().unwrap();
if rt_lock.is_none() {
return Err(Error::NotRunning);
}
let payjoin_sender = self.payjoin_sender.as_ref().ok_or(Error::PayjoinSender)?;
let psbt = match payjoin_sender.create_payjoin_request(payjoin_uri.clone(), amount) {
Ok(psbt) => psbt,
Err(e) => {
dbg!("Failed to create payjoin request: {}", e);
log_error!(self.logger, "Failed to create payjoin request: {}", e);
return Err(Error::PayjoinSender);
},
};
payjoin_sender.send_payjoin_request(payjoin_uri, psbt).await
}

/// Disconnects all peers, stops all running background tasks, and shuts down [`Node`].
///
/// After this returns most API methods will return [`Error::NotRunning`].
Expand Down
Loading

0 comments on commit b444fab

Please sign in to comment.