@@ -162,6 +162,7 @@ pub struct MutinyBip21RawMaterials {
162162 pub invoice : Option < Bolt11Invoice > ,
163163 pub btc_amount : Option < String > ,
164164 pub labels : Vec < String > ,
165+ pub pj : Option < String > ,
165166}
166167
167168#[ derive( Debug , Serialize , Deserialize , Clone , Eq , PartialEq ) ]
@@ -1009,7 +1010,7 @@ impl<S: MutinyStorage> NodeManager<S> {
10091010 Err ( MutinyError :: WalletOperationFailed )
10101011 }
10111012
1012- /// Creates a BIP 21 invoice. This creates a new address and a lightning invoice.
1013+ /// Creates a BIP 21 invoice. This creates a new address, a lightning invoice, and payjoin session .
10131014 /// The lightning invoice may return errors related to the LSP. Check the error and
10141015 /// fallback to `get_new_address` and warn the user that Lightning is not available.
10151016 ///
@@ -1052,14 +1053,126 @@ impl<S: MutinyStorage> NodeManager<S> {
10521053 return Err ( MutinyError :: WalletOperationFailed ) ;
10531054 } ;
10541055
1056+ // If we are in safe mode, we don't create payjoin sessions
1057+ let pj = {
1058+ // TODO get from &self config
1059+ const PJ_RELAY_URL : & str = "http://localhost:8080" ;
1060+ const OH_RELAY_URL : & str = "http://localhost:8080" ;
1061+ const OHTTP_CONFIG_BASE64 : & str = "AQAg7YjKSn1zBziW3LvPCQ8X18hH0dU67G-vOcMHu0-m81AABAABAAM" ;
1062+ let mut enroller = payjoin:: receive:: Enroller :: from_relay_config (
1063+ PJ_RELAY_URL ,
1064+ OHTTP_CONFIG_BASE64 ,
1065+ OH_RELAY_URL ,
1066+ //Some("c53989e590b0f02edeec42a9c43fd1e4e960aec243bb1e6064324bd2c08ec498")
1067+ ) ;
1068+ let http_client = reqwest:: Client :: builder ( )
1069+ //.danger_accept_invalid_certs(true) ? is tls unchecked :O
1070+ . build ( )
1071+ . unwrap ( ) ;
1072+ // enroll client
1073+ let ( req, context) = enroller. extract_req ( ) . unwrap ( ) ;
1074+ let ohttp_response = http_client
1075+ . post ( req. url )
1076+ . body ( req. body )
1077+ . send ( )
1078+ . await
1079+ . unwrap ( ) ;
1080+ let ohttp_response = ohttp_response. bytes ( ) . await . unwrap ( ) ;
1081+ let enrolled = enroller
1082+ . process_res ( ohttp_response. as_ref ( ) , context)
1083+ . map_err ( |e| anyhow ! ( "parse error {}" , e) )
1084+ . unwrap ( ) ;
1085+ let pj_uri = enrolled. fallback_target ( ) ;
1086+ log_debug ! ( self . logger, "{pj_uri}" ) ;
1087+ let wallet = self . wallet . clone ( ) ;
1088+ // run await payjoin task in the background as it'll keep polling the relay
1089+ wasm_bindgen_futures:: spawn_local ( async move {
1090+ let wallet = wallet. clone ( ) ;
1091+ let pj_txid = Self :: receive_payjoin ( wallet, enrolled) . await . unwrap ( ) ;
1092+ log:: info!( "Received payjoin txid: {}" , pj_txid) ;
1093+ } ) ;
1094+ Some ( pj_uri)
1095+ } ;
1096+
10551097 Ok ( MutinyBip21RawMaterials {
10561098 address,
10571099 invoice,
10581100 btc_amount : amount. map ( |amount| bitcoin:: Amount :: from_sat ( amount) . to_btc ( ) . to_string ( ) ) ,
10591101 labels,
1102+ pj,
10601103 } )
10611104 }
10621105
1106+ /// Poll the payjoin relay to maintain a payjoin session and create a payjoin proposal.
1107+ pub async fn receive_payjoin (
1108+ wallet : Arc < OnChainWallet < S > > ,
1109+ mut enrolled : payjoin:: receive:: Enrolled ,
1110+ ) -> Result < Txid , MutinyError > {
1111+ let http_client = reqwest:: Client :: builder ( )
1112+ //.danger_accept_invalid_certs(true) ? is tls unchecked :O
1113+ . build ( )
1114+ . unwrap ( ) ;
1115+ let proposal: payjoin:: receive:: UncheckedProposal =
1116+ Self :: poll_for_fallback_psbt ( & http_client, & mut enrolled)
1117+ . await
1118+ . unwrap ( ) ;
1119+ let payjoin_proposal = wallet. process_payjoin_proposal ( proposal) . unwrap ( ) ;
1120+
1121+ let ( req, ohttp_ctx) = payjoin_proposal
1122+ . extract_v2_req ( )
1123+ . unwrap ( ) ; // extraction failed
1124+ let res = http_client
1125+ . post ( req. url )
1126+ . body ( req. body )
1127+ . send ( )
1128+ . await
1129+ . unwrap ( ) ;
1130+ let res = res. bytes ( ) . await . unwrap ( ) ;
1131+ // enroll must succeed
1132+ let _res = payjoin_proposal
1133+ . deserialize_res ( res. to_vec ( ) , ohttp_ctx)
1134+ . unwrap ( ) ;
1135+ // convert from bitcoin 29 to 30
1136+ let txid = payjoin_proposal. psbt ( ) . clone ( ) . extract_tx ( ) . txid ( ) ;
1137+ let txid = Txid :: from_str ( & txid. to_string ( ) ) . unwrap ( ) ;
1138+ Ok ( txid)
1139+ }
1140+
1141+ async fn poll_for_fallback_psbt (
1142+ client : & reqwest:: Client ,
1143+ enroller : & mut payjoin:: receive:: Enrolled ,
1144+ ) -> Result < payjoin:: receive:: UncheckedProposal , ( ) > {
1145+ loop {
1146+ let ( req, context) = enroller. extract_req ( ) . unwrap ( ) ;
1147+ let ohttp_response = client
1148+ . post ( req. url )
1149+ . body ( req. body )
1150+ . send ( )
1151+ . await
1152+ . unwrap ( ) ;
1153+ let ohttp_response = ohttp_response. bytes ( ) . await . unwrap ( ) ;
1154+ let proposal = enroller
1155+ . process_res ( ohttp_response. as_ref ( ) , context)
1156+ . map_err ( |e| anyhow ! ( "parse error {}" , e) )
1157+ . unwrap ( ) ;
1158+ match proposal {
1159+ Some ( proposal) => return Ok ( proposal) ,
1160+ None => Self :: delay ( 5000 ) . await . unwrap ( ) ,
1161+ }
1162+ }
1163+ }
1164+
1165+ async fn delay ( millis : u32 ) -> Result < ( ) , wasm_bindgen:: JsValue > {
1166+ let promise = js_sys:: Promise :: new ( & mut |yes, _| {
1167+ let win = web_sys:: window ( ) . expect ( "should have a Window" ) ;
1168+ win. set_timeout_with_callback_and_timeout_and_arguments_0 ( & yes, millis as i32 )
1169+ . expect ( "should set a timeout" ) ;
1170+ } ) ;
1171+
1172+ wasm_bindgen_futures:: JsFuture :: from ( promise) . await ?;
1173+ Ok ( ( ) )
1174+ }
1175+
10631176 /// Sends an on-chain transaction to the given address.
10641177 /// The amount is in satoshis and the fee rate is in sat/vbyte.
10651178 ///
0 commit comments