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

Commit 66124c2

Browse files
committed
Send v2 payjoin
1 parent 3220c92 commit 66124c2

File tree

1 file changed

+90
-44
lines changed

1 file changed

+90
-44
lines changed

mutiny-core/src/nodemanager.rs

Lines changed: 90 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ use reqwest::Client;
6262
use serde::{Deserialize, Serialize};
6363
use serde_json::Value;
6464
use std::cmp::max;
65-
use std::io::Cursor;
6665
use std::str::FromStr;
6766
use std::sync::atomic::{AtomicBool, Ordering};
6867
#[cfg(not(target_arch = "wasm32"))]
@@ -755,7 +754,7 @@ impl<S: MutinyStorage> NodeManager<S> {
755754
))
756755
}
757756

758-
// Send v1 payjoin request
757+
// Send v2 payjoin request
759758
pub async fn send_payjoin(
760759
&self,
761760
uri: Uri<'_, NetworkUnchecked>,
@@ -768,65 +767,112 @@ impl<S: MutinyStorage> NodeManager<S> {
768767
.map_err(|_| MutinyError::IncorrectNetwork)?;
769768
let address = uri.address.clone();
770769
let original_psbt = self.wallet.create_signed_psbt(address, amount, fee_rate)?;
771-
770+
// TODO ensure this creates a pending tx in the UI. Ensure locked UTXO.
772771
let fee_rate = if let Some(rate) = fee_rate {
773772
FeeRate::from_sat_per_vb(rate)
774773
} else {
775774
let sat_per_kwu = self.fee_estimator.get_normal_fee_rate();
776775
FeeRate::from_sat_per_kwu(sat_per_kwu as f32)
777776
};
778777
let fee_rate = payjoin::bitcoin::FeeRate::from_sat_per_kwu(fee_rate.sat_per_kwu() as u64);
779-
let original_psbt = payjoin::bitcoin::psbt::PartiallySignedTransaction::from_str(
780-
&original_psbt.to_string(),
781-
)
782-
.map_err(|_| MutinyError::WalletOperationFailed)?;
783778
log_debug!(self.logger, "Creating payjoin request");
784-
let (req, ctx) =
785-
payjoin::send::RequestBuilder::from_psbt_and_uri(original_psbt.clone(), uri)
786-
.unwrap()
787-
.build_recommended(fee_rate)
788-
.map_err(|_| MutinyError::PayjoinCreateRequest)?
789-
.extract_v1()?;
779+
let req_ctx = payjoin::send::RequestBuilder::from_psbt_and_uri(original_psbt.clone(), uri)
780+
.map_err(|_| MutinyError::PayjoinCreateRequest)?
781+
.build_recommended(fee_rate)
782+
.map_err(|_| MutinyError::PayjoinConfigError)?;
783+
self.spawn_payjoin_sender(labels, original_psbt.clone(), req_ctx)
784+
.await;
785+
Ok(original_psbt.extract_tx().txid())
786+
}
790787

791-
let client = Client::builder()
792-
.build()
793-
.map_err(|e| MutinyError::Other(e.into()))?;
788+
async fn spawn_payjoin_sender(
789+
&self,
790+
labels: Vec<String>,
791+
original_psbt: bitcoin::psbt::Psbt,
792+
req_ctx: payjoin::send::RequestContext,
793+
) {
794+
let wallet = self.wallet.clone();
795+
let logger = self.logger.clone();
796+
let stop = self.stop.clone();
797+
utils::spawn(async move {
798+
let proposal_psbt = match Self::poll_payjoin_sender(stop, req_ctx).await {
799+
Ok(psbt) => psbt,
800+
Err(e) => {
801+
log_error!(logger, "Error polling payjoin sender: {e}");
802+
return;
803+
}
804+
};
794805

795-
log_debug!(self.logger, "Sending payjoin request");
796-
let res = client
797-
.post(req.url)
798-
.body(req.body)
799-
.header("Content-Type", "text/plain")
800-
.send()
801-
.await
802-
.map_err(|_| MutinyError::PayjoinCreateRequest)?
803-
.bytes()
806+
if let Err(e) = Self::handle_proposal_psbt(
807+
logger.clone(),
808+
wallet,
809+
original_psbt,
810+
proposal_psbt,
811+
labels,
812+
)
804813
.await
805-
.map_err(|_| MutinyError::PayjoinCreateRequest)?;
806-
807-
let mut cursor = Cursor::new(res.to_vec());
814+
{
815+
// Ensure ResponseError is logged with debug formatting
816+
log_error!(logger, "Error handling payjoin proposal: {:?}", e);
817+
}
818+
});
819+
}
808820

809-
log_debug!(self.logger, "Processing payjoin response");
810-
let proposal_psbt = ctx.process_response(&mut cursor).map_err(|e| {
811-
// unrecognized error contents may only appear in debug logs and will not Display
812-
log_debug!(self.logger, "Payjoin response error: {:?}", e);
813-
e
814-
})?;
821+
async fn poll_payjoin_sender(
822+
stop: Arc<AtomicBool>,
823+
mut req_ctx: payjoin::send::RequestContext,
824+
) -> Result<bitcoin::psbt::Psbt, MutinyError> {
825+
let http = Client::builder()
826+
.build()
827+
.map_err(|_| MutinyError::Other(anyhow!("failed to build http client")))?;
828+
loop {
829+
if stop.load(Ordering::Relaxed) {
830+
return Err(MutinyError::NotRunning);
831+
}
815832

816-
// convert to pdk types
817-
let original_psbt = PartiallySignedTransaction::from_str(&original_psbt.to_string())
818-
.map_err(|_| MutinyError::PayjoinConfigError)?;
819-
let proposal_psbt = PartiallySignedTransaction::from_str(&proposal_psbt.to_string())
820-
.map_err(|_| MutinyError::PayjoinConfigError)?;
833+
let (req, ctx) = req_ctx
834+
.extract_v2(crate::payjoin::OHTTP_RELAYS[0].to_owned())
835+
.map_err(|_| MutinyError::PayjoinConfigError)?;
836+
let response = http
837+
.post(req.url)
838+
.header("Content-Type", "message/ohttp-req")
839+
.body(req.body)
840+
.send()
841+
.await
842+
.map_err(|_| MutinyError::Other(anyhow!("failed to parse payjoin response")))?;
843+
let mut reader =
844+
std::io::Cursor::new(response.bytes().await.map_err(|_| {
845+
MutinyError::Other(anyhow!("failed to parse payjoin response"))
846+
})?);
847+
848+
let psbt = ctx
849+
.process_response(&mut reader)
850+
.map_err(MutinyError::PayjoinResponse)?;
851+
if let Some(psbt) = psbt {
852+
let psbt = bitcoin::psbt::Psbt::from_str(&psbt.to_string())
853+
.map_err(|_| MutinyError::Other(anyhow!("psbt conversion failed")))?;
854+
return Ok(psbt);
855+
} else {
856+
log::info!("No response yet for POST payjoin request, retrying some seconds");
857+
std::thread::sleep(std::time::Duration::from_secs(5));
858+
}
859+
}
860+
}
821861

822-
log_debug!(self.logger, "Sending payjoin..");
823-
let tx = self
824-
.wallet
862+
async fn handle_proposal_psbt(
863+
logger: Arc<MutinyLogger>,
864+
wallet: Arc<OnChainWallet<S>>,
865+
original_psbt: PartiallySignedTransaction,
866+
proposal_psbt: PartiallySignedTransaction,
867+
labels: Vec<String>,
868+
) -> Result<Txid, MutinyError> {
869+
log_debug!(logger, "Sending payjoin..");
870+
let tx = wallet
825871
.send_payjoin(original_psbt, proposal_psbt, labels)
826872
.await?;
827873
let txid = tx.txid();
828-
self.broadcast_transaction(tx).await?;
829-
log_debug!(self.logger, "Payjoin broadcast! TXID: {txid}");
874+
wallet.broadcast_transaction(tx).await?;
875+
log_info!(logger, "Payjoin broadcast! TXID: {txid}");
830876
Ok(txid)
831877
}
832878

0 commit comments

Comments
 (0)