|
| 1 | +mod common; |
| 2 | + |
| 3 | +use common::{ |
| 4 | + expect_payjoin_tx_sent_successfully_event, generate_blocks_and_wait, |
| 5 | + premine_and_distribute_funds, setup_bitcoind_and_electrsd, wait_for_tx, |
| 6 | +}; |
| 7 | + |
| 8 | +use bitcoin::Amount; |
| 9 | +use ldk_node::{ |
| 10 | + payment::{PaymentDirection, PaymentKind, PaymentStatus}, |
| 11 | + Event, |
| 12 | +}; |
| 13 | +use payjoin::{ |
| 14 | + receive::v2::{Enrolled, Enroller}, |
| 15 | + OhttpKeys, PjUriBuilder, |
| 16 | +}; |
| 17 | + |
| 18 | +use crate::common::{ |
| 19 | + expect_payjoin_tx_broadcasted, expect_payjoin_tx_pending_event, random_config, setup_payjoin_node, setup_two_nodes |
| 20 | +}; |
| 21 | + |
| 22 | +struct PayjoinReceiver { |
| 23 | + ohttp_keys: OhttpKeys, |
| 24 | + enrolled: Enrolled, |
| 25 | +} |
| 26 | + |
| 27 | +impl PayjoinReceiver { |
| 28 | + fn enroll() -> Self { |
| 29 | + let payjoin_directory = payjoin::Url::parse("https://payjo.in").unwrap(); |
| 30 | + let payjoin_relay = payjoin::Url::parse("https://pj.bobspacebkk.com").unwrap(); |
| 31 | + let ohttp_keys = { |
| 32 | + let payjoin_directory = payjoin_directory.join("/ohttp-keys").unwrap(); |
| 33 | + let proxy = reqwest::Proxy::all(payjoin_relay.clone()).unwrap(); |
| 34 | + let client = reqwest::blocking::Client::builder().proxy(proxy).build().unwrap(); |
| 35 | + let response = client.get(payjoin_directory).send().unwrap(); |
| 36 | + let response = response.bytes().unwrap(); |
| 37 | + OhttpKeys::decode(response.to_vec().as_slice()).unwrap() |
| 38 | + }; |
| 39 | + dbg!(&ohttp_keys); |
| 40 | + let mut enroller = Enroller::from_directory_config( |
| 41 | + payjoin_directory.clone(), |
| 42 | + ohttp_keys.clone(), |
| 43 | + payjoin_relay.clone(), |
| 44 | + ); |
| 45 | + let (req, ctx) = enroller.extract_req().unwrap(); |
| 46 | + let mut headers = reqwest::header::HeaderMap::new(); |
| 47 | + headers.insert( |
| 48 | + reqwest::header::CONTENT_TYPE, |
| 49 | + reqwest::header::HeaderValue::from_static("message/ohttp-req"), |
| 50 | + ); |
| 51 | + let response = reqwest::blocking::Client::new() |
| 52 | + .post(&req.url.to_string()) |
| 53 | + .body(req.body) |
| 54 | + .headers(headers) |
| 55 | + .send() |
| 56 | + .unwrap(); |
| 57 | + let response = match response.bytes() { |
| 58 | + Ok(response) => response, |
| 59 | + Err(_) => { |
| 60 | + panic!("Error reading response"); |
| 61 | + }, |
| 62 | + }; |
| 63 | + let enrolled = enroller.process_res(response.to_vec().as_slice(), ctx).unwrap(); |
| 64 | + Self { ohttp_keys, enrolled } |
| 65 | + } |
| 66 | + |
| 67 | + pub(crate) fn receive( |
| 68 | + &self, amount: bitcoin::Amount, receiving_address: bitcoin::Address, |
| 69 | + ) -> String { |
| 70 | + let enrolled = self.enrolled.clone(); |
| 71 | + let fallback_target = enrolled.fallback_target(); |
| 72 | + let ohttp_keys = self.ohttp_keys.clone(); |
| 73 | + let pj_part = payjoin::Url::parse(&fallback_target).unwrap(); |
| 74 | + let payjoin_uri = PjUriBuilder::new(receiving_address, pj_part, Some(ohttp_keys.clone())) |
| 75 | + .amount(amount) |
| 76 | + .build(); |
| 77 | + payjoin_uri.to_string() |
| 78 | + } |
| 79 | + |
| 80 | + pub(crate) fn process_payjoin_request(self) { |
| 81 | + let mut enrolled = self.enrolled; |
| 82 | + let (req, context) = enrolled.extract_req().unwrap(); |
| 83 | + |
| 84 | + let client = reqwest::blocking::Client::new(); |
| 85 | + let response = client |
| 86 | + .post(req.url.to_string()) |
| 87 | + .body(req.body) |
| 88 | + .headers(PayjoinReceiver::ohttp_headers()) |
| 89 | + .send() |
| 90 | + .unwrap(); |
| 91 | + let response = response.bytes().unwrap(); |
| 92 | + let response = enrolled.process_res(response.to_vec().as_slice(), context).unwrap(); |
| 93 | + let unchecked_proposal = response.unwrap(); |
| 94 | + |
| 95 | + let proposal = unchecked_proposal.assume_interactive_receiver(); |
| 96 | + let proposal = proposal.check_inputs_not_owned(|_script| Ok(false)).unwrap(); |
| 97 | + let proposal = proposal.check_no_mixed_input_scripts().unwrap(); |
| 98 | + let proposal = proposal.check_no_inputs_seen_before(|_outpoint| Ok(false)).unwrap(); |
| 99 | + let provisional_proposal = proposal.identify_receiver_outputs(|_script| Ok(true)).unwrap(); |
| 100 | + |
| 101 | + // Finalise Payjoin Proposal |
| 102 | + let mut payjoin_proposal = |
| 103 | + provisional_proposal.finalize_proposal(|psbt| Ok(psbt.clone()), None).unwrap(); |
| 104 | + |
| 105 | + let (receiver_request, _) = payjoin_proposal.extract_v2_req().unwrap(); |
| 106 | + reqwest::blocking::Client::new() |
| 107 | + .post(&receiver_request.url.to_string()) |
| 108 | + .body(receiver_request.body) |
| 109 | + .headers(PayjoinReceiver::ohttp_headers()) |
| 110 | + .send() |
| 111 | + .unwrap(); |
| 112 | + } |
| 113 | + |
| 114 | + fn ohttp_headers() -> reqwest::header::HeaderMap { |
| 115 | + let mut headers = reqwest::header::HeaderMap::new(); |
| 116 | + headers.insert( |
| 117 | + reqwest::header::CONTENT_TYPE, |
| 118 | + reqwest::header::HeaderValue::from_static("message/ohttp-req"), |
| 119 | + ); |
| 120 | + headers |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +#[test] |
| 125 | +fn send_receive_regular_payjoin_transaction() { |
| 126 | + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); |
| 127 | + let config_a = random_config(false); |
| 128 | + let (receiver, _) = setup_two_nodes(&electrsd, false, false, false); |
| 129 | + let payjoin_sender = setup_payjoin_node(&electrsd, config_a); |
| 130 | + let addr_a = payjoin_sender.onchain_payment().new_address().unwrap(); |
| 131 | + let premine_amount_sat = 100_000_00; |
| 132 | + premine_and_distribute_funds( |
| 133 | + &bitcoind.client, |
| 134 | + &electrsd.client, |
| 135 | + vec![addr_a], |
| 136 | + Amount::from_sat(premine_amount_sat), |
| 137 | + ); |
| 138 | + payjoin_sender.sync_wallets().unwrap(); |
| 139 | + assert_eq!(payjoin_sender.list_balances().spendable_onchain_balance_sats, premine_amount_sat); |
| 140 | + assert_eq!(payjoin_sender.list_balances().spendable_onchain_balance_sats, 100_000_00); |
| 141 | + assert_eq!(payjoin_sender.next_event(), None); |
| 142 | + |
| 143 | + let payjoin_receiver = PayjoinReceiver::enroll(); |
| 144 | + let payjoin_uri = payjoin_receiver |
| 145 | + .receive(Amount::from_sat(80_000), receiver.onchain_payment().new_address().unwrap()); |
| 146 | + dbg!(&payjoin_uri); |
| 147 | + |
| 148 | + assert!(payjoin_sender.payjoin_payment().send(payjoin_uri).is_ok()); |
| 149 | + |
| 150 | + let txid = expect_payjoin_tx_pending_event!(payjoin_sender); |
| 151 | + let payments = payjoin_sender.list_payments(); |
| 152 | + let payment = payments.first().unwrap(); |
| 153 | + assert_eq!(payment.amount_msat, Some(80_000)); |
| 154 | + assert_eq!(payment.status, PaymentStatus::Pending); |
| 155 | + assert_eq!(payment.direction, PaymentDirection::Outbound); |
| 156 | + assert_eq!(payment.kind, PaymentKind::Payjoin); |
| 157 | + |
| 158 | + payjoin_receiver.process_payjoin_request(); |
| 159 | + |
| 160 | + expect_payjoin_tx_broadcasted!(payjoin_sender); |
| 161 | + |
| 162 | + wait_for_tx(&electrsd.client, txid); |
| 163 | + generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 3); |
| 164 | + payjoin_sender.sync_wallets().unwrap(); |
| 165 | + generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 4); |
| 166 | + payjoin_sender.sync_wallets().unwrap(); |
| 167 | + |
| 168 | + expect_payjoin_tx_sent_successfully_event!(payjoin_sender); |
| 169 | +} |
0 commit comments