diff --git a/Cargo.toml b/Cargo.toml index 7e4a3b348..eea5ad4eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,7 @@ lightning-liquidity = { version = "0.1.0-alpha.1", features = ["std"] } bdk = { version = "0.29.0", default-features = false, features = ["std", "async-interface", "use-esplora-async", "sqlite-bundled", "keys-bip39"]} -reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] } +reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls", "blocking"] } rusqlite = { version = "0.28.0", features = ["bundled"] } bitcoin = "0.30.2" bip39 = "2.0.0" @@ -69,6 +69,11 @@ 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.13.0", features = ["receive", "send"] } +http-body-util = "0.1.0" +hyper = {version = "1.2.0", features = ["http1", "server"]} +bytes = "1.5.0" +hyper-util = {version = "0.1.3", features = ["tokio"] } [target.'cfg(vss)'.dependencies] vss-client = "0.2" diff --git a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt index 39c19821d..1d0d5f104 100644 --- a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt +++ b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt @@ -119,6 +119,7 @@ class LibraryTest { config1.listeningAddresses = listOf(listenAddress1) config1.network = Network.REGTEST config1.logLevel = LogLevel.TRACE + config1.payjoin_server_port = 1345 println("Config 1: $config1") @@ -127,6 +128,7 @@ class LibraryTest { config2.listeningAddresses = listOf(listenAddress2) config2.network = Network.REGTEST config2.logLevel = LogLevel.TRACE + config2.payjoin_server_port = 1346 println("Config 2: $config2") val builder1 = Builder.fromConfig(config1) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 30b2d4a1b..37ed24307 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -15,6 +15,7 @@ dictionary Config { sequence trusted_peers_0conf; u64 probing_liquidity_limit_multiplier; LogLevel log_level; + u16 payjoin_server_port; }; interface Builder { diff --git a/src/builder.rs b/src/builder.rs index a09b2563f..046900d50 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -12,6 +12,7 @@ use crate::logger::{log_error, FilesystemLogger, Logger}; use crate::message_handler::NodeCustomMessageHandler; use crate::payment_store::PaymentStore; use crate::peer_store::PeerStore; +use crate::pjoin::LDKPayjoinExecuter; use crate::sweep::OutputSweeper; use crate::tx_broadcaster::TransactionBroadcaster; use crate::types::{ @@ -19,6 +20,7 @@ use crate::types::{ OnionMessenger, PeerManager, }; use crate::wallet::Wallet; +use crate::LDKPayjoin; use crate::{LogLevel, Node}; use lightning::chain::{chainmonitor, BestBlock, Watch}; @@ -944,6 +946,13 @@ fn build_with_store_internal( }; let (stop_sender, _) = tokio::sync::watch::channel(()); + let payjoin_executer = LDKPayjoinExecuter::new( + Arc::clone(&wallet), + Arc::clone(&logger), + Arc::clone(&peer_manager), + Arc::clone(&channel_manager), + ); + let payjoin = Arc::new(LDKPayjoin::new(payjoin_executer)); Ok(Node { runtime, @@ -957,6 +966,7 @@ fn build_with_store_internal( channel_manager, chain_monitor, output_sweeper, + payjoin, peer_manager, keys_manager, network_graph, diff --git a/src/config.rs b/src/config.rs index 945d712c9..1e33a3615 100644 --- a/src/config.rs +++ b/src/config.rs @@ -44,6 +44,9 @@ pub(crate) const WALLET_SYNC_INTERVAL_MINIMUM_SECS: u64 = 10; // The length in bytes of our wallets' keys seed. pub(crate) const WALLET_KEYS_SEED_LEN: usize = 64; +// Port used by the Payjoin HTTP server. +pub(crate) const DEFAULT_PAYJOIN_HTTP_SERVER_PORT: u16 = 3227; + #[derive(Debug, Clone)] /// Represents the configuration of an [`Node`] instance. /// @@ -104,6 +107,8 @@ pub struct Config { /// /// Any messages below this level will be excluded from the logs. pub log_level: LogLevel, + /// Payjoin server port + pub payjoin_server_port: u16, } impl Default for Config { @@ -120,6 +125,7 @@ impl Default for Config { trusted_peers_0conf: Vec::new(), probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER, log_level: DEFAULT_LOG_LEVEL, + payjoin_server_port: DEFAULT_PAYJOIN_HTTP_SERVER_PORT, } } } diff --git a/src/lib.rs b/src/lib.rs index 24b2123f5..9491df82a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,6 +89,7 @@ mod logger; mod message_handler; mod payment_store; mod peer_store; +mod pj_new_crate; mod sweep; mod tx_broadcaster; mod types; @@ -96,7 +97,8 @@ mod types; mod uniffi_types; mod wallet; -pub use bip39; +use crate::pj_new_crate::ScheduledChannel; +use crate::pjoin::LDKPayjoin; pub use bitcoin; pub use lightning; pub use lightning_invoice; @@ -107,6 +109,8 @@ pub use error::Error as NodeError; use error::Error; pub use event::Event; +use payjoin::Uri; +mod pjoin; pub use types::ChannelConfig; pub use io::utils::generate_entropy_mnemonic; @@ -157,18 +161,18 @@ use lightning_transaction_sync::EsploraSyncClient; use lightning::routing::router::{PaymentParameters, RouteParameters}; use lightning_invoice::{payment, Bolt11Invoice, Currency}; -use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; +use bitcoin::{hashes::sha256::Hash as Sha256, Amount}; use bitcoin::{Address, Txid}; use rand::Rng; -use std::default::Default; -use std::net::ToSocketAddrs; +use std::net::{SocketAddr, ToSocketAddrs}; use std::sync::{Arc, Mutex, RwLock}; use std::time::{Duration, Instant, SystemTime}; +use std::{default::Default, str::FromStr}; #[cfg(feature = "uniffi")] uniffi::include_scaffolding!("ldk_node"); @@ -188,6 +192,7 @@ pub struct Node { channel_manager: Arc>, chain_monitor: Arc>, output_sweeper: Arc>, + payjoin: Arc>, peer_manager: Arc>, keys_manager: Arc, network_graph: Arc, @@ -461,7 +466,30 @@ impl Node { }); } - // Regularly reconnect to persisted peers. + let payjoin_handler = Arc::clone(&self.payjoin); + let mut stop_payjoin_server = self.stop_sender.subscribe(); + let pj_port = self.config.payjoin_server_port; + runtime.spawn(async move { + let addr = SocketAddr::from(([127, 0, 0, 1], pj_port)); + let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + loop { + let (stream, _) = match listener.accept().await { + Ok(res) => res, + Err(e) => { + println!("Failed to accept incoming payjoin connection: {}", e); + continue; + }, + }; + tokio::select! { + _ = stop_payjoin_server.changed() => { + return; + } + _ = payjoin_handler.serve(stream) => {} + } + } + }); + + // Regularly reconnect to channel peers. let connect_pm = Arc::clone(&self.peer_manager); let connect_logger = Arc::clone(&self.logger); let connect_peer_store = Arc::clone(&self.peer_store); @@ -667,6 +695,33 @@ impl Node { self.runtime.read().unwrap().is_some() } + /// Request a new channel to be opened with a remote peer. + pub async fn schedule_payjoin_channel( + &self, channel_amount_sats: u64, push_msat: Option, announce_channel: bool, + node_id: PublicKey, + ) -> Result { + let channel = + ScheduledChannel::new(channel_amount_sats, push_msat, announce_channel, node_id); + self.payjoin.schedule(channel).await; + let bip21 = self.payjoin_bip21(channel_amount_sats); + bip21 + } + + /// Generate a BIP21 URI for a payjoin request. + pub fn payjoin_bip21(&self, amount_sats: u64) -> Result { + let address = self.wallet.get_new_address()?; + let amount = Amount::from_sat(amount_sats); + let pj = format!("https://0.0.0.0:{}/payjoin", self.config.payjoin_server_port); + let pj_uri_string = format!("{}?amount={}&pj={}", address.to_qr_uri(), amount.to_btc(), pj); + assert!(Uri::from_str(&pj_uri_string).is_ok()); + Ok(pj_uri_string) + } + + /// List all scheduled payjoin channels. + pub async fn list_scheduled_channels(&self) -> Result, Error> { + Ok(self.payjoin.list_scheduled_channels().await) + } + /// Disconnects all peers, stops all running background tasks, and shuts down [`Node`]. /// /// After this returns most API methods will return [`Error::NotRunning`]. diff --git a/src/pj_new_crate.rs b/src/pj_new_crate.rs new file mode 100644 index 000000000..dfe3210f2 --- /dev/null +++ b/src/pj_new_crate.rs @@ -0,0 +1,231 @@ +/// Payjoin is a protocol for improving the privacy of Bitcoin transactions. It allows the +/// receiver of a Bitcoin payment to add inputs to the transaction, making it look like a regular +/// payment. This makes it harder for blockchain analysis to determine which transaction outputs +/// are change and which are payments. +/// +/// In Lightning Network, the payjoin protocol can be used to receive payment to your bitcoin +/// wallet and fund a channel at the same transaction. This can save on-chain fees. +/// +/// This module provides `PayjoinScheduler` and a `PayjoinExecuter` trait that can be used to +/// implement the payjoin protocol in a Lightning Network node. +use bitcoin::secp256k1::PublicKey; +use http_body_util::{BodyExt, Full}; +use hyper::body::Incoming; +use hyper::server::conn::http1; +use hyper::service::service_fn; +use hyper::Request; +use hyper_util::rt::TokioIo; +use rand::Rng; +use std::sync::Arc; +use tokio::net::TcpStream; +use tokio::sync::Mutex; +use tokio::task::JoinError; + +/// `PayjoinExecuter` is a trait that defines an interface for executing payjoin requests in +/// Lightning Network environment where it tries to create a channel with a predefined channel +/// details and use the incoming payjoin payment to fund the channel. +/// +/// `PayjoinExecuter` is used by `PayjoinScheduler` to execute on the incoming payjoin requests and +/// schedulded channels. +pub trait PayjoinExecuter { + /// The `request_to_psbt` method is called when a payjoin request is received. The method should + /// return a PSBT that is the result of the negotiation with a counterparty node after they + /// responded with FundingSigned message. + fn request_to_psbt( + &self, channel: ScheduledChannel, request: String, + ) -> impl std::future::Future>> + std::marker::Send; +} + +/// A scheduled channel is a channel that is scheduled to be created with a counterparty node. The +/// channel is opened when a payjoin request is received and the channel is funded with the +/// incoming payment. +#[derive(Clone, Debug)] +pub struct ScheduledChannel { + channel_amount_sats: u64, + push_msat: Option, + user_channel_id: u128, + announce_channel: bool, + node_id: PublicKey, +} + +impl ScheduledChannel { + /// Create a new `ScheduledChannel` with the given channel details. + pub fn new( + channel_amount_sats: u64, push_msat: Option, announce_channel: bool, + node_id: PublicKey, + ) -> Self { + let user_channel_id: u128 = rand::thread_rng().gen::(); + Self { channel_amount_sats, push_msat, user_channel_id, announce_channel, node_id } + } + /// Get the channel amount in satoshis. + /// + /// The channel amount is the amount that is used to fund the channel when it is created. + pub fn channel_amount_sats(&self) -> u64 { + self.channel_amount_sats + } + /// Get the push amount in millisatoshis. + /// + /// The push amount is the amount that is pushed to the counterparty node when the channel is + /// created. + pub fn push_msat(&self) -> Option { + self.push_msat + } + /// Get the user channel id. + pub fn user_channel_id(&self) -> u128 { + self.user_channel_id + } + /// Get the announce channel flag. + /// + /// The announce channel flag is used to determine if the channel should be announced to the + /// network when it is created. + pub fn announce_channel(&self) -> bool { + self.announce_channel + } + /// Get the node id of the counterparty node. + /// + /// The node id is the public key of the counterparty node that is used to create the channel. + pub fn node_id(&self) -> PublicKey { + self.node_id + } +} + +/// `PayjoinScheduler` is a scheduler that handles the incoming payjoin requests and the channels +/// that are to be created with the counterparty node. +/// +/// It manages a list of `ScheduledChannel` and executes on the incoming payjoin requests using the +/// `PayjoinExecuter` trait. +#[derive(Clone)] +pub struct PayjoinScheduler( + Vec, + P, +); + +impl

PayjoinScheduler

+where + P: PayjoinExecuter + Send + Sync + 'static + Clone, +{ + /// Create a new `PayjoinScheduler` with the given channels and executer. + pub fn new(channels: Vec, executer: P) -> Self { + Self(channels, executer) + } + + /// Schedule a new channel to be created with the counterparty node. + /// + /// The channel is added to the list of scheduled channels and is used to create a channel when + /// a payjoin request is received. + pub fn schedule(&mut self, channel: ScheduledChannel) { + self.0.push(channel); + } + + /// List the scheduled channels. + pub fn list_scheduled_channels(&self) -> Vec { + self.0.clone() + } + + /// Pop the scheduled channel from the list of scheduled channels. + /// + /// The channel is removed from the list of scheduled channels and is used to create a channel + /// when a payjoin request is received. + pub fn pop_scheduled_channel(&mut self) -> Option { + self.0.pop() + } + + /// Execute on the incoming payjoin request. + pub async fn request_to_psbt( + &self, channel: ScheduledChannel, request: String, + ) -> Result> { + self.1.request_to_psbt(channel, request).await + } + + /// Serve an incoming payjoin request. + /// + /// The incoming payjoin request is served using the given `TcpStream`. + /// The payjoin request is handled by the payjoin_handler function. + /// + /// The `PayjoinScheduler` is shared across multiple threads using the `Arc` and `Mutex` types. + /// And is accessible from the payjoin_handler function. + pub async fn serve(&self, stream: TcpStream) -> Result<(), JoinError> { + let io = TokioIo::new(stream); + let channels = self.0.clone(); + let executer = self.1.clone(); + let payjoin_scheduler = Arc::new(Mutex::new(PayjoinScheduler::new(channels, executer))); + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new() + .serve_connection( + io, + service_fn(move |http_request| { + payjoin_handler(http_request, payjoin_scheduler.clone()) + }), + ) + .await + { + println!("Error serving connection: {:?}", err); + } + }) + .await + } +} + +async fn payjoin_handler( + http_request: Request, pj_scheduler: Arc>>, +) -> Result>, hyper::Error> { + let make_http_response = + |s: String| -> Result>, hyper::Error> { + Ok(hyper::Response::builder().body(Full::new(bytes::Bytes::from(s))).unwrap()) + }; + match (http_request.method(), http_request.uri().path()) { + (&hyper::Method::POST, "/payjoin") => { + // integrat payjoin crate here + let _headers = http_request.headers().clone(); + let body = http_request.into_body().collect().await?; + let body = String::from_utf8(body.to_bytes().to_vec()).unwrap(); + let mut scheduler = pj_scheduler.lock().await; + let channel = scheduler.pop_scheduled_channel().unwrap(); + let res = scheduler.request_to_psbt(channel, body).await.unwrap(); + return make_http_response(res); + }, + _ => make_http_response("404".into()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bitcoin::secp256k1::{self, Secp256k1}; + + #[derive(Clone)] + struct PayjoinExecuterImpl; + + impl PayjoinExecuter for PayjoinExecuterImpl { + async fn request_to_psbt( + &self, _channel: ScheduledChannel, _request: String, + ) -> Result> { + Ok(String::new()) + } + } + + #[tokio::test] + async fn test_payjoin_scheduler() { + let create_pubkey = || -> PublicKey { + let secp = Secp256k1::new(); + PublicKey::from_secret_key(&secp, &secp256k1::SecretKey::from_slice(&[1; 32]).unwrap()) + }; + let executer = PayjoinExecuterImpl; + let executer = executer; + let channels = Vec::new(); + let mut scheduler = PayjoinScheduler::new(channels, executer); + let channel_amount_sats = 100; + let push_msat = None; + let announce_channel = false; + let node_id = create_pubkey(); + let channel = + ScheduledChannel::new(channel_amount_sats, push_msat, announce_channel, node_id); + scheduler.schedule(channel.clone()); + let channels = scheduler.list_scheduled_channels(); + assert_eq!(channels.len(), 1); + let ch = scheduler.pop_scheduled_channel().unwrap(); + assert_eq!(channel.user_channel_id, ch.user_channel_id); + let channels = scheduler.list_scheduled_channels(); + assert_eq!(channels.len(), 0); + } +} diff --git a/src/pjoin.rs b/src/pjoin.rs new file mode 100644 index 000000000..8bf773004 --- /dev/null +++ b/src/pjoin.rs @@ -0,0 +1,83 @@ +/// This is an implementation of the payjoin protocol using `pj_new_crate` and `payjoin` crates. +/// +/// Payjoin is used in the context of channel opening, allowing a node to fund a channel using +/// funds from an incoming payjoin request. +use bitcoin::secp256k1::{self, PublicKey, Secp256k1}; +use lightning::util::persist::KVStore; +use tokio::sync::Mutex; + +use crate::{ + logger::FilesystemLogger, + pj_new_crate::{PayjoinExecuter, PayjoinScheduler, ScheduledChannel}, + types::{ChannelManager, PeerManager, Wallet}, +}; +use std::sync::Arc; + +pub struct LDKPayjoinExecuter { + channel_manager: Arc>, + logger: Arc, + peer_manager: Arc>, + wallet: Arc, +} + +impl Clone for LDKPayjoinExecuter +where + K: KVStore + Sync + Send + 'static, +{ + fn clone(&self) -> Self { + Self { + channel_manager: self.channel_manager.clone(), + logger: self.logger.clone(), + peer_manager: self.peer_manager.clone(), + wallet: self.wallet.clone(), + } + } +} + +impl LDKPayjoinExecuter { + pub fn new( + wallet: Arc, logger: Arc, peer_manager: Arc>, + channel_manager: Arc>, + ) -> Self { + Self { wallet, logger, peer_manager, channel_manager } + } +} + +impl PayjoinExecuter for LDKPayjoinExecuter { + async fn request_to_psbt( + &self, _channel: ScheduledChannel, request: String, + ) -> Result> { + // unimplemented!(); + Ok(request) + } +} + +pub struct LDKPayjoin { + scheduler: Arc>>>, +} + +impl LDKPayjoin { + pub fn new(executer: LDKPayjoinExecuter) -> Self { + // just for testing + let test_pubkey = || -> PublicKey { + let secp = Secp256k1::new(); + PublicKey::from_secret_key(&secp, &secp256k1::SecretKey::from_slice(&[1; 32]).unwrap()) + }; + let test_channels = vec![ScheduledChannel::new(10_000, Some(1_000), true, test_pubkey())]; + // let channels = Vec::new(); + let payjoin_scheduler = PayjoinScheduler::new(test_channels, executer); + Self { scheduler: Arc::new(Mutex::new(payjoin_scheduler)) } + } + + pub async fn schedule(&self, channel: ScheduledChannel) { + self.scheduler.lock().await.schedule(channel); + } + + pub async fn list_scheduled_channels(&self) -> Vec { + self.scheduler.lock().await.list_scheduled_channels() + } + + pub async fn serve(&self, stream: tokio::net::TcpStream) -> Result<(), tokio::task::JoinError> { + self.scheduler.lock().await.serve(stream).await + } +} diff --git a/src/wallet.rs b/src/wallet.rs index aa38eb986..1f41653e6 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -2,6 +2,7 @@ use crate::logger::{log_error, log_info, log_trace, Logger}; use crate::Error; +use bitcoin::psbt::Psbt; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; use lightning::ln::msgs::{DecodeError, UnsignedGossipMessage}; @@ -166,6 +167,17 @@ where Ok(self.inner.lock().unwrap().get_balance()?) } + pub(crate) fn _list_unspent(&self) -> Result, Error> { + Ok(self.inner.lock().unwrap().list_unspent()?) + } + + pub(crate) fn _wallet_process_psbt(&self, psbt: &Psbt) -> Result { + let wallet = self.inner.lock().unwrap(); + let mut psbt = psbt.clone(); + wallet.sign(&mut psbt, SignOptions::default())?; + Ok(psbt) + } + /// Send funds to the given address. /// /// If `amount_msat_or_drain` is `None` the wallet will be drained, i.e., all available funds will be diff --git a/tests/common.rs b/tests/common.rs index 3696f9b71..61165e33f 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -141,6 +141,7 @@ pub(crate) fn random_config() -> Config { config.listening_addresses = Some(rand_listening_addresses); config.log_level = LogLevel::Gossip; + config.payjoin_server_port = random_port(); config } diff --git a/tests/integration_tests_payjoin.rs b/tests/integration_tests_payjoin.rs new file mode 100644 index 000000000..b861ce698 --- /dev/null +++ b/tests/integration_tests_payjoin.rs @@ -0,0 +1,119 @@ +mod common; +use crate::common::{premine_and_distribute_funds, random_config, setup_node}; +use bitcoin::Amount; +use bitcoincore_rpc::{Client as BitcoindClient, RpcApi}; +use common::setup_bitcoind_and_electrsd; + +mod mock_payjoin_sender { + use bitcoincore_rpc::Client as BitcoindClient; + use bitcoincore_rpc::RpcApi; + use payjoin::bitcoin::address::NetworkChecked; + use std::collections::HashMap; + use std::str::FromStr; + + use bitcoincore_rpc::bitcoin::psbt::Psbt; + use payjoin::send::RequestBuilder; + + pub fn try_payjoin( + sender_wallet: &BitcoindClient, pj_uri: payjoin::Uri<'static, NetworkChecked>, + ) -> (String, String) { + // Step 1. Extract the parameters from the payjoin URI + let amount_to_send = pj_uri.amount.unwrap(); + let receiver_address = pj_uri.address.clone(); + // Step 2. Construct URI request parameters, a finalized "Original PSBT" paying `.amount` to `.address` + let mut outputs = HashMap::with_capacity(1); + outputs.insert(receiver_address.to_string(), amount_to_send); + let options = bitcoincore_rpc::json::WalletCreateFundedPsbtOptions { + lock_unspent: Some(false), + fee_rate: Some(bitcoincore_rpc::bitcoin::Amount::from_sat(10000)), + ..Default::default() + }; + let sender_psbt = sender_wallet + .wallet_create_funded_psbt( + &[], // inputs + &outputs, + None, // locktime + Some(options), + None, + ) + .unwrap(); + let psbt = + sender_wallet.wallet_process_psbt(&sender_psbt.psbt, None, None, None).unwrap().psbt; + let psbt = Psbt::from_str(&psbt).unwrap(); + // Step 4. Construct the request with the PSBT and parameters + let (req, _ctx) = RequestBuilder::from_psbt_and_uri(psbt.clone(), pj_uri) + .unwrap() + .build_with_additional_fee( + bitcoincore_rpc::bitcoin::Amount::from_sat(1), + None, + bitcoincore_rpc::bitcoin::FeeRate::MIN, + true, + ) + .unwrap() + .extract_v1() + .unwrap(); + // Step 5. Send the request and receive response + // let payjoin_url = pj_uri.extras.e + // BITCOIN:BCRT1Q0S724W239Z2XQGSZV6TE96HMYLEDCTX3GDFZEP?amount=0.01&pj=https://localhost:3000 + let url_http = req.url.as_str().replace("https", "http"); + dbg!(&url_http); + let res = reqwest::blocking::Client::new(); + let res = res + .post(&url_http) + .body(req.body.clone()) + .header("content-type", "text/plain") + .send() + .unwrap(); + let res = res.text().unwrap(); + (res, String::from_utf8(req.body).unwrap()) + // Step 6. Process the response + // + // An `Ok` response should include a Payjoin Proposal PSBT. + // Check that it's signed, following protocol, not trying to steal or otherwise error. + //let psbt = ctx.process_response(&mut res.as_bytes()).unwrap(); + //// Step 7. Sign and finalize the Payjoin Proposal PSBT + //// + //// Most software can handle adding the last signatures to a PSBT without issue. + //let psbt = sender_wallet + // .wallet_process_psbt(&base64::encode(psbt.serialize()), None, None, None) + // .unwrap() + // .psbt; + //let tx = sender_wallet.finalize_psbt(&psbt, Some(true)).unwrap().hex.unwrap(); + //// Step 8. Broadcast the Payjoin Transaction + //let txid = sender_wallet.send_raw_transaction(&tx).unwrap(); + //txid + } +} + +#[test] +fn payjoin() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let payjoin_sender_wallet: BitcoindClient = bitcoind.create_wallet("payjoin_sender").unwrap(); + let config_a = random_config(); + let node_a = setup_node(&electrsd, config_a); + let addr_a = node_a.new_onchain_address().unwrap(); + let addr_sender = payjoin_sender_wallet.get_new_address(None, None).unwrap().assume_checked(); + let premine_amount_sat = 100_000; + premine_and_distribute_funds( + &bitcoind.client, + &electrsd.client, + vec![addr_a, addr_sender], + Amount::from_sat(premine_amount_sat), + ); + node_a.sync_wallets().unwrap(); + assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat); + assert_eq!( + payjoin_sender_wallet.get_balances().unwrap().mine.trusted.to_sat(), + premine_amount_sat + ); + assert_eq!(node_a.next_event(), None); + let funding_amount_sat = 80_000; + let pj_uri = node_a.payjoin_bip21(funding_amount_sat).unwrap(); + println!("Payjoin URI: {:?}", pj_uri); + dbg!(&pj_uri); + let (receiver_response, sender_original_psbt) = mock_payjoin_sender::try_payjoin( + &payjoin_sender_wallet, + payjoin::Uri::try_from(pj_uri).unwrap().assume_checked(), + ); + assert_eq!(receiver_response, sender_original_psbt); +}