Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow node wallet to receive payjoin transactions #301

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 25 additions & 19 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,23 @@ panic = 'abort' # Abort on panic
default = []

[dependencies]
lightning = { version = "0.0.123", features = ["std"] }
lightning-invoice = { version = "0.31.0" }
lightning-net-tokio = { version = "0.0.123" }
lightning-persister = { version = "0.0.123" }
lightning-background-processor = { version = "0.0.123", features = ["futures"] }
lightning-rapid-gossip-sync = { version = "0.0.123" }
lightning-transaction-sync = { version = "0.0.123", features = ["esplora-async-https", "time"] }
lightning-liquidity = { version = "0.1.0-alpha.4", features = ["std"] }

#lightning = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["std"] }
#lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" }
#lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" }
#lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" }
#lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["futures"] }
#lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" }
#lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["esplora-async"] }
#lightning-liquidity = { git = "https://github.com/lightningdevkit/lightning-liquidity", branch="main", features = ["std"] }
# lightning = { version = "0.0.123", features = ["std"] }
# lightning-invoice = { version = "0.31.0" }
# lightning-net-tokio = { version = "0.0.123" }
# lightning-persister = { version = "0.0.123" }
# lightning-background-processor = { version = "0.0.123", features = ["futures"] }
# lightning-rapid-gossip-sync = { version = "0.0.123" }
# lightning-transaction-sync = { version = "0.0.123", features = ["esplora-async-https", "time"] }
# lightning-liquidity = { version = "0.1.0-alpha.4", features = ["std"] }

lightning = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event", features = ["std"] }
lightning-invoice = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event" }
lightning-net-tokio = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event" }
lightning-persister = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event" }
lightning-background-processor = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event", features = ["futures"] }
lightning-rapid-gossip-sync = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event" }
lightning-transaction-sync = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event", features = ["esplora-async"] }
lightning-liquidity = { git = "https://github.com/jbesraa/lightning-liquidity", branch="pj-fixes", features = ["std"] }

