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

payjoin / rust-payjoin / 26173671454

20 May 2026 03:47PM UTC coverage: 84.348% (-0.9%) from 85.284%
26173671454

Pull #1514

github

web-flow
Merge b77bb7636 into 17a3b6889
Pull Request #1514: [WIP] Add AS-aware relay selection

551 of 814 new or added lines in 5 files covered. (67.69%)

7 existing lines in 2 files now uncovered.

12087 of 14330 relevant lines covered (84.35%)

378.93 hits per line

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

87.8
/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::db::v2::SessionId;
15

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

21
#[async_trait::async_trait]
22
pub trait App: Send + Sync {
23
    async fn new(config: Config) -> Result<Self>
24
    where
25
        Self: Sized;
26
    fn wallet(&self) -> BitcoindWallet;
27
    async fn send_payjoin(&self, bip21: &str, fee_rate: FeeRate) -> Result<()>;
28
    async fn receive_payjoin(&self, amount: Amount) -> Result<()>;
29
    #[cfg(feature = "v2")]
30
    async fn resume_payjoins(&self) -> Result<()>;
31
    #[cfg(feature = "v2")]
32
    async fn history(&self) -> Result<()>;
33
    #[cfg(feature = "v2")]
34
    async fn fallback_sender(&self, session_id: SessionId) -> Result<()>;
35

36
    fn create_original_psbt(
5✔
37
        &self,
5✔
38
        address: &Address,
5✔
39
        amount: Amount,
5✔
40
        fee_rate: FeeRate,
5✔
41
    ) -> Result<Psbt> {
5✔
42
        // Check if wallet has spendable UTXOs before attempting to create PSBT
43
        if !self.wallet().has_spendable_utxos()? {
5✔
44
            return Err(anyhow::anyhow!(
×
45
                "No spendable UTXOs available in wallet. Please ensure your wallet has confirmed funds."
×
46
            ));
×
47
        }
5✔
48

49
        // wallet_create_funded_psbt requires a HashMap<address: String, Amount>
50
        let mut outputs = HashMap::with_capacity(1);
5✔
51
        outputs.insert(address.to_string(), amount);
5✔
52

53
        self.wallet().create_psbt(outputs, fee_rate, true)
5✔
54
    }
5✔
55

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

59
        let signed = self.wallet().process_psbt(&psbt)?;
4✔
60
        let tx = signed.extract_tx()?;
4✔
61

62
        let txid = self.wallet().broadcast_tx(&tx)?;
4✔
63

64
        println!("Payjoin sent. TXID: {txid}");
4✔
65
        Ok(txid)
4✔
66
    }
4✔
67
}
68

69
#[cfg(feature = "v1")]
70
fn http_agent(config: &Config) -> Result<reqwest::Client> {
3✔
71
    Ok(http_client_builder(config)?.build()?)
3✔
72
}
3✔
73

74
pub(crate) fn http_client_builder(config: &Config) -> Result<reqwest::ClientBuilder> {
14✔
75
    #[cfg(feature = "_manual-tls")]
76
    {
77
        let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().http1_only();
14✔
78
        if let Some(root_cert_path) = config.root_certificate.as_ref() {
14✔
79
            let cert_der = std::fs::read(root_cert_path)?;
14✔
80
            builder = builder
14✔
81
                .add_root_certificate(reqwest::tls::Certificate::from_der(cert_der.as_slice())?);
14✔
NEW
82
        }
×
83
        Ok(builder)
14✔
84
    }
85

86
    #[cfg(not(feature = "_manual-tls"))]
87
    {
88
        let _ = config;
89
        Ok(reqwest::Client::builder().http1_only())
90
    }
91
}
14✔
92

93
async fn handle_interrupt(tx: watch::Sender<()>) {
16✔
94
    if let Err(e) = signal::ctrl_c().await {
16✔
95
        eprintln!("Error setting up Ctrl-C handler: {e}");
×
96
    }
13✔
97
    let _ = tx.send(());
13✔
98
}
13✔
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