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

payjoin / rust-payjoin / 23595840742

26 Mar 2026 01:04PM UTC coverage: 84.044% (-0.08%) from 84.125%
23595840742

Pull #1445

github

web-flow
Merge 27b133bde into 49a680bfd
Pull Request #1445: Add locktime flag to payjoin-cli

10 of 23 new or added lines in 6 files covered. (43.48%)

165 existing lines in 5 files now uncovered.

10650 of 12672 relevant lines covered (84.04%)

416.5 hits per line

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

53.33
/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 serde::Deserialize;
7
use url::Url;
8

9
/// Locktime strategy for transaction creation.
10
#[derive(Debug, Clone)]
11
pub enum Locktime {
12
    /// Use the given absolute block height as the locktime.
13
    Value(u32),
14
    /// Set locktime to current block height + 1 (anti-fee-sniping).
15
    CurrentBlockHeight,
16
}
17

18
#[derive(Debug, Clone, Deserialize, Parser)]
19
pub struct Flags {
20
    #[arg(long = "bip77", help = "Use BIP77 (v2) protocol (default)", action = clap::ArgAction::SetTrue)]
21
    pub bip77: Option<bool>,
22
    #[arg(long = "bip78", help = "Use BIP78 (v1) protocol", action = clap::ArgAction::SetTrue)]
23
    pub bip78: Option<bool>,
24
}
25

26
#[derive(Debug, Parser)]
27
#[command(
28
    version = env!("CARGO_PKG_VERSION"),
29
    about = "Payjoin - bitcoin scaling, savings, and privacy by default",
30
    long_about = None,
31
    subcommand_required = true
32
)]
33
pub struct Cli {
34
    #[command(flatten)]
35
    pub flags: Flags,
36

37
    #[command(subcommand)]
38
    pub command: Commands,
39

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

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

46
    #[arg(
47
        long,
48
        short = 'r',
49
        num_args(1),
50
        help = "The URL of the Bitcoin RPC host, e.g. regtest default is http://localhost:18443"
51
    )]
52
    pub rpchost: Option<Url>,
53

54
    #[arg(
55
        long = "cookie-file",
56
        short = 'c',
57
        num_args(1),
58
        help = "Path to the cookie file of the bitcoin node"
59
    )]
60
    pub cookie_file: Option<PathBuf>,
61

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

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

68
    #[cfg(feature = "v1")]
69
    #[arg(long = "port", help = "The local port to listen on")]
70
    pub port: Option<u16>,
71

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

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

80
    #[cfg(feature = "v2")]
81
    #[arg(long = "ohttp-keys", help = "The ohttp key config file path", value_parser = value_parser!(PathBuf))]
82
    pub ohttp_keys: Option<PathBuf>,
83

84
    #[cfg(feature = "v2")]
85
    #[arg(long = "pj-directory", help = "The directory to store payjoin requests", value_parser = value_parser!(Url))]
86
    pub pj_directory: Option<Url>,
87

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

92
    #[cfg(feature = "_manual-tls")]
93
    #[arg(long = "certificate-key", help = "Specify the certificate private key", value_parser = value_parser!(PathBuf))]
94
    pub certificate_key: Option<PathBuf>,
95
}
96

97
#[derive(Subcommand, Debug)]
98
pub enum Commands {
99
    /// Send a payjoin payment
100
    Send {
101
        /// The `bitcoin:...` payjoin uri to send to
102
        #[arg(required = true)]
103
        bip21: String,
104

105
        /// Fee rate in sat/vB
106
        #[arg(required = true, short, long = "fee-rate", value_parser = parse_fee_rate_in_sat_per_vb)]
107
        fee_rate: FeeRate,
108

109
        /// Locktime: a u32 block height, or "current-block-height" for current height + 1
110
        #[arg(short, long = "locktime", value_parser = parse_locktime)]
111
        locktime: Option<Locktime>,
112
    },
113
    /// Receive a payjoin payment
114
    Receive {
115
        /// The amount to receive in satoshis
116
        #[arg(required = true, value_parser = parse_amount_in_sat)]
117
        amount: Amount,
118

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

123
        #[cfg(feature = "v1")]
124
        /// The local port to listen on
125
        #[arg(short, long = "port")]
126
        port: Option<u16>,
127

128
        #[cfg(feature = "v1")]
129
        /// The `pj=` endpoint to receive the payjoin request
130
        #[arg(long = "pj-endpoint", value_parser = value_parser!(Url))]
131
        pj_endpoint: Option<Url>,
132

133
        #[cfg(feature = "v2")]
134
        /// The directory to store payjoin requests
135
        #[arg(long = "pj-directory", value_parser = value_parser!(Url))]
136
        pj_directory: Option<Url>,
137

138
        #[cfg(feature = "v2")]
139
        /// The path to the ohttp keys file
140
        #[arg(long = "ohttp-keys", value_parser = value_parser!(PathBuf))]
141
        ohttp_keys: Option<PathBuf>,
142
    },
143
    /// Resume pending payjoins (BIP77/v2 only)
144
    #[cfg(feature = "v2")]
145
    Resume,
146
    #[cfg(feature = "v2")]
147
    /// Show payjoin session history
148
    History,
149
}
150

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

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

NEW
UNCOV
161
pub fn parse_locktime(s: &str) -> Result<Locktime, String> {
×
NEW
UNCOV
162
    if s == "current-block-height" {
×
NEW
UNCOV
163
        Ok(Locktime::CurrentBlockHeight)
×
164
    } else {
NEW
UNCOV
165
        s.parse::<u32>().map(Locktime::Value).map_err(|_| {
×
NEW
UNCOV
166
            format!("invalid locktime '{s}': expected a u32 or \"current-block-height\"")
×
NEW
UNCOV
167
        })
×
168
    }
NEW
UNCOV
169
}
×
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