#lightning = { path = "../rust-lightning/lightning", features = ["std"] }
#lightning-invoice = { path = "../rust-lightning/lightning-invoice" }
Expand All @@ -68,6 +68,7 @@ tokio = { version = "1.37", default-features = false, features = [ "rt-multi-thr
esplora-client = { version = "0.6", default-features = false }
libc = "0.2"
uniffi = { version = "0.26.0", features = ["build"], optional = true }
payjoin = { version = "0.16.0", default-features = false, features = ["send", "receive", "v2"] }

[target.'cfg(vss)'.dependencies]
vss-client = "0.2"
Expand All @@ -77,8 +78,8 @@ prost = { version = "0.11.6", default-features = false}
winapi = { version = "0.3", features = ["winbase"] }

[dev-dependencies]
lightning = { version = "0.0.123", features = ["std", "_test_utils"] }
#lightning = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["std", "_test_utils"] }
# lightning = { version = "0.0.123", features = ["std", "_test_utils"] }
lightning = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event", features = ["std", "_test_utils"] }
electrum-client = { version = "0.15.1", default-features = true }
bitcoincore-rpc = { version = "0.17.0", default-features = false }
proptest = "1.0.0"
Expand All @@ -101,3 +102,8 @@ panic = "abort"

[profile.dev]
panic = "abort"

[[example]]
name = "ldk-node-with-payjoin-support"
path = "examples/ldk-node-with-payjoin-support.rs"

31 changes: 31 additions & 0 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ interface Node {
Bolt12Payment bolt12_payment();
SpontaneousPayment spontaneous_payment();
OnchainPayment onchain_payment();
PayjoinPayment payjoin_payment();
[Throws=NodeError]
void connect(PublicKey node_id, SocketAddress address, boolean persist);
[Throws=NodeError]
Expand Down Expand Up @@ -148,6 +149,13 @@ interface OnchainPayment {
Txid send_all_to_address([ByRef]Address address);
};

interface PayjoinPayment {
[Throws=NodeError]
void send(string payjoin_uri);
[Throws=NodeError]
void send_with_amount(string payjoin_uri, u64 amount_sats);
};

[Error]
enum NodeError {
"AlreadyRunning",
Expand Down Expand Up @@ -196,6 +204,14 @@ enum NodeError {
"InsufficientFunds",
"LiquiditySourceUnavailable",
"LiquidityFeeTooHigh",
"PayjoinUnavailable",
"PayjoinUriInvalid",
"PayjoinRequestMissingAmount",
"PayjoinRequestCreationFailed",
"PayjoinResponseProcessingFailed",
"PayjoinReceiverUnavailable",
"PayjoinReceiverRequestValidationFailed",
"PayjoinReceiverEnrollementFailed"
};

dictionary NodeStatus {
Expand Down Expand Up @@ -227,6 +243,7 @@ enum BuildError {
"KVStoreSetupFailed",
"WalletSetupFailed",
"LoggerSetupFailed",
"InvalidPayjoinConfig",
};

[Enum]
Expand All @@ -238,6 +255,9 @@ interface Event {
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id);
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
PayjoinPaymentPending(Txid txid, u64 amount, ScriptBuf receipient);
PayjoinPaymentSuccess(Txid txid, u64 amount, ScriptBuf receipient);
PayjoinPaymentFailed(Txid? txid, u64 amount, ScriptBuf receipient, PayjoinPaymentFailureReason reason);
};

enum PaymentFailureReason {
Expand All @@ -249,6 +269,13 @@ enum PaymentFailureReason {
"UnexpectedError",
};

enum PayjoinPaymentFailureReason {
"Timeout",
"TransactionFinalisationFailed",
"InvalidReceiverResponse",
};


[Enum]
interface ClosureReason {
CounterpartyForceClosed(UntrustedString peer_msg);
Expand All @@ -274,6 +301,7 @@ interface PaymentKind {
Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId offer_id);
Bolt12Refund(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret);
Spontaneous(PaymentHash hash, PaymentPreimage? preimage);
Payjoin();
};

enum PaymentDirection {
Expand Down Expand Up @@ -499,3 +527,6 @@ typedef string Mnemonic;

[Custom]
typedef string UntrustedString;

[Custom]
typedef string ScriptBuf;
71 changes: 71 additions & 0 deletions examples/ldk-node-with-payjoin-support.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use ldk_node::bitcoin::Network;
use ldk_node::{Builder, LogLevel};

fn main() {
let mut builder = Builder::new();
builder.set_log_level(LogLevel::Gossip);
builder.set_network(Network::Testnet);
builder.set_esplora_server("https://blockstream.info/testnet/api".to_string());
builder.set_gossip_source_rgs(
"https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string(),
);

// Payjoin directory is needed only if you are setting up Payjoin receiver,
// not required for Payjoin sender.
let payjoin_directory = "https://payjo.in".to_string();
// Payjoin relay is required for both Payjoin receiver and sender.
let payjoin_relay = "https://pj.bobspacebkk.com".to_string();

// Enable sending payjoin transactions
// builder.set_payjoin_sender_config(payjoin_relay.clone());
// ohttp keys refer to the Payjoin directory keys that are needed for the Payjoin receiver
// enrollement. If those keys are not provided the node will attempt to fetch them for you.
// let ohttp_keys = None;
// Enable receiving payjoin transactions
builder.set_payjoin_config(payjoin_directory, payjoin_relay);

let node = builder.build().unwrap();

node.start().unwrap();

// Receiving payjoin transaction
let payjoin_payment = node.payjoin_payment();
let amount_to_receive = bitcoin::Amount::from_sat(1000);
let payjoin_uri = payjoin_payment.receive(amount_to_receive).unwrap();
let payjoin_uri = payjoin_uri.to_string();

println!("Payjoin URI: {}", payjoin_uri);

//** Open a channel from incoming payjoin transactions ***//
// let payjoin_payment = node.payjoin_payment();
// let channel_amount_sats = bitcoin::Amount::from_sat(10000);
// use bitcoin::secp256k1::PublicKey;
// use lightning::ln::msgs::SocketAddress;
// let counterparty_node_id: PublicKey = unimplemented!();
// let counterparty_address: SocketAddress = unimplemented!();
// let payjoin_uri = match payjoin_payment.receive_with_channel_opening(channel_amount_sats, None, true,
// counterparty_node_id, counterparty_address,
// ).await {
// Ok(a) => a,
// Err(e) => {
// panic!("{}", e);
// },
// };
// let payjoin_uri = payjoin_uri.to_string();
// println!("Payjoin URI: {}", payjoin_uri);

//** Sending payjoin transaction **//
// let payjoin_uri = payjoin::Uri::try_from(payjoin_uri).unwrap();
// match payjoin_payment.send(payjoin_uri, None, None).await {
// Ok(Some(txid)) => {
// dbg!("Sent transaction and got a response. Transaction completed")
// },
// Ok(None) => {
// dbg!("Sent transaction and got no response. We will keep polling the response for the next 24hours")
// },
// Err(e) => {
// dbg!(e);
// }
// }
node.stop().unwrap();
}
73 changes: 71 additions & 2 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ 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_receiver::PayjoinReceiver;
use crate::payment::payjoin::handler::PayjoinHandler;
use crate::payment::store::PaymentStore;
use crate::peer_store::PeerStore;
use crate::tx_broadcaster::TransactionBroadcaster;
Expand Down Expand Up @@ -93,6 +95,13 @@ struct LiquiditySourceConfig {
lsps2_service: Option<(SocketAddress, PublicKey, Option<String>)>,
}

#[derive(Debug, Clone)]
struct PayjoinConfig {
payjoin_directory: payjoin::Url,
payjoin_relay: payjoin::Url,
ohttp_keys: Option<payjoin::OhttpKeys>,
}

impl Default for LiquiditySourceConfig {
fn default() -> Self {
Self { lsps2_service: None }
Expand Down Expand Up @@ -132,6 +141,8 @@ pub enum BuildError {
WalletSetupFailed,
/// We failed to setup the logger.
LoggerSetupFailed,
/// Invalid Payjoin configuration.
InvalidPayjoinConfig,
}

impl fmt::Display for BuildError {
Expand All @@ -152,6 +163,10 @@ impl fmt::Display for BuildError {
Self::KVStoreSetupFailed => write!(f, "Failed to setup KVStore."),
Self::WalletSetupFailed => write!(f, "Failed to setup onchain wallet."),
Self::LoggerSetupFailed => write!(f, "Failed to setup the logger."),
Self::InvalidPayjoinConfig => write!(
f,
"Invalid Payjoin configuration. Make sure the provided arguments are valid URLs."
),
}
}
}
Expand All @@ -172,6 +187,7 @@ pub struct NodeBuilder {
chain_data_source_config: Option<ChainDataSourceConfig>,
gossip_source_config: Option<GossipSourceConfig>,
liquidity_source_config: Option<LiquiditySourceConfig>,
payjoin_config: Option<PayjoinConfig>,
}

impl NodeBuilder {
Expand All @@ -187,12 +203,14 @@ impl NodeBuilder {
let chain_data_source_config = None;
let gossip_source_config = None;
let liquidity_source_config = None;
let payjoin_config = None;
Self {
config,
entropy_source_config,
chain_data_source_config,
gossip_source_config,
liquidity_source_config,
payjoin_config,
}
}

Expand Down Expand Up @@ -247,6 +265,27 @@ impl NodeBuilder {
self
}

/// Configures the [`Node`] instance to enable payjoin transactions.
pub fn set_payjoin_config(
&mut self, payjoin_directory: String, payjoin_relay: String, ohttp_keys: Option<String>,
) -> Result<&mut Self, BuildError> {
let payjoin_relay =
payjoin::Url::parse(&payjoin_relay).map_err(|_| BuildError::InvalidPayjoinConfig)?;
let payjoin_directory = payjoin::Url::parse(&payjoin_directory)
.map_err(|_| BuildError::InvalidPayjoinConfig)?;
let ohttp_keys = if let Some(ohttp_keys) = ohttp_keys {
let keys = match payjoin::OhttpKeys::decode(ohttp_keys.as_bytes()) {
Ok(keys) => keys,
Err(_) => return Err(BuildError::InvalidPayjoinConfig),
};
Some(keys)
} else {
None
};
self.payjoin_config = Some(PayjoinConfig { payjoin_directory, payjoin_relay, ohttp_keys });
Ok(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 @@ -365,6 +404,7 @@ impl NodeBuilder {
self.chain_data_source_config.as_ref(),
self.gossip_source_config.as_ref(),
self.liquidity_source_config.as_ref(),
self.payjoin_config.as_ref(),
seed_bytes,
logger,
vss_store,
Expand All @@ -386,6 +426,7 @@ impl NodeBuilder {
self.chain_data_source_config.as_ref(),
self.gossip_source_config.as_ref(),
self.liquidity_source_config.as_ref(),
self.payjoin_config.as_ref(),
seed_bytes,
logger,
kv_store,
Expand Down Expand Up @@ -453,6 +494,11 @@ impl ArcedNodeBuilder {
self.inner.write().unwrap().set_gossip_source_p2p();
}

/// Configures the [`Node`] instance to enable payjoin transactions.
pub fn set_payjoin_config(&self, payjoin_relay: String) -> Result<(), BuildError> {
self.inner.write().unwrap().set_payjoin_config(payjoin_relay).map(|_| ())
}

/// 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 @@ -521,8 +567,9 @@ impl ArcedNodeBuilder {
fn build_with_store_internal(
config: Arc<Config>, chain_data_source_config: Option<&ChainDataSourceConfig>,
gossip_source_config: Option<&GossipSourceConfig>,
liquidity_source_config: Option<&LiquiditySourceConfig>, seed_bytes: [u8; 64],
logger: Arc<FilesystemLogger>, kv_store: Arc<DynStore>,
liquidity_source_config: Option<&LiquiditySourceConfig>,
payjoin_config: Option<&PayjoinConfig>, seed_bytes: [u8; 64], logger: Arc<FilesystemLogger>,
kv_store: Arc<DynStore>,
) -> 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 @@ -966,6 +1013,26 @@ fn build_with_store_internal(
let (stop_sender, _) = tokio::sync::watch::channel(());
let (event_handling_stopped_sender, _) = tokio::sync::watch::channel(());

let mut payjoin_handler = None;
let mut payjoin_receiver = None;
if let Some(pj_config) = payjoin_config {
payjoin_handler = Some(Arc::new(PayjoinHandler::new(
pj_config.payjoin_relay.clone(),
Arc::clone(&tx_sync),
Arc::clone(&event_queue),
Arc::clone(&wallet),
Arc::clone(&payment_store),
)));
payjoin_receiver = Some(Arc::new(PayjoinReceiver::new(
Arc::clone(&logger),
Arc::clone(&wallet),
Arc::clone(&channel_manager),
Arc::clone(&config),
pj_config.payjoin_directory.clone(),
pj_config.payjoin_relay.clone(),
pj_config.ohttp_keys.clone(),
)));
}
let is_listening = Arc::new(AtomicBool::new(false));
let latest_wallet_sync_timestamp = Arc::new(RwLock::new(None));
let latest_onchain_wallet_sync_timestamp = Arc::new(RwLock::new(None));
Expand All @@ -987,6 +1054,8 @@ fn build_with_store_internal(
channel_manager,
chain_monitor,
output_sweeper,
payjoin_handler,
payjoin_receiver,
peer_manager,
connection_manager,
keys_manager,
Expand Down
Loading