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