soroban_cli/commands/
mod.rs

1use std::str::FromStr;
2
3use async_trait::async_trait;
4use clap::{error::ErrorKind, CommandFactory, FromArgMatches, Parser};
5
6use crate::{config, print::Print, utils::deprecate_message};
7
8pub mod cache;
9pub mod cfg;
10pub mod completion;
11pub mod container;
12pub mod contract;
13pub mod doctor;
14pub mod env;
15pub mod events;
16pub mod fee_stats;
17pub mod fees;
18pub mod global;
19pub mod keys;
20pub mod ledger;
21pub mod message;
22pub mod network;
23pub mod plugin;
24pub mod snapshot;
25pub mod tx;
26pub mod version;
27
28pub mod txn_result;
29
30pub const HEADING_RPC: &str = "Options (RPC)";
31pub const HEADING_ARCHIVE: &str = "Options (Archive)";
32pub const HEADING_GLOBAL: &str = "Options (Global)";
33const ABOUT: &str =
34    "Work seamlessly with Stellar accounts, contracts, and assets from the command line.
35
36- Generate and manage keys and accounts
37- Build, deploy, and interact with contracts
38- Deploy asset contracts
39- Stream events
40- Start local testnets
41- Decode, encode XDR
42- More!
43
44For additional information see:
45
46- Stellar Docs: https://developers.stellar.org
47- Smart Contract Docs: https://developers.stellar.org/docs/build/smart-contracts/overview
48- CLI Docs: https://developers.stellar.org/docs/tools/developer-tools/cli/stellar-cli";
49
50// long_about is shown when someone uses `--help`; short help when using `-h`
51const LONG_ABOUT: &str = "
52
53To get started generate a new identity:
54
55    stellar keys generate alice
56
57Use keys with the `--source` flag in other commands.
58
59Commands that work with contracts are organized under the `contract` subcommand. List them:
60
61    stellar contract --help
62
63Use contracts like a CLI:
64
65    stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- --help
66
67Anything after the `--` double dash (the \"slop\") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the contract schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it:
68
69    stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- hello --to world
70";
71
72#[derive(Parser, Debug)]
73#[command(
74    name = "stellar",
75    about = ABOUT,
76    version = version::long(),
77    long_about = ABOUT.to_string() + LONG_ABOUT,
78    disable_help_subcommand = true,
79)]
80pub struct Root {
81    #[clap(flatten)]
82    pub global_args: global::Args,
83
84    #[command(subcommand)]
85    pub cmd: Cmd,
86}
87
88impl Root {
89    pub fn new() -> Result<Self, Error> {
90        Self::try_parse().map_err(|e| {
91            if std::env::args().any(|s| s == "--list") {
92                let print = Print::new(std::env::args().any(|s| s == "--quiet" || s == "-q"));
93                deprecate_message(print, "--list", "Use `stellar plugin ls` instead.");
94                let _ = plugin::ls::Cmd.run();
95                std::process::exit(0);
96            }
97
98            match e.kind() {
99                ErrorKind::InvalidSubcommand => match plugin::default::run() {
100                    Ok(()) => Error::Clap(e),
101                    Err(e) => Error::PluginDefault(e),
102                },
103                _ => Error::Clap(e),
104            }
105        })
106    }
107
108    pub fn from_arg_matches<I, T>(itr: I) -> Result<Self, clap::Error>
109    where
110        I: IntoIterator<Item = T>,
111        T: Into<std::ffi::OsString> + Clone,
112    {
113        Self::from_arg_matches_mut(&mut Self::command().get_matches_from(itr))
114    }
115
116    pub async fn run(&mut self) -> Result<(), Error> {
117        let print = Print::new(self.global_args.quiet);
118
119        if self.global_args.locator.global {
120            deprecate_message(
121                print,
122                "--global",
123                "Global configuration is now the default behavior.",
124            );
125        }
126
127        match &mut self.cmd {
128            Cmd::Completion(completion) => completion.run(),
129            Cmd::Plugin(plugin) => plugin.run(&self.global_args).await?,
130            Cmd::Contract(contract) => contract.run(&self.global_args).await?,
131            Cmd::Doctor(doctor) => doctor.run(&self.global_args).await?,
132            Cmd::Config(config) => config.run()?,
133            Cmd::Events(events) => events.run().await?,
134            Cmd::Xdr(xdr) => xdr.run()?,
135            Cmd::Strkey(strkey) => strkey.run()?,
136            Cmd::Network(network) => network.run(&self.global_args).await?,
137            Cmd::Container(container) => container.run(&self.global_args).await?,
138            Cmd::Snapshot(snapshot) => snapshot.run(&self.global_args).await?,
139            Cmd::Version(version) => version.run(),
140            Cmd::Keys(id) => id.run(&self.global_args).await?,
141            Cmd::Tx(tx) => tx.run(&self.global_args).await?,
142            Cmd::Ledger(ledger) => ledger.run(&self.global_args).await?,
143            Cmd::Message(message) => message.run(&self.global_args).await?,
144            Cmd::Cache(cache) => cache.run()?,
145            Cmd::Env(env) => env.run(&self.global_args)?,
146            Cmd::Fees(env) => env.run(&self.global_args).await?,
147            Cmd::FeeStats(env) => env.run(&self.global_args).await?,
148        }
149        Ok(())
150    }
151}
152
153impl FromStr for Root {
154    type Err = clap::Error;
155
156    fn from_str(s: &str) -> Result<Self, Self::Err> {
157        Self::from_arg_matches(s.split_whitespace())
158    }
159}
160
161#[derive(Parser, Debug)]
162pub enum Cmd {
163    /// Tools for smart contract developers
164    #[command(subcommand)]
165    Contract(contract::Cmd),
166
167    /// Diagnose and troubleshoot CLI and network issues
168    Doctor(doctor::Cmd),
169
170    /// Watch the network for contract events
171    Events(events::Cmd),
172
173    /// Prints the environment variables
174    ///
175    /// Prints to stdout in a format that can be used as .env file. Environment
176    /// variables have precedence over defaults.
177    ///
178    /// Pass a name to get the value of a single environment variable.
179    ///
180    /// If there are no environment variables in use, prints the defaults.
181    Env(env::Cmd),
182
183    /// Create and manage identities including keys and addresses
184    #[command(subcommand)]
185    Keys(keys::Cmd),
186
187    /// Configure connection to networks
188    #[command(subcommand)]
189    Network(network::Cmd),
190
191    /// Start local networks in containers
192    #[command(subcommand)]
193    Container(container::Cmd),
194
195    /// Manage CLI configuration
196    #[command(subcommand)]
197    Config(cfg::Cmd),
198
199    /// Download a snapshot of a ledger from an archive.
200    #[command(subcommand)]
201    Snapshot(snapshot::Cmd),
202
203    /// Sign, Simulate, and Send transactions
204    #[command(subcommand)]
205    Tx(tx::Cmd),
206
207    /// Decode and encode XDR
208    Xdr(stellar_xdr::cli::Root),
209
210    /// Decode and encode strkey
211    Strkey(stellar_strkey::cli::Root),
212
213    /// Print shell completion code for the specified shell.
214    #[command(long_about = completion::LONG_ABOUT)]
215    Completion(completion::Cmd),
216
217    /// Cache for transactions and contract specs
218    #[command(subcommand)]
219    Cache(cache::Cmd),
220
221    /// Print version information
222    Version(version::Cmd),
223
224    /// The subcommand for CLI plugins
225    #[command(subcommand)]
226    Plugin(plugin::Cmd),
227
228    /// Fetch ledger information
229    #[command(subcommand)]
230    Ledger(ledger::Cmd),
231
232    /// Sign and verify arbitrary messages using SEP-53
233    #[command(subcommand)]
234    Message(message::Cmd),
235
236    /// ⚠️ Deprecated, use `fees stats` instead. Fetch network feestats
237    FeeStats(fee_stats::Cmd),
238
239    /// Fetch network feestats and configure CLI fee settings
240    #[command(subcommand)]
241    Fees(fees::Cmd),
242}
243
244#[derive(thiserror::Error, Debug)]
245pub enum Error {
246    // TODO: stop using Debug for displaying errors
247    #[error(transparent)]
248    Contract(#[from] contract::Error),
249
250    #[error(transparent)]
251    Doctor(#[from] doctor::Error),
252
253    #[error(transparent)]
254    Events(#[from] events::Error),
255
256    #[error(transparent)]
257    Keys(#[from] keys::Error),
258
259    #[error(transparent)]
260    Xdr(#[from] stellar_xdr::cli::Error),
261
262    #[error(transparent)]
263    Strkey(#[from] stellar_strkey::cli::Error),
264
265    #[error(transparent)]
266    Clap(#[from] clap::error::Error),
267
268    #[error(transparent)]
269    Plugin(#[from] plugin::Error),
270
271    #[error(transparent)]
272    PluginDefault(#[from] plugin::default::Error),
273
274    #[error(transparent)]
275    Network(#[from] network::Error),
276
277    #[error(transparent)]
278    Container(#[from] container::Error),
279
280    #[error(transparent)]
281    Config(#[from] cfg::Error),
282
283    #[error(transparent)]
284    Snapshot(#[from] snapshot::Error),
285
286    #[error(transparent)]
287    Tx(#[from] tx::Error),
288
289    #[error(transparent)]
290    Cache(#[from] cache::Error),
291
292    #[error(transparent)]
293    Env(#[from] env::Error),
294
295    #[error(transparent)]
296    Ledger(#[from] ledger::Error),
297
298    #[error(transparent)]
299    Message(#[from] message::Error),
300
301    #[error(transparent)]
302    FeeStats(#[from] fee_stats::Error),
303
304    #[error(transparent)]
305    Fees(#[from] fees::Error),
306}
307
308#[async_trait]
309pub trait NetworkRunnable {
310    type Error;
311    type Result;
312
313    async fn run_against_rpc_server(
314        &self,
315        global_args: Option<&global::Args>,
316        config: Option<&config::Args>,
317    ) -> Result<Self::Result, Self::Error>;
318}