Skip to content
This repository was archived by the owner on Feb 3, 2025. It is now read-only.

Commit e7e9194

Browse files
committed
Send v2 payjoin
1 parent 015b970 commit e7e9194

File tree

2 files changed

+93
-43
lines changed

2 files changed

+93
-43
lines changed

mutiny-core/src/nodemanager.rs

Lines changed: 91 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -898,75 +898,126 @@ impl<S: MutinyStorage> NodeManager<S> {
898898
Ok(enroller.process_res(ohttp_response.as_ref(), context)?)
899899
}
900900

901-
// Send v1 payjoin request
901+
// Send v2 payjoin request
902902
pub async fn send_payjoin(
903903
&self,
904904
uri: Uri<'_, payjoin::bitcoin::address::NetworkChecked>,
905905
amount: u64,
906906
labels: Vec<String>,
907907
fee_rate: Option<f32>,
908-
) -> Result<Txid, MutinyError> {
908+
) -> Result<(), MutinyError> {
909909
let address = Address::from_str(&uri.address.to_string())
910910
.map_err(|_| MutinyError::PayjoinConfigError)?;
911911
let original_psbt = self.wallet.create_signed_psbt(address, amount, fee_rate)?;
912-
912+
// TODO ensure this creates a pending tx in the UI. Ensure locked UTXO.
913913
let fee_rate = if let Some(rate) = fee_rate {
914914
FeeRate::from_sat_per_vb(rate)
915915
} else {
916916
let sat_per_kwu = self.fee_estimator.get_normal_fee_rate();
917917
FeeRate::from_sat_per_kwu(sat_per_kwu as f32)
918918
};
919919
let fee_rate = payjoin::bitcoin::FeeRate::from_sat_per_kwu(fee_rate.sat_per_kwu() as u64);
920-
let original_psbt = payjoin::bitcoin::psbt::PartiallySignedTransaction::from_str(
920+
let original_psbt_30 = payjoin::bitcoin::psbt::PartiallySignedTransaction::from_str(
921921
&original_psbt.to_string(),
922922
)
923923
.map_err(|_| MutinyError::PayjoinConfigError)?;
924924
log_debug!(self.logger, "Creating payjoin request");
925-
let (req, ctx) =
926-
payjoin::send::RequestBuilder::from_psbt_and_uri(original_psbt.clone(), uri)
927-
.unwrap()
928-
.build_recommended(fee_rate)
929-
.map_err(|_| MutinyError::PayjoinConfigError)?
930-
.extract_v1()?;
931-
932-
let client = Client::builder()
933-
.build()
925+
let req_ctx = payjoin::send::RequestBuilder::from_psbt_and_uri(original_psbt_30, uri)
926+
.unwrap()
927+
.build_recommended(fee_rate)
934928
.map_err(|_| MutinyError::PayjoinConfigError)?;
929+
self.spawn_payjoin_sender(labels, original_psbt, req_ctx)
930+
.await;
931+
Ok(())
932+
}
935933

936-
log_debug!(self.logger, "Sending payjoin request");
937-
let res = client
938-
.post(req.url)
939-
.body(req.body)
940-
.header("Content-Type", "text/plain")
941-
.send()
942-
.await
943-
.map_err(|_| MutinyError::PayjoinCreateRequest)?
944-
.bytes()
945-
.await
946-
.map_err(|_| MutinyError::PayjoinCreateRequest)?;
934+
async fn spawn_payjoin_sender(
935+
&self,
936+
labels: Vec<String>,
937+
original_psbt: bitcoin::psbt::Psbt,
938+
req_ctx: payjoin::send::RequestContext,
939+
) {
940+
let wallet = self.wallet.clone();
941+
let logger = self.logger.clone();
942+
let stop = self.stop.clone();
943+
utils::spawn(async move {
944+
let proposal_psbt = match Self::poll_payjoin_sender(stop, req_ctx).await {
945+
Ok(psbt) => psbt,
946+
Err(e) => {
947+
log_error!(logger, "Error polling payjoin sender: {e}");
948+
return;
949+
}
950+
};
947951

948-
let mut cursor = Cursor::new(res.to_vec());
952+
if let Err(e) = Self::handle_proposal_psbt(
953+
logger.clone(),
954+
wallet,
955+
original_psbt,
956+
proposal_psbt,
957+
labels,
958+
)
959+
.await
960+
{
961+
log_error!(logger, "Error handling payjoin proposal: {e}");
962+
}
963+
});
964+
}
949965

950-
log_debug!(self.logger, "Processing payjoin response");
951-
let proposal_psbt = ctx.process_response(&mut cursor).map_err(|e| {
952-
log_error!(self.logger, "Error processing payjoin response: {e}");
953-
e
954-
})?;
966+
async fn poll_payjoin_sender(
967+
stop: Arc<AtomicBool>,
968+
req_ctx: payjoin::send::RequestContext,
969+
) -> Result<bitcoin::psbt::Psbt, MutinyError> {
970+
let http = Client::builder()
971+
.build()
972+
.map_err(|_| MutinyError::Other(anyhow!("failed to build http client")))?;
973+
loop {
974+
if stop.load(Ordering::Relaxed) {
975+
return Err(MutinyError::NotRunning);
976+
}
955977

956-
// convert to pdk types
957-
let original_psbt = PartiallySignedTransaction::from_str(&original_psbt.to_string())
958-
.map_err(|_| MutinyError::PayjoinConfigError)?;
959-
let proposal_psbt = PartiallySignedTransaction::from_str(&proposal_psbt.to_string())
960-
.map_err(|_| MutinyError::PayjoinConfigError)?;
978+
let (req, ctx) = req_ctx
979+
.extract_v2(&crate::payjoin::OHTTP_RELAYS[0])
980+
.map_err(|_| MutinyError::PayjoinConfigError)?;
981+
let response = http
982+
.post(req.url)
983+
.body(req.body)
984+
.send()
985+
.await
986+
.map_err(|_| MutinyError::Other(anyhow!("failed to parse payjoin response")))?;
987+
let mut reader =
988+
std::io::Cursor::new(response.bytes().await.map_err(|_| {
989+
MutinyError::Other(anyhow!("failed to parse payjoin response"))
990+
})?);
991+
992+
println!("Sent fallback transaction");
993+
let psbt = ctx
994+
.process_response(&mut reader)
995+
.map_err(MutinyError::PayjoinValidateResponse)?;
996+
if let Some(psbt) = psbt {
997+
let psbt = bitcoin::psbt::Psbt::from_str(&psbt.to_string())
998+
.map_err(|_| MutinyError::Other(anyhow!("psbt conversion failed")))?;
999+
return Ok(psbt);
1000+
} else {
1001+
log::info!("No response yet for POST payjoin request, retrying some seconds");
1002+
std::thread::sleep(std::time::Duration::from_secs(5));
1003+
}
1004+
}
1005+
}
9611006

962-
log_debug!(self.logger, "Sending payjoin..");
963-
let tx = self
964-
.wallet
1007+
async fn handle_proposal_psbt(
1008+
logger: Arc<MutinyLogger>,
1009+
wallet: Arc<OnChainWallet<S>>,
1010+
original_psbt: PartiallySignedTransaction,
1011+
proposal_psbt: PartiallySignedTransaction,
1012+
labels: Vec<String>,
1013+
) -> Result<Txid, MutinyError> {
1014+
log_debug!(logger, "Sending payjoin..");
1015+
let tx = wallet
9651016
.send_payjoin(original_psbt, proposal_psbt, labels)
9661017
.await?;
9671018
let txid = tx.txid();
968-
self.broadcast_transaction(tx).await?;
969-
log_debug!(self.logger, "Payjoin broadcast! TXID: {txid}");
1019+
wallet.broadcast_transaction(tx).await?;
1020+
log_info!(logger, "Payjoin broadcast! TXID: {txid}");
9701021
Ok(txid)
9711022
}
9721023

mutiny-wasm/src/lib.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ impl MutinyWallet {
469469
amount: u64, /* override the uri amount if desired */
470470
labels: Vec<String>,
471471
fee_rate: Option<f32>,
472-
) -> Result<String, MutinyJsError> {
472+
) -> Result<(), MutinyJsError> {
473473
// I know walia parses `pj=` and `pjos=` but payjoin::Uri parses the whole bip21 uri
474474
let pj_uri = payjoin::Uri::try_from(payjoin_uri.as_str())
475475
.map_err(|_| MutinyJsError::InvalidArgumentsError)?
@@ -478,8 +478,7 @@ impl MutinyWallet {
478478
.inner
479479
.node_manager
480480
.send_payjoin(pj_uri, amount, labels, fee_rate)
481-
.await?
482-
.to_string())
481+
.await?)
483482
}
484483

485484
/// Sweeps all the funds from the wallet to the given address.

0 commit comments

Comments
 (0)