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

payjoin / rust-payjoin / 25466196017

06 May 2026 11:08PM UTC coverage: 85.045% (-0.1%) from 85.169%
25466196017

Pull #1516

github

web-flow
Merge 4b4680afc into 676ede97e
Pull Request #1516: Allow receiver configure session expiry.

2 of 23 new or added lines in 2 files covered. (8.7%)

11516 of 13541 relevant lines covered (85.05%)

399.35 hits per line

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

52.38
/payjoin-cli/src/cli/mod.rs
1
use std::path::PathBuf;
2

3
use clap::{value_parser, Parser, Subcommand};
4
use payjoin::bitcoin::amount::ParseAmountError;
5
use payjoin::bitcoin::{Amount, FeeRate};
6
use payjoin::Url;
7
use serde::Deserialize;
8

9
#[derive(Debug, Clone, Deserialize, Parser)]
10
pub struct Flags {
11
    #[arg(long = "bip77", help = "Use BIP77 (v2) protocol (default)", action = clap::ArgAction::SetTrue)]
12
    pub bip77: Option<bool>,
13
    #[arg(long = "bip78", help = "Use BIP78 (v1) protocol", action = clap::ArgAction::SetTrue)]
14
    pub bip78: Option<bool>,
15
}
16

17
#[derive(Debug, Parser)]
18
#[command(
19
    version = env!("CARGO_PKG_VERSION"),
20
    about = "Payjoin - bitcoin scaling, savings, and privacy by default",
21
    long_about = None,
22
    subcommand_required = true
23
)]
24
pub struct Cli {
25
    #[command(flatten)]
26
    pub flags: Flags,
27

28
    #[command(subcommand)]
29
    pub command: Commands,
30

31
    #[arg(long, short = 'd', help = "Sets a custom database path")]
32
    pub db_path: Option<PathBuf>,
33

34
    #[arg(long = "max-fee-rate", short = 'f', help = "The maximum fee rate to accept in sat/vB")]
35
    pub max_fee_rate: Option<FeeRate>,
36

37
    #[arg(
38
        long,
39
        short = 'r',
40
        num_args(1),
41
        help = "The URL of the Bitcoin RPC host, e.g. regtest default is http://localhost:18443"
42
    )]
43
    pub rpchost: Option<Url>,
44

45
    #[arg(
46
        long = "cookie-file",
47
        short = 'c',
48
        num_args(1),
49
        help = "Path to the cookie file of the bitcoin node"
50
    )]
51
    pub cookie_file: Option<PathBuf>,
52

53
    #[arg(long = "rpcuser", num_args(1), help = "The username for the bitcoin node")]
54
    pub rpcuser: Option<String>,
55

56
    #[arg(long = "rpcpassword", num_args(1), help = "The password for the bitcoin node")]
57
    pub rpcpassword: Option<String>,
58

59
    #[cfg(feature = "v1")]
60
    #[arg(long = "port", help = "The local port to listen on")]
61
    pub port: Option<u16>,
62

63
    #[cfg(feature = "v1")]
64
    #[arg(long = "pj-endpoint", help = "The `pj=` endpoint to receive the payjoin request", value_parser = value_parser!(Url))]
65
    pub pj_endpoint: Option<Url>,
66

67
    #[cfg(feature = "v2")]
68
    #[arg(long = "ohttp-relays", help = "One or more ohttp relay URLs, comma-separated", value_parser = value_parser!(Url), value_delimiter = ',', action = clap::ArgAction::Append)]
69
    pub ohttp_relays: Option<Vec<Url>>,
70

71
    #[cfg(feature = "v2")]
72
    #[arg(long = "ohttp-keys", help = "The ohttp key config file path", value_parser = value_parser!(PathBuf))]
73
    pub ohttp_keys: Option<PathBuf>,
74

75
    #[cfg(feature = "v2")]
76
    #[arg(long = "pj-directory", help = "The directory to store payjoin requests", value_parser = value_parser!(Url))]
77
    pub pj_directory: Option<Url>,
78

79
    #[cfg(feature = "_manual-tls")]
80
    #[arg(long = "root-certificate", help = "Specify a TLS certificate to be added as a root", value_parser = value_parser!(PathBuf))]
81
    pub root_certificate: Option<PathBuf>,
82

83
    #[cfg(feature = "_manual-tls")]
84
    #[arg(long = "certificate-key", help = "Specify the certificate private key", value_parser = value_parser!(PathBuf))]
