paralock_cli/commands/
mod.rs1pub mod doctor;
4pub mod maker;
5pub mod taker;
6
7use std::sync::Arc;
8use std::time::Duration;
9
10use paralock::builder::{EvmChainOptions, connect_evm_chain};
11use paralock::price::{CoinGecko, CoinGeckoOptions};
12use paralock::{
13 AdaptorEvmClient, EvmChain, EvmClient, FixedPrice, Lightwalletd, LightwalletdOptions, PriceSource, ZcashNode,
14 ZcashRpc,
15};
16
17use crate::config::{Backend, Chain, KeySource, Price, Setup};
18use crate::error::{CliError, CliResult};
19
20pub fn zcash_node(setup: &Setup) -> CliResult<Arc<dyn ZcashNode>> {
22 Ok(match &setup.zcash {
23 Backend::Rpc { url, auth } => {
24 let rpc = ZcashRpc::new(url.clone())?;
25 match auth {
26 Some((user, secret)) => {
27 let (cookie_user, password) = secret.load()?;
28 let user = cookie_user.unwrap_or_else(|| user.clone());
29 Arc::new(rpc.with_auth(user, password.as_str()))
30 }
31 None => Arc::new(rpc),
32 }
33 }
34 Backend::Lightwalletd { url } => Arc::new(Lightwalletd::with_options(url, LightwalletdOptions::default())?),
35 })
36}
37
38pub fn backend_label(setup: &Setup) -> String {
40 match &setup.zcash {
41 Backend::Rpc { url, .. } => format!("Zcash RPC {}", redact(url)),
42 Backend::Lightwalletd { url } => format!("lightwalletd {}", redact(url)),
43 }
44}
45
46pub fn redact(url: &str) -> String {
48 let (scheme, rest) = url.split_once("://").unwrap_or(("", url));
49 let host_and_path = rest.rsplit_once('@').map_or(rest, |(_, h)| h);
50 let host_and_path = host_and_path.split(['?', '#']).next().unwrap_or("");
51 if scheme.is_empty() { host_and_path.to_owned() } else { format!("{scheme}://{host_and_path}") }
52}
53
54pub fn price_source(setup: &Setup) -> CliResult<Arc<dyn PriceSource>> {
56 Ok(match &setup.price {
57 Price::Fixed(price) => Arc::new(FixedPrice(*price)),
58 Price::CoinGecko { url, fallback } => {
59 let mut options = CoinGeckoOptions { fallback: *fallback, ..CoinGeckoOptions::default() };
60 if let Some(url) = url {
61 options.url = url.clone();
62 }
63 Arc::new(CoinGecko::with_options(options)?)
64 }
65 })
66}
67
68pub fn price_label(setup: &Setup) -> String {
70 match &setup.price {
71 Price::Fixed(price) => format!("fixed ${price}/ZEC (not for real funds)"),
72 Price::CoinGecko { url: Some(url), .. } => format!("{} (CoinGecko format)", redact(url)),
73 Price::CoinGecko { url: None, fallback: Some(f) } => format!("CoinGecko (devnet fallback ${f}/ZEC)"),
74 Price::CoinGecko { url: None, fallback: None } => "CoinGecko".to_owned(),
75 }
76}
77
78pub fn evm_options(chain: &Chain, account: &str) -> EvmChainOptions {
80 let mut o = EvmChainOptions::new(chain.rpc.clone(), chain.chain_id, chain.htlc.clone(), account);
81 o.adaptor_escrow = chain.adaptor_escrow.clone();
82 o.from_block = chain.from_block;
83 o.fee_token = chain.fee_token.clone();
84 o.rpc_timeout = chain.rpc_timeout;
85 o
86}
87
88pub async fn connect(
91 chain: &Chain,
92 key: &KeySource,
93 rpc: Option<&str>,
94 receipt_timeout: Option<Duration>,
95) -> CliResult<(Arc<dyn EvmClient>, Option<Arc<dyn AdaptorEvmClient>>)> {
96 let account = key.load_0x()?;
97 let mut options = evm_options(chain, &account);
98 if let Some(rpc) = rpc {
99 options.rpc_url = rpc.to_owned();
100 }
101 options.receipt_timeout = receipt_timeout;
102 connect_evm_chain(chain.chain, &options, &account).await.map_err(|e| {
103 CliError::from(e)
104 .context(format!("{} ({})", chain.chain.display_name(), redact(&options.rpc_url)))
105 .hint("is the RPC reachable, on the configured chain id, with the Paralock contracts? `paralock doctor`")
106 })
107}
108
109pub fn pick_chain(setup: &Setup, chain: Option<EvmChain>) -> CliResult<EvmChain> {
111 if let Some(chain) = chain {
112 setup.chain(chain)?;
113 return Ok(chain);
114 }
115 if setup.chains.contains_key(&EvmChain::Tempo) {
116 return Ok(EvmChain::Tempo);
117 }
118 let mut chains = setup.chains.keys();
119 match (chains.next(), chains.next()) {
120 (Some(only), None) => Ok(*only),
121 _ => {
122 let names: Vec<&str> = setup.chains.keys().map(|c| c.as_str()).collect();
123 Err(CliError::usage(format!("pick a chain with --chain ({})", names.join(", "))))
124 }
125 }
126}
127
128pub async fn shutdown_signal() {
130 #[cfg(unix)]
131 {
132 use tokio::signal::unix::{SignalKind, signal};
133 let mut term = match signal(SignalKind::terminate()) {
134 Ok(term) => term,
135 Err(_) => {
136 let _ = tokio::signal::ctrl_c().await;
137 return;
138 }
139 };
140 tokio::select! {
141 _ = tokio::signal::ctrl_c() => {}
142 _ = term.recv() => {}
143 }
144 }
145 #[cfg(not(unix))]
146 let _ = tokio::signal::ctrl_c().await;
147}
148
149pub const PROBE_TIMEOUT: Duration = Duration::from_secs(5);