Skip to main content

paralock_cli/
lib.rs

1//! # paralock-cli
2//!
3//! The `paralock` binary: swap shielded ZEC for stablecoins on EVM chains (Tempo, Base, Ethereum, Arbitrum,
4//! Robinhood Chain, HyperEVM) and run a [Paralock](https://paralock.xyz) market maker, without a Node process. It is
5//! built on the [`paralock`] Rust SDK and speaks the same wire protocol as the TypeScript SDK and the npm CLI, so it
6//! trades with TypeScript makers and serves TypeScript takers.
7//!
8//! ```text
9//! paralock doctor                              # check nodes, contracts, keys, balances, the maker, the price
10//! paralock quote --zec 0.5 --chain base        # a checked quote, nothing locked
11//! paralock swap --zec 0.5 --chain base --private
12//! paralock pay --usd 25 --to 0x… --chain tempo
13//! paralock swaps | resume <id> | refund <id>   # recovery from the swap store
14//! paralock maker                               # serve a maker (HTTP API, settlement loops)
15//! paralock --devnet swap --zec 0.1             # against the local devnet of `npx paralock dev`
16//! ```
17//!
18//! Configuration comes from a `paralock.toml` ([`config`]) whose keys are read from environment variables or key
19//! files, or, with `--devnet`, from the `devnet.json` the npm CLI writes. `--json` prints one JSON document per
20//! command for scripts. Exit codes are described on [`error::Exit`].
21//!
22//! The library is the binary's implementation; it is not a stable API. Embed [`paralock`] instead.
23//!
24//! > **Status:** experimental and not audited. In both modes the Zcash leg is transparent on chain, and the devnet is
25//! > not mainnet.
26
27pub mod amount;
28pub mod cli;
29pub mod commands;
30pub mod config;
31pub mod devnet;
32pub mod error;
33pub mod output;
34pub mod state;
35
36use std::io::IsTerminal as _;
37use std::process::ExitCode;
38
39use clap::Parser as _;
40
41use crate::cli::{Cli, Command};
42use crate::config::{Globals, Role};
43use crate::error::{CliResult, Exit};
44use crate::output::Output;
45
46/// The protocol version this binary speaks.
47pub const PROTOCOL_VERSION: &str = "1.1";
48
49/// Runs the binary: parses the arguments, runs the command and returns the exit code.
50pub fn main() -> ExitCode {
51    let cli = match Cli::try_parse() {
52        Ok(cli) => cli,
53        Err(e) => {
54            let _ = e.print();
55            return ExitCode::from(if e.use_stderr() { Exit::Usage.code() } else { Exit::Ok.code() });
56        }
57    };
58    init_tracing(&cli);
59    let out = Output { json: cli.global.json };
60    let runtime = match tokio::runtime::Builder::new_multi_thread().enable_all().build() {
61        Ok(runtime) => runtime,
62        Err(e) => {
63            out.error(&error::CliError::failed(format!("can't start the async runtime: {e}")));
64            return ExitCode::from(Exit::Failed.code());
65        }
66    };
67    let result = runtime.block_on(run(cli, out));
68    // Settlement loops and HTTP connections end with the runtime.
69    runtime.shutdown_timeout(std::time::Duration::from_secs(2));
70    match result {
71        Ok(exit) => ExitCode::from(exit.code()),
72        Err(e) => {
73            out.error(&e);
74            ExitCode::from(e.exit.code())
75        }
76    }
77}
78
79fn init_tracing(cli: &Cli) {
80    use tracing_subscriber::EnvFilter;
81    let default = match cli.global.verbose {
82        0 => "warn",
83        1 => "warn,paralock=info,paralock_evm=info,paralock_cli=info",
84        _ => "info,paralock=debug,paralock_evm=debug,paralock_cli=debug",
85    };
86    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(default));
87    let _ = tracing_subscriber::fmt()
88        .with_env_filter(filter)
89        .with_writer(std::io::stderr)
90        .with_ansi(std::io::stderr().is_terminal())
91        .with_target(cli.global.verbose > 1)
92        .try_init();
93}
94
95/// Runs a parsed command line.
96pub async fn run(cli: Cli, out: Output) -> CliResult<Exit> {
97    let globals = Globals {
98        devnet: cli.global.devnet,
99        config: cli.global.config.clone(),
100        maker_url: cli.global.maker_url.clone(),
101        evm_account: cli.global.evm_account,
102        lightwalletd: cli.global.lightwalletd,
103    };
104    match cli.command {
105        Command::Version => {
106            out.say(format!(
107                "paralock {} (paralock-cli, protocol v{PROTOCOL_VERSION}; experimental, not audited)",
108                env!("CARGO_PKG_VERSION")
109            ));
110            out.result(serde_json::json!({ "version": env!("CARGO_PKG_VERSION"), "protocol": PROTOCOL_VERSION }));
111            Ok(Exit::Ok)
112        }
113        Command::Doctor(args) => {
114            let role = if args.maker { Role::Maker } else { Role::Taker };
115            let setup = config::load(&globals, role)?;
116            commands::doctor::doctor(&setup, out, args).await
117        }
118        Command::Quote(args) => commands::taker::quote(&config::load(&globals, Role::Taker)?, out, args).await,
119        Command::Swap(args) => commands::taker::swap(&config::load(&globals, Role::Taker)?, out, args).await,
120        Command::Pay(args) => commands::taker::pay(&config::load(&globals, Role::Taker)?, out, args).await,
121        Command::Swaps(args) => commands::taker::swaps(&config::load(&globals, Role::Taker)?, out, args),
122        Command::Resume(args) => commands::taker::resume(&config::load(&globals, Role::Taker)?, out, args).await,
123        Command::Refund(args) => commands::taker::refund(&config::load(&globals, Role::Taker)?, out, args).await,
124        Command::Maker(args) => commands::maker::maker(&config::load(&globals, Role::Maker)?, out, args).await,
125    }
126}