Skip to main content

paralock_cli/
cli.rs

1//! The command line (clap).
2
3use std::net::SocketAddr;
4use std::path::PathBuf;
5
6use clap::{ArgAction, Args, Parser, Subcommand};
7use paralock::EvmChain;
8
9const ABOUT: &str = "Trustless swaps between shielded ZEC and stablecoins on EVM chains, and a Paralock market maker";
10
11const AFTER_HELP: &str = "\
12Configuration:
13  paralock.toml, from --config <file>, $PARALOCK_CONFIG, ./paralock.toml or
14  <state>/paralock.toml. Keys come from environment variables or key files
15  (key_env, key_file), never from the file itself unless allow_inline_keys.
16
17Devnet:
18  --devnet reads devnet.json from <checkout>/.paralock inside a Paralock
19  checkout, else $PARALOCK_HOME, else ~/.paralock. Starting and stopping the
20  devnet (Zcash regtest, anvil, contracts) stays in the npm CLI:
21  `npx paralock dev`, `npx paralock devnet up|status|down`.
22
23Exit codes:
24  0 success, 1 failure, 2 usage or configuration error, 3 a swap failed after
25  locking ZEC (refunded, or refundable with `paralock refund <id>`).
26
27Experimental and not audited. The Zcash leg is transparent on chain.";
28
29/// `paralock`: swap shielded ZEC for stablecoins and run a market maker.
30#[derive(Debug, Parser)]
31#[command(name = "paralock", version, about = ABOUT, after_help = AFTER_HELP, propagate_version = true)]
32pub struct Cli {
33    /// Options for every command.
34    #[command(flatten)]
35    pub global: GlobalArgs,
36    /// The command.
37    #[command(subcommand)]
38    pub command: Command,
39}
40
41/// Options for every command.
42#[derive(Debug, Args)]
43pub struct GlobalArgs {
44    /// Use the local devnet started by `npx paralock dev` (its devnet.json) instead of paralock.toml.
45    #[arg(long, global = true)]
46    pub devnet: bool,
47    /// The configuration file.
48    #[arg(long, short = 'c', global = true, value_name = "FILE")]
49    pub config: Option<PathBuf>,
50    /// Machine-readable output: one JSON document on stdout (newline-delimited records for `maker`).
51    #[arg(long, global = true)]
52    pub json: bool,
53    /// The maker to trade with (overrides maker_url / the devnet's maker).
54    #[arg(long, global = true, value_name = "URL")]
55    pub maker_url: Option<String>,
56    /// Devnet: use anvil dev account N (0-9) as this command's EVM account.
57    #[arg(long, global = true, value_name = "N")]
58    pub evm_account: Option<usize>,
59    /// Devnet: reach Zcash through lightwalletd instead of the node's JSON-RPC.
60    #[arg(long, global = true)]
61    pub lightwalletd: bool,
62    /// More logs on stderr (-v info, -vv debug). RUST_LOG overrides.
63    #[arg(long, short = 'v', global = true, action = ArgAction::Count)]
64    pub verbose: u8,
65}
66
67/// The commands.
68#[derive(Debug, Subcommand)]
69pub enum Command {
70    /// Check the Zcash backend, the EVM chains and contracts, your keys and balances, the maker and the price feed.
71    Doctor(DoctorArgs),
72    /// Ask the maker for a quote and check it (keys, chain id, token, windows, price), without swapping.
73    Quote(QuoteArgs),
74    /// Swap ZEC for stablecoins.
75    Swap(SwapArgs),
76    /// Pay an exact dollar amount to an address, from ZEC.
77    Pay(PayArgs),
78    /// List the swaps saved in the taker's store (unfinished ones by default).
79    Swaps(SwapsArgs),
80    /// Continue a saved swap after a crash or restart (claims if the maker's lock is there and still safe).
81    Resume(ResumeArgs),
82    /// Refund a saved swap's ZEC once its refund height is reached.
83    Refund(RefundArgs),
84    /// Run a market maker: the protocol's HTTP API with the SDK's server, no Node process.
85    Maker(MakerArgs),
86    /// Print the version.
87    Version,
88}
89
90fn chain(value: &str) -> Result<EvmChain, String> {
91    EvmChain::parse(&value.to_ascii_lowercase())
92        .ok_or_else(|| "expected tempo, base, ethereum, arbitrum, robinhood or hyperevm".to_owned())
93}
94
95/// `doctor`.
96#[derive(Debug, Args)]
97pub struct DoctorArgs {
98    /// Check the maker side (its keys, inventory and listen address) instead of the taker side.
99    #[arg(long)]
100    pub maker: bool,
101}
102
103/// `quote`.
104#[derive(Debug, Args)]
105#[command(group = clap::ArgGroup::new("amount").required(true).args(["zec", "usd"]))]
106pub struct QuoteArgs {
107    /// ZEC to sell (exact in).
108    #[arg(long, value_name = "ZEC")]
109    pub zec: Option<String>,
110    /// Dollars to receive (exact out, as for a payment).
111    #[arg(long, value_name = "USD")]
112    pub usd: Option<String>,
113    /// Where the stablecoins are paid (default: tempo, or the only configured chain).
114    #[arg(long, value_parser = chain)]
115    pub chain: Option<EvmChain>,
116    /// Private mode: adaptor signatures, no hash on either chain.
117    #[arg(long)]
118    pub private: bool,
119}
120
121/// Options shared by `swap` and `pay`.
122#[derive(Debug, Args)]
123pub struct SwapCommon {
124    /// Where the stablecoins are paid (default: tempo, or the only configured chain).
125    #[arg(long, value_parser = chain)]
126    pub chain: Option<EvmChain>,
127    /// Private mode: adaptor signatures, no hash on either chain.
128    #[arg(long)]
129    pub private: bool,
130    /// Let the relayer submit the claim (no gas needed on the receiving address).
131    #[arg(long)]
132    pub relay: bool,
133    /// If the swap fails after locking ZEC, don't wait for the refund height to refund it (do it later with
134    /// `paralock refund <id>`).
135    #[arg(long)]
136    pub no_refund: bool,
137    /// Don't ask for confirmation on mainnet.
138    #[arg(long, short = 'y')]
139    pub yes: bool,
140}
141
142/// `swap`.
143#[derive(Debug, Args)]
144pub struct SwapArgs {
145    /// ZEC to sell.
146    #[arg(long, value_name = "ZEC")]
147    pub zec: String,
148    /// Shared options.
149    #[command(flatten)]
150    pub common: SwapCommon,
151    /// Receive at a new address created for this swap (its key is saved next to the swap store).
152    #[arg(long)]
153    pub fresh_recipient: bool,
154    /// Only allow the standard amounts 0.1, 0.5, 1 and 5 ZEC.
155    #[arg(long)]
156    pub standard_only: bool,
157    /// Wait a random 0..MS milliseconds before claiming.
158    #[arg(long, value_name = "MS")]
159    pub jitter: Option<u64>,
160    /// Lock the ZEC and let the maker never lock: refund after the timeout (a demo of the refund path).
161    #[arg(long)]
162    pub refund_demo: bool,
163}
164
165/// `pay`.
166#[derive(Debug, Args)]
167pub struct PayArgs {
168    /// Dollars the recipient receives (exactly).
169    #[arg(long, value_name = "USD")]
170    pub usd: String,
171    /// The recipient's EVM address.
172    #[arg(long, value_name = "0x…")]
173    pub to: String,
174    /// A label for the payment (e.g. a merchant).
175    #[arg(long)]
176    pub label: Option<String>,
177    /// Shared options.
178    #[command(flatten)]
179    pub common: SwapCommon,
180}
181
182/// `swaps`.
183#[derive(Debug, Args)]
184pub struct SwapsArgs {
185    /// Include finished swaps.
186    #[arg(long)]
187    pub all: bool,
188}
189
190/// `resume`.
191#[derive(Debug, Args)]
192pub struct ResumeArgs {
193    /// The swap id (`paralock swaps` lists them).
194    pub id: String,
195    /// Submit the claim from your own account even if the swap was started with --relay.
196    #[arg(long)]
197    pub no_relay: bool,
198}
199
200/// `refund`.
201#[derive(Debug, Args)]
202pub struct RefundArgs {
203    /// The swap id (`paralock swaps` lists them).
204    pub id: String,
205    /// Fail instead of waiting when the refund height is not reached yet.
206    #[arg(long)]
207    pub no_wait: bool,
208}
209
210/// `maker`.
211#[derive(Debug, Args)]
212pub struct MakerArgs {
213    /// Address to listen on (default: maker.listen, or 127.0.0.1:7420).
214    #[arg(long, value_name = "ADDR", conflicts_with = "port")]
215    pub listen: Option<SocketAddr>,
216    /// Port to listen on, on 127.0.0.1.
217    #[arg(long)]
218    pub port: Option<u16>,
219    /// Devnet: never mine regtest blocks (by default the maker mines one when the chain stalls for 6 s, as when no
220    /// other maker runs).
221    #[arg(long)]
222    pub no_mine: bool,
223}