85
    pub certificate_key: Option<PathBuf>,
86
}
87

88
#[derive(Subcommand, Debug)]
89
pub enum Commands {
90
    /// Send a payjoin payment
91
    Send {
92
        /// The `bitcoin:...` payjoin uri to send to
93
        #[arg(required = true)]
94
        bip21: String,
95

96
        /// Fee rate in sat/vB
97
        #[arg(required = true, short, long = "fee-rate", value_parser = parse_fee_rate_in_sat_per_vb)]
98
        fee_rate: FeeRate,
99
    },
100
    /// Receive a payjoin payment
101
    Receive {
102
        /// The amount to receive in satoshis
103
        #[arg(required = true, value_parser = parse_amount_in_sat)]
104
        amount: Amount,
105

106
        /// The maximum effective fee rate the receiver is willing to pay (in sat/vB)
107
        #[arg(short, long = "max-fee-rate", value_parser = parse_fee_rate_in_sat_per_vb)]
108
        max_fee_rate: Option<FeeRate>,
109

110
        #[cfg(feature = "v1")]
111
        /// The local port to listen on
112
        #[arg(short, long = "port")]
113
        port: Option<u16>,
114

115
        #[cfg(feature = "v1")]
116
        /// The `pj=` endpoint to receive the payjoin request
117
        #[arg(long = "pj-endpoint", value_parser = parse_boxed_url)]
118
        pj_endpoint: Option<Box<Url>>,
119

120
        #[cfg(feature = "v2")]
121
        /// The directory to store payjoin requests
122
        #[arg(long = "pj-directory", value_parser = parse_boxed_url)]
123
        pj_directory: Option<Box<Url>>,
124

125
        #[cfg(feature = "v2")]
126
        /// The path to the ohttp keys file
127
        #[arg(long = "ohttp-keys", value_parser = value_parser!(PathBuf))]
128
        ohttp_keys: Option<PathBuf>,
129

130
        #[cfg(feature = "v2")]
131
        /// Receive session lifetime in seconds (BIP77 mailbox session). Omit for the payjoin crate default.
132
        #[arg(long = "session-ttl", value_parser = parse_session_ttl)]
133
        session_ttl: Option<u64>,
134
    },
135
    /// Resume pending payjoins (BIP77/v2 only)
136
    #[cfg(feature = "v2")]
137
    Resume,
138
    #[cfg(feature = "v2")]
139
    /// Show payjoin session history
140
    History,
141
    #[cfg(feature = "v2")]
142
    /// Broadcast the original transaction for a sender session (BIP77/v2 only)
143
    Fallback {
144
        /// The session ID to broadcast the fallback transaction for
145
        #[arg(required = true)]
146
        session_id: i64,
147
    },
148
}
149

150
pub fn parse_amount_in_sat(s: &str) -> Result<Amount, ParseAmountError> {
5✔
151
    Amount::from_str_in(s, payjoin::bitcoin::Denomination::Satoshi)
5✔
152
}
5✔
153

154
pub fn parse_fee_rate_in_sat_per_vb(s: &str) -> Result<FeeRate, std::num::ParseFloatError> {
6✔
155
    let fee_rate_sat_per_vb: f32 = s.parse()?;
6✔
156
    let fee_rate_sat_per_kwu = fee_rate_sat_per_vb * 250.0_f32;
6✔
157
    Ok(FeeRate::from_sat_per_kwu(fee_rate_sat_per_kwu.ceil() as u64))
6✔
158
}
6✔
159

160
fn parse_boxed_url(s: &str) -> Result<Box<Url>, String> {
5✔
161
    s.parse::<Url>().map(Box::new).map_err(|e| e.to_string())
5✔
162
}
5✔
163

164
/// Parse a positive session TTL in seconds, bounded by u32::MAX
165
#[cfg(feature = "v2")]
NEW
166
fn parse_session_ttl(s: &str) -> Result<u64, String> {
×
NEW
167
    let secs: u64 = s.parse().map_err(|e: std::num::ParseIntError| e.to_string())?;
×
NEW
168
    if secs == 0 {
×
NEW
169
        return Err("session TTL must be greater than 0".to_string());
×
NEW
170
    }
×
NEW
171
    if secs > u32::MAX as u64 {
×
NEW
172
        return Err(format!("session TTL must be <= {} seconds", u32::MAX));
×
NEW
173
    }
×
NEW
174
    Ok(secs)
×
NEW
175
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc