@@ -31,10 +31,11 @@ use crate::{gossip::*, scorer::HubPreferentialScorer};
31
31
use crate :: { labels:: LabelStorage , subscription:: MutinySubscriptionClient } ;
32
32
use anyhow:: anyhow;
33
33
use bdk:: chain:: { BlockId , ConfirmationTime } ;
34
- use bdk:: { wallet:: AddressIndex , LocalUtxo } ;
34
+ use bdk:: { wallet:: AddressIndex , FeeRate , LocalUtxo } ;
35
35
use bitcoin:: blockdata:: script;
36
36
use bitcoin:: hashes:: hex:: ToHex ;
37
37
use bitcoin:: hashes:: { sha256, Hash } ;
38
+ use bitcoin:: psbt:: PartiallySignedTransaction ;
38
39
use bitcoin:: secp256k1:: { rand, PublicKey , Secp256k1 , SecretKey } ;
39
40
use bitcoin:: util:: bip32:: { DerivationPath , ExtendedPrivKey } ;
40
41
use bitcoin:: { Address , Network , OutPoint , Transaction , Txid } ;
@@ -62,6 +63,7 @@ use nostr::{EventBuilder, Keys, Kind, Tag, TagKind};
62
63
use reqwest:: Client ;
63
64
use serde:: { Deserialize , Serialize } ;
64
65
use serde_json:: Value ;
66
+ use std:: io:: Cursor ;
65
67
use std:: str:: FromStr ;
66
68
use std:: sync:: atomic:: { AtomicBool , Ordering } ;
67
69
use std:: { collections:: HashMap , ops:: Deref , sync:: Arc } ;
@@ -162,6 +164,7 @@ pub struct MutinyBip21RawMaterials {
162
164
pub invoice : Option < Bolt11Invoice > ,
163
165
pub btc_amount : Option < String > ,
164
166
pub labels : Vec < String > ,
167
+ pub pj : Option < String > ,
165
168
}
166
169
167
170
#[ derive( Debug , Serialize , Deserialize , Clone , Eq , PartialEq ) ]
@@ -1009,7 +1012,7 @@ impl<S: MutinyStorage> NodeManager<S> {
1009
1012
Err ( MutinyError :: WalletOperationFailed )
1010
1013
}
1011
1014
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 .
1013
1016
/// The lightning invoice may return errors related to the LSP. Check the error and
1014
1017
/// fallback to `get_new_address` and warn the user that Lightning is not available.
1015
1018
///
@@ -1052,14 +1055,125 @@ impl<S: MutinyStorage> NodeManager<S> {
1052
1055
return Err ( MutinyError :: WalletOperationFailed ) ;
1053
1056
} ;
1054
1057
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
+
1055
1099
Ok ( MutinyBip21RawMaterials {
1056
1100
address,
1057
1101
invoice,
1058
1102
btc_amount : amount. map ( |amount| bitcoin:: Amount :: from_sat ( amount) . to_btc ( ) . to_string ( ) ) ,
1059
1103
labels,
1104
+ pj,
1060
1105
} )
1061
1106
}
1062
1107
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
+
1063
1177
/// Sends an on-chain transaction to the given address.
1064
1178
/// The amount is in satoshis and the fee rate is in sat/vbyte.
1065
1179
///
0 commit comments