@@ -31,10 +31,11 @@ use crate::{gossip::*, scorer::HubPreferentialScorer};
3131use crate :: { labels:: LabelStorage , subscription:: MutinySubscriptionClient } ;
3232use anyhow:: anyhow;
3333use bdk:: chain:: { BlockId , ConfirmationTime } ;
34- use bdk:: { wallet:: AddressIndex , LocalUtxo } ;
34+ use bdk:: { wallet:: AddressIndex , FeeRate , LocalUtxo } ;
3535use bitcoin:: blockdata:: script;
3636use bitcoin:: hashes:: hex:: ToHex ;
3737use bitcoin:: hashes:: { sha256, Hash } ;
38+ use bitcoin:: psbt:: PartiallySignedTransaction ;
3839use bitcoin:: secp256k1:: { rand, PublicKey , Secp256k1 , SecretKey } ;
3940use bitcoin:: util:: bip32:: { DerivationPath , ExtendedPrivKey } ;
4041use bitcoin:: { Address , Network , OutPoint , Transaction , Txid } ;
@@ -62,6 +63,7 @@ use nostr::{EventBuilder, Keys, Kind, Tag, TagKind};
6263use reqwest:: Client ;
6364use serde:: { Deserialize , Serialize } ;
6465use serde_json:: Value ;
66+ use std:: io:: Cursor ;
6567use std:: str:: FromStr ;
6668use std:: sync:: atomic:: { AtomicBool , Ordering } ;
6769use std:: { collections:: HashMap , ops:: Deref , sync:: Arc } ;
@@ -162,6 +164,7 @@ pub struct MutinyBip21RawMaterials {
162164 pub invoice : Option < Bolt11Invoice > ,
163165 pub btc_amount : Option < String > ,
164166 pub labels : Vec < String > ,
167+ pub pj : Option < String > ,
165168}
166169
167170#[ derive( Debug , Serialize , Deserialize , Clone , Eq , PartialEq ) ]
@@ -1009,7 +1012,7 @@ impl<S: MutinyStorage> NodeManager<S> {
10091012 Err ( MutinyError :: WalletOperationFailed )
10101013 }
10111014
1012- /// Creates a BIP 21 invoice. This creates a new address and a lightning invoice.
1015+ /// Creates a BIP 21 invoice. This creates a new address, a lightning invoice, and payjoin session .
10131016 /// The lightning invoice may return errors related to the LSP. Check the error and
10141017 /// fallback to `get_new_address` and warn the user that Lightning is not available.
10151018 ///
@@ -1052,14 +1055,125 @@ impl<S: MutinyStorage> NodeManager<S> {
10521055 return Err ( MutinyError :: WalletOperationFailed ) ;
10531056 } ;
10541057
1058+ // If we are in safe mode, we don't create payjoin sessions
1059+ let pj = {
1060+ // TODO get from &self config
1061+ const PJ_RELAY_URL : & str = "http://localhost:8080" ;
1062+ const OH_RELAY_URL : & str = "http://localhost:8080" ;
1063+ const OHTTP_CONFIG_BASE64 : & str = "AQAg7YjKSn1zBziW3LvPCQ8X18hH0dU67G-vOcMHu0-m81AABAABAAM" ;
1064+ let mut enroller = payjoin:: receive:: Enroller :: from_relay_config (
1065+ PJ_RELAY_URL ,
1066+ OHTTP_CONFIG_BASE64 ,
1067+ OH_RELAY_URL ,
1068+ //Some("c53989e590b0f02edeec42a9c43fd1e4e960aec243bb1e6064324bd2c08ec498")
1069+ ) ;
1070+ let http_client = reqwest:: Client :: builder ( )
1071+ //.danger_accept_invalid_certs(true) ? is tls unchecked :O
1072+ . build ( )
1073+ . unwrap ( ) ;
1074+ // enroll client
1075+ let ( req, context) = enroller. extract_req ( ) . unwrap ( ) ;
1076+ let ohttp_response = http_client
1077+ . post ( req. url )
1078+ . body ( req. body )
1079+ . send ( )
1080+ . await
1081+ . unwrap ( ) ;
1082+ let ohttp_response = ohttp_response. bytes ( ) . await . unwrap ( ) ;
1083+ let enrolled = enroller
1084+ . process_res ( ohttp_response. as_ref ( ) , context)
1085+ . map_err ( |e| anyhow ! ( "parse error {}" , e) )
1086+ . unwrap ( ) ;
1087+ let pj_uri = enrolled. fallback_target ( ) ;
1088+ log_debug ! ( self . logger, "{pj_uri}" ) ;
1089+ let wallet = self . wallet . clone ( ) ;
1090+ // run await payjoin task in the background as it'll keep polling the relay
1091+ wasm_bindgen_futures:: spawn_local ( async move {
1092+ let wallet = wallet. clone ( ) ;
1093+ let pj_txid = Self :: receive_payjoin ( wallet, enrolled) . await . unwrap ( ) ;
1094+ log:: info!( "Received payjoin txid: {}" , pj_txid) ;
1095+ } ) ;
1096+ Some ( pj_uri)
1097+ } ;
1098+
10551099 Ok ( MutinyBip21RawMaterials {
10561100 address,
10571101 invoice,
10581102 btc_amount : amount. map ( |amount| bitcoin:: Amount :: from_sat ( amount) . to_btc ( ) . to_string ( ) ) ,
10591103 labels,
1104+ pj,
10601105 } )
10611106 }
10621107
1108+ /// Poll the payjoin relay to maintain a payjoin session and create a payjoin proposal.
1109+ pub async fn receive_payjoin (
1110+ wallet : Arc < OnChainWallet < S > > ,
1111+ mut enrolled : payjoin:: receive:: Enrolled ,
1112+ ) -> Result < Txid , MutinyError > {
1113+ let http_client = reqwest:: Client :: builder ( )
1114+ //.danger_accept_invalid_certs(true) ? is tls unchecked :O
1115+ . build ( )
1116+ . unwrap ( ) ;
1117+ let proposal: payjoin:: receive:: UncheckedProposal =
1118+ Self :: poll_for_fallback_psbt ( & http_client, & mut enrolled)
1119+ . await
1120+ . unwrap ( ) ;
1121+ let payjoin_proposal = wallet. process_payjoin_proposal ( proposal) . unwrap ( ) ;
1122+
1123+ let ( req, ohttp_ctx) = payjoin_proposal
1124+ . extract_v2_req ( )
1125+ . unwrap ( ) ; // extraction failed
1126+ let res = http_client
1127+ . post ( req. url )
1128+ . body ( req. body )
1129+ . send ( )
1130+ . await
1131+ . unwrap ( ) ;
1132+ let res = res. bytes ( ) . await . unwrap ( ) ;
1133+ let res = payjoin_proposal
1134+ . deserialize_res ( res. to_vec ( ) , ohttp_ctx)
1135+ . unwrap ( ) ;
1136+ // convert from bitcoin 29 to 30
1137+ let txid = payjoin_proposal. psbt ( ) . clone ( ) . extract_tx ( ) . txid ( ) ;
1138+ let txid = Txid :: from_str ( & txid. to_string ( ) ) . unwrap ( ) ;
1139+ Ok ( txid)
1140+ }
1141+
1142+ async fn poll_for_fallback_psbt (
1143+ client : & reqwest:: Client ,
1144+ enroller : & mut payjoin:: receive:: Enrolled ,
1145+ ) -> Result < payjoin:: receive:: UncheckedProposal , ( ) > {
1146+ loop {
1147+ let ( req, context) = enroller. extract_req ( ) . unwrap ( ) ;
1148+ let ohttp_response = client
1149+ . post ( req. url )
1150+ . body ( req. body )
1151+ . send ( )
1152+ . await
1153+ . unwrap ( ) ;
1154+ let ohttp_response = ohttp_response. bytes ( ) . await . unwrap ( ) ;
1155+ let proposal = enroller
1156+ . process_res ( ohttp_response. as_ref ( ) , context)
1157+ . map_err ( |e| anyhow ! ( "parse error {}" , e) )
1158+ . unwrap ( ) ;
1159+ match proposal {
1160+ Some ( proposal) => return Ok ( proposal) ,
1161+ None => Self :: delay ( 5000 ) . await . unwrap ( ) ,
1162+ }
1163+ }
1164+ }
1165+
1166+ async fn delay ( millis : u32 ) -> Result < ( ) , wasm_bindgen:: JsValue > {
1167+ let promise = js_sys:: Promise :: new ( & mut |yes, _| {
1168+ let win = web_sys:: window ( ) . expect ( "should have a Window" ) ;
1169+ win. set_timeout_with_callback_and_timeout_and_arguments_0 ( & yes, millis as i32 )
1170+ . expect ( "should set a timeout" ) ;
1171+ } ) ;
1172+
1173+ wasm_bindgen_futures:: JsFuture :: from ( promise) . await ?;
1174+ Ok ( ( ) )
1175+ }
1176+
10631177 /// Sends an on-chain transaction to the given address.
10641178 /// The amount is in satoshis and the fee rate is in sat/vbyte.
10651179 ///
0 commit comments