• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

payjoin / rust-payjoin / 29448737091

15 Jul 2026 08:33PM UTC coverage: 86.187% (+0.2%) from 85.938%
29448737091

Pull #1707

github

web-flow
Merge bb5eb7fc2 into 1da296b10
Pull Request #1707: Prepare release-ready C# NuGet package workflow

13758 of 15963 relevant lines covered (86.19%)

345.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.1
/payjoin-cli/src/app/mod.rs
1
use std::collections::HashMap;
2

3
use anyhow::Result;
4
use payjoin::bitcoin::psbt::Psbt;
5
use payjoin::bitcoin::{self, Address, Amount, FeeRate};
6
use tokio::signal;
7
use tokio::sync::watch;
8

9
pub mod config;
10
pub mod wallet;
11
use crate::app::config::Config;
12
use crate::app::wallet::BitcoindWallet;
13
#[cfg(feature = "v2")]
14
use crate::cli::Role;
15
#[cfg(feature = "v2")]
16
use crate::db::v2::SessionId;
17

18
#[cfg(feature = "v1")]
19
pub(crate) mod v1;
20
#[cfg(feature = "v2")]
21
pub(crate) mod v2;
22

23
#[async_trait::async_trait]
24
pub trait App: Send + Sync {
25
    async fn new(config: Config) -> Result<Self>
26
    where
27
        Self: Sized;
28
    fn wallet(&self) -> BitcoindWallet;
29
    async fn send_payjoin(&self, bip21: &str, fee_rate: FeeRate) -> Result<()>;
30
    async fn receive_payjoin(&self, amount: Amount) -> Result<()>;
31
    #[cfg(feature = "v2")]
32
    async fn resume_payjoins(&self, session_id: Option<SessionId>) -> Result<()>;
33
    #[cfg(feature = "v2")]
34
    async fn history(&self) -> Result<()>;
35
    #[cfg(feature = "v2")]
36
    async fn cancel(
37
        &self,
38
        session_id: SessionId,
39
        no_broadcast: bool,
40
        role: Option<Role>,
41
    ) -> Result<()>;
42

43
    fn create_original_psbt(
6✔
44
        &self,
6✔
45
        address: &Address,
6✔
46
        amount: Amount,
6✔
47
        fee_rate: FeeRate,
6✔
48
    ) -> Result<Psbt> {
6✔
49
        // Check if wallet has spendable UTXOs before attempting to create PSBT
50
        if !self.wallet().has_spendable_utxos()? {
6✔
51
            return Err(anyhow::anyhow!(
×
52
                "No spendable UTXOs available in wallet. Please ensure your wallet has confirmed funds."
×
53
            ));
×
54
        }
6✔
55

56
        // wallet_create_funded_psbt requires a HashMap<address: String, Amount>
57
        let mut outputs = HashMap::with_capacity(1);
6✔
58
        outputs.insert(address.to_string(), amount);
6✔
59

60
        self.wallet().create_psbt(outputs, fee_rate, true)
6✔
61
    }
6✔
62

63
    fn process_pj_response(&self, psbt: Psbt) -> Result<bitcoin::Txid> {
4✔
64
        tracing::trace!("Proposed psbt: {psbt:#?}");
4✔
65

66
        let signed = self.wallet().process_psbt(&psbt)?;
4✔
67
        let tx = signed.extract_tx()?;
4✔
68

69
        let txid = self.wallet().broadcast_tx(&tx)?;
4✔
70
        Ok(txid)
4✔
71
    }
4✔
72
}
73

74
#[cfg(feature = "_manual-tls")]
75
fn http_agent(config: &Config) -> Result<reqwest::Client> {
20✔
76
    Ok(http_agent_builder(config.root_certificate.as_ref())?.build()?)
20✔
77
}
20✔
78

79
#[cfg(not(feature = "_manual-tls"))]
80
fn http_agent(_config: &Config) -> Result<reqwest::Client> {
81
    Ok(reqwest::Client::builder().http1_only().build()?)
82
}
83

84
#[cfg(feature = "_manual-tls")]
85
fn http_agent_builder(
20✔
86
    root_cert_path: Option<&std::path::PathBuf>,
20✔
87
) -> Result<reqwest::ClientBuilder> {
20✔
88
    let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().http1_only();
20✔
89

90
    if let Some(root_cert_path) = root_cert_path {
20✔
91
        let cert_der = std::fs::read(root_cert_path)?;
20✔
92
        builder =
20✔
93
            builder.add_root_certificate(reqwest::tls::Certificate::from_der(cert_der.as_slice())?)
20✔
94
    }
×
95
    Ok(builder)
20✔
96
}
20✔
97

98
async fn handle_interrupt(tx: watch::Sender<()>) {
24✔
99
    if let Err(e) = signal::ctrl_c().await {
24✔
100
        eprintln!("Error setting up Ctrl-C handler: {e}");
×
101
    }
15✔
102
    let _ = tx.send(());
15✔
103
}
15✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc