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

Commit 5bc812f

Browse files
committed
Send v2 payjoin
1 parent 6063e6a commit 5bc812f

File tree

2 files changed

+93
-44
lines changed

2 files changed

+93
-44
lines changed

mutiny-core/src/nodemanager.rs

Lines changed: 91 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ use payjoin::Uri;
5252
use reqwest::Client;
5353
use serde::{Deserialize, Serialize};
5454
use serde_json::Value;
55-
use std::io::Cursor;
5655
use std::str::FromStr;
5756
use std::sync::atomic::{AtomicBool, Ordering};
5857
use std::{collections::HashMap, ops::Deref, sync::Arc};
@@ -810,75 +809,126 @@ impl<S: MutinyStorage> NodeManager<S> {
810809
Ok(enroller.process_res(ohttp_response.as_ref(), context)?)
811810
}
812811

813-
// Send v1 payjoin request
812+
// Send v2 payjoin request
814813
pub async fn send_payjoin(
815814
&self,
816815
uri: Uri<'_, payjoin::bitcoin::address::NetworkChecked>,
817816
amount: u64,
818817
labels: Vec<String>,
819818
fee_rate: Option<f32>,
820-
) -> Result<Txid, MutinyError> {
819+
) -> Result<(), MutinyError> {
821820
let address = Address::from_str(&uri.address.to_string())
822821
.map_err(|_| MutinyError::PayjoinConfigError)?;
823822
let original_psbt = self.wallet.create_signed_psbt(address, amount, fee_rate)?;
824-
823+
// TODO ensure this creates a pending tx in the UI. Ensure locked UTXO.
825824
let fee_rate = if let Some(rate) = fee_rate {
826825
FeeRate::from_sat_per_vb(rate)
827826
} else {
828827
let sat_per_kwu = self.fee_estimator.get_normal_fee_rate();
829828
FeeRate::from_sat_per_kwu(sat_per_kwu as f32)
830829
};
831830
let fee_rate = payjoin::bitcoin::FeeRate::from_sat_per_kwu(fee_rate.sat_per_kwu() as u64);
832-
let original_psbt = payjoin::bitcoin::psbt::PartiallySignedTransaction::from_str(
831+
let original_psbt_30 = payjoin::bitcoin::psbt::PartiallySignedTransaction::from_str(
833832
&original_psbt.to_string(),
834833
)
835834
.map_err(|_| MutinyError::PayjoinConfigError)?;
836835
log_debug!(self.logger, "Creating payjoin request");
837-
let (req, ctx) =
838-
payjoin::send::RequestBuilder::from_psbt_and_uri(original_psbt.clone(), uri)
839-
.unwrap()
840-
.build_recommended(fee_rate)
841-
.map_err(|_| MutinyError::PayjoinConfigError)?
842-
.extract_v1()?;
843-
844-
let client = Client::builder()
845-
.build()
836+
let req_ctx = payjoin::send::RequestBuilder::from_psbt_and_uri(original_psbt_30, uri)
837+
.unwrap()
838+
.build_recommended(fee_rate)
846839
.map_err(|_| MutinyError::PayjoinConfigError)?;
840+
self.spawn_payjoin_sender(labels, original_psbt, req_ctx)
841+
.await;
842+
Ok(())
843+
}
847844

848-
log_debug!(self.logger, "Sending payjoin request");
849-
let res = client
850-
.post(req.url)
851-
.body(req.body)
852-
.header("Content-Type", "text/plain")
853-
.send()
854-
.await
855-
.map_err(|_| MutinyError::PayjoinCreateRequest)?
856-
.bytes()
857-
.await
858-
.map_err(|_| MutinyError::PayjoinCreateRequest)?;
845+
async fn spawn_payjoin_sender(
846+
&self,
847+
labels: Vec<String>,
848+
original_psbt: bitcoin::psbt::Psbt,
849+
req_ctx: payjoin::send::RequestContext,
850+
) {
851+
let wallet = self.wallet.clone();
852+
let logger = self.logger.clone();
853+
let stop = self.stop.clone();
854+
utils::spawn(async move {
855+
let proposal_psbt = match Self::poll_payjoin_sender(stop, req_ctx).await {
856+
Ok(psbt) => psbt,
857+
Err(e) => {
858+
log_error!(logger, "Error polling payjoin sender: {e}");
859+
return;
860+
}
861+
};
859862

860-
let mut cursor = Cursor::new(res.to_vec());
863+
if let Err(e) = Self::handle_proposal_psbt(
864+
logger.clone(),
865+
wallet,
866+
original_psbt,
867+
proposal_psbt,
868+
labels,
869+
)
870+
.await
871+
{
872+
log_error!(logger, "Error handling payjoin proposal: {e}");
873+
}
874+
});
875+
}
861876

862-
log_debug!(self.logger, "Processing payjoin response");
863-
let proposal_psbt = ctx.process_response(&mut cursor).map_err(|e| {
864-
log_error!(self.logger, "Error processing payjoin response: {e}");
865-
e
866-
})?;
877+
async fn poll_payjoin_sender(
878+
stop: Arc<AtomicBool>,
879+
req_ctx: payjoin::send::RequestContext,
880+
) -> Result<bitcoin::psbt::Psbt, MutinyError> {
881+
let http = Client::builder()
882+
.build()
883+
.map_err(|_| MutinyError::Other(anyhow!("failed to build http client")))?;
884+
loop {
885+
if stop.load(Ordering::Relaxed) {
886+
return Err(MutinyError::NotRunning);
887+
}
867888

868-
// convert to pdk types
869-
let original_psbt = PartiallySignedTransaction::from_str(&original_psbt.to_string())
870-
.map_err(|_| MutinyError::PayjoinConfigError)?;
871-
let proposal_psbt = PartiallySignedTransaction::from_str(&proposal_psbt.to_string())
872-
.map_err(|_| MutinyError::PayjoinConfigError)?;
889+
let (req, ctx) = req_ctx
890+
.extract_v2(crate::payjoin::OHTTP_RELAYS[0])
891+
.map_err(|_| MutinyError::PayjoinConfigError)?;
892+
let response = http
893+
.post(req.url)
894+
.body(req.body)
895+
.send()
896+
.await
897+
.map_err(|_| MutinyError::Other(anyhow!("failed to parse payjoin response")))?;
898+
let mut reader =
899+
std::io::Cursor::new(response.bytes().await.map_err(|_| {
900+
MutinyError::Other(anyhow!("failed to parse payjoin response"))
901+
})?);
902+
903+
println!("Sent fallback transaction");
904+
let psbt = ctx
905+
.process_response(&mut reader)
906+
.map_err(MutinyError::PayjoinResponse)?;
907+
if let Some(psbt) = psbt {
908+
let psbt = bitcoin::psbt::Psbt::from_str(&psbt.to_string())
909+
.map_err(|_| MutinyError::Other(anyhow!("psbt conversion failed")))?;
910+
return Ok(psbt);
911+
} else {
912+
log::info!("No response yet for POST payjoin request, retrying some seconds");
913+
std::thread::sleep(std::time::Duration::from_secs(5));
914+
}
915+
}
916+
}
873917

874-
log_debug!(self.logger, "Sending payjoin..");
875-
let tx = self
876-
.wallet
918+
async fn handle_proposal_psbt(
919+
logger: Arc<MutinyLogger>,
920+
wallet: Arc<OnChainWallet<S>>,
921+
original_psbt: PartiallySignedTransaction,
922+
proposal_psbt: PartiallySignedTransaction,
923+
labels: Vec<String>,
924+
) -> Result<Txid, MutinyError> {
925+
log_debug!(logger, "Sending payjoin..");
926+
let tx = wallet
877927
.send_payjoin(original_psbt, proposal_psbt, labels)
878928
.await?;
879929
let txid = tx.txid();
880-
self.broadcast_transaction(tx).await?;
881-
log_debug!(self.logger, "Payjoin broadcast! TXID: {txid}");
930+
wallet.broadcast_transaction(tx).await?;
931+
log_info!(logger, "Payjoin broadcast! TXID: {txid}");
882932
Ok(txid)
883933
}
884934

mutiny-wasm/src/lib.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ impl MutinyWallet {
485485
amount: u64, /* override the uri amount if desired */
486486
labels: Vec<String>,
487487
fee_rate: Option<f32>,
488-
) -> Result<String, MutinyJsError> {
488+
) -> Result<(), MutinyJsError> {
489489
// I know walia parses `pj=` and `pjos=` but payjoin::Uri parses the whole bip21 uri
490490
let pj_uri = payjoin::Uri::try_from(payjoin_uri.as_str())
491491
.map_err(|_| MutinyJsError::InvalidArgumentsError)?
@@ -494,8 +494,7 @@ impl MutinyWallet {
494494
.inner
495495
.node_manager
496496
.send_payjoin(pj_uri, amount, labels, fee_rate)
497-
.await?
498-
.to_string())
497+
.await?)
499498
}
500499

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

0 commit comments

Comments
 (0)