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

Commit 363826d

Browse files
committed
Send v2 payjoin
1 parent d629f0c commit 363826d

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};
@@ -811,75 +810,126 @@ impl<S: MutinyStorage> NodeManager<S> {
811810
Ok(enroller.process_res(ohttp_response.as_ref(), context)?)
812811
}
813812

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

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

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

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

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

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

mutiny-wasm/src/lib.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ impl MutinyWallet {
483483
amount: u64, /* override the uri amount if desired */
484484
labels: Vec<String>,
485485
fee_rate: Option<f32>,
486-
) -> Result<String, MutinyJsError> {
486+
) -> Result<(), MutinyJsError> {
487487
// I know walia parses `pj=` and `pjos=` but payjoin::Uri parses the whole bip21 uri
488488
let pj_uri = payjoin::Uri::try_from(payjoin_uri.as_str())
489489
.map_err(|_| MutinyJsError::InvalidArgumentsError)?
@@ -492,8 +492,7 @@ impl MutinyWallet {
492492
.inner
493493
.node_manager
494494
.send_payjoin(pj_uri, amount, labels, fee_rate)
495-
.await?
496-
.to_string())
495+
.await?)
497496
}
498497

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

0 commit comments

Comments
 (